@plainconceptsplatform/agent-harness 3.1.1 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +452 -434
  2. package/cli/commands/update.js +121 -121
  3. package/cli/fragments/archive/az.md +97 -97
  4. package/cli/fragments/archive/gh.md +96 -96
  5. package/cli/fragments/archive/gl.md +96 -96
  6. package/cli/fragments/archive/none.md +75 -75
  7. package/cli/fragments/guardrails/codegraph.md +5 -5
  8. package/cli/fragments/guardrails/humanizer.md +4 -4
  9. package/cli/fragments/guardrails/memory.md +4 -4
  10. package/cli/fragments/guardrails/rtk.md +3 -3
  11. package/cli/fragments/guardrails/simple-english.md +4 -4
  12. package/cli/fragments/ops-ship/az.md +81 -81
  13. package/cli/presets/agents-content.json +34 -34
  14. package/cli/presets/browser.json +10 -10
  15. package/cli/steps/browser/index.js +48 -48
  16. package/cli/steps/copy/commands.js +79 -79
  17. package/cli/steps/copy/index.js +88 -88
  18. package/cli/steps/copy/skills.js +307 -307
  19. package/cli/steps/metadata/index.js +115 -115
  20. package/cli/steps/models/write.js +38 -38
  21. package/cli/utils/copy.js +5 -2
  22. package/cli/utils/paths.js +80 -80
  23. package/harness/.opencode/_gitignore +9 -9
  24. package/harness/.opencode/commands/init.md +5 -5
  25. package/harness/.opencode/commands/make-architecture.md +5 -5
  26. package/harness/.opencode/commands/make-design.md +5 -5
  27. package/harness/.opencode/commands/make-engineer.md +5 -5
  28. package/harness/.opencode/commands/make-guardrails.md +5 -5
  29. package/harness/.opencode/commands/make-user-model.md +5 -5
  30. package/harness/.opencode/commands/plan-apply.md +9 -9
  31. package/harness/.opencode/commands/plan-goal.md +5 -5
  32. package/harness/.opencode/commands/plan-quick.md +5 -5
  33. package/harness/.opencode/commands/plan-story.md +9 -9
  34. package/harness/.opencode/commands/repo-audit.md +5 -5
  35. package/harness/.opencode/commands/repo-initialize.md +5 -5
  36. package/harness/.opencode/commands/repo-onboard.md +5 -5
  37. package/harness/.opencode/commands/repo-verify.md +5 -5
  38. package/harness/.opencode/scripts/check-diff.mjs +225 -0
  39. package/harness/.opencode/scripts/code-rules.mjs +280 -0
  40. package/harness/AGENTS.md +49 -49
  41. package/package.json +1 -1
  42. package/skills/README.md +18 -18
  43. package/skills/browser-automation/SKILL.md +72 -72
  44. package/skills/pc-guardrails-generic/SKILL.md +2 -1
  45. package/skills/pc-make-architecture/SKILL.md +32 -32
  46. package/skills/pc-make-design/SKILL.md +45 -45
  47. package/skills/pc-make-engineer/signal-mapping.md +53 -53
  48. package/skills/pc-make-guardrails/SKILL.md +44 -44
  49. package/skills/pc-make-user-model/SKILL.md +56 -56
  50. package/skills/pc-plan-apply/SKILL.md +1 -1
  51. package/skills/pc-plan-apply/simple-mode.md +21 -21
  52. package/skills/pc-plan-archive/SKILL.md +66 -66
  53. package/skills/pc-plan-goal/branching.md +39 -39
  54. package/skills/pc-plan-goal/output.md +71 -71
  55. package/skills/pc-plan-quick/SKILL.md +46 -46
  56. package/skills/pc-plan-story/SKILL.md +93 -93
  57. package/skills/pc-repo-help/SKILL.md +87 -87
  58. package/skills/pc-repo-initialize/SKILL.md +119 -119
  59. package/skills/pc-repo-onboard/SKILL.md +32 -32
  60. package/skills/pc-repo-verify/SKILL.md +150 -150
  61. package/skills/pc-userstory-az/SKILL.md +71 -71
  62. package/skills/pc-userstory-browser/SKILL.md +64 -64
  63. package/skills/pc-userstory-gh/SKILL.md +63 -63
  64. package/skills/pc-userstory-jira/SKILL.md +74 -74
@@ -1,121 +1,121 @@
1
- import chalk from 'chalk'
2
- import { execa } from 'execa'
3
- import fse from 'fs-extra'
4
- import path from 'node:path'
5
- import { fileURLToPath } from 'url'
6
- import { readHarnessConfig } from './shared.js'
7
- import { copyContentStep } from '../steps/copy/index.js'
8
- import { writeModelsToConfigs } from '../steps/models/write.js'
9
- import { patchGuardrails } from '../steps/optimization/patch-guardrails.js'
10
- import { reconcileTools } from '../steps/optimization/detect.js'
11
- import { writeHarnessConfig } from '../steps/metadata/index.js'
12
- import { info } from '../utils/exec.js'
13
- import { exit } from '../utils/process.js'
14
-
15
- const SIMPLE_ENGLISH_ENTRY = {
16
- source: 'AminBlg/SimpleEnglish',
17
- sourceType: 'github',
18
- skillPath: 'skills/simple-english/SKILL.md',
19
- }
20
-
21
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
22
- const platformsPreset = await fse.readJson(path.resolve(__dirname, '../presets/platforms.json'))
23
- const VALID_PLATFORMS = new Set(platformsPreset.map(p => p.value))
24
-
25
- function resolvePlatform(value, fallback = 'github') {
26
- return VALID_PLATFORMS.has(value) ? value : fallback
27
- }
28
-
29
- export async function runUpdate() {
30
- const saved = await readHarnessConfig()
31
- if (!saved?.platform) {
32
- console.log(chalk.red('No agent-harness config found. Run the wizard first.'))
33
- exit(1)
34
- return
35
- }
36
-
37
- const backlogPlatform = resolvePlatform(saved.platform.backlog)
38
- const repoPlatform = resolvePlatform(saved.platform.repo)
39
-
40
- const ctx = {
41
- hasDesign: !!saved.preexisting?.design,
42
- hasArchitecture: !!saved.preexisting?.architecture,
43
- hasOpenspec: !!saved.preexisting?.openspec,
44
- sourceMode: saved.source?.mode ?? 'current',
45
- sourceRoots: Array.isArray(saved.source?.roots) ? saved.source.roots : [],
46
- maxConcurrentAgents: saved.agents?.maxConcurrent ?? 3,
47
- installScope: 'local',
48
- skipSkills: false,
49
- updateMode: true,
50
- }
51
-
52
- console.log()
53
- console.log(chalk.bold('Updating project from saved config'))
54
- console.log(chalk.dim(` backlog: ${backlogPlatform} repo: ${repoPlatform} agents: ${ctx.maxConcurrentAgents}`))
55
- console.log()
56
-
57
- await copyContentStep({ backlogPlatform, repoPlatform }, ctx)
58
-
59
- if (saved.models) {
60
- await writeModelsToConfigs({ buildModel: saved.models.build, keepExisting: true })
61
- }
62
-
63
- // Reconcile against what is actually installed before patching guardrails.
64
- // The saved flags drift: a project can be running the codegraph and
65
- // agentmemory MCP servers with harness.json still recording them as false,
66
- // and patchGuardrails would then empty those marker sections and strip the
67
- // guidance for tools the project genuinely uses.
68
- const tools = reconcileTools(saved.tools ?? {})
69
- const gained = Object.keys(tools).filter(k => tools[k] && !saved.tools?.[k])
70
- if (gained.length > 0) {
71
- info(`Detected installed tools missing from harness.json: ${gained.join(', ')}`)
72
- }
73
-
74
- await migrateCavemanSkill(tools)
75
- await patchGuardrails({
76
- rtk: !!tools.rtk,
77
- simpleEnglish: !!(tools.simpleEnglish || tools.caveman),
78
- codegraph: !!tools.codegraph,
79
- memory: !!tools.memory,
80
- humanizer: !!tools.humanizer,
81
- })
82
-
83
- await writeHarnessConfig({
84
- ...ctx,
85
- backlogPlatform,
86
- repoPlatform,
87
- maxConcurrentAgents: ctx.maxConcurrentAgents,
88
- planModel: saved.models?.plan ?? null,
89
- buildModel: saved.models?.build ?? null,
90
- fastModel: saved.models?.fast ?? null,
91
- optionalTools: tools,
92
- })
93
-
94
- console.log()
95
- console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
96
- console.log(chalk.bold.green(' Update complete!'))
97
- console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
98
- console.log()
99
- }
100
-
101
- async function migrateCavemanSkill(tools) {
102
- if (!tools.caveman || tools.simpleEnglish) return
103
-
104
- const lockPath = path.join(process.cwd(), 'skills-lock.json')
105
- const lock = await fse.readJson(lockPath).catch(() => ({ version: 1, skills: {} }))
106
- lock.skills ??= {}
107
- delete lock.skills.caveman
108
- lock.skills['simple-english'] = SIMPLE_ENGLISH_ENTRY
109
- await fse.writeJson(lockPath, lock, { spaces: 2 })
110
- await fse.remove(path.join(process.cwd(), '.agents', 'skills', 'caveman'))
111
-
112
- const result = await execa('npx', ['skills', 'experimental_install', '--yes'], {
113
- cwd: process.cwd(),
114
- reject: false,
115
- stdio: 'pipe',
116
- timeout: 600000,
117
- })
118
- if (result.exitCode !== 0) {
119
- console.log(chalk.yellow('Simple English was added to skills-lock.json but could not be installed. Run npx skills experimental_install --yes.'))
120
- }
121
- }
1
+ import chalk from 'chalk'
2
+ import { execa } from 'execa'
3
+ import fse from 'fs-extra'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'url'
6
+ import { readHarnessConfig } from './shared.js'
7
+ import { copyContentStep } from '../steps/copy/index.js'
8
+ import { writeModelsToConfigs } from '../steps/models/write.js'
9
+ import { patchGuardrails } from '../steps/optimization/patch-guardrails.js'
10
+ import { reconcileTools } from '../steps/optimization/detect.js'
11
+ import { writeHarnessConfig } from '../steps/metadata/index.js'
12
+ import { info } from '../utils/exec.js'
13
+ import { exit } from '../utils/process.js'
14
+
15
+ const SIMPLE_ENGLISH_ENTRY = {
16
+ source: 'AminBlg/SimpleEnglish',
17
+ sourceType: 'github',
18
+ skillPath: 'skills/simple-english/SKILL.md',
19
+ }
20
+
21
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
22
+ const platformsPreset = await fse.readJson(path.resolve(__dirname, '../presets/platforms.json'))
23
+ const VALID_PLATFORMS = new Set(platformsPreset.map(p => p.value))
24
+
25
+ function resolvePlatform(value, fallback = 'github') {
26
+ return VALID_PLATFORMS.has(value) ? value : fallback
27
+ }
28
+
29
+ export async function runUpdate() {
30
+ const saved = await readHarnessConfig()
31
+ if (!saved?.platform) {
32
+ console.log(chalk.red('No agent-harness config found. Run the wizard first.'))
33
+ exit(1)
34
+ return
35
+ }
36
+
37
+ const backlogPlatform = resolvePlatform(saved.platform.backlog)
38
+ const repoPlatform = resolvePlatform(saved.platform.repo)
39
+
40
+ const ctx = {
41
+ hasDesign: !!saved.preexisting?.design,
42
+ hasArchitecture: !!saved.preexisting?.architecture,
43
+ hasOpenspec: !!saved.preexisting?.openspec,
44
+ sourceMode: saved.source?.mode ?? 'current',
45
+ sourceRoots: Array.isArray(saved.source?.roots) ? saved.source.roots : [],
46
+ maxConcurrentAgents: saved.agents?.maxConcurrent ?? 3,
47
+ installScope: 'local',
48
+ skipSkills: false,
49
+ updateMode: true,
50
+ }
51
+
52
+ console.log()
53
+ console.log(chalk.bold('Updating project from saved config'))
54
+ console.log(chalk.dim(` backlog: ${backlogPlatform} repo: ${repoPlatform} agents: ${ctx.maxConcurrentAgents}`))
55
+ console.log()
56
+
57
+ await copyContentStep({ backlogPlatform, repoPlatform }, ctx)
58
+
59
+ if (saved.models) {
60
+ await writeModelsToConfigs({ buildModel: saved.models.build, keepExisting: true })
61
+ }
62
+
63
+ // Reconcile against what is actually installed before patching guardrails.
64
+ // The saved flags drift: a project can be running the codegraph and
65
+ // agentmemory MCP servers with harness.json still recording them as false,
66
+ // and patchGuardrails would then empty those marker sections and strip the
67
+ // guidance for tools the project genuinely uses.
68
+ const tools = reconcileTools(saved.tools ?? {})
69
+ const gained = Object.keys(tools).filter(k => tools[k] && !saved.tools?.[k])
70
+ if (gained.length > 0) {
71
+ info(`Detected installed tools missing from harness.json: ${gained.join(', ')}`)
72
+ }
73
+
74
+ await migrateCavemanSkill(tools)
75
+ await patchGuardrails({
76
+ rtk: !!tools.rtk,
77
+ simpleEnglish: !!(tools.simpleEnglish || tools.caveman),
78
+ codegraph: !!tools.codegraph,
79
+ memory: !!tools.memory,
80
+ humanizer: !!tools.humanizer,
81
+ })
82
+
83
+ await writeHarnessConfig({
84
+ ...ctx,
85
+ backlogPlatform,
86
+ repoPlatform,
87
+ maxConcurrentAgents: ctx.maxConcurrentAgents,
88
+ planModel: saved.models?.plan ?? null,
89
+ buildModel: saved.models?.build ?? null,
90
+ fastModel: saved.models?.fast ?? null,
91
+ optionalTools: tools,
92
+ })
93
+
94
+ console.log()
95
+ console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
96
+ console.log(chalk.bold.green(' Update complete!'))
97
+ console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
98
+ console.log()
99
+ }
100
+
101
+ async function migrateCavemanSkill(tools) {
102
+ if (!tools.caveman || tools.simpleEnglish) return
103
+
104
+ const lockPath = path.join(process.cwd(), 'skills-lock.json')
105
+ const lock = await fse.readJson(lockPath).catch(() => ({ version: 1, skills: {} }))
106
+ lock.skills ??= {}
107
+ delete lock.skills.caveman
108
+ lock.skills['simple-english'] = SIMPLE_ENGLISH_ENTRY
109
+ await fse.writeJson(lockPath, lock, { spaces: 2 })
110
+ await fse.remove(path.join(process.cwd(), '.agents', 'skills', 'caveman'))
111
+
112
+ const result = await execa('npx', ['skills', 'experimental_install', '--yes'], {
113
+ cwd: process.cwd(),
114
+ reject: false,
115
+ stdio: 'pipe',
116
+ timeout: 600000,
117
+ })
118
+ if (result.exitCode !== 0) {
119
+ console.log(chalk.yellow('Simple English was added to skills-lock.json but could not be installed. Run npx skills experimental_install --yes.'))
120
+ }
121
+ }
@@ -1,97 +1,97 @@
1
- 2. **Find the oldest change with a completed PR**
2
-
3
- List unarchived changes (top-level only, excludes `archive/`):
4
-
5
- ```bash
6
- find "$REPO_ROOT/openspec/changes" -mindepth 1 -maxdepth 1 -type d -not -name 'archive' | sort
7
- ```
8
-
9
- If empty, report a blocker and stop.
10
-
11
- List completed PRs:
12
-
13
- ```bash
14
- az repos pr list --repository {repo} --status completed --query "sort_by(@, &closedDate)[].{name:title,sourceRefName:sourceRefName,closedDate:closedDate,pullRequestId:pullRequestId}"
15
- ```
16
-
17
- Match each change to a completed PR using its ID and slug as search hints:
18
- - No match → skip (record as blocked: `no merged PR found`).
19
- - One match → eligible.
20
- - Multiple matches → ask the user which PR belongs to that change.
21
-
22
- If nothing is eligible, report a blocker and stop. Otherwise select the eligible change with the **oldest** PR `closedDate` as the candidate.
23
-
24
- 3. **Confirm the candidate**
25
-
26
- Show the candidate (ID, title, PR ID, merged date) and any blocked changes, then ask:
27
-
28
- ```text
29
- Oldest unarchived merged change found:
30
- ID: {change-id}
31
- Title: {title from resolved PR}
32
- PR ID: {pullRequestId}
33
- Merged: {closedDate}
34
-
35
- Proceed with archiving? [yes/no]
36
- ```
37
-
38
- Stop if the user does not confirm.
39
-
40
- 4. **Archive the change**
41
-
42
- ```bash
43
- git checkout -b archive/{change-id}
44
- ```
45
-
46
- Load `@openspec-archive-change` skill and follow it to archive the change.
47
-
48
- 5. **Update docs**
49
-
50
- Compare the archived change's specs against `ARCHITECTURE.md` and `DESIGN.md`. If updates are needed, show them and get user approval before applying.
51
-
52
- 6. **Create the archive PR**
53
-
54
- ```bash
55
- # Stage specific paths only: a shared tree may hold another agent's or a
56
- # person's edits, and -A commits them under this message.
57
- git add openspec/changes/ # plus ARCHITECTURE.md / DESIGN.md if step 5 updated them
58
- git commit -m "archive: {title} ({change-id})"
59
- git push origin archive/{change-id}
60
-
61
- az repos pr create \
62
- --repository {repo} \
63
- --source-branch refs/heads/archive/{change-id} \
64
- --target-branch "refs/heads/$DEFAULT_BRANCH" \
65
- --title "archive: {title} ({change-id})" \
66
- --description "Archive SDD artifacts for {change-id} after merge of {sourceRefName}." \
67
- --auto-complete
68
- ```
69
-
70
- If work was stashed in step 1, restore it after the PR is created unless the user opts out.
71
-
72
- 7. **Report**
73
-
74
- Display:
75
-
76
- ```text
77
- Archive complete
78
-
79
- Change ID: {change-id}
80
- Title: {title}
81
- Original PR: {original-pr-link}
82
- Archive PR: {archive-pr-link}
83
-
84
- Documentation updates:
85
- - ARCHITECTURE.md: {count} changes applied
86
- - DESIGN.md: {count} changes applied
87
- ```
88
-
89
- ## Rules
90
-
91
- - All OpenSpec paths resolve from `git rev-parse --show-toplevel`. Never use `/openspec/...`.
92
- - Only process top-level directories in `$REPO_ROOT/openspec/changes/`; exclude `archive/`.
93
- - Use change ID and slug only as search hints; do not assume the source branch name.
94
- - The oldest eligible merged change is the only candidate: never ask the user which change to archive (but do ask which PR if multiple match one change).
95
- - Never proceed if the selected PR is not completed.
96
- - Never use browser tools or direct web requests for Azure DevOps. Use `az` CLI only.
97
- - Never invent or guess PR, branch, or merge metadata.
1
+ 2. **Find the oldest change with a completed PR**
2
+
3
+ List unarchived changes (top-level only, excludes `archive/`):
4
+
5
+ ```bash
6
+ find "$REPO_ROOT/openspec/changes" -mindepth 1 -maxdepth 1 -type d -not -name 'archive' | sort
7
+ ```
8
+
9
+ If empty, report a blocker and stop.
10
+
11
+ List completed PRs:
12
+
13
+ ```bash
14
+ az repos pr list --repository {repo} --status completed --query "sort_by(@, &closedDate)[].{name:title,sourceRefName:sourceRefName,closedDate:closedDate,pullRequestId:pullRequestId}"
15
+ ```
16
+
17
+ Match each change to a completed PR using its ID and slug as search hints:
18
+ - No match → skip (record as blocked: `no merged PR found`).
19
+ - One match → eligible.
20
+ - Multiple matches → ask the user which PR belongs to that change.
21
+
22
+ If nothing is eligible, report a blocker and stop. Otherwise select the eligible change with the **oldest** PR `closedDate` as the candidate.
23
+
24
+ 3. **Confirm the candidate**
25
+
26
+ Show the candidate (ID, title, PR ID, merged date) and any blocked changes, then ask:
27
+
28
+ ```text
29
+ Oldest unarchived merged change found:
30
+ ID: {change-id}
31
+ Title: {title from resolved PR}
32
+ PR ID: {pullRequestId}
33
+ Merged: {closedDate}
34
+
35
+ Proceed with archiving? [yes/no]
36
+ ```
37
+
38
+ Stop if the user does not confirm.
39
+
40
+ 4. **Archive the change**
41
+
42
+ ```bash
43
+ git checkout -b archive/{change-id}
44
+ ```
45
+
46
+ Load `@openspec-archive-change` skill and follow it to archive the change.
47
+
48
+ 5. **Update docs**
49
+
50
+ Compare the archived change's specs against `ARCHITECTURE.md` and `DESIGN.md`. If updates are needed, show them and get user approval before applying.
51
+
52
+ 6. **Create the archive PR**
53
+
54
+ ```bash
55
+ # Stage specific paths only: a shared tree may hold another agent's or a
56
+ # person's edits, and -A commits them under this message.
57
+ git add openspec/changes/ # plus ARCHITECTURE.md / DESIGN.md if step 5 updated them
58
+ git commit -m "archive: {title} ({change-id})"
59
+ git push origin archive/{change-id}
60
+
61
+ az repos pr create \
62
+ --repository {repo} \
63
+ --source-branch refs/heads/archive/{change-id} \
64
+ --target-branch "refs/heads/$DEFAULT_BRANCH" \
65
+ --title "archive: {title} ({change-id})" \
66
+ --description "Archive SDD artifacts for {change-id} after merge of {sourceRefName}." \
67
+ --auto-complete
68
+ ```
69
+
70
+ If work was stashed in step 1, restore it after the PR is created unless the user opts out.
71
+
72
+ 7. **Report**
73
+
74
+ Display:
75
+
76
+ ```text
77
+ Archive complete
78
+
79
+ Change ID: {change-id}
80
+ Title: {title}
81
+ Original PR: {original-pr-link}
82
+ Archive PR: {archive-pr-link}
83
+
84
+ Documentation updates:
85
+ - ARCHITECTURE.md: {count} changes applied
86
+ - DESIGN.md: {count} changes applied
87
+ ```
88
+
89
+ ## Rules
90
+
91
+ - All OpenSpec paths resolve from `git rev-parse --show-toplevel`. Never use `/openspec/...`.
92
+ - Only process top-level directories in `$REPO_ROOT/openspec/changes/`; exclude `archive/`.
93
+ - Use change ID and slug only as search hints; do not assume the source branch name.
94
+ - The oldest eligible merged change is the only candidate: never ask the user which change to archive (but do ask which PR if multiple match one change).
95
+ - Never proceed if the selected PR is not completed.
96
+ - Never use browser tools or direct web requests for Azure DevOps. Use `az` CLI only.
97
+ - Never invent or guess PR, branch, or merge metadata.
@@ -1,96 +1,96 @@
1
- 2. **Find the oldest change with a completed PR**
2
-
3
- List unarchived changes (top-level only, excludes `archive/`):
4
-
5
- ```bash
6
- find "$REPO_ROOT/openspec/changes" -mindepth 1 -maxdepth 1 -type d -not -name 'archive' | sort
7
- ```
8
-
9
- If empty, report a blocker and stop.
10
-
11
- List completed PRs:
12
-
13
- ```bash
14
- gh pr list --repo {owner}/{repo} --state merged --json title,headRefName,mergedAt,number --jq 'sort_by(.mergedAt) | .[] | {name: .title, sourceRefName: .headRefName, mergedAt: .mergedAt, pullRequestId: .number}'
15
- ```
16
-
17
- Match each change to a completed PR using its ID and slug as search hints:
18
- - No match → skip (record as blocked: `no merged PR found`).
19
- - One match → eligible.
20
- - Multiple matches → ask the user which PR belongs to that change.
21
-
22
- If nothing is eligible, report a blocker and stop. Otherwise select the eligible change with the **oldest** PR `mergedAt` as the candidate.
23
-
24
- 3. **Confirm the candidate**
25
-
26
- Show the candidate (ID, title, PR ID, merged date) and any blocked changes, then ask:
27
-
28
- ```text
29
- Oldest unarchived merged change found:
30
- ID: {change-id}
31
- Title: {title from resolved PR}
32
- PR ID: {pullRequestId}
33
- Merged: {mergedAt}
34
-
35
- Proceed with archiving? [yes/no]
36
- ```
37
-
38
- Stop if the user does not confirm.
39
-
40
- 4. **Archive the change**
41
-
42
- ```bash
43
- git checkout -b archive/{change-id}
44
- ```
45
-
46
- Load `@openspec-archive-change` skill and follow it to archive the change.
47
-
48
- 5. **Update docs**
49
-
50
- Compare the archived change's specs against `ARCHITECTURE.md` and `DESIGN.md`. If updates are needed, show them and get user approval before applying.
51
-
52
- 6. **Create the archive PR**
53
-
54
- ```bash
55
- # Stage specific paths only: a shared tree may hold another agent's or a
56
- # person's edits, and -A commits them under this message.
57
- git add openspec/changes/ # plus ARCHITECTURE.md / DESIGN.md if step 5 updated them
58
- git commit -m "archive: {title} ({change-id})"
59
- git push origin archive/{change-id}
60
-
61
- gh pr create \
62
- --repo {owner}/{repo} \
63
- --base "$DEFAULT_BRANCH" \
64
- --head archive/{change-id} \
65
- --title "archive: {title} ({change-id})" \
66
- --body "Archive SDD artifacts for {change-id} after merge."
67
- ```
68
-
69
- If work was stashed in step 1, restore it after the PR is created unless the user opts out.
70
-
71
- 7. **Report**
72
-
73
- Display:
74
-
75
- ```text
76
- Archive complete
77
-
78
- Change ID: {change-id}
79
- Title: {title}
80
- Original PR: {original-pr-link}
81
- Archive PR: {archive-pr-link}
82
-
83
- Documentation updates:
84
- - ARCHITECTURE.md: {count} changes applied
85
- - DESIGN.md: {count} changes applied
86
- ```
87
-
88
- ## Rules
89
-
90
- - All OpenSpec paths resolve from `git rev-parse --show-toplevel`. Never use `/openspec/...`.
91
- - Only process top-level directories in `$REPO_ROOT/openspec/changes/`; exclude `archive/`.
92
- - Use change ID and slug only as search hints; do not assume the source branch name.
93
- - The oldest eligible merged change is the only candidate: never ask the user which change to archive (but do ask which PR if multiple match one change).
94
- - Never proceed if the selected PR is not completed.
95
- - Never use browser tools or direct web requests for GitHub. Use `gh` CLI only.
96
- - Never invent or guess PR, branch, or merge metadata.
1
+ 2. **Find the oldest change with a completed PR**
2
+
3
+ List unarchived changes (top-level only, excludes `archive/`):
4
+
5
+ ```bash
6
+ find "$REPO_ROOT/openspec/changes" -mindepth 1 -maxdepth 1 -type d -not -name 'archive' | sort
7
+ ```
8
+
9
+ If empty, report a blocker and stop.
10
+
11
+ List completed PRs:
12
+
13
+ ```bash
14
+ gh pr list --repo {owner}/{repo} --state merged --json title,headRefName,mergedAt,number --jq 'sort_by(.mergedAt) | .[] | {name: .title, sourceRefName: .headRefName, mergedAt: .mergedAt, pullRequestId: .number}'
15
+ ```
16
+
17
+ Match each change to a completed PR using its ID and slug as search hints:
18
+ - No match → skip (record as blocked: `no merged PR found`).
19
+ - One match → eligible.
20
+ - Multiple matches → ask the user which PR belongs to that change.
21
+
22
+ If nothing is eligible, report a blocker and stop. Otherwise select the eligible change with the **oldest** PR `mergedAt` as the candidate.
23
+
24
+ 3. **Confirm the candidate**
25
+
26
+ Show the candidate (ID, title, PR ID, merged date) and any blocked changes, then ask:
27
+
28
+ ```text
29
+ Oldest unarchived merged change found:
30
+ ID: {change-id}
31
+ Title: {title from resolved PR}
32
+ PR ID: {pullRequestId}
33
+ Merged: {mergedAt}
34
+
35
+ Proceed with archiving? [yes/no]
36
+ ```
37
+
38
+ Stop if the user does not confirm.
39
+
40
+ 4. **Archive the change**
41
+
42
+ ```bash
43
+ git checkout -b archive/{change-id}
44
+ ```
45
+
46
+ Load `@openspec-archive-change` skill and follow it to archive the change.
47
+
48
+ 5. **Update docs**
49
+
50
+ Compare the archived change's specs against `ARCHITECTURE.md` and `DESIGN.md`. If updates are needed, show them and get user approval before applying.
51
+
52
+ 6. **Create the archive PR**
53
+
54
+ ```bash
55
+ # Stage specific paths only: a shared tree may hold another agent's or a
56
+ # person's edits, and -A commits them under this message.
57
+ git add openspec/changes/ # plus ARCHITECTURE.md / DESIGN.md if step 5 updated them
58
+ git commit -m "archive: {title} ({change-id})"
59
+ git push origin archive/{change-id}
60
+
61
+ gh pr create \
62
+ --repo {owner}/{repo} \
63
+ --base "$DEFAULT_BRANCH" \
64
+ --head archive/{change-id} \
65
+ --title "archive: {title} ({change-id})" \
66
+ --body "Archive SDD artifacts for {change-id} after merge."
67
+ ```
68
+
69
+ If work was stashed in step 1, restore it after the PR is created unless the user opts out.
70
+
71
+ 7. **Report**
72
+
73
+ Display:
74
+
75
+ ```text
76
+ Archive complete
77
+
78
+ Change ID: {change-id}
79
+ Title: {title}
80
+ Original PR: {original-pr-link}
81
+ Archive PR: {archive-pr-link}
82
+
83
+ Documentation updates:
84
+ - ARCHITECTURE.md: {count} changes applied
85
+ - DESIGN.md: {count} changes applied
86
+ ```
87
+
88
+ ## Rules
89
+
90
+ - All OpenSpec paths resolve from `git rev-parse --show-toplevel`. Never use `/openspec/...`.
91
+ - Only process top-level directories in `$REPO_ROOT/openspec/changes/`; exclude `archive/`.
92
+ - Use change ID and slug only as search hints; do not assume the source branch name.
93
+ - The oldest eligible merged change is the only candidate: never ask the user which change to archive (but do ask which PR if multiple match one change).
94
+ - Never proceed if the selected PR is not completed.
95
+ - Never use browser tools or direct web requests for GitHub. Use `gh` CLI only.
96
+ - Never invent or guess PR, branch, or merge metadata.