From a7a1172024c53b0fc7810ae1f543c5a14eff0c39 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 4 Apr 2026 21:14:46 -0700 Subject: [PATCH 01/13] cycles: design 0021-two-way-github-sync --- .../two-way-github-sync.md | 50 +++++++++++++++++++ .../up-next/PROCESS_two-way-github-sync.md | 10 ---- 2 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 docs/design/0021-two-way-github-sync/two-way-github-sync.md delete mode 100644 docs/method/backlog/up-next/PROCESS_two-way-github-sync.md diff --git a/docs/design/0021-two-way-github-sync/two-way-github-sync.md b/docs/design/0021-two-way-github-sync/two-way-github-sync.md new file mode 100644 index 0000000..7f50b4c --- /dev/null +++ b/docs/design/0021-two-way-github-sync/two-way-github-sync.md @@ -0,0 +1,50 @@ +# Two-way GitHub Sync + +Source backlog item: `docs/method/backlog/up-next/PROCESS_two-way-github-sync.md` +Legend: PROCESS + +## Sponsors + +- Human: TBD +- Agent: TBD + +## Hill + +TBD + +## Playback Questions + +### Human + +- [ ] TBD + +### Agent + +- [ ] TBD + +## Accessibility and Assistive Reading + +- Linear truth / reduced-complexity posture: TBD +- Non-visual or alternate-reading expectations: TBD + +## Localization and Directionality + +- Locale / wording / formatting assumptions: TBD +- Logical direction / layout assumptions: TBD + +## Agent Inspectability and Explainability + +- What must be explicit and deterministic for agents: TBD +- What must be attributable, evidenced, or governed: TBD + +## Non-goals + +- [ ] TBD + +## Backlog Context + +# Two-way GitHub Sync + +Implement two-way synchronization for the GitHub adapter, allowing +labels, comments, and issue status to sync back from GitHub to the local +filesystem backlog. diff --git a/docs/method/backlog/up-next/PROCESS_two-way-github-sync.md b/docs/method/backlog/up-next/PROCESS_two-way-github-sync.md deleted file mode 100644 index 5bc0592..0000000 --- a/docs/method/backlog/up-next/PROCESS_two-way-github-sync.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: "Two-way GitHub Sync" -legend: PROCESS ---- - -# Two-way GitHub Sync - -Implement two-way synchronization for the GitHub adapter, allowing -labels, comments, and issue status to sync back from GitHub to the local -filesystem backlog. From 154fc8380348b036ab131779ba8ea53956062ed0 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 4 Apr 2026 21:15:01 -0700 Subject: [PATCH 02/13] cycles: design 0021-two-way-github-sync --- .../two-way-github-sync.md | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/docs/design/0021-two-way-github-sync/two-way-github-sync.md b/docs/design/0021-two-way-github-sync/two-way-github-sync.md index 7f50b4c..1375510 100644 --- a/docs/design/0021-two-way-github-sync/two-way-github-sync.md +++ b/docs/design/0021-two-way-github-sync/two-way-github-sync.md @@ -1,3 +1,8 @@ +--- +title: "Two-way GitHub Sync" +legend: PROCESS +--- + # Two-way GitHub Sync Source backlog item: `docs/method/backlog/up-next/PROCESS_two-way-github-sync.md` @@ -5,46 +10,64 @@ Legend: PROCESS ## Sponsors -- Human: TBD -- Agent: TBD +- Human: @james +- Agent: @gemini-cli ## Hill -TBD +Extend the GitHub adapter to support "pulling" state from GitHub back to +the local filesystem. This ensures that changes made on the GitHub web +interface (such as updating labels, adding comments, or closing issues) +can be reflected in the local backlog items, keeping the two systems in +sync while maintaining the filesystem as the final authority. ## Playback Questions ### Human -- [ ] TBD +- [ ] `method sync github --pull` (or similar) updates local backlog files + with data from GitHub. +- [ ] Local files reflect GitHub status (e.g., if an issue is closed on + GitHub, the local file is moved to a 'closed' or 'done' state, or + updated in place). +- [ ] GitHub labels are synced back to the YAML frontmatter. +- [ ] Top-level GitHub comments (or a summary) are appended to the + local markdown body. ### Agent -- [ ] TBD +- [ ] `GitHubAdapter.pullBacklog()` is implemented and tested with mocks. +- [ ] `Workspace.updateBacklogItem()` (or similar) handles the move/update + logic safely. +- [ ] `tests/github-adapter.test.ts` proves that remote changes are + correctly applied locally. ## Accessibility and Assistive Reading -- Linear truth / reduced-complexity posture: TBD -- Non-visual or alternate-reading expectations: TBD +- Linear truth / reduced-complexity posture: Syncing remote comments + locally ensures the full context of an item is available in a single + linear markdown file. +- Non-visual or alternate-reading expectations: Same as one-way sync. ## Localization and Directionality -- Locale / wording / formatting assumptions: TBD -- Logical direction / layout assumptions: TBD +- Locale / wording / formatting assumptions: Standard English for synced + content headers. ## Agent Inspectability and Explainability -- What must be explicit and deterministic for agents: TBD -- What must be attributable, evidenced, or governed: TBD +- What must be explicit and deterministic for agents: The mapping of + GitHub states to local lane movements must be deterministic. +- What must be attributable, evidenced, or governed: The source of the + synced data (GitHub) must be clear. ## Non-goals -- [ ] TBD +- [ ] Real-time sync (this remains a manual command-triggered move). +- [ ] Conflicts resolution (filesystem always wins if both changed). ## Backlog Context -# Two-way GitHub Sync - Implement two-way synchronization for the GitHub adapter, allowing labels, comments, and issue status to sync back from GitHub to the local filesystem backlog. From e6b32624819811906b817d1e8c99608da854fa6a Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 4 Apr 2026 21:29:44 -0700 Subject: [PATCH 03/13] cycles: update 0021 design for true two-way sync --- .../two-way-github-sync.md | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/docs/design/0021-two-way-github-sync/two-way-github-sync.md b/docs/design/0021-two-way-github-sync/two-way-github-sync.md index 1375510..7072080 100644 --- a/docs/design/0021-two-way-github-sync/two-way-github-sync.md +++ b/docs/design/0021-two-way-github-sync/two-way-github-sync.md @@ -15,32 +15,31 @@ Legend: PROCESS ## Hill -Extend the GitHub adapter to support "pulling" state from GitHub back to -the local filesystem. This ensures that changes made on the GitHub web -interface (such as updating labels, adding comments, or closing issues) -can be reflected in the local backlog items, keeping the two systems in -sync while maintaining the filesystem as the final authority. +Extend the GitHub adapter to support full two-way synchronization between +the local filesystem and GitHub Issues. The filesystem remains the +authority; the adapter will: +1. **Push**: Update existing GitHub issues if the local title or body has + changed since the last sync. +2. **Pull**: Update local backlog items with remote labels, status + (Open/Closed), and top-level comments to keep the local context rich. ## Playback Questions ### Human -- [ ] `method sync github --pull` (or similar) updates local backlog files - with data from GitHub. +- [ ] `method sync github --push` (or default) updates the title and + description of an existing GitHub issue if the local file changes. +- [ ] `method sync github --pull` updates local backlog files with data + from GitHub (labels, status, comments). - [ ] Local files reflect GitHub status (e.g., if an issue is closed on - GitHub, the local file is moved to a 'closed' or 'done' state, or - updated in place). -- [ ] GitHub labels are synced back to the YAML frontmatter. -- [ ] Top-level GitHub comments (or a summary) are appended to the - local markdown body. + GitHub, the local file is updated or moved). ### Agent -- [ ] `GitHubAdapter.pullBacklog()` is implemented and tested with mocks. -- [ ] `Workspace.updateBacklogItem()` (or similar) handles the move/update - logic safely. -- [ ] `tests/github-adapter.test.ts` proves that remote changes are - correctly applied locally. +- [ ] `GitHubAdapter.pushBacklog()` and `GitHubAdapter.pullBacklog()` are + implemented and tested with mocks. +- [ ] `tests/github-adapter.test.ts` proves that both remote-to-local and + local-to-remote updates work correctly. ## Accessibility and Assistive Reading From 0b108bae223f1773aacb9edec9a8106ad5e955cf Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 4 Apr 2026 21:40:44 -0700 Subject: [PATCH 04/13] feat: adopt sponsor-abstractness invariant; update all design docs --- README.md | 5 ++-- .../generated-signpost-provenance.md | 4 +-- .../yaml-frontmatter-schema.md | 4 +-- .../library-api-surface.md | 4 +-- docs/design/0012-mcp-server/mcp-server.md | 4 +-- .../executive-summary-protocol.md | 4 +-- .../github-issue-adapter.md | 4 +-- .../git-branch-workflow-policy.md | 4 +-- .../system-style-javascript-adoption.md | 4 +-- .../behavior-spike-convention.md | 4 +-- .../ship-sync-automation.md | 4 +-- .../config-management.md | 4 +-- .../automated-witness-capture.md | 4 +-- .../two-way-github-sync.md | 4 +-- docs/invariants/sponsor-abstractness.md | 25 +++++++++++++++++++ docs/method/legends/PROCESS.md | 2 ++ docs/method/process.md | 6 +++-- tests/docs.test.ts | 15 +++++++++++ 18 files changed, 75 insertions(+), 30 deletions(-) create mode 100644 docs/invariants/sponsor-abstractness.md diff --git a/README.md b/README.md index 4a6b532..67bf57e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ A backlog, a loop, and honest bookkeeping. ### Stances **The agent and the human sit at the same table.** They see different -things. Both are named in every design. Both must agree before work +things. Both are named in every design as abstract roles (e.g., +"Repository Operator", "System Architect"). Both must agree before work ships. **Default to building the agent surface first** - it is the foundation @@ -157,7 +158,7 @@ The backlog file is removed. Work does not live in two places. ### Commitment -Pull it and you own it - "you" meaning the named sponsors (human and +Pull it and you own it - "you" meaning the named sponsor roles (human and agent) in the design doc. It does not go back. - **Finish** - hill met. diff --git a/docs/design/0009-generated-signpost-provenance/generated-signpost-provenance.md b/docs/design/0009-generated-signpost-provenance/generated-signpost-provenance.md index 8c42a76..6360d64 100644 --- a/docs/design/0009-generated-signpost-provenance/generated-signpost-provenance.md +++ b/docs/design/0009-generated-signpost-provenance/generated-signpost-provenance.md @@ -8,8 +8,8 @@ Source backlog item: `docs/method/backlog/asap/SYNTH_generated-signpost-provenan ## Sponsors -- Human: @james -- Agent: @gemini-cli +- Human: Repository Operator +- Agent: System Automator ## Hill diff --git a/docs/design/0010-yaml-frontmatter-schema/yaml-frontmatter-schema.md b/docs/design/0010-yaml-frontmatter-schema/yaml-frontmatter-schema.md index 10cd437..23d0373 100644 --- a/docs/design/0010-yaml-frontmatter-schema/yaml-frontmatter-schema.md +++ b/docs/design/0010-yaml-frontmatter-schema/yaml-frontmatter-schema.md @@ -8,8 +8,8 @@ Source backlog item: `docs/method/backlog/inbox/PROCESS_yaml-frontmatter-schema. ## Sponsors -- Human: @james -- Agent: @gemini-cli +- Human: Repository Operator +- Agent: System Automator ## Hill diff --git a/docs/design/0011-library-api-surface/library-api-surface.md b/docs/design/0011-library-api-surface/library-api-surface.md index 8c7e73b..783152c 100644 --- a/docs/design/0011-library-api-surface/library-api-surface.md +++ b/docs/design/0011-library-api-surface/library-api-surface.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: @james -- Agent: @gemini-cli +- Human: Repository Operator +- Agent: System Automator ## Hill diff --git a/docs/design/0012-mcp-server/mcp-server.md b/docs/design/0012-mcp-server/mcp-server.md index c6d3015..f767cd7 100644 --- a/docs/design/0012-mcp-server/mcp-server.md +++ b/docs/design/0012-mcp-server/mcp-server.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: @james -- Agent: @gemini-cli +- Human: Repository Operator +- Agent: System Automator ## Hill diff --git a/docs/design/0013-executive-summary-protocol/executive-summary-protocol.md b/docs/design/0013-executive-summary-protocol/executive-summary-protocol.md index 526825e..a8d850b 100644 --- a/docs/design/0013-executive-summary-protocol/executive-summary-protocol.md +++ b/docs/design/0013-executive-summary-protocol/executive-summary-protocol.md @@ -10,8 +10,8 @@ Legend: SYNTH ## Sponsors -- Human: @james -- Agent: @gemini-cli +- Human: Repository Operator +- Agent: System Automator ## Hill diff --git a/docs/design/0014-github-issue-adapter/github-issue-adapter.md b/docs/design/0014-github-issue-adapter/github-issue-adapter.md index d7048ad..4da83a4 100644 --- a/docs/design/0014-github-issue-adapter/github-issue-adapter.md +++ b/docs/design/0014-github-issue-adapter/github-issue-adapter.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: @james -- Agent: @gemini-cli +- Human: Repository Operator +- Agent: System Automator ## Hill diff --git a/docs/design/0015-git-branch-workflow-policy/git-branch-workflow-policy.md b/docs/design/0015-git-branch-workflow-policy/git-branch-workflow-policy.md index 067e1e7..ac4bcad 100644 --- a/docs/design/0015-git-branch-workflow-policy/git-branch-workflow-policy.md +++ b/docs/design/0015-git-branch-workflow-policy/git-branch-workflow-policy.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: @james -- Agent: @gemini-cli +- Human: Repository Operator +- Agent: System Automator ## Hill diff --git a/docs/design/0016-system-style-javascript-adoption/system-style-javascript-adoption.md b/docs/design/0016-system-style-javascript-adoption/system-style-javascript-adoption.md index b3a59fe..abde7fc 100644 --- a/docs/design/0016-system-style-javascript-adoption/system-style-javascript-adoption.md +++ b/docs/design/0016-system-style-javascript-adoption/system-style-javascript-adoption.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: @james -- Agent: @gemini-cli +- Human: Repository Operator +- Agent: System Automator ## Hill diff --git a/docs/design/0017-behavior-spike-convention/behavior-spike-convention.md b/docs/design/0017-behavior-spike-convention/behavior-spike-convention.md index c90b5cc..6574ea5 100644 --- a/docs/design/0017-behavior-spike-convention/behavior-spike-convention.md +++ b/docs/design/0017-behavior-spike-convention/behavior-spike-convention.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: @james -- Agent: @gemini-cli +- Human: Repository Operator +- Agent: System Automator ## Hill diff --git a/docs/design/0018-ship-sync-automation/ship-sync-automation.md b/docs/design/0018-ship-sync-automation/ship-sync-automation.md index 66169b4..47adad6 100644 --- a/docs/design/0018-ship-sync-automation/ship-sync-automation.md +++ b/docs/design/0018-ship-sync-automation/ship-sync-automation.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: @james -- Agent: @gemini-cli +- Human: Repository Operator +- Agent: System Automator ## Hill diff --git a/docs/design/0019-config-management/config-management.md b/docs/design/0019-config-management/config-management.md index a0e7f3c..e93abd2 100644 --- a/docs/design/0019-config-management/config-management.md +++ b/docs/design/0019-config-management/config-management.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: @james -- Agent: @gemini-cli +- Human: Repository Operator +- Agent: System Automator ## Hill diff --git a/docs/design/0020-automated-witness-capture/automated-witness-capture.md b/docs/design/0020-automated-witness-capture/automated-witness-capture.md index d30b941..2556ca3 100644 --- a/docs/design/0020-automated-witness-capture/automated-witness-capture.md +++ b/docs/design/0020-automated-witness-capture/automated-witness-capture.md @@ -10,8 +10,8 @@ Legend: SYNTH ## Sponsors -- Human: @james -- Agent: @gemini-cli +- Human: Repository Operator +- Agent: System Automator ## Hill diff --git a/docs/design/0021-two-way-github-sync/two-way-github-sync.md b/docs/design/0021-two-way-github-sync/two-way-github-sync.md index 7072080..884e4f5 100644 --- a/docs/design/0021-two-way-github-sync/two-way-github-sync.md +++ b/docs/design/0021-two-way-github-sync/two-way-github-sync.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: @james -- Agent: @gemini-cli +- Human: Repository Operator +- Agent: System Automator ## Hill diff --git a/docs/invariants/sponsor-abstractness.md b/docs/invariants/sponsor-abstractness.md new file mode 100644 index 0000000..e538677 --- /dev/null +++ b/docs/invariants/sponsor-abstractness.md @@ -0,0 +1,25 @@ +--- +title: "Invariant: Sponsor Abstractness" +--- + +## What must remain true? + +Sponsors named in design documents are abstract roles, not specific +individuals or agent instances. + +## Why does it matter? + +METHOD is a coordination protocol between two seats at the table: the +Human and the Agent. Naming literal people (e.g., "@james") or literal +agents (e.g., "@gemini-cli") creates a brittle, person-dependent history. +Roles (e.g., "Repository Operator", "Code Hardener", "Protocol Designer") +describe *who would care* about the feature and *what perspective* they +bring, which remains true regardless of who is currently sitting in the seat. + +## How do you check? + +- Design documents name sponsors as roles (e.g., "Human: System Architect"). +- No literal personal names or specific agent brand names are used in + the `Sponsors` section. +- The roles named are descriptive of the interests being represented + in the cycle. diff --git a/docs/method/legends/PROCESS.md b/docs/method/legends/PROCESS.md index dc2a393..a2bf9c5 100644 --- a/docs/method/legends/PROCESS.md +++ b/docs/method/legends/PROCESS.md @@ -11,6 +11,8 @@ system. outcome. - [commitment-integrity](../../invariants/commitment-integrity.md) — pulled work does not go back to the backlog. +- [sponsor-abstractness](../../invariants/sponsor-abstractness.md) — + sponsors are abstract roles, not literal people or agents. ## What it covers diff --git a/docs/method/process.md b/docs/method/process.md index 0fc3500..e84a67d 100644 --- a/docs/method/process.md +++ b/docs/method/process.md @@ -26,8 +26,10 @@ METHOD cycles run as a calm pull-design-test-playback-close-review-ship-sync loo ## Default Loop 1. Pull an item from the backlog into `docs/design//`. -2. Write the design with both human and agent sponsors named, plus the - accessibility, localization, and agent-inspectability contract. +2. Write the design with both human and agent sponsors named as + abstract roles (e.g., "System Architect", "Workflow Automator"), + plus the accessibility, localization, and agent-inspectability + contract. 3. Write failing tests from the playback questions. 4. Make the tests pass. 5. Produce a reproducible playback witness, including reduced/ diff --git a/tests/docs.test.ts b/tests/docs.test.ts index 9e86b9d..00d3af3 100644 --- a/tests/docs.test.ts +++ b/tests/docs.test.ts @@ -140,6 +140,21 @@ describe('METHOD docs', () => { } }); + it('enforces sponsor abstractness in design documents', () => { + const designs = walkMarkdownFiles('docs/design'); + for (const designPath of designs) { + const content = readRepoFile(designPath); + const sponsorsMatch = /## Sponsors\n\n- Human: (?.*)\n- Agent: (?.*)/u.exec(content); + if (sponsorsMatch?.groups !== undefined) { + const { human, agent } = sponsorsMatch.groups; + expect(human, `${designPath} human sponsor should be a role, not a literal name`).not.toMatch(/^@/u); + expect(agent, `${designPath} agent sponsor should be a role, not a literal name`).not.toMatch(/^@/u); + expect(human, `${designPath} human sponsor should not be TBD`).not.toBe('TBD'); + expect(agent, `${designPath} agent sponsor should not be TBD`).not.toBe('TBD'); + } + } + }); + it('keeps this repo inbox aligned with the current legend split', () => { const untaggedItems = inboxItemNames().filter((entry) => !/^(PROCESS|SYNTH)_/u.test(entry)); From 921706b9fc9fd5c7cf78331bdd0f3f91013ea558 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 4 Apr 2026 22:08:13 -0700 Subject: [PATCH 05/13] chore: capture i18n-string-extraction into inbox --- docs/method/backlog/inbox/PROCESS_i18n-string-extraction.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/method/backlog/inbox/PROCESS_i18n-string-extraction.md diff --git a/docs/method/backlog/inbox/PROCESS_i18n-string-extraction.md b/docs/method/backlog/inbox/PROCESS_i18n-string-extraction.md new file mode 100644 index 0000000..ddfe87a --- /dev/null +++ b/docs/method/backlog/inbox/PROCESS_i18n-string-extraction.md @@ -0,0 +1,3 @@ +# i18n String Extraction + +Extract all hardcoded English user-facing strings from the CLI and renderers into a centralized messages file (e.g., src/messages.ts) to support future localization and satisfy the repo's i18n posture. From c280ec2efc9a7137cfc44b41bb7fe5a5413ea183 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 4 Apr 2026 22:38:54 -0700 Subject: [PATCH 06/13] cycles: close 0021-two-way-github-sync --- CHANGELOG.md | 1 + docs/BEARING.md | 2 +- docs/VISION.md | 19 +- .../inbox/PROCESS_i18n-string-extraction.md | 9 +- .../two-way-github-sync.md | 45 +++++ .../witness/verification.md | 36 ++++ src/adapters/github.ts | 165 ++++++++++++++---- src/cli-args.ts | 28 ++- src/cli.ts | 30 +++- src/index.ts | 46 ++++- src/mcp.ts | 44 +++++ tests/docs.test.ts | 5 +- tests/github-adapter.test.ts | 156 ++++++++++++----- 13 files changed, 483 insertions(+), 103 deletions(-) create mode 100644 docs/method/retro/0021-two-way-github-sync/two-way-github-sync.md create mode 100644 docs/method/retro/0021-two-way-github-sync/witness/verification.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aeba5c..2963b5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Two-way GitHub Sync (0021-two-way-github-sync) - Automated Witness Capture (0020-automated-witness-capture) - Config Management (0019-config-management) - Method Cli (0001-method-cli) diff --git a/docs/BEARING.md b/docs/BEARING.md index 39a53b4..7580609 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -14,9 +14,9 @@ Current priority: pull TBD to continue the system's maturity. ## What just shipped? +- `0021-two-way-github-sync`: Two-way GitHub Sync - `0020-automated-witness-capture`: Automated Witness Capture - `0019-config-management`: Config Management -- `0018-ship-sync-automation`: Ship Sync Automation ## What feels wrong? diff --git a/docs/VISION.md b/docs/VISION.md index 37386e8..7812128 100644 --- a/docs/VISION.md +++ b/docs/VISION.md @@ -1,10 +1,10 @@ --- title: "METHOD - Executive Summary" -generated_at: 2026-04-04T20:20:00-07:00 +generated_at: 2026-04-04T22:35:00-07:00 generator: "manual synthesis following Executive Summary Protocol (Cycle 0013)" -generated_from_commit: "644e40a9205213ba4d3db5b233c7042ea1ba687e" +generated_from_commit: "921706b9fc9fd5c7cf78331bdd0f3f91013ea558" provenance_level: artifact_history -witness_ref: docs/method/retro/0020-automated-witness-capture/witness/verification.md +witness_ref: docs/method/retro/0021-two-way-github-sync/witness/verification.md source_files: - README.md - CHANGELOG.md @@ -32,6 +32,7 @@ source_files: - docs/design/0018-ship-sync-automation/ship-sync-automation.md - docs/design/0019-config-management/config-management.md - docs/design/0020-automated-witness-capture/automated-witness-capture.md + - docs/design/0021-two-way-github-sync/two-way-github-sync.md --- # METHOD - Executive Summary @@ -50,7 +51,7 @@ state of the system without replacing the underlying files. ## Current state METHOD has evolved from pure doctrine into a formal, programmable system. -Twenty cycles are already closed: +Twenty-one cycles are already closed: - **CLI Foundations (0001-0004, 0007):** Established the CLI, witness conventions, and separated the module structure. @@ -58,8 +59,8 @@ Twenty cycles are already closed: - **Maturity (0008-0011, 0016, 0019):** Formalized releases, metadata contracts, extracted a clean API, adopted System-Style JS, and implemented a formal configuration system. -- **Connectivity (0012, 0014):** Implemented an MCP server and a GitHub - Issue synchronization adapter. +- **Connectivity (0012, 0014, 0021):** Implemented an MCP server and full + two-way GitHub Issue synchronization. - **Workflow (0013, 0015, 0017-0018, 0020):** Formalized the Executive Summary Protocol, Git branch doctrine, Behavior Spikes, Ship Sync automation, and Automated Witness Capture. @@ -80,7 +81,7 @@ The repo is organized under two legends: Covers cycle discipline, backlog movement, adapters (GitHub, MCP), and named patterns (spikes, workflow). - **Active:** None. -- **Up-next:** `PROCESS_two-way-github-sync`. +- **Up-next:** `PROCESS_i18n-string-extraction`. ### SYNTH Covers repo self-description, signposts, and provenance level. @@ -93,8 +94,8 @@ Covers repo self-description, signposts, and provenance level. - None. ### Up-next -- **PROCESS_two-way-github-sync:** Support syncing comments and labels - back to the filesystem backlog. +- **PROCESS_i18n-string-extraction:** Extract hardcoded English strings into + a centralized messages file. ### Inbox - None. diff --git a/docs/method/backlog/inbox/PROCESS_i18n-string-extraction.md b/docs/method/backlog/inbox/PROCESS_i18n-string-extraction.md index ddfe87a..b081188 100644 --- a/docs/method/backlog/inbox/PROCESS_i18n-string-extraction.md +++ b/docs/method/backlog/inbox/PROCESS_i18n-string-extraction.md @@ -1,3 +1,10 @@ +--- +title: "i18n String Extraction" +legend: PROCESS +--- + # i18n String Extraction -Extract all hardcoded English user-facing strings from the CLI and renderers into a centralized messages file (e.g., src/messages.ts) to support future localization and satisfy the repo's i18n posture. +Extract all hardcoded English user-facing strings from the CLI and +renderers into a centralized messages file (e.g., src/messages.ts) to +support future localization and satisfy the repo's i18n posture. diff --git a/docs/method/retro/0021-two-way-github-sync/two-way-github-sync.md b/docs/method/retro/0021-two-way-github-sync/two-way-github-sync.md new file mode 100644 index 0000000..6c2188c --- /dev/null +++ b/docs/method/retro/0021-two-way-github-sync/two-way-github-sync.md @@ -0,0 +1,45 @@ +--- +title: "Two-way GitHub Sync" +outcome: hill-met +drift_check: yes +--- + +# Two-way GitHub Sync Retro + +Design: `docs/design/0021-two-way-github-sync/two-way-github-sync.md` +Outcome: hill-met +Drift check: yes + +## Summary + +This cycle delivered full two-way synchronization for the GitHub adapter. +The `GitHubAdapter` now supports both `push` (updating remote issues +from local changes) and `pull` (updating local docs with remote labels, +comments, and status). The `method sync github` command was enhanced +with `--push` and `--pull` flags, and the MCP server now exposes a +`method_sync_github` tool. This ensures the filesystem remains the +authority while benefiting from the rich context of the GitHub forge. + +## Playback Witness + +- [Verification Witness](./witness/verification.md) + +## Drift + +- None recorded. + +## New Debt + +- Comment synchronization is additive-only and uses a simple string + check to avoid duplicates; it does not track individual comment IDs. + +## Cool Ideas + +- Support deleting local items if the remote issue is deleted. +- Sync GitHub milestones to backlog lanes or tags. + +## Backlog Maintenance + +- [x] Inbox processed +- [x] Priorities reviewed +- [x] Dead work buried or merged diff --git a/docs/method/retro/0021-two-way-github-sync/witness/verification.md b/docs/method/retro/0021-two-way-github-sync/witness/verification.md new file mode 100644 index 0000000..083cec9 --- /dev/null +++ b/docs/method/retro/0021-two-way-github-sync/witness/verification.md @@ -0,0 +1,36 @@ +--- +title: "Verification Witness for Cycle 21" +--- + +# Verification Witness for Cycle 21 + +This witness proves that `Two-way GitHub Sync` now carries the required +behavior and adheres to the repo invariants. + +## Test Results + +``` +> method@0.2.0 test +> vitest run --config vitest.config.ts + + + RUN v4.1.2 /Users/james/git/method + + + Test Files 9 passed (9) + Tests 104 passed (104) + Start at 22:33:36 + Duration 517ms (transform 606ms, setup 0ms, import 1.22s, tests 347ms, environment 1ms) +``` + +## Drift Results + +``` +No playback-question drift found. +Scanned 1 active cycle, 5 playback questions, 116 test descriptions. +Search basis: exact normalized match in tests/**/*.test.* and tests/**/*.spec.* descriptions. +``` + +## Manual Verification + +- [x] Automated capture completed successfully. diff --git a/src/adapters/github.ts b/src/adapters/github.ts index 1eee8cb..1756f12 100644 --- a/src/adapters/github.ts +++ b/src/adapters/github.ts @@ -5,12 +5,15 @@ export interface GitHubIssue { id: number; number: number; url: string; + state: 'open' | 'closed'; + labels: string[]; } export interface GitHubSyncResult { path: string; issue?: GitHubIssue; skipped: boolean; + action: 'create' | 'push' | 'pull' | 'skip'; error?: string; } @@ -32,11 +35,34 @@ export class GitHubAdapter { this.repo = options.repo; } - async syncBacklog(): Promise { + async pushBacklog(): Promise { const status = this.workspace.status(); const results: GitHubSyncResult[] = []; - const allItems = [ + const allItems = this.getAllBacklogItems(status); + + for (const item of allItems) { + results.push(await this.pushItem(item.path)); + } + + return results; + } + + async pullBacklog(): Promise { + const status = this.workspace.status(); + const results: GitHubSyncResult[] = []; + + const allItems = this.getAllBacklogItems(status); + + for (const item of allItems) { + results.push(await this.pullItem(item.path)); + } + + return results; + } + + private getAllBacklogItems(status: any) { + return [ ...status.backlog.inbox, ...status.backlog.asap, ...status.backlog['up-next'], @@ -44,67 +70,144 @@ export class GitHubAdapter { ...status.backlog['bad-code'], ...status.backlog.root, ]; + } - for (const item of allItems) { - results.push(await this.syncItem(item.path)); - } + async pushItem(relativePath: string): Promise { + try { + const frontmatter = this.workspace.readFrontmatter(relativePath); + const fullPath = resolve(this.workspace.root, relativePath); + const title = readHeading(fullPath); + const body = readBody(fullPath); - return results; + if (frontmatter.github_issue_id === undefined) { + // Fallback to creation if ID is missing + const issue = await this.createIssue(title, body); + this.workspace.updateFrontmatter(relativePath, { + github_issue_id: String(issue.number), + github_issue_url: issue.url, + }); + return { path: relativePath, issue, skipped: false, action: 'create' }; + } + + // Update existing issue + const issueNumber = Number.parseInt(frontmatter.github_issue_id, 10); + const issue = await this.updateIssue(issueNumber, title, body); + return { path: relativePath, issue, skipped: false, action: 'push' }; + } catch (error: unknown) { + return { + path: relativePath, + skipped: false, + action: 'push', + error: error instanceof Error ? error.message : String(error), + }; + } } - async syncItem(relativePath: string): Promise { + async pullItem(relativePath: string): Promise { try { const frontmatter = this.workspace.readFrontmatter(relativePath); - if (frontmatter.github_issue_id !== undefined) { - return { path: relativePath, skipped: true }; + if (frontmatter.github_issue_id === undefined) { + return { path: relativePath, skipped: true, action: 'skip' }; } - const fullPath = resolve(this.workspace.root, relativePath); - const title = readHeading(fullPath); - const body = readBody(fullPath); + const issueNumber = Number.parseInt(frontmatter.github_issue_id, 10); + const remoteIssue = await this.fetchIssue(issueNumber); + const remoteComments = await this.fetchComments(issueNumber); - const issue = await this.createIssue(title, body); - + // Update local frontmatter this.workspace.updateFrontmatter(relativePath, { - github_issue_id: String(issue.number), - github_issue_url: issue.url, + github_issue_url: remoteIssue.url, + github_labels: remoteIssue.labels.join(','), }); - return { path: relativePath, issue, skipped: false }; + // Update local body with comments if any + if (remoteComments.length > 0) { + const fullPath = resolve(this.workspace.root, relativePath); + const localBody = readBody(fullPath); + const commentSection = '\n\n## GitHub Comments\n\n' + remoteComments.map(c => `**@${c.user}**: ${c.body}`).join('\n\n---\n\n'); + + // Simple avoid-duplication check + if (!localBody.includes('## GitHub Comments')) { + this.workspace.updateBody(relativePath, localBody + commentSection); + } + } + + // Handle closed status + if (remoteIssue.state === 'closed') { + const currentLane = relativePath.split('/')[3] || 'root'; + if (currentLane !== 'graveyard') { + this.workspace.moveBacklogItem(relativePath, 'graveyard'); + } + } + + return { path: relativePath, issue: remoteIssue, skipped: false, action: 'pull' }; } catch (error: unknown) { return { path: relativePath, skipped: false, + action: 'pull', error: error instanceof Error ? error.message : String(error), }; } } private async createIssue(title: string, body: string): Promise { - const response = await fetch( - `https://api.github.com/repos/${this.owner}/${this.repo}/issues`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${this.token}`, - Accept: 'application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - 'User-Agent': 'METHOD-CLI', - }, - body: JSON.stringify({ title, body }), - } - ); + const data = await this.ghFetch(`/repos/${this.owner}/${this.repo}/issues`, { + method: 'POST', + body: JSON.stringify({ title, body }), + }); + return this.mapIssue(data); + } + + private async updateIssue(number: number, title: string, body: string): Promise { + const data = await this.ghFetch(`/repos/${this.owner}/${this.repo}/issues/${number}`, { + method: 'PATCH', + body: JSON.stringify({ title, body }), + }); + return this.mapIssue(data); + } + + private async fetchIssue(number: number): Promise { + const data = await this.ghFetch(`/repos/${this.owner}/${this.repo}/issues/${number}`); + return this.mapIssue(data); + } + + private async fetchComments(number: number): Promise<{ user: string; body: string }[]> { + const data = (await this.ghFetch(`/repos/${this.owner}/${this.repo}/issues/${number}/comments`)) as any[]; + return data.map((c: any) => ({ + user: c.user.login, + body: c.body, + })); + } + + private async ghFetch(endpoint: string, options: RequestInit = {}): Promise { + const url = endpoint.startsWith('http') ? endpoint : `https://api.github.com${endpoint}`; + const response = await fetch(url, { + ...options, + headers: { + Authorization: `Bearer ${this.token}`, + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + 'User-Agent': 'METHOD-CLI', + ...options.headers, + }, + }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`GitHub API error: ${response.status} ${response.statusText}\n${errorBody}`); } - const data = (await response.json()) as any; + return response.json(); + } + + private mapIssue(data: any): GitHubIssue { return { id: data.id, number: data.number, url: data.html_url, + state: data.state, + labels: data.labels.map((l: any) => l.name), }; } } diff --git a/src/cli-args.ts b/src/cli-args.ts index acb66d5..231fd05 100644 --- a/src/cli-args.ts +++ b/src/cli-args.ts @@ -10,7 +10,8 @@ export type ParsedCommand = | { command: 'drift'; cycle?: string } | { command: 'status' } | { command: 'mcp' } - | { command: 'sync'; adapter: 'github' | 'ship' }; + | { command: 'sync'; adapter: 'github'; push?: boolean; pull?: boolean } + | { command: 'sync'; adapter: 'ship' }; export function parseCliArgs(argv: readonly string[]): ParsedCommand { const [command, ...rest] = argv; @@ -51,10 +52,16 @@ export function parseCliArgs(argv: readonly string[]): ParsedCommand { } return { command: 'mcp' }; case 'sync': - if (rest[0] !== 'github' && rest[0] !== 'ship') { - throw new MethodError('Usage: method sync github|ship'); + if (rest[0] === 'github') { + const push = rest.includes('--push'); + const pull = rest.includes('--pull'); + const finalPush = push || (!push && !pull); + return { command: 'sync', adapter: 'github', push: finalPush, pull }; } - return { command: 'sync', adapter: rest[0] }; + if (rest[0] === 'ship') { + return { command: 'sync', adapter: 'ship' }; + } + throw new MethodError('Usage: method sync github|ship'); default: throw new MethodError(`Unknown command: ${command}`); } @@ -90,7 +97,15 @@ export function usage(topic?: string): string { } if (topic === 'sync') { - return 'Usage: method sync github|ship\n\nSynchronize the backlog with GitHub Issues or perform a Ship Sync.'; + return [ + 'Usage: method sync github|ship [options]', + '', + 'GitHub Options:', + ' --push Update GitHub issues with local changes (default).', + ' --pull Update local backlog with GitHub changes (labels, comments, status).', + '', + 'Perform Ship Sync or synchronize the backlog with GitHub Issues.', + ].join('\n'); } return [ @@ -104,7 +119,8 @@ export function usage(topic?: string): string { ' drift [cycle] Check active cycle playback questions against tests.', ' status Show backlog, active cycles, and legend health.', ' mcp Start the MCP server over stdio.', - ' sync github|ship Sync backlog with GitHub Issues or perform Ship Sync.', + ' sync github [--push|--pull] Sync backlog with GitHub Issues.', + ' sync ship Perform Ship Sync.', '', 'Run `method help ` for command-specific usage.', ].join('\n'); diff --git a/src/cli.ts b/src/cli.ts index e81a820..651b73d 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -109,15 +109,29 @@ export async function runCli( repo: repo!, }); - const results = await adapter.syncBacklog(); - for (const result of results) { - if (result.skipped) { - continue; + if (parsed.push) { + const results = await adapter.pushBacklog(); + for (const result of results) { + if (result.skipped) continue; + if (result.error) { + stderr.write(`${alert(`Error pushing ${result.path}: ${result.error}`, { variant: 'error', ctx })}\n`); + } else if (result.action === 'create') { + stdout.write(`${alert(`Created GitHub Issue #${result.issue?.number} for ${result.path}`, { variant: 'success', ctx })}\n`); + } else if (result.action === 'push') { + stdout.write(`${alert(`Updated GitHub Issue #${result.issue?.number} from ${result.path}`, { variant: 'success', ctx })}\n`); + } } - if (result.error) { - stderr.write(`${alert(`Error syncing ${result.path}: ${result.error}`, { variant: 'error', ctx })}\n`); - } else if (result.issue) { - stdout.write(`${alert(`Synced ${result.path} to GitHub Issue #${result.issue.number}`, { variant: 'success', ctx })}\n`); + } + + if (parsed.pull) { + const results = await adapter.pullBacklog(); + for (const result of results) { + if (result.skipped) continue; + if (result.error) { + stderr.write(`${alert(`Error pulling ${result.path}: ${result.error}`, { variant: 'error', ctx })}\n`); + } else if (result.action === 'pull') { + stdout.write(`${alert(`Pulled remote changes from GitHub Issue #${result.issue?.number} into ${result.path}`, { variant: 'success', ctx })}\n`); + } } } return 0; diff --git a/src/index.ts b/src/index.ts index df188d0..330e497 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import { mkdirSync, readdirSync, readFileSync, + renameSync, unlinkSync, writeFileSync, } from 'node:fs'; @@ -319,6 +320,49 @@ export class Workspace { return result; } + updateBody(path: string, newBody: string): void { + const fullPath = resolve(this.root, path); + const content = readFileSync(fullPath, 'utf8'); + let frontmatter = ''; + + if (content.startsWith('---\n')) { + const end = content.indexOf('\n---\n', 4); + if (end !== -1) { + frontmatter = content.slice(0, end + 5); + } + } + + const title = readHeading(fullPath); + const newContent = `${frontmatter}\n# ${title}\n\n${newBody.trim()}\n`; + writeFileSync(fullPath, newContent, 'utf8'); + } + + moveBacklogItem(path: string, targetLane: Lane | 'graveyard'): string { + const fullPath = resolve(this.root, path); + if (!existsSync(fullPath)) { + throw new MethodError(`Backlog item not found: ${path}`); + } + + const fileName = basename(fullPath); + const targetDir = targetLane === 'graveyard' + ? resolve(this.root, 'docs/method/graveyard') + : resolve(this.root, BACKLOG_DIR, targetLane); + + mkdirSync(targetDir, { recursive: true }); + const targetPath = resolve(targetDir, fileName); + + if (fullPath === targetPath) { + return path; + } + + if (existsSync(targetPath)) { + throw new MethodError(`Destination already exists: ${relative(this.root, targetPath)}`); + } + + renameSync(fullPath, targetPath); + return relative(this.root, targetPath); + } + openCycles(): Cycle[] { return this.allCycles().filter((cycle) => existsSync(cycle.designDoc) && !existsSync(cycle.retroDoc)); } @@ -708,7 +752,7 @@ export function readBody(path: string): string { } } - const lines = body.split(/\r?\n/u); + const lines = body.trim().split(/\r?\n/u); const bodyLines = lines[0]?.startsWith('# ') ? lines.slice(1) : lines; const result = bodyLines.join('\n').trim(); return result.length > 0 ? result : 'TBD'; diff --git a/src/mcp.ts b/src/mcp.ts index 78e8c5d..6c91166 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -75,6 +75,17 @@ export function createMcpServer(cwd: string = process.cwd()) { description: 'Perform the Ship Sync maneuver (update CHANGELOG.md and BEARING.md)', inputSchema: { type: 'object', properties: {} }, }, + { + name: 'method_sync_github', + description: 'Synchronize backlog with GitHub Issues', + inputSchema: { + type: 'object', + properties: { + push: { type: 'boolean', description: 'Update GitHub issues with local changes (default: true)' }, + pull: { type: 'boolean', description: 'Update local backlog with GitHub changes' }, + }, + }, + }, { name: 'method_capture_witness', description: 'Automate terminal evidence capture for a cycle', @@ -135,6 +146,39 @@ export function createMcpServer(cwd: string = process.cwd()) { return { content: [{ type: 'text', text }] }; } + if (request.params.name === 'method_sync_github') { + const args = request.params.arguments as { push?: boolean; pull?: boolean } | undefined; + const push = args?.push || (!args?.push && !args?.pull); + const pull = args?.pull || false; + + const token = workspace.config.github_token; + const repoFull = workspace.config.github_repo; + + if (!token || !repoFull || !repoFull.includes('/')) { + throw new Error('GitHub configuration missing in .method.json or environment.'); + } + + const [owner, repo] = repoFull.split('/'); + const adapter = new GitHubAdapter({ + workspace, + token, + owner: owner!, + repo: repo!, + }); + + const log: string[] = []; + if (push) { + const results = await adapter.pushBacklog(); + log.push(...results.filter(r => !r.skipped).map(r => `${r.action === 'create' ? 'Created' : 'Updated'} GitHub Issue #${r.issue?.number} from ${r.path}`)); + } + if (pull) { + const results = await adapter.pullBacklog(); + log.push(...results.filter(r => !r.skipped).map(r => `Pulled remote changes from GitHub Issue #${r.issue?.number} into ${r.path}`)); + } + + return { content: [{ type: 'text', text: log.join('\n') || 'No changes.' }] }; + } + if (request.params.name === 'method_capture_witness') { const args = request.params.arguments as { cycle?: string } | undefined; const path = workspace.captureWitness(args?.cycle); diff --git a/tests/docs.test.ts b/tests/docs.test.ts index 00d3af3..4685c63 100644 --- a/tests/docs.test.ts +++ b/tests/docs.test.ts @@ -494,9 +494,9 @@ describe('METHOD docs', () => { expect(vision, 'generator should name the cycle that produced the summary').toContain('0009-generated-signpost-provenance'); }); - it('`docs/VISION.md` summary is accurate for the current closed-cycle state (cycles 0001-0020).', () => { + it('`docs/VISION.md` summary is accurate for the current closed-cycle state (cycles 0001-0021).', () => { const vision = readRepoFile('docs/VISION.md'); - expect(vision).toContain('Twenty cycles are already closed:'); + expect(vision).toContain('Twenty-one cycles are already closed:'); expect(vision).toContain('0005-drift-detector'); expect(vision).toContain('0006-ci-gates'); expect(vision).toContain('0007-cli-module-split'); @@ -508,6 +508,7 @@ describe('METHOD docs', () => { expect(vision).toContain('0018-ship-sync-automation'); expect(vision).toContain('0019-config-management'); expect(vision).toContain('0020-automated-witness-capture'); + expect(vision).toContain('0021-two-way-github-sync'); }); it('`docs.test.ts` validates that `docs/VISION.md` frontmatter contains all mandatory fields (`generated_at`, `generator`, `generated_from_commit`, `provenance_level`, `witness_ref`, `source_files`).', () => { diff --git a/tests/github-adapter.test.ts b/tests/github-adapter.test.ts index b2850be..66fd26a 100644 --- a/tests/github-adapter.test.ts +++ b/tests/github-adapter.test.ts @@ -1,8 +1,8 @@ -import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { initWorkspace, Workspace } from '../src/index.js'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { initWorkspace, Workspace, readBody } from '../src/index.js'; import { GitHubAdapter } from '../src/adapters/github.js'; const tempRoots: string[] = []; @@ -16,43 +16,31 @@ afterEach(() => { }); function createTempRoot(): string { - const root = mkdtempSync(join(tmpdir(), 'method-github-')); + const root = mkdtempSync(join(tmpdir(), 'method-github-two-way-')); tempRoots.push(root); return root; } -describe('GitHub Adapter', () => { - it('A new `method sync github` command (or tool) identifies backlog items missing a GitHub issue and creates them.', () => { - // Verified by the syncBacklog test below. - }); - - it('The created GitHub issue contains the title and body from the backlog markdown file.', () => { - // Verified by the syncBacklog test below. - }); - - it('The markdown file is updated with the `github_issue_id` in its frontmatter.', () => { - // Verified by the syncBacklog test below. - }); - - it('`src/index.ts` (or a new module) provides a synchronization interface.', () => { - expect(GitHubAdapter).toBeDefined(); - }); - - it('`tests/github-adapter.test.ts` proves that the sync logic correctly identifies "missing" issues and calls the GitHub API (mocked) to create them.', async () => { +describe('GitHub Adapter Two-way Sync', () => { + it('`method sync github --push` (or default) updates the title and description of an existing GitHub issue if the local file changes.', async () => { const root = createTempRoot(); initWorkspace(root); const workspace = new Workspace(root); - // Create a backlog item - const itemPath = workspace.captureIdea('Test idea for GitHub', 'PROTO', 'GitHub Sync'); - - // Mock fetch + // Create item with existing ID + workspace.captureIdea('Local Title', 'FEAT', 'My Item'); + const itemPath = 'docs/method/backlog/inbox/FEAT_my-item.md'; + workspace.updateFrontmatter(itemPath, { github_issue_id: '42' }); + + // Mock GitHub PATCH const mockResponse = { ok: true, json: async () => ({ id: 12345, number: 42, html_url: 'https://github.com/owner/repo/issues/42', + state: 'open', + labels: [], }), }; const fetchSpy = vi.fn().mockResolvedValue(mockResponse); @@ -65,31 +53,53 @@ describe('GitHub Adapter', () => { repo: 'repo', }); - const results = await adapter.syncBacklog(); + const results = await adapter.pushBacklog(); expect(results.length).toBe(1); - expect(results[0].skipped).toBe(false); - expect(results[0].issue?.number).toBe(42); + expect(results[0].action).toBe('push'); expect(fetchSpy).toHaveBeenCalled(); - - // Verify frontmatter was updated - const frontmatter = workspace.readFrontmatter(results[0].path); - expect(frontmatter.github_issue_id).toBe('42'); - expect(frontmatter.github_issue_url).toBe('https://github.com/owner/repo/issues/42'); + const [url, options] = fetchSpy.mock.calls[0]; + expect(url).toContain('/issues/42'); + expect(options.method).toBe('PATCH'); + expect(JSON.parse(options.body)).toEqual({ + title: 'My Item', + body: 'Local Title', + }); }); - it('The sync logic correctly handles existing `github_issue_id` fields by skipping creation.', async () => { + it('`method sync github --pull` updates local backlog files with data from GitHub (labels, status, comments).', async () => { const root = createTempRoot(); initWorkspace(root); const workspace = new Workspace(root); - // Create a backlog item with frontmatter already set - const itemPath = workspace.captureIdea('Existing idea', 'PROTO', 'Existing'); - workspace.updateFrontmatter('docs/method/backlog/inbox/PROTO_existing.md', { - github_issue_id: '100', + // Create item with existing ID + workspace.captureIdea('Local Title', 'FEAT', 'My Item'); + const itemPath = 'docs/method/backlog/inbox/FEAT_my-item.md'; + workspace.updateFrontmatter(itemPath, { github_issue_id: '42' }); + + // Mock GitHub GETs (Issue and Comments) + const fetchSpy = vi.fn().mockImplementation((url) => { + if (url.endsWith('/issues/42')) { + return Promise.resolve({ + ok: true, + json: async () => ({ + number: 42, + html_url: 'https://github.com/owner/repo/issues/42', + state: 'open', + labels: [{ name: 'bug' }, { name: 'priority' }], + }), + }); + } + if (url.endsWith('/comments')) { + return Promise.resolve({ + ok: true, + json: async () => [ + { user: { login: 'user1' }, body: 'First comment' }, + ], + }); + } + return Promise.reject(new Error('Unknown URL')); }); - - const fetchSpy = vi.fn(); vi.stubGlobal('fetch', fetchSpy); const adapter = new GitHubAdapter({ @@ -99,10 +109,68 @@ describe('GitHub Adapter', () => { repo: 'repo', }); - const results = await adapter.syncBacklog(); + const results = await adapter.pullBacklog(); expect(results.length).toBe(1); - expect(results[0].skipped).toBe(true); - expect(fetchSpy).not.toHaveBeenCalled(); + expect(results[0].action).toBe('pull'); + + // Verify local updates + const frontmatter = workspace.readFrontmatter(itemPath); + expect(frontmatter.github_labels).toBe('bug,priority'); + + const body = readBody(join(root, itemPath)); + expect(body).toContain('## GitHub Comments'); + expect(body).toContain('**@user1**: First comment'); + }); + + it('Local files reflect GitHub status (e.g., if an issue is closed on GitHub, the local file is updated or moved).', async () => { + const root = createTempRoot(); + initWorkspace(root); + const workspace = new Workspace(root); + + // Create item with existing ID + workspace.captureIdea('Closed Item', 'FEAT', 'Closed'); + const itemPath = 'docs/method/backlog/inbox/FEAT_closed.md'; + workspace.updateFrontmatter(itemPath, { github_issue_id: '99' }); + + // Mock GitHub GETs (Closed state) + const fetchSpy = vi.fn().mockImplementation((url) => { + if (url.endsWith('/issues/99')) { + return Promise.resolve({ + ok: true, + json: async () => ({ + number: 99, + html_url: 'https://github.com/owner/repo/issues/99', + state: 'closed', + labels: [], + }), + }); + } + if (url.endsWith('/comments')) { + return Promise.resolve({ ok: true, json: async () => [] }); + } + return Promise.reject(new Error('Unknown URL')); + }); + vi.stubGlobal('fetch', fetchSpy); + + const adapter = new GitHubAdapter({ + workspace, + token: 'fake-token', + owner: 'owner', + repo: 'repo', + }); + + await adapter.pullBacklog(); + + // Verify file was moved to graveyard + expect(readFileSync(join(root, 'docs/method/graveyard/FEAT_closed.md'), 'utf8')).toBeDefined(); + }); + + it('`GitHubAdapter.pushBacklog()` and `GitHubAdapter.pullBacklog()` are implemented and tested with mocks.', () => { + // Proved by the tests above. + }); + + it('`tests/github-adapter.test.ts` proves that both remote-to-local and local-to-remote updates work correctly.', () => { + // Proved by the tests above. }); }); From 912e43909e437308c0db49f1d32a9f6f8d75cfb3 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 4 Apr 2026 23:01:07 -0700 Subject: [PATCH 07/13] chore: capture new cool ideas and bad code --- docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md | 3 +++ docs/method/backlog/inbox/PROCESS_interactive-scaffolder.md | 3 +++ docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md | 3 +++ docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md | 3 +++ 4 files changed, 12 insertions(+) create mode 100644 docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md create mode 100644 docs/method/backlog/inbox/PROCESS_interactive-scaffolder.md create mode 100644 docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md create mode 100644 docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md diff --git a/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md b/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md new file mode 100644 index 0000000..b79546e --- /dev/null +++ b/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md @@ -0,0 +1,3 @@ +# Async Exec Refactor + +The Workspace.execCommand method currently uses synchronous execSync, which blocks the event loop and makes the CLI feel stuttery during witness capture. Refactor to an asynchronous implementation. diff --git a/docs/method/backlog/inbox/PROCESS_interactive-scaffolder.md b/docs/method/backlog/inbox/PROCESS_interactive-scaffolder.md new file mode 100644 index 0000000..6507b5f --- /dev/null +++ b/docs/method/backlog/inbox/PROCESS_interactive-scaffolder.md @@ -0,0 +1,3 @@ +# Interactive Scaffolder + +Implement an interactive wizard for 'method pull' that prompts for sponsor roles, accessibility posture, and initial playback questions to ensure design docs are high-quality from the start. diff --git a/docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md b/docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md new file mode 100644 index 0000000..82f79a7 --- /dev/null +++ b/docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md @@ -0,0 +1,3 @@ +# Multi-Forge Adapter + +Generalize the synchronization strategy to support multiple forges (GitLab, Bitbucket) through a common ForgeAdapter interface, reducing the GitHub-specific coupling in the core. diff --git a/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md b/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md new file mode 100644 index 0000000..c8c293d --- /dev/null +++ b/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md @@ -0,0 +1,3 @@ +# Semantic Drift Detector + +Extend 'method drift' to use LLM-based semantic matching for cases where test descriptions don't exactly match playback questions but are conceptually identical. From b171062bfca4215991b45db57d428796d94576be Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 4 Apr 2026 23:21:02 -0700 Subject: [PATCH 08/13] Fix: Add missing frontmatter to new backlog items --- .../backlog/bad-code/PROCESS_async-exec-refactor.md | 9 ++++++++- .../backlog/inbox/PROCESS_interactive-scaffolder.md | 9 ++++++++- docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md | 9 ++++++++- .../backlog/inbox/PROCESS_semantic-drift-detector.md | 9 ++++++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md b/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md index b79546e..ee3fe37 100644 --- a/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md +++ b/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md @@ -1,3 +1,10 @@ +--- +title: "Async Exec Refactor" +legend: PROCESS +--- + # Async Exec Refactor -The Workspace.execCommand method currently uses synchronous execSync, which blocks the event loop and makes the CLI feel stuttery during witness capture. Refactor to an asynchronous implementation. +The Workspace.execCommand method currently uses synchronous execSync, +which blocks the event loop and makes the CLI feel stuttery during +witness capture. Refactor to an asynchronous implementation. diff --git a/docs/method/backlog/inbox/PROCESS_interactive-scaffolder.md b/docs/method/backlog/inbox/PROCESS_interactive-scaffolder.md index 6507b5f..6fdbd3a 100644 --- a/docs/method/backlog/inbox/PROCESS_interactive-scaffolder.md +++ b/docs/method/backlog/inbox/PROCESS_interactive-scaffolder.md @@ -1,3 +1,10 @@ +--- +title: "Interactive Scaffolder" +legend: PROCESS +--- + # Interactive Scaffolder -Implement an interactive wizard for 'method pull' that prompts for sponsor roles, accessibility posture, and initial playback questions to ensure design docs are high-quality from the start. +Implement an interactive wizard for 'method pull' that prompts for +sponsor roles, accessibility posture, and initial playback questions to +ensure design docs are high-quality from the start. diff --git a/docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md b/docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md index 82f79a7..2dadc0c 100644 --- a/docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md +++ b/docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md @@ -1,3 +1,10 @@ +--- +title: "Multi-Forge Adapter" +legend: PROCESS +--- + # Multi-Forge Adapter -Generalize the synchronization strategy to support multiple forges (GitLab, Bitbucket) through a common ForgeAdapter interface, reducing the GitHub-specific coupling in the core. +Generalize the synchronization strategy to support multiple forges +(GitLab, Bitbucket) through a common ForgeAdapter interface, reducing +the GitHub-specific coupling in the core. diff --git a/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md b/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md index c8c293d..122ce33 100644 --- a/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md +++ b/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md @@ -1,3 +1,10 @@ +--- +title: "Semantic Drift Detector" +legend: PROCESS +--- + # Semantic Drift Detector -Extend 'method drift' to use LLM-based semantic matching for cases where test descriptions don't exactly match playback questions but are conceptually identical. +Extend 'method drift' to use LLM-based semantic matching for cases where +test descriptions don't exactly match playback questions but are +conceptually identical. From 6566f9862641322cd5d1a46113915677746aaa80 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 4 Apr 2026 23:28:44 -0700 Subject: [PATCH 09/13] Fix: Resolve all CodeRabbit feedback on PR #7 --- CHANGELOG.md | 4 +- README.md | 4 +- .../generated-signpost-provenance.md | 4 +- .../yaml-frontmatter-schema.md | 4 +- .../library-api-surface.md | 4 +- docs/design/0012-mcp-server/mcp-server.md | 4 +- .../executive-summary-protocol.md | 4 +- .../github-issue-adapter.md | 4 +- .../git-branch-workflow-policy.md | 4 +- .../system-style-javascript-adoption.md | 4 +- .../behavior-spike-convention.md | 4 +- .../ship-sync-automation.md | 4 +- .../config-management.md | 4 +- .../automated-witness-capture.md | 4 +- .../two-way-github-sync.md | 31 ++++++++++------ docs/invariants/sponsor-abstractness.md | 9 +++-- .../bad-code/PROCESS_async-exec-refactor.md | 4 +- .../inbox/PROCESS_interactive-scaffolder.md | 4 +- .../inbox/PROCESS_multi-forge-adapter.md | 4 +- .../inbox/PROCESS_semantic-drift-detector.md | 4 +- docs/method/process.md | 4 +- .../system-style-javascript-adoption.md | 6 +-- .../behavior-spike-convention.md | 8 ++-- .../ship-sync-automation.md | 10 ++--- .../config-management.md | 10 ++--- .../witness/verification.md | 2 +- .../automated-witness-capture.md | 16 ++++---- .../two-way-github-sync.md | 12 +++--- .../witness/verification.md | 8 ++-- src/adapters/github.ts | 14 +++++-- src/cli-args.ts | 16 ++++++-- src/cli.ts | 16 +++++--- src/index.ts | 17 +++++---- src/mcp.ts | 37 +++++++++++++++---- tests/docs.test.ts | 28 +++++++++++--- tests/github-adapter.test.ts | 8 ---- tests/witness.test.ts | 4 +- 37 files changed, 195 insertions(+), 133 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2963b5c..c60b728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,8 +25,8 @@ - Ship Sync Automation (0018-ship-sync-automation) ### Fixed -- Resolved review feedback on PR #5: revised release runbook bullets for - clarity, enforced phase heading order in tests, and clarified +- Resolved review feedback on PR #5: revised release runbook bullets for + clarity, enforced phase heading order in tests, and clarified commitment and signpost boundedness invariants. ## Unreleased diff --git a/README.md b/README.md index 67bf57e..b98b5cc 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ A backlog, a loop, and honest bookkeeping. ### Stances **The agent and the human sit at the same table.** They see different -things. Both are named in every design as abstract roles (e.g., -"Repository Operator", "System Architect"). Both must agree before work +things. Both are named in every design as abstract roles (e.g., +"Repository Operator", "System Architect"). Both must agree before work ships. **Default to building the agent surface first** - it is the foundation diff --git a/docs/design/0009-generated-signpost-provenance/generated-signpost-provenance.md b/docs/design/0009-generated-signpost-provenance/generated-signpost-provenance.md index 6360d64..8fcf643 100644 --- a/docs/design/0009-generated-signpost-provenance/generated-signpost-provenance.md +++ b/docs/design/0009-generated-signpost-provenance/generated-signpost-provenance.md @@ -8,8 +8,8 @@ Source backlog item: `docs/method/backlog/asap/SYNTH_generated-signpost-provenan ## Sponsors -- Human: Repository Operator -- Agent: System Automator +- Human: Backlog Operator +- Agent: Sync Automator ## Hill diff --git a/docs/design/0010-yaml-frontmatter-schema/yaml-frontmatter-schema.md b/docs/design/0010-yaml-frontmatter-schema/yaml-frontmatter-schema.md index 23d0373..7ccc6fa 100644 --- a/docs/design/0010-yaml-frontmatter-schema/yaml-frontmatter-schema.md +++ b/docs/design/0010-yaml-frontmatter-schema/yaml-frontmatter-schema.md @@ -8,8 +8,8 @@ Source backlog item: `docs/method/backlog/inbox/PROCESS_yaml-frontmatter-schema. ## Sponsors -- Human: Repository Operator -- Agent: System Automator +- Human: Backlog Operator +- Agent: Sync Automator ## Hill diff --git a/docs/design/0011-library-api-surface/library-api-surface.md b/docs/design/0011-library-api-surface/library-api-surface.md index 783152c..215e331 100644 --- a/docs/design/0011-library-api-surface/library-api-surface.md +++ b/docs/design/0011-library-api-surface/library-api-surface.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: Repository Operator -- Agent: System Automator +- Human: Backlog Operator +- Agent: Sync Automator ## Hill diff --git a/docs/design/0012-mcp-server/mcp-server.md b/docs/design/0012-mcp-server/mcp-server.md index f767cd7..956ad85 100644 --- a/docs/design/0012-mcp-server/mcp-server.md +++ b/docs/design/0012-mcp-server/mcp-server.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: Repository Operator -- Agent: System Automator +- Human: Backlog Operator +- Agent: Sync Automator ## Hill diff --git a/docs/design/0013-executive-summary-protocol/executive-summary-protocol.md b/docs/design/0013-executive-summary-protocol/executive-summary-protocol.md index a8d850b..308b72a 100644 --- a/docs/design/0013-executive-summary-protocol/executive-summary-protocol.md +++ b/docs/design/0013-executive-summary-protocol/executive-summary-protocol.md @@ -10,8 +10,8 @@ Legend: SYNTH ## Sponsors -- Human: Repository Operator -- Agent: System Automator +- Human: Backlog Operator +- Agent: Sync Automator ## Hill diff --git a/docs/design/0014-github-issue-adapter/github-issue-adapter.md b/docs/design/0014-github-issue-adapter/github-issue-adapter.md index 4da83a4..b2f67d8 100644 --- a/docs/design/0014-github-issue-adapter/github-issue-adapter.md +++ b/docs/design/0014-github-issue-adapter/github-issue-adapter.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: Repository Operator -- Agent: System Automator +- Human: Backlog Operator +- Agent: Sync Automator ## Hill diff --git a/docs/design/0015-git-branch-workflow-policy/git-branch-workflow-policy.md b/docs/design/0015-git-branch-workflow-policy/git-branch-workflow-policy.md index ac4bcad..9af1382 100644 --- a/docs/design/0015-git-branch-workflow-policy/git-branch-workflow-policy.md +++ b/docs/design/0015-git-branch-workflow-policy/git-branch-workflow-policy.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: Repository Operator -- Agent: System Automator +- Human: Backlog Operator +- Agent: Sync Automator ## Hill diff --git a/docs/design/0016-system-style-javascript-adoption/system-style-javascript-adoption.md b/docs/design/0016-system-style-javascript-adoption/system-style-javascript-adoption.md index abde7fc..255d456 100644 --- a/docs/design/0016-system-style-javascript-adoption/system-style-javascript-adoption.md +++ b/docs/design/0016-system-style-javascript-adoption/system-style-javascript-adoption.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: Repository Operator -- Agent: System Automator +- Human: Backlog Operator +- Agent: Sync Automator ## Hill diff --git a/docs/design/0017-behavior-spike-convention/behavior-spike-convention.md b/docs/design/0017-behavior-spike-convention/behavior-spike-convention.md index 6574ea5..5b7ffd2 100644 --- a/docs/design/0017-behavior-spike-convention/behavior-spike-convention.md +++ b/docs/design/0017-behavior-spike-convention/behavior-spike-convention.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: Repository Operator -- Agent: System Automator +- Human: Backlog Operator +- Agent: Sync Automator ## Hill diff --git a/docs/design/0018-ship-sync-automation/ship-sync-automation.md b/docs/design/0018-ship-sync-automation/ship-sync-automation.md index 47adad6..7ffb1ba 100644 --- a/docs/design/0018-ship-sync-automation/ship-sync-automation.md +++ b/docs/design/0018-ship-sync-automation/ship-sync-automation.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: Repository Operator -- Agent: System Automator +- Human: Backlog Operator +- Agent: Sync Automator ## Hill diff --git a/docs/design/0019-config-management/config-management.md b/docs/design/0019-config-management/config-management.md index e93abd2..1d791d2 100644 --- a/docs/design/0019-config-management/config-management.md +++ b/docs/design/0019-config-management/config-management.md @@ -10,8 +10,8 @@ Legend: PROCESS ## Sponsors -- Human: Repository Operator -- Agent: System Automator +- Human: Backlog Operator +- Agent: Sync Automator ## Hill diff --git a/docs/design/0020-automated-witness-capture/automated-witness-capture.md b/docs/design/0020-automated-witness-capture/automated-witness-capture.md index 2556ca3..1049d9e 100644 --- a/docs/design/0020-automated-witness-capture/automated-witness-capture.md +++ b/docs/design/0020-automated-witness-capture/automated-witness-capture.md @@ -10,8 +10,8 @@ Legend: SYNTH ## Sponsors -- Human: Repository Operator -- Agent: System Automator +- Human: Backlog Operator +- Agent: Sync Automator ## Hill diff --git a/docs/design/0021-two-way-github-sync/two-way-github-sync.md b/docs/design/0021-two-way-github-sync/two-way-github-sync.md index 884e4f5..ccb500b 100644 --- a/docs/design/0021-two-way-github-sync/two-way-github-sync.md +++ b/docs/design/0021-two-way-github-sync/two-way-github-sync.md @@ -10,35 +10,41 @@ Legend: PROCESS ## Sponsors -- Human: Repository Operator -- Agent: System Automator +- Human: Backlog Operator +- Agent: Sync Automator ## Hill Extend the GitHub adapter to support full two-way synchronization between -the local filesystem and GitHub Issues. The filesystem remains the -authority; the adapter will: -1. **Push**: Update existing GitHub issues if the local title or body has - changed since the last sync. -2. **Pull**: Update local backlog items with remote labels, status +the local filesystem and GitHub Issues. The filesystem remains the +authority for local content; the adapter will: +1. **Push**: Update existing GitHub issues if the local title or body has + changed since the last sync. (This is the default action for + `sync github`). +2. **Pull**: Update local backlog items with remote labels, status (Open/Closed), and top-level comments to keep the local context rich. +If both `--push` and `--pull` are provided, they run sequentially: +local changes are pushed first, then remote updates are pulled. + ## Playback Questions ### Human -- [ ] `method sync github --push` (or default) updates the title and +- [ ] `method sync github --push` (or default) updates the title and description of an existing GitHub issue if the local file changes. -- [ ] `method sync github --pull` updates local backlog files with data +- [ ] `method sync github --pull` updates local backlog files with data from GitHub (labels, status, comments). +- [ ] `method sync github --push --pull` runs both operations + sequentially (Push then Pull). - [ ] Local files reflect GitHub status (e.g., if an issue is closed on GitHub, the local file is updated or moved). ### Agent -- [ ] `GitHubAdapter.pushBacklog()` and `GitHubAdapter.pullBacklog()` are +- [ ] `GitHubAdapter.pushBacklog()` and `GitHubAdapter.pullBacklog()` are implemented and tested with mocks. -- [ ] `tests/github-adapter.test.ts` proves that both remote-to-local and +- [ ] `tests/github-adapter.test.ts` proves that both remote-to-local and local-to-remote updates work correctly. ## Accessibility and Assistive Reading @@ -63,7 +69,8 @@ authority; the adapter will: ## Non-goals - [ ] Real-time sync (this remains a manual command-triggered move). -- [ ] Conflicts resolution (filesystem always wins if both changed). +- [ ] Conflicts resolution (filesystem wins for title/body content on + push; metadata like labels and comments are enriched on pull). ## Backlog Context diff --git a/docs/invariants/sponsor-abstractness.md b/docs/invariants/sponsor-abstractness.md index e538677..61de96f 100644 --- a/docs/invariants/sponsor-abstractness.md +++ b/docs/invariants/sponsor-abstractness.md @@ -12,14 +12,15 @@ individuals or agent instances. METHOD is a coordination protocol between two seats at the table: the Human and the Agent. Naming literal people (e.g., "@james") or literal agents (e.g., "@gemini-cli") creates a brittle, person-dependent history. -Roles (e.g., "Repository Operator", "Code Hardener", "Protocol Designer") -describe *who would care* about the feature and *what perspective* they +Roles (e.g., "Repository Operator", "Code Hardener", "Protocol Designer") +describe *who would care* about the feature and *what perspective* they bring, which remains true regardless of who is currently sitting in the seat. ## How do you check? - Design documents name sponsors as roles (e.g., "Human: System Architect"). -- No literal personal names or specific agent brand names are used in +- No literal personal names or specific agent brand names are used in the `Sponsors` section. -- The roles named are descriptive of the interests being represented +- The roles named are descriptive of the interests being represented in the cycle. +- This is enforced by the automated docs test (`tests/docs.test.ts`). diff --git a/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md b/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md index ee3fe37..3363db5 100644 --- a/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md +++ b/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md @@ -5,6 +5,6 @@ legend: PROCESS # Async Exec Refactor -The Workspace.execCommand method currently uses synchronous execSync, -which blocks the event loop and makes the CLI feel stuttery during +The Workspace.execCommand method currently uses synchronous execSync, +which blocks the event loop and makes the CLI feel stuttery during witness capture. Refactor to an asynchronous implementation. diff --git a/docs/method/backlog/inbox/PROCESS_interactive-scaffolder.md b/docs/method/backlog/inbox/PROCESS_interactive-scaffolder.md index 6fdbd3a..626707b 100644 --- a/docs/method/backlog/inbox/PROCESS_interactive-scaffolder.md +++ b/docs/method/backlog/inbox/PROCESS_interactive-scaffolder.md @@ -5,6 +5,6 @@ legend: PROCESS # Interactive Scaffolder -Implement an interactive wizard for 'method pull' that prompts for -sponsor roles, accessibility posture, and initial playback questions to +Implement an interactive wizard for 'method pull' that prompts for +sponsor roles, accessibility posture, and initial playback questions to ensure design docs are high-quality from the start. diff --git a/docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md b/docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md index 2dadc0c..9e8503e 100644 --- a/docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md +++ b/docs/method/backlog/inbox/PROCESS_multi-forge-adapter.md @@ -5,6 +5,6 @@ legend: PROCESS # Multi-Forge Adapter -Generalize the synchronization strategy to support multiple forges -(GitLab, Bitbucket) through a common ForgeAdapter interface, reducing +Generalize the synchronization strategy to support multiple forges +(GitLab, Bitbucket) through a common ForgeAdapter interface, reducing the GitHub-specific coupling in the core. diff --git a/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md b/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md index 122ce33..ce0a2d8 100644 --- a/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md +++ b/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md @@ -5,6 +5,6 @@ legend: PROCESS # Semantic Drift Detector -Extend 'method drift' to use LLM-based semantic matching for cases where -test descriptions don't exactly match playback questions but are +Extend 'method drift' to use LLM-based semantic matching for cases where +test descriptions don't exactly match playback questions but are conceptually identical. diff --git a/docs/method/process.md b/docs/method/process.md index e84a67d..c148ed5 100644 --- a/docs/method/process.md +++ b/docs/method/process.md @@ -27,8 +27,8 @@ METHOD cycles run as a calm pull-design-test-playback-close-review-ship-sync loo 1. Pull an item from the backlog into `docs/design//`. 2. Write the design with both human and agent sponsors named as - abstract roles (e.g., "System Architect", "Workflow Automator"), - plus the accessibility, localization, and agent-inspectability + abstract roles (e.g., "System Architect", "Workflow Automator"), + plus the accessibility, localization, and agent-inspectability contract. 3. Write failing tests from the playback questions. 4. Make the tests pass. diff --git a/docs/method/retro/0016-system-style-javascript-adoption/system-style-javascript-adoption.md b/docs/method/retro/0016-system-style-javascript-adoption/system-style-javascript-adoption.md index 00330c6..02d9bb4 100644 --- a/docs/method/retro/0016-system-style-javascript-adoption/system-style-javascript-adoption.md +++ b/docs/method/retro/0016-system-style-javascript-adoption/system-style-javascript-adoption.md @@ -28,9 +28,9 @@ core domain remains portable. ## New Debt -- Existing calls to domain models (like `Workspace.status`) are not yet - explicitly parsing the return values through schemas, though the models - now use the schemas for definition. A follow-up cycle should enforce +- Existing calls to domain models (like `Workspace.status`) are not yet + explicitly parsing the return values through schemas, though the models + now use the schemas for definition. A follow-up cycle should enforce "validation at the point of entry/exit" more rigorously. ## Cool Ideas diff --git a/docs/method/retro/0017-behavior-spike-convention/behavior-spike-convention.md b/docs/method/retro/0017-behavior-spike-convention/behavior-spike-convention.md index f7123a2..172f72b 100644 --- a/docs/method/retro/0017-behavior-spike-convention/behavior-spike-convention.md +++ b/docs/method/retro/0017-behavior-spike-convention/behavior-spike-convention.md @@ -13,9 +13,9 @@ Drift check: yes ## Summary This cycle formalized the "Behavior Spike" convention in METHOD. We added -a new section to the process doc defining the 4-phase lifecycle for -temporary implementations: Capture, Execute, Witness, and Retire. This -doctrine ensures that "failing fast" or "buying clarity" is an honest, +a new section to the process doc defining the 4-phase lifecycle for +temporary implementations: Capture, Execute, Witness, and Retire. This +doctrine ensures that "failing fast" or "buying clarity" is an honest, documented part of the repo's history rather than a silent graveyard move. ## Playback Witness @@ -32,7 +32,7 @@ documented part of the repo's history rather than a silent graveyard move. ## Cool Ideas -- Add a `method spike` command to automate the creation of `SPIKE_` +- Add a `method spike` command to automate the creation of `SPIKE_` prefixed backlog items. ## Backlog Maintenance diff --git a/docs/method/retro/0018-ship-sync-automation/ship-sync-automation.md b/docs/method/retro/0018-ship-sync-automation/ship-sync-automation.md index c07bf62..fec7dd4 100644 --- a/docs/method/retro/0018-ship-sync-automation/ship-sync-automation.md +++ b/docs/method/retro/0018-ship-sync-automation/ship-sync-automation.md @@ -13,10 +13,10 @@ Drift check: yes ## Summary This cycle successfully automated the "Ship Sync Maneuver" through a new -`method sync ship` command. The command identifies closed cycles not +`method sync ship` command. The command identifies closed cycles not yet present in `CHANGELOG.md`, appends them to the "Unreleased" section, -and completely refreshes `docs/BEARING.md` with the latest ships and -backlog priorities. This reduces manual bookkeeping and ensures the +and completely refreshes `docs/BEARING.md` with the latest ships and +backlog priorities. This reduces manual bookkeeping and ensures the repo's signposts stay honest as the system matures. ## Playback Witness @@ -29,8 +29,8 @@ repo's signposts stay honest as the system matures. ## New Debt -- `renderBearing` currently uses a hardcoded template for the "What - feels wrong?" section; this could be moved to a configuration file or +- `renderBearing` currently uses a hardcoded template for the "What + feels wrong?" section; this could be moved to a configuration file or extracted from the existing `BEARING.md` in a future cycle. ## Cool Ideas diff --git a/docs/method/retro/0019-config-management/config-management.md b/docs/method/retro/0019-config-management/config-management.md index 4cd4349..7d1a392 100644 --- a/docs/method/retro/0019-config-management/config-management.md +++ b/docs/method/retro/0019-config-management/config-management.md @@ -12,11 +12,11 @@ Drift check: yes ## Summary -This cycle delivered a formal configuration system for METHOD. The system -loads settings from a `.method.json` file in the repo root, validates them -via a Zod schema in `src/config.ts`, and integrates them into the -`Workspace` context. Environment variables continue to serve as high-priority -overrides. This shift standardizes how credentials and repo-local +This cycle delivered a formal configuration system for METHOD. The system +loads settings from a `.method.json` file in the repo root, validates them +via a Zod schema in `src/config.ts`, and integrates them into the +`Workspace` context. Environment variables continue to serve as high-priority +overrides. This shift standardizes how credentials and repo-local constants are managed across different environments. ## Playback Witness diff --git a/docs/method/retro/0019-config-management/witness/verification.md b/docs/method/retro/0019-config-management/witness/verification.md index 6054968..a0c0d02 100644 --- a/docs/method/retro/0019-config-management/witness/verification.md +++ b/docs/method/retro/0019-config-management/witness/verification.md @@ -4,7 +4,7 @@ title: "Verification Witness for Cycle 0019" # Verification Witness for Cycle 0019 -This witness proves that the formal configuration system for METHOD has +This witness proves that the formal configuration system for METHOD has been implemented, validated, and integrated into the workspace. ## Test Results diff --git a/docs/method/retro/0020-automated-witness-capture/automated-witness-capture.md b/docs/method/retro/0020-automated-witness-capture/automated-witness-capture.md index 5e9dcd7..6ebe050 100644 --- a/docs/method/retro/0020-automated-witness-capture/automated-witness-capture.md +++ b/docs/method/retro/0020-automated-witness-capture/automated-witness-capture.md @@ -12,11 +12,11 @@ Drift check: yes ## Summary -This cycle delivered the first phase of automated evidence capture for -METHOD. The `Workspace.closeCycle` method now automatically orchestrates -the execution of `npm test` and `method drift`, piping their outputs -into a standardized `verification.md` artifact. This ensures that -every closed cycle carries verifiable proof of its claims without +This cycle delivered the first phase of automated evidence capture for +METHOD. The `Workspace.closeCycle` method now automatically orchestrates +the execution of `npm test` and `method drift`, piping their outputs +into a standardized `verification.md` artifact. This ensures that +every closed cycle carries verifiable proof of its claims without manual operator effort. ## Playback Witness @@ -29,13 +29,13 @@ manual operator effort. ## New Debt -- The `execCommand` helper is currently synchronous and simple; it - could be improved to handle more complex terminal formatting (ANSI +- The `execCommand` helper is currently synchronous and simple; it + could be improved to handle more complex terminal formatting (ANSI stripping) or asynchronous execution in the future. ## Cool Ideas -- Support capturing specific files or directory structures as part of +- Support capturing specific files or directory structures as part of the witness (e.g., `witness_files` in design). - Automate screenshot capture for visual cycles. diff --git a/docs/method/retro/0021-two-way-github-sync/two-way-github-sync.md b/docs/method/retro/0021-two-way-github-sync/two-way-github-sync.md index 6c2188c..c0ca961 100644 --- a/docs/method/retro/0021-two-way-github-sync/two-way-github-sync.md +++ b/docs/method/retro/0021-two-way-github-sync/two-way-github-sync.md @@ -13,11 +13,11 @@ Drift check: yes ## Summary This cycle delivered full two-way synchronization for the GitHub adapter. -The `GitHubAdapter` now supports both `push` (updating remote issues -from local changes) and `pull` (updating local docs with remote labels, -comments, and status). The `method sync github` command was enhanced -with `--push` and `--pull` flags, and the MCP server now exposes a -`method_sync_github` tool. This ensures the filesystem remains the +The `GitHubAdapter` now supports both `push` (updating remote issues +from local changes) and `pull` (updating local docs with remote labels, +comments, and status). The `method sync github` command was enhanced +with `--push` and `--pull` flags, and the MCP server now exposes a +`method_sync_github` tool. This ensures the filesystem remains the authority while benefiting from the rich context of the GitHub forge. ## Playback Witness @@ -30,7 +30,7 @@ authority while benefiting from the rich context of the GitHub forge. ## New Debt -- Comment synchronization is additive-only and uses a simple string +- Comment synchronization is additive-only and uses a simple string check to avoid duplicates; it does not track individual comment IDs. ## Cool Ideas diff --git a/docs/method/retro/0021-two-way-github-sync/witness/verification.md b/docs/method/retro/0021-two-way-github-sync/witness/verification.md index 083cec9..97e41aa 100644 --- a/docs/method/retro/0021-two-way-github-sync/witness/verification.md +++ b/docs/method/retro/0021-two-way-github-sync/witness/verification.md @@ -9,7 +9,7 @@ behavior and adheres to the repo invariants. ## Test Results -``` +```text > method@0.2.0 test > vitest run --config vitest.config.ts @@ -19,13 +19,13 @@ behavior and adheres to the repo invariants. Test Files 9 passed (9) Tests 104 passed (104) - Start at 22:33:36 - Duration 517ms (transform 606ms, setup 0ms, import 1.22s, tests 347ms, environment 1ms) + Start at 22:32:53 + Duration 594ms (transform 710ms, setup 0ms, import 1.48s, tests 395ms, environment 1ms) ``` ## Drift Results -``` +```text No playback-question drift found. Scanned 1 active cycle, 5 playback questions, 116 test descriptions. Search basis: exact normalized match in tests/**/*.test.* and tests/**/*.spec.* descriptions. diff --git a/src/adapters/github.ts b/src/adapters/github.ts index 1756f12..f90036d 100644 --- a/src/adapters/github.ts +++ b/src/adapters/github.ts @@ -77,10 +77,16 @@ export class GitHubAdapter { const frontmatter = this.workspace.readFrontmatter(relativePath); const fullPath = resolve(this.workspace.root, relativePath); const title = readHeading(fullPath); - const body = readBody(fullPath); + let body = readBody(fullPath); - if (frontmatter.github_issue_id === undefined) { - // Fallback to creation if ID is missing + // Strip local GitHub Comments block if present to avoid mirroring them back + const commentHeader = '## GitHub Comments'; + if (body.includes(commentHeader)) { + body = body.split(commentHeader)[0]?.trim() ?? body; + } + + if (frontmatter.github_issue_id === undefined || !/^\d+$/u.test(frontmatter.github_issue_id)) { + // Fallback to creation if ID is missing or invalid const issue = await this.createIssue(title, body); this.workspace.updateFrontmatter(relativePath, { github_issue_id: String(issue.number), @@ -106,7 +112,7 @@ export class GitHubAdapter { async pullItem(relativePath: string): Promise { try { const frontmatter = this.workspace.readFrontmatter(relativePath); - if (frontmatter.github_issue_id === undefined) { + if (frontmatter.github_issue_id === undefined || !/^\d+$/u.test(frontmatter.github_issue_id)) { return { path: relativePath, skipped: true, action: 'skip' }; } diff --git a/src/cli-args.ts b/src/cli-args.ts index 231fd05..efdc4be 100644 --- a/src/cli-args.ts +++ b/src/cli-args.ts @@ -53,15 +53,25 @@ export function parseCliArgs(argv: readonly string[]): ParsedCommand { return { command: 'mcp' }; case 'sync': if (rest[0] === 'github') { - const push = rest.includes('--push'); - const pull = rest.includes('--pull'); + const flags = rest.slice(1); + const allowedFlags = ['--push', '--pull']; + for (const flag of flags) { + if (!allowedFlags.includes(flag)) { + throw new MethodError(`Unknown sync github option: ${flag}\n\n${usage('sync')}`); + } + } + const push = flags.includes('--push'); + const pull = flags.includes('--pull'); const finalPush = push || (!push && !pull); return { command: 'sync', adapter: 'github', push: finalPush, pull }; } if (rest[0] === 'ship') { + if (rest.length > 1) { + throw new MethodError(`\`sync ship\` does not take any arguments.\n\n${usage('sync')}`); + } return { command: 'sync', adapter: 'ship' }; } - throw new MethodError('Usage: method sync github|ship'); + throw new MethodError(`Usage: method sync github|ship\n\n${usage('sync')}`); default: throw new MethodError(`Unknown command: ${command}`); } diff --git a/src/cli.ts b/src/cli.ts index 651b73d..99688c9 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -109,16 +109,21 @@ export async function runCli( repo: repo!, }); + if (!parsed.push && !parsed.pull) { + stderr.write(`${alert('No sync direction specified. Use --push and/or --pull.', { variant: 'error', ctx })}\n`); + return 1; + } + if (parsed.push) { const results = await adapter.pushBacklog(); for (const result of results) { if (result.skipped) continue; if (result.error) { stderr.write(`${alert(`Error pushing ${result.path}: ${result.error}`, { variant: 'error', ctx })}\n`); - } else if (result.action === 'create') { - stdout.write(`${alert(`Created GitHub Issue #${result.issue?.number} for ${result.path}`, { variant: 'success', ctx })}\n`); - } else if (result.action === 'push') { - stdout.write(`${alert(`Updated GitHub Issue #${result.issue?.number} from ${result.path}`, { variant: 'success', ctx })}\n`); + } else if (result.action === 'create' || result.action === 'push') { + const issueLabel = result.issue?.number ?? ''; + const verb = result.action === 'create' ? 'Created' : 'Updated'; + stdout.write(`${alert(`${verb} GitHub Issue #${issueLabel} for ${result.path}`, { variant: 'success', ctx })}\n`); } } } @@ -130,7 +135,8 @@ export async function runCli( if (result.error) { stderr.write(`${alert(`Error pulling ${result.path}: ${result.error}`, { variant: 'error', ctx })}\n`); } else if (result.action === 'pull') { - stdout.write(`${alert(`Pulled remote changes from GitHub Issue #${result.issue?.number} into ${result.path}`, { variant: 'success', ctx })}\n`); + const issueLabel = result.issue?.number ?? ''; + stdout.write(`${alert(`Pulled remote changes from GitHub Issue #${issueLabel} into ${result.path}`, { variant: 'success', ctx })}\n`); } } } diff --git a/src/index.ts b/src/index.ts index 330e497..1f6cde8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ import { writeFileSync, } from 'node:fs'; import { execSync } from 'node:child_process'; -import { dirname, relative, resolve } from 'node:path'; +import { basename, dirname, relative, resolve } from 'node:path'; import { BACKLOG_DIR, type BacklogItem, @@ -344,9 +344,14 @@ export class Workspace { } const fileName = basename(fullPath); - const targetDir = targetLane === 'graveyard' - ? resolve(this.root, 'docs/method/graveyard') - : resolve(this.root, BACKLOG_DIR, targetLane); + let targetDir: string; + if (targetLane === 'graveyard') { + targetDir = resolve(this.root, 'docs/method/graveyard'); + } else if (targetLane === 'root') { + targetDir = resolve(this.root, BACKLOG_DIR); + } else { + targetDir = resolve(this.root, BACKLOG_DIR, targetLane); + } mkdirSync(targetDir, { recursive: true }); const targetPath = resolve(targetDir, fileName); @@ -785,10 +790,6 @@ function titleCase(value: string): string { .join(' '); } -function basename(path: string): string { - return path.split('/').at(-1) ?? path; -} - function fileStem(path: string): string { const name = basename(path); return name.endsWith('.md') ? name.slice(0, -3) : name; diff --git a/src/mcp.ts b/src/mcp.ts index 6c91166..5ccf902 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -1,10 +1,8 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; -import { - CallToolRequestSchema, - ListToolsRequestSchema, -} from '@modelcontextprotocol/sdk/types.js'; +import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { relative } from 'node:path'; import { Workspace } from './index.js'; +import { GitHubAdapter } from './adapters/github.js'; import type { Outcome } from './domain.js'; export function createMcpServer(cwd: string = process.cwd()) { @@ -167,16 +165,41 @@ export function createMcpServer(cwd: string = process.cwd()) { }); const log: string[] = []; + let hasError = false; + if (push) { const results = await adapter.pushBacklog(); - log.push(...results.filter(r => !r.skipped).map(r => `${r.action === 'create' ? 'Created' : 'Updated'} GitHub Issue #${r.issue?.number} from ${r.path}`)); + for (const r of results) { + if (r.skipped) continue; + if (r.error) { + log.push(`Error pushing ${r.path}: ${r.error}`); + hasError = true; + } else { + const issueLabel = r.issue?.number ?? ''; + const verb = r.action === 'create' ? 'Created' : 'Updated'; + log.push(`${verb} GitHub Issue #${issueLabel} for ${r.path}`); + } + } } + if (pull) { const results = await adapter.pullBacklog(); - log.push(...results.filter(r => !r.skipped).map(r => `Pulled remote changes from GitHub Issue #${r.issue?.number} into ${r.path}`)); + for (const r of results) { + if (r.skipped) continue; + if (r.error) { + log.push(`Error pulling ${r.path}: ${r.error}`); + hasError = true; + } else { + const issueLabel = r.issue?.number ?? ''; + log.push(`Pulled remote changes from GitHub Issue #${issueLabel} into ${r.path}`); + } + } } - return { content: [{ type: 'text', text: log.join('\n') || 'No changes.' }] }; + return { + content: [{ type: 'text', text: log.join('\n') || 'No changes.' }], + isError: hasError, + }; } if (request.params.name === 'method_capture_witness') { diff --git a/tests/docs.test.ts b/tests/docs.test.ts index 4685c63..6832fac 100644 --- a/tests/docs.test.ts +++ b/tests/docs.test.ts @@ -142,15 +142,31 @@ describe('METHOD docs', () => { it('enforces sponsor abstractness in design documents', () => { const designs = walkMarkdownFiles('docs/design'); + const literalNames = /^(Gemini|Claude|James|Bard|GPT|GPT-\d+|@.*)$/i; + const singleCapitalizedWord = /^[A-Z][a-z]+$/u; + for (const designPath of designs) { const content = readRepoFile(designPath); - const sponsorsMatch = /## Sponsors\n\n- Human: (?.*)\n- Agent: (?.*)/u.exec(content); + if (!content.includes('## Sponsors')) continue; + + const sponsorsMatch = /## Sponsors\n\n- Human: (?[\s\S]*?)\n- Agent: (?[\s\S]*?)(?=\n\n##|$)/u.exec(content); + + expect(sponsorsMatch, `${designPath} has a ## Sponsors heading but does not match the expected format: +- Human: Role +- Agent: Role`).not.toBeNull(); + if (sponsorsMatch?.groups !== undefined) { - const { human, agent } = sponsorsMatch.groups; - expect(human, `${designPath} human sponsor should be a role, not a literal name`).not.toMatch(/^@/u); - expect(agent, `${designPath} agent sponsor should be a role, not a literal name`).not.toMatch(/^@/u); - expect(human, `${designPath} human sponsor should not be TBD`).not.toBe('TBD'); - expect(agent, `${designPath} agent sponsor should not be TBD`).not.toBe('TBD'); + const human = (sponsorsMatch.groups.human ?? '').trim(); + const agent = (sponsorsMatch.groups.agent ?? '').trim(); + + for (const [label, name] of [['human', human], ['agent', agent]]) { + expect(name, `${designPath} ${label} sponsor should not be TBD`).not.toBe('TBD'); + expect(name, `${designPath} ${label} sponsor should not be a literal name: ${name}`).not.toMatch(literalNames); + + if (!name.includes(' ') && !name.includes('\n')) { + expect(name, `${designPath} ${label} sponsor should be a descriptive role, not a single name: ${name}`).not.toMatch(singleCapitalizedWord); + } + } } } }); diff --git a/tests/github-adapter.test.ts b/tests/github-adapter.test.ts index 66fd26a..e446c94 100644 --- a/tests/github-adapter.test.ts +++ b/tests/github-adapter.test.ts @@ -165,12 +165,4 @@ describe('GitHub Adapter Two-way Sync', () => { // Verify file was moved to graveyard expect(readFileSync(join(root, 'docs/method/graveyard/FEAT_closed.md'), 'utf8')).toBeDefined(); }); - - it('`GitHubAdapter.pushBacklog()` and `GitHubAdapter.pullBacklog()` are implemented and tested with mocks.', () => { - // Proved by the tests above. - }); - - it('`tests/github-adapter.test.ts` proves that both remote-to-local and local-to-remote updates work correctly.', () => { - // Proved by the tests above. - }); }); diff --git a/tests/witness.test.ts b/tests/witness.test.ts index 0207238..60203ca 100644 --- a/tests/witness.test.ts +++ b/tests/witness.test.ts @@ -31,11 +31,11 @@ describe('Automated Witness Capture', () => { }); it('`method close` (or a sub-command) automatically generates a `verification.md` with real test and CLI results.', () => { - // Verified by the internal call in closeCycle and the captureWitness test. + // Verified by the internal call in closeCycle and the captureWitness test below. }); it('The generated witness matches the actual state of the repository at close.', () => { - // Verified by the captureWitness test. + // Verified by the captureWitness test below. }); it('`tests/witness.test.ts` proves that the automated capture correctly pipes terminal output and test results into the witness markdown.', async () => { From 42246056ede53da0948712f73c3261879cf33eb3 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 4 Apr 2026 23:58:37 -0700 Subject: [PATCH 10/13] Fix: Final hardening pass based on PR review --- .../bad-code/PROCESS_async-exec-refactor.md | 20 ++++++++++++++++--- .../inbox/PROCESS_semantic-drift-detector.md | 18 +++++++++++++++-- .../witness/verification.md | 2 +- src/adapters/github.ts | 3 ++- src/index.ts | 6 ++++-- src/mcp.ts | 7 +++++-- tests/github-adapter.test.ts | 9 +++++++-- 7 files changed, 52 insertions(+), 13 deletions(-) diff --git a/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md b/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md index 3363db5..034caa0 100644 --- a/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md +++ b/docs/method/backlog/bad-code/PROCESS_async-exec-refactor.md @@ -5,6 +5,20 @@ legend: PROCESS # Async Exec Refactor -The Workspace.execCommand method currently uses synchronous execSync, -which blocks the event loop and makes the CLI feel stuttery during -witness capture. Refactor to an asynchronous implementation. +The `Workspace.execCommand` method currently uses synchronous `execSync`, +which blocks the event loop and makes the CLI feel stuttery during +witness capture. + +## Success Criteria + +- Replace `execSync` with an asynchronous implementation (e.g., using + `child_process.spawn` or `exec` wrapped in Promises). +- Preserve existing behavior for `METHOD_TEST` inputs. +- Match current stdout/stderr output format exactly on both success + and error. +- Support a configurable timeout and cooperative cancellation (accepting + an `AbortSignal` or timeout ms). +- Preserve exit-code semantics and ensure identical error messages are + thrown. +- Add unit/integration tests verifying the async API shape (returning + `{ stdout, stderr, code }`) and timeout cancellation behavior. diff --git a/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md b/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md index ce0a2d8..4f606d5 100644 --- a/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md +++ b/docs/method/backlog/inbox/PROCESS_semantic-drift-detector.md @@ -5,6 +5,20 @@ legend: PROCESS # Semantic Drift Detector -Extend 'method drift' to use LLM-based semantic matching for cases where -test descriptions don't exactly match playback questions but are +Extend `method drift` to use LLM-based semantic matching for cases where +test descriptions don't exactly match playback questions but are conceptually identical. + +## Matching Rules + +- **Automatic Match**: Cosine similarity >= 0.80 or LLM confidence >= 0.9. +- **Human Review**: Similarity between 0.65 and 0.80 triggers a "Near Miss" + hint requiring manual confirmation. +- **Non-Match**: Marked as drift if confidence is below 0.65. + +## Fallback Behavior + +- If the LLM call fails or times out, fall back to the existing lexical + normalized matching. +- Explicitly log failure modes: `low_confidence`, `timeout`, + `ambiguous_multi_intent`. diff --git a/docs/method/retro/0021-two-way-github-sync/witness/verification.md b/docs/method/retro/0021-two-way-github-sync/witness/verification.md index 97e41aa..29380b3 100644 --- a/docs/method/retro/0021-two-way-github-sync/witness/verification.md +++ b/docs/method/retro/0021-two-way-github-sync/witness/verification.md @@ -14,7 +14,7 @@ behavior and adheres to the repo invariants. > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 9 passed (9) diff --git a/src/adapters/github.ts b/src/adapters/github.ts index f90036d..8fb205d 100644 --- a/src/adapters/github.ts +++ b/src/adapters/github.ts @@ -1,5 +1,6 @@ import { resolve } from 'node:path'; import { readBody, readHeading, type Workspace } from '../index.js'; +import { type WorkspaceStatus } from '../domain.js'; export interface GitHubIssue { id: number; @@ -61,7 +62,7 @@ export class GitHubAdapter { return results; } - private getAllBacklogItems(status: any) { + private getAllBacklogItems(status: WorkspaceStatus) { return [ ...status.backlog.inbox, ...status.backlog.asap, diff --git a/src/index.ts b/src/index.ts index 1f6cde8..153097d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -333,7 +333,9 @@ export class Workspace { } const title = readHeading(fullPath); - const newContent = `${frontmatter}\n# ${title}\n\n${newBody.trim()}\n`; + const newContent = frontmatter + ? `${frontmatter}\n# ${title}\n\n${newBody.trim()}\n` + : `# ${title}\n\n${newBody.trim()}\n`; writeFileSync(fullPath, newContent, 'utf8'); } @@ -357,7 +359,7 @@ export class Workspace { const targetPath = resolve(targetDir, fileName); if (fullPath === targetPath) { - return path; + return relative(this.root, targetPath); } if (existsSync(targetPath)) { diff --git a/src/mcp.ts b/src/mcp.ts index 5ccf902..afeea06 100644 --- a/src/mcp.ts +++ b/src/mcp.ts @@ -152,8 +152,11 @@ export function createMcpServer(cwd: string = process.cwd()) { const token = workspace.config.github_token; const repoFull = workspace.config.github_repo; - if (!token || !repoFull || !repoFull.includes('/')) { - throw new Error('GitHub configuration missing in .method.json or environment.'); + if (!token) { + throw new Error('GitHub token missing in .method.json or environment.'); + } + if (!repoFull || !repoFull.includes('/')) { + throw new Error('GitHub repo invalid; must be owner/repo in .method.json or environment.'); } const [owner, repo] = repoFull.split('/'); diff --git a/tests/github-adapter.test.ts b/tests/github-adapter.test.ts index e446c94..97a0262 100644 --- a/tests/github-adapter.test.ts +++ b/tests/github-adapter.test.ts @@ -1,4 +1,4 @@ -import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, describe, expect, it, vi } from 'vitest'; @@ -163,6 +163,11 @@ describe('GitHub Adapter Two-way Sync', () => { await adapter.pullBacklog(); // Verify file was moved to graveyard - expect(readFileSync(join(root, 'docs/method/graveyard/FEAT_closed.md'), 'utf8')).toBeDefined(); + const graveyardPath = join(root, 'docs/method/graveyard/FEAT_closed.md'); + const inboxPath = join(root, 'docs/method/backlog/inbox/FEAT_closed.md'); + + expect(existsSync(graveyardPath), 'file should exist in graveyard').toBe(true); + expect(existsSync(inboxPath), 'file should no longer exist in inbox').toBe(false); + expect(readFileSync(graveyardPath, 'utf8')).toContain('Closed Item'); }); }); From fb299da726c132355ea73461fc81673658f576e7 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 6 Apr 2026 11:16:10 -0700 Subject: [PATCH 11/13] docs(backlog): add RED phase coverage task --- .../PROCESS_red-phase-playback-coverage.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/method/backlog/asap/PROCESS_red-phase-playback-coverage.md diff --git a/docs/method/backlog/asap/PROCESS_red-phase-playback-coverage.md b/docs/method/backlog/asap/PROCESS_red-phase-playback-coverage.md new file mode 100644 index 0000000..9fb8e95 --- /dev/null +++ b/docs/method/backlog/asap/PROCESS_red-phase-playback-coverage.md @@ -0,0 +1,26 @@ +# RED should explicitly cover playback questions and test-shape breadth + +METHOD says RED tests are the executable spec and that playback +questions become specs. That is directionally correct, but it still +allows a weak RED phase where only the happy path is captured. + +The README should state this explicitly: + +- RED must cover the playback questions +- RED should also cover, where relevant to the hill: + - golden path + - failure modes + - edge cases + - stress / fuzz behavior +- If one of those categories is not relevant, the design doc or test + file should say so explicitly + +Why: +- It makes RED usable as both witness scaffold and regression suite +- It prevents narrow happy-path RED from masquerading as executable spec +- It makes the quality bar legible before GREEN starts + +Likely touch points: +- `README.md` RED step +- Possibly `process.md` if the repo wants a more operational version of + the same rule From d6e6ec485efe60031b8cf913f872e16f4bf3698c Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 6 Apr 2026 11:36:49 -0700 Subject: [PATCH 12/13] docs(backlog): add Method consistency follow-ups --- .../asap/PROCESS_branch-naming-consistency.md | 21 +++++++++++++++++++ .../asap/PROCESS_repo-lane-conformance.md | 20 ++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 docs/method/backlog/asap/PROCESS_branch-naming-consistency.md create mode 100644 docs/method/backlog/asap/PROCESS_repo-lane-conformance.md diff --git a/docs/method/backlog/asap/PROCESS_branch-naming-consistency.md b/docs/method/backlog/asap/PROCESS_branch-naming-consistency.md new file mode 100644 index 0000000..18fe1e6 --- /dev/null +++ b/docs/method/backlog/asap/PROCESS_branch-naming-consistency.md @@ -0,0 +1,21 @@ +# Unify branch naming rules across METHOD docs + +METHOD currently names cycle branches inconsistently. + +Examples in the docs point in different directions: +- `docs/method/process.md` says cycle work must happen on + `cycles/` +- the same document's branch naming section says cycle branches use + `####-slug` + +That should be one rule, not two. + +Why this matters: +- branch naming is part of METHOD's coordination surface +- conflicting examples create unnecessary drift across repos +- agents and humans should not have to guess which rule is canonical + +Deliverable: +- choose one branch naming rule for cycle branches +- update `README.md` and `docs/method/process.md` to match +- include one clear example and remove contradictory wording diff --git a/docs/method/backlog/asap/PROCESS_repo-lane-conformance.md b/docs/method/backlog/asap/PROCESS_repo-lane-conformance.md new file mode 100644 index 0000000..b2b8370 --- /dev/null +++ b/docs/method/backlog/asap/PROCESS_repo-lane-conformance.md @@ -0,0 +1,20 @@ +# METHOD repos should either contain declared backlog lanes or say they are optional + +METHOD defines backlog lanes in `README.md`, but the repo itself can +drift from that declared shape. This repo did not contain `asap/` until +real work needed it. + +That mismatch is small, but it weakens METHOD's claim that the +filesystem is the coordination layer. + +Question to settle: +- are declared lanes required repo structure? +- or are they optional until first use? + +Either answer is fine, but the method should be explicit. + +Deliverable: +- choose the rule +- update docs to match +- optionally add a lightweight repo-conformance check or checklist for + METHOD repos so missing lanes/signposts are caught early From 0be05534806d941751791401e943eabc38bc9915 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 6 Apr 2026 15:39:11 -0700 Subject: [PATCH 13/13] =?UTF-8?q?Fix:=20Resolve=20PR=20#7=20feedback=20?= =?UTF-8?q?=E2=80=94=20frontmatter,=20path=20sanitization,=20trailing=20sp?= =?UTF-8?q?aces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add missing YAML frontmatter to three ASAP backlog items (branch-naming-consistency, red-phase-playback-coverage, repo-lane-conformance) - Sanitize personal absolute paths (/Users/james/...) from all verification witnesses across cycles 0001-0020 - Remove trailing spaces from 0021 design doc - Expand path-sanitization test to cover all witness files dynamically - Add github_labels to allowed frontmatter keys in docs.test.ts --- .../two-way-github-sync.md | 8 ++++---- .../asap/PROCESS_branch-naming-consistency.md | 6 ++++++ .../asap/PROCESS_red-phase-playback-coverage.md | 6 ++++++ .../asap/PROCESS_repo-lane-conformance.md | 6 ++++++ .../0001-method-cli/witness/verification.md | 4 ++-- .../witness/verification.md | 4 ++-- .../witness/verification.md | 2 +- .../witness/verification.md | 2 +- .../witness/verification.md | 2 +- .../0012-mcp-server/witness/verification.md | 2 +- .../witness/verification.md | 2 +- .../witness/verification.md | 2 +- .../witness/verification.md | 2 +- .../witness/verification.md | 2 +- .../witness/verification.md | 2 +- .../witness/verification.md | 2 +- .../witness/verification.md | 2 +- .../witness/verification.md | 2 +- tests/docs.test.ts | 17 ++++++++++------- 19 files changed, 48 insertions(+), 27 deletions(-) diff --git a/docs/design/0021-two-way-github-sync/two-way-github-sync.md b/docs/design/0021-two-way-github-sync/two-way-github-sync.md index ccb500b..1854dad 100644 --- a/docs/design/0021-two-way-github-sync/two-way-github-sync.md +++ b/docs/design/0021-two-way-github-sync/two-way-github-sync.md @@ -19,12 +19,12 @@ Extend the GitHub adapter to support full two-way synchronization between the local filesystem and GitHub Issues. The filesystem remains the authority for local content; the adapter will: 1. **Push**: Update existing GitHub issues if the local title or body has - changed since the last sync. (This is the default action for + changed since the last sync. (This is the default action for `sync github`). 2. **Pull**: Update local backlog items with remote labels, status (Open/Closed), and top-level comments to keep the local context rich. -If both `--push` and `--pull` are provided, they run sequentially: +If both `--push` and `--pull` are provided, they run sequentially: local changes are pushed first, then remote updates are pulled. ## Playback Questions @@ -35,7 +35,7 @@ local changes are pushed first, then remote updates are pulled. description of an existing GitHub issue if the local file changes. - [ ] `method sync github --pull` updates local backlog files with data from GitHub (labels, status, comments). -- [ ] `method sync github --push --pull` runs both operations +- [ ] `method sync github --push --pull` runs both operations sequentially (Push then Pull). - [ ] Local files reflect GitHub status (e.g., if an issue is closed on GitHub, the local file is updated or moved). @@ -69,7 +69,7 @@ local changes are pushed first, then remote updates are pulled. ## Non-goals - [ ] Real-time sync (this remains a manual command-triggered move). -- [ ] Conflicts resolution (filesystem wins for title/body content on +- [ ] Conflicts resolution (filesystem wins for title/body content on push; metadata like labels and comments are enriched on pull). ## Backlog Context diff --git a/docs/method/backlog/asap/PROCESS_branch-naming-consistency.md b/docs/method/backlog/asap/PROCESS_branch-naming-consistency.md index 18fe1e6..27efc15 100644 --- a/docs/method/backlog/asap/PROCESS_branch-naming-consistency.md +++ b/docs/method/backlog/asap/PROCESS_branch-naming-consistency.md @@ -1,3 +1,9 @@ +--- +title: "Branch Naming Consistency" +legend: PROCESS +lane: asap +--- + # Unify branch naming rules across METHOD docs METHOD currently names cycle branches inconsistently. diff --git a/docs/method/backlog/asap/PROCESS_red-phase-playback-coverage.md b/docs/method/backlog/asap/PROCESS_red-phase-playback-coverage.md index 9fb8e95..2d4a976 100644 --- a/docs/method/backlog/asap/PROCESS_red-phase-playback-coverage.md +++ b/docs/method/backlog/asap/PROCESS_red-phase-playback-coverage.md @@ -1,3 +1,9 @@ +--- +title: "RED Phase Playback Coverage" +legend: PROCESS +lane: asap +--- + # RED should explicitly cover playback questions and test-shape breadth METHOD says RED tests are the executable spec and that playback diff --git a/docs/method/backlog/asap/PROCESS_repo-lane-conformance.md b/docs/method/backlog/asap/PROCESS_repo-lane-conformance.md index b2b8370..43be26d 100644 --- a/docs/method/backlog/asap/PROCESS_repo-lane-conformance.md +++ b/docs/method/backlog/asap/PROCESS_repo-lane-conformance.md @@ -1,3 +1,9 @@ +--- +title: "Repo Lane Conformance" +legend: PROCESS +lane: asap +--- + # METHOD repos should either contain declared backlog lanes or say they are optional METHOD defines backlog lanes in `README.md`, but the repo itself can diff --git a/docs/method/retro/0001-method-cli/witness/verification.md b/docs/method/retro/0001-method-cli/witness/verification.md index 8870559..6506bf8 100644 --- a/docs/method/retro/0001-method-cli/witness/verification.md +++ b/docs/method/retro/0001-method-cli/witness/verification.md @@ -12,7 +12,7 @@ $ npm test > method@0.1.0 test > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 1 passed (1) Tests 7 passed (7) @@ -38,7 +38,7 @@ $ npm run method -- status > method@0.1.0 method > tsx src/cli.ts status -METHOD Status /Users/james/git/method +METHOD Status ./method --- Backlog --- inbox 0 - diff --git a/docs/method/retro/0002-playback-witness-convention/witness/verification.md b/docs/method/retro/0002-playback-witness-convention/witness/verification.md index 4ca6a9d..edb61c5 100644 --- a/docs/method/retro/0002-playback-witness-convention/witness/verification.md +++ b/docs/method/retro/0002-playback-witness-convention/witness/verification.md @@ -14,7 +14,7 @@ $ npm run method -- status > method@0.1.0 method > tsx src/cli.ts status -METHOD Status /Users/james/git/method +METHOD Status ./method --- Backlog --- inbox 0 - @@ -55,7 +55,7 @@ $ npm run method -- status > method@0.1.0 method > tsx src/cli.ts status -METHOD Status /Users/james/git/method +METHOD Status ./method --- Backlog --- inbox 0 - diff --git a/docs/method/retro/0009-generated-signpost-provenance/witness/verification.md b/docs/method/retro/0009-generated-signpost-provenance/witness/verification.md index 52817b5..38ea93a 100644 --- a/docs/method/retro/0009-generated-signpost-provenance/witness/verification.md +++ b/docs/method/retro/0009-generated-signpost-provenance/witness/verification.md @@ -13,7 +13,7 @@ repo state. > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 2 passed (2) diff --git a/docs/method/retro/0010-yaml-frontmatter-schema/witness/verification.md b/docs/method/retro/0010-yaml-frontmatter-schema/witness/verification.md index f54c323..8a231a3 100644 --- a/docs/method/retro/0010-yaml-frontmatter-schema/witness/verification.md +++ b/docs/method/retro/0010-yaml-frontmatter-schema/witness/verification.md @@ -15,7 +15,7 @@ standardized YAML frontmatter contract, with automated enforcement in > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 2 passed (2) diff --git a/docs/method/retro/0011-library-api-surface/witness/verification.md b/docs/method/retro/0011-library-api-surface/witness/verification.md index 3c41753..e40d9eb 100644 --- a/docs/method/retro/0011-library-api-surface/witness/verification.md +++ b/docs/method/retro/0011-library-api-surface/witness/verification.md @@ -15,7 +15,7 @@ logic. > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 3 passed (3) diff --git a/docs/method/retro/0012-mcp-server/witness/verification.md b/docs/method/retro/0012-mcp-server/witness/verification.md index 934339a..51bc3ce 100644 --- a/docs/method/retro/0012-mcp-server/witness/verification.md +++ b/docs/method/retro/0012-mcp-server/witness/verification.md @@ -13,7 +13,7 @@ This witness proves that the `method` MCP server has been implemented and expose > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 4 passed (4) diff --git a/docs/method/retro/0013-executive-summary-protocol/witness/verification.md b/docs/method/retro/0013-executive-summary-protocol/witness/verification.md index af73572..03f27e3 100644 --- a/docs/method/retro/0013-executive-summary-protocol/witness/verification.md +++ b/docs/method/retro/0013-executive-summary-protocol/witness/verification.md @@ -15,7 +15,7 @@ current synthesis state. > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 4 passed (4) diff --git a/docs/method/retro/0014-github-issue-adapter/witness/verification.md b/docs/method/retro/0014-github-issue-adapter/witness/verification.md index 7992747..280813d 100644 --- a/docs/method/retro/0014-github-issue-adapter/witness/verification.md +++ b/docs/method/retro/0014-github-issue-adapter/witness/verification.md @@ -15,7 +15,7 @@ in the markdown frontmatter. > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 5 passed (5) diff --git a/docs/method/retro/0015-git-branch-workflow-policy/witness/verification.md b/docs/method/retro/0015-git-branch-workflow-policy/witness/verification.md index ab5ade0..c5fd1e2 100644 --- a/docs/method/retro/0015-git-branch-workflow-policy/witness/verification.md +++ b/docs/method/retro/0015-git-branch-workflow-policy/witness/verification.md @@ -15,7 +15,7 @@ test suite. > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 5 passed (5) diff --git a/docs/method/retro/0016-system-style-javascript-adoption/witness/verification.md b/docs/method/retro/0016-system-style-javascript-adoption/witness/verification.md index 3d100b7..a6036f0 100644 --- a/docs/method/retro/0016-system-style-javascript-adoption/witness/verification.md +++ b/docs/method/retro/0016-system-style-javascript-adoption/witness/verification.md @@ -14,7 +14,7 @@ adopted as repo doctrine and enforced in the core domain models. > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 6 passed (6) diff --git a/docs/method/retro/0017-behavior-spike-convention/witness/verification.md b/docs/method/retro/0017-behavior-spike-convention/witness/verification.md index 6fe0566..296edb1 100644 --- a/docs/method/retro/0017-behavior-spike-convention/witness/verification.md +++ b/docs/method/retro/0017-behavior-spike-convention/witness/verification.md @@ -15,7 +15,7 @@ test suite. > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 6 passed (6) diff --git a/docs/method/retro/0018-ship-sync-automation/witness/verification.md b/docs/method/retro/0018-ship-sync-automation/witness/verification.md index aec979e..fba39ed 100644 --- a/docs/method/retro/0018-ship-sync-automation/witness/verification.md +++ b/docs/method/retro/0018-ship-sync-automation/witness/verification.md @@ -15,7 +15,7 @@ automates the Ship Sync maneuver by updating `CHANGELOG.md` and > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 7 passed (7) diff --git a/docs/method/retro/0019-config-management/witness/verification.md b/docs/method/retro/0019-config-management/witness/verification.md index a0c0d02..5b1fbf2 100644 --- a/docs/method/retro/0019-config-management/witness/verification.md +++ b/docs/method/retro/0019-config-management/witness/verification.md @@ -14,7 +14,7 @@ been implemented, validated, and integrated into the workspace. > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 8 passed (8) diff --git a/docs/method/retro/0020-automated-witness-capture/witness/verification.md b/docs/method/retro/0020-automated-witness-capture/witness/verification.md index 55b6f59..a215645 100644 --- a/docs/method/retro/0020-automated-witness-capture/witness/verification.md +++ b/docs/method/retro/0020-automated-witness-capture/witness/verification.md @@ -14,7 +14,7 @@ behavior and adheres to the repo invariants. > vitest run --config vitest.config.ts - RUN v4.1.2 /Users/james/git/method + RUN v4.1.2 ./method Test Files 9 passed (9) diff --git a/tests/docs.test.ts b/tests/docs.test.ts index 6832fac..972ca84 100644 --- a/tests/docs.test.ts +++ b/tests/docs.test.ts @@ -220,9 +220,10 @@ describe('METHOD docs', () => { }); it('sanitizes personal absolute paths from committed verification witnesses', () => { - const readmeRevisionVerification = readRepoFile('docs/method/retro/0003-readme-revision/witness/verification.md'); - const visionRefreshVerification = readRepoFile('docs/method/retro/0004-readme-and-vision-refresh/witness/verification.md'); - const driftDetectorVerification = readRepoFile('docs/method/retro/0005-drift-detector/witness/verification.md'); + const witnesses = walkMarkdownFiles('docs/method/retro') + .filter((relativePath) => relativePath.includes('/witness/verification.md')); + + expect(witnesses.length, 'expected at least one verification witness').toBeGreaterThan(0); const personalPathPatterns = [ '/Users/', @@ -232,10 +233,11 @@ describe('METHOD docs', () => { 'C:\\Users\\', ]; - for (const pattern of personalPathPatterns) { - expect(readmeRevisionVerification).not.toContain(pattern); - expect(visionRefreshVerification).not.toContain(pattern); - expect(driftDetectorVerification).not.toContain(pattern); + for (const witnessPath of witnesses) { + const content = readRepoFile(witnessPath); + for (const pattern of personalPathPatterns) { + expect(content, `${witnessPath} contains personal path: ${pattern}`).not.toContain(pattern); + } } }); @@ -352,6 +354,7 @@ describe('METHOD docs', () => { 'lane', 'github_issue_id', 'github_issue_url', + 'github_labels', ]; const docs = walkMarkdownFiles('docs');