diff --git a/.gitattributes b/.gitattributes index d7686b163c..da6c2af8e7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -*.cs diff=csharp +*.cs diff=csharp *.cpp diff=cpp *.h diff=cpp *.idh diff=cpp @@ -31,6 +31,3 @@ Bin/ilrepack-assemblies -whitespace *.json -whitespace *.js -whitespace Src/LexText/ParserCore/ParserCoreTests/**/*.txt -whitespace - -# Use bd merge for beads JSONL files -.beads/issues.jsonl merge=beads diff --git a/.github/AGENTS.md b/.github/AGENTS.md index 47046a19a5..41573dd93a 100644 --- a/.github/AGENTS.md +++ b/.github/AGENTS.md @@ -1,143 +1,45 @@ # FieldWorks Agentic Instructions -## Purpose & Scope -- Give AI coding agents a fast, reliable playbook for FieldWorks—what the repo contains, how to build/test, and how to keep documentation accurate. -- Assume nothing beyond this file and linked instructions; only search the repo when a referenced step fails or is missing. - -See `.github/AI_GOVERNANCE.md` for the documentation taxonomy and “source of truth” rules. - -## Repository Snapshot -- Product: FieldWorks (FLEx) — Windows-first linguistics suite maintained by SIL International. -- Languages & tech: C#, C++/CLI, native C++, WiX, PowerShell, XML, JSON, XAML/WinForms. -- Tooling: Visual Studio 2022 (Desktop workloads), MSBuild Traversal (`FieldWorks.proj`), WiX 3.14.x, NUnit-style tests, Crowdin localization. -- Docs: `ReadMe.md` → https://github.com/sillsdev/FwDocumentation/wiki for deep dives; `.github/src-catalog.md` + per-folder `AGENTS.md` describe Src/ layout. - -## Core Rules -- Prefer `./build.ps1`; avoid ad-hoc project builds that skip traversal ordering. -- Run tests relevant to your change before pushing; do not assume CI coverage. -- For bug fixes, default to TDD (Red-Green-Refactor): write a failing test first, then implement the minimal fix. -- If you need to pause current edits to stay test-first, use `git stash` to hold changes while implementing the tests to fail, then restore with `git stash apply`. -- Keep localization via `.resx` and respect `crowdin.json`; never hardcode translatable strings. -- Avoid COM/registry edits without a test plan. -- Stay within documented tooling—no surprise dependencies or scripts without updating instructions. -- **Terminal commands**: **ALWAYS use `scripts/Agent/` wrapper scripts** for git or file reading requiring pipes/filters. See `.github/instructions/terminal.instructions.md` for the transformation table. - -## Build & Test Essentials -- Prerequisites: install VS 2022 Desktop workloads, WiX 3.14.x (pre-installed on windows-latest), Git, LLVM/clangd + standalone OmniSharp (for Serena C++/C# support), and optional Crowdin CLI only when needed. -- Verify your environment: `.\Build\Agent\Verify-FwDependencies.ps1 -IncludeOptional` -- Common commands: - ```powershell - # Full traversal build (Debug/x64 defaults) - .\build.ps1 - - # Run tests - .\test.ps1 - ``` -- Tests: follow `.github/instructions/testing.instructions.md`; use VS Test Explorer or `vstest.console.exe` for managed tests. -- Installer edits must follow `.github/instructions/installer.instructions.md` plus WiX validation before PR. -- Installer builds: use `.\Build\Agent\Setup-InstallerBuild.ps1 -ValidateOnly` to check prerequisites, `-SetupPatch` for patch builds. - -## Workflow Shortcuts -| Task | Reference | -| --- | --- | -| Build/test rules | `.github/instructions/build.instructions.md`, `.github/instructions/testing.instructions.md` | -| Debugging | `.github/instructions/debugging.instructions.md` | -| Managed / Native / Installer guidance | `.github/instructions/managed.instructions.md`, `.github/instructions/native.instructions.md`, `.github/instructions/installer.instructions.md` | -| Security & PowerShell rules | `.github/instructions/security.instructions.md`, `.github/instructions/powershell.instructions.md` | -| Guidance governance | `.github/AI_GOVERNANCE.md` | -| **Agent wrapper scripts** | `scripts/Agent/` - build, test, and git helpers for auto-approval | -| Prompts & specs | `.github/prompts/*.prompt.md`, `.github/spec-templates/`, `.github/recipes/` | -| Chat modes | `.github/chatmodes/*.chatmode.md` | - -## Instruction & Prompt Expectations -- Instruction files live under `.github/instructions/` with `applyTo`, `name`, and `description` frontmatter only; keep content ≤ 200 lines with Purpose/Scope, Key Rules, Examples. -- Chat modes constrain role-specific behavior (managed/native/installer/technical-writer) and should be referenced when invoking agents. - -**Context7 Guidance:** When requesting API references, code examples, or library-specific patterns, consult Context7 first (for example, call `resolve-library-id` then `get-library-docs` or `search-code`). Prefer the Context7 libraries listed in `.vscode/context7-configuration.json` and include the resolved library ID in your prompt when possible. Context7 lookups are considered safe and are configured for auto-approval in this workspace. - -## AGENTS.md Maintenance -1. **Detect** stale folders: `python .github/detect_copilot_needed.py --strict --base origin/ --json .cache/copilot/detect.json`. -2. **Plan** diffs + reference groups: `python .github/plan_copilot_updates.py --detect-json .cache/copilot/detect.json --out .cache/copilot/diff-plan.json`. -3. **Scaffold** (optional) when a file drifts from the canonical layout: `python .github/scaffold_copilot_markdown.py --folders Src/`. -4. **Apply** the auto change-log from the planner: `python .github/copilot_apply_updates.py --plan .cache/copilot/diff-plan.json --folders Src/`. -5. **Edit narrative sections** using the planner JSON (change counts, commit log, `reference_groups`), keeping human guidance short and linking to subfolder docs where possible. -6. **Validate** with `python .github/check_copilot_docs.py --only-changed --fail` (or use `--paths Src/Foo/AGENTS.md` for targeted checks). -7. When documentation exceeds ~200 lines or acts as a parent index, migrate to `.github/templates/organizational-copilot.template.md` and keep the parent doc as a navigation index. -8. Run `.github/prompts/copilot-folder-review.prompt.md` with the updated plan slice to simulate an agent review before committing. - -## CI & Validation Requirements -- GitHub Actions workflows live under `.github/workflows/`; keep them passing. -- Local parity checks: - ```powershell - # Commit messages (gitlint) - python -m pip install --upgrade gitlint - git fetch origin - gitlint --ignore body-is-missing --commits origin/.. - - # Whitespace - git log --check --pretty=format:"---% h% s" origin/.. - git diff --check --cached - ``` -- Before PRs, ensure: - - Build + relevant tests succeed locally. - - Installer/config changes validated with WiX tooling. - - Analyzer/lint warnings addressed. - -### Build & Test Commands (ALWAYS use the scripts) -```powershell -# Build -.\build.ps1 -.\build.ps1 -Configuration Release -.\build.ps1 -BuildTests - -# Test -.\test.ps1 -.\test.ps1 -TestFilter "TestCategory!=Slow" -.\test.ps1 -TestProject "Src/Common/FwUtils/FwUtilsTests" -.\test.ps1 -NoBuild # Skip build, use existing binaries - -# Both scripts automatically: -# - Clean stale obj/ folders and conflicting processes -# - Set up VS environment -``` - -**DO NOT** use raw `msbuild` directly - let the scripts handle it. - -## Where to Make Changes -- Source: `Src/` contains managed/native projects—mirror existing patterns and keep tests near the code (`Src/.Tests`). -- Installer: `FLExInstaller/` with WiX artifacts. -- Shared headers/libs: `Include/`, `Lib/` (avoid committing large binaries unless policy allows). -- Localization: update `.resx` files; never edit `crowdin.json` unless you understand Crowdin flows. -- Build infrastructure: `Build/` + `Bld/` orchestrate targets/props—change sparingly and document impacts. - -## JIRA Integration - -**LT-prefixed tickets** (e.g., `LT-22382`) are JIRA issues from `https://jira.sil.org/`. - -⚠️ **NEVER browse to `jira.sil.org` URLs** - requires authentication. **ALWAYS use Python scripts:** - -```powershell -# Get issue details (inline Python) -python -c "import sys; sys.path.insert(0, '.github/skills/atlassian-readonly-skills/scripts'); from jira_issues import jira_get_issue; print(jira_get_issue('LT-22382'))" - -# Or export your assigned issues to JSON -python .github/skills/jira-to-beads/scripts/export_jira_assigned.py -``` - -| Scenario | Skill | -|----------|-------| -| Read issue details | `atlassian-readonly-skills` (default) | -| Create/update/comment | `atlassian-skills` (only when user explicitly requests) | -| Bulk import to Beads | `jira-to-beads` | - -See `/AGENTS.md` → "Atlassian / JIRA Skills" section for full configuration and details. - -## Confidence Checklist -- [ ] Prefer traversal builds over per-project compile hacks. -- [ ] Keep coding style aligned with `.editorconfig` and existing patterns. -- [ ] Validate installer/localization changes before PR. -- [ ] Record uncertainties with `FIXME()` and resolve them when evidence is available. -- [ ] Refer back to this guide whenever you need repo-wide ground truth. +Short repo-level instructions for agents. + +## Build and test + +- Build with `.\build.ps1`. +- Test with `.\test.ps1`. +- Use `Build/Agent/` or `scripts/Agent/` wrappers instead of custom shell pipelines where wrappers exist. + +## Documentation model + +- Keep AGENTS guidance minimal and requirement-only. +- Source of prescriptive constraints is `.github/instructions/*.instructions.md`. + +## Serena operating model + +- Use Serena tools first for symbol-aware exploration and edits. + +## Key constraints + +- Preserve native-before-managed build ordering. +- Preserve registration-free COM behavior. +- Keep localization in `.resx`. + +## Jira and issue flow + +- Use GitHub issues/PRs for local issue flow. +- For `LT-` tickets, use the Atlassian skill scripts; do not attempt direct Jira URL browsing. + +## Relevant files + +- `.github/instructions/build.instructions.md` +- `.github/instructions/navigation.instructions.md` +- `.github/instructions/testing.instructions.md` +- `.github/instructions/managed.instructions.md` +- `.github/instructions/native.instructions.md` +- `.github/instructions/installer.instructions.md` +- `.github/rubrics/*.yaml` +- `.github/skills/rubric-verify/SKILL.md` +- `Src/AGENTS.md` +- `FLExInstaller/AGENTS.md` diff --git a/.github/AI_GOVERNANCE.md b/.github/AI_GOVERNANCE.md index 0c8777188b..d826b40e71 100644 --- a/.github/AI_GOVERNANCE.md +++ b/.github/AI_GOVERNANCE.md @@ -34,6 +34,7 @@ Use for: - `debugging.instructions.md` - `installer.instructions.md` - `managed.instructions.md` +- `navigation.instructions.md` - `native.instructions.md` - `powershell.instructions.md` - `repo.instructions.md` diff --git a/.github/check_copilot_docs.py b/.github/check_copilot_docs.py deleted file mode 100644 index 83e93f78f4..0000000000 --- a/.github/check_copilot_docs.py +++ /dev/null @@ -1,417 +0,0 @@ -#!/usr/bin/env python3 -""" -check_copilot_docs.py — Validate Src/**/AGENTS.md against the canonical skeleton - -Checks: -- Frontmatter: last-reviewed, last-reviewed-tree, status -- last-reviewed-tree not FIXME and matches the current git tree hash -- Required headings present -- References entries appear to map to real files in repo (best-effort) - -Usage: - python .github/check_copilot_docs.py [--root ] [--fail] [--json ] [--verbose] - [--only-changed] [--base ] [--head ] [--since ] - -Exit codes: - 0 = no issues - 1 = warnings (non-fatal) and no --fail provided - 2 = failures when --fail provided -""" -import argparse -import json -import os -import re -import sys -import subprocess -from pathlib import Path - -from copilot_tree_hash import compute_folder_tree_hash - -REQUIRED_HEADINGS = [ - "Purpose", - "Architecture", - "Key Components", - "Technology Stack", - "Dependencies", - "Interop & Contracts", - "Threading & Performance", - "Config & Feature Flags", - "Build Information", - "Interfaces and Data Models", - "Entry Points", - "Test Index", - "Usage Hints", - "Related Folders", - "References", -] - -ORGANIZATIONAL_REQUIRED_HEADINGS = [ - "Purpose", - "Subfolder Map", - "When Updating This Folder", - "Related Guidance", -] - -REFERENCE_EXTS = { - ".cs", - ".cpp", - ".cc", - ".c", - ".h", - ".hpp", - ".ixx", - ".xml", - ".xsl", - ".xslt", - ".xsd", - ".dtd", - ".xaml", - ".resx", - ".config", - ".csproj", - ".vcxproj", - ".props", - ".targets", -} - -PLACEHOLDER_PREFIXES = ("tbd",) - - -def find_repo_root(start: Path) -> Path: - p = start.resolve() - while p != p.parent: - if (p / ".git").exists(): - return p - p = p.parent - return start.resolve() - - -def run(cmd, cwd=None): - return subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT).decode( - "utf-8", errors="replace" - ) - - -def git_changed_files( - root: Path, base: str = None, head: str = "HEAD", since: str = None -): - if since: - diff_range = f"{since}..{head}" - elif base: - # Ensure origin/ prefix if a bare branch name is provided - if not base.startswith("origin/") and "/" not in base: - base = f"origin/{base}" - diff_range = f"{base}..{head}" - else: - # Fallback: compare to merge-base with origin/HEAD (best effort) - try: - mb = run(["git", "merge-base", head, "origin/HEAD"], cwd=str(root)).strip() - diff_range = f"{mb}..{head}" - except Exception: - diff_range = f"HEAD~1..{head}" - out = run(["git", "diff", "--name-only", diff_range], cwd=str(root)) - return [l.strip().replace("\\", "/") for l in out.splitlines() if l.strip()] - - -def index_repo_files(root: Path): - index = {} - for dirpath, dirnames, filenames in os.walk(root): - # Skip some big or irrelevant directories - rel = Path(dirpath).relative_to(root) - parts = rel.parts - if parts and parts[0] in { - ".git", - "packages", - "Obj", - "Output", - "Downloads", - }: - continue - for f in filenames: - index.setdefault(f, []).append(os.path.join(dirpath, f)) - return index - - -def parse_frontmatter(text: str): - # Strip UTF-8 BOM if present - if text.startswith("\ufeff"): - text = text[1:] - lines = text.splitlines() - fm = {} - if len(lines) >= 3 and lines[0].strip() == "---": - # Find closing '---' - try: - end_idx = lines[1:].index("---") + 1 - except ValueError: - # Not properly closed; try to find a line that is just '---' - end_idx = -1 - for i in range(1, min(len(lines), 100)): - if lines[i].strip() == "---": - end_idx = i - break - if end_idx == -1: - return None, text - fm_lines = lines[1:end_idx] - body = "\n".join(lines[end_idx + 1 :]) - for l in fm_lines: - l = l.strip() - if not l or l.startswith("#"): - continue - if ":" in l: - k, v = l.split(":", 1) - fm[k.strip()] = v.strip().strip('"') - return fm, body - return None, text - - -def split_sections(text: str): - sections = {} - current = None - buffer = [] - for line in text.splitlines(): - if line.startswith("## "): - if current is not None: - sections[current] = "\n".join(buffer).strip() - current = line[3:].strip() - buffer = [] - else: - if current is not None: - buffer.append(line) - if current is not None: - sections[current] = "\n".join(buffer).strip() - return sections - - -def is_organizational_doc(sections): - return ( - "Subfolder Map" in sections - and "When Updating This Folder" in sections - and "Related Guidance" in sections - ) - - -def extract_references(reference_section: str): - refs = [] - for line in reference_section.splitlines(): - for token in re.split(r"[\s,()]+", line): - if any(token.endswith(ext) for ext in REFERENCE_EXTS): - token = token.rstrip(".,;:") - refs.append(token) - return list(dict.fromkeys(refs)) - - -def maybe_placeholder(text: str) -> bool: - stripped = text.strip() - if not stripped: - return True - lowered = stripped.lower() - return any(lowered.startswith(prefix) for prefix in PLACEHOLDER_PREFIXES) - - -def validate_file(path: Path, repo_index: dict, verbose=False): - result = { - "path": str(path), - "frontmatter": { - "missing": [], - "tree_missing": False, - "tree_placeholder": False, - "tree_value": "", - }, - "headings_missing": [], - "references_missing": [], - "empty_sections": [], - "warnings": [], - "tree_mismatch": False, - "current_tree": "", - "ok": True, - } - text = path.read_text(encoding="utf-8-sig", errors="replace") - fm, body = parse_frontmatter(text) - if not fm: - result["frontmatter"]["missing"] = [ - "last-reviewed", - "last-reviewed-tree", - "status", - ] - result["frontmatter"]["tree_missing"] = True - result["ok"] = False - else: - for key in ["last-reviewed", "last-reviewed-tree", "status"]: - if key not in fm or not fm[key]: - result["frontmatter"]["missing"].append(key) - tree_value = fm.get("last-reviewed-tree", "") - result["frontmatter"]["tree_value"] = tree_value - if not tree_value: - result["frontmatter"]["tree_missing"] = True - result["ok"] = False - elif tree_value.startswith("FIXME"): - result["frontmatter"]["tree_placeholder"] = True - result["ok"] = False - result["warnings"].append( - "last-reviewed-tree placeholder; regenerate frontmatter" - ) - if fm.get("last-verified-commit"): - result["warnings"].append( - "legacy last-verified-commit entry detected; rerun scaffolder" - ) - if result["frontmatter"]["missing"]: - result["ok"] = False - - sections = split_sections(body) - organizational = is_organizational_doc(sections) - required_headings = ( - ORGANIZATIONAL_REQUIRED_HEADINGS if organizational else REQUIRED_HEADINGS - ) - - for h in required_headings: - if h not in sections: - result["headings_missing"].append(h) - if result["headings_missing"]: - result["ok"] = False - - for h in required_headings: - if h in sections: - if maybe_placeholder(sections[h]): - result["empty_sections"].append(h) - if result["empty_sections"]: - for h in result["empty_sections"]: - result["warnings"].append(f"Section '{h}' is empty or placeholder text") - - refs = [] - if not organizational: - refs = extract_references(sections.get("References", "")) - for r in refs: - base = os.path.basename(r) - if base not in repo_index: - result["references_missing"].append(r) - # references_missing doesn't necessarily fail; treat as warning unless all missing - if refs and len(result["references_missing"]) == len(refs): - result["ok"] = False - - if verbose: - print(f"Checked {path}") - return result - - -def main(): - ap = argparse.ArgumentParser() - ap.add_argument("--root", default=str(find_repo_root(Path.cwd()))) - ap.add_argument("--fail", action="store_true", help="Exit non-zero on failures") - ap.add_argument( - "--json", dest="json_out", default=None, help="Write JSON report to file" - ) - ap.add_argument("--verbose", action="store_true") - ap.add_argument( - "--only-changed", - action="store_true", - help="Validate only changed AGENTS.md files", - ) - ap.add_argument( - "--paths", - nargs="*", - help="Specific AGENTS.md paths to validate (relative to repo root)", - ) - ap.add_argument( - "--base", - default=None, - help="Base git ref (e.g., origin/ or branch name)", - ) - ap.add_argument("--head", default="HEAD", help="Head ref (default HEAD)") - ap.add_argument( - "--since", default=None, help="Alternative to base/head: since this ref" - ) - args = ap.parse_args() - - root = Path(args.root).resolve() - src = root / "Src" - if not src.exists(): - print(f"ERROR: Src/ not found under {root}") - return 2 - - repo_index = index_repo_files(root) - - paths_to_check = [] - if args.paths: - for rel in args.paths: - candidate = Path(rel) - if not candidate.is_absolute(): - candidate = root / rel - paths_to_check.append(candidate) - elif args.only_changed: - changed = git_changed_files( - root, base=args.base, head=args.head, since=args.since - ) - for p in changed: - if p.endswith("/AGENTS.md") and p.startswith("Src/"): - paths_to_check.append(root / p) - if not paths_to_check: - paths_to_check = list(src.rglob("AGENTS.md")) - - results = [] - for copath in paths_to_check: - result = validate_file(copath, repo_index, verbose=args.verbose) - rel_parts = Path(result["path"]).relative_to(root).parts - folder_key = "/".join(rel_parts[:-1]) - result["folder"] = folder_key - results.append(result) - - for r in results: - folder_key = r.get("folder") - folder_path = root / folder_key if folder_key else None - if not folder_path or not folder_path.exists(): - r["warnings"].append( - "Folder missing for tree hash computation; verify path" - ) - r["ok"] = False - continue - try: - current_hash = compute_folder_tree_hash(root, folder_path, ref=args.head) - r["current_tree"] = current_hash - except Exception as exc: - r["warnings"].append(f"Unable to compute tree hash: {exc}") - r["ok"] = False - continue - - tree_value = r["frontmatter"].get("tree_value", "") - if tree_value and not tree_value.startswith("FIXME"): - if current_hash != tree_value: - r["tree_mismatch"] = True - r["warnings"].append( - "last-reviewed-tree mismatch with current folder state" - ) - r["ok"] = False - - failures = [r for r in results if not r["ok"]] - print(f"Checked {len(results)} AGENTS.md files. Failures: {len(failures)}") - for r in failures: - print(f"- {r['path']}") - if r["frontmatter"]["missing"]: - print(f" frontmatter missing: {', '.join(r['frontmatter']['missing'])}") - if r["frontmatter"].get("tree_missing"): - print(" last-reviewed-tree missing") - if r["frontmatter"].get("tree_placeholder"): - print(" last-reviewed-tree placeholder; update via scaffolder") - if r.get("tree_mismatch"): - print(" last-reviewed-tree does not match current folder hash") - if r["headings_missing"]: - print(f" headings missing: {', '.join(r['headings_missing'])}") - if r["references_missing"]: - print( - f" unresolved references: {', '.join(r['references_missing'][:10])}{' …' if len(r['references_missing'])>10 else ''}" - ) - warnings = [r for r in results if r["warnings"]] - for r in warnings: - print(f"- WARN {r['path']}: { '; '.join(r['warnings']) }") - - if args.json_out: - with open(args.json_out, "w", encoding="utf-8") as f: - json.dump(results, f, indent=2) - - if args.fail and failures: - return 2 - return 0 if not failures else 1 - - -if __name__ == "__main__": - sys.exit(main()) - diff --git a/.github/copilot_apply_updates.py b/.github/copilot_apply_updates.py deleted file mode 100644 index 6ddc60150e..0000000000 --- a/.github/copilot_apply_updates.py +++ /dev/null @@ -1,163 +0,0 @@ -#!/usr/bin/env python3 -"""Apply planner output to refresh auto sections in AGENTS.md files.""" -from __future__ import annotations - -import argparse -import json -from pathlib import Path -from typing import Dict, List, Optional - -AUTO_START = "" -AUTO_END = "" - - -def read_plan(path: Path) -> Dict[str, object]: - return json.loads(path.read_text(encoding="utf-8")) - - -def select_entries(plan: Dict[str, object], folders: Optional[List[str]]) -> List[Dict[str, object]]: - entries = plan.get("folders", []) - if not folders: - return entries - want = {f.replace("\\", "/") for f in folders} - filtered = [] - for entry in entries: - folder = entry.get("folder") - if folder in want: - filtered.append(entry) - return filtered - - -def build_auto_block(entry: Dict[str, object], heading: str, max_files: int, max_commits: int) -> str: - counts = entry.get("change_counts", {}) - summary = ( - f"Files: {counts.get('total', 0)} (code={counts.get('code', 0)}, tests={counts.get('tests', 0)}, resources={counts.get('resources', 0)})" - ) - risk = entry.get("risk_score", "unknown") - recorded = entry.get("recorded_commit") or entry.get("diff_base") - lines = [AUTO_START, f"## {heading}", "", f"- Snapshot: {recorded}", f"- Risk: {risk}", f"- {summary}"] - - changes = entry.get("changes", []) - if changes: - lines.append("\n### File Highlights") - for change in changes[:max_files]: - status = change.get("status", "?") - rel = change.get("path", "") - kind = change.get("kind", "") - ext = change.get("ext", "") - lines.append(f"- {status} {rel} ({kind or 'code'} {ext})") - if len(changes) > max_files: - lines.append(f"- ... {len(changes) - max_files} more file(s)") - - log_entries = entry.get("commit_log", []) - if log_entries: - lines.append("\n### Recent Commits") - for commit in log_entries[:max_commits]: - short = commit.get("hash", "")[:8] - date = commit.get("date", "") - summary_line = commit.get("summary", "") - lines.append(f"- {short} {date} — {summary_line}") - if len(log_entries) > max_commits: - lines.append(f"- ... {len(log_entries) - max_commits} more commit(s)") - - prompts = entry.get("prompts", {}).get("doc-refresh", []) - if prompts: - lines.append("\n### Prompt seeds") - for prompt in prompts: - lines.append(f"- {prompt}") - - lines.append(AUTO_END) - lines.append("") - return "\n".join(lines) - - -def find_frontmatter_end(text: str) -> int: - lines = text.splitlines(keepends=True) - if not lines or not lines[0].strip().startswith("---"): - return 0 - for idx in range(1, min(len(lines), 200)): - if lines[idx].strip().startswith("---"): - return sum(len(line) for line in lines[: idx + 1]) - return 0 - - -def inject_block(text: str, block: str) -> str: - start = text.find(AUTO_START) - if start != -1: - end = text.find(AUTO_END, start) - if end == -1: - end = start - else: - end += len(AUTO_END) - before = text[:start].rstrip() - after = text[end:].lstrip() - pieces = [before, block, after] - return "\n\n".join(piece for piece in pieces if piece).strip() + "\n" - fm_end = find_frontmatter_end(text) - before = text[:fm_end].rstrip() - after = text[fm_end:].lstrip() - parts = [before, block, after] - return "\n\n".join(part for part in parts if part).strip() + "\n" - - -def apply_auto_block(copath: Path, block: str, dry_run: bool) -> bool: - if not copath.exists(): - return False - original = copath.read_text(encoding="utf-8", errors="replace") - updated = inject_block(original, block) - if updated == original: - return False - if dry_run: - return True - copath.write_text(updated, encoding="utf-8") - return True - - -def main() -> int: - ap = argparse.ArgumentParser(description="Inject auto change-log sections into AGENTS.md files") - ap.add_argument("--root", default=str(Path.cwd())) - ap.add_argument("--plan", default=".cache/copilot/diff-plan.json") - ap.add_argument("--folders", nargs="*", help="Subset of folders to update") - ap.add_argument("--heading", default="Change Log (auto)") - ap.add_argument("--max-files", type=int, default=25) - ap.add_argument("--max-commits", type=int, default=10) - ap.add_argument("--dry-run", action="store_true") - args = ap.parse_args() - - root = Path(args.root).resolve() - plan_path = (root / args.plan) if not Path(args.plan).is_absolute() else Path(args.plan) - if not plan_path.exists(): - print(f"Plan file not found: {plan_path}") - return 1 - - plan = read_plan(plan_path) - entries = select_entries(plan, args.folders) - if not entries: - print("No matching entries to apply.") - return 0 - - applied = 0 - for entry in entries: - copilot_rel = entry.get("copilot_path") - if not copilot_rel: - continue - copath = (root / copilot_rel).resolve() - block = build_auto_block(entry, args.heading, args.max_files, args.max_commits) - changed = apply_auto_block(copath, block, args.dry_run) - if changed: - applied += 1 - action = "(dry run)" if args.dry_run else "" - print(f"Updated {copath} {action}") - else: - print(f"No changes needed for {copath}") - - if args.dry_run: - print(f"Dry run complete. {applied} file(s) would change.") - else: - print(f"Applied auto blocks to {applied} file(s).") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) - diff --git a/.github/copilot_cache.py b/.github/copilot_cache.py deleted file mode 100644 index 4a86f595c2..0000000000 --- a/.github/copilot_cache.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 -"""Cache helpers for COPILOT planning scripts.""" -from __future__ import annotations - -import json -from datetime import datetime, timezone -from pathlib import Path -from typing import Any, Dict, Optional - -ISO_FORMAT = "%Y-%m-%dT%H:%M:%SZ" - - -class CopilotCache: - def __init__(self, repo_root: Path) -> None: - self.repo_root = repo_root - self.cache_root = repo_root / ".cache" / "copilot" - self.diff_dir = self.cache_root / "diffs" - self.cache_root.mkdir(parents=True, exist_ok=True) - self.diff_dir.mkdir(parents=True, exist_ok=True) - - def _path_for_folder(self, folder: str) -> Path: - safe = folder.replace("\\", "/").replace("/", "__") - return self.diff_dir / f"{safe}.json" - - def load_folder(self, folder: str, recorded_tree: str, head_tree: str) -> Optional[Dict[str, Any]]: - path = self._path_for_folder(folder) - if not path.exists(): - return None - try: - data = json.loads(path.read_text(encoding="utf-8")) - except json.JSONDecodeError: - return None - if data.get("recorded_tree") == recorded_tree and data.get("current_tree") == head_tree: - return data - return None - - def save_folder(self, folder: str, payload: Dict[str, Any]) -> None: - payload = dict(payload) - payload["cached_at"] = datetime.now(timezone.utc).strftime(ISO_FORMAT) - path = self._path_for_folder(folder) - path.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8") - - def clear_folder(self, folder: str) -> None: - path = self._path_for_folder(folder) - if path.exists(): - path.unlink() diff --git a/.github/copilot_change_utils.py b/.github/copilot_change_utils.py deleted file mode 100644 index ddbbbf96f0..0000000000 --- a/.github/copilot_change_utils.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 -"""Shared helpers for COPILOT automation scripts.""" -from __future__ import annotations - -import os -from dataclasses import dataclass -from typing import Dict, Iterable, List - -RESOURCE_EXTS = { - ".resx", - ".xaml", - ".xml", - ".xsd", - ".xsl", - ".xslt", - ".config", - ".json", -} - -TEST_TOKENS = ( - "/tests/", - "\\tests\\", - ".tests/", - ".tests\\", - ".tests.", -) - - -@dataclass -class ChangeClassification: - path: str - ext: str - kind: str # code, test, resource, or other - is_test: bool - is_resource: bool - - -def classify_path(rel_path: str) -> ChangeClassification: - """Classify a relative path into buckets used by risk scoring.""" - norm = rel_path.replace("\\", "/") - lower = norm.lower() - ext = os.path.splitext(lower)[1] - is_test = any(token in lower for token in TEST_TOKENS) or lower.endswith("tests.cs") - is_resource = ext in RESOURCE_EXTS - if is_test: - kind = "test" - elif is_resource: - kind = "resource" - else: - kind = "code" - return ChangeClassification(path=norm, ext=ext, kind=kind, is_test=is_test, is_resource=is_resource) - - -def summarize_paths(paths: Iterable[str]) -> Dict[str, int]: - """Return aggregate counts for a collection of relative paths.""" - counts = { - "total": 0, - "code": 0, - "tests": 0, - "resources": 0, - } - for rel in paths: - info = classify_path(rel) - counts["total"] += 1 - if info.kind == "test": - counts["tests"] += 1 - elif info.kind == "resource": - counts["resources"] += 1 - else: - counts["code"] += 1 - return counts - - -def compute_risk_score(counts: Dict[str, int]) -> str: - """Estimate a simple risk level based on change counts.""" - total = counts.get("total", 0) - if total == 0: - return "none" - code_changes = counts.get("code", 0) - test_changes = counts.get("tests", 0) - if code_changes >= 10 or (code_changes >= 5 and test_changes == 0): - return "high" - if total >= 5: - return "medium" - return "low" - - -def classify_with_status(entries: Iterable[str]) -> List[Dict[str, str]]: - """Split "status\tpath" lines into structured dictionaries.""" - results: List[Dict[str, str]] = [] - for raw in entries: - if not raw.strip(): - continue - parts = raw.split("\t", 1) - if len(parts) != 2: - continue - status, rel_path = parts - info = classify_path(rel_path) - results.append( - { - "status": status, - "path": info.path, - "kind": info.kind, - "ext": info.ext, - "is_test": info.is_test, - "is_resource": info.is_resource, - } - ) - return results diff --git a/.github/copilot_doc_utils.py b/.github/copilot_doc_utils.py deleted file mode 100644 index ddd36b6709..0000000000 --- a/.github/copilot_doc_utils.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python3 -"""Shared markdown helpers for COPILOT documents.""" -from __future__ import annotations - -from pathlib import Path -from typing import Tuple - -AUTO_START = "" -AUTO_END = "" -AUTO_PLACEHOLDER = """\n## Change Log (auto)\n\nThis section is populated by running:\n1. `python .github/plan_copilot_updates.py --folders `\n2. `python .github/copilot_apply_updates.py --folders `\n\nDo not edit this block manually; rerun the scripts above after code or doc updates.\n\n""" -AUTO_HINT_HEADING = "## References (auto-generated hints)" - - -def _split_frontmatter(text: str) -> Tuple[str, str]: - if not text.startswith("---\n"): - return "", text - end = text.find("\n---", 3) - if end == -1: - return "", text - end += len("\n---\n") - return text[:end], text[end:] - - -def ensure_auto_change_log_block(text: str) -> Tuple[str, bool]: - if AUTO_START in text and AUTO_END in text: - return text, False - front, body = _split_frontmatter(text) - before = front.strip() - after = body.strip() - pieces = [part for part in (before, AUTO_PLACEHOLDER.strip(), after) if part] - updated = "\n\n".join(pieces) + "\n" - return updated, True - - -def remove_legacy_auto_hint(text: str) -> Tuple[str, bool]: - if AUTO_HINT_HEADING not in text: - return text, False - start = text.find(AUTO_HINT_HEADING) - if start == -1: - return text, False - after_heading = text[start:] - end = after_heading.find("\n## ", len(AUTO_HINT_HEADING)) - if end == -1: - trimmed = text[:start].rstrip() - return (trimmed + "\n").rstrip() + "\n", True - end_index = start + end - updated = (text[:start].rstrip() + "\n\n" + text[end_index:].lstrip()).strip() + "\n" - return updated, True - - -def split_after_auto_block(text: str) -> Tuple[str, str]: - start = text.find(AUTO_START) - if start == -1: - return text, "" - end = text.find(AUTO_END, start) - if end == -1: - return text, "" - end += len(AUTO_END) - newline_idx = text.find("\n", end) - if newline_idx == -1: - newline_idx = len(text) - prefix = text[:newline_idx].rstrip() + "\n\n" - suffix = text[newline_idx:].lstrip() - return prefix, suffix - - -def load_template(path: Path) -> str: - return path.read_text(encoding="utf-8").strip() diff --git a/.github/copilot_tree_hash.py b/.github/copilot_tree_hash.py deleted file mode 100644 index 17434a23b1..0000000000 --- a/.github/copilot_tree_hash.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env python3 -"""Utility helpers for computing deterministic Src/ tree hashes. - -The goal is to capture the set of tracked files under a folder (excluding the -folder's AGENTS.md) and produce a stable digest that represents the code/data -state that documentation was written against. - -We hash the list of files paired with their git blob SHAs at the specified ref -(default HEAD). The working tree is not considered; callers should ensure they -run these helpers on a clean tree or handle dirty-state warnings separately. -""" -from __future__ import annotations - -import hashlib -import subprocess -from pathlib import Path -from typing import Iterable, Tuple - -__all__ = [ - "compute_folder_tree_hash", - "list_tracked_blobs", -] - - -def run(cmd: Iterable[str], cwd: Path) -> str: - """Run a subprocess and return stdout decoded as UTF-8.""" - return subprocess.check_output(cmd, cwd=str(cwd), stderr=subprocess.STDOUT).decode( - "utf-8", errors="replace" - ) - - -def list_tracked_blobs( - root: Path, folder: Path, ref: str = "HEAD" -) -> Iterable[Tuple[str, str]]: - """Yield (relative_path, blob_sha) for tracked files under ``folder``. - - ``ref`` defaults to ``HEAD``. ``folder`` must be inside ``root``. - ``AGENTS.md`` is excluded by design so the hash reflects code/data only. - """ - - rel = folder.relative_to(root).as_posix() - if not rel.startswith("Src/"): - raise ValueError(f"Folder must reside under Src/: {rel}") - - try: - output = run( - [ - "git", - "ls-tree", - "-r", - "--full-tree", - ref, - "--", - rel, - ], - cwd=root, - ) - except subprocess.CalledProcessError as exc: - raise RuntimeError( - f"Failed to list tracked files for {rel}: {exc.output.decode('utf-8', errors='replace')}" - ) from exc - - for line in output.splitlines(): - parts = line.split() - if len(parts) < 4: - continue - _, obj_type, blob_sha, *rest = parts - if obj_type != "blob": - continue - path = rest[-1] - if path.endswith("/AGENTS.md") or path == "AGENTS.md": - continue - yield path, blob_sha - - -def compute_folder_tree_hash(root: Path, folder: Path, ref: str = "HEAD") -> str: - """Compute a stable sha256 digest representing ``folder`` at ``ref``. - - The digest is the sha256 of ``"{relative_path}:{blob_sha}\n"`` for each - tracked file (sorted lexicographically) underneath ``folder`` excluding the - AGENTS.md documentation. When a folder has no tracked files besides - AGENTS.md the digest is the sha256 of the empty string. - """ - - items = sorted(list_tracked_blobs(root, folder, ref)) - digest = hashlib.sha256() - for rel_path, blob_sha in items: - digest.update(f"{rel_path}:{blob_sha}\n".encode("utf-8")) - return digest.hexdigest() - diff --git a/.github/detect_copilot_needed.py b/.github/detect_copilot_needed.py deleted file mode 100644 index 14435a04ce..0000000000 --- a/.github/detect_copilot_needed.py +++ /dev/null @@ -1,259 +0,0 @@ -#!/usr/bin/env python3 -""" -detect_copilot_needed.py — Identify folders with code/config changes that likely require AGENTS.md updates. - -Intended for CI (advisory or failing), and for local pre-commit checks. - -Logic: -- Compute changed files between a base and head ref (or since a rev). -- Consider only changes under Src/** that match code/config extensions. -- Group by top-level folder: Src//... -- For each impacted folder, compare the folder's current git tree hash to the - `last-reviewed-tree` recorded in AGENTS.md and track whether the doc changed. -- Report folders whose hashes no longer match or whose docs are missing. -- Optionally validate changed AGENTS.md files with check_copilot_docs.py. - -Exit codes: - 0 = no issues (either no impacted folders or all have AGENTS.md changes, and validations passed) - 1 = advisory warnings (impacted folders without AGENTS.md updated), when --strict not set - 2 = strict failure when --strict is set and there are issues, or validation fails - -Examples: - python .github/detect_copilot_needed.py --base origin/release/9.3 --head HEAD --strict - python .github/detect_copilot_needed.py --since origin/release/9.3 -""" -import argparse -import json -import os -import subprocess -from pathlib import Path -from typing import Dict, Optional, Tuple - -from copilot_change_utils import compute_risk_score, summarize_paths -from copilot_tree_hash import compute_folder_tree_hash - -CODE_EXTS = { - ".cs", - ".cpp", - ".cc", - ".c", - ".h", - ".hpp", - ".ixx", - ".xml", - ".xsl", - ".xslt", - ".xsd", - ".dtd", - ".xaml", - ".resx", - ".config", - ".csproj", - ".vcxproj", - ".props", - ".targets", -} - - -def run(cmd, cwd=None): - return subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT).decode( - "utf-8", errors="replace" - ) - - -def git_changed_files( - root: Path, base: str = None, head: str = "HEAD", since: str = None -): - if since: - diff_range = f"{since}..{head}" - elif base: - diff_range = f"{base}..{head}" - else: - # Fallback: compare to merge-base with origin/HEAD (best effort) - try: - mb = run(["git", "merge-base", head, "origin/HEAD"], cwd=str(root)).strip() - diff_range = f"{mb}..{head}" - except Exception: - diff_range = f"HEAD~1..{head}" - out = run(["git", "diff", "--name-only", diff_range], cwd=str(root)) - return [l.strip().replace("\\", "/") for l in out.splitlines() if l.strip()] - - -def top_level_src_folder(path: str): - # Expect paths like Src//... - parts = path.split("/") - if len(parts) >= 2 and parts[0] == "Src": - return "/".join(parts[:2]) # Src/Folder - return None - - -def parse_frontmatter(path: Path) -> Tuple[Optional[Dict[str, str]], str]: - if not path.exists(): - return None, "" - text = path.read_text(encoding="utf-8-sig", errors="replace") - lines = text.splitlines() - if len(lines) >= 3 and lines[0].strip() == "---": - end_idx = -1 - for i in range(1, min(len(lines), 200)): - if lines[i].strip() == "---": - end_idx = i - break - if end_idx == -1: - return None, text - fm_lines = lines[1:end_idx] - fm: Dict[str, str] = {} - for l in fm_lines: - l = l.strip() - if not l or l.startswith("#"): - continue - if ":" in l: - k, v = l.split(":", 1) - fm[k.strip()] = v.strip().strip('"') - return fm, "\n".join(lines[end_idx + 1 :]) - return None, "" - - -def main(): - ap = argparse.ArgumentParser() - ap.add_argument("--root", default=str(Path.cwd())) - ap.add_argument( - "--base", default=None, help="Base git ref (e.g., origin/release/9.3)" - ) - ap.add_argument("--head", default="HEAD", help="Head ref (default HEAD)") - ap.add_argument( - "--since", default=None, help="Alternative to base/head: since this ref" - ) - ap.add_argument("--json", dest="json_out", default=None) - ap.add_argument( - "--validate-changed", - action="store_true", - help="Validate changed AGENTS.md with check_copilot_docs.py", - ) - ap.add_argument("--strict", action="store_true", help="Exit non-zero on issues") - args = ap.parse_args() - - root = Path(args.root).resolve() - changed = git_changed_files(root, base=args.base, head=args.head, since=args.since) - - impacted: Dict[str, set] = {} - copilot_changed = set() - for p in changed: - if p.endswith("/AGENTS.md"): - copilot_changed.add(p) - # Only care about Src/** files that look like code/config - if not p.startswith("Src/"): - continue - if p.endswith("/AGENTS.md"): - continue - _, ext = os.path.splitext(p) - if ext.lower() not in CODE_EXTS: - continue - folder = top_level_src_folder(p) - if folder: - impacted.setdefault(folder, set()).add(p) - - results = [] - issues = 0 - for folder, files in sorted(impacted.items()): - copath_rel = f"{folder}/AGENTS.md" - copath = root / copath_rel - folder_path = root / folder - doc_changed = copath_rel in copilot_changed - reasons = [] - recorded_hash: Optional[str] = None - fm, _ = parse_frontmatter(copath) - if not copath.exists(): - reasons.append("AGENTS.md missing") - elif not fm: - reasons.append("frontmatter missing") - else: - recorded_hash = fm.get("last-reviewed-tree") - if not recorded_hash or recorded_hash.startswith("FIXME"): - reasons.append("last-reviewed-tree missing or placeholder") - current_hash: Optional[str] = None - hash_error: Optional[str] = None - if folder_path.exists(): - try: - current_hash = compute_folder_tree_hash( - root, folder_path, ref=args.head - ) - except Exception as exc: # pragma: no cover - diagnostics only - hash_error = str(exc) - reasons.append("unable to compute tree hash") - else: - reasons.append("folder missing at head ref") - - counts = summarize_paths(files) - risk_level = compute_risk_score(counts) - - if current_hash and recorded_hash and current_hash == recorded_hash: - up_to_date = True - else: - up_to_date = False - if current_hash and recorded_hash and current_hash != recorded_hash: - reasons.append("tree hash mismatch") - if not doc_changed and not reasons: - # Defensive catch-all - reasons.append("AGENTS.md not updated") - - entry = { - "folder": folder, - "files_changed": sorted(files), - "copilot_path": copath_rel, - "copilot_changed": doc_changed, - "last_reviewed_tree": recorded_hash, - "current_tree": current_hash, - "status": "OK" if up_to_date else "STALE", - "reasons": reasons, - "change_counts": counts, - "risk_score": risk_level, - } - if hash_error: - entry["hash_error"] = hash_error - if not up_to_date: - issues += 1 - results.append(entry) - - # Optional validation for changed AGENTS.md files - validation_failures = [] - if args.validate_changed and copilot_changed: - try: - cmd = ["python", ".github/check_copilot_docs.py", "--fail"] - # Limit to changed files by setting CWD and relying on script to scan all; keep simple - run(cmd, cwd=str(root)) - except subprocess.CalledProcessError as e: - validation_failures.append(e.output.decode("utf-8", errors="replace")) - issues += 1 - - print(f"Impacted folders: {len(impacted)}") - for e in results: - if e["status"] == "OK": - detail = "hash aligned" - else: - detail = ", ".join(e["reasons"]) if e["reasons"] else "hash mismatch" - extras = "" - if e.get("risk_score"): - extras = f"; risk={e['risk_score']}" - print(f"- {e['folder']}: {e['status']} ({detail}{extras})") - - if validation_failures: - print("\nValidation failures from check_copilot_docs.py:") - for vf in validation_failures: - print(vf) - - if args.json_out: - json_path = Path(args.json_out) - if not json_path.is_absolute(): - json_path = Path.cwd() / json_path - json_path.parent.mkdir(parents=True, exist_ok=True) - with open(json_path, "w", encoding="utf-8") as f: - json.dump({"impacted": results}, f, indent=2) - - if args.strict and issues: - return 2 - return 0 if not issues else 1 - - -if __name__ == "__main__": - raise SystemExit(main()) - diff --git a/.github/instructions/navigation.instructions.md b/.github/instructions/navigation.instructions.md new file mode 100644 index 0000000000..b9b724a18e --- /dev/null +++ b/.github/instructions/navigation.instructions.md @@ -0,0 +1,38 @@ +--- +applyTo: "**/*" +name: "navigation.instructions" +description: "Navigation policy for agentic code discovery and hidden-dependency tasks" +--- + +# Navigation policy (FieldWorks) + +## Purpose & Scope +Define when agents should use structural navigation (Serena symbol/reference tools) versus lexical search, so hidden dependencies are not missed. + +## Core policy +- Use Serena symbol tools before broad file reads for code exploration and edits. +- Treat lexical search (`grep`/keyword search) as a helper, not a complete dependency map. + +## Task classification +- **Semantic task**: wording in the issue maps directly to files/symbols (renames, copy updates, obvious feature toggles). +- **Structural task**: changes likely propagate through inheritance, interfaces, factories, DI/composition, build graph, installer topology, or interop boundaries. +- **Hidden-dependency risk**: issue text is narrow but implementation may cross layers (native/managed boundary, COM/reg-free behavior, parser pipelines, installer chains). + +## Required workflow +1. If structural or hidden-dependency risk is present, run Serena reference/navigation steps before editing: + - locate defining symbol(s), + - enumerate referencing symbols/callers, + - inspect likely boundary files first. +2. Only then proceed to implementation. + +## Veto protocol +- If lexical search returns weak or zero relevant results, do **not** conclude “no dependency.” +- Escalate to structural navigation (symbol references and topology traversal) and continue until likely callers/consumers are inspected. + +## Prompt/checklist placement +- Put navigation checklist items at the **end** of long agent prompts to improve compliance in long-context runs. + +## References +- `.github/instructions/build.instructions.md` +- `.github/instructions/testing.instructions.md` +- `.github/instructions/security.instructions.md` diff --git a/.github/instructions/terminal.instructions.md b/.github/instructions/terminal.instructions.md index 1226d90c3d..927d119e99 100644 --- a/.github/instructions/terminal.instructions.md +++ b/.github/instructions/terminal.instructions.md @@ -31,7 +31,3 @@ Placement policy for wrappers: | `Read-FileContent.ps1` | File reading with filtering | **Build/test**: Run `.\build.ps1` or `.\test.ps1` directly—they're auto-approvable. -## Beads CLI (auto-approvable patterns) -- `br` and `bv --robot-*` commands are auto-approvable **when they do not use pipes, `&&`, or redirection**. -- Prefer `--json`/`--robot` output and parse in the agent instead of piping to `jq`. -- `br sync --flush-only` does **not** run git; you must run git commands separately. \ No newline at end of file diff --git a/.github/migrate_copilot_format.py b/.github/migrate_copilot_format.py deleted file mode 100644 index f549f16075..0000000000 --- a/.github/migrate_copilot_format.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python3 -"""One-time helper to migrate AGENTS.md files to the new auto change-log format.""" -from __future__ import annotations - -import argparse -import re -from pathlib import Path -from typing import List, Tuple - -AUTO_START = "" -AUTO_END = "" -AUTO_PLACEHOLDER = """ -## Change Log (auto) - -This section is populated by running: -1. `python .github/plan_copilot_updates.py --folders ` -2. `python .github/copilot_apply_updates.py --folders ` - -Do not edit this block manually; rerun the scripts above after code or doc updates. - -""" - -AUTO_HINT_HEADING = "## References (auto-generated hints)" -SECTION_RE = re.compile(r"^## ", re.MULTILINE) - - -def find_frontmatter_end(text: str) -> int: - if not text.startswith("---\n"): - return 0 - idx = text.find("\n---", 3) - if idx == -1: - return 0 - return idx + len("\n---\n") - - -def remove_auto_hint_section(text: str) -> Tuple[str, bool]: - if AUTO_HINT_HEADING not in text: - return text, False - start = text.find(AUTO_HINT_HEADING) - if start == -1: - return text, False - match = SECTION_RE.search(text, start + len(AUTO_HINT_HEADING)) - end = match.start() if match else len(text) - updated = (text[:start].rstrip() + "\n\n" + text[end:].lstrip()).strip() + "\n" - return updated, True - - -def ensure_auto_block(text: str) -> Tuple[str, bool]: - if AUTO_START in text and AUTO_END in text: - return text, False - fm_end = find_frontmatter_end(text) - before = text[:fm_end].rstrip() - after = text[fm_end:].lstrip() - pieces = [part for part in [before, AUTO_PLACEHOLDER.strip(), after] if part] - updated = "\n\n".join(pieces) + "\n" - return updated, True - - -def migrate_file(path: Path, dry_run: bool) -> Tuple[bool, List[str]]: - text = path.read_text(encoding="utf-8", errors="replace") - notes: List[str] = [] - text, removed = remove_auto_hint_section(text) - if removed: - notes.append("removed auto hints section") - text, inserted = ensure_auto_block(text) - if inserted: - notes.append("inserted auto change-log placeholder") - if not notes: - return False, [] - if not dry_run: - path.write_text(text, encoding="utf-8") - return True, notes - - -def resolve_folders(root: Path, folders: List[str], include_all: bool) -> List[Path]: - targets = set() - for folder in folders: - rel = folder.replace("\\", "/") - if rel.startswith("Src/"): - targets.add(rel) - else: - abs_path = Path(folder) - if not abs_path.is_absolute(): - abs_path = (root / folder).resolve() - rel = abs_path.relative_to(root).as_posix() - targets.add(rel) - if include_all or not targets: - for copilot in root.glob("Src/**/AGENTS.md"): - targets.add(copilot.parent.relative_to(root).as_posix()) - paths = [] - for rel in sorted(targets): - copilot = root / rel / "AGENTS.md" - if copilot.exists(): - paths.append(copilot) - return paths - - -def main() -> int: - ap = argparse.ArgumentParser(description="Migrate AGENTS.md files to new auto-block format") - ap.add_argument("--root", default=str(Path.cwd())) - ap.add_argument("--folders", nargs="*", default=[], help="Specific Src/ entries") - ap.add_argument("--all", action="store_true", help="Process every AGENTS.md under Src/") - ap.add_argument("--dry-run", action="store_true") - args = ap.parse_args() - - root = Path(args.root).resolve() - paths = resolve_folders(root, args.folders, args.all) - if not paths: - print("No AGENTS.md files matched the criteria.") - return 0 - - changed = 0 - for copilot_path in paths: - updated, notes = migrate_file(copilot_path, args.dry_run) - if updated: - changed += 1 - action = "DRY" if args.dry_run else "UPDATED" - print(f"[{action}] {copilot_path}: {', '.join(notes)}") - else: - print(f"[SKIP] {copilot_path}: already compliant") - - if args.dry_run: - print(f"Dry run complete. {changed} file(s) would change.") - else: - print(f"Migration complete. {changed} file(s) updated.") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) - diff --git a/.github/plan_copilot_updates.py b/.github/plan_copilot_updates.py deleted file mode 100644 index f7300a87d9..0000000000 --- a/.github/plan_copilot_updates.py +++ /dev/null @@ -1,358 +0,0 @@ -#!/usr/bin/env python3 -"""Plan focused AGENTS.md refreshes using cached diffs.""" -from __future__ import annotations - -import argparse -import json -import subprocess -from datetime import datetime, timezone -import os -from pathlib import Path -from typing import Dict, List, Optional, Sequence, Tuple - -FILE_GROUPS = { - "Project files": {".csproj", ".vcxproj", ".props", ".targets"}, - "Key C# files": {".cs"}, - "Key C++ files": {".cpp", ".cc", ".c"}, - "Key headers": {".h", ".hpp", ".ixx"}, - "Data contracts/transforms": { - ".xml", - ".xsl", - ".xslt", - ".xsd", - ".dtd", - ".xaml", - ".resx", - ".config", - }, -} -def collect_reference_groups(folder: Path, root: Path, limit_per_group: int = 25) -> Dict[str, List[str]]: - - groups: Dict[str, List[str]] = {k: [] for k in FILE_GROUPS} - skip = {"obj", "bin", "packages", "output", "downloads"} - for dirpath, dirnames, filenames in os.walk(folder): - rel_parts = {p.lower() for p in Path(dirpath).parts} - if rel_parts & skip: - continue - for filename in filenames: - ext = os.path.splitext(filename)[1].lower() - for group_name, exts in FILE_GROUPS.items(): - if ext in exts: - rel = Path(dirpath, filename).relative_to(root) - groups[group_name].append(str(rel).replace("\\", "/")) - break - for key in groups: - groups[key] = sorted(groups[key])[:limit_per_group] - return groups - -from copilot_cache import CopilotCache -from copilot_change_utils import ( - classify_with_status, - compute_risk_score, - summarize_paths, -) -from copilot_tree_hash import compute_folder_tree_hash - - -def run_git(cmd: Sequence[str], cwd: Path) -> str: - return subprocess.check_output(cmd, cwd=str(cwd), stderr=subprocess.STDOUT).decode( - "utf-8", errors="replace" - ) - - -def parse_frontmatter(path: Path) -> Tuple[Optional[Dict[str, str]], str]: - if not path.exists(): - return None, "" - text = path.read_text(encoding="utf-8-sig", errors="replace") - lines = text.splitlines() - if len(lines) < 3 or lines[0].strip() != "---": - return None, text - end_idx = -1 - for idx in range(1, min(len(lines), 200)): - if lines[idx].strip() == "---": - end_idx = idx - break - if end_idx == -1: - return None, text - fm: Dict[str, str] = {} - for raw in lines[1:end_idx]: - stripped = raw.strip() - if not stripped or stripped.startswith("#"): - continue - if ":" in stripped: - key, value = stripped.split(":", 1) - fm[key.strip()] = value.strip().strip('"') - body = "\n".join(lines[end_idx + 1 :]) - return fm, body - - -def read_detect_json(path: Optional[Path]) -> List[Dict[str, object]]: - if not path: - return [] - data = json.loads(path.read_text(encoding="utf-8")) - return data.get("impacted", []) - - -def find_matching_commit( - root: Path, folder_rel: str, folder_path: Path, target_hash: Optional[str], head: str, limit: int -) -> Optional[str]: - if not target_hash or target_hash.startswith("FIXME"): - return None - try: - revs = run_git( - [ - "git", - "rev-list", - "--max-count", - str(limit), - head, - "--", - folder_rel, - ], - root, - ).splitlines() - except subprocess.CalledProcessError: - return None - for commit in revs: - try: - digest = compute_folder_tree_hash(root, folder_path, ref=commit) - except Exception: - continue - if digest == target_hash: - return commit - return None - - -def determine_fallback_base(root: Path, head: str) -> str: - try: - mb = run_git(["git", "merge-base", head, "origin/HEAD"], root).strip() - if mb: - return mb - except Exception: - pass - return f"{head}~1" - - -def git_diff_lines(root: Path, base: str, head: str, folder_rel: str) -> List[str]: - try: - output = run_git( - [ - "git", - "diff", - "--name-status", - f"{base}..{head}", - "--", - folder_rel, - ], - root, - ) - except subprocess.CalledProcessError as exc: - raise RuntimeError(exc.output.decode("utf-8", errors="replace")) from exc - return [line.strip() for line in output.splitlines() if line.strip()] - - -def git_log(root: Path, base: Optional[str], head: str, folder_rel: str) -> List[Dict[str, str]]: - if base: - rev_range = f"{base}..{head}" - else: - rev_range = head - try: - output = run_git( - [ - "git", - "log", - "--date=iso", - "--pretty=format:%H\t%ad\t%s", - rev_range, - "--", - folder_rel, - ], - root, - ) - except subprocess.CalledProcessError: - return [] - entries = [] - for line in output.splitlines(): - parts = line.split("\t", 2) - if len(parts) != 3: - continue - entries.append({"hash": parts[0], "date": parts[1], "summary": parts[2]}) - return entries - - -def build_prompts(folder: str, counts: Dict[str, int], risk: str) -> Dict[str, List[str]]: - total = counts.get("total", 0) - code = counts.get("code", 0) - tests = counts.get("tests", 0) - resources = counts.get("resources", 0) - summary = ( - f"{total} files changed (code={code}, tests={tests}, resources={resources}); risk={risk}." - ) - return { - "doc-refresh": [ - f"Update AGENTS.md for {folder}. Prioritize Purpose/Architecture sections using planner data.", - f"Highlight API or UI updates, then confirm Usage/Test sections reflect {summary}", - "Finish with verification notes and TODOs for manual testing.", - ] - } - - -def build_plan_entry( - root: Path, - folder_rel: str, - head: str, - fallback_base: str, - cache: CopilotCache, - refresh_cache: bool, - search_limit: int, -) -> Optional[Dict[str, object]]: - folder_path = root / folder_rel - if not folder_path.exists(): - return None - copilot_path = folder_path / "AGENTS.md" - fm, _ = parse_frontmatter(copilot_path) - if fm is None: - fm = {} - recorded_tree = fm.get("last-reviewed-tree") - notes: List[str] = [] - if not recorded_tree or recorded_tree.startswith("FIXME"): - notes.append("missing last-reviewed-tree") - try: - current_tree = compute_folder_tree_hash(root, folder_path, ref=head) - except Exception as exc: - notes.append(f"unable to compute current tree: {exc}") - current_tree = None - - cache_entry: Optional[Dict[str, object]] = None - if not refresh_cache and recorded_tree and current_tree: - cache_entry = cache.load_folder(folder_rel, recorded_tree, current_tree) - if cache_entry: - cache_entry = dict(cache_entry) - cache_entry.setdefault("notes", []).extend(notes) - cache_entry["from_cache"] = True - return cache_entry - - recorded_commit = find_matching_commit( - root, folder_rel, folder_path, recorded_tree, head, search_limit - ) - if not recorded_commit and recorded_tree: - notes.append("recorded hash commit not found (consider increasing --search-limit)") - diff_base = recorded_commit or fallback_base - diff_lines = git_diff_lines(root, diff_base, head, folder_rel) - classified = classify_with_status(diff_lines) - counts = summarize_paths([item["path"] for item in classified]) - status_counts: Dict[str, int] = {} - for item in classified: - status_counts[item["status"]] = status_counts.get(item["status"], 0) + 1 - risk = compute_risk_score(counts) - commit_log = git_log(root, recorded_commit or fallback_base, head, folder_rel) - - entry = { - "folder": folder_rel, - "copilot_path": str(copilot_path.relative_to(root)), - "recorded_tree": recorded_tree, - "current_tree": current_tree, - "recorded_commit": recorded_commit, - "diff_base": diff_base, - "risk_score": risk, - "change_counts": counts, - "status_counts": status_counts, - "changes": classified, - "commit_log": commit_log, - "notes": notes, - "prompts": build_prompts(folder_rel, counts, risk), - "from_cache": False, - "reference_groups": collect_reference_groups(folder_path, root), # Add reference groups - } - cache.save_folder(folder_rel, entry) - return entry - - -def resolve_folders( - root: Path, - detect_entries: List[Dict[str, object]], - folders: Optional[List[str]], - include_all: bool, -) -> List[str]: - targets = set() - if folders: - for folder in folders: - rel = folder.replace("\\", "/") - if rel.startswith("Src/"): - targets.add(rel) - else: - abs_path = Path(folder) - if not abs_path.is_absolute(): - abs_path = (root / folder).resolve() - rel = abs_path.relative_to(root).as_posix() - targets.add(rel) - elif detect_entries: - for entry in detect_entries: - folder = entry.get("folder") - status = entry.get("status") - if folder and status != "OK": - targets.add(str(folder)) - if include_all: - for path in sorted(root.glob("Src/**/AGENTS.md")): - targets.add(path.parent.relative_to(root).as_posix()) - return sorted(targets) - - -def main() -> int: - ap = argparse.ArgumentParser(description="Plan AGENTS.md updates with cached diffs") - ap.add_argument("--root", default=str(Path.cwd())) - ap.add_argument("--head", default="HEAD") - ap.add_argument("--detect-json", help="Path to detect_copilot_needed --json output") - ap.add_argument("--folders", nargs="*", help="Explicit Src/ paths") - ap.add_argument("--all", action="store_true", help="Plan for every AGENTS.md folder") - ap.add_argument("--out", default=".cache/copilot/diff-plan.json") - ap.add_argument("--fallback-base", help="Fallback git ref if recorded hash commit is unknown") - ap.add_argument("--refresh-cache", action="store_true") - ap.add_argument("--search-limit", type=int, default=800, help="Max commits to scan per folder") - args = ap.parse_args() - - root = Path(args.root).resolve() - detect_entries = read_detect_json(Path(args.detect_json)) if args.detect_json else [] - targets = resolve_folders(root, detect_entries, args.folders, args.all) - if not targets: - print("No folders to plan. Provide --folders, --all, or --detect-json input.") - return 0 - - fallback_base = args.fallback_base or determine_fallback_base(root, args.head) - cache = CopilotCache(root) - entries: List[Dict[str, object]] = [] - - for folder in targets: - entry = build_plan_entry( - root, - folder, - args.head, - fallback_base, - cache, - args.refresh_cache, - args.search_limit, - ) - if entry: - entries.append(entry) - print( - f"Planned {folder}: {entry['change_counts'].get('total', 0)} files, risk={entry['risk_score']}" - ) - else: - print(f"Skipped {folder} (unable to build entry)") - - output_path = (root / args.out) if not Path(args.out).is_absolute() else Path(args.out) - output_path.parent.mkdir(parents=True, exist_ok=True) - payload = { - "generated_at": datetime.now(timezone.utc).isoformat(), - "head": args.head, - "fallback_base": fallback_base, - "folders": entries, - } - output_path.write_text(json.dumps(payload, indent=2), encoding="utf-8") - print(f"Wrote plan with {len(entries)} folder(s) to {output_path}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) - diff --git a/.github/rubrics/fieldworks-rubric.base.yaml b/.github/rubrics/fieldworks-rubric.base.yaml new file mode 100644 index 0000000000..db175ed181 --- /dev/null +++ b/.github/rubrics/fieldworks-rubric.base.yaml @@ -0,0 +1,126 @@ +schemaVersion: 2 +name: FieldWorks Change Rubric (Base) +description: Weighted, execution-free review rubric for repository-grounded verification. + +scoring: + criterionScale: + min: 0 + max: 5 + guidance: + "0": missing or incorrect + "3": acceptable + "5": strong + overallFormula: sum(categoryPercent * categoryWeight) + thresholds: + pass: 85 + conditional: 70 + +# focusCriteria semantics: specialization rubrics list focusCriteria ids. +# Focused criteria receive +50% relative weight within their category +# and the scorer should prioritize gathering evidence for them first. +# Non-focused criteria are still scored at their base weight. +focusCriteriaBoost: 1.5 + +requiredCommands: + - .\build.ps1 + - .\test.ps1 + +hardGates: + - id: build_test_gate + rule: All requiredCommands exit 0 for the touched areas. + failureEffect: fail + - id: security_gate + rule: No new unsanitized input path crosses a native, COM, or file-system boundary without validation. + failureEffect: fail + - id: evidence_gate + rule: Each scored criterion cites a concrete evidence artifact (command output, file diff, or explicit N/A rationale). + failureEffect: conditional + +categories: + - id: file_change + weight: 0.25 + criteria: + - id: scope_minimality + weight: 0.30 + check: Every changed file maps to the stated intent. + evidence: diff file list annotated with purpose + - id: repo_conventions + weight: 0.30 + check: Build and test use repository scripts (build.ps1, test.ps1). + evidence: command transcript or CI log + - id: docs_sync + weight: 0.15 + check: Docs/instructions updated if and only if behavior or process changed. + evidence: changed doc paths or explicit N/A + - id: reviewability + weight: 0.25 + check: Changes are partitioned into reviewable, self-contained units. + evidence: commit/PR structure note + + - id: spec_alignment + weight: 0.25 + criteria: + - id: requirements_traceability + weight: 0.30 + check: Each requirement or acceptance signal maps to at least one changed file or test. + evidence: requirement-to-file mapping + - id: acceptance_coverage + weight: 0.25 + check: Every stated acceptance signal has a corresponding verification result. + evidence: acceptance signal checklist with pass/fail + - id: contract_safety + weight: 0.25 + check: Public API signatures and observable behavior are unchanged unless the spec requires it. + evidence: API diff or behavioral comparison note + - id: test_coverage + weight: 0.20 + check: New or changed behavior has at least one corresponding test. + evidence: test names covering the change + + - id: integrity + weight: 0.25 + criteria: + - id: boundary_safety + weight: 0.30 + check: Untrusted input is validated before crossing native, COM, or file-system boundaries. + evidence: input validation code path or N/A rationale + - id: warning_discipline + weight: 0.25 + check: No new compiler or analyzer warnings are introduced. + evidence: build output diff or clean build log + - id: artifact_integrity + weight: 0.25 + check: Generated or runtime artifacts (configs, manifests, resources) are present and well-formed. + evidence: artifact file check or N/A + - id: no_regression + weight: 0.20 + check: Existing tests outside the change scope continue to pass. + evidence: full test suite result summary + + - id: runtime + weight: 0.25 + criteria: + - id: execution_success + weight: 0.45 + check: Touched scenarios execute to completion with expected outcomes. + evidence: test output or manual run transcript + - id: diagnostics_quality + weight: 0.20 + check: Changed code paths emit actionable log/trace output on failure. + evidence: trace output sample or code inspection note + - id: state_verification + weight: 0.20 + check: Post-execution state (DB, files, settings) matches expectations. + evidence: state comparison or N/A rationale + - id: verification_reproducibility + weight: 0.15 + check: Verification steps are repeatable on a clean machine using only repo scripts and documented environment. + evidence: environment assumptions listed + +output: + requiredSections: + - scope + - scoresByCategory + - hardGateResults + - evidenceIndex + - finalVerdict diff --git a/.github/rubrics/installer-deployment.rubric.yaml b/.github/rubrics/installer-deployment.rubric.yaml new file mode 100644 index 0000000000..91d2f226ff --- /dev/null +++ b/.github/rubrics/installer-deployment.rubric.yaml @@ -0,0 +1,25 @@ +extends: ./fieldworks-rubric.base.yaml +name: Installer and Deployment Rubric +specialization: wix-installer-deployment + +requiredCommands: + - .\build.ps1 + - .\scripts\Agent\Invoke-InstallerCheck.ps1 -InstallerType Bundle -Configuration Debug -Platform x64 + +hardGates: + - id: evidence_gate + rule: Installer evidence contains before/after snapshots and diff report for this run. + failureEffect: fail + +focusCriteria: + - acceptance_coverage + - execution_success + - state_verification + +specialChecks: + - id: footprint_verified + check: Install footprint (files/registry/shortcuts/uninstall entries) matches expected result. + evidence: installer snapshot diff + - id: upgrade_uninstall_behavior + check: Upgrade/uninstall behavior for touched packaging paths was validated or explicitly deferred. + evidence: run notes + rationale diff --git a/.github/rubrics/interop-boundary.rubric.yaml b/.github/rubrics/interop-boundary.rubric.yaml new file mode 100644 index 0000000000..cadbfa987a --- /dev/null +++ b/.github/rubrics/interop-boundary.rubric.yaml @@ -0,0 +1,25 @@ +extends: ./fieldworks-rubric.base.yaml +name: Interop Boundary Change Rubric +specialization: native-managed-com-boundary + +requiredCommands: + - .\build.ps1 + - .\test.ps1 -NoBuild + +hardGates: + - id: security_gate + rule: Input validation and marshaling safety are demonstrated for every changed interop surface. + failureEffect: fail + +focusCriteria: + - boundary_safety + - contract_safety + - diagnostics_quality + +specialChecks: + - id: regfree_com_preserved + check: No global COM registration is introduced; reg-free manifests remain valid. + evidence: changed manifests/config + runtime validation note + - id: marshalling_verified + check: Pointer, buffer, and string marshaling in touched interop signatures produce correct results in tests. + evidence: test names exercising interop paths diff --git a/.github/rubrics/parser-grammar.rubric.yaml b/.github/rubrics/parser-grammar.rubric.yaml new file mode 100644 index 0000000000..39ee95531e --- /dev/null +++ b/.github/rubrics/parser-grammar.rubric.yaml @@ -0,0 +1,20 @@ +extends: ./fieldworks-rubric.base.yaml +name: Parser and Grammar Change Rubric +specialization: parsing-morphology-analysis + +requiredCommands: + - .\build.ps1 + - .\test.ps1 -NoBuild -TestFilter "TestCategory!=Slow" + +focusCriteria: + - requirements_traceability + - acceptance_coverage + - verification_reproducibility + +specialChecks: + - id: rule_traceability + check: Each grammar or parsing rule change maps to at least one acceptance example. + evidence: case mapping in verification notes + - id: regression_guard + check: Existing parsing behavior outside the intended scope remains stable. + evidence: targeted regression test outcomes diff --git a/.github/rubrics/ui-build-workflow.rubric.yaml b/.github/rubrics/ui-build-workflow.rubric.yaml new file mode 100644 index 0000000000..fa8dffd001 --- /dev/null +++ b/.github/rubrics/ui-build-workflow.rubric.yaml @@ -0,0 +1,20 @@ +extends: ./fieldworks-rubric.base.yaml +name: UI and Build Workflow Rubric +specialization: ui-managed-build-integration + +requiredCommands: + - .\build.ps1 + - .\test.ps1 -NoBuild + +focusCriteria: + - scope_minimality + - repo_conventions + - test_coverage + +specialChecks: + - id: localization_compliance + check: Translatable UI strings use .resx files + evidence: changed resource file paths + - id: workflow_integrity + check: Build and test workflow remains script-first for touched areas. + evidence: command transcript summary diff --git a/.github/scaffold_copilot_markdown.py b/.github/scaffold_copilot_markdown.py deleted file mode 100644 index 9c98a774fd..0000000000 --- a/.github/scaffold_copilot_markdown.py +++ /dev/null @@ -1,322 +0,0 @@ -#!/usr/bin/env python3 -"""Normalize AGENTS.md files so Copilot agents receive predictable structure. - -Key behaviors (aligned with the detect → plan → draft workflow): - -* Detect impacted `Src/` entries from git diff, or honor `--folders` / `--all`. -* Ensure the YAML frontmatter includes `last-reviewed`, `last-reviewed-tree`, and `status`, - refreshing the tree hash with the latest git data. -* Create and order the canonical section list so humans keep narrative content short. -* Drop legacy "References (auto-generated hints)" blocks and insert the fenced - `` placeholder when missing. -* Leave any unknown/custom sections intact by appending them after the canonical list. - -Usage examples: - -```powershell -python .github/scaffold_copilot_markdown.py --base origin/release/9.3 -python .github/scaffold_copilot_markdown.py --folders Src/Common/Controls Src/Cellar -python .github/scaffold_copilot_markdown.py --all -``` -""" -from __future__ import annotations - -import argparse -import subprocess -from collections import OrderedDict -from pathlib import Path -from typing import Dict, Iterable, List, Optional, Tuple - -import datetime as dt - -from copilot_doc_utils import ensure_auto_change_log_block, remove_legacy_auto_hint -from copilot_tree_hash import compute_folder_tree_hash - -REQUIRED_HEADINGS = [ - "Purpose", - "Architecture", - "Key Components", - "Technology Stack", - "Dependencies", - "Interop & Contracts", - "Threading & Performance", - "Config & Feature Flags", - "Build Information", - "Interfaces and Data Models", - "Entry Points", - "Test Index", - "Usage Hints", - "Related Folders", - "References", -] - -PLACEHOLDER_TEXT = "TBD - populate from code. Use planner output for context." - - -def run(cmd: List[str], cwd: Optional[str] = None) -> str: - return subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT).decode( - "utf-8", errors="replace" - ) - - -def git_changed_files( - root: Path, - base: Optional[str] = None, - head: str = "HEAD", - since: Optional[str] = None, -) -> List[str]: - if since: - diff_range = f"{since}..{head}" - elif base: - diff_range = f"{base}..{head}" - else: - try: - mb = run(["git", "merge-base", head, "origin/HEAD"], cwd=str(root)).strip() - diff_range = f"{mb}..{head}" - except Exception: - diff_range = f"HEAD~1..{head}" - out = run(["git", "diff", "--name-only", diff_range], cwd=str(root)) - return [l.strip().replace("\\", "/") for l in out.splitlines() if l.strip()] - - -def top_level_src_folder(path: str) -> Optional[str]: - parts = path.split("/") - if len(parts) >= 2 and parts[0] == "Src": - return "/".join(parts[:2]) - return None - - -def parse_frontmatter(text: str) -> Tuple[Optional[Dict[str, str]], str]: - # Strip UTF-8 BOM if present - if text.startswith("\ufeff"): - text = text[1:] - lines = text.splitlines() - if len(lines) >= 3 and lines[0].strip() == "---": - end_idx = -1 - for i in range(1, min(len(lines), 200)): - if lines[i].strip() == "---": - end_idx = i - break - if end_idx == -1: - return None, text - fm_lines = lines[1:end_idx] - fm: Dict[str, str] = {} - for l in fm_lines: - l = l.strip() - if not l or l.startswith("#"): - continue - if ":" in l: - k, v = l.split(":", 1) - fm[k.strip()] = v.strip().strip('"') - body = "\n".join(lines[end_idx + 1 :]) - return fm, body - return None, text - - -def render_frontmatter(fm: Dict[str, str]) -> str: - lines = ["---"] - for key in ["last-reviewed", "last-reviewed-tree", "status"]: - value = fm.get(key, "") - lines.append(f"{key}: {value}") - lines.append("---") - return "\n".join(lines) + "\n\n" - - -class CopilotDoc: - def __init__( - self, - text: str, - folder_name: str, - tree_hash: Optional[str], - status: str = "draft", - ): - self.folder_name = folder_name - self.status = status or "draft" - today = dt.date.today().strftime("%Y-%m-%d") - self.tree_hash = tree_hash or "FIXME(set-tree-hash)" - - fm, body = parse_frontmatter(text) - if not fm: - fm = { - "last-reviewed": today, - "last-reviewed-tree": self.tree_hash, - "status": self.status, - } - body = text - else: - if fm.pop("last-verified-commit", None) is not None: - fm.setdefault("last-reviewed-tree", self.tree_hash) - if not fm.get("last-reviewed"): - fm["last-reviewed"] = today - if not fm.get("last-reviewed-tree"): - fm["last-reviewed-tree"] = self.tree_hash - else: - # Always overwrite with the latest computed hash when available. - if tree_hash: - fm["last-reviewed-tree"] = tree_hash - if not fm.get("status"): - fm["status"] = self.status - self.frontmatter = { - "last-reviewed": fm.get("last-reviewed", today), - "last-reviewed-tree": fm.get("last-reviewed-tree", self.tree_hash), - "status": fm.get("status", self.status), - } - - self.title, body_after_title = self._extract_title(body) - self.sections = self._split_sections(body_after_title) - - def _extract_title(self, body: str) -> Tuple[str, str]: - lines = body.splitlines() - title = "" - remainder_start = 0 - for idx, line in enumerate(lines): - if line.startswith("# "): - title = line.strip() - remainder_start = idx + 1 - break - if line.strip(): - # Ignore non-empty pre-title text but preserve remainder - remainder_start = idx - if not title: - title = f"# {self.folder_name} COPILOT summary" - remainder = "\n".join(lines[remainder_start:]) - return title, remainder - - def _split_sections(self, text: str) -> "OrderedDict[str, str]": - sections: "OrderedDict[str, List[str]]" = OrderedDict() - current_heading: Optional[str] = None - buffer: List[str] = [] - lines = text.splitlines() - for line in lines: - if line.startswith("## "): - if current_heading is not None: - sections[current_heading] = buffer - current_heading = line[3:].strip() - buffer = [line] - else: - if current_heading is None: - # Skip stray text before first heading - continue - buffer.append(line) - if current_heading is not None: - sections[current_heading] = buffer - return OrderedDict( - (heading, "\n".join([l.rstrip() for l in lines]).rstrip() + "\n") - for heading, lines in sections.items() - ) - - def ensure_canonical_sections(self): - new_sections: "OrderedDict[str, str]" = OrderedDict() - for heading in REQUIRED_HEADINGS: - if heading in self.sections: - new_sections[heading] = self.sections.pop(heading) - else: - new_sections[heading] = f"## {heading}\n{PLACEHOLDER_TEXT}\n\n" - # Append any leftover, non-canonical sections - for heading, content in self.sections.items(): - new_sections[heading] = content - self.sections = new_sections - - def render(self) -> str: - document = [] - document.append(render_frontmatter(self.frontmatter)) - document.append(f"{self.title}\n\n") - for heading, content in self.sections.items(): - # Ensure each block ends with a blank line - block = content.rstrip() + "\n\n" - document.append(block) - return "".join(document).rstrip() + "\n" - - -def ensure_copilot_doc( - root: Path, folder: Path, status: str, ref: str -) -> Tuple[str, Optional[str]]: - copath = folder / "AGENTS.md" - if copath.exists(): - original = copath.read_text(encoding="utf-8-sig", errors="replace") - else: - original = "" - try: - tree_hash = compute_folder_tree_hash(root, folder, ref=ref) - except Exception as exc: # pragma: no cover - diagnostics only - print(f"WARNING: Unable to compute tree hash for {folder}: {exc}") - tree_hash = None - doc = CopilotDoc(original, folder.name, tree_hash, status=status) - doc.ensure_canonical_sections() - rendered = doc.render() - rendered, _ = remove_legacy_auto_hint(rendered) - rendered, _ = ensure_auto_change_log_block(rendered) - return rendered, tree_hash - - -def main() -> int: - ap = argparse.ArgumentParser() - ap.add_argument("--root", default=str(Path.cwd())) - ap.add_argument("--base", default=None) - ap.add_argument("--head", default="HEAD") - ap.add_argument("--since", default=None) - ap.add_argument( - "--folders", - nargs="*", - default=None, - help="Explicit Src/ paths to update", - ) - ap.add_argument("--status", default="draft") - ap.add_argument( - "--ref", - default=None, - help="Git ref used when computing folder tree hashes (default: head ref)", - ) - ap.add_argument( - "--all", - action="store_true", - help="Normalize every AGENTS.md under Src/ regardless of git changes", - ) - args = ap.parse_args() - - root = Path(args.root).resolve() - - if args.folders: - folders = [] - for f in args.folders: - candidate = Path(f.replace("\\", "/")) - if not candidate.is_absolute(): - candidate = (root / candidate).resolve() - folders.append(candidate) - else: - if args.all: - folders = sorted((path.parent for path in root.glob("Src/**/AGENTS.md"))) - else: - changed = git_changed_files( - root, base=args.base, head=args.head, since=args.since - ) - tops = set() - for path in changed: - if not path.startswith("Src/"): - continue - if path.endswith("/AGENTS.md"): - continue - parts = path.split("/") - if len(parts) >= 2: - tops.add("/".join(parts[:2])) - folders = [root / t for t in sorted(tops)] - - ref = args.ref or args.head - updated = 0 - for folder in folders: - if not folder.exists(): - continue - content, tree_hash = ensure_copilot_doc(root, folder, args.status, ref) - copath = folder / "AGENTS.md" - copath.write_text(content, encoding="utf-8") - suffix = f" (tree {tree_hash})" if tree_hash else "" - print(f"Prepared {copath}{suffix}") - updated += 1 - - print(f"Prepared/updated {updated} AGENTS.md files.") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) - diff --git a/.github/skills/atlassian-readonly-skills/SKILL.md b/.github/skills/atlassian-readonly-skills/SKILL.md index 875dd60ed1..cc61df316b 100644 --- a/.github/skills/atlassian-readonly-skills/SKILL.md +++ b/.github/skills/atlassian-readonly-skills/SKILL.md @@ -42,12 +42,7 @@ python -c "import sys; sys.path.insert(0, '.github/skills/atlassian-readonly-ski python -c "import sys; sys.path.insert(0, '.github/skills/atlassian-readonly-skills/scripts'); from jira_workflow import jira_get_transitions; print(jira_get_transitions('LT-22382'))" ``` -Alternatively, use the CLI helper in `jira-to-beads` skill: - -```powershell -# Export your assigned issues to .cache/jira_assigned.json -python .github/skills/jira-to-beads/scripts/export_jira_assigned.py -``` +Use the script modules in this skill directly. ## Configuration @@ -102,11 +97,7 @@ BITBUCKET_PAT_TOKEN=your_pat_token ### Mode 2: Parameter-Based (Agent Environments) -When deploying skills in Agent environments where environment variables are not available, pass credentials directly to skill functions using the `AtlassianCredentials` object. - -```python -from scripts._common import AtlassianCredentials, check_available_skills -from scripts.jira_issues import jira_get_issue +Alternatively, call the scripts in this skill directly. # Create credentials object credentials = AtlassianCredentials( @@ -114,12 +105,12 @@ credentials = AtlassianCredentials( jira_url="https://your-company.atlassian.net", jira_username="your.email@company.com", jira_api_token="your_api_token", - + # Confluence configuration (optional) confluence_url="https://your-company.atlassian.net/wiki", confluence_username="your.email@company.com", confluence_api_token="your_api_token", - + # Bitbucket configuration (optional) # bitbucket_url="https://bitbucket.your-company.com", # bitbucket_pat_token="your_pat_token" diff --git a/.github/skills/beads/CLAUDE.md b/.github/skills/beads/CLAUDE.md deleted file mode 100644 index f734508f39..0000000000 --- a/.github/skills/beads/CLAUDE.md +++ /dev/null @@ -1,86 +0,0 @@ -# Beads Skill Maintenance Guide - -## Architecture Decisions - -ADRs in `adr/` document key decisions. These are NOT loaded during skill invocation—they're reference material for maintainers making changes. - -| ADR | Decision | -|-----|----------| -| [ADR-0001](adr/0001-bd-prime-as-source-of-truth.md) | Use `bd prime` as CLI reference source of truth | - -## Key Principle: DRY via bd prime - -**NEVER duplicate CLI documentation in SKILL.md or resources.** - -- `bd prime` outputs AI-optimized workflow context -- `bd --help` provides specific usage -- Both auto-update with bd releases - -**SKILL.md should only contain:** -- Decision frameworks (bd vs TodoWrite) -- Prerequisites (install verification) -- Resource index (progressive disclosure) -- Pointers to `bd prime` and `--help` - -## Keeping the Skill Updated - -### When bd releases new version: - -1. **Check for new features**: `bd --help` for new commands -2. **Update SKILL.md frontmatter**: `version: "X.Y.Z"` -3. **Add resources for conceptual features** (agents, gates, chemistry patterns) -4. **Don't add CLI reference** — that's `bd prime`'s job - -### What belongs in resources: - -| Content Type | Belongs in Resources? | Why | -|--------------|----------------------|-----| -| Conceptual frameworks | ✅ Yes | bd prime doesn't explain "when to use" | -| Decision trees | ✅ Yes | Cognitive guidance, not CLI reference | -| Advanced patterns | ✅ Yes | Depth beyond `--help` | -| CLI command syntax | ❌ No | Use `bd --help` | -| Workflow checklists | ❌ No | `bd prime` covers this | - -### Resource update checklist: - -``` -[ ] Check if bd prime now covers this content -[ ] If yes, remove from resources (avoid duplication) -[ ] If no, update resource for new bd version -[ ] Update version compatibility in README.md -``` - -## File Roles - -| File | Purpose | When to Update | -|------|---------|----------------| -| SKILL.md | Entry point, resource index | New features, version bumps | -| README.md | Human docs, installation | Structure changes | -| CLAUDE.md | This file, maintenance guide | Architecture changes | -| adr/*.md | Decision records | When making architectural decisions | -| resources/*.md | Deep-dive guides | New conceptual content | - -## Testing Changes - -After skill updates: - -```bash -# Verify SKILL.md is within token budget -wc -w claude-plugin/skills/beads/SKILL.md # Target: 400-600 words - -# Verify links resolve -# (Manual check: ensure all resource links in SKILL.md exist) - -# Verify bd prime still works -bd prime | head -20 -``` - -## Attribution - -Resources adapted from other sources should include attribution header: - -```markdown -# Resource Title - -> Adapted from [source] -``` diff --git a/.github/skills/beads/README.md b/.github/skills/beads/README.md deleted file mode 100644 index cb5346d3d9..0000000000 --- a/.github/skills/beads/README.md +++ /dev/null @@ -1,121 +0,0 @@ -# Beads Skill for Claude Code - -A comprehensive skill for using [beads](https://github.com/steveyegge/beads) (bd) issue tracking with Claude Code. - -## What This Skill Does - -This skill teaches Claude Code how to use bd effectively for: -- **Multi-session work tracking** - Persistent memory across conversation compactions -- **Dependency management** - Graph-based issue relationships -- **Session handoff** - Writing notes that survive context resets -- **Molecules and wisps** (v0.34.0+) - Reusable work templates and ephemeral workflows - -## Installation - -Copy the `beads/` directory to your Claude Code skills location: - -```bash -# Global installation -cp -r beads ~/.claude/skills/ - -# Or project-local -cp -r beads .claude/skills/ -``` - -## When Claude Uses This Skill - -The skill activates when conversations involve: -- "multi-session", "complex dependencies", "resume after weeks" -- "project memory", "persistent context", "side quest tracking" -- Work that spans multiple days or compaction cycles -- Tasks too complex for simple TodoWrite lists - -## File Structure - -``` -beads/ -├── SKILL.md # Main skill file (Claude reads this first) -├── CLAUDE.md # Maintenance guide for updating the skill -├── README.md # This file (for humans) -├── adr/ # Architectural Decision Records -│ └── 0001-bd-prime-as-source-of-truth.md -└── resources/ # Detailed documentation (loaded on demand) - ├── BOUNDARIES.md # When to use bd vs TodoWrite - ├── CLI_REFERENCE.md # CLI command reference - ├── DEPENDENCIES.md # Dependency semantics (A blocks B vs B blocks A) - ├── INTEGRATION_PATTERNS.md # TodoWrite and other tool integration - ├── ISSUE_CREATION.md # When and how to create issues - ├── MOLECULES.md # Protos, mols, wisps (v0.34.0+) - ├── PATTERNS.md # Common usage patterns - ├── RESUMABILITY.md # Writing notes for post-compaction recovery - ├── STATIC_DATA.md # Using bd for reference databases - ├── TROUBLESHOOTING.md # Common issues and fixes - ├── WORKFLOWS.md # Step-by-step workflow guides - ├── AGENTS.md # Agent bead tracking (v0.40+) - ├── ASYNC_GATES.md # Human-in-the-loop gates - ├── CHEMISTRY_PATTERNS.md # Mol vs Wisp decision tree - └── WORKTREES.md # Parallel development patterns -``` - -## Key Concepts - -### bd vs TodoWrite - -| Use bd when... | Use TodoWrite when... | -|----------------|----------------------| -| Work spans multiple sessions | Single-session tasks | -| Complex dependencies exist | Linear step-by-step work | -| Need to resume after weeks | Just need a quick checklist | -| Knowledge work with fuzzy boundaries | Clear, immediate tasks | - -### The Dependency Direction Trap - -`bd dep add A B` means **"A depends on B"** (B must complete before A can start). - -```bash -# Want: "Setup must complete before Implementation" -bd dep add implementation setup # ✓ CORRECT -# NOT: bd dep add setup implementation # ✗ WRONG -``` - -### Surviving Compaction - -When Claude's context gets compacted, conversation history is lost but bd state survives. Write notes as if explaining to a future Claude with zero context: - -```bash -bd update issue-123 --notes "COMPLETED: JWT auth with RS256 -KEY DECISION: RS256 over HS256 for key rotation -IN PROGRESS: Password reset flow -NEXT: Implement rate limiting" -``` - -## Requirements - -- [bd CLI](https://github.com/steveyegge/beads) installed (`brew install beads`) -- A git repository (bd requires git for sync) -- Initialized database (`bd init` in project root) - -## Version Compatibility - -| Version | Features | -|---------|----------| -| v0.47.0+ | Pull-first sync, resolve-conflicts, dry-run create, gate auto-discovery | -| v0.43.0+ | Full support: agents, gates, worktrees, chemistry patterns | -| v0.40.0+ | Agent beads, async gates, worktree management | -| v0.34.0+ | Molecules, wisps, cross-project dependencies | -| v0.15.0+ | Core: dependencies, notes, status tracking | -| Earlier | Basic functionality, some features missing | - -## Contributing - -This skill is maintained at [github.com/steveyegge/beads](https://github.com/steveyegge/beads) in the `claude-plugin/skills/beads/` directory. - -Issues and PRs welcome for: -- Documentation improvements -- New workflow patterns -- Bug fixes in examples -- Additional troubleshooting scenarios - -## License - -MIT (same as beads) diff --git a/.github/skills/beads/SKILL.md b/.github/skills/beads/SKILL.md deleted file mode 100644 index b1b2271989..0000000000 --- a/.github/skills/beads/SKILL.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -name: beads -description: > - Git-backed issue tracker for multi-session work with dependencies and persistent - memory across conversation compaction. Use when work spans sessions, has blockers, - or needs context recovery after compaction. -allowed-tools: "Read,Bash(bd:*)" -version: "0.47.1" -author: "Steve Yegge " -license: "MIT" ---- - -# Beads - Persistent Task Memory for AI Agents - -Graph-based issue tracker that survives conversation compaction. Provides persistent memory for multi-session work with complex dependencies. - -## bd vs TodoWrite - -| bd (persistent) | TodoWrite (ephemeral) | -|-----------------|----------------------| -| Multi-session work | Single-session tasks | -| Complex dependencies | Linear execution | -| Survives compaction | Conversation-scoped | -| Git-backed, team sync | Local to session | - -**Decision test**: "Will I need this context in 2 weeks?" → YES = bd - -**When to use bd**: -- Work spans multiple sessions or days -- Tasks have dependencies or blockers -- Need to survive conversation compaction -- Exploratory/research work with fuzzy boundaries -- Collaboration with team (git sync) - -**When to use TodoWrite**: -- Single-session linear tasks -- Simple checklist for immediate work -- All context is in current conversation -- Will complete within current session - -## Prerequisites - -```bash -bd --version # Requires v0.47.0+ -``` - -- **bd CLI** installed and in PATH -- **Git repository** (bd requires git for sync) -- **Initialization**: `bd init` run once (humans do this, not agents) - -## CLI Reference - -**Run `bd prime`** for AI-optimized workflow context (auto-loaded by hooks). -**Run `bd --help`** for specific command usage. - -Essential commands: `bd ready`, `bd create`, `bd show`, `bd update`, `bd close`, `bd sync` - -## Session Protocol - -1. `bd ready` — Find unblocked work -2. `bd show ` — Get full context -3. `bd update --status in_progress` — Start work -4. Add notes as you work (critical for compaction survival) -5. `bd close --reason "..."` — Complete task -6. `bd sync` — Persist to git (always run at session end) - -## Advanced Features - -| Feature | CLI | Resource | -|---------|-----|----------| -| Molecules (templates) | `bd mol --help` | [MOLECULES.md](resources/MOLECULES.md) | -| Chemistry (pour/wisp) | `bd pour`, `bd wisp` | [CHEMISTRY_PATTERNS.md](resources/CHEMISTRY_PATTERNS.md) | -| Agent beads | `bd agent --help` | [AGENTS.md](resources/AGENTS.md) | -| Async gates | `bd gate --help` | [ASYNC_GATES.md](resources/ASYNC_GATES.md) | -| Worktrees | `bd worktree --help` | [WORKTREES.md](resources/WORKTREES.md) | - -## Resources - -| Resource | Content | -|----------|---------| -| [BOUNDARIES.md](resources/BOUNDARIES.md) | bd vs TodoWrite detailed comparison | -| [CLI_REFERENCE.md](resources/CLI_REFERENCE.md) | Complete command syntax | -| [DEPENDENCIES.md](resources/DEPENDENCIES.md) | Dependency system deep dive | -| [INTEGRATION_PATTERNS.md](resources/INTEGRATION_PATTERNS.md) | TodoWrite and tool integration | -| [ISSUE_CREATION.md](resources/ISSUE_CREATION.md) | When and how to create issues | -| [MOLECULES.md](resources/MOLECULES.md) | Proto definitions, component labels | -| [PATTERNS.md](resources/PATTERNS.md) | Common usage patterns | -| [RESUMABILITY.md](resources/RESUMABILITY.md) | Compaction survival guide | -| [STATIC_DATA.md](resources/STATIC_DATA.md) | Database schema reference | -| [TROUBLESHOOTING.md](resources/TROUBLESHOOTING.md) | Error handling and fixes | -| [WORKFLOWS.md](resources/WORKFLOWS.md) | Step-by-step workflow patterns | -| [AGENTS.md](resources/AGENTS.md) | Agent bead tracking (v0.40+) | -| [ASYNC_GATES.md](resources/ASYNC_GATES.md) | Human-in-the-loop gates | -| [CHEMISTRY_PATTERNS.md](resources/CHEMISTRY_PATTERNS.md) | Mol vs Wisp decision tree | -| [WORKTREES.md](resources/WORKTREES.md) | Parallel development patterns | - -## Full Documentation - -- **bd prime**: AI-optimized workflow context -- **GitHub**: [github.com/steveyegge/beads](https://github.com/steveyegge/beads) diff --git a/.github/skills/beads/adr/0001-bd-prime-as-source-of-truth.md b/.github/skills/beads/adr/0001-bd-prime-as-source-of-truth.md deleted file mode 100644 index 8ea7996841..0000000000 --- a/.github/skills/beads/adr/0001-bd-prime-as-source-of-truth.md +++ /dev/null @@ -1,59 +0,0 @@ -# ADR-0001: Use bd prime as CLI Reference Source of Truth - -## Status - -Accepted - -## Context - -The beads skill maintained CLI reference documentation in multiple locations: - -- `SKILL.md` inline (~2,000+ words of CLI reference) -- `references/CLI_REFERENCE.md` (~2,363 words) -- Scattered examples throughout resource files - -This created: -- **Duplication**: Same commands documented 2-3 times -- **Drift risk**: Documentation can fall behind bd versions -- **Token overhead**: ~3,000+ tokens loaded even for simple operations - -Meanwhile, bd provides `bd prime` which generates AI-optimized workflow context automatically. - -## Decision - -Use `bd prime` as the single source of truth for CLI commands: - -1. **SKILL.md** contains only value-add content (decision frameworks, cognitive patterns) -2. **CLI reference** points to `bd prime` (auto-loaded by hooks) and `bd --help` -3. **Resources** provide depth for advanced features (molecules, agents, gates) - -## Consequences - -### Positive - -- **Zero maintenance**: CLI docs auto-update with bd versions -- **DRY**: Single source of truth -- **Accurate**: No version drift possible -- **Lighter SKILL.md**: ~500 words vs ~3,300 - -### Negative - -- **Dependency on bd prime format**: If output changes significantly, may need adaptation -- **External tool requirement**: Skill assumes bd is installed - -## Implementation - -Files restructured: -- `SKILL.md` — Reduced from 3,306 to ~500 words -- `references/` → `resources/` — Directory rename for consistency -- New resources added: `agents.md`, `async-gates.md`, `chemistry-patterns.md`, `worktrees.md` -- Existing resources preserved with path updates - -## Related - -- Claude Code skill progressive disclosure guidelines -- Similar pattern implemented in other Claude Code skill ecosystems - -## Date - -2025-01-02 diff --git a/.github/skills/beads/resources/AGENTS.md b/.github/skills/beads/resources/AGENTS.md deleted file mode 100644 index 807af0b051..0000000000 --- a/.github/skills/beads/resources/AGENTS.md +++ /dev/null @@ -1,62 +0,0 @@ -# Agent Beads - -> Adapted from ACF beads skill - -**v0.40+**: First-class support for agent tracking via `type=agent` beads. - -## When to Use Agent Beads - -| Scenario | Agent Bead? | Why | -|----------|-------------|-----| -| Multi-agent orchestration | Yes | Track state, assign work via slots | -| Single Claude session | No | Overkill—just use regular beads | -| Long-running background agents | Yes | Heartbeats enable liveness detection | -| Role-based agent systems | Yes | Role beads define agent capabilities | - -## Bead Types - -| Type | Purpose | Has Slots? | -|------|---------|------------| -| `agent` | AI agent tracking | Yes (hook, role) | -| `role` | Role definitions for agents | No | - -Other types (`task`, `bug`, `feature`, `epic`) remain unchanged. - -## State Machine - -Agent beads track state for coordination: - -``` -idle → spawning → running/working → done → idle - ↓ - stuck → (needs intervention) -``` - -**Key states**: `idle`, `spawning`, `running`, `working`, `stuck`, `done`, `stopped`, `dead` - -The `dead` state is set by Witness (monitoring system) via heartbeat timeout—agents don't set this themselves. - -## Slot Architecture - -Slots are named references from agent beads to other beads: - -| Slot | Cardinality | Purpose | -|------|-------------|---------| -| `hook` | 0..1 | Current work attached to agent | -| `role` | 1 | Role definition bead (required) | - -**Why slots?** They enforce constraints (one work item at a time) and enable queries like "what is agent X working on?" or "which agent has this work?" - -## Monitoring Integration - -Agent beads enable: - -- **Witness System**: Monitors agent health via heartbeats -- **State Coordination**: ZFC-compliant state machine for multi-agent systems -- **Work Attribution**: Track which agent owns which work - -## CLI Reference - -Run `bd agent --help` for state/heartbeat/show commands. -Run `bd slot --help` for set/clear/show commands. -Run `bd create --help` for `--type=agent` and `--type=role` options. diff --git a/.github/skills/beads/resources/ASYNC_GATES.md b/.github/skills/beads/resources/ASYNC_GATES.md deleted file mode 100644 index 526ac6ca45..0000000000 --- a/.github/skills/beads/resources/ASYNC_GATES.md +++ /dev/null @@ -1,168 +0,0 @@ -# Async Gates for Workflow Coordination - -> Adapted from ACF beads skill - -`bd gate` provides async coordination primitives for cross-session and external-condition workflows. Gates are **wisps** (ephemeral issues) that block until a condition is met. - ---- - -## Gate Types - -| Type | Await Syntax | Use Case | -|------|--------------|----------| -| Human | `human:` | Cross-session human approval | -| CI | `gh:run:` | Wait for GitHub Actions completion | -| PR | `gh:pr:` | Wait for PR merge/close | -| Timer | `timer:` | Deployment propagation delay | -| Mail | `mail:` | Wait for matching email | - ---- - -## Creating Gates - -```bash -# Human approval gate -bd gate create --await human:deploy-approval \ - --title "Approve production deploy" \ - --timeout 4h - -# CI gate (GitHub Actions) -bd gate create --await gh:run:123456789 \ - --title "Wait for CI" \ - --timeout 30m - -# PR merge gate -bd gate create --await gh:pr:42 \ - --title "Wait for PR approval" \ - --timeout 24h - -# Timer gate (deployment propagation) -bd gate create --await timer:15m \ - --title "Wait for deployment propagation" -``` - -**Required options**: -- `--await ` — Gate condition (see types above) -- `--timeout ` — Recommended: prevents forever-open gates - -**Optional**: -- `--title ` — Human-readable description -- `--notify ` — Email/beads addresses to notify - ---- - -## Monitoring Gates - -```bash -bd gate list # All open gates -bd gate list --all # Include closed -bd gate show # Details for specific gate -bd gate eval # Auto-close elapsed/completed gates -bd gate eval --dry-run # Preview what would close -``` - -**Auto-close behavior** (`bd gate eval`): -- `timer:*` — Closes when duration elapsed -- `gh:run:*` — Checks GitHub API, closes on success/failure -- `gh:pr:*` — Checks GitHub API, closes on merge/close -- `human:*` — Requires explicit `bd gate approve` - ---- - -## Closing Gates - -```bash -# Human gates require explicit approval -bd gate approve -bd gate approve --comment "Reviewed and approved by Steve" - -# Manual close (any gate) -bd gate close -bd gate close --reason "No longer needed" - -# Auto-close via evaluation -bd gate eval -``` - ---- - -## Best Practices - -1. **Always set timeouts**: Prevents forever-open gates - ```bash - bd gate create --await human:... --timeout 24h - ``` - -2. **Clear titles**: Title should indicate what's being gated - ```bash - --title "Approve Phase 2: Core Implementation" - ``` - -3. **Eval periodically**: Run at session start to close elapsed gates - ```bash - bd gate eval - ``` - -4. **Clean up obsolete gates**: Close gates that are no longer needed - ```bash - bd gate close --reason "superseded by new approach" - ``` - -5. **Check before creating**: Avoid duplicate gates - ```bash - bd gate list | grep "spec-myfeature" - ``` - ---- - -## Gates vs Issues - -| Aspect | Gates (Wisp) | Issues | -|--------|--------------|--------| -| Persistence | Ephemeral (not synced) | Permanent (synced to git) | -| Purpose | Block on external condition | Track work items | -| Lifecycle | Auto-close when condition met | Manual close | -| Visibility | `bd gate list` | `bd list` | -| Use case | CI, approval, timers | Tasks, bugs, features | - -Gates are designed to be temporary coordination primitives—they exist only until their condition is satisfied. - ---- - -## Troubleshooting - -### Gate won't close - -```bash -# Check gate details -bd gate show - -# For gh:run gates, verify the run exists -gh run view - -# Force close if stuck -bd gate close --reason "manual override" -``` - -### Can't find gate ID - -```bash -# List all gates (including closed) -bd gate list --all - -# Search by title pattern -bd gate list | grep "Phase 2" -``` - -### CI run ID detection fails - -```bash -# Check GitHub CLI auth -gh auth status - -# List runs manually -gh run list --branch - -# Use specific workflow -gh run list --workflow ci.yml --branch -``` diff --git a/.github/skills/beads/resources/BOUNDARIES.md b/.github/skills/beads/resources/BOUNDARIES.md deleted file mode 100644 index c98f56408e..0000000000 --- a/.github/skills/beads/resources/BOUNDARIES.md +++ /dev/null @@ -1,469 +0,0 @@ -# Boundaries: When to Use bd vs TodoWrite - -This reference provides detailed decision criteria for choosing between bd issue tracking and TodoWrite for task management. - -## Contents - -- [The Core Question](#the-core-question) -- [Decision Matrix](#decision-matrix) - - [Use bd for](#use-bd-for): Multi-Session Work, Complex Dependencies, Knowledge Work, Side Quests, Project Memory - - [Use TodoWrite for](#use-todowrite-for): Single-Session Tasks, Linear Execution, Immediate Context, Simple Tracking -- [Detailed Comparison](#detailed-comparison) -- [Integration Patterns](#integration-patterns) - - Pattern 1: bd as Strategic, TodoWrite as Tactical - - Pattern 2: TodoWrite as Working Copy of bd - - Pattern 3: Transition Mid-Session -- [Real-World Examples](#real-world-examples) - - Strategic Document Development, Simple Feature Implementation, Bug Investigation, Refactoring with Dependencies -- [Common Mistakes](#common-mistakes) - - Using TodoWrite for multi-session work, using bd for simple tasks, not transitioning when complexity emerges, creating too many bd issues, never using bd -- [The Transition Point](#the-transition-point) -- [Summary Heuristics](#summary-heuristics) - -## The Core Question - -**"Could I resume this work after 2 weeks away?"** - -- If bd would help you resume → **use bd** -- If markdown skim would suffice → **TodoWrite is fine** - -This heuristic captures the essential difference: bd provides structured context that persists across long gaps, while TodoWrite excels at immediate session tracking. - -## Decision Matrix - -### Use bd for: - -#### Multi-Session Work -Work spanning multiple compaction cycles or days where context needs to persist. - -**Examples:** -- Strategic document development requiring research across multiple sessions -- Feature implementation split across several coding sessions -- Bug investigation requiring experimentation over time -- Architecture design evolving through multiple iterations - -**Why bd wins**: Issues capture context that survives compaction. Return weeks later and see full history, design decisions, and current status. - -#### Complex Dependencies -Work with blockers, prerequisites, or hierarchical structure. - -**Examples:** -- OAuth integration requiring database setup, endpoint creation, and frontend changes -- Research project with multiple parallel investigation threads -- Refactoring with dependencies between different code areas -- Migration requiring sequential steps in specific order - -**Why bd wins**: Dependency graph shows what's blocking what. `bd ready` automatically surfaces unblocked work. No manual tracking required. - -#### Knowledge Work -Tasks with fuzzy boundaries, exploration, or strategic thinking. - -**Examples:** -- Architecture decision requiring research into frameworks and trade-offs -- API design requiring research into multiple options -- Performance optimization requiring measurement and experimentation -- Documentation requiring understanding system architecture - -**Why bd wins**: `design` and `acceptance_criteria` fields capture evolving understanding. Issues can be refined as exploration reveals more information. - -#### Side Quests -Exploratory work that might pause the main task. - -**Examples:** -- During feature work, discover a better pattern worth exploring -- While debugging, notice related architectural issue -- During code review, identify potential improvement -- While writing tests, find edge case requiring research - -**Why bd wins**: Create issue with `discovered-from` dependency, pause main work safely. Context preserved for both tracks. Resume either one later. - -#### Project Memory -Need to resume work after significant time with full context. - -**Examples:** -- Open source contributions across months -- Part-time projects with irregular schedule -- Complex features split across sprints -- Research projects with long investigation periods - -**Why bd wins**: Git-backed database persists indefinitely. All context, decisions, and history available on resume. No relying on conversation scrollback or markdown files. - ---- - -### Use TodoWrite for: - -#### Single-Session Tasks -Work that completes within current conversation. - -**Examples:** -- Implementing a single function based on clear spec -- Fixing a bug with known root cause -- Adding unit tests for existing code -- Updating documentation for recent changes - -**Why TodoWrite wins**: Simple checklist is perfect for linear execution. No need for persistence or dependencies. Clear completion within session. - -#### Linear Execution -Straightforward step-by-step tasks with no branching. - -**Examples:** -- Database migration with clear sequence -- Deployment checklist -- Code style cleanup across files -- Dependency updates following upgrade guide - -**Why TodoWrite wins**: Steps are predetermined and sequential. No discovery, no blockers, no side quests. Just execute top to bottom. - -#### Immediate Context -All information already in conversation. - -**Examples:** -- User provides complete spec and asks for implementation -- Bug report with reproduction steps and fix approach -- Refactoring request with clear before/after vision -- Config changes based on user preferences - -**Why TodoWrite wins**: No external context to track. Everything needed is in current conversation. TodoWrite provides user visibility, nothing more needed. - -#### Simple Tracking -Just need a checklist to show progress to user. - -**Examples:** -- Breaking down implementation into visible steps -- Showing validation workflow progress -- Demonstrating systematic approach -- Providing reassurance work is proceeding - -**Why TodoWrite wins**: User wants to see thinking and progress. TodoWrite is visible in conversation. bd is invisible background structure. - ---- - -## Detailed Comparison - -| Aspect | bd | TodoWrite | -|--------|-----|-----------| -| **Persistence** | Git-backed, survives compaction | Session-only, lost after conversation | -| **Dependencies** | Graph-based, automatic ready detection | Manual, no automatic tracking | -| **Discoverability** | `bd ready` surfaces work | Scroll conversation for todos | -| **Complexity** | Handles nested epics, blockers | Flat list only | -| **Visibility** | Background structure, not in conversation | Visible to user in chat | -| **Setup** | Requires `.beads/` directory in project | Always available | -| **Best for** | Complex, multi-session, explorative | Simple, single-session, linear | -| **Context capture** | Design notes, acceptance criteria, links | Just task description | -| **Evolution** | Issues can be updated, refined over time | Static once written | -| **Audit trail** | Full history of changes | Only visible in conversation | - -## Integration Patterns - -bd and TodoWrite can coexist effectively in a session. Use both strategically. - -### Pattern 1: bd as Strategic, TodoWrite as Tactical - -**Setup:** -- bd tracks high-level issues and dependencies -- TodoWrite tracks current session's execution steps - -**Example:** -``` -bd issue: "Implement user authentication" (epic) - ├─ Child issue: "Create login endpoint" - ├─ Child issue: "Add JWT token validation" ← Currently working on this - └─ Child issue: "Implement logout" - -TodoWrite (for JWT validation): -- [ ] Install JWT library -- [ ] Create token validation middleware -- [ ] Add tests for token expiry -- [ ] Update API documentation -``` - -**When to use:** -- Complex features with clear implementation steps -- User wants to see current progress but larger context exists -- Multi-session work currently in single-session execution phase - -### Pattern 2: TodoWrite as Working Copy of bd - -**Setup:** -- Start with bd issue containing full context -- Create TodoWrite checklist from bd issue's acceptance criteria -- Update bd as TodoWrite items complete - -**Example:** -``` -Session start: -- Check bd: "issue-auth-42: Add JWT token validation" is ready -- Extract acceptance criteria into TodoWrite -- Mark bd issue as in_progress -- Work through TodoWrite items -- Update bd design notes as you learn -- When TodoWrite completes, close bd issue -``` - -**When to use:** -- bd issue is ready but execution is straightforward -- User wants visible progress tracking -- Need structured approach to larger issue - -### Pattern 3: Transition Mid-Session - -**From TodoWrite to bd:** - -Recognize mid-execution that work is more complex than anticipated. - -**Trigger signals:** -- Discovering blockers or dependencies -- Realizing work won't complete this session -- Finding side quests or related issues -- Needing to pause and resume later - -**How to transition:** -``` -1. Create bd issue with current TodoWrite content -2. Note: "Discovered this is multi-session work during implementation" -3. Add dependencies as discovered -4. Keep TodoWrite for current session -5. Update bd issue before session ends -6. Next session: resume from bd, create new TodoWrite if needed -``` - -**From bd to TodoWrite:** - -Rare, but happens when bd issue turns out simpler than expected. - -**Trigger signals:** -- All context already clear -- No dependencies discovered -- Can complete within session -- User wants execution visibility - -**How to transition:** -``` -1. Keep bd issue for historical record -2. Create TodoWrite from issue description -3. Execute via TodoWrite -4. Close bd issue when done -5. Note: "Completed in single session, simpler than expected" -``` - -## Real-World Examples - -### Example 1: Database Migration Planning - -**Scenario**: Planning migration from MySQL to PostgreSQL for production application. - -**Why bd**: -- Multi-session work across days/weeks -- Fuzzy boundaries - scope emerges through investigation -- Side quests - discover schema incompatibilities requiring refactoring -- Dependencies - can't migrate data until schema validated -- Project memory - need to resume after interruptions - -**bd structure**: -``` -db-epic: "Migrate production database to PostgreSQL" - ├─ db-1: "Audit current MySQL schema and queries" - ├─ db-2: "Research PostgreSQL equivalents for MySQL features" (blocks schema design) - ├─ db-3: "Design PostgreSQL schema with type mappings" - └─ db-4: "Create migration scripts and test data integrity" (blocked by db-3) -``` - -**TodoWrite role**: None initially. Might use TodoWrite for single-session testing sprints once migration scripts ready. - -### Example 2: Simple Feature Implementation - -**Scenario**: Add logging to existing endpoint based on clear specification. - -**Why TodoWrite**: -- Single session work -- Linear execution - add import, call logger, add test -- All context in user message -- Completes within conversation - -**TodoWrite**: -``` -- [ ] Import logging library -- [ ] Add log statements to endpoint -- [ ] Add test for log output -- [ ] Run tests -``` - -**bd role**: None. Overkill for straightforward task. - -### Example 3: Bug Investigation - -**Initial assessment**: Seems simple, try TodoWrite first. - -**TodoWrite**: -``` -- [ ] Reproduce bug -- [ ] Identify root cause -- [ ] Implement fix -- [ ] Add regression test -``` - -**What actually happens**: Reproducing bug reveals it's intermittent. Root cause investigation shows multiple potential issues. Needs time to investigate. - -**Transition to bd**: -``` -Create bd issue: "Fix intermittent auth failure in production" - - Description: Initially seemed simple but reproduction shows complex race condition - - Design: Three potential causes identified, need to test each - - Created issues for each hypothesis with discovered-from dependency - -Pause for day, resume next session from bd context -``` - -### Example 4: Refactoring with Dependencies - -**Scenario**: Extract common validation logic from three controllers. - -**Why bd**: -- Dependencies - must extract before modifying callers -- Multi-file changes need coordination -- Potential side quest - might discover better pattern during extraction -- Need to track which controllers updated - -**bd structure**: -``` -refactor-1: "Create shared validation module" - → blocks refactor-2, refactor-3, refactor-4 - -refactor-2: "Update auth controller to use shared validation" -refactor-3: "Update user controller to use shared validation" -refactor-4: "Update payment controller to use shared validation" -``` - -**TodoWrite role**: Could use TodoWrite for individual controller updates as implementing. - -**Why this works**: bd ensures you don't forget to update a controller. `bd ready` shows next available work. Dependencies prevent starting controller update before extraction complete. - -## Common Mistakes - -### Mistake 1: Using TodoWrite for Multi-Session Work - -**What happens**: -- Next session, forget what was done -- Scroll conversation history to reconstruct -- Lose design decisions made during implementation -- Start over or duplicate work - -**Solution**: Create bd issue instead. Persist context across sessions. - -### Mistake 2: Using bd for Simple Linear Tasks - -**What happens**: -- Overhead of creating issue not justified -- User can't see progress in conversation -- Extra tool use for no benefit - -**Solution**: Use TodoWrite. It's designed for exactly this case. - -### Mistake 3: Not Transitioning When Complexity Emerges - -**What happens**: -- Start with TodoWrite for "simple" task -- Discover blockers and dependencies mid-way -- Keep using TodoWrite despite poor fit -- Lose context when conversation ends - -**Solution**: Transition to bd when complexity signal appears. Not too late mid-session. - -### Mistake 4: Creating Too Many bd Issues - -**What happens**: -- Every tiny task gets an issue -- Database cluttered with trivial items -- Hard to find meaningful work in `bd ready` - -**Solution**: Reserve bd for work that actually benefits from persistence. Use "2 week test" - would bd help resume after 2 weeks? If no, skip it. - -### Mistake 5: Never Using bd Because TodoWrite is Familiar - -**What happens**: -- Multi-session projects become markdown swamps -- Lose track of dependencies and blockers -- Can't resume work effectively -- Rotten half-implemented plans - -**Solution**: Force yourself to use bd for next multi-session project. Experience the difference in organization and resumability. - -### Mistake 6: Always Asking Before Creating Issues (or Never Asking) - -**When to create directly** (no user question needed): -- **Bug reports**: Clear scope, specific problem ("Found: auth doesn't check profile permissions") -- **Research tasks**: Investigative work ("Research workaround for Slides export") -- **Technical TODOs**: Discovered during implementation ("Add validation to form handler") -- **Side quest capture**: Discoveries that need tracking ("Issue: MCP can't read Shared Drive files") - -**Why create directly**: Asking slows discovery capture. User expects proactive issue creation for clear-cut problems. - -**When to ask first** (get user input): -- **Strategic work**: Fuzzy boundaries, multiple valid approaches ("Should we implement X or Y pattern?") -- **Potential duplicates**: Might overlap with existing work -- **Large epics**: Multiple approaches, unclear scope ("Plan migration strategy") -- **Major scope changes**: Changing direction of existing issue - -**Why ask**: Ensures alignment on fuzzy work, prevents duplicate effort, clarifies scope before investment. - -**Rule of thumb**: If you can write a clear, specific issue title and description in one sentence, create directly. If you need user input to clarify the work, ask first. - -**Examples**: -- ✅ Create directly: "workspace MCP: Google Doc → .docx export fails with UTF-8 encoding error" -- ✅ Create directly: "Research: Workarounds for reading Google Slides from Shared Drives" -- ❓ Ask first: "Should we refactor the auth system now or later?" (strategic decision) -- ❓ Ask first: "I found several data validation issues, should I file them all?" (potential overwhelming) - -## The Transition Point - -Most work starts with an implicit mental model: - -**"This looks straightforward"** → TodoWrite - -**As work progresses:** - -✅ **Stays straightforward** → Continue with TodoWrite, complete in session - -⚠️ **Complexity emerges** → Transition to bd, preserve context - -The skill is recognizing the transition point: - -**Transition signals:** -- "This is taking longer than expected" -- "I've discovered a blocker" -- "This needs more research" -- "I should pause this and investigate X first" -- "The user might not be available to continue today" -- "I found three related issues while working on this" - -**When you notice these signals**: Create bd issue, preserve context, work from structured foundation. - -## Summary Heuristics - -Quick decision guides: - -**Time horizon:** -- Same session → TodoWrite -- Multiple sessions → bd - -**Dependency structure:** -- Linear steps → TodoWrite -- Blockers/prerequisites → bd - -**Scope clarity:** -- Well-defined → TodoWrite -- Exploratory → bd - -**Context complexity:** -- Conversation has everything → TodoWrite -- External context needed → bd - -**User interaction:** -- User watching progress → TodoWrite visible in chat -- Background work → bd invisible structure - -**Resume difficulty:** -- Easy from markdown → TodoWrite -- Need structured history → bd - -When in doubt: **Use the 2-week test**. If you'd struggle to resume this work after 2 weeks without bd, use bd. diff --git a/.github/skills/beads/resources/CHEMISTRY_PATTERNS.md b/.github/skills/beads/resources/CHEMISTRY_PATTERNS.md deleted file mode 100644 index 95f89f7e46..0000000000 --- a/.github/skills/beads/resources/CHEMISTRY_PATTERNS.md +++ /dev/null @@ -1,197 +0,0 @@ -# Chemistry Patterns - -> Adapted from ACF beads skill - -Beads uses a chemistry metaphor for work templates. This guide covers when and how to use each phase. - -## Phase Transitions - -``` -┌─────────────────────────────────────────────────────────────┐ -│ PROTO (Solid) │ -│ Frozen template, reusable pattern │ -│ .beads/ with template label │ -└─────────────────────────┬───────────────────────────────────┘ - │ - ┌───────────────┼───────────────┐ - │ │ │ - ▼ │ ▼ -┌─────────────────┐ │ ┌─────────────────┐ -│ MOL (Liquid) │ │ │ WISP (Vapor) │ -│ bd pour │ │ │ bd wisp create │ -│ │ │ │ │ -│ Persistent │ │ │ Ephemeral │ -│ .beads/ │ │ │ .beads-wisp/ │ -│ Git synced │ │ │ Gitignored │ -└────────┬────────┘ │ └────────┬────────┘ - │ │ │ - │ │ ┌───────┴───────┐ - │ │ │ │ - ▼ │ ▼ ▼ - ┌──────────┐ │ ┌─────────┐ ┌─────────┐ - │ CLOSE │ │ │ SQUASH │ │ BURN │ - │ normally │ │ │ → digest│ │ → gone │ - └──────────┘ │ └─────────┘ └─────────┘ - │ - ▼ - ┌───────────────┐ - │ DISTILL │ - │ Extract proto │ - │ from ad-hoc │ - │ epic │ - └───────────────┘ -``` - -## Decision Tree: Mol vs Wisp - -``` -Will this work be referenced later? -│ -├─ YES → Does it need audit trail / git history? -│ │ -│ ├─ YES → MOL (bd pour) -│ │ Examples: Features, bugs, specs -│ │ -│ └─ NO → Could go either way -│ Consider: Will someone else see this? -│ │ -│ ├─ YES → MOL -│ └─ NO → WISP (then squash if valuable) -│ -└─ NO → WISP (bd wisp create) - Examples: Grooming, health checks, scratch work - End state: burn (no value) or squash (capture learnings) -``` - -## Quick Reference - -| Scenario | Use | Command | End State | -|----------|-----|---------|-----------| -| New feature work | Mol | `bd pour spec` | Close normally | -| Bug fix | Mol | `bd pour bug` | Close normally | -| Grooming session | Wisp | `bd wisp create grooming` | Squash → digest | -| Code review | Wisp | `bd wisp create review` | Squash findings | -| Research spike | Wisp | `bd wisp create spike` | Squash or burn | -| Session health check | Wisp | `bd wisp create health` | Burn | -| Agent coordination | Wisp | `bd wisp create coordinator` | Burn | - -## Common Patterns - -### Pattern 1: Grooming Wisp - -Use for periodic backlog maintenance. - -```bash -# Start grooming -bd wisp create grooming --var date="2025-01-02" - -# Work through checklist (stale, duplicates, verification) -# Track findings in wisp notes - -# End: capture summary -bd mol squash # Creates digest: "Closed 3, added 5 relationships" -``` - -**Why wisp?** Grooming is operational—you don't need permanent issues for "reviewed stale items." - -### Pattern 2: Code Review Wisp - -Use for PR review checklists. - -```bash -# Start review -bd wisp create pr-review --var pr="123" --var repo="myproject" - -# Track review findings (security, performance, style) -# Each finding is a child issue in the wisp - -# End: promote real issues, discard noise -bd mol squash # Creates permanent issues for real findings -``` - -**Why wisp?** Review checklists are ephemeral. Only actual findings become permanent issues. - -### Pattern 3: Research Spike Wisp - -Use for time-boxed exploration. - -```bash -# Start spike (2 hour timebox) -bd wisp create spike --var topic="GraphQL pagination" - -# Explore, take notes in wisp issues -# Track sources, findings, dead ends - -# End: decide outcome -bd mol squash # If valuable → creates research summary issue -# OR -bd mol burn # If dead end → no trace -``` - -**Why wisp?** Research might lead nowhere. Don't pollute the database with abandoned explorations. - -## Commands Reference - -### Creating Work - -```bash -# Persistent mol (solid → liquid) -bd pour # Synced to git -bd pour --var key=value - -# Ephemeral wisp (solid → vapor) -bd wisp create # Not synced -bd wisp create --var key=value -``` - -### Ending Work - -```bash -# Mol: close normally -bd close - -# Wisp: squash (condense to digest) -bd mol squash # Creates permanent digest issue - -# Wisp: burn (evaporate, no trace) -bd mol burn # Deletes with no record -``` - -### Managing - -```bash -# List wisps -bd wisp list - -# Garbage collect orphaned wisps -bd wisp gc - -# View proto/mol structure -bd mol show - -# List available protos -bd mol catalog -``` - -## Storage Locations - -| Type | Location | Git Behavior | -|------|----------|--------------| -| Proto | `.beads/` | Synced (template label) | -| Mol | `.beads/` | Synced | -| Wisp | `.beads-wisp/` | Gitignored | - -## Anti-Patterns - -| Don't | Do Instead | -|-------|------------| -| Create mol for one-time diagnostic | Use wisp, then burn | -| Create wisp for real feature work | Use mol (needs audit trail) | -| Burn wisp with valuable findings | Squash first (captures digest) | -| Let wisps accumulate | Burn or squash at session end | -| Create ad-hoc epics for repeatable patterns | Distill into proto | - -## Related Resources - -- [MOLECULES.md](MOLECULES.md) — Proto definitions -- [WORKFLOWS.md](WORKFLOWS.md) — General beads workflows diff --git a/.github/skills/beads/resources/CLI_REFERENCE.md b/.github/skills/beads/resources/CLI_REFERENCE.md deleted file mode 100644 index d3be5d5c51..0000000000 --- a/.github/skills/beads/resources/CLI_REFERENCE.md +++ /dev/null @@ -1,702 +0,0 @@ -# CLI Command Reference - -**For:** AI agents and developers using bd command-line interface -**Version:** 0.47.1+ - -## Quick Navigation - -- [Health & Status](#health--status) -- [Basic Operations](#basic-operations) -- [Issue Management](#issue-management) -- [Dependencies & Labels](#dependencies--labels) -- [Filtering & Search](#filtering--search) -- [Visualization](#visualization) -- [Advanced Operations](#advanced-operations) -- [Database Management](#database-management) - -## Health & Status - -### Doctor (Start Here for Problems) - -```bash -# Basic health check -bd doctor # Check installation health -bd doctor --json # Machine-readable output - -# Fix issues -bd doctor --fix # Auto-fix with confirmation -bd doctor --fix --yes # Auto-fix without confirmation -bd doctor --dry-run # Preview what --fix would do - -# Deep validation -bd doctor --deep # Full graph integrity validation - -# Performance diagnostics -bd doctor --perf # Run performance diagnostics -bd doctor --output diag.json # Export diagnostics to file - -# Specific checks -bd doctor --check=pollution # Detect test issues -bd doctor --check=pollution --clean # Delete test issues - -# Recovery modes -bd doctor --fix --source=jsonl # Rebuild DB from JSONL -bd doctor --fix --force # Force repair on corrupted DB -``` - -### Status Overview - -```bash -# Quick database snapshot (like git status for issues) -bd status # Summary with activity -bd status --json # JSON format -bd status --no-activity # Skip git activity (faster) -bd status --assigned # Show issues assigned to you -bd stats # Alias for bd status -``` - -### Prime (AI Context) - -```bash -# Output AI-optimized workflow context -bd prime # Auto-detects MCP vs CLI mode -bd prime --full # Force full CLI output -bd prime --mcp # Force minimal MCP output -bd prime --stealth # No git operations mode -bd prime --export # Dump default content for customization -``` - -**Customization:** Place `.beads/PRIME.md` to override default output. - -## Basic Operations - -### Check Status - -```bash -# Check database path and daemon status -bd info --json - -# Example output: -# { -# "database_path": "/path/to/.beads/beads.db", -# "issue_prefix": "bd", -# "daemon_running": true -# } -``` - -### Find Work - -```bash -# Find ready work (no blockers) -bd ready --json -bd list --ready --json # Same, integrated into list (v0.47.1+) - -# Find blocked work -bd blocked --json # Show all blocked issues -bd blocked --parent bd-epic --json # Blocked descendants of epic - -# Find molecules waiting on gates for resume (v0.47.0+) -bd ready --gated --json # Gate-resume discovery - -# Find stale issues (not updated recently) -bd stale --days 30 --json # Default: 30 days -bd stale --days 90 --status in_progress --json # Filter by status -bd stale --limit 20 --json # Limit results -``` - -## Issue Management - -### Create Issues - -```bash -# Basic creation -# IMPORTANT: Always quote titles and descriptions with double quotes -bd create "Issue title" -t bug|feature|task -p 0-4 -d "Description" --json - -# Create with explicit ID (for parallel workers) -bd create "Issue title" --id worker1-100 -p 1 --json - -# Create with labels (--labels or --label work) -bd create "Issue title" -t bug -p 1 -l bug,critical --json -bd create "Issue title" -t bug -p 1 --label bug,critical --json - -# Examples with special characters (all require quoting): -bd create "Fix: auth doesn't validate tokens" -t bug -p 1 --json -bd create "Add support for OAuth 2.0" -d "Implement RFC 6749 (OAuth 2.0 spec)" --json - -# Create multiple issues from markdown file -bd create -f feature-plan.md --json - -# Create epic with hierarchical child tasks -bd create "Auth System" -t epic -p 1 --json # Returns: bd-a3f8e9 -bd create "Login UI" -p 1 --json # Auto-assigned: bd-a3f8e9.1 -bd create "Backend validation" -p 1 --json # Auto-assigned: bd-a3f8e9.2 -bd create "Tests" -p 1 --json # Auto-assigned: bd-a3f8e9.3 - -# Create and link discovered work (one command) -bd create "Found bug" -t bug -p 1 --deps discovered-from: --json - -# Preview creation without side effects (v0.47.0+) -bd create "Issue title" -t task -p 1 --dry-run --json # Shows what would be created -``` - -### Quick Capture (q) - -```bash -# Create issue and output only the ID (for scripting) -bd q "Fix login bug" # Outputs: bd-a1b2 -bd q "Task" -t task -p 1 # With type and priority -bd q "Bug" -t bug -l critical # With labels - -# Scripting examples -ISSUE=$(bd q "New feature") # Capture ID in variable -bd q "Task" | xargs bd show # Pipe to other commands -``` - -### Update Issues - -```bash -# Update one or more issues -bd update [...] --status in_progress --json -bd update [...] --priority 1 --json - -# Edit issue fields in $EDITOR (HUMANS ONLY - not for agents) -# NOTE: This command is intentionally NOT exposed via the MCP server -# Agents should use 'bd update' with field-specific parameters instead -bd edit # Edit description -bd edit --title # Edit title -bd edit --design # Edit design notes -bd edit --notes # Edit notes -bd edit --acceptance # Edit acceptance criteria -``` - -### Close/Reopen Issues - -```bash -# Complete work (supports multiple IDs) -bd close [...] --reason "Done" --json - -# Reopen closed issues (supports multiple IDs) -bd reopen [...] --reason "Reopening" --json -``` - -### View Issues - -```bash -# Show dependency tree -bd dep tree - -# Get issue details (supports multiple IDs) -bd show [...] --json -``` - -### Comments - -```bash -# List comments on an issue -bd comments bd-123 # Human-readable -bd comments bd-123 --json # JSON format - -# Add a comment -bd comments add bd-123 "This is a comment" -bd comments add bd-123 -f notes.txt # From file -``` - -## Dependencies & Labels - -### Dependencies - -```bash -# Link discovered work (old way - two commands) -bd dep add --type discovered-from - -# Create and link in one command (new way - preferred) -bd create "Issue title" -t bug -p 1 --deps discovered-from: --json -``` - -### Labels - -```bash -# Label management (supports multiple IDs) -bd label add [...]