From 2326d59c2b4a24d9f89e5f9f32aea72b5ac3fdb1 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 31 May 2026 22:08:59 -0700 Subject: [PATCH 1/9] docs: make GitHub Issues the Method work tracker --- .github/ISSUE_TEMPLATE/bug.yml | 53 +++++++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/spike.yml | 45 ++++++ .github/ISSUE_TEMPLATE/work-item.yml | 109 ++++++++++++++ .github/pull_request_template.md | 20 +++ CONTRIBUTING.md | 18 ++- README.md | 35 +++-- docs/GUIDE.md | 70 +++++---- docs/PROCESS.md | 66 +++++--- ...e-github-issues-the-method-work-tracker.md | 141 ++++++++++++++++++ 10 files changed, 484 insertions(+), 78 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/spike.yml create mode 100644 .github/ISSUE_TEMPLATE/work-item.yml create mode 100644 .github/pull_request_template.md create mode 100644 docs/design/PROCESS_make-github-issues-the-method-work-tracker.md diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..61b75cf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,53 @@ +name: Bug report +description: Report a Method defect or regression. +title: "" +labels: + - bug + - lane:inbox +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..f23055a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/work-item.yml @@ -0,0 +1,109 @@ +name: Method work item +description: Propose shaped Method work with a hill, scope, and witness plan. +title: "" +labels: + - lane:inbox +body: + - type: markdown + attributes: + value: | + Use a short, branch-safe title. Active work uses a branch slug derived from this title. + - type: dropdown + id: lane + attributes: + label: Lane + description: The Method lane for this work. + options: + - lane:inbox + - lane:asap + - lane:bad-code + - lane:cool-ideas + - lane:release + default: 0 + validations: + required: true + - type: dropdown + id: legend + attributes: + label: Legend + description: Repo-local Method legend. + options: + - legend:process + - legend:synth + - legend:core + default: 0 + validations: + required: true + - 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..6110e59 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,20 @@ +## 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/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..7363fa1 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 @@ -32,8 +33,8 @@ method inbox "Add dark mode support" --legend PROCESS # 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 @@ -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..7915fda 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,7 +70,7 @@ flowchart LR ```mermaid gitGraph commit id: "main" - branch cycles/PROCESS_my-feature + branch my-feature commit id: "design doc" commit id: "failing tests" commit id: "make tests pass" @@ -80,12 +80,12 @@ gitGraph 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 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..6c62a6b 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,27 @@ repair it before continuing. ## Workflow -METHOD uses Git for distributed coordination but remains forge-agnostic. +METHOD 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 +112,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 +271,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..7d835e9 --- /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" +source_issue: "https://github.com/flyingrobots/method/issues/27" +branch: "make-github-issues-the-method-work-tracker" +--- + +# 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 + +- [ ] Does a new contributor see GitHub Issues as the canonical place to + find and propose work? +- [ ] Does the repo still explain that design docs, witnesses, playbacks, + retros, and release evidence remain Method artifacts? +- [ ] Does active work have a predictable branch name derived from the + issue title? + +### Agent + +- [ ] Are Method lane, legend, priority, type, and active-work states + represented as explicit GitHub labels? +- [ ] Do issue and PR templates encode the Method evidence contract? +- [ ] 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 backlog files are not deleted in this cycle. They remain as +migration input until a migration command or manual migration pass +creates corresponding GitHub Issues and archives/stubs the old files. + +## 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. +5. Archive or stub migrated backlog cards 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 + +- [ ] GitHub labels for the Method issue taxonomy exist in + `flyingrobots/method`. +- [ ] GitHub issue templates exist for work items, bugs, and spikes. +- [ ] A PR template links GitHub issue tracking to Method evidence. +- [ ] README and PROCESS state that GitHub Issues are the live tracker. +- [ ] CONTRIBUTING starts from GitHub Issues, not local backlog files. +- [ ] Branch naming doctrine uses issue-title slugs. +- [ ] Existing filesystem-backlog GitHub sync behavior is documented as + migration/legacy behavior. +- [ ] The current Method backlog migration path is documented before + Echo migration starts. From 90e58eab9ea5795fc5b18c889efa8443a366a5a7 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 31 May 2026 22:18:17 -0700 Subject: [PATCH 2/9] feat: add Method backlog issue migration --- ...e-github-issues-the-method-work-tracker.md | 3 +- package.json | 1 + scripts/migrate-backlog-to-github-issues.mjs | 350 ++++++++++++++++++ 3 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 scripts/migrate-backlog-to-github-issues.mjs 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 index 7d835e9..a5a0b50 100644 --- a/docs/design/PROCESS_make-github-issues-the-method-work-tracker.md +++ b/docs/design/PROCESS_make-github-issues-the-method-work-tracker.md @@ -80,7 +80,8 @@ creates corresponding GitHub Issues and archives/stubs the old files. 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. + provenance in the issue body using + `scripts/migrate-backlog-to-github-issues.mjs` or `npm run migrate`. 5. Archive or stub migrated backlog cards so they stop acting as a second live tracker. 6. Leave historical graveyard files read-only until a separate cleanup 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..451c0c2 --- /dev/null +++ b/scripts/migrate-backlog-to-github-issues.mjs @@ -0,0 +1,350 @@ +#!/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 { 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.']], + ['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:spike', ['C5DEF5', 'Temporary proof or learning cycle.']], + ['type:maintenance', ['BFDADC', 'Maintenance, cleanup, or operational workflow work.']], +]); + +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); + if (!dryRun) { + ensureLabels(repo, [...new Set(cards.flatMap((card) => card.labels))]); + } + + const results = []; + for (const card of cards) { + const existing = findExistingIssue(repo, 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 = 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---', 4); + if (closeIndex === -1) { + return { frontmatter: {}, body: raw }; + } + const yaml = raw.slice(4, closeIndex); + const bodyStart = raw.indexOf('\n', closeIndex + 4); + const body = bodyStart === -1 ? '' : raw.slice(bodyStart + 1); + 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 = []; + const laneLabel = /^v\d+\.\d+\.\d+$/u.test(lane) ? 'lane:release' : `lane:${lane}`; + labels.push(laneLabel); + + 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 renderIssueBody({ relativePath, lane, legend, priority, title, originalBody }) { + const metadata = [ + `Source backlog: \`${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) { + const existingRaw = run('gh', ['label', 'list', '--repo', repo, '--limit', '500', '--json', 'name', '--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.']; + run('gh', ['label', 'create', label, '--repo', repo, '--color', color, '--description', description]); + existing.add(label); + } +} + +function findExistingIssue(repo, relativePath) { + const search = `"${relativePath}" in:body`; + const result = run( + 'gh', + ['issue', 'list', '--repo', repo, '--state', 'all', '--search', search, '--limit', '10', '--json', 'number,title,url,state'], + { allowFailure: true }, + ); + if (!result.ok) { + return undefined; + } + const matches = JSON.parse(result.stdout); + const exact = matches.find((issue) => typeof issue.url === 'string'); + if (exact === undefined) { + return undefined; + } + return { + number: exact.number, + title: exact.title, + url: exact.url, + state: exact.state, + }; +} + +function createIssue(repo, card) { + 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 = run('gh', args).stdout.trim(); + const number = Number.parseInt(stdout.slice(stdout.lastIndexOf('/') + 1), 10); + return { number, title: card.title, url: stdout, state: 'OPEN' }; + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + +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); +} + +main(); From 6d7ee1af2f2e18ec212feb654ca7642a7effadaf Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 31 May 2026 22:28:51 -0700 Subject: [PATCH 3/9] test: update Method docs policy expectations --- docs/PROCESS.md | 5 +++-- ...make-github-issues-the-method-work-tracker.md | 1 - tests/docs.test.ts | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/PROCESS.md b/docs/PROCESS.md index 6c62a6b..2429fe2 100644 --- a/docs/PROCESS.md +++ b/docs/PROCESS.md @@ -84,8 +84,9 @@ repair it before continuing. ## Workflow -METHOD uses Git for distributed coordination. GitHub Issues are the -default live tracker for work in this implementation. +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 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 index a5a0b50..fbb8061 100644 --- a/docs/design/PROCESS_make-github-issues-the-method-work-tracker.md +++ b/docs/design/PROCESS_make-github-issues-the-method-work-tracker.md @@ -2,7 +2,6 @@ title: "Make GitHub Issues The Method Work Tracker" legend: PROCESS cycle: "PROCESS_make-github-issues-the-method-work-tracker" -source_issue: "https://github.com/flyingrobots/method/issues/27" branch: "make-github-issues-the-method-work-tracker" --- diff --git a/tests/docs.test.ts b/tests/docs.test.ts index 662eefd..11129a6 100644 --- a/tests/docs.test.ts +++ b/tests/docs.test.ts @@ -433,9 +433,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 +664,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.', () => { From 4b13ec4d7f9c5744380d39005cd588015e62ef8a Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 31 May 2026 22:29:07 -0700 Subject: [PATCH 4/9] test: remove stale cycle branch assumptions --- .../PROCESS_make-github-issues-the-method-work-tracker.md | 1 - tests/docs.test.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) 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 index fbb8061..17eb306 100644 --- a/docs/design/PROCESS_make-github-issues-the-method-work-tracker.md +++ b/docs/design/PROCESS_make-github-issues-the-method-work-tracker.md @@ -2,7 +2,6 @@ title: "Make GitHub Issues The Method Work Tracker" legend: PROCESS cycle: "PROCESS_make-github-issues-the-method-work-tracker" -branch: "make-github-issues-the-method-work-tracker" --- # Make GitHub Issues The Method Work Tracker diff --git a/tests/docs.test.ts b/tests/docs.test.ts index 11129a6..31af81c 100644 --- a/tests/docs.test.ts +++ b/tests/docs.test.ts @@ -424,7 +424,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'); }); From ba4d9132dbd28947b178b907e916d48a1aeef3e8 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 31 May 2026 23:00:23 -0700 Subject: [PATCH 5/9] fix: harden Method issue migration --- .github/ISSUE_TEMPLATE/bug.yml | 2 +- .github/ISSUE_TEMPLATE/work-item.yml | 35 ++--- ARCHITECTURE.md | 3 +- README.md | 4 +- docs/GUIDE.md | 4 +- ...e-github-issues-the-method-work-tracker.md | 11 +- .../github-issue-migration/README.md | 47 +++++++ ...yard-and-enforce-retirement-disposition.md | 0 ...rkspace-initialization-signpost-aliases.md | 0 .../CORE_agent-cycle-discipline-guardrails.md | 0 ...ORE_method-repair-stale-cycle-detection.md | 0 ...S_architecture-source-inventory-section.md | 0 .../PROCESS_backlog-query-surface.md | 0 ...ROCESS_backlog-summary-signpost-section.md | 0 .../PROCESS_command-registry-pattern.md | 0 .../PROCESS_cycle-packet-markdownlint-gate.md | 0 .../PROCESS_design-doc-template-catalog.md | 0 ...PROCESS_doctor-policy-toleration-config.md | 0 ...SS_doctor-strict-mode-for-release-gates.md | 0 ...s-string-matching-over-meaningful-tests.md | 0 .../PROCESS_filesystem-abstraction.md | 0 ...r-should-preview-destructive-operations.md | 0 .../PROCESS_getting-started-walkthrough.md | 0 .../PROCESS_github-milestone-lane-sync.md | 0 .../PROCESS_i18n-string-extraction.md | 0 .../PROCESS_interactive-scaffolder.md | 0 .../PROCESS_legend-audit-and-assignment.md | 0 ...SS_maintenance-track-for-sub-cycle-work.md | 0 .../cool-ideas/PROCESS_multi-forge-adapter.md | 0 .../cool-ideas/PROCESS_next-work-menu.md | 0 ...posture-sections-in-design-doc-template.md | 0 .../PROCESS_remote-issue-deletion-sync.md | 0 .../PROCESS_retro-scaffold-refresh-command.md | 0 .../PROCESS_review-closeout-helper.md | 0 .../PROCESS_review-config-hardening.md | 0 ...ght-sized-cycle-ceremony-t-shirt-sizing.md | 0 ...unt-scales-with-overhead-not-complexity.md | 0 .../PROCESS_signpost-template-overrides.md | 0 ...OCESS_visual-witness-screenshot-capture.md | 0 .../PROCESS_witness-file-capture.md | 0 ...rtifact-history-and-semantic-provenance.md | 0 .../cool-ideas/SYNTH_cycle-witness-command.md | 0 .../cool-ideas/SYNTH_sync-vision-command.md | 0 scripts/migrate-backlog-to-github-issues.mjs | 103 ++++++++++++--- .../migrate-backlog-to-github-issues.test.ts | 124 ++++++++++++++++++ 45 files changed, 277 insertions(+), 56 deletions(-) create mode 100644 docs/method/graveyard/github-issue-migration/README.md rename docs/method/{backlog => graveyard/github-issue-migration}/bad-code/PROCESS_reserve-graveyard-and-enforce-retirement-disposition.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/bad-code/PROCESS_workspace-initialization-signpost-aliases.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/CORE_agent-cycle-discipline-guardrails.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/CORE_method-repair-stale-cycle-detection.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_architecture-source-inventory-section.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_backlog-query-surface.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_backlog-summary-signpost-section.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_command-registry-pattern.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_cycle-packet-markdownlint-gate.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_design-doc-template-catalog.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_doctor-policy-toleration-config.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_doctor-strict-mode-for-release-gates.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_drift-detector-rewards-string-matching-over-meaningful-tests.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_filesystem-abstraction.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_flatten-repair-should-preview-destructive-operations.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_getting-started-walkthrough.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_github-milestone-lane-sync.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_i18n-string-extraction.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_interactive-scaffolder.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_legend-audit-and-assignment.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_maintenance-track-for-sub-cycle-work.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_multi-forge-adapter.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_next-work-menu.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_optional-posture-sections-in-design-doc-template.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_remote-issue-deletion-sync.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_retro-scaffold-refresh-command.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_review-closeout-helper.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_review-config-hardening.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_right-sized-cycle-ceremony-t-shirt-sizing.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_signpost-count-scales-with-overhead-not-complexity.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_signpost-template-overrides.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_visual-witness-screenshot-capture.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/PROCESS_witness-file-capture.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/SYNTH_artifact-history-and-semantic-provenance.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/SYNTH_cycle-witness-command.md (100%) rename docs/method/{backlog => graveyard/github-issue-migration}/cool-ideas/SYNTH_sync-vision-command.md (100%) create mode 100644 tests/migrate-backlog-to-github-issues.test.ts diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 61b75cf..a9d7477 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -2,8 +2,8 @@ name: Bug report description: Report a Method defect or regression. title: "" labels: - - bug - lane:inbox + - type:bug body: - type: textarea id: observed diff --git a/.github/ISSUE_TEMPLATE/work-item.yml b/.github/ISSUE_TEMPLATE/work-item.yml index f23055a..ab4bff9 100644 --- a/.github/ISSUE_TEMPLATE/work-item.yml +++ b/.github/ISSUE_TEMPLATE/work-item.yml @@ -3,37 +3,22 @@ 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. - - type: dropdown - id: lane - attributes: - label: Lane - description: The Method lane for this work. - options: - - lane:inbox - - lane:asap - - lane:bad-code - - lane:cool-ideas - - lane:release - default: 0 - validations: - required: true - - type: dropdown - id: legend + 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: Legend - description: Repo-local Method legend. - options: - - legend:process - - legend:synth - - legend:core - default: 0 - validations: - required: true + label: Requested classification + description: Optional triage request. This does not set canonical labels. + placeholder: | + Suggested lane: + Suggested legend: - type: textarea id: hill attributes: 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/README.md b/README.md index 7363fa1..9ead23c 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ 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 @@ -41,7 +41,7 @@ gh issue list --label lane:asap | 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 | diff --git a/docs/GUIDE.md b/docs/GUIDE.md index 7915fda..f5bb299 100644 --- a/docs/GUIDE.md +++ b/docs/GUIDE.md @@ -76,7 +76,7 @@ gitGraph commit id: "make tests pass" commit id: "close cycle" checkout main - merge cycles/PROCESS_my-feature id: "PR merge" + merge my-feature id: "PR merge" commit id: "ship sync" ``` @@ -151,7 +151,7 @@ runbook. ### Capture ideas immediately -If work-worthy idea surfaces during work, capture it now as a GitHub +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 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 index 17eb306..b780034 100644 --- a/docs/design/PROCESS_make-github-issues-the-method-work-tracker.md +++ b/docs/design/PROCESS_make-github-issues-the-method-work-tracker.md @@ -2,6 +2,7 @@ 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 @@ -65,9 +66,9 @@ short, sane, branch-safe, and contributor-readable. ### Decision 4: Filesystem backlog becomes legacy/migration surface -Existing backlog files are not deleted in this cycle. They remain as -migration input until a migration command or manual migration pass -creates corresponding GitHub Issues and archives/stubs the old files. +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 @@ -80,8 +81,8 @@ creates corresponding GitHub Issues and archives/stubs the old files. 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 or stub migrated backlog cards so they stop acting as a - second live tracker. +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. 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..67f2bcc --- /dev/null +++ b/docs/method/graveyard/github-issue-migration/README.md @@ -0,0 +1,47 @@ +# 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/scripts/migrate-backlog-to-github-issues.mjs b/scripts/migrate-backlog-to-github-issues.mjs index 451c0c2..61f5606 100644 --- a/scripts/migrate-backlog-to-github-issues.mjs +++ b/scripts/migrate-backlog-to-github-issues.mjs @@ -3,6 +3,7 @@ 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'; @@ -13,16 +14,27 @@ const LABELS = new Map([ ['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()]; + function main() { const args = parseArgs(process.argv.slice(2)); const root = process.cwd(); @@ -38,13 +50,14 @@ function main() { } const cards = collectBacklogCards(root, backlogRoot); + const existingIssues = loadExistingIssueMap(repo); if (!dryRun) { - ensureLabels(repo, [...new Set(cards.flatMap((card) => card.labels))]); + ensureLabels(repo, CANONICAL_LABELS); } const results = []; for (const card of cards) { - const existing = findExistingIssue(repo, card.relativePath); + const existing = existingIssues.get(card.relativePath); if (existing !== undefined) { results.push({ action: 'skip-existing', card, issue: existing }); continue; @@ -222,7 +235,7 @@ function labelsFor({ lane, legend, priority }) { function renderIssueBody({ relativePath, lane, legend, priority, title, originalBody }) { const metadata = [ - `Source backlog: \`${relativePath}\``, + sourceBacklogMarker(relativePath), `Original lane: \`${lane}\``, legend === undefined ? undefined : `Original legend: \`${legend}\``, priority === undefined ? undefined : `Original priority: \`${priority}\``, @@ -255,27 +268,59 @@ function ensureLabels(repo, labels) { } } -function findExistingIssue(repo, relativePath) { - const search = `"${relativePath}" in:body`; - const result = run( +function loadExistingIssueMap(repo, runner) { + const commandRunner = runner ?? run; + const result = commandRunner( 'gh', - ['issue', 'list', '--repo', repo, '--state', 'all', '--search', search, '--limit', '10', '--json', 'number,title,url,state'], - { allowFailure: true }, + [ + '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) { - return undefined; + throw new Error(`Unable to load existing GitHub issues for ${repo}; aborting to avoid duplicate migration issues.`); } - const matches = JSON.parse(result.stdout); - const exact = matches.find((issue) => typeof issue.url === 'string'); - if (exact === undefined) { - return undefined; + 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)) { + if (!bySourcePath.has(relativePath)) { + bySourcePath.set(relativePath, { + number: issue.number, + title: issue.title, + url: issue.url, + state: issue.state, + }); + } + } } - return { - number: exact.number, - title: exact.title, - url: exact.url, - state: exact.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); } function createIssue(repo, card) { @@ -296,6 +341,9 @@ function createIssue(repo, card) { } function run(command, args, options = {}) { + if (options.runner !== undefined) { + return options.runner(command, args, options); + } const result = spawnSync(command, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], @@ -347,4 +395,19 @@ function fail(message) { process.exit(1); } -main(); +export { + CANONICAL_LABELS, + collectBacklogCards, + labelsFor, + loadExistingIssueMap, + parseMarkdownDoc, + renderIssueBody, + renderJson, + sourceBacklogMarker, + sourceBacklogPaths, + titleCase, +}; + +if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) { + main(); +} 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..446639d --- /dev/null +++ b/tests/migrate-backlog-to-github-issues.test.ts @@ -0,0 +1,124 @@ +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, + labelsFor, + loadExistingIssueMap, + renderJson, + sourceBacklogMarker, +} 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', + ]); + }); + + 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('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('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'], + }, + ]); + }); +}); From 85601d88cf6b9e13c4ec91b731d3c318c4a03a72 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 31 May 2026 23:00:48 -0700 Subject: [PATCH 6/9] test: account for migrated backlog archive --- docs/method/graveyard/github-issue-migration/README.md | 5 +++++ tests/docs.test.ts | 1 + 2 files changed, 6 insertions(+) diff --git a/docs/method/graveyard/github-issue-migration/README.md b/docs/method/graveyard/github-issue-migration/README.md index 67f2bcc..3804824 100644 --- a/docs/method/graveyard/github-issue-migration/README.md +++ b/docs/method/graveyard/github-issue-migration/README.md @@ -1,3 +1,8 @@ +--- +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. diff --git a/tests/docs.test.ts b/tests/docs.test.ts index 31af81c..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}`)); From 42373baf962215dbad4f65834aea4b84b1bb3dfe Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 1 Jun 2026 01:07:40 -0700 Subject: [PATCH 7/9] Fix: audit production dependencies --- package-lock.json | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) 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" From c0330bd0f7f4e31370570fcec966b4e24d4189a4 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 1 Jun 2026 02:20:43 -0700 Subject: [PATCH 8/9] Fix: address final Method review findings --- .github/pull_request_template.md | 2 ++ docs/GUIDE.md | 4 +-- scripts/migrate-backlog-to-github-issues.mjs | 20 +++++++++-- .../migrate-backlog-to-github-issues.test.ts | 33 +++++++++++++++++++ 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6110e59..84ab844 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,3 +1,5 @@ +# Pull Request + ## Linked issue Closes # diff --git a/docs/GUIDE.md b/docs/GUIDE.md index f5bb299..c4cc1e4 100644 --- a/docs/GUIDE.md +++ b/docs/GUIDE.md @@ -70,13 +70,13 @@ flowchart LR ```mermaid gitGraph commit id: "main" - branch 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 my-feature id: "PR merge" + merge foo-feature-needs-external-bar-integration id: "PR merge" commit id: "ship sync" ``` diff --git a/scripts/migrate-backlog-to-github-issues.mjs b/scripts/migrate-backlog-to-github-issues.mjs index 61f5606..d795d4c 100644 --- a/scripts/migrate-backlog-to-github-issues.mjs +++ b/scripts/migrate-backlog-to-github-issues.mjs @@ -164,7 +164,7 @@ function sortedDirNames(dir) { function readBacklogCard(root, backlogRoot, laneName, fullPath) { const raw = readFileSync(fullPath, 'utf8'); const parsed = parseMarkdownDoc(raw); - const relativePath = relative(root, fullPath); + 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; @@ -294,7 +294,13 @@ function loadExistingIssueMap(repo, runner) { for (const issue of issues) { const body = typeof issue.body === 'string' ? issue.body : ''; for (const relativePath of sourceBacklogPaths(body)) { - if (!bySourcePath.has(relativePath)) { + 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, @@ -320,7 +326,14 @@ function sourceBacklogMarker(relativePath) { } function sourceBacklogPaths(body) { - return [...body.matchAll(/Source backlog: `([^`]+)`/gu)].map((match) => match[1]).filter(Boolean); + 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) { @@ -403,6 +416,7 @@ export { parseMarkdownDoc, renderIssueBody, renderJson, + normalizeSourcePath, sourceBacklogMarker, sourceBacklogPaths, titleCase, diff --git a/tests/migrate-backlog-to-github-issues.test.ts b/tests/migrate-backlog-to-github-issues.test.ts index 446639d..a973a6f 100644 --- a/tests/migrate-backlog-to-github-issues.test.ts +++ b/tests/migrate-backlog-to-github-issues.test.ts @@ -9,6 +9,7 @@ import { loadExistingIssueMap, renderJson, sourceBacklogMarker, + sourceBacklogPaths, } from '../scripts/migrate-backlog-to-github-issues.mjs'; const tempDirs: string[] = []; @@ -89,6 +90,38 @@ describe('backlog GitHub issue migration script', () => { }); }); + 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', () => ({ From 5c01035796e966bea58a1eb055a2eee10788aea4 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 1 Jun 2026 02:29:16 -0700 Subject: [PATCH 9/9] Fix: close Method tracker review findings --- ...e-github-issues-the-method-work-tracker.md | 28 ++++---- ...e-github-issues-the-method-work-tracker.md | 63 +++++++++++++++++ .../witness/verification.md | 53 ++++++++++++++ scripts/migrate-backlog-to-github-issues.mjs | 70 +++++++++++++++---- .../migrate-backlog-to-github-issues.test.ts | 62 ++++++++++++++++ 5 files changed, 247 insertions(+), 29 deletions(-) create mode 100644 docs/method/retro/PROCESS_make-github-issues-the-method-work-tracker/PROCESS_make-github-issues-the-method-work-tracker.md create mode 100644 docs/method/retro/PROCESS_make-github-issues-the-method-work-tracker/witness/verification.md 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 index b780034..75ada97 100644 --- a/docs/design/PROCESS_make-github-issues-the-method-work-tracker.md +++ b/docs/design/PROCESS_make-github-issues-the-method-work-tracker.md @@ -29,19 +29,19 @@ docs, witnesses, playbacks, and retros remain durable repo evidence. ### Human -- [ ] Does a new contributor see GitHub Issues as the canonical place to +- [x] Does a new contributor see GitHub Issues as the canonical place to find and propose work? -- [ ] Does the repo still explain that design docs, witnesses, playbacks, +- [x] Does the repo still explain that design docs, witnesses, playbacks, retros, and release evidence remain Method artifacts? -- [ ] Does active work have a predictable branch name derived from the +- [x] Does active work have a predictable branch name derived from the issue title? ### Agent -- [ ] Are Method lane, legend, priority, type, and active-work states +- [x] Are Method lane, legend, priority, type, and active-work states represented as explicit GitHub labels? -- [ ] Do issue and PR templates encode the Method evidence contract? -- [ ] Do docs classify existing filesystem backlog commands as legacy or +- [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 @@ -128,14 +128,14 @@ but not in live backlog lanes. ## Acceptance Criteria -- [ ] GitHub labels for the Method issue taxonomy exist in +- [x] GitHub labels for the Method issue taxonomy exist in `flyingrobots/method`. -- [ ] GitHub issue templates exist for work items, bugs, and spikes. -- [ ] A PR template links GitHub issue tracking to Method evidence. -- [ ] README and PROCESS state that GitHub Issues are the live tracker. -- [ ] CONTRIBUTING starts from GitHub Issues, not local backlog files. -- [ ] Branch naming doctrine uses issue-title slugs. -- [ ] Existing filesystem-backlog GitHub sync behavior is documented as +- [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. -- [ ] The current Method backlog migration path is documented before +- [x] The current Method backlog migration path is documented before Echo migration starts. 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/scripts/migrate-backlog-to-github-issues.mjs b/scripts/migrate-backlog-to-github-issues.mjs index d795d4c..5063c9d 100644 --- a/scripts/migrate-backlog-to-github-issues.mjs +++ b/scripts/migrate-backlog-to-github-issues.mjs @@ -34,6 +34,7 @@ const LABELS = new Map([ ]); const CANONICAL_LABELS = [...LABELS.keys()]; +const RELEASE_LANE_PATTERN = /^v\d+\.\d+\.\d+$/u; function main() { const args = parseArgs(process.argv.slice(2)); @@ -190,13 +191,12 @@ function parseMarkdownDoc(raw) { if (!raw.startsWith('---\n')) { return { frontmatter: {}, body: raw }; } - const closeIndex = raw.indexOf('\n---', 4); + const closeIndex = raw.indexOf('\n---\n', 4); if (closeIndex === -1) { return { frontmatter: {}, body: raw }; } const yaml = raw.slice(4, closeIndex); - const bodyStart = raw.indexOf('\n', closeIndex + 4); - const body = bodyStart === -1 ? '' : raw.slice(bodyStart + 1); + const body = raw.slice(closeIndex + '\n---\n'.length); try { return { frontmatter: parseYaml(yaml) ?? {}, body }; } catch { @@ -215,8 +215,7 @@ function normalizeString(value) { function labelsFor({ lane, legend, priority }) { const labels = []; - const laneLabel = /^v\d+\.\d+\.\d+$/u.test(lane) ? 'lane:release' : `lane:${lane}`; - labels.push(laneLabel); + labels.push(labelForLane(lane)); if (legend !== undefined) { labels.push(`legend:${legend.toLowerCase()}`); @@ -233,6 +232,24 @@ function labelsFor({ lane, legend, priority }) { 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), @@ -255,15 +272,26 @@ function renderIssueBody({ relativePath, lane, legend, priority, title, original ].join('\n'); } -function ensureLabels(repo, labels) { - const existingRaw = run('gh', ['label', 'list', '--repo', repo, '--limit', '500', '--json', 'name', '--jq', '.[].name']).stdout; +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.']; - run('gh', ['label', 'create', label, '--repo', repo, '--color', color, '--description', description]); + commandRunner('gh', ['label', 'create', label, '--repo', repo, '--color', color, '--description', description]); existing.add(label); } } @@ -336,7 +364,8 @@ function normalizeSourcePath(relativePath) { return relativePath.replace(/\\/gu, '/'); } -function createIssue(repo, card) { +function createIssue(repo, card, runner) { + const commandRunner = runner ?? run; const dir = mkdtempSync(join(tmpdir(), 'method-migrate-')); const bodyFile = join(dir, 'body.md'); try { @@ -345,18 +374,27 @@ function createIssue(repo, card) { for (const label of card.labels) { args.push('--label', label); } - const stdout = run('gh', args).stdout.trim(); - const number = Number.parseInt(stdout.slice(stdout.lastIndexOf('/') + 1), 10); - return { number, title: card.title, url: stdout, state: 'OPEN' }; + 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 run(command, args, options = {}) { - if (options.runner !== undefined) { - return options.runner(command, args, options); +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'], @@ -411,6 +449,8 @@ function fail(message) { export { CANONICAL_LABELS, collectBacklogCards, + createIssue, + ensureLabels, labelsFor, loadExistingIssueMap, parseMarkdownDoc, diff --git a/tests/migrate-backlog-to-github-issues.test.ts b/tests/migrate-backlog-to-github-issues.test.ts index a973a6f..44a7188 100644 --- a/tests/migrate-backlog-to-github-issues.test.ts +++ b/tests/migrate-backlog-to-github-issues.test.ts @@ -5,8 +5,11 @@ import { afterEach, describe, expect, it } from 'vitest'; import { CANONICAL_LABELS, collectBacklogCards, + createIssue, + ensureLabels, labelsFor, loadExistingIssueMap, + parseMarkdownDoc, renderJson, sourceBacklogMarker, sourceBacklogPaths, @@ -60,6 +63,11 @@ describe('backlog GitHub issue migration script', () => { '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', () => { @@ -133,6 +141,60 @@ describe('backlog GitHub issue migration script', () => { ).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([ {