@izkac/forgekit 0.1.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.
- package/bin/forge.mjs +100 -0
- package/bin/forgekit.mjs +84 -0
- package/bin/review.mjs +82 -0
- package/package.json +46 -0
- package/scripts/prepack.mjs +78 -0
- package/scripts/run-tests.mjs +43 -0
- package/src/adr.mjs +236 -0
- package/src/adr.test.mjs +170 -0
- package/src/change.mjs +234 -0
- package/src/change.test.mjs +83 -0
- package/src/cleanup-sessions.mjs +70 -0
- package/src/config.mjs +103 -0
- package/src/defer.mjs +75 -0
- package/src/doctor.mjs +341 -0
- package/src/doctor.test.mjs +114 -0
- package/src/init.mjs +575 -0
- package/src/install.mjs +777 -0
- package/src/install.test.mjs +104 -0
- package/src/integrity-check.mjs +58 -0
- package/src/integrity.mjs +317 -0
- package/src/integrity.test.mjs +296 -0
- package/src/lib/workspaces.mjs +55 -0
- package/src/lib.mjs +138 -0
- package/src/models.defaults.json +41 -0
- package/src/new-session.mjs +82 -0
- package/src/openspec-overlays/README.md +19 -0
- package/src/openspec-overlays/openspec-apply-change-footer.md +14 -0
- package/src/openspec-overlays/opsx-apply-completion-step.md +1 -0
- package/src/openspec-overlays/opsx-apply-implement-step.md +11 -0
- package/src/paths.mjs +92 -0
- package/src/plan-engine.mjs +260 -0
- package/src/plan-engine.test.mjs +245 -0
- package/src/preferences.defaults.json +78 -0
- package/src/preferences.mjs +438 -0
- package/src/preferences.test.mjs +174 -0
- package/src/record-evidence.mjs +204 -0
- package/src/record-evidence.test.mjs +260 -0
- package/src/resolve-model.mjs +312 -0
- package/src/resolve-model.test.mjs +194 -0
- package/src/review/carryforward.mjs +413 -0
- package/src/review/carryforward.test.mjs +587 -0
- package/src/review/cli.test.mjs +117 -0
- package/src/review/export.mjs +172 -0
- package/src/review/export.test.mjs +197 -0
- package/src/review/fixtures/valid-review.json +42 -0
- package/src/review/lib.mjs +894 -0
- package/src/review/lib.test.mjs +266 -0
- package/src/review/merge-tentative.mjs +292 -0
- package/src/review/merge-tentative.test.mjs +363 -0
- package/src/review/new-review.mjs +200 -0
- package/src/review/render.mjs +108 -0
- package/src/review/schema-consistency.test.mjs +83 -0
- package/src/review/schema.json +196 -0
- package/src/review/signals.mjs +144 -0
- package/src/review/signals.test.mjs +62 -0
- package/src/score-cli.mjs +68 -0
- package/src/score.mjs +489 -0
- package/src/score.test.mjs +253 -0
- package/src/session-reminder.mjs +168 -0
- package/src/session-status.mjs +70 -0
- package/src/set-models.mjs +186 -0
- package/src/set-phase.mjs +177 -0
- package/src/set-phase.test.mjs +317 -0
- package/src/set-prefs.mjs +294 -0
- package/src/spine.mjs +91 -0
- package/src/triage-prompt.mjs +175 -0
- package/src/triage-prompt.test.mjs +50 -0
- package/src/vendor-openspec-overlays.mjs +176 -0
- package/src/vendor-openspec-overlays.test.mjs +62 -0
- package/vendor/skills/archive-to-adr/SKILL.md +149 -0
- package/vendor/skills/forge/SKILL.md +136 -0
- package/vendor/skills/forge/phases/brainstorm.md +23 -0
- package/vendor/skills/forge/phases/finish.md +87 -0
- package/vendor/skills/forge/phases/implement.md +76 -0
- package/vendor/skills/forge/phases/plan-openspec.md +40 -0
- package/vendor/skills/forge/phases/plan-specs.md +97 -0
- package/vendor/skills/forge/phases/review.md +25 -0
- package/vendor/skills/forge/phases/verify.md +120 -0
- package/vendor/skills/forge/references/forge-layout.md +85 -0
- package/vendor/skills/forge/references/pace.md +115 -0
- package/vendor/skills/forge/references/plan-routing.md +51 -0
- package/vendor/skills/forge/references/runtime-integrity.md +157 -0
- package/vendor/skills/forge/references/substantial-work.md +37 -0
- package/vendor/skills/forge/references/tdd-core.md +29 -0
- package/vendor/skills/forge/references/test-evidence.md +30 -0
- package/vendor/skills/forge/references/test-strategy.md +68 -0
- package/vendor/skills/forge/skills/NOTICE.md +17 -0
- package/vendor/skills/forge/skills/brainstorming/SKILL.md +120 -0
- package/vendor/skills/forge/skills/requesting-code-review/SKILL.md +67 -0
- package/vendor/skills/forge/skills/requesting-code-review/code-reviewer.md +146 -0
- package/vendor/skills/forge/skills/subagent-driven-development/SKILL.md +87 -0
- package/vendor/skills/forge/skills/systematic-debugging/SKILL.md +234 -0
- package/vendor/skills/forge/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/vendor/skills/forge/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/vendor/skills/forge/skills/systematic-debugging/find-polluter.sh +63 -0
- package/vendor/skills/forge/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/vendor/skills/forge/skills/test-driven-development/SKILL.md +290 -0
- package/vendor/skills/forge/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/vendor/skills/forge/skills/verification-before-completion/SKILL.md +59 -0
- package/vendor/skills/forge/subagents/final-reviewer-prompt.md +53 -0
- package/vendor/skills/forge/subagents/implementer-prompt.md +38 -0
- package/vendor/skills/forge/subagents/task-reviewer-prompt.md +61 -0
- package/vendor/skills/git-resolve-adr-conflict/SKILL.md +132 -0
- package/vendor/skills/thorough-code-review/SKILL.md +290 -0
- package/vendor/skills/thorough-code-review/examples/accepted-risks-janus.md +32 -0
- package/vendor/skills/thorough-code-review/examples.md +133 -0
- package/vendor/skills/thorough-code-review/reference/accepted-risks.md +26 -0
- package/vendor/skills/thorough-code-review/reference/lenses.md +96 -0
- package/vendor/skills/thorough-code-review/reference/phase1-scout.md +62 -0
- package/vendor/skills/thorough-code-review/reference/phase1c-coverage.md +44 -0
- package/vendor/skills/thorough-code-review/reference/phase2-skeptic.md +105 -0
- package/vendor/skills/thorough-code-review/reference/report-schema.json +222 -0
- package/vendor/skills/thorough-code-review/reference/report-template.md +115 -0
- package/vendor/skills/thorough-code-review/reference/severity-rubric.md +49 -0
- package/vendor/skills/thorough-code-review/reference/signals-preflight.md +55 -0
- package/vendor/templates/adr/README.md +7 -0
- package/vendor/templates/adr/decisions.md +141 -0
- package/vendor/templates/adr/hooks/check-pending-adrs.mjs +74 -0
- package/vendor/templates/adr/hooks/check-pending-adrs.sh +3 -0
- package/vendor/templates/adr/hooks/openspec-archive-agent-message.mjs +52 -0
- package/vendor/templates/adr/hooks/openspec-archive-agent-message.sh +3 -0
- package/vendor/templates/project/claude/commands/forge-apply.md +75 -0
- package/vendor/templates/project/claude/commands/forge-brainstorm.md +7 -0
- package/vendor/templates/project/claude/commands/forge-build.md +17 -0
- package/vendor/templates/project/claude/commands/forge-plan.md +12 -0
- package/vendor/templates/project/claude/commands/forge-skip.md +14 -0
- package/vendor/templates/project/claude/commands/forge-status.md +16 -0
- package/vendor/templates/project/claude/commands/forge.md +16 -0
- package/vendor/templates/project/claude/hooks/forge-prompt-hook.mjs +73 -0
- package/vendor/templates/project/claude/hooks/forge-session-start.mjs +19 -0
- package/vendor/templates/project/claude/hooks/forge-triage-hook.mjs +77 -0
- package/vendor/templates/project/claude/rules/forge.md +16 -0
- package/vendor/templates/project/codex/rules/forge.md +10 -0
- package/vendor/templates/project/cursor/commands/forge-apply.md +75 -0
- package/vendor/templates/project/cursor/commands/forge-brainstorm.md +10 -0
- package/vendor/templates/project/cursor/commands/forge-build.md +17 -0
- package/vendor/templates/project/cursor/commands/forge-plan.md +15 -0
- package/vendor/templates/project/cursor/commands/forge-skip.md +14 -0
- package/vendor/templates/project/cursor/commands/forge-status.md +16 -0
- package/vendor/templates/project/cursor/commands/forge.md +16 -0
- package/vendor/templates/project/cursor/hooks/forge-session-start.mjs +30 -0
- package/vendor/templates/project/cursor/hooks/forge-session-start.sh +3 -0
- package/vendor/templates/project/cursor/rules/forge.mdc +21 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Architecture decision records (ADRs)
|
|
2
|
+
|
|
3
|
+
> How this project captures **architectural** decisions. ADRs live next to OpenSpec
|
|
4
|
+
> archives. Scaffolded by Forgekit (`forge init --adr` / `forgekit install` with ADRs on).
|
|
5
|
+
|
|
6
|
+
ADR directory for this project: **`{{ADR_DIR}}`** (configured in `.forge/config.json`).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. Why ADRs *and* OpenSpec archives?
|
|
11
|
+
|
|
12
|
+
They overlap in “why we did something,” but they serve different lookups:
|
|
13
|
+
|
|
14
|
+
| OpenSpec archive (verbose) | ADR (short) |
|
|
15
|
+
| -------------------------- | ----------- |
|
|
16
|
+
| `proposal.md`, `design.md`, `tasks.md`, specs | One file, one sharpened decision |
|
|
17
|
+
| “Replay the implementation story” | “Why is X shaped this way?” |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. When to write an ADR
|
|
22
|
+
|
|
23
|
+
| Write an ADR when the change … | Skip the ADR when … |
|
|
24
|
+
| ------------------------------ | ------------------- |
|
|
25
|
+
| Establishes or moves a boundary | Bug fix / copy / docs-only |
|
|
26
|
+
| Chooses among real alternatives | Refactor that changes no decision |
|
|
27
|
+
| Adds a constraint future code must follow | Chore rename without semantics |
|
|
28
|
+
| Picks a costly-to-swap vendor or protocol | Narrow feature inside a prior ADR |
|
|
29
|
+
| Codifies a convention across the repo | Noise would drown readers |
|
|
30
|
+
|
|
31
|
+
If you’re listing code edits, that belongs in `design.md`—not an ADR.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 3. Format (`{{ADR_DIR}}/NNNN-short-topic.md`)
|
|
36
|
+
|
|
37
|
+
```markdown
|
|
38
|
+
# NNNN. <Short, decision-shaped title>
|
|
39
|
+
|
|
40
|
+
- **Status**: Proposed | Accepted | Superseded by ADR-NNNN (…)
|
|
41
|
+
- **Date**: YYYY-MM-DD
|
|
42
|
+
- **Area**: <product or layer>
|
|
43
|
+
- **Related**: openspec/changes/archive/<change>/, …
|
|
44
|
+
|
|
45
|
+
## Context
|
|
46
|
+
|
|
47
|
+
One paragraph: pressure or constraint forcing a decision.
|
|
48
|
+
|
|
49
|
+
## Decision
|
|
50
|
+
|
|
51
|
+
One imperative paragraph stating what this project commits to.
|
|
52
|
+
|
|
53
|
+
## Alternatives considered
|
|
54
|
+
|
|
55
|
+
- **Option A** — rejected because …
|
|
56
|
+
- **Chosen** — …
|
|
57
|
+
|
|
58
|
+
## Consequences
|
|
59
|
+
|
|
60
|
+
### Positive
|
|
61
|
+
### Negative
|
|
62
|
+
### Neutral
|
|
63
|
+
|
|
64
|
+
## References
|
|
65
|
+
|
|
66
|
+
- Archive: openspec/changes/archive/<name>/
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Keep ADRs terse (roughly tens to low hundreds of lines each).
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 4. Numbering
|
|
74
|
+
|
|
75
|
+
- Sequential `NNNN`; never recycle numbers—supersede with a **new** ADR.
|
|
76
|
+
- Prefer decision-shaped filenames: `0004-outbox-only-on-critical-path.md` not `0004-add-worker.md`.
|
|
77
|
+
|
|
78
|
+
All ADRs sit under **`{{ADR_DIR}}/`** even when scoped to one area—the decision
|
|
79
|
+
usually crosses layers.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 5. Status index (`{{ADR_DIR}}/README.md`)
|
|
84
|
+
|
|
85
|
+
Maintain the table of `#`, Title, Status, Date. Leave placeholder rows until real ADRs land.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 6. Cross-link OpenSpec ↔ ADRs
|
|
90
|
+
|
|
91
|
+
ADR **References** should point at the dated archive folder.
|
|
92
|
+
|
|
93
|
+
Archived `proposal.md` should gain:
|
|
94
|
+
|
|
95
|
+
```markdown
|
|
96
|
+
## Decision record
|
|
97
|
+
|
|
98
|
+
This change is recorded as ADR-NNNN ({{ADR_DIR}}/NNNN-short-topic.md).
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or, for non-architectural archives, the one-line stamp:
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
No ADR — non-architectural change
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Capability specs may cite ADRs as normative “why.”
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 7. ADRs in agent retrieval
|
|
112
|
+
|
|
113
|
+
Short, focused ADRs embed well. **Do not** add `{{ADR_DIR}}/` to
|
|
114
|
+
`.cursorindexignore` (or equivalent).
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 7a. Hooks: archive → ADR
|
|
119
|
+
|
|
120
|
+
When ADR wiring is enabled, archive hooks remind agents to run **archive-to-adr**
|
|
121
|
+
after `openspec archive` or a move into `openspec/changes/archive/YYYY-MM-DD-…`.
|
|
122
|
+
|
|
123
|
+
Session-start backstops list archives whose `proposal.md` lacks `ADR-[0-9]` **and**
|
|
124
|
+
lacks the `No ADR — non-architectural change` stamp.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 8. Maintenance habit
|
|
129
|
+
|
|
130
|
+
- On each meaningful archive: decide ADR vs stamp.
|
|
131
|
+
- Periodically skim the index for stale status.
|
|
132
|
+
- Supersede with new ADRs—don’t delete old ones.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 9. TL;DR
|
|
137
|
+
|
|
138
|
+
- ADRs **distill** decisions; OpenSpec archives keep the **full** story.
|
|
139
|
+
- Use this file + `{{ADR_DIR}}/README.md` + `{{ADR_DIR}}/NNNN-*.md`.
|
|
140
|
+
- Cross-link archives and ADRs both directions.
|
|
141
|
+
- After archive: run **archive-to-adr** (or stamp “No ADR”).
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Pending-ADR backstop: list archived proposals missing ADR link and No-ADR stamp.
|
|
4
|
+
* Honors .forge/config.json adr.enabled === false.
|
|
5
|
+
*
|
|
6
|
+
* Usage: node check-pending-adrs.mjs [repoRoot]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { spawnSync } from 'node:child_process';
|
|
12
|
+
|
|
13
|
+
function gitRoot() {
|
|
14
|
+
const r = spawnSync('git', ['rev-parse', '--show-toplevel'], {
|
|
15
|
+
encoding: 'utf8',
|
|
16
|
+
shell: process.platform === 'win32',
|
|
17
|
+
});
|
|
18
|
+
if (r.status !== 0) return null;
|
|
19
|
+
return String(r.stdout || '').trim() || null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function loadConfig(cwd) {
|
|
23
|
+
const p = path.join(cwd, '.forge', 'config.json');
|
|
24
|
+
if (!fs.existsSync(p)) return {};
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
27
|
+
} catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function listProposals(cwd, archiveRoot) {
|
|
33
|
+
const dir = path.join(cwd, ...archiveRoot.split('/'));
|
|
34
|
+
if (!fs.existsSync(dir)) return [];
|
|
35
|
+
/** @type {string[]} */
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const name of fs.readdirSync(dir)) {
|
|
38
|
+
const proposal = path.join(dir, name, 'proposal.md');
|
|
39
|
+
if (fs.existsSync(proposal)) out.push(proposal);
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const cwd = process.argv[2] || gitRoot();
|
|
45
|
+
if (!cwd) process.exit(0);
|
|
46
|
+
|
|
47
|
+
const cfg = loadConfig(cwd);
|
|
48
|
+
if (cfg.adr && cfg.adr.enabled === false) process.exit(0);
|
|
49
|
+
|
|
50
|
+
const archives = [
|
|
51
|
+
'openspec/changes/archive',
|
|
52
|
+
`${(cfg.plan && typeof cfg.plan === 'object' && cfg.plan.dir) || 'specs'}/changes/archive`,
|
|
53
|
+
];
|
|
54
|
+
/** @type {string[]} */
|
|
55
|
+
const pending = [];
|
|
56
|
+
for (const root of archives) {
|
|
57
|
+
for (const proposal of listProposals(cwd, root)) {
|
|
58
|
+
const body = fs.readFileSync(proposal, 'utf8');
|
|
59
|
+
if (/ADR-[0-9]/.test(body)) continue;
|
|
60
|
+
if (body.includes('No ADR — non-architectural change')) continue;
|
|
61
|
+
pending.push(path.relative(cwd, proposal).replace(/\\/g, '/'));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (pending.length === 0) process.exit(0);
|
|
66
|
+
|
|
67
|
+
const shown = pending.slice(0, 10).join('\n');
|
|
68
|
+
process.stdout.write(
|
|
69
|
+
`Archived changes whose proposal.md does not reference an ADR yet:\n\n${shown}\n\n` +
|
|
70
|
+
`If any had architectural impact, run the archive-to-adr skill.\n` +
|
|
71
|
+
`Otherwise add a one-line stamp to each proposal.md:\n` +
|
|
72
|
+
` 'No ADR — non-architectural change'\n`,
|
|
73
|
+
);
|
|
74
|
+
process.exit(0);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Agent reminder after a successful change archive (stdout only).
|
|
4
|
+
* Usage: node openspec-archive-agent-message.mjs <change-slug>
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
|
|
10
|
+
const changeName = process.argv[2];
|
|
11
|
+
if (!changeName) {
|
|
12
|
+
process.stderr.write('Usage: openspec-archive-agent-message.mjs <change-slug>\n');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let adrDir = 'docs/adr';
|
|
17
|
+
let decisionsDoc = 'docs/decisions.md';
|
|
18
|
+
let archiveRoot = 'openspec/changes/archive';
|
|
19
|
+
let adrEnabled = true;
|
|
20
|
+
|
|
21
|
+
const cfgPath = path.join(process.cwd(), '.forge', 'config.json');
|
|
22
|
+
if (fs.existsSync(cfgPath)) {
|
|
23
|
+
try {
|
|
24
|
+
const c = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
25
|
+
if (c.plan?.engine === 'specs') {
|
|
26
|
+
archiveRoot = `${c.plan.dir || 'specs'}/changes/archive`;
|
|
27
|
+
}
|
|
28
|
+
if (c.adr?.enabled === false) adrEnabled = false;
|
|
29
|
+
if (c.adr?.dir) adrDir = c.adr.dir;
|
|
30
|
+
if (c.adr?.decisionsDoc) decisionsDoc = c.adr.decisionsDoc;
|
|
31
|
+
} catch {
|
|
32
|
+
// keep defaults
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!adrEnabled) {
|
|
37
|
+
process.stdout.write(
|
|
38
|
+
`Change '${changeName}' was archived. ADRs are disabled for this project\n` +
|
|
39
|
+
`(\`.forge/config.json\` → adr.enabled: false). No archive-to-adr follow-up.\n\n` +
|
|
40
|
+
`Suggested commit (display only — do NOT commit unless the user asks):\n\n` +
|
|
41
|
+
`openspec: archive ${changeName}\n`,
|
|
42
|
+
);
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
process.stdout.write(
|
|
47
|
+
`Change '${changeName}' was just archived.\n\n` +
|
|
48
|
+
`1. archive-to-adr: locate ${archiveRoot}/YYYY-MM-DD-${changeName}/, open proposal.md, and evaluate an ADR (${decisionsDoc}). If yes: ${adrDir}/NNNN-<topic>.md, update ${adrDir}/README.md, add a ## Decision record to proposal.md. If no: add one line to proposal.md: No ADR — non-architectural change.\n\n` +
|
|
49
|
+
`2. Suggested commit (display only — do NOT run git commit / push / gh pr create unless the user explicitly asks):\n\n` +
|
|
50
|
+
`openspec: archive ${changeName}\n`,
|
|
51
|
+
);
|
|
52
|
+
process.exit(0);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: /forge:apply
|
|
3
|
+
description: Forge — apply a tracked change with subagent TDD, verify, and review
|
|
4
|
+
category: Workflow
|
|
5
|
+
tags: [workflow, forge, openspec]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
**Forge-owned command.** Use this instead of bare `/opsx:apply` for disciplined implementation of a tracked change (OpenSpec or built-in specs engine — `.forge/config.json` → `plan.engine`).
|
|
9
|
+
|
|
10
|
+
Read the Forge skill (`~/.claude/skills/forge/SKILL.md`) and forgekit `docs/forge.md`.
|
|
11
|
+
|
|
12
|
+
**Input**: Optionally specify a change name (e.g., `/forge:apply add-auth`). If omitted, infer from context or active Forge session.
|
|
13
|
+
|
|
14
|
+
## 0. Forge session
|
|
15
|
+
|
|
16
|
+
1. Announce: "Using Forge apply."
|
|
17
|
+
2. Resume `.forge/active.json` or bootstrap: `forge new <slug>`
|
|
18
|
+
3. Set phase (use the project's engine as plan-type):
|
|
19
|
+
```bash
|
|
20
|
+
forge phase implement --plan-type openspec|specs --openspec "<change>"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 1–5. Load the change
|
|
24
|
+
|
|
25
|
+
**OpenSpec engine** — vendor CLI, follow `openspec-apply-change` (same as `/opsx:apply` steps 1–5):
|
|
26
|
+
|
|
27
|
+
1. **Select the change** — announce "Using change: \<name\>"
|
|
28
|
+
2. `openspec status --change "<name>" --json`
|
|
29
|
+
3. `openspec instructions apply --change "<name>" --json`
|
|
30
|
+
4. Read all `contextFiles` from CLI output
|
|
31
|
+
5. Show progress (N/M tasks, schema, remaining tasks)
|
|
32
|
+
|
|
33
|
+
Handle blocked / all_done states per vendor skill before implementing.
|
|
34
|
+
|
|
35
|
+
**Specs engine** — no CLI:
|
|
36
|
+
|
|
37
|
+
1. **Select the change** under `specs/changes/<name>/` — announce "Using change: \<name\>"
|
|
38
|
+
2. Read `proposal.md`, `design.md` (if present), `tasks.md`
|
|
39
|
+
3. Show progress (N/M checkboxes, remaining tasks)
|
|
40
|
+
|
|
41
|
+
## 6. Implement (Forge — REQUIRED)
|
|
42
|
+
|
|
43
|
+
Follow ~/.claude/skills/forge/phases/implement.md`:
|
|
44
|
+
|
|
45
|
+
- **Do not** implement all tasks inline in coordinator context
|
|
46
|
+
- Per pending task: brief → **implementer** subagent (TDD first) → **spec reviewer** → **quality reviewer** → mark `- [x]` in `tasks.md`
|
|
47
|
+
- Bundled skills: `skills/subagent-driven-development` + `skills/test-driven-development`
|
|
48
|
+
|
|
49
|
+
Pause on unclear tasks, design issues, errors, or user interrupt.
|
|
50
|
+
|
|
51
|
+
## 7. Verify (Forge — REQUIRED when all tasks done)
|
|
52
|
+
|
|
53
|
+
Follow ~/.claude/skills/forge/phases/verify.md`:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
forge phase verify
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Run affected workspace tests — **audit per-task `test-evidence.md`**; do not re-run the same commands if subagents already recorded passing runs (see verify phase).
|
|
60
|
+
|
|
61
|
+
## 8. Review (Forge — REQUIRED)
|
|
62
|
+
|
|
63
|
+
Follow ~/.claude/skills/forge/phases/review.md`:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
forge phase review
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Final reviewer; save to `.forge/sessions/<id>/reviews/final-review.md`.
|
|
70
|
+
|
|
71
|
+
## 9. Finish
|
|
72
|
+
|
|
73
|
+
When verify + review pass: suggest archive — `/opsx:archive` (OpenSpec) or the dated move into `specs/changes/archive/` (specs) — per [finish phase](~/.claude/skills/forge/phases/finish.md).
|
|
74
|
+
|
|
75
|
+
**Skip Forge for this task only:** `/forge:skip` (runs work without brainstorm/plan/verify chain).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Forge — brainstorm phase only
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Brainstorm phase only. Follow ~/.claude/skills/forge/phases/brainstorm.md`.
|
|
6
|
+
|
|
7
|
+
Invoke bundled **skills/brainstorming**. Save notes to `.forge/sessions/<id>/brainstorm/`. Terminal state: **OpenSpec propose** (`/opsx:propose`) — do not implement until artefacts are approved.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: /forge:build
|
|
3
|
+
description: Forge — implement phase (subagent-driven + TDD)
|
|
4
|
+
category: Workflow
|
|
5
|
+
tags: [workflow, forge, openspec]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Implement phase. Follow ~/.claude/skills/forge/phases/implement.md`.
|
|
9
|
+
|
|
10
|
+
**REQUIRED:** forge `skills/subagent-driven-development` + `skills/test-driven-development` per task.
|
|
11
|
+
|
|
12
|
+
- OpenSpec plan → **`/forge:apply`** (preferred) or `openspec-apply-change` / `/opsx:apply` wrapped in subagent loop
|
|
13
|
+
- Specs plan (`planType: specs`) → **`/forge:apply`** — tasks from `specs/changes/<name>/tasks.md`, no vendor CLI steps
|
|
14
|
+
- Throwaway plan → tasks from `.forge/.../plan.md`
|
|
15
|
+
- Direct → brief from `.forge/.../brainstorm/notes.md` + `decisions.md`
|
|
16
|
+
|
|
17
|
+
Then verify and review phases.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Forge — plan phase (tracked change propose)
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Plan phase. Follow ~/.claude/skills/forge/references/plan-routing.md`.
|
|
6
|
+
|
|
7
|
+
**Engine from `.forge/config.json` → `plan.engine`** — do not ask for a plan mode:
|
|
8
|
+
|
|
9
|
+
- `openspec` → run `/opsx:propose <prefix>-<slug>` or the `openspec-propose` skill per [plan-openspec.md](~/.claude/skills/forge/phases/plan-openspec.md)
|
|
10
|
+
- `specs` → author `specs/changes/<name>/{proposal,tasks}.md` per [plan-specs.md](~/.claude/skills/forge/phases/plan-specs.md)
|
|
11
|
+
|
|
12
|
+
Get user approval on the change artefacts before `/forge:build` or `/forge:apply`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: /forge:skip
|
|
3
|
+
description: Explicitly skip Forge for this task
|
|
4
|
+
category: Workflow
|
|
5
|
+
tags: [workflow, forge]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
User opted out of Forge for **this task only**.
|
|
9
|
+
|
|
10
|
+
1. If active session exists: `forge phase skipped`
|
|
11
|
+
2. Do **not** brainstorm or plan
|
|
12
|
+
3. Execute the request under normal project rules
|
|
13
|
+
|
|
14
|
+
Triage defaults to Forge; `/forge:skip` is the explicit override.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: /forge:status
|
|
3
|
+
description: Forge — show active session progress
|
|
4
|
+
category: Workflow
|
|
5
|
+
tags: [workflow, forge]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Show Forge session status:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
forge status
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Summarize `phase`, `planType`, `openspecChange`, and task progress for the user.
|
|
15
|
+
|
|
16
|
+
Reference: forgekit `docs/forge.md`
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: /forge
|
|
3
|
+
description: Forge — start or resume disciplined development (brainstorm → plan → build → review)
|
|
4
|
+
category: Workflow
|
|
5
|
+
tags: [workflow, forge, openspec]
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Run the **Forge** workflow for substantial work.
|
|
9
|
+
|
|
10
|
+
Read and follow the Forge skill (`~/.claude/skills/forge/SKILL.md`) and forgekit `docs/forge.md`.
|
|
11
|
+
|
|
12
|
+
1. Triage — substantial work? (skip only if user said `/forge:skip`)
|
|
13
|
+
2. Resume from `.forge/active.json` or `forge new <slug>`
|
|
14
|
+
3. Continue from current `phase` in `session.json`
|
|
15
|
+
|
|
16
|
+
Subcommands: `/forge:brainstorm`, `/forge:plan`, `/forge:apply`, `/forge:build`, `/forge:status`, `/forge:skip`
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* UserPromptSubmit: when the user invokes /forge or /forge:*, inject session context.
|
|
4
|
+
* Requires `forge` on PATH.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
const REPO_ROOT = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
12
|
+
const ACTIVE_FILE = path.join(REPO_ROOT, '.forge', 'active.json');
|
|
13
|
+
|
|
14
|
+
function readStdin() {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
let data = '';
|
|
17
|
+
if (process.stdin.isTTY) return resolve('');
|
|
18
|
+
process.stdin.setEncoding('utf8');
|
|
19
|
+
process.stdin.on('data', (chunk) => {
|
|
20
|
+
data += chunk;
|
|
21
|
+
});
|
|
22
|
+
process.stdin.on('end', () => resolve(data));
|
|
23
|
+
process.stdin.on('error', () => resolve(data));
|
|
24
|
+
setTimeout(() => resolve(data), 1500);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extractPrompt(raw) {
|
|
29
|
+
if (!raw) return '';
|
|
30
|
+
try {
|
|
31
|
+
const j = JSON.parse(raw);
|
|
32
|
+
return (
|
|
33
|
+
(typeof j?.prompt === 'string' && j.prompt) ||
|
|
34
|
+
(typeof j?.user_prompt === 'string' && j.user_prompt) ||
|
|
35
|
+
(typeof j?.message === 'string' && j.message) ||
|
|
36
|
+
''
|
|
37
|
+
);
|
|
38
|
+
} catch {
|
|
39
|
+
return raw;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isForgeInvocation(prompt) {
|
|
44
|
+
return /^\s*\/forge(?::|\s|$)/i.test(prompt);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function emit(message) {
|
|
48
|
+
process.stdout.write(`<system-reminder>\n${message}\n</system-reminder>\n`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const raw = await readStdin();
|
|
52
|
+
const prompt = extractPrompt(raw);
|
|
53
|
+
|
|
54
|
+
if (!isForgeInvocation(prompt)) {
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!fs.existsSync(ACTIVE_FILE)) {
|
|
59
|
+
emit(
|
|
60
|
+
'[forge] No active session. Bootstrap with `forge new <slug>` then follow the Forge skill.',
|
|
61
|
+
);
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const r = spawnSync('forge', ['reminder', '--format', 'plain', '--prompt', prompt], {
|
|
66
|
+
encoding: 'utf8',
|
|
67
|
+
cwd: REPO_ROOT,
|
|
68
|
+
shell: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (r.status === 0 && r.stdout.trim()) {
|
|
72
|
+
emit(r.stdout.trim());
|
|
73
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code SessionStart: inject active Forge session when present.
|
|
4
|
+
* Requires `forge` on PATH (npm link @izkac/forgekit or global install).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
const REPO_ROOT = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
10
|
+
|
|
11
|
+
const r = spawnSync('forge', ['reminder', '--format', 'claude-session-start'], {
|
|
12
|
+
encoding: 'utf8',
|
|
13
|
+
cwd: REPO_ROOT,
|
|
14
|
+
shell: true,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
if (r.status === 0 && r.stdout.trim()) {
|
|
18
|
+
process.stdout.write(r.stdout);
|
|
19
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* UserPromptSubmit: auto-triage substantial work into Forge (Claude Code).
|
|
4
|
+
* Requires `forge` on PATH.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
|
|
11
|
+
const REPO_ROOT = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
12
|
+
const ACTIVE_FILE = path.join(REPO_ROOT, '.forge', 'active.json');
|
|
13
|
+
|
|
14
|
+
function readStdin() {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
let data = '';
|
|
17
|
+
if (process.stdin.isTTY) return resolve('');
|
|
18
|
+
process.stdin.setEncoding('utf8');
|
|
19
|
+
process.stdin.on('data', (chunk) => {
|
|
20
|
+
data += chunk;
|
|
21
|
+
});
|
|
22
|
+
process.stdin.on('end', () => resolve(data));
|
|
23
|
+
process.stdin.on('error', () => resolve(data));
|
|
24
|
+
setTimeout(() => resolve(data), 1500);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extractPrompt(raw) {
|
|
29
|
+
if (!raw) return '';
|
|
30
|
+
try {
|
|
31
|
+
const j = JSON.parse(raw);
|
|
32
|
+
return (
|
|
33
|
+
(typeof j?.prompt === 'string' && j.prompt) ||
|
|
34
|
+
(typeof j?.user_prompt === 'string' && j.user_prompt) ||
|
|
35
|
+
(typeof j?.message === 'string' && j.message) ||
|
|
36
|
+
''
|
|
37
|
+
);
|
|
38
|
+
} catch {
|
|
39
|
+
return raw;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function emit(message) {
|
|
44
|
+
process.stdout.write(`<system-reminder>\n${message}\n</system-reminder>\n`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function runForge(args) {
|
|
48
|
+
return spawnSync('forge', args, {
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
cwd: REPO_ROOT,
|
|
51
|
+
shell: true,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const raw = await readStdin();
|
|
56
|
+
const prompt = extractPrompt(raw);
|
|
57
|
+
if (!prompt) process.exit(0);
|
|
58
|
+
|
|
59
|
+
const check = runForge(['triage', '--check', prompt]);
|
|
60
|
+
if (check.status !== 0) process.exit(0);
|
|
61
|
+
|
|
62
|
+
const hasSession = fs.existsSync(ACTIVE_FILE);
|
|
63
|
+
const args = ['triage', '--message'];
|
|
64
|
+
if (hasSession) args.push('--has-session');
|
|
65
|
+
args.push(prompt);
|
|
66
|
+
|
|
67
|
+
const msg = runForge(args);
|
|
68
|
+
if (msg.status === 0 && msg.stdout.trim()) {
|
|
69
|
+
if (hasSession) {
|
|
70
|
+
const rem = runForge(['reminder', '--format', 'plain']);
|
|
71
|
+
if (rem.status === 0 && rem.stdout.trim()) {
|
|
72
|
+
emit(`${msg.stdout.trim()}\n\nActive session:\n${rem.stdout.trim()}`);
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
emit(msg.stdout.trim());
|
|
77
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Forge (thin rule)
|
|
2
|
+
|
|
3
|
+
Full workflow: forgekit `docs/forge.md` · Skill: user-installed **Forge** (`~/.claude/skills/forge/SKILL.md` after `forge install`).
|
|
4
|
+
|
|
5
|
+
**Default:** triage before implementation. Substantial work → **Forge**
|
|
6
|
+
(brainstorm → OpenSpec → subagent TDD implement → review).
|
|
7
|
+
|
|
8
|
+
**Forge = OpenSpec:** after brainstorm, proceed directly to `/opsx:propose` — do not ask for a plan mode. Work too small for OpenSpec should skip Forge (`/forge:skip` or direct execution).
|
|
9
|
+
|
|
10
|
+
**Skip Forge** only when work is trivial OR user sends **`/forge:skip`**.
|
|
11
|
+
|
|
12
|
+
Scratch sessions: `.forge/sessions/` (14-day retention). Active pointer: `.forge/active.json`.
|
|
13
|
+
|
|
14
|
+
CLI: `forge new`, `forge status`, `forge prefs`, `forge models`, `forge phase`, `forge doctor`.
|
|
15
|
+
|
|
16
|
+
Do not edit vendor OpenSpec skills — Forge orchestrates them. Workflow skills are bundled under the Forge skill’s `skills/` folder.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Forge (thin rule)
|
|
2
|
+
|
|
3
|
+
Full workflow: forgekit `docs/forge.md` · Skill: user-installed **Forge** (`~/.codex/skills/forge/SKILL.md` after `forge install`).
|
|
4
|
+
|
|
5
|
+
**Default:** triage before implementation. Substantial work → **Forge**
|
|
6
|
+
(brainstorm → OpenSpec → subagent TDD implement → review).
|
|
7
|
+
|
|
8
|
+
**Forge = OpenSpec:** after brainstorm, proceed directly to OpenSpec propose. Skip with `/forge:skip` or when work is trivial.
|
|
9
|
+
|
|
10
|
+
Scratch: `.forge/` · CLI: `forge new|status|prefs|models|phase|doctor`
|