diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..a9d7477 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,53 @@ +name: Bug report +description: Report a Method defect or regression. +title: "" +labels: + - lane:inbox + - type:bug +body: + - type: textarea + id: observed + attributes: + label: Observed behavior + description: What happened? + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + description: What should have happened? + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Reproduction + description: Minimal commands, repo state, and inputs needed to reproduce. + placeholder: | + 1. ... + 2. ... + validations: + required: true + - type: textarea + id: impact + attributes: + label: Impact + description: Who is blocked or misled by this? + - type: textarea + id: acceptance + attributes: + label: Acceptance criteria + placeholder: | + - [ ] ... + validations: + required: true + - type: textarea + id: witness + attributes: + label: Witness plan + value: | + Expected evidence: + - failing test or reproduction: + - fix validation: + - retro: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..e9a37c5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Method process overview + url: https://github.com/flyingrobots/method/blob/main/docs/PROCESS.md + about: Read the Method workflow before opening work. diff --git a/.github/ISSUE_TEMPLATE/spike.yml b/.github/ISSUE_TEMPLATE/spike.yml new file mode 100644 index 0000000..58039fc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/spike.yml @@ -0,0 +1,45 @@ +name: Method spike +description: Capture a temporary proof, learning cycle, or constraint probe. +title: "" +labels: + - lane:inbox + - type:spike +body: + - type: textarea + id: question + attributes: + label: Question + description: What are we trying to prove or learn? + validations: + required: true + - type: textarea + id: why-now + attributes: + label: Why now + description: What decision will this unblock? + validations: + required: true + - type: textarea + id: constraints + attributes: + label: Constraints + description: Stack, time, safety, or quality constraints. + placeholder: | + - ... + - type: textarea + id: evidence + attributes: + label: Expected evidence + description: What transcript, demo, test, or artifact proves the learning? + placeholder: | + - [ ] ... + validations: + required: true + - type: textarea + id: closeout + attributes: + label: Adopt, adapt, or retire decision + description: Fill this in during closeout. + value: | + Decision: + Follow-up: diff --git a/.github/ISSUE_TEMPLATE/work-item.yml b/.github/ISSUE_TEMPLATE/work-item.yml new file mode 100644 index 0000000..ab4bff9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/work-item.yml @@ -0,0 +1,94 @@ +name: Method work item +description: Propose shaped Method work with a hill, scope, and witness plan. +title: "" +labels: + - lane:inbox + - type:enhancement +body: + - type: markdown + attributes: + value: | + Use a short, branch-safe title. Active work uses a branch slug derived from this title. + Labels are canonical tracker metadata. If you want a lane or legend other than inbox triage, + request it below; a maintainer or agent must apply the actual label. + - type: textarea + id: requested-classification + attributes: + label: Requested classification + description: Optional triage request. This does not set canonical labels. + placeholder: | + Suggested lane: + Suggested legend: + - type: textarea + id: hill + attributes: + label: Hill + description: Name the user value in Method/IBM Design Thinking style. + placeholder: As a contributor, I want ..., so that ... + validations: + required: true + - type: textarea + id: sponsored-perspectives + attributes: + label: Sponsored perspectives + description: Name abstract human and agent seats, not literal people or model brands. + value: | + Human: + Agent: + validations: + required: true + - type: textarea + id: context + attributes: + label: Context + description: What repo truth, user need, or defect motivates this work? + validations: + required: true + - type: textarea + id: scope + attributes: + label: Scope + description: What is in bounds? + placeholder: | + - ... + validations: + required: true + - type: textarea + id: out-of-scope + attributes: + label: Out of scope + description: What should not be solved in this issue? + placeholder: | + - ... + - type: textarea + id: acceptance + attributes: + label: Acceptance criteria + description: Concrete checks that prove the issue is complete. + placeholder: | + - [ ] ... + validations: + required: true + - type: textarea + id: witness + attributes: + label: Witness plan + description: Expected evidence for tests, playback, docs, and retro. + value: | + Expected evidence: + - tests: + - playback: + - docs: + - retro: + validations: + required: true + - type: textarea + id: method-artifacts + attributes: + label: Method artifacts + description: Fill these links as the work proceeds. + value: | + Design: + Witness: + Retro: + PR: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..84ab844 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,22 @@ +# Pull Request + +## Linked issue + +Closes # + +## Branch / issue-title check + +- [ ] Branch name is the linked issue title slug, or the exception is explained here. + +## Method evidence + +- [ ] Design doc linked or not required. +- [ ] Tests or validation evidence included. +- [ ] Playback/witness included or not required. +- [ ] Retro included or not required. + +## Tracker hygiene + +- [ ] Linked issue had `work-in-progress` while active. +- [ ] Linked issue lane/status labels are current. +- [ ] Follow-up work is captured as GitHub Issues, not hidden in chat or local-only backlog files. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 91a6534..836de31 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -110,7 +110,7 @@ Two-way sync between the local backlog and GitHub Issues: ## Testing -22 test files in `tests/` using Vitest: +23 test files in `tests/` using Vitest: - `api.test.ts` - `build.test.ts` @@ -127,6 +127,7 @@ Two-way sync between the local backlog and GitHub Issues: - `github-adapter.test.ts` - `lane-contract.test.ts` - `mcp.test.ts` +- `migrate-backlog-to-github-issues.test.ts` - `package.test.ts` - `repo-discipline.test.ts` - `review-state-exec.test.ts` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 06e7a2c..54d4f6d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,13 +21,17 @@ npm test ## Making Changes -1. **Check the backlog.** Run `method status` to see what is in - `inbox/`, `asap/`, `up-next/`, and `cool-ideas/`. -2. **Open an issue** if you want to propose new work or discuss an - existing backlog item before starting. -3. **Create a cycle branch** from `main`: `cycles/####-slug`. -4. **Follow the loop.** Design, RED, GREEN, playback, close, PR. -5. **Open a pull request** against `main` with the full cycle packet +1. **Start from GitHub Issues.** Work is tracked in repo-local GitHub + Issues, not local backlog files. Use Method lane labels such as + `lane:inbox`, `lane:asap`, `lane:bad-code`, and `lane:cool-ideas`. +2. **Normalize the issue title before starting.** Issue titles should be + short, branch-safe, and contributor-readable. +3. **Mark active work.** Add `work-in-progress` when you start so other + contributors and agents know the issue is claimed. +4. **Create a branch from the issue title slug** from `main`, for + example `foo-feature-needs-external-bar-integration`. +5. **Follow the loop.** Design, RED, GREEN, playback, close, PR. +6. **Open a pull request** against `main` with the full Method evidence (design doc, tests, implementation, retro, witness). ## Code Style diff --git a/README.md b/README.md index 185b5fa..9ead23c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ # METHOD -A backlog, a loop, and honest bookkeeping. +Issues, a loop, and honest bookkeeping. ## What is METHOD? METHOD is a lightweight process framework for software projects where humans and agents collaborate. It provides: -- A **backlog** organized by lanes (inbox, asap, bad-code, cool-ideas) +- **GitHub Issues** organized by Method labels (`lane:inbox`, + `lane:asap`, `lane:bad-code`, `lane:cool-ideas`, release milestones) - A **cycle loop** (pull → design → test → playback → close → review → ship) - **Honest bookkeeping** through design docs, retros, witnesses, and drift detection @@ -27,20 +28,20 @@ npm install @flyingrobots/method method init . # Capture an idea -method inbox "Add dark mode support" --legend PROCESS +gh issue create --title "Add dark mode support" --label lane:inbox # Check workspace health method doctor -# See what's in the backlog -method status +# Pick work from GitHub Issues +gh issue list --label lane:asap ``` ## Documentation | Document | What it covers | |----------|---------------| -| [`docs/PROCESS.md`](docs/PROCESS.md) | Cycle doctrine — how work flows from backlog to shipped | +| [`docs/PROCESS.md`](docs/PROCESS.md) | Cycle doctrine — how work flows from issue to shipped | | [`docs/RELEASE.md`](docs/RELEASE.md) | Release doctrine and runbook | | [`docs/GUIDE.md`](docs/GUIDE.md) | Operator advice and getting started | | [`docs/CLI.md`](docs/CLI.md) | CLI command reference | @@ -51,8 +52,14 @@ method status ## Principles -**The filesystem is the database.** A directory is a priority. A -filename is an identity. Moving a file is a decision. `ls` is the query. +**GitHub Issues are the live work tracker.** A label is a lane. A +milestone is release scope. `work-in-progress` means someone is actively +working the issue. This keeps METHOD legible to ordinary open-source +contributors. + +**The repository is the evidence ledger.** Design docs, tests, playback +witnesses, retros, release notes, and generated signposts stay in the +repo because they are durable evidence, not ephemeral tracker state. **Tests are the executable spec.** Design names the hill and the playback questions. Tests prove the answers. @@ -61,7 +68,7 @@ playback questions. Tests prove the answers. are rerunnable proof, not victory photos. **Process should be calm.** No sprints. No velocity. No burndown. A -backlog tiered by judgment, and a loop for doing it well. +small issue taxonomy, clear evidence, and a loop for doing work well. **The METHOD repo gets no special pleading.** This repository uses METHOD on itself. If repo truth conflicts with doctrine, repair the @@ -83,20 +90,20 @@ docs/ releases/ vX.Y.Z.md user-facing release notes method/ - backlog/ - inbox/ raw ideas - asap/ pull next - cool-ideas/ experiments - bad-code/ tech debt + backlog/ legacy/migration-only backlog cards legends/ named domains retro// retrospectives + witnesses - graveyard/ retired work + graveyard/ legacy retired work +.github/ + ISSUE_TEMPLATE/ GitHub issue forms for Method work + pull_request_template.md PR evidence checklist ``` ## The loop ```text -idea → inbox → lane → design doc → RED → GREEN → playback → retro +GitHub issue → lane label → branch → design doc → RED → GREEN + → playback → retro → PR/review → main → ship sync ``` diff --git a/docs/GUIDE.md b/docs/GUIDE.md index d58cc3e..c4cc1e4 100644 --- a/docs/GUIDE.md +++ b/docs/GUIDE.md @@ -22,8 +22,8 @@ method init . # Check workspace health method doctor -# See what's on the backlog -method status +# See live work +gh issue list --label lane:asap ``` ## The cycle lifecycle @@ -32,7 +32,7 @@ Every unit of shipped work follows the same loop: ```mermaid flowchart TD - A[Backlog item] -->|method pull| B[Design doc] + A[GitHub issue] -->|pull issue| B[Design doc] B --> C[Write failing tests] C --> D[Make tests pass] D --> E[Playback witness] @@ -45,24 +45,24 @@ flowchart TD style H fill:#bfb,stroke:#333 ``` -## Backlog flow +## Issue flow -Ideas enter the inbox, get triaged into lanes, and eventually get -pulled into cycles or retired to the graveyard: +Ideas enter GitHub Issues, get triaged with lane labels, and eventually +get pulled into cycles or closed with a disposition: ```mermaid flowchart LR - Idea -->|method inbox| Inbox - Inbox -->|triage| ASAP - Inbox -->|triage| CoolIdeas[cool-ideas] - Inbox -->|triage| BadCode[bad-code] - Inbox -->|triage| Release[vX.Y.Z] - Inbox -->|method retire| Graveyard - - ASAP -->|method pull| Cycle - BadCode -->|method pull| Cycle - CoolIdeas -->|method pull| Cycle - Release -->|method pull| Cycle + Idea -->|open issue| Inbox[lane:inbox] + Inbox -->|triage label| ASAP[lane:asap] + Inbox -->|triage label| CoolIdeas[lane:cool-ideas] + Inbox -->|triage label| BadCode[lane:bad-code] + Inbox -->|milestone + label| Release[lane:release] + Inbox -->|close with disposition| Closed + + ASAP -->|work-in-progress| Cycle + BadCode -->|work-in-progress| Cycle + CoolIdeas -->|work-in-progress| Cycle + Release -->|work-in-progress| Cycle ``` ## Cycle branches @@ -70,22 +70,22 @@ flowchart LR ```mermaid gitGraph commit id: "main" - branch cycles/PROCESS_my-feature + branch foo-feature-needs-external-bar-integration commit id: "design doc" commit id: "failing tests" commit id: "make tests pass" commit id: "close cycle" checkout main - merge cycles/PROCESS_my-feature id: "PR merge" + merge foo-feature-needs-external-bar-integration id: "PR merge" commit id: "ship sync" ``` -## File lifecycle +## Evidence lifecycle ```mermaid flowchart TD - subgraph Backlog - BI[backlog/inbox/PROCESS_foo.md] + subgraph Tracker + I[GitHub issue] end subgraph Active @@ -98,26 +98,27 @@ flowchart TD end subgraph Retired - GY[graveyard/PROCESS_foo.md] + C[closed issue with disposition] end - BI -->|pull| DD + I -->|pull| DD DD -->|close| RD DD -->|close| W - BI -->|retire| GY + I -->|close| C ``` ## Common commands | Task | Command | |------|---------| -| Capture an idea | `method inbox "idea" --legend PROCESS` | -| Add shaped backlog item | `method backlog add --lane asap --title "Title" --legend PROCESS` | -| Move between lanes | `method backlog move PROCESS_foo --to asap` | -| Pull into a cycle | `method pull PROCESS_foo` | +| Capture an idea | Open a GitHub issue with `lane:inbox` | +| Move between lanes | Change the issue `lane:*` label | +| Mark active work | Add `work-in-progress` | +| Create branch | Use the issue title slug | +| Pull into a cycle | Link the issue from the design doc | | Check drift | `method drift` | | Close a cycle | `method close --drift-check yes --outcome hill-met` | -| Retire an item | `method retire PROCESS_foo --reason "Replaced by X" --yes` | +| Retire an item | Close the issue with a disposition comment | | Check workspace health | `method doctor` | | Generate health receipt | `method doctor --receipt` | | Refresh generated docs | `method sync refs` | @@ -150,19 +151,22 @@ runbook. ### Capture ideas immediately -If a backlog-worthy idea surfaces during work, capture it now. Do not -leave it in chat or assume you'll remember it later. +If a work-worthy idea surfaces during work, capture it now as a GitHub +Issue. Do not leave it in chat or assume you'll remember it later. ```bash -method inbox "the idea" --legend PROCESS +gh issue create --label lane:inbox ``` ### Keep one raw-intake path Review notes, critique, and outside-in observations all go to -`method inbox` with `--source` and `--captured-at` when provenance +GitHub Issues with provenance in the issue body or comments when it matters. Don't invent parallel holding areas. +Filesystem backlog commands still exist for migration and legacy repos, +but new Method work should start in GitHub Issues. + ### Advice is not doctrine This guide is for patterns that help in practice but are not yet strong diff --git a/docs/PROCESS.md b/docs/PROCESS.md index 9904b1f..2429fe2 100644 --- a/docs/PROCESS.md +++ b/docs/PROCESS.md @@ -4,7 +4,10 @@ METHOD cycles run as a calm pull-design-test-playback-close-review-ship-sync loo ## Rules -- Pulling work is commitment. The backlog item does not go back. +- GitHub Issues are the live work tracker. Repository files are the + evidence ledger. +- Pulling work is commitment. The issue is marked `work-in-progress` + and does not silently fall back into the queue. - Playback questions drive the design and the tests. - Playback verification requires the matching perspective: Human playback questions must be verified by a human operator, and Agent @@ -28,10 +31,10 @@ METHOD cycles run as a calm pull-design-test-playback-close-review-ship-sync loo where a human operator attests that each human hill holds true. - `human`, `agent`, and `user` are abstract seats or perspectives in METHOD doctrine, not literal people, accounts, or model brands. -- Backlog maintenance happens at cycle boundaries, not continuously. -- All raw intake, including feedback or review notes, lands in - `docs/method/backlog/inbox/`. Use explicit metadata such as - `source` and `captured_at` when provenance matters. +- Issue maintenance happens at cycle boundaries, not continuously. +- All raw intake, including feedback or review notes, lands in GitHub + Issues with `lane:inbox`. Use the issue body and comments for + provenance when origin or timing matters. - Repo-level ship surfaces such as `BEARING.md` and `CHANGELOG.md` reflect merged `main` state, not branch-local closeout state. - If merged `main` is ever found carrying an open cycle packet, treat @@ -40,7 +43,8 @@ METHOD cycles run as a calm pull-design-test-playback-close-review-ship-sync loo - Review visibility is available through `method review-state`, which summarizes branch and PR context without turning METHOD into a forge cockpit. -- All cycle work must be done on a branch named `cycles/_`. +- All cycle work must be done on a branch named from the linked issue + title slug, for example `foo-feature-needs-external-bar-integration`. - Once a full cycle is complete (after the retro), the operator must push the branch and open a PR to the target branch (usually `main`). - Agents must stage and commit all modified files at the end of each turn @@ -48,8 +52,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/.md` or, for +1. Pull a GitHub issue into `docs/design/.md` or, for release-tagged work, into `docs/releases//design/.md`. + Add `work-in-progress` to the issue and link the issue from the + design doc frontmatter. 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 @@ -78,21 +84,28 @@ repair it before continuing. ## Workflow -METHOD uses Git for distributed coordination but remains forge-agnostic. +METHOD remains forge-agnostic in doctrine: it uses Git for distributed +coordination. GitHub Issues are the default live tracker for work in this +implementation. ### Branch Naming -- **Cycle Branches:** Use `cycles/_` (e.g., - `cycles/PROCESS_git-branch-workflow-policy`). +- **Cycle Branches:** Use the linked issue title slug (e.g., + `foo-feature-needs-external-bar-integration`). - **Maintenance Branches:** Use `maint-slug` for low-risk changes that require review (e.g., `maint-fix-typos`). -- **Triage/Backlog:** Small backlog captures or moves can happen - directly on `main` or on a `triage-slug` branch. +- **Triage:** Small label, title, or issue-body cleanup can happen in + GitHub without a branch when no repo files change. + +Issue titles are workflow identity. Before work starts, keep titles +short, branch-safe, and readable. Avoid legend prefixes, issue numbers, +and vague titles. ### The Cycle Lifecycle -1. **Pull:** Pull the backlog item. -2. **Branch:** Create a cycle branch from the latest `main`. +1. **Pull:** Pull the GitHub issue. +2. **Branch:** Create a branch from the latest `main` using the issue + title slug. 3. **Execute:** Perform the loop (design, tests, act, playback). 4. **Close:** Run `method close` to write the retro and witness metadata. 5. **Merge:** Open a PR/Review. Once approved, merge to `main`. @@ -100,14 +113,19 @@ METHOD uses Git for distributed coordination but remains forge-agnostic. ### Intake and Triage 1. **Capture:** Store raw review notes, critique, or outside-in - observations as backlog notes in `docs/method/backlog/inbox/`. -2. **Annotate when useful:** Record explicit frontmatter metadata such - as `source` and `captured_at` when the origin or timing matters. + observations as GitHub Issues with `lane:inbox`. +2. **Annotate when useful:** Record provenance in the issue body or + comments when the origin or timing matters. 3. **Process:** During a maintenance pass, convert the inbox item into - a durable choice: `asap`, a release lane such as `v2.4.5`, - `bad-code`, `cool-ideas`, a direct docs repair, or the graveyard. -4. **Do not split intake systems:** The inbox is the raw intake path. - Avoid parallel holding areas for critique or review notes. + a durable choice: `lane:asap`, `lane:bad-code`, + `lane:cool-ideas`, `lane:release` plus a milestone, a direct docs + repair, or closure with a disposition comment. +4. **Do not split intake systems:** GitHub Issues are the raw intake + path. Avoid parallel holding areas for critique or review notes. + +Legacy filesystem backlog commands and `docs/method/backlog/**` remain +compatibility and migration surfaces until repo-local migrations are +complete. They are not the new authority model. ### The Ship Sync Maneuver @@ -254,13 +272,14 @@ ceremonies. The mechanism is passive legibility. If you can answer these questions by reading the repo, you do not need a standup: -- What is actively open? → `method status` +- What is actively open? → GitHub Issues by Method lane label - Is this workspace healthy? → `method doctor` - What is under review? → `method review-state` - What is committed? → each design doc names its sponsors and hill -- What is next? → `method status` and the backlog +- What is next? → `lane:asap` issues without `work-in-progress` - What failed and why? → `ls docs/method/retro/` -- What did we decide not to do? → `ls docs/method/graveyard/` +- What did we decide not to do? → closed GitHub Issues with disposition + comments, plus legacy `docs/method/graveyard/` during migration No standups. No syncs. No status emails. No sprint planning. No retro meetings. The retro is a document, not a ceremony. The repo is the diff --git a/docs/design/PROCESS_make-github-issues-the-method-work-tracker.md b/docs/design/PROCESS_make-github-issues-the-method-work-tracker.md new file mode 100644 index 0000000..75ada97 --- /dev/null +++ b/docs/design/PROCESS_make-github-issues-the-method-work-tracker.md @@ -0,0 +1,141 @@ +--- +title: "Make GitHub Issues The Method Work Tracker" +legend: PROCESS +cycle: "PROCESS_make-github-issues-the-method-work-tracker" +github_issue_url: "https://github.com/flyingrobots/method/issues/27" +--- + +# Make GitHub Issues The Method Work Tracker + +Source issue: https://github.com/flyingrobots/method/issues/27 +Legend: PROCESS + +## Sponsors + +- Human: OSS contributor and Method maintainer +- Agent: Workflow automation agent + +These labels are abstract roles. In this design, `user` means the served +perspective, like in a user story, not a literal named person or model +brand. + +## Hill + +As an OSS contributor and agent collaborator, I can discover, start, +track, and close Method work through GitHub Issues while Method design +docs, witnesses, playbacks, and retros remain durable repo evidence. + +## Playback Questions + +### Human + +- [x] Does a new contributor see GitHub Issues as the canonical place to + find and propose work? +- [x] Does the repo still explain that design docs, witnesses, playbacks, + retros, and release evidence remain Method artifacts? +- [x] Does active work have a predictable branch name derived from the + issue title? + +### Agent + +- [x] Are Method lane, legend, priority, type, and active-work states + represented as explicit GitHub labels? +- [x] Do issue and PR templates encode the Method evidence contract? +- [x] Do docs classify existing filesystem backlog commands as legacy or + migration surfaces rather than the new authority model? + +## Design + +### Decision 1: GitHub Issues are the live tracker + +Method work is tracked in repo-local GitHub Issues. Lanes are labels. +Release scope is a milestone plus `lane:release`. Active work carries +`work-in-progress`. + +### Decision 2: Repo files are the evidence ledger + +Design docs, tests, playback witnesses, retrospectives, release notes, +and generated signposts remain in the repository. They are evidence, +not the live tracker. + +### Decision 3: Branches follow issue title slugs + +Active work branches use a normalized slug derived from the issue title. +This makes issue names part of workflow identity, so titles should be +short, sane, branch-safe, and contributor-readable. + +### Decision 4: Filesystem backlog becomes legacy/migration surface + +Existing live backlog files are archived after their corresponding +GitHub Issues are created. They remain available as historical evidence, +but not in live backlog lanes. + +## Migration Plan + +### Method pilot + +1. Create the canonical label set in `flyingrobots/method`. +2. Add GitHub issue templates and a PR template. +3. Update doctrine so GitHub Issues are the live tracker and repo files + are the evidence ledger. +4. Migrate live Method backlog cards into GitHub Issues with source-file + provenance in the issue body using + `scripts/migrate-backlog-to-github-issues.mjs` or `npm run migrate`. +5. Archive migrated backlog cards under the Method graveyard so they + stop acting as a second live tracker. +6. Leave historical graveyard files read-only until a separate cleanup + decision says whether to archive or import them. + +### Echo follow-up + +1. Query existing Echo issues before creating anything. +2. Match backlog cards to existing issues by title, source path, and + concept. +3. Update matching issues with Method lane labels and source-file + provenance. +4. Create new issues only for live cards with no existing issue. +5. Transfer work to another repo when the card belongs elsewhere. +6. Archive/stub migrated Echo backlog cards only after the issue mapping + is inspectable. + +## Accessibility and Assistive Reading + +- Linear truth / reduced-complexity posture: GitHub Issues provide the + live list of work; repo docs provide durable evidence. Users should + not need to reconcile two active trackers. +- Non-visual or alternate-reading expectations: issue templates use + plain markdown fields and checklist criteria. + +## Localization and Directionality + +- Locale / wording / formatting assumptions: issue labels use stable + ASCII identifiers independent of prose. +- Logical direction / layout assumptions: no layout-dependent meaning. + +## Agent Inspectability and Explainability + +- What must be explicit and deterministic for agents: lane labels, + `work-in-progress`, source issue links, and branch slugs. +- What must be attributable, evidenced, or governed: every Method cycle + should link the GitHub issue, PR, design doc, witness, and retro. + +## Non-goals + +- [ ] Bulk migrate Echo backlog items. +- [ ] Replace every filesystem-backlog CLI command in this cycle. +- [ ] Delete historical graveyard files before a migration policy exists. +- [ ] Add GitHub Projects. + +## Acceptance Criteria + +- [x] GitHub labels for the Method issue taxonomy exist in + `flyingrobots/method`. +- [x] GitHub issue templates exist for work items, bugs, and spikes. +- [x] A PR template links GitHub issue tracking to Method evidence. +- [x] README and PROCESS state that GitHub Issues are the live tracker. +- [x] CONTRIBUTING starts from GitHub Issues, not local backlog files. +- [x] Branch naming doctrine uses issue-title slugs. +- [x] Existing filesystem-backlog GitHub sync behavior is documented as + migration/legacy behavior. +- [x] The current Method backlog migration path is documented before + Echo migration starts. diff --git a/docs/method/graveyard/github-issue-migration/README.md b/docs/method/graveyard/github-issue-migration/README.md new file mode 100644 index 0000000..3804824 --- /dev/null +++ b/docs/method/graveyard/github-issue-migration/README.md @@ -0,0 +1,52 @@ +--- +title: "GitHub Issue Migration Archive" +provenance_level: artifact_history +--- + +# GitHub Issue Migration Archive + +These cards were migrated from live Method backlog lanes to GitHub Issues. +They remain here as historical evidence only; GitHub Issues are the live +work tracker. + +The `Source backlog` path recorded in each GitHub Issue is the original +live backlog path before archival. + +| Issue | Original source path | Archived card | +|-------|----------------------|---------------| +| [#28](https://github.com/flyingrobots/method/issues/28) | `docs/method/backlog/bad-code/PROCESS_reserve-graveyard-and-enforce-retirement-disposition.md` | `docs/method/graveyard/github-issue-migration/bad-code/PROCESS_reserve-graveyard-and-enforce-retirement-disposition.md` | +| [#29](https://github.com/flyingrobots/method/issues/29) | `docs/method/backlog/bad-code/PROCESS_workspace-initialization-signpost-aliases.md` | `docs/method/graveyard/github-issue-migration/bad-code/PROCESS_workspace-initialization-signpost-aliases.md` | +| [#30](https://github.com/flyingrobots/method/issues/30) | `docs/method/backlog/cool-ideas/CORE_agent-cycle-discipline-guardrails.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/CORE_agent-cycle-discipline-guardrails.md` | +| [#31](https://github.com/flyingrobots/method/issues/31) | `docs/method/backlog/cool-ideas/CORE_method-repair-stale-cycle-detection.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/CORE_method-repair-stale-cycle-detection.md` | +| [#32](https://github.com/flyingrobots/method/issues/32) | `docs/method/backlog/cool-ideas/PROCESS_architecture-source-inventory-section.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_architecture-source-inventory-section.md` | +| [#33](https://github.com/flyingrobots/method/issues/33) | `docs/method/backlog/cool-ideas/PROCESS_backlog-query-surface.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_backlog-query-surface.md` | +| [#34](https://github.com/flyingrobots/method/issues/34) | `docs/method/backlog/cool-ideas/PROCESS_backlog-summary-signpost-section.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_backlog-summary-signpost-section.md` | +| [#35](https://github.com/flyingrobots/method/issues/35) | `docs/method/backlog/cool-ideas/PROCESS_command-registry-pattern.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_command-registry-pattern.md` | +| [#36](https://github.com/flyingrobots/method/issues/36) | `docs/method/backlog/cool-ideas/PROCESS_cycle-packet-markdownlint-gate.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_cycle-packet-markdownlint-gate.md` | +| [#37](https://github.com/flyingrobots/method/issues/37) | `docs/method/backlog/cool-ideas/PROCESS_design-doc-template-catalog.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_design-doc-template-catalog.md` | +| [#38](https://github.com/flyingrobots/method/issues/38) | `docs/method/backlog/cool-ideas/PROCESS_doctor-policy-toleration-config.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_doctor-policy-toleration-config.md` | +| [#39](https://github.com/flyingrobots/method/issues/39) | `docs/method/backlog/cool-ideas/PROCESS_doctor-strict-mode-for-release-gates.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_doctor-strict-mode-for-release-gates.md` | +| [#40](https://github.com/flyingrobots/method/issues/40) | `docs/method/backlog/cool-ideas/PROCESS_drift-detector-rewards-string-matching-over-meaningful-tests.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_drift-detector-rewards-string-matching-over-meaningful-tests.md` | +| [#41](https://github.com/flyingrobots/method/issues/41) | `docs/method/backlog/cool-ideas/PROCESS_filesystem-abstraction.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_filesystem-abstraction.md` | +| [#42](https://github.com/flyingrobots/method/issues/42) | `docs/method/backlog/cool-ideas/PROCESS_flatten-repair-should-preview-destructive-operations.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_flatten-repair-should-preview-destructive-operations.md` | +| [#43](https://github.com/flyingrobots/method/issues/43) | `docs/method/backlog/cool-ideas/PROCESS_getting-started-walkthrough.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_getting-started-walkthrough.md` | +| [#44](https://github.com/flyingrobots/method/issues/44) | `docs/method/backlog/cool-ideas/PROCESS_github-milestone-lane-sync.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_github-milestone-lane-sync.md` | +| [#45](https://github.com/flyingrobots/method/issues/45) | `docs/method/backlog/cool-ideas/PROCESS_i18n-string-extraction.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_i18n-string-extraction.md` | +| [#46](https://github.com/flyingrobots/method/issues/46) | `docs/method/backlog/cool-ideas/PROCESS_interactive-scaffolder.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_interactive-scaffolder.md` | +| [#47](https://github.com/flyingrobots/method/issues/47) | `docs/method/backlog/cool-ideas/PROCESS_legend-audit-and-assignment.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_legend-audit-and-assignment.md` | +| [#48](https://github.com/flyingrobots/method/issues/48) | `docs/method/backlog/cool-ideas/PROCESS_maintenance-track-for-sub-cycle-work.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_maintenance-track-for-sub-cycle-work.md` | +| [#49](https://github.com/flyingrobots/method/issues/49) | `docs/method/backlog/cool-ideas/PROCESS_multi-forge-adapter.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_multi-forge-adapter.md` | +| [#50](https://github.com/flyingrobots/method/issues/50) | `docs/method/backlog/cool-ideas/PROCESS_next-work-menu.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_next-work-menu.md` | +| [#51](https://github.com/flyingrobots/method/issues/51) | `docs/method/backlog/cool-ideas/PROCESS_optional-posture-sections-in-design-doc-template.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_optional-posture-sections-in-design-doc-template.md` | +| [#52](https://github.com/flyingrobots/method/issues/52) | `docs/method/backlog/cool-ideas/PROCESS_remote-issue-deletion-sync.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_remote-issue-deletion-sync.md` | +| [#53](https://github.com/flyingrobots/method/issues/53) | `docs/method/backlog/cool-ideas/PROCESS_retro-scaffold-refresh-command.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_retro-scaffold-refresh-command.md` | +| [#54](https://github.com/flyingrobots/method/issues/54) | `docs/method/backlog/cool-ideas/PROCESS_review-closeout-helper.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_review-closeout-helper.md` | +| [#55](https://github.com/flyingrobots/method/issues/55) | `docs/method/backlog/cool-ideas/PROCESS_review-config-hardening.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_review-config-hardening.md` | +| [#56](https://github.com/flyingrobots/method/issues/56) | `docs/method/backlog/cool-ideas/PROCESS_right-sized-cycle-ceremony-t-shirt-sizing.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_right-sized-cycle-ceremony-t-shirt-sizing.md` | +| [#57](https://github.com/flyingrobots/method/issues/57) | `docs/method/backlog/cool-ideas/PROCESS_signpost-count-scales-with-overhead-not-complexity.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_signpost-count-scales-with-overhead-not-complexity.md` | +| [#58](https://github.com/flyingrobots/method/issues/58) | `docs/method/backlog/cool-ideas/PROCESS_signpost-template-overrides.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_signpost-template-overrides.md` | +| [#59](https://github.com/flyingrobots/method/issues/59) | `docs/method/backlog/cool-ideas/PROCESS_visual-witness-screenshot-capture.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_visual-witness-screenshot-capture.md` | +| [#60](https://github.com/flyingrobots/method/issues/60) | `docs/method/backlog/cool-ideas/PROCESS_witness-file-capture.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_witness-file-capture.md` | +| [#61](https://github.com/flyingrobots/method/issues/61) | `docs/method/backlog/cool-ideas/SYNTH_artifact-history-and-semantic-provenance.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/SYNTH_artifact-history-and-semantic-provenance.md` | +| [#62](https://github.com/flyingrobots/method/issues/62) | `docs/method/backlog/cool-ideas/SYNTH_cycle-witness-command.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/SYNTH_cycle-witness-command.md` | +| [#63](https://github.com/flyingrobots/method/issues/63) | `docs/method/backlog/cool-ideas/SYNTH_sync-vision-command.md` | `docs/method/graveyard/github-issue-migration/cool-ideas/SYNTH_sync-vision-command.md` | diff --git a/docs/method/backlog/bad-code/PROCESS_reserve-graveyard-and-enforce-retirement-disposition.md b/docs/method/graveyard/github-issue-migration/bad-code/PROCESS_reserve-graveyard-and-enforce-retirement-disposition.md similarity index 100% rename from docs/method/backlog/bad-code/PROCESS_reserve-graveyard-and-enforce-retirement-disposition.md rename to docs/method/graveyard/github-issue-migration/bad-code/PROCESS_reserve-graveyard-and-enforce-retirement-disposition.md diff --git a/docs/method/backlog/bad-code/PROCESS_workspace-initialization-signpost-aliases.md b/docs/method/graveyard/github-issue-migration/bad-code/PROCESS_workspace-initialization-signpost-aliases.md similarity index 100% rename from docs/method/backlog/bad-code/PROCESS_workspace-initialization-signpost-aliases.md rename to docs/method/graveyard/github-issue-migration/bad-code/PROCESS_workspace-initialization-signpost-aliases.md diff --git a/docs/method/backlog/cool-ideas/CORE_agent-cycle-discipline-guardrails.md b/docs/method/graveyard/github-issue-migration/cool-ideas/CORE_agent-cycle-discipline-guardrails.md similarity index 100% rename from docs/method/backlog/cool-ideas/CORE_agent-cycle-discipline-guardrails.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/CORE_agent-cycle-discipline-guardrails.md diff --git a/docs/method/backlog/cool-ideas/CORE_method-repair-stale-cycle-detection.md b/docs/method/graveyard/github-issue-migration/cool-ideas/CORE_method-repair-stale-cycle-detection.md similarity index 100% rename from docs/method/backlog/cool-ideas/CORE_method-repair-stale-cycle-detection.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/CORE_method-repair-stale-cycle-detection.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_architecture-source-inventory-section.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_architecture-source-inventory-section.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_architecture-source-inventory-section.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_architecture-source-inventory-section.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_backlog-query-surface.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_backlog-query-surface.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_backlog-query-surface.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_backlog-query-surface.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_backlog-summary-signpost-section.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_backlog-summary-signpost-section.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_backlog-summary-signpost-section.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_backlog-summary-signpost-section.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_command-registry-pattern.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_command-registry-pattern.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_command-registry-pattern.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_command-registry-pattern.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_cycle-packet-markdownlint-gate.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_cycle-packet-markdownlint-gate.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_cycle-packet-markdownlint-gate.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_cycle-packet-markdownlint-gate.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_design-doc-template-catalog.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_design-doc-template-catalog.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_design-doc-template-catalog.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_design-doc-template-catalog.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_doctor-policy-toleration-config.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_doctor-policy-toleration-config.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_doctor-policy-toleration-config.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_doctor-policy-toleration-config.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_doctor-strict-mode-for-release-gates.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_doctor-strict-mode-for-release-gates.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_doctor-strict-mode-for-release-gates.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_doctor-strict-mode-for-release-gates.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_drift-detector-rewards-string-matching-over-meaningful-tests.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_drift-detector-rewards-string-matching-over-meaningful-tests.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_drift-detector-rewards-string-matching-over-meaningful-tests.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_drift-detector-rewards-string-matching-over-meaningful-tests.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_filesystem-abstraction.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_filesystem-abstraction.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_filesystem-abstraction.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_filesystem-abstraction.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_flatten-repair-should-preview-destructive-operations.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_flatten-repair-should-preview-destructive-operations.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_flatten-repair-should-preview-destructive-operations.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_flatten-repair-should-preview-destructive-operations.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_getting-started-walkthrough.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_getting-started-walkthrough.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_getting-started-walkthrough.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_getting-started-walkthrough.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_github-milestone-lane-sync.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_github-milestone-lane-sync.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_github-milestone-lane-sync.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_github-milestone-lane-sync.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_i18n-string-extraction.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_i18n-string-extraction.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_i18n-string-extraction.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_i18n-string-extraction.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_interactive-scaffolder.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_interactive-scaffolder.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_interactive-scaffolder.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_interactive-scaffolder.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_legend-audit-and-assignment.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_legend-audit-and-assignment.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_legend-audit-and-assignment.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_legend-audit-and-assignment.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_maintenance-track-for-sub-cycle-work.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_maintenance-track-for-sub-cycle-work.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_maintenance-track-for-sub-cycle-work.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_maintenance-track-for-sub-cycle-work.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_multi-forge-adapter.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_multi-forge-adapter.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_multi-forge-adapter.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_multi-forge-adapter.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_next-work-menu.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_next-work-menu.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_next-work-menu.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_next-work-menu.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_optional-posture-sections-in-design-doc-template.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_optional-posture-sections-in-design-doc-template.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_optional-posture-sections-in-design-doc-template.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_optional-posture-sections-in-design-doc-template.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_remote-issue-deletion-sync.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_remote-issue-deletion-sync.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_remote-issue-deletion-sync.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_remote-issue-deletion-sync.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_retro-scaffold-refresh-command.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_retro-scaffold-refresh-command.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_retro-scaffold-refresh-command.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_retro-scaffold-refresh-command.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_review-closeout-helper.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_review-closeout-helper.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_review-closeout-helper.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_review-closeout-helper.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_review-config-hardening.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_review-config-hardening.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_review-config-hardening.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_review-config-hardening.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_right-sized-cycle-ceremony-t-shirt-sizing.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_right-sized-cycle-ceremony-t-shirt-sizing.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_right-sized-cycle-ceremony-t-shirt-sizing.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_right-sized-cycle-ceremony-t-shirt-sizing.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_signpost-count-scales-with-overhead-not-complexity.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_signpost-count-scales-with-overhead-not-complexity.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_signpost-count-scales-with-overhead-not-complexity.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_signpost-count-scales-with-overhead-not-complexity.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_signpost-template-overrides.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_signpost-template-overrides.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_signpost-template-overrides.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_signpost-template-overrides.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_visual-witness-screenshot-capture.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_visual-witness-screenshot-capture.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_visual-witness-screenshot-capture.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_visual-witness-screenshot-capture.md diff --git a/docs/method/backlog/cool-ideas/PROCESS_witness-file-capture.md b/docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_witness-file-capture.md similarity index 100% rename from docs/method/backlog/cool-ideas/PROCESS_witness-file-capture.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/PROCESS_witness-file-capture.md diff --git a/docs/method/backlog/cool-ideas/SYNTH_artifact-history-and-semantic-provenance.md b/docs/method/graveyard/github-issue-migration/cool-ideas/SYNTH_artifact-history-and-semantic-provenance.md similarity index 100% rename from docs/method/backlog/cool-ideas/SYNTH_artifact-history-and-semantic-provenance.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/SYNTH_artifact-history-and-semantic-provenance.md diff --git a/docs/method/backlog/cool-ideas/SYNTH_cycle-witness-command.md b/docs/method/graveyard/github-issue-migration/cool-ideas/SYNTH_cycle-witness-command.md similarity index 100% rename from docs/method/backlog/cool-ideas/SYNTH_cycle-witness-command.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/SYNTH_cycle-witness-command.md diff --git a/docs/method/backlog/cool-ideas/SYNTH_sync-vision-command.md b/docs/method/graveyard/github-issue-migration/cool-ideas/SYNTH_sync-vision-command.md similarity index 100% rename from docs/method/backlog/cool-ideas/SYNTH_sync-vision-command.md rename to docs/method/graveyard/github-issue-migration/cool-ideas/SYNTH_sync-vision-command.md diff --git a/docs/method/retro/PROCESS_make-github-issues-the-method-work-tracker/PROCESS_make-github-issues-the-method-work-tracker.md b/docs/method/retro/PROCESS_make-github-issues-the-method-work-tracker/PROCESS_make-github-issues-the-method-work-tracker.md new file mode 100644 index 0000000..3c48aa9 --- /dev/null +++ b/docs/method/retro/PROCESS_make-github-issues-the-method-work-tracker/PROCESS_make-github-issues-the-method-work-tracker.md @@ -0,0 +1,63 @@ +--- +title: "Make GitHub Issues The Method Work Tracker" +cycle: "PROCESS_make-github-issues-the-method-work-tracker" +design_doc: "docs/design/PROCESS_make-github-issues-the-method-work-tracker.md" +github_issue_url: "https://github.com/flyingrobots/method/issues/27" +outcome: hill-met +drift_check: yes +--- + +# Make GitHub Issues The Method Work Tracker Retro + +## Summary + +GitHub Issues are now the Method repo's live work tracker. Repository +files remain the evidence ledger for design docs, witnesses, playbacks, +retros, release notes, and durable process artifacts. + +This cycle added the canonical Method issue label taxonomy, issue +templates, PR template, GitHub-first workflow doctrine, migration +tooling, migrated-backlog archive, and tests around the migration +surface. Follow-up review hardened the migration command so invalid +lanes, ambiguous frontmatter, duplicate source markers, malformed issue +creation output, and incomplete label enumeration fail closed. + +## Playback Witness + +- Verification artifact: + `docs/method/retro/PROCESS_make-github-issues-the-method-work-tracker/witness/verification.md` +- Human playback: the maintainer directed the transition to GitHub + Issues, approved the Method pilot, and requested final closeout before + wrapping the PR. +- Agent playback: docs, templates, migration tooling, tests, and PR + review evidence demonstrate that issue labels are the live tracking + surface while repo files remain Method evidence. + +## Drift + +- Review found a process drift issue: the PR had entered review before + the cycle packet had a retro and witness. This retro and its witness + repair that drift before merge. +- Review found migration-safety drift around legacy lanes and parser + edge cases. The script now maps legacy `up-next` cards to + `lane:asap`, rejects unknown lanes, requires exact frontmatter + delimiters, guards `gh issue create` parsing, and loads labels through + paginated GitHub API enumeration. + +## New Debt + +- Echo backlog migration remains a separate follow-up cycle. +- Legacy filesystem backlog commands remain compatibility and migration + surfaces until future cycles retire or adapt them. +- GitHub Projects are intentionally out of scope for this cycle. + +## Cool Ideas + +- Add a future release gate that refuses to merge Method PRs whose + design packet has unchecked playback questions or no retro witness. + +## Backlog Maintenance + +- [x] Inbox processed +- [x] Priorities reviewed +- [x] Dead work buried or merged diff --git a/docs/method/retro/PROCESS_make-github-issues-the-method-work-tracker/witness/verification.md b/docs/method/retro/PROCESS_make-github-issues-the-method-work-tracker/witness/verification.md new file mode 100644 index 0000000..3e30fd2 --- /dev/null +++ b/docs/method/retro/PROCESS_make-github-issues-the-method-work-tracker/witness/verification.md @@ -0,0 +1,53 @@ +--- +title: "Verification Witness" +cycle: "PROCESS_make-github-issues-the-method-work-tracker" +--- + +# Verification Witness + +## Scope + +Cycle: +`PROCESS_make-github-issues-the-method-work-tracker` + +Issue: +https://github.com/flyingrobots/method/issues/27 + +PR: +https://github.com/flyingrobots/method/pull/64 + +## Verification Commands + +Run from the Method repository root: + +```bash +npm audit --omit=dev +npm run lint +npm test +gh pr checks 64 --repo flyingrobots/method +``` + +## Expected Result + +- `npm audit --omit=dev` reports zero production vulnerabilities. +- `npm run lint` passes. +- `npm test` passes. +- GitHub PR checks for PR #64 pass, apart from external reviewer status + contexts that reflect review state rather than local code execution. + +## Playback Evidence + +- Human-facing docs identify GitHub Issues as the live tracker and repo + files as the evidence ledger. +- Branch naming docs use issue-title slugs. +- Issue and PR templates require Method evidence links. +- Migration tooling preserves source-file provenance and fails closed on + duplicate source markers. +- Migration tooling maps legacy `up-next` backlog cards to canonical + `lane:asap` instead of creating `lane:up-next`. +- Migration tooling rejects unknown lanes instead of creating arbitrary + `lane:*` labels. +- Migration tooling requires exact YAML frontmatter delimiters and keeps + malformed YAML as raw markdown. +- Migration tooling parses `gh issue create` output defensively and + aborts when no issue number can be recovered. diff --git a/package-lock.json b/package-lock.json index 229ee5b..b058a64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1711,12 +1711,12 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", - "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", "license": "MIT", "dependencies": { - "ip-address": "10.1.0" + "ip-address": "^10.2.0" }, "engines": { "node": ">= 16" @@ -1735,9 +1735,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "funding": [ { "type": "github", @@ -1924,9 +1924,9 @@ } }, "node_modules/hono": { - "version": "4.12.14", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz", - "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==", + "version": "4.12.23", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz", + "integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -1975,9 +1975,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", "engines": { "node": ">= 12" @@ -2358,9 +2358,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -2509,9 +2509,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -2529,7 +2529,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -2551,9 +2551,9 @@ } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" diff --git a/package.json b/package.json index a074c86..ba893d6 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "lint": "biome check src/ tests/", "lint:fix": "biome check --fix src/ tests/", "method": "tsx src/cli.ts", + "migrate": "node scripts/migrate-backlog-to-github-issues.mjs", "test": "vitest run --config vitest.config.ts", "test:watch": "vitest --config vitest.config.ts" }, diff --git a/scripts/migrate-backlog-to-github-issues.mjs b/scripts/migrate-backlog-to-github-issues.mjs new file mode 100644 index 0000000..5063c9d --- /dev/null +++ b/scripts/migrate-backlog-to-github-issues.mjs @@ -0,0 +1,467 @@ +#!/usr/bin/env node +import { spawnSync } from 'node:child_process'; +import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { basename, join, relative, resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { parse as parseYaml } from 'yaml'; + +const DEFAULT_BACKLOG_ROOT = 'docs/method/backlog'; + +const LABELS = new Map([ + ['lane:inbox', ['D0E8FF', 'Raw intake; needs triage before work starts.']], + ['lane:asap', ['FFB3BA', 'Highest-priority Method lane; pull next.']], + ['lane:bad-code', ['FFDFBA', 'Known debt, rot, or structural risk.']], + ['lane:cool-ideas', ['D5E8D4', 'Interesting but not committed work.']], + ['lane:release', ['E1D5E7', 'Release-scoped work; pair with a milestone.']], + ['work-in-progress', ['FBCA04', 'Someone is actively working this issue.']], + ['blocked', ['D93F0B', 'Blocked; cannot proceed without external input or dependency.']], + ['needs-design', ['5319E7', 'Needs or is missing a Method design artifact.']], + ['needs-playback', ['1D76DB', 'Needs playback review or verification.']], + ['needs-witness', ['006B75', 'Needs reproducible witness evidence.']], + ['needs-retro', ['7057FF', 'Needs Method retro/closeout evidence.']], + ['legend:process', ['1D76DB', 'Method process, workflow, adapters, and CLI work.']], + ['legend:synth', ['5319E7', 'Synthesis, signposts, executive summaries, provenance.']], + ['legend:core', ['0052CC', 'Core Method runtime/domain behavior.']], + ['priority:high', ['D93F0B', 'High priority.']], + ['priority:medium', ['FBCA04', 'Medium priority.']], + ['priority:low', ['C2E0C6', 'Low priority.']], + ['type:bug', ['D73A4A', 'Defect or regression.']], + ['type:enhancement', ['A2EEEF', 'Feature or improvement.']], + ['type:docs', ['0075CA', 'Documentation work.']], + ['type:spike', ['C5DEF5', 'Temporary proof or learning cycle.']], + ['type:maintenance', ['BFDADC', 'Maintenance, cleanup, or operational workflow work.']], +]); + +const CANONICAL_LABELS = [...LABELS.keys()]; +const RELEASE_LANE_PATTERN = /^v\d+\.\d+\.\d+$/u; + +function main() { + const args = parseArgs(process.argv.slice(2)); + const root = process.cwd(); + const backlogRoot = resolve(root, args.backlogRoot ?? DEFAULT_BACKLOG_ROOT); + const repo = args.repo ?? process.env.GITHUB_REPO ?? currentGitHubRepo(); + const dryRun = args.dryRun; + + if (!repo.includes('/')) { + fail('GitHub repo must be OWNER/REPO. Pass --repo OWNER/REPO or set GITHUB_REPO.'); + } + if (!existsSync(backlogRoot)) { + fail(`Backlog root does not exist: ${relative(root, backlogRoot)}`); + } + + const cards = collectBacklogCards(root, backlogRoot); + const existingIssues = loadExistingIssueMap(repo); + if (!dryRun) { + ensureLabels(repo, CANONICAL_LABELS); + } + + const results = []; + for (const card of cards) { + const existing = existingIssues.get(card.relativePath); + if (existing !== undefined) { + results.push({ action: 'skip-existing', card, issue: existing }); + continue; + } + if (dryRun) { + results.push({ action: 'create-dry-run', card }); + continue; + } + const issue = createIssue(repo, card); + results.push({ action: 'created', card, issue }); + } + + if (args.json) { + process.stdout.write(`${JSON.stringify(renderJson(results), null, 2)}\n`); + } else { + renderText(results, repo, dryRun); + } +} + +function parseArgs(argv) { + const args = { + dryRun: false, + json: false, + repo: undefined, + backlogRoot: undefined, + }; + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index]; + if (arg === '--dry-run') { + args.dryRun = true; + continue; + } + if (arg === '--json') { + args.json = true; + continue; + } + if (arg === '--repo') { + args.repo = requireValue(argv, (index += 1), '--repo'); + continue; + } + if (arg === '--backlog-root') { + args.backlogRoot = requireValue(argv, (index += 1), '--backlog-root'); + continue; + } + if (arg === '--help' || arg === '-h') { + process.stdout.write(helpText()); + process.exit(0); + } + fail(`Unknown argument: ${arg}\n\n${helpText()}`); + } + return args; +} + +function requireValue(argv, index, flag) { + const value = argv[index]; + if (value === undefined || value.startsWith('--')) { + fail(`${flag} requires a value.`); + } + return value; +} + +function helpText() { + return [ + 'Usage: npm run migrate -- [--repo OWNER/REPO] [--backlog-root PATH] [--dry-run] [--json]', + '', + 'Migrates live Method backlog lane cards into GitHub Issues.', + '', + 'Only files under docs/method/backlog//*.md are migrated.', + 'Design docs, retros, witnesses, releases, and graveyard files are ignored.', + '', + ].join('\n'); +} + +function currentGitHubRepo() { + const result = run('gh', ['repo', 'view', '--json', 'nameWithOwner', '--jq', '.nameWithOwner'], { allowFailure: true }); + return result.ok ? result.stdout.trim() : ''; +} + +function collectBacklogCards(root, backlogRoot) { + const cards = []; + for (const laneName of sortedDirNames(backlogRoot)) { + const laneDir = join(backlogRoot, laneName); + if (!statSync(laneDir).isDirectory()) { + continue; + } + for (const fileName of sortedDirNames(laneDir)) { + if (!fileName.endsWith('.md')) { + continue; + } + const fullPath = join(laneDir, fileName); + if (!statSync(fullPath).isFile()) { + continue; + } + cards.push(readBacklogCard(root, backlogRoot, laneName, fullPath)); + } + } + return cards; +} + +function sortedDirNames(dir) { + return readdirSync(dir).sort((left, right) => left.localeCompare(right)); +} + +function readBacklogCard(root, backlogRoot, laneName, fullPath) { + const raw = readFileSync(fullPath, 'utf8'); + const parsed = parseMarkdownDoc(raw); + const relativePath = normalizeSourcePath(relative(root, fullPath)); + const stem = basename(fullPath, '.md'); + const filenameLegend = stem.includes('_') ? stem.split('_')[0] : undefined; + const filenameSlug = stem.includes('_') ? stem.slice(stem.indexOf('_') + 1) : stem; + const title = normalizeString(parsed.frontmatter.title) ?? firstHeading(parsed.body) ?? titleCase(filenameSlug); + const lane = normalizeString(parsed.frontmatter.lane) ?? laneName; + const legend = normalizeString(parsed.frontmatter.legend) ?? filenameLegend; + const priority = normalizeString(parsed.frontmatter.priority); + const labels = labelsFor({ lane, legend, priority }); + const body = renderIssueBody({ relativePath, lane, legend, priority, title, originalBody: parsed.body.trim() }); + return { + fullPath, + relativePath, + title, + lane, + legend, + priority, + labels, + body, + }; +} + +function parseMarkdownDoc(raw) { + if (!raw.startsWith('---\n')) { + return { frontmatter: {}, body: raw }; + } + const closeIndex = raw.indexOf('\n---\n', 4); + if (closeIndex === -1) { + return { frontmatter: {}, body: raw }; + } + const yaml = raw.slice(4, closeIndex); + const body = raw.slice(closeIndex + '\n---\n'.length); + try { + return { frontmatter: parseYaml(yaml) ?? {}, body }; + } catch { + return { frontmatter: {}, body: raw }; + } +} + +function firstHeading(body) { + const match = /^#\s+(.+)$/mu.exec(body); + return match?.[1]?.trim(); +} + +function normalizeString(value) { + return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined; +} + +function labelsFor({ lane, legend, priority }) { + const labels = []; + labels.push(labelForLane(lane)); + + if (legend !== undefined) { + labels.push(`legend:${legend.toLowerCase()}`); + } + if (priority !== undefined) { + labels.push(`priority:${priority.toLowerCase()}`); + } + if (lane === 'bad-code') { + labels.push('type:maintenance'); + } + if (legend?.toUpperCase() === 'SPIKE') { + labels.push('type:spike'); + } + return [...new Set(labels)]; +} + +function labelForLane(lane) { + const laneValue = normalizeString(lane); + if (laneValue === undefined) { + throw new Error('Missing Method backlog lane.'); + } + if (RELEASE_LANE_PATTERN.test(laneValue)) { + return 'lane:release'; + } + if (laneValue === 'up-next') { + return 'lane:asap'; + } + const label = `lane:${laneValue}`; + if (!LABELS.has(label)) { + throw new Error(`Unknown Method backlog lane "${laneValue}". Refusing to create non-canonical label ${label}.`); + } + return label; +} + +function renderIssueBody({ relativePath, lane, legend, priority, title, originalBody }) { + const metadata = [ + sourceBacklogMarker(relativePath), + `Original lane: \`${lane}\``, + legend === undefined ? undefined : `Original legend: \`${legend}\``, + priority === undefined ? undefined : `Original priority: \`${priority}\``, + ].filter(Boolean); + + return [ + '## Migrated from Method backlog', + '', + 'This issue was created from a legacy filesystem backlog card. GitHub Issues are now the live work tracker; repository docs remain Method evidence.', + '', + ...metadata, + '', + '## Original backlog card', + '', + originalBody.length > 0 ? originalBody : `# ${title}`, + '', + ].join('\n'); +} + +function ensureLabels(repo, labels, runner) { + const commandRunner = runner ?? run; + const existingRaw = commandRunner('gh', [ + 'api', + `repos/${repo}/labels`, + '--method', + 'GET', + '--paginate', + '-f', + 'per_page=100', + '--jq', + '.[].name', + ]).stdout; + const existing = new Set(existingRaw.split('\n').map((line) => line.trim()).filter(Boolean)); + for (const label of labels) { + if (existing.has(label)) { + continue; + } + const [color, description] = LABELS.get(label) ?? ['BFDADC', 'Migrated Method label.']; + commandRunner('gh', ['label', 'create', label, '--repo', repo, '--color', color, '--description', description]); + existing.add(label); + } +} + +function loadExistingIssueMap(repo, runner) { + const commandRunner = runner ?? run; + const result = commandRunner( + 'gh', + [ + 'api', + `repos/${repo}/issues`, + '--method', + 'GET', + '--paginate', + '-f', + 'state=all', + '-f', + 'per_page=100', + '--jq', + '.[] | select(.pull_request == null) | {number,title,url:.html_url,state,body}', + ], + ); + if (!result.ok) { + throw new Error(`Unable to load existing GitHub issues for ${repo}; aborting to avoid duplicate migration issues.`); + } + const issues = parseJsonLines(result.stdout); + const bySourcePath = new Map(); + for (const issue of issues) { + const body = typeof issue.body === 'string' ? issue.body : ''; + for (const relativePath of sourceBacklogPaths(body)) { + const existing = bySourcePath.get(relativePath); + if (existing !== undefined && existing.number !== issue.number) { + throw new Error( + `Duplicate Source backlog marker ${relativePath}: issue #${existing.number} ${existing.url} conflicts with issue #${issue.number} ${issue.url}`, + ); + } + if (existing === undefined) { + bySourcePath.set(relativePath, { + number: issue.number, + title: issue.title, + url: issue.url, + state: issue.state, + }); + } + } + } + return bySourcePath; +} + +function parseJsonLines(raw) { + return raw + .split('\n') + .map((line) => line.trim()) + .filter(Boolean) + .map((line) => JSON.parse(line)); +} + +function sourceBacklogMarker(relativePath) { + return `Source backlog: \`${relativePath}\``; +} + +function sourceBacklogPaths(body) { + return [...body.matchAll(/Source backlog: `([^`]+)`/gu)] + .map((match) => match[1]) + .filter(Boolean) + .map((relativePath) => normalizeSourcePath(relativePath)); +} + +function normalizeSourcePath(relativePath) { + return relativePath.replace(/\\/gu, '/'); +} + +function createIssue(repo, card, runner) { + const commandRunner = runner ?? run; + const dir = mkdtempSync(join(tmpdir(), 'method-migrate-')); + const bodyFile = join(dir, 'body.md'); + try { + writeFileSync(bodyFile, card.body, 'utf8'); + const args = ['issue', 'create', '--repo', repo, '--title', card.title, '--body-file', bodyFile]; + for (const label of card.labels) { + args.push('--label', label); + } + const stdout = commandRunner('gh', args).stdout.trim(); + return { number: issueNumberFromCreateOutput(stdout), title: card.title, url: stdout, state: 'OPEN' }; + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + +function issueNumberFromCreateOutput(stdout) { + const url = stdout.trim(); + const match = /\/issues\/(\d+)(?:[?#].*)?$/u.exec(url); + if (match === null) { + throw new Error(`Could not parse issue number from gh issue create output: ${JSON.stringify(stdout)}`); + } + const number = Number.parseInt(match[1], 10); + if (!Number.isSafeInteger(number)) { + throw new Error(`Could not parse issue number from gh issue create output: ${JSON.stringify(stdout)}`); + } + return number; +} + +function run(command, args, options = {}) { + const result = spawnSync(command, args, { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + }); + const ok = result.status === 0; + if (!ok && !options.allowFailure) { + fail(`${command} ${args.join(' ')} failed:\n${result.stderr || result.stdout}`); + } + return { + ok, + stdout: result.stdout ?? '', + stderr: result.stderr ?? '', + status: result.status ?? 1, + }; +} + +function renderJson(results) { + return results.map((result) => ({ + action: result.action, + path: result.card.relativePath, + title: result.card.title, + labels: result.card.labels, + issue: result.issue, + })); +} + +function renderText(results, repo, dryRun) { + process.stdout.write(`Method backlog migration ${dryRun ? '(dry-run)' : '(apply)'} for ${repo}\n`); + for (const result of results) { + const issueText = result.issue === undefined ? '' : ` -> ${result.issue.url}`; + process.stdout.write(`${result.action.padEnd(15)} ${result.card.relativePath}${issueText}\n`); + } + const created = results.filter((result) => result.action === 'created').length; + const skipped = results.filter((result) => result.action === 'skip-existing').length; + const dry = results.filter((result) => result.action === 'create-dry-run').length; + process.stdout.write(`Summary: created=${created} skipped_existing=${skipped} dry_run=${dry}\n`); +} + +function titleCase(slug) { + return slug + .split(/[-_]/u) + .filter(Boolean) + .map((word) => `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`) + .join(' '); +} + +function fail(message) { + process.stderr.write(`${message}\n`); + process.exit(1); +} + +export { + CANONICAL_LABELS, + collectBacklogCards, + createIssue, + ensureLabels, + labelsFor, + loadExistingIssueMap, + parseMarkdownDoc, + renderIssueBody, + renderJson, + normalizeSourcePath, + sourceBacklogMarker, + sourceBacklogPaths, + titleCase, +}; + +if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) { + main(); +} diff --git a/tests/docs.test.ts b/tests/docs.test.ts index 662eefd..7867e43 100644 --- a/tests/docs.test.ts +++ b/tests/docs.test.ts @@ -13,6 +13,7 @@ function readRepoFile(relativePath: string): string { function readBacklogDoc(filename: string): string { const matches = [ ...walkMarkdownFiles('docs/method/backlog'), + ...walkMarkdownFiles('docs/method/graveyard/github-issue-migration'), ...walkMarkdownFiles('docs/design'), ...walkMarkdownFiles('docs/releases'), ].filter((relativePath) => relativePath.endsWith(`/${filename}`)); @@ -424,7 +425,7 @@ describe('METHOD docs', () => { it('docs/PROCESS.md contains the branching and commitment rules.', () => { const process = readRepoFile('docs/PROCESS.md'); - expect(process).toContain('cycles/_'); + expect(process).toContain('linked issue title slug'); expect(process).toContain('stage and commit all modified files'); }); @@ -433,9 +434,9 @@ describe('METHOD docs', () => { expect(process).toContain('## Workflow'); }); - it('`docs.test.ts` validates that the policy includes specific naming patterns (e.g., `_`).', () => { + it('`docs.test.ts` validates that the policy includes specific naming patterns (e.g., issue-title slugs).', () => { const process = readRepoFile('docs/PROCESS.md'); - expect(process).toContain('_'); + expect(process).toContain('foo-feature-needs-external-bar-integration'); expect(process).toContain('maint-slug'); }); @@ -664,17 +665,17 @@ describe('METHOD docs', () => { expect(runbook).toContain('Never guess. Never claim success'); }); - it('Branch naming uses one canonical pattern in docs/PROCESS.md, with no contradictory examples.', () => { + it('Branch naming uses one canonical issue-title-slug pattern in docs/PROCESS.md, with no contradictory examples.', () => { const process = readRepoFile('docs/PROCESS.md'); - expect(process).toContain('cycles/_'); + expect(process).toContain('linked issue title slug'); - // process.md Rules section must use cycles/_, not bare cycles/ + // process.md must not retain the old cycle-name branch placeholders. expect(process).not.toContain('cycles/'); + expect(process).not.toContain('cycles/_'); - // Branch naming section must show cycles/ prefix - expect(process).toContain('cycles/'); - expect(process).toContain('`cycles/_`'); + // Branch naming section must show the issue-title slug pattern. + expect(process).toContain('`foo-feature-needs-external-bar-integration`'); }); it('The RED step in docs/PROCESS.md names playback questions as the test driver.', () => { diff --git a/tests/migrate-backlog-to-github-issues.test.ts b/tests/migrate-backlog-to-github-issues.test.ts new file mode 100644 index 0000000..44a7188 --- /dev/null +++ b/tests/migrate-backlog-to-github-issues.test.ts @@ -0,0 +1,219 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterEach, describe, expect, it } from 'vitest'; +import { + CANONICAL_LABELS, + collectBacklogCards, + createIssue, + ensureLabels, + labelsFor, + loadExistingIssueMap, + parseMarkdownDoc, + renderJson, + sourceBacklogMarker, + sourceBacklogPaths, +} from '../scripts/migrate-backlog-to-github-issues.mjs'; + +const tempDirs: string[] = []; + +afterEach(() => { + for (const dir of tempDirs.splice(0)) { + rmSync(dir, { recursive: true, force: true }); + } +}); + +function tempRoot() { + const root = mkdtempSync(join(tmpdir(), 'method-migrate-test-')); + tempDirs.push(root); + return root; +} + +describe('backlog GitHub issue migration script', () => { + it('collects lane cards with title, legend, priority, and Method labels', () => { + const root = tempRoot(); + const backlogRoot = join(root, 'docs/method/backlog'); + mkdirSync(join(backlogRoot, 'bad-code'), { recursive: true }); + writeFileSync( + join(backlogRoot, 'bad-code/CORE_fix-old-thing.md'), + ['---', 'priority: high', '---', '', '# Fix Old Thing', '', 'The original card body.', ''].join('\n'), + 'utf8', + ); + + const cards = collectBacklogCards(root, backlogRoot); + + expect(cards).toHaveLength(1); + expect(cards[0]).toMatchObject({ + lane: 'bad-code', + legend: 'CORE', + priority: 'high', + relativePath: 'docs/method/backlog/bad-code/CORE_fix-old-thing.md', + title: 'Fix Old Thing', + }); + expect(cards[0]?.labels).toEqual(['lane:bad-code', 'legend:core', 'priority:high', 'type:maintenance']); + expect(cards[0]?.body).toContain(sourceBacklogMarker('docs/method/backlog/bad-code/CORE_fix-old-thing.md')); + }); + + it('keeps the full canonical label set independent of migrated card contents', () => { + expect(CANONICAL_LABELS).toEqual( + expect.arrayContaining(['lane:inbox', 'work-in-progress', 'type:bug', 'type:enhancement', 'type:spike']), + ); + expect(labelsFor({ lane: 'v1.2.3', legend: 'PROCESS', priority: 'medium' })).toEqual([ + 'lane:release', + 'legend:process', + 'priority:medium', + ]); + expect(labelsFor({ lane: 'up-next', legend: 'PROCESS', priority: 'high' })).toEqual(['lane:asap', 'legend:process', 'priority:high']); + }); + + it('rejects unknown lanes instead of creating non-canonical labels', () => { + expect(() => labelsFor({ lane: 'somegarbage', legend: 'PROCESS' })).toThrow('Unknown Method backlog lane "somegarbage"'); + }); + + it('loads exact migrated source paths from existing GitHub issue bodies without search indexing', () => { + let observedArgs: string[] = []; + const existing = loadExistingIssueMap('owner/repo', (_command, args) => { + observedArgs = args; + return { + ok: true, + status: 0, + stderr: '', + stdout: `${JSON.stringify({ + number: 7, + title: 'Migrated card', + url: 'https://github.com/owner/repo/issues/7', + state: 'OPEN', + body: `${sourceBacklogMarker('docs/method/backlog/cool-ideas/PROCESS_card.md')}\n`, + })}\n`, + }; + }); + + expect(observedArgs).toContain('--paginate'); + expect(observedArgs).toContain('--jq'); + expect(existing.get('docs/method/backlog/cool-ideas/PROCESS_card.md')).toEqual({ + number: 7, + title: 'Migrated card', + url: 'https://github.com/owner/repo/issues/7', + state: 'OPEN', + }); + }); + + it('normalizes source backlog paths before comparing migration identity', () => { + expect(sourceBacklogPaths('Source backlog: `docs\\method\\backlog\\cool-ideas\\PROCESS_card.md`')).toEqual([ + 'docs/method/backlog/cool-ideas/PROCESS_card.md', + ]); + }); + + it('fails closed when two issues claim the same migrated source path', () => { + expect(() => + loadExistingIssueMap('owner/repo', () => ({ + ok: true, + status: 0, + stderr: '', + stdout: [ + JSON.stringify({ + number: 7, + title: 'First migrated card', + url: 'https://github.com/owner/repo/issues/7', + state: 'OPEN', + body: sourceBacklogMarker('docs/method/backlog/cool-ideas/PROCESS_card.md'), + }), + JSON.stringify({ + number: 8, + title: 'Duplicate migrated card', + url: 'https://github.com/owner/repo/issues/8', + state: 'OPEN', + body: sourceBacklogMarker('docs/method/backlog/cool-ideas/PROCESS_card.md'), + }), + ].join('\n'), + })), + ).toThrow('Duplicate Source backlog marker docs/method/backlog/cool-ideas/PROCESS_card.md'); + }); + + it('fails closed when existing issue loading fails', () => { + expect(() => + loadExistingIssueMap('owner/repo', () => ({ + ok: false, + status: 1, + stdout: '', + stderr: 'rate limited', + })), + ).toThrow('Unable to load existing GitHub issues'); + }); + + it('parses frontmatter only when the closing delimiter is exact', () => { + const raw = ['---', 'title: Not Frontmatter', '---not-a-close', '# Body', ''].join('\n'); + + expect(parseMarkdownDoc(raw)).toEqual({ frontmatter: {}, body: raw }); + }); + + it('keeps raw markdown when YAML frontmatter cannot be parsed', () => { + const raw = ['---', 'title: [unterminated', '---', '', '# Body', ''].join('\n'); + + expect(parseMarkdownDoc(raw)).toEqual({ frontmatter: {}, body: raw }); + }); + + it('uses paginated GitHub label loading before creating missing labels', () => { + const calls: string[][] = []; + + ensureLabels('owner/repo', ['lane:inbox', 'type:bug'], (_command, args) => { + calls.push(args); + if (args[0] === 'api') { + return { ok: true, status: 0, stderr: '', stdout: 'lane:inbox\n' }; + } + return { ok: true, status: 0, stderr: '', stdout: '' }; + }); + + expect(calls[0]).toEqual(expect.arrayContaining(['api', 'repos/owner/repo/labels', '--paginate', '-f', 'per_page=100'])); + expect(calls[1]).toEqual(expect.arrayContaining(['label', 'create', 'type:bug', '--repo', 'owner/repo'])); + }); + + it('parses issue numbers from gh issue create URLs', () => { + const issue = createIssue('owner/repo', { title: 'Card', body: 'Body', labels: ['lane:inbox'] }, () => ({ + ok: true, + status: 0, + stderr: '', + stdout: 'https://github.com/owner/repo/issues/42?created=1\n', + })); + + expect(issue).toMatchObject({ + number: 42, + title: 'Card', + url: 'https://github.com/owner/repo/issues/42?created=1', + state: 'OPEN', + }); + }); + + it('fails closed when gh issue create output does not contain an issue number', () => { + expect(() => + createIssue('owner/repo', { title: 'Card', body: 'Body', labels: ['lane:inbox'] }, () => ({ + ok: true, + status: 0, + stderr: '', + stdout: 'https://github.com/owner/repo/pulls/nope\n', + })), + ).toThrow('Could not parse issue number from gh issue create output'); + }); + + it('renders dry-run style JSON without shelling out to GitHub', () => { + const results = renderJson([ + { + action: 'create-dry-run', + card: { + relativePath: 'docs/method/backlog/cool-ideas/PROCESS_card.md', + title: 'Card', + labels: ['lane:cool-ideas', 'legend:process'], + }, + }, + ]); + + expect(results).toEqual([ + { + action: 'create-dry-run', + path: 'docs/method/backlog/cool-ideas/PROCESS_card.md', + title: 'Card', + labels: ['lane:cool-ideas', 'legend:process'], + }, + ]); + }); +});