create-issflow 1.5.0 → 1.7.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/README.md +8 -2
- package/bin/cli.js +26 -5
- package/package.json +1 -1
- package/template/.claude/agents/debugger.md +1 -1
- package/template/.claude/agents/e2e-runner.md +1 -1
- package/template/.claude/agents/implementer.md +1 -1
- package/template/.claude/agents/planner.md +1 -1
- package/template/.claude/agents/researcher.md +1 -1
- package/template/.claude/agents/test-author.md +1 -1
- package/template/.claude/commands/change-request.md +10 -0
- package/template/.claude/commands/feature.md +206 -0
- package/template/.claude/commands/goal.md +77 -0
- package/template/.claude/commands/quick.md +7 -2
- package/template/.claude/hooks/feature-gate.js +91 -0
- package/template/.claude/hooks/plan-gate.js +64 -0
- package/template/.claude/hooks/session-start.js +6 -2
- package/template/.claude/istartsoft-flow/METHODOLOGY.md +127 -2
- package/template/.claude/templates/FEATURE-template.md +43 -0
- package/template/.claude/templates/automation/Dockerfile +44 -0
- package/template/.claude/templates/automation/feature-docker.js +107 -0
- package/template/.claude/templates/automation/issflow-feature.yml +82 -0
- package/template/.claude/templates/automation/issflow-goal.yml +48 -0
package/README.md
CHANGED
|
@@ -15,6 +15,12 @@ npx create-issflow --tool=claude # or pass it: claude | codex | cursor | gemi
|
|
|
15
15
|
|
|
16
16
|
Flags:
|
|
17
17
|
- `--tool=<name>` — pick the target tool (skips the prompt). `all` writes every adapter.
|
|
18
|
+
- `--ci` — also write `.github/workflows/issflow-feature.yml` (headless `/feature`
|
|
19
|
+
via GitHub Actions: label an issue `feature:approved` → the lane runs).
|
|
20
|
+
- `--docker` — also write `Dockerfile.issflow` + `scripts/feature-docker.js`
|
|
21
|
+
(headless `/feature` in an unprivileged container; the container is the sandbox).
|
|
22
|
+
The image must ship Node ≥ 18 — the kit's hooks are Node scripts; the shipped
|
|
23
|
+
Dockerfile is Node-based and the wrapper preflights `node` before every run.
|
|
18
24
|
- `--dry-run` — print what would happen, write nothing.
|
|
19
25
|
- `--force` — overwrite existing kit files (default keeps yours; conflicts are
|
|
20
26
|
written as `<file>.issflow-new` for you to merge).
|
|
@@ -25,9 +31,9 @@ Flags:
|
|
|
25
31
|
The portable kit (every tool) in `<project>/.claude/`:
|
|
26
32
|
|
|
27
33
|
- `agents/` — planner · researcher · implementer · test-author · debugger · e2e-runner · synthesizer
|
|
28
|
-
- `commands/` — `/overview` `/propose` `/phase` `/sprint` `/ui-audit` `/qa-audit` `/security-audit` `/release` `/uat` `/change-request` `/replan` `/quick` `/synthesize` `/runbook` `/store-wisdom` `/log-issue` `/log-decision` `/unstuck`
|
|
34
|
+
- `commands/` — `/overview` `/feature` `/goal` `/propose` `/phase` `/sprint` `/ui-audit` `/qa-audit` `/security-audit` `/release` `/uat` `/change-request` `/replan` `/quick` `/synthesize` `/runbook` `/store-wisdom` `/log-issue` `/log-decision` `/unstuck`
|
|
29
35
|
- `skills/` — caveman · grill-me · karpathy-guidelines · ux-design · security · code-standards
|
|
30
|
-
- `hooks/` — session-start · context-guard · pre-compact · subagent-stop
|
|
36
|
+
- `hooks/` — session-start · context-guard · plan-gate (rule-13 enforcement) · pre-compact · subagent-stop · feature-gate (Stop gate for `/feature`, artifact-verified)
|
|
31
37
|
- `istartsoft-flow/METHODOLOGY.md` — the full methodology (single source of truth)
|
|
32
38
|
|
|
33
39
|
Plus a root `AGENTS.md` (the open standard) and the per-tool adapter:
|
package/bin/cli.js
CHANGED
|
@@ -15,6 +15,8 @@ const CWD = process.cwd();
|
|
|
15
15
|
const argv = process.argv.slice(2);
|
|
16
16
|
const DRY = argv.includes('--dry-run');
|
|
17
17
|
const FORCE = argv.includes('--force');
|
|
18
|
+
const CI = argv.includes('--ci');
|
|
19
|
+
const DOCKER = argv.includes('--docker');
|
|
18
20
|
const HELP = argv.includes('-h') || argv.includes('--help');
|
|
19
21
|
const toolArg = (argv.find(a => a.startsWith('--tool=')) || '').split('=')[1];
|
|
20
22
|
|
|
@@ -42,6 +44,8 @@ Tools (--tool=, interactive prompt if omitted):`);
|
|
|
42
44
|
for (const [k, v] of Object.entries(TOOLS)) log(` ${k.padEnd(7)} ${v}`);
|
|
43
45
|
log(`
|
|
44
46
|
Flags:
|
|
47
|
+
--ci also write .github/workflows/issflow-feature.yml (headless /feature via GitHub Actions)
|
|
48
|
+
--docker also write Dockerfile.issflow + scripts/feature-docker.js (headless /feature in a container)
|
|
45
49
|
--dry-run print what would happen, write nothing
|
|
46
50
|
--force overwrite existing kit files (default keeps yours -> <file>.issflow-new)
|
|
47
51
|
-h, --help this message
|
|
@@ -121,9 +125,11 @@ function adapterClaude() {
|
|
|
121
125
|
writeFile('.claude/flow-config.json', flowConfig());
|
|
122
126
|
const HOOKS = {
|
|
123
127
|
SessionStart: [{ matcher: 'startup|clear|compact', hooks: [{ type: 'command', command: 'node .claude/hooks/session-start.js' }] }],
|
|
124
|
-
PreToolUse: [{ matcher: '*', hooks: [{ type: 'command', command: 'node .claude/hooks/context-guard.js' }] }
|
|
128
|
+
PreToolUse: [{ matcher: '*', hooks: [{ type: 'command', command: 'node .claude/hooks/context-guard.js' }] },
|
|
129
|
+
{ matcher: 'Edit|Write|MultiEdit|NotebookEdit', hooks: [{ type: 'command', command: 'node .claude/hooks/plan-gate.js' }] }],
|
|
125
130
|
PreCompact: [{ matcher: 'auto|manual', hooks: [{ type: 'command', command: 'node .claude/hooks/pre-compact.js' }] }],
|
|
126
131
|
SubagentStop: [{ hooks: [{ type: 'command', command: 'node .claude/hooks/subagent-stop.js' }] }],
|
|
132
|
+
Stop: [{ hooks: [{ type: 'command', command: 'node .claude/hooks/feature-gate.js' }] }],
|
|
127
133
|
};
|
|
128
134
|
const sp = path.join(CWD, '.claude', 'settings.json');
|
|
129
135
|
let settings = {};
|
|
@@ -211,9 +217,9 @@ function agentsMd() {
|
|
|
211
217
|
'## Roles — `.claude/agents/`', '',
|
|
212
218
|
'planner · researcher · implementer · test-author · debugger · e2e-runner · synthesizer', '',
|
|
213
219
|
'## Procedures — `.claude/commands/` (run as `/name`)', '',
|
|
214
|
-
'/overview · /
|
|
215
|
-
'/
|
|
216
|
-
'/log-issue · /log-decision · /unstuck', '',
|
|
220
|
+
'/overview · /feature · /goal · /propose · /phase · /sprint · /ui-audit · /qa-audit ·',
|
|
221
|
+
'/security-audit · /release · /uat · /change-request · /replan · /quick · /synthesize ·',
|
|
222
|
+
'/runbook · /store-wisdom · /log-issue · /log-decision · /unstuck', '',
|
|
217
223
|
'## Skills — `.claude/skills/` (loaded on demand)', '',
|
|
218
224
|
'caveman · grill-me · karpathy-guidelines · ux-design · security (Secure SDLC) · code-standards', '',
|
|
219
225
|
'## Autonomy', '',
|
|
@@ -231,7 +237,9 @@ function agentsMd() {
|
|
|
231
237
|
'11 Secure SDLC: threat-model → secure coding → SAST/SCA/secrets each phase → pentest',
|
|
232
238
|
'gate + security review before deploy (`security` skill) · 12 code-standards gate:',
|
|
233
239
|
'lint/format clean + naming per language idiom + declared architecture (`code-standards`) ·',
|
|
234
|
-
'13 PLAN-APPROVAL gate: no phase/sprint starts until `docs/PLAN.md` is human-approved
|
|
240
|
+
'13 PLAN-APPROVAL gate: no phase/sprint starts until `docs/PLAN.md` is human-approved ·',
|
|
241
|
+
'14 UNDERSTAND-FIRST gate: brief back any new free-text task and wait for confirm',
|
|
242
|
+
'before executing (an approved PLAN/FEATURE/CR/goal is the recorded confirmation).', '',
|
|
235
243
|
'## Your stack', '',
|
|
236
244
|
'Declare your stack (language, framework, infra, auth, test + E2E runner,',
|
|
237
245
|
'planning source) once in `docs/OVERVIEW.md`. Every rule references *your declared',
|
|
@@ -310,6 +318,17 @@ function main() {
|
|
|
310
318
|
// 3. per-tool adapters
|
|
311
319
|
for (const t of targets) ADAPTERS[t]();
|
|
312
320
|
|
|
321
|
+
// 3b. headless feature lane (opt-in): materialize the automation templates.
|
|
322
|
+
const autoDir = path.join(TPL, '.claude', 'templates', 'automation');
|
|
323
|
+
if (CI) {
|
|
324
|
+
writeFile(path.join('.github', 'workflows', 'issflow-feature.yml'), fs.readFileSync(path.join(autoDir, 'issflow-feature.yml'), 'utf8'));
|
|
325
|
+
writeFile(path.join('.github', 'workflows', 'issflow-goal.yml'), fs.readFileSync(path.join(autoDir, 'issflow-goal.yml'), 'utf8'));
|
|
326
|
+
}
|
|
327
|
+
if (DOCKER) {
|
|
328
|
+
writeFile('Dockerfile.issflow', fs.readFileSync(path.join(autoDir, 'Dockerfile'), 'utf8'));
|
|
329
|
+
writeFile(path.join('scripts', 'feature-docker.js'), fs.readFileSync(path.join(autoDir, 'feature-docker.js'), 'utf8'), { exec: true });
|
|
330
|
+
}
|
|
331
|
+
|
|
313
332
|
// 4. .gitignore: track the workflow dirs if .claude/* is ignored
|
|
314
333
|
const gi = path.join(CWD, '.gitignore');
|
|
315
334
|
if (fs.existsSync(gi)) {
|
|
@@ -326,6 +345,8 @@ function main() {
|
|
|
326
345
|
if (conflicts) log('Review *.issflow-new files and merge manually (your originals were untouched).');
|
|
327
346
|
for (const w of warnings) log(` ! ${w}`);
|
|
328
347
|
log(NEXT_STEPS[TOOL] || NEXT_STEPS.claude);
|
|
348
|
+
if (!CI && !DOCKER) log('Headless feature lane: re-run with --ci (GitHub Action) and/or --docker (container runner).');
|
|
349
|
+
log('New to the kit? Plain-language guide (EN/TH): https://iamstarter.github.io/istartsoftflow/how-to-use.html');
|
|
329
350
|
}
|
|
330
351
|
|
|
331
352
|
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-issflow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Scaffold the iStartSoftFlow AI-coding workflow into a project. Stack-agnostic, tool-agnostic (Claude Code, Codex, Cursor, Gemini, Aider), non-destructive.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-issflow": "bin/cli.js"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: debugger
|
|
3
3
|
description: Diagnoses one specific failing test or bug in an ISOLATED context. Keeps debug noise out of the main session.
|
|
4
4
|
tools: Read, Grep, Glob, Edit, Bash, Write
|
|
5
|
-
model:
|
|
5
|
+
model: inherit
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
You are the DEBUGGER. Caveman ULTRA mode.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: e2e-runner
|
|
3
3
|
description: Writes and runs functional browser E2E (your declared E2E runner, e.g. Playwright) BLIND — reads the acceptance spec, OVERVIEW (stack), docs/ENDPOINTS.md, and the E2E runner config, never the implementation. Writes a trace to docs/research/e2e-<phase-slug>.md; returns a terse summary.
|
|
4
4
|
tools: Read, Grep, Glob, Write, Bash
|
|
5
|
-
model:
|
|
5
|
+
model: sonnet
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
You are the E2E-RUNNER. Caveman ULTRA mode.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: implementer
|
|
3
3
|
description: Implements exactly one phase from docs/PLAN.md. Writes code only — no tests. On TDD phases runs in SCAFFOLD or FILL mode. Maintains docs/ENDPOINTS.md after each phase.
|
|
4
4
|
tools: Read, Grep, Glob, Edit, Write, Bash
|
|
5
|
-
model:
|
|
5
|
+
model: inherit
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
You are the IMPLEMENTER. Caveman ULTRA mode. Apply karpathy-guidelines skill.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: planner
|
|
3
3
|
description: Turns research findings and OVERVIEW into a vertical-slice phase plan. Phase 0 (infra) leads only when infra is self-managed; with managed infra it is N/A. Last code phase always includes deployment. Writes docs/PLAN.md.
|
|
4
4
|
tools: Read, Grep, Glob, Write
|
|
5
|
-
model:
|
|
5
|
+
model: inherit
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
You are the PLANNER. Caveman ULTRA mode.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: researcher
|
|
3
3
|
description: Two-mode fact gathering. DESIGN mode: domain/constraint research before planning — discovers service limits, API contracts, architectural constraints. IMPL mode: codebase + service investigation during a phase. Always checks KB snapshot first. Always writes findings to docs/research/, returns only terse summary + path.
|
|
4
4
|
tools: Read, Grep, Glob, Write, WebSearch, WebFetch
|
|
5
|
-
model:
|
|
5
|
+
model: sonnet
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
You are the RESEARCHER. Caveman ULTRA mode.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: test-author
|
|
3
3
|
description: Writes tests for a phase WITHOUT reading the implementation logic. On TDD phases, writes the suite BEFORE logic exists (RED-first). Tests behavior from the plan's acceptance spec only.
|
|
4
4
|
tools: Read, Grep, Glob, Write, Bash
|
|
5
|
-
model:
|
|
5
|
+
model: inherit
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
You are the TEST-AUTHOR. Caveman ULTRA mode. You write UNBIASED tests.
|
|
@@ -18,6 +18,16 @@ Read `docs/PROPOSAL.md` (the approved baseline), `docs/PLAN.md`, `docs/STATE.md`
|
|
|
18
18
|
and `docs/CHANGES.md` (create if missing). No approved proposal yet -> you're still
|
|
19
19
|
pre-build; use `/propose` instead.
|
|
20
20
|
|
|
21
|
+
**FEATURE-LANE VARIANT** — the change targets a feature-lane run (STATE has a
|
|
22
|
+
`feature: <slug>` line, or `/feature` hard-stopped here on scope creep): the
|
|
23
|
+
baseline is the APPROVED `docs/features/<slug>/FEATURE.md`, not the proposal.
|
|
24
|
+
Run the same steps against the doc — impact on its slices, effort Δ if the
|
|
25
|
+
project prices work — then log the CR and **amend the FEATURE doc** (scope +
|
|
26
|
+
acceptance criteria) and reset its header to `> Approval: PENDING`: a changed
|
|
27
|
+
doc needs a fresh human approval before the lane re-arms. Skip the PROPOSAL/
|
|
28
|
+
plan-replan steps unless the change spills beyond the feature. No proposal in
|
|
29
|
+
this project is fine for this variant — the feature doc is the commercial unit.
|
|
30
|
+
|
|
21
31
|
## STEP 1 — CLASSIFY
|
|
22
32
|
Is this an in-scope **clarification** (no cost change) or a real **scope change**
|
|
23
33
|
(add / remove / alter)? Clarification -> log it, proceed, no re-price. Scope change
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Feature lane — one APPROVED Feature doc in, a tested + hardened + documented feature branch out. Near-100% hands-off; humans remain at doc approval, UAT, and merge. Runs interactive or headless (CI / Docker).
|
|
3
|
+
argument-hint: [path to Feature doc, e.g. docs/features/<slug>/FEATURE.md · "dry-run"]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Caveman ULTRA mode. You are the ORCHESTRATOR. Route work to subagents —
|
|
7
|
+
you do NOT implement or debug yourself. Subagents cannot talk to the user; only YOU can.
|
|
8
|
+
|
|
9
|
+
Target doc: $ARGUMENTS (default: the single `docs/features/*/FEATURE.md` whose
|
|
10
|
+
`> Status:` reads `approved, pending` — zero or multiple candidates -> STOP and list them.)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## ENTRY MODES (before the pipeline)
|
|
15
|
+
|
|
16
|
+
- **`/feature new <name>`** — scaffold only. Copy
|
|
17
|
+
`.claude/templates/FEATURE-template.md` to `docs/features/<kebab-name>/FEATURE.md`,
|
|
18
|
+
fill `<feature name>`, then STOP: the human writes the spec and flips
|
|
19
|
+
`> Approval:` to APPROVED. Never auto-approve; never continue into the pipeline.
|
|
20
|
+
- **`/feature from-story <key>`** — planning-stack bridge. If a story MCP is
|
|
21
|
+
connected (e.g. iSSM `load_story`), load the story and transform it into the
|
|
22
|
+
template's sections (story intent -> What & why, ACs -> Acceptance criteria,
|
|
23
|
+
the rest mapped or left as explicit gaps). Write it as `Approval: PENDING` and
|
|
24
|
+
STOP for human review + approval — a story is planning input, not a sign-off.
|
|
25
|
+
No story MCP connected -> say so and fall back to `/feature new`.
|
|
26
|
+
- **`/feature <path>`** (or bare `/feature`) — run the pipeline below.
|
|
27
|
+
|
|
28
|
+
HEADLESS DETECT: env `ISSFLOW_HEADLESS=1` (set by the CI workflow / Docker runner)
|
|
29
|
+
means NO human is available this run. Every hard-stop below then degrades to:
|
|
30
|
+
write the blocker to `docs/features/<slug>/BLOCKED.md` + mark the gate `parked`
|
|
31
|
+
+ EXIT cleanly with a report. Never guess through a hard-stop.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## DRY-RUN CHECK (first)
|
|
36
|
+
|
|
37
|
+
`$ARGUMENTS` contains `dry-run` -> full analysis, EXECUTE NOTHING. Print the
|
|
38
|
+
ACTION PLAN (files · agents · tests/gates · branch · delivery target) then STOP.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 0. INTAKE + PRE-FLIGHT
|
|
43
|
+
|
|
44
|
+
a. BROWNFIELD CHECK: `docs/OVERVIEW.md` must exist — the feature lane extends an
|
|
45
|
+
EXISTING product. Missing -> STOP; route to `/overview` (greenfield lane).
|
|
46
|
+
|
|
47
|
+
b. APPROVAL GATE (the ONE human gate at entry): the Feature doc must carry a header
|
|
48
|
+
```
|
|
49
|
+
> Approval: APPROVED <name> <date>
|
|
50
|
+
> Automation: <none | push | push+pr>
|
|
51
|
+
```
|
|
52
|
+
Missing/PENDING -> HARD-STOP: a human approves the DOC, not the run (need a doc?
|
|
53
|
+
`/feature new <name>` scaffolds `.claude/templates/FEATURE-template.md`). Doc approval
|
|
54
|
+
is the rule-13 sign-off *scoped to this doc only* — the mini-plan it produces
|
|
55
|
+
inherits approval as long as it stays inside the doc's stated scope.
|
|
56
|
+
|
|
57
|
+
c. WORKSPACE: slug = kebab-case of the doc's title. Ensure
|
|
58
|
+
`docs/features/<slug>/FEATURE.md` (move the doc there if given elsewhere).
|
|
59
|
+
Write `docs/features/<slug>/GATES.md` — the gate checklist, all unchecked:
|
|
60
|
+
```
|
|
61
|
+
# GATES — <slug>
|
|
62
|
+
- [ ] spec-complete
|
|
63
|
+
- [ ] adversarial-doc-review
|
|
64
|
+
- [ ] mini-plan
|
|
65
|
+
- [ ] contract-probe
|
|
66
|
+
- [ ] build-green
|
|
67
|
+
- [ ] review-harden
|
|
68
|
+
- [ ] regression-green
|
|
69
|
+
- [ ] test-plan
|
|
70
|
+
- [ ] docs-updated
|
|
71
|
+
- [ ] token-stamp
|
|
72
|
+
- [ ] summary
|
|
73
|
+
- [ ] memory-queued
|
|
74
|
+
```
|
|
75
|
+
The `Stop` hook (`feature-gate.js`) BLOCKS session end while any gate is
|
|
76
|
+
unchecked — check each box the moment its step truly passes, never earlier.
|
|
77
|
+
Aborting legitimately: set `feature: <slug> (parked — <reason>)` in STATE.md.
|
|
78
|
+
|
|
79
|
+
d. STATE + BRANCH: `docs/STATE.md` gets `feature: <slug> (active)`. Work on
|
|
80
|
+
branch `feature/<slug>` (create from the default branch if needed). Never the
|
|
81
|
+
default branch.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 1. PREPARATION & SPEC COMPLETION
|
|
86
|
+
|
|
87
|
+
Dispatch `researcher` (IMPL mode) scoped to the doc: which modules/files the
|
|
88
|
+
feature touches, which dependencies + versions are involved, what the doc leaves
|
|
89
|
+
unstated. Fill every gap with the most reasonable interpretation and log each one
|
|
90
|
+
under `## Assumptions` in FEATURE.md (AUTO decision protocol — decide + log +
|
|
91
|
+
continue). Check gate `spec-complete`.
|
|
92
|
+
|
|
93
|
+
## 2. ADVERSARIAL DOC-REVIEW (before planning)
|
|
94
|
+
|
|
95
|
+
Dispatch TWO reviewers in parallel, each told to ATTACK the doc, not summarize it:
|
|
96
|
+
- correctness lens: contradictions, missing edge cases, undefined behaviour,
|
|
97
|
+
hidden scope, unstated integrations.
|
|
98
|
+
- security lens: does the feature cross a TRUST BOUNDARY (auth, untrusted input,
|
|
99
|
+
data store, money, PII)? If yes, threat-model it now (`security` skill) and fold
|
|
100
|
+
abuse cases into the acceptance criteria as negative cases.
|
|
101
|
+
|
|
102
|
+
Merge findings into FEATURE.md `## Acceptance criteria` (positive + negative).
|
|
103
|
+
Internally CONTRADICTORY spec -> HARD-STOP (methodology hard-stop 3). Merely
|
|
104
|
+
incomplete -> fill + log. Check gate `adversarial-doc-review`.
|
|
105
|
+
|
|
106
|
+
## 3. MINI-PLAN
|
|
107
|
+
|
|
108
|
+
Dispatch `planner` scoped to the doc -> `docs/features/<slug>/PLAN.md`: 1–3
|
|
109
|
+
VERTICAL slices, each with acceptance spec + `[N pts]`. The main `docs/PLAN.md`
|
|
110
|
+
is NOT touched — the feature lane is self-contained. If the plan cannot fit the
|
|
111
|
+
doc's stated scope (planner needs work the doc never mentions) -> HARD-STOP:
|
|
112
|
+
that is scope creep; route to `/change-request`. Check gate `mini-plan`.
|
|
113
|
+
|
|
114
|
+
## 4. CONTRACT SURFACE PROBE
|
|
115
|
+
|
|
116
|
+
Before any test is written: enumerate every public surface the feature ADDS or
|
|
117
|
+
CHANGES — endpoints, exported functions/classes, CLI commands, events, schemas.
|
|
118
|
+
Diff against `docs/ENDPOINTS.md` + the existing types. Write
|
|
119
|
+
`docs/features/<slug>/CONTRACTS.md`: exact signatures, request/response shapes,
|
|
120
|
+
error shapes. A changed EXISTING contract is a breaking change -> list every
|
|
121
|
+
caller + the migration in CONTRACTS.md. This file is what test-author asserts
|
|
122
|
+
against. Check gate `contract-probe`.
|
|
123
|
+
|
|
124
|
+
## 5. BUILD LOOP (per slice — the /phase machinery, feature-scoped)
|
|
125
|
+
|
|
126
|
+
For each slice in `docs/features/<slug>/PLAN.md`, in order:
|
|
127
|
+
- TDD APPLICABILITY per the methodology (default `TDD_PHASE=true`, log the call).
|
|
128
|
+
- TDD slice: SCAFFOLD (implementer, stubs from CONTRACTS.md) -> RED (`test-author`,
|
|
129
|
+
BLIND — reads FEATURE.md acceptance criteria + CONTRACTS.md only, never the
|
|
130
|
+
implementation) -> GREEN (`implementer`) -> e2e (`e2e-runner`) when the slice has
|
|
131
|
+
a user-facing flow. Non-TDD slice: IMPLEMENT -> TEST.
|
|
132
|
+
- Debug circuit breaker: WARN at 2, cap 3, `/unstuck` once per slice; still stuck
|
|
133
|
+
-> park the slice (BLOCKED in the feature PLAN) + continue to the next
|
|
134
|
+
independent slice. Blockers batch-report at the end, never mid-flow.
|
|
135
|
+
- Rule 11 (secure coding + SAST/SCA/secrets) and rule 12 (lint/format/naming/
|
|
136
|
+
architecture) apply per slice as in `/phase`.
|
|
137
|
+
- `implementer` updates `docs/ENDPOINTS.md` as surfaces land.
|
|
138
|
+
|
|
139
|
+
All slices done + full feature suite green -> check gate `build-green`.
|
|
140
|
+
|
|
141
|
+
## 6. REVIEW & HARDEN (adversarial + self-review)
|
|
142
|
+
|
|
143
|
+
- Dispatch TWO reviewers against the full feature diff: correctness lens +
|
|
144
|
+
security lens. CONFIRMED findings -> fix loop, capped at 2 rounds; leftovers
|
|
145
|
+
are logged in ISSUES.md, severity-tagged; any HIGH leftover -> HARD-STOP.
|
|
146
|
+
- Self-review: reread the diff against FEATURE.md acceptance criteria — every
|
|
147
|
+
criterion maps to a test; every assumption still holds.
|
|
148
|
+
- Run the regression corpus (`scripts/regression.sh`) + the full real suite.
|
|
149
|
+
No regression corpus in this repo (fresh brownfield install)? -> run the
|
|
150
|
+
project's OWN full test suite (the test command declared in OVERVIEW.md, or
|
|
151
|
+
the repo's obvious one — package.json test script, make test, pytest, …) and
|
|
152
|
+
note "corpus: n/a (brownfield)" in the summary. NOTHING runnable at all ->
|
|
153
|
+
that is a hard-stop: a feature cannot be verified green on a repo with no
|
|
154
|
+
tests; say so instead of pretending.
|
|
155
|
+
Green -> check gates `review-harden` + `regression-green`.
|
|
156
|
+
|
|
157
|
+
## 7. MANUAL TEST PLAN
|
|
158
|
+
|
|
159
|
+
Write `docs/features/<slug>/TEST-PLAN.md` — an all-case scenario sheet a human
|
|
160
|
+
tester can run without reading code: happy paths, negative cases, edge cases,
|
|
161
|
+
per-scenario steps + expected results, in the project language (OVERVIEW). Same
|
|
162
|
+
format `/uat` consumes, so the sheet plugs straight into the UAT cycle.
|
|
163
|
+
Check gate `test-plan`.
|
|
164
|
+
|
|
165
|
+
## 8. DOCS + STAMPS + MEMORY
|
|
166
|
+
|
|
167
|
+
a. Documentation update: `docs/ENDPOINTS.md` final pass; README/docs deltas the
|
|
168
|
+
feature makes stale. Check gate `docs-updated`.
|
|
169
|
+
b. Token stamp — append to FEATURE.md:
|
|
170
|
+
```
|
|
171
|
+
## Token stamp
|
|
172
|
+
context: ~<pct>% of window · subagents: <n> · slices: <n> · debug rounds: <n>
|
|
173
|
+
```
|
|
174
|
+
Check gate `token-stamp`.
|
|
175
|
+
c. Summary — append to FEATURE.md `## Summary`: what shipped, assumptions made,
|
|
176
|
+
parked blockers, contract changes. One line to `docs/HISTORY.md`.
|
|
177
|
+
Check gate `summary`.
|
|
178
|
+
d. Memory update — append wisdom candidates (resolved issues, reusable findings)
|
|
179
|
+
to `docs/WISDOM-QUEUE.md`. The QUEUE is local + automatic; pushing to the
|
|
180
|
+
shared KB stays human (`/store-wisdom` reads the queue). Check gate `memory-queued`.
|
|
181
|
+
|
|
182
|
+
Then: STATE.md -> `feature: <slug> (done)`.
|
|
183
|
+
|
|
184
|
+
## 9. DELIVERY
|
|
185
|
+
|
|
186
|
+
Commit the feature branch (clear message per slice or one squashed commit).
|
|
187
|
+
Then by the doc's `> Automation:` header:
|
|
188
|
+
- `none` (default) -> STOP here. Show the human: branch name, summary, TEST-PLAN
|
|
189
|
+
path. Push is theirs (methodology hard-stop 1).
|
|
190
|
+
- `push` -> push `feature/<slug>` (`git push -u origin feature/<slug>`). No PR.
|
|
191
|
+
- `push+pr` -> push + open a PR to the default branch. PR body = FEATURE.md
|
|
192
|
+
Summary + link to TEST-PLAN.md. NEVER merge. NEVER deploy. Merge and prod
|
|
193
|
+
stay human, always.
|
|
194
|
+
|
|
195
|
+
Final report (interactive: to the user · headless: as the run's closing output):
|
|
196
|
+
gates table · parked blockers · assumptions · branch/PR · "next: run /uat with
|
|
197
|
+
docs/features/<slug>/TEST-PLAN.md".
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## What stays human (by design — do not automate these away)
|
|
202
|
+
|
|
203
|
+
1. Writing + APPROVING the Feature doc (entry gate).
|
|
204
|
+
2. Any hard-stop: contradictory spec · scope creep · HIGH security finding.
|
|
205
|
+
3. UAT against TEST-PLAN.md, and the merge.
|
|
206
|
+
4. Production deploy — never in this lane; that is `/release`.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Goal layer — declare an OUTCOME, then let the kit drive lanes toward it until done, blocked, or budget spent. Goal-driven (stops on the outcome), not just time-driven like an interval loop.
|
|
3
|
+
argument-hint: [set "<outcome>" · run [id] · status · done <id> · drop <id> · "dry-run"]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Caveman ULTRA mode. You are the ORCHESTRATOR. Goals live in `docs/GOALS.md`.
|
|
7
|
+
|
|
8
|
+
A GOAL is bigger than one task: "clear the approved feature queue", "get the
|
|
9
|
+
release candidate green", "close every open HIGH issue". The goal layer picks
|
|
10
|
+
the next actionable unit, routes it through the RIGHT lane (METHODOLOGY → Lane
|
|
11
|
+
routing), and repeats — with the same gates every lane already enforces.
|
|
12
|
+
|
|
13
|
+
DRY-RUN: with `dry-run`, `/goal run` prints the pick-order + lanes it would fire
|
|
14
|
+
and STOPS. `/goal set` always stops at the confirmation gate anyway.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## /goal set "<outcome>"
|
|
19
|
+
|
|
20
|
+
1. UNDERSTAND-FIRST gate (hard rule 14) — BRIEF-BACK before writing anything:
|
|
21
|
+
- the outcome as YOU understand it, restated in one paragraph
|
|
22
|
+
- **Done when** — a measurable finish line (else the loop never terminates)
|
|
23
|
+
- scope / out-of-scope · assumptions · which lanes will likely fire
|
|
24
|
+
- **Budget** — max units per run (features/phases/quick fixes) so a runaway
|
|
25
|
+
goal cannot burn the wallet
|
|
26
|
+
STOP for explicit confirmation. Correction -> re-brief. Never skip this.
|
|
27
|
+
2. On confirm, append to `docs/GOALS.md`:
|
|
28
|
+
```
|
|
29
|
+
## G<n> — <outcome> [active]
|
|
30
|
+
> Done when: <measurable condition>
|
|
31
|
+
> Budget: <max units per run / other caps>
|
|
32
|
+
> Approved: <name> <date>
|
|
33
|
+
```
|
|
34
|
+
The `Approved:` line is what arms HEADLESS goal runs (same doctrine as the
|
|
35
|
+
feature lane: recorded consent, scoped to this goal).
|
|
36
|
+
|
|
37
|
+
## /goal run [id] (default: the single active goal)
|
|
38
|
+
|
|
39
|
+
LOOP — repeat until a stop condition:
|
|
40
|
+
1. PICK the next actionable unit, in this order:
|
|
41
|
+
a. an in-progress unit in STATE (finish what is started)
|
|
42
|
+
b. an APPROVED, pending `docs/features/*/FEATURE.md` that advances the goal
|
|
43
|
+
c. the next pending PLAN phase that advances the goal (plan must be approved — rule 13)
|
|
44
|
+
d. an open ISSUES.md item inside the goal's scope (route `/quick` or `/feature`)
|
|
45
|
+
Nothing actionable -> report + stop.
|
|
46
|
+
2. ROUTE it through the lane-routing table (`/feature` · `/phase` · `/quick`).
|
|
47
|
+
The lane runs with ALL its own gates — the goal layer never bypasses one.
|
|
48
|
+
3. TICK: append one line under the goal (`- [x] <unit> — <lane> — <result>`),
|
|
49
|
+
decrement budget, update STATE (`goal: G<n> (active — <units left>)`).
|
|
50
|
+
4. CHECK "Done when". Met -> mark `[done]`, STATE `goal: G<n> (done)`, final
|
|
51
|
+
report (units shipped · parked blockers · budget used). Not met -> loop.
|
|
52
|
+
|
|
53
|
+
STOP conditions (whichever first): Done-when met · budget spent · a lane
|
|
54
|
+
hard-stop (surface it; headless: `BLOCKED.md` + clean exit) · nothing actionable.
|
|
55
|
+
Every stop produces ONE consolidated report — never a silent end.
|
|
56
|
+
|
|
57
|
+
## /goal status · /goal done <id> · /goal drop <id>
|
|
58
|
+
|
|
59
|
+
Show goals + progress ticks · force-close (human says it's done) · abandon
|
|
60
|
+
(log why). Both edits keep the history lines — GOALS.md is append-style memory.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Recurrence (running a goal on a schedule)
|
|
65
|
+
|
|
66
|
+
`/goal run` is one pass: it works until done/blocked/budget. To keep pressure on
|
|
67
|
+
a long goal, re-fire the pass on a schedule — host-level, not kit-level:
|
|
68
|
+
- **Claude Code web/desktop**: `/loop 30m /goal run` — the host's interval loop
|
|
69
|
+
re-invokes the pass; the goal layer supplies the state + finish line that a
|
|
70
|
+
bare interval loop lacks (it stops itself when Done-when is met).
|
|
71
|
+
- **CI (headless)**: `create-issflow --ci` also installs
|
|
72
|
+
`.github/workflows/issflow-goal.yml` — a cron-ready workflow that runs
|
|
73
|
+
`/goal run` with `ISSFLOW_HEADLESS=1` (schedule commented out by default;
|
|
74
|
+
uncomment to arm). The `Approved:` line in GOALS.md is the recorded consent.
|
|
75
|
+
- **Docker**: `node scripts/feature-docker.js` per feature stays the unit
|
|
76
|
+
runner; a goal pass inside a container is `claude -p "/goal run"` on the
|
|
77
|
+
same image (cron it with the scheduler you already have).
|
|
@@ -21,11 +21,16 @@ If any fail -> STOP, tell me, recommend `/phase`.
|
|
|
21
21
|
(Hard rule 10: never route phase-worthy work through `/quick` to dodge the RED gate.)
|
|
22
22
|
|
|
23
23
|
Steps:
|
|
24
|
+
0. UNDERSTAND-FIRST (hard rule 14): brief back in 2–3 lines — the change as you
|
|
25
|
+
understand it · file(s) you'll touch · blast radius — and WAIT for my confirm.
|
|
26
|
+
One cheap turn beats redoing a misunderstood edit. (Already confirmed in this
|
|
27
|
+
conversation? say so and proceed — don't re-ask the same understanding.)
|
|
24
28
|
1. grep docs/ISSUES.md for anything related.
|
|
25
29
|
2. Make the change. Smallest diff that works.
|
|
26
30
|
3. Run it — lint/typecheck/smoke. Show me result.
|
|
27
|
-
4. REGRESSION GUARD: run `scripts/regression.sh` (mock corpus).
|
|
28
|
-
|
|
31
|
+
4. REGRESSION GUARD: run `scripts/regression.sh` (mock corpus). No corpus in this
|
|
32
|
+
repo? run the project's own test suite instead (or the touched area's tests).
|
|
33
|
+
A break BLOCKS the `/quick` — surface it to me and stop. No agent chain is added.
|
|
29
34
|
5. Error you cannot fix in 2 tries -> STOP. Recommend `/phase`.
|
|
30
35
|
6. Change revealed a bug -> `/log-issue`.
|
|
31
36
|
7. ARCHITECTURE SELF-CHECK: touched an agent, hook, command, or workflow rule?
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Stop hook — the feature-lane gate. While a /feature run is active
|
|
3
|
+
// (docs/STATE.md has `feature: <slug> (active)`), the session may not end
|
|
4
|
+
// with unchecked gates in docs/features/<slug>/GATES.md — and a CHECKED gate
|
|
5
|
+
// whose artifact is verifiable must have the artifact on disk (a ticked box
|
|
6
|
+
// with no TEST-PLAN.md behind it is treated as unchecked). Suites staying
|
|
7
|
+
// green is still proven by the steps themselves; this hook proves the
|
|
8
|
+
// deliverables exist. Pure Node, cross-platform, zero deps.
|
|
9
|
+
'use strict';
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
try { process.chdir(process.env.CLAUDE_PROJECT_DIR || '.'); } catch (_) {}
|
|
14
|
+
|
|
15
|
+
const allow = () => process.exit(0);
|
|
16
|
+
const block = (reason) => { process.stdout.write(JSON.stringify({ decision: 'block', reason }) + '\n'); process.exit(0); };
|
|
17
|
+
const read = (f) => { try { return fs.readFileSync(f, 'utf8'); } catch (_) { return null; } };
|
|
18
|
+
|
|
19
|
+
// stdin: hook input JSON. stop_hook_active=true means we already blocked once
|
|
20
|
+
// this stop — allow through to avoid an infinite block loop.
|
|
21
|
+
let input = {};
|
|
22
|
+
try { input = JSON.parse(fs.readFileSync(0, 'utf8') || '{}'); } catch (_) {}
|
|
23
|
+
if (input.stop_hook_active) allow();
|
|
24
|
+
|
|
25
|
+
const state = read(path.join('docs', 'STATE.md'));
|
|
26
|
+
if (!state) allow();
|
|
27
|
+
|
|
28
|
+
// an ACTIVE feature arms the full gate; a DONE feature still gets artifact
|
|
29
|
+
// verification (done-with-fake-gates must not slip through by never stopping
|
|
30
|
+
// while active). `(parked — reason)` disarms entirely.
|
|
31
|
+
const mActive = state.match(/^\s*feature:\s*([A-Za-z0-9._-]+)\s*\(active\)/m);
|
|
32
|
+
const mDone = state.match(/^\s*feature:\s*([A-Za-z0-9._-]+)\s*\(done\)/m);
|
|
33
|
+
const m = mActive || mDone;
|
|
34
|
+
if (!m) allow();
|
|
35
|
+
const slug = m[1];
|
|
36
|
+
const isDone = !mActive;
|
|
37
|
+
|
|
38
|
+
const gates = read(path.join('docs', 'features', slug, 'GATES.md'));
|
|
39
|
+
if (!gates) block(
|
|
40
|
+
`Feature "${slug}" is active in docs/STATE.md but docs/features/${slug}/GATES.md is missing. ` +
|
|
41
|
+
`Recreate the gate checklist (see /feature step 0c), or park the run: set ` +
|
|
42
|
+
`"feature: ${slug} (parked — <reason>)" in docs/STATE.md.`
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const open = gates.split('\n').filter((l) => /^\s*- \[ \]/.test(l)).map((l) => l.replace(/^\s*- \[ \]\s*/, '').trim());
|
|
46
|
+
const checked = gates.split('\n').filter((l) => /^\s*- \[[xX]\]/.test(l)).map((l) => l.replace(/^\s*- \[[xX]\]\s*/, '').trim());
|
|
47
|
+
|
|
48
|
+
// artifact verification — a checked gate must have its deliverable on disk.
|
|
49
|
+
const fdir = path.join('docs', 'features', slug);
|
|
50
|
+
const featureDoc = read(path.join(fdir, 'FEATURE.md')) || '';
|
|
51
|
+
const ARTIFACTS = {
|
|
52
|
+
'mini-plan': () => fs.existsSync(path.join(fdir, 'PLAN.md')) || `docs/features/${slug}/PLAN.md is missing`,
|
|
53
|
+
'contract-probe': () => fs.existsSync(path.join(fdir, 'CONTRACTS.md')) || `docs/features/${slug}/CONTRACTS.md is missing`,
|
|
54
|
+
'test-plan': () => fs.existsSync(path.join(fdir, 'TEST-PLAN.md')) || `docs/features/${slug}/TEST-PLAN.md is missing`,
|
|
55
|
+
'token-stamp': () => /^##\s*Token stamp/mi.test(featureDoc) || `FEATURE.md has no "## Token stamp" section`,
|
|
56
|
+
'summary': () => /^##\s*Summary/mi.test(featureDoc) || `FEATURE.md has no "## Summary" section`,
|
|
57
|
+
'memory-queued': () => fs.existsSync(path.join('docs', 'WISDOM-QUEUE.md')) || `docs/WISDOM-QUEUE.md is missing`,
|
|
58
|
+
};
|
|
59
|
+
const fake = [];
|
|
60
|
+
for (const g of checked) {
|
|
61
|
+
const check = ARTIFACTS[g];
|
|
62
|
+
if (!check) continue;
|
|
63
|
+
const r = check();
|
|
64
|
+
if (r !== true) fake.push(`${g} (${r})`);
|
|
65
|
+
}
|
|
66
|
+
if (fake.length) block(
|
|
67
|
+
`Feature "${slug}": ${fake.length} gate(s) are checked but their artifacts do not exist — ` +
|
|
68
|
+
`${fake.join(' · ')}. A gate is done when its deliverable is on disk, not when the box is ticked. ` +
|
|
69
|
+
`Produce the artifact(s) (see /feature), or untick the gate(s) and finish the step.`
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
if (isDone) {
|
|
73
|
+
if (open.length) block(
|
|
74
|
+
`Feature "${slug}" is marked (done) in docs/STATE.md but GATES.md still has ` +
|
|
75
|
+
`${open.length} unchecked gate(s): ${open.join(' · ')}. Finish them, or set the state ` +
|
|
76
|
+
`to "feature: ${slug} (active)" / "(parked — <reason>)" to reflect reality.`
|
|
77
|
+
);
|
|
78
|
+
allow();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (open.length === 0) block(
|
|
82
|
+
`Feature "${slug}": all gates are checked but docs/STATE.md still says (active). ` +
|
|
83
|
+
`Finish the close-out: set "feature: ${slug} (done)" in docs/STATE.md (/feature step 8).`
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
block(
|
|
87
|
+
`Feature "${slug}" has ${open.length} unchecked gate(s): ${open.join(' · ')}. ` +
|
|
88
|
+
`Finish those steps and tick each box in docs/features/${slug}/GATES.md, ` +
|
|
89
|
+
`or park the run: set "feature: ${slug} (parked — <reason>)" in docs/STATE.md ` +
|
|
90
|
+
`and record the blocker in docs/features/${slug}/BLOCKED.md.`
|
|
91
|
+
);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// PreToolUse plan gate — hard rule 13 as code. While docs/PLAN.md carries
|
|
3
|
+
// `> Approval: PENDING`, no SOURCE file may be created or edited: the plan
|
|
4
|
+
// needs a human sign-off before build work starts. Docs, kit config, and
|
|
5
|
+
// planning artifacts stay writable (planning is exactly what PENDING is for).
|
|
6
|
+
// An active feature lane is exempt — its doc approval is its own rule-13-scoped
|
|
7
|
+
// gate and it never touches the main PLAN. Fail-OPEN on any error: a hook bug
|
|
8
|
+
// must never wedge the tool loop. Pure Node, cross-platform, zero deps.
|
|
9
|
+
'use strict';
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const silent = () => process.exit(0);
|
|
14
|
+
const deny = (reason) => {
|
|
15
|
+
process.stdout.write(JSON.stringify({
|
|
16
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: reason },
|
|
17
|
+
}));
|
|
18
|
+
process.exit(0);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let input = '';
|
|
22
|
+
process.stdin.setEncoding('utf8');
|
|
23
|
+
process.stdin.on('data', (d) => (input += d));
|
|
24
|
+
process.stdin.on('end', () => {
|
|
25
|
+
let evt;
|
|
26
|
+
try { evt = JSON.parse(input); } catch (_) { return silent(); }
|
|
27
|
+
try { run(evt); } catch (_) { silent(); }
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function run(evt) {
|
|
31
|
+
const MUTATORS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
|
|
32
|
+
if (!MUTATORS.has(evt.tool_name || '')) return silent();
|
|
33
|
+
|
|
34
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || evt.cwd || '.';
|
|
35
|
+
const read = (f) => { try { return fs.readFileSync(path.join(projectDir, f), 'utf8'); } catch (_) { return null; } };
|
|
36
|
+
|
|
37
|
+
const plan = read(path.join('docs', 'PLAN.md'));
|
|
38
|
+
if (!plan) return silent(); // no plan world (e.g. /quick-only repo)
|
|
39
|
+
const approval = plan.match(/^>\s*Approval:\s*(.+)$/m);
|
|
40
|
+
if (!approval) return silent(); // not our PLAN format — don't guess
|
|
41
|
+
if (!/pending/i.test(approval[1])) return silent(); // signed off — build away
|
|
42
|
+
|
|
43
|
+
// the feature lane carries its own scoped approval and never edits the main PLAN.
|
|
44
|
+
const state = read(path.join('docs', 'STATE.md')) || '';
|
|
45
|
+
if (/^\s*feature:\s*\S+\s*\(active\)/m.test(state)) return silent();
|
|
46
|
+
|
|
47
|
+
const file = String((evt.tool_input || {}).file_path || (evt.tool_input || {}).notebook_path || '');
|
|
48
|
+
if (!file) return silent();
|
|
49
|
+
const rel = path.relative(projectDir, path.resolve(projectDir, file)).split(path.sep).join('/');
|
|
50
|
+
|
|
51
|
+
// writable while PENDING: planning + docs + kit + repo meta — never product source.
|
|
52
|
+
const ALLOW = [
|
|
53
|
+
/^docs\//, /^\.claude\//, /^\.cursor\//, /^\.github\//, /^\.aider/, /^\.git/,
|
|
54
|
+
/^(README|AGENTS|CLAUDE|GEMINI|CHANGELOG|LICENSE)[^/]*$/i, /^[^/]+\.md$/,
|
|
55
|
+
];
|
|
56
|
+
if (ALLOW.some((re) => re.test(rel))) return silent();
|
|
57
|
+
|
|
58
|
+
deny(
|
|
59
|
+
`PLAN-APPROVAL gate (hard rule 13): docs/PLAN.md still reads "> Approval: PENDING", ` +
|
|
60
|
+
`so source changes are blocked (attempted: ${rel}). Get the plan signed off first — ` +
|
|
61
|
+
`run /overview's PLAN-APPROVAL step (or /replan, then re-approve) and stamp the header ` +
|
|
62
|
+
`"> Approval: approved <date> v<n>". Planning artifacts under docs/ stay writable.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -77,13 +77,15 @@ if (issues !== null) {
|
|
|
77
77
|
emit('');
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
// 3b. research index
|
|
80
|
+
// 3b. research index — token economy: inject only the 5 newest rows, each
|
|
81
|
+
// truncated; the full table stays on disk for grep, sessions don't re-pay it.
|
|
81
82
|
const idx = read('docs/research/INDEX.md');
|
|
82
83
|
if (idx !== null) {
|
|
83
84
|
const rows = idx.split('\n').filter((l) => /^\|\s*\d{4}-\d{2}-\d{2}/.test(l));
|
|
84
85
|
emit(`## research/INDEX.md (${rows.length} prior investigations)`);
|
|
85
86
|
emit('grep this before any new research or debugging.');
|
|
86
|
-
for (const l of rows.slice(-
|
|
87
|
+
for (const l of rows.slice(-5)) emit(' ' + (l.length > 220 ? l.slice(0, 217) + '…' : l));
|
|
88
|
+
if (rows.length > 5) emit(` … (+${rows.length - 5} older — grep docs/research/INDEX.md)`);
|
|
87
89
|
emit('');
|
|
88
90
|
}
|
|
89
91
|
|
|
@@ -137,6 +139,8 @@ emit(' Hard-stops only: security / irreversible-or-outbound actions / contradic
|
|
|
137
139
|
emit('- caveman ULTRA mode is active.');
|
|
138
140
|
emit('- PLAN-APPROVAL gate (rule 13): no /phase or /sprint while STATE `plan:` reads');
|
|
139
141
|
emit(' PENDING — the plan needs a human sign-off via /overview first.');
|
|
142
|
+
emit('- UNDERSTAND-FIRST (rule 14): new free-text task -> brief back your understanding');
|
|
143
|
+
emit(' (goal · scope · assumptions · blast radius) and WAIT for confirm before executing.');
|
|
140
144
|
emit('- before debugging ANY error: grep ISSUES.md AND research/INDEX.md first.');
|
|
141
145
|
emit('- debug attempts: WARN at 2; cap 3. AUTO: log + park the slice + continue (batched');
|
|
142
146
|
emit(' report at the phase boundary). GUIDED: stop and ask you.');
|
|
@@ -137,6 +137,63 @@ AUTO governs the *dev loop between* those gates, never the plan or the money dec
|
|
|
137
137
|
|
|
138
138
|
-----
|
|
139
139
|
|
|
140
|
+
## Feature lane (hands-off delivery — `/feature`)
|
|
141
|
+
|
|
142
|
+
The lifecycle above is the GREENFIELD lane (a whole project). The **feature lane**
|
|
143
|
+
is the BROWNFIELD lane: one APPROVED Feature doc in → a tested + hardened +
|
|
144
|
+
documented feature branch out, near-100% hands-off. Humans remain at exactly three
|
|
145
|
+
points: **approve the doc** (entry) · **UAT** (`/uat` against the generated
|
|
146
|
+
TEST-PLAN) · **merge**. Production deploy is never in this lane (`/release` owns it).
|
|
147
|
+
|
|
148
|
+
Pipeline (canonical body in `.claude/commands/feature.md`):
|
|
149
|
+
spec completion → adversarial doc-review → mini-plan → contract surface probe →
|
|
150
|
+
build loop (the `/phase` machinery, feature-scoped) → review & harden → manual
|
|
151
|
+
test plan → docs + token stamp + summary → memory queue → delivery.
|
|
152
|
+
|
|
153
|
+
Three layers make "hands-off" real — each uses the mechanism suited to its job:
|
|
154
|
+
|
|
155
|
+
1. **Judgment → the orchestrator + subagents.** `/feature` routes the pipeline
|
|
156
|
+
through the standard roles under AUTO; the circuit breaker, rules 11/12, and
|
|
157
|
+
blind TDD apply unchanged.
|
|
158
|
+
2. **Must-happen-every-time → a lifecycle hook.** `docs/features/<slug>/GATES.md`
|
|
159
|
+
is the run's gate checklist; the `Stop` hook (`.claude/hooks/feature-gate.js`)
|
|
160
|
+
BLOCKS session end while the feature is `(active)` in STATE with unchecked
|
|
161
|
+
gates — and it verifies ARTIFACTS, not ticks: a checked `test-plan` /
|
|
162
|
+
`mini-plan` / `contract-probe` / `token-stamp` / `summary` / `memory-queued`
|
|
163
|
+
gate whose deliverable is missing on disk blocks too (also re-checked in the
|
|
164
|
+
`(done)` state). A prompt can be ignored; the hook cannot. Rule 13 gets the
|
|
165
|
+
same treatment: the `PreToolUse` plan gate (`.claude/hooks/plan-gate.js`)
|
|
166
|
+
denies source Edit/Write while `docs/PLAN.md` reads `> Approval: PENDING`
|
|
167
|
+
(docs/kit paths stay writable; an active feature lane is exempt — its doc
|
|
168
|
+
approval is its own scoped gate). Hosts without lifecycle hooks run these as
|
|
169
|
+
model-run rituals.
|
|
170
|
+
3. **No-human-at-the-keyboard → a headless runner.** `ISSFLOW_HEADLESS=1` tells the
|
|
171
|
+
lane no human is present: every hard-stop degrades to a `BLOCKED.md` report +
|
|
172
|
+
clean exit — never a guess. Two shipped runners (`create-issflow --ci|--docker`,
|
|
173
|
+
sources in `.claude/templates/automation/`):
|
|
174
|
+
- **GitHub Actions** — label an issue `feature:approved` (or dispatch with a doc
|
|
175
|
+
path); the workflow runs the lane on an ephemeral runner.
|
|
176
|
+
- **Docker** — `node scripts/feature-docker.js <FEATURE.md>` builds
|
|
177
|
+
`Dockerfile.issflow` and runs the lane in an unprivileged container with the
|
|
178
|
+
repo mounted; the container is the sandbox that makes a skip-permissions run
|
|
179
|
+
acceptable. Works for any host that ships a headless CLI. The runner image
|
|
180
|
+
MUST contain Node ≥ 18 — the lifecycle hooks are `node .claude/hooks/*.js`,
|
|
181
|
+
and without node they die silently (no session context, no feature gate).
|
|
182
|
+
The shipped image is Node-based and the wrapper preflights `node` before
|
|
183
|
+
every run, including bring-your-own images (`ISSFLOW_IMAGE=<name>`).
|
|
184
|
+
`--worktree` isolates a run in its own `git worktree` + branch, so 2–3
|
|
185
|
+
features build in parallel without touching your checkout or each other
|
|
186
|
+
(path-identical mounts: Linux/macOS).
|
|
187
|
+
|
|
188
|
+
**Approval semantics (rule-13 scoped).** The doc header
|
|
189
|
+
`> Approval: APPROVED <name> <date>` is the human sign-off, scoped to that doc
|
|
190
|
+
only; the mini-plan inherits it while it stays inside the doc's stated scope —
|
|
191
|
+
scope creep hard-stops to `/change-request`. The `> Automation: none|push|push+pr`
|
|
192
|
+
header pre-authorizes outbound git actions for THAT run (hard-stop 1 satisfied by
|
|
193
|
+
recorded consent). Merge and prod deploy can never be pre-authorized.
|
|
194
|
+
|
|
195
|
+
-----
|
|
196
|
+
|
|
140
197
|
## BMAD integration (planning front-end)
|
|
141
198
|
|
|
142
199
|
iStartSoftFlow is the EXECUTION loop; **BMAD-METHOD** is an optional PLANNING
|
|
@@ -201,14 +258,56 @@ can. Escalation is at most two hops.
|
|
|
201
258
|
|
|
202
259
|
The orchestrator ROUTES. It does not implement or debug.
|
|
203
260
|
|
|
261
|
+
**Model routing (per-role tiers).** Each role's `.claude/agents/<role>.md` pins a
|
|
262
|
+
`model:` tier suited to its work, so the RIGHT model runs each task by default:
|
|
263
|
+
|
|
264
|
+
| Role | `model:` | Why |
|
|
265
|
+
|------|----------|-----|
|
|
266
|
+
| planner · debugger · implementer · test-author | `inherit` | hardest reasoning — follows the session model the OWNER picked (`/model` / `--model`), so one choice cascades |
|
|
267
|
+
| researcher · e2e-runner | `sonnet` | judgment-heavy but mid-tier is the sweet spot |
|
|
268
|
+
| synthesizer | `haiku` | mechanical compression — cheapest tier |
|
|
269
|
+
|
|
270
|
+
Owner wants a SPECIFIC model? Edit the role's `model:` line — values `haiku` ·
|
|
271
|
+
`sonnet` · `opus` · `inherit` · or a full model id. The installer is
|
|
272
|
+
non-destructive, so your pins survive kit updates. Hosts without per-agent model
|
|
273
|
+
support run everything on the session model (graceful degrade).
|
|
274
|
+
|
|
204
275
|
-----
|
|
205
276
|
|
|
206
277
|
## Procedures (the slash-command set)
|
|
207
278
|
|
|
208
279
|
Named procedures, each with a canonical body in `.claude/commands/<name>.md`.
|
|
209
280
|
|
|
281
|
+
**Lane routing — which entry point for which job:**
|
|
282
|
+
|
|
283
|
+
| The job | Lane |
|
|
284
|
+
|---------|------|
|
|
285
|
+
| Brand-new project (no OVERVIEW yet) | `/overview` → `/phase` (optionally `/sprint`) |
|
|
286
|
+
| New FEATURE on an existing product | `/feature` (scaffold the doc with `/feature new`) |
|
|
287
|
+
| Small, obvious, non-phase change (a fix, a rename, a copy tweak) | `/quick` |
|
|
288
|
+
| Scope change to already-approved work | `/change-request` |
|
|
289
|
+
| An OUTCOME spanning several units ("clear the feature queue") | `/goal` (drives the lanes above) |
|
|
290
|
+
| Whole-product quality sweep / pre-release | `/ui-audit` · `/qa-audit` · `/security-audit` · `/release` |
|
|
291
|
+
|
|
292
|
+
On ambiguity between `/quick` and `/feature`: does it add or change a public
|
|
293
|
+
surface or need its own acceptance criteria? -> `/feature`. Otherwise `/quick`.
|
|
294
|
+
|
|
210
295
|
- **overview** — bootstrap a project: design-research → grill r1 → design-research
|
|
211
296
|
→ re-grill r2 → `OVERVIEW.md` → planner → `PLAN.md`.
|
|
297
|
+
- **feature [new <name> | from-story <key> | doc]** — the brownfield feature lane:
|
|
298
|
+
one APPROVED Feature doc → spec completion → adversarial doc-review → mini-plan
|
|
299
|
+
→ contract probe → build loop → review & harden → manual test plan →
|
|
300
|
+
docs/stamps/memory → delivery. `new` scaffolds the doc from
|
|
301
|
+
`.claude/templates/FEATURE-template.md`; `from-story` transforms a BMAD/iSSM
|
|
302
|
+
story into a PENDING doc (approval stays human). Gate checklist in
|
|
303
|
+
`docs/features/<slug>/GATES.md`, enforced by the `Stop` hook with artifact
|
|
304
|
+
verification. Headless-capable (CI / Docker, `ISSFLOW_HEADLESS=1`). See "Feature lane".
|
|
305
|
+
- **goal [set|run|status|done|drop]** — the goal layer: declare an OUTCOME with a
|
|
306
|
+
measurable Done-when + budget (`set`, behind the rule-14 brief-back), then
|
|
307
|
+
`run` loops pick-next-unit → route lane → tick until done / budget / hard-stop.
|
|
308
|
+
Goal-driven, not time-driven: it stops itself on the finish line. Recurrence is
|
|
309
|
+
host-level (interval loop or the cron-ready `issflow-goal.yml`). `docs/GOALS.md`
|
|
310
|
+
holds state; its `Approved:` line arms headless passes.
|
|
212
311
|
- **propose** — turn approved requirements + stack into `PROPOSAL.md` (scope, phase
|
|
213
312
|
breakdown, effort + cost estimate, assumptions) with a client sign-off gate.
|
|
214
313
|
- **change-request** — a mid-project scope change: impact analysis + re-estimate +
|
|
@@ -363,7 +462,7 @@ Mirrors the installer's `--dry-run`. (In a dry-run, even AUTO never acts — it
|
|
|
363
462
|
|
|
364
463
|
-----
|
|
365
464
|
|
|
366
|
-
## Hard rules (1–
|
|
465
|
+
## Hard rules (1–14)
|
|
367
466
|
|
|
368
467
|
1. Before debugging ANY error: grep `docs/ISSUES.md` AND `docs/research/INDEX.md`.
|
|
369
468
|
The SESSION-OPEN ritual surfaces ISSUES.md — there is no excuse to miss it.
|
|
@@ -427,6 +526,16 @@ Mirrors the installer's `--dry-run`. (In a dry-run, even AUTO never acts — it
|
|
|
427
526
|
the planning twin of the commercial sign-off gate (`/propose`). A `/replan` that
|
|
428
527
|
adds or reshapes UNBUILT scope reverts the affected plan to `PENDING` and
|
|
429
528
|
re-surfaces it for confirmation before those phases run.
|
|
529
|
+
14. **UNDERSTAND-FIRST gate (brief-back).** No new task starts executing on an
|
|
530
|
+
unconfirmed understanding. Any command that takes free-text work (`/quick`,
|
|
531
|
+
`/change-request`, `/goal set`, the `/overview` grill) BRIEFS BACK first:
|
|
532
|
+
restate the task — goal · scope · out-of-scope · assumptions · plan sketch ·
|
|
533
|
+
blast radius — then WAIT for explicit confirmation before touching anything.
|
|
534
|
+
A recorded approval artifact IS the confirmation for its lane (approved
|
|
535
|
+
PLAN → phases · APPROVED FEATURE doc → the feature lane · approved CR → the
|
|
536
|
+
change · `Approved:` goal → goal runs) — that is exactly what arms headless.
|
|
537
|
+
Rationale: a wrong understanding burns tokens and context at 100× the cost
|
|
538
|
+
of one confirm turn. AUTO governs execution AFTER intake, never instead of it.
|
|
430
539
|
|
|
431
540
|
-----
|
|
432
541
|
|
|
@@ -476,6 +585,22 @@ the KB. The kit works normally without a KB.
|
|
|
476
585
|
summary, known limitations, approver — the gate to promote to production.
|
|
477
586
|
- `docs/ui-audit-<date>.md` · `docs/qa-audit-<date>.md` · `docs/security-audit-<date>.md`
|
|
478
587
|
— scored whole-product audit reports (from the `*-audit` commands).
|
|
588
|
+
- `docs/features/<slug>/` — one dir per feature-lane run: `FEATURE.md` (the doc:
|
|
589
|
+
approval header, acceptance criteria, assumptions, token stamp, summary),
|
|
590
|
+
`PLAN.md` (mini-plan), `CONTRACTS.md` (probed public surfaces), `TEST-PLAN.md`
|
|
591
|
+
(the UAT scenario sheet), `GATES.md` (the Stop-hook-enforced checklist),
|
|
592
|
+
`BLOCKED.md` (headless blocker reports).
|
|
593
|
+
- `docs/WISDOM-QUEUE.md` — auto-appended wisdom candidates from feature runs;
|
|
594
|
+
`/store-wisdom` reads it before pushing to the shared KB (push stays human).
|
|
595
|
+
- `.claude/templates/automation/` — headless-runner sources (GitHub Actions ·
|
|
596
|
+
Dockerfile · docker wrapper), materialized by `create-issflow --ci` /
|
|
597
|
+
`--docker` as `.github/workflows/issflow-feature.yml` +
|
|
598
|
+
`.github/workflows/issflow-goal.yml` (cron-ready, disarmed by default) ·
|
|
599
|
+
`Dockerfile.issflow` · `scripts/feature-docker.js`.
|
|
600
|
+
- `docs/GOALS.md` — the goal layer's state: one `## G<n>` block per goal
|
|
601
|
+
(Done-when · Budget · `Approved:` line · progress ticks). Maintained by `/goal`.
|
|
602
|
+
- `.claude/templates/FEATURE-template.md` — the Feature-doc form `/feature new`
|
|
603
|
+
scaffolds (Approval/Automation headers + spec sections).
|
|
479
604
|
- `docs/STATE.md` — current position. Small. Rewritten, not appended.
|
|
480
605
|
- `docs/ISSUES.md` — error log. Deduped by synthesizer.
|
|
481
606
|
- `docs/PLAN.md` — the phase plan (the product backlog). Carries an `> Approval:`
|
|
@@ -527,7 +652,7 @@ the same everywhere — only the *wiring* differs.
|
|
|
527
652
|
|
|
528
653
|
| Host | Entry file | Commands | Subagents | Lifecycle hooks | Shared KB |
|
|
529
654
|
|------|-----------|----------|-----------|-----------------|-----------|
|
|
530
|
-
| **Claude Code** (reference) | `CLAUDE.md` (`@AGENTS.md`) + `.claude/` | `.claude/commands/` | native | SessionStart · PreToolUse (context-
|
|
655
|
+
| **Claude Code** (reference) | `CLAUDE.md` (`@AGENTS.md`) + `.claude/` | `.claude/commands/` | native | SessionStart · PreToolUse (context watchdog + rule-13 plan gate) · PreCompact · SubagentStop · Stop (feature gate) | yes |
|
|
531
656
|
| **Codex CLI** | `AGENTS.md` (native) | `.claude/commands/` (read as prompts) | read as reference | model-run | yes |
|
|
532
657
|
| **Cursor** | `.cursor/rules/` + `AGENTS.md` | `.cursor/commands/` | reads `.claude/agents/` | `.cursor/hooks.json` (sessionStart · subagentStop) | yes |
|
|
533
658
|
| **Gemini CLI** | `GEMINI.md` + `AGENTS.md` | `.claude/commands/` (read as prompts) | read as reference | model-run | yes |
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# FEATURE: <feature name>
|
|
2
|
+
|
|
3
|
+
> Status: draft
|
|
4
|
+
> Approval: PENDING <!-- APPROVED <name> <YYYY-MM-DD> arms the lane -->
|
|
5
|
+
> Automation: none <!-- none | push | push+pr — what the run may do outbound -->
|
|
6
|
+
|
|
7
|
+
<!--
|
|
8
|
+
The ONE human input of the feature lane. Fill it in, flip Approval to
|
|
9
|
+
APPROVED, then run: /feature docs/features/<slug>/FEATURE.md
|
|
10
|
+
(or headless: node scripts/feature-docker.js docs/features/<slug>/FEATURE.md)
|
|
11
|
+
Everything below Approval is the spec the agents build and test against —
|
|
12
|
+
write WHAT and WHY; the lane decides HOW and logs it.
|
|
13
|
+
-->
|
|
14
|
+
|
|
15
|
+
## What & why
|
|
16
|
+
|
|
17
|
+
<2–5 sentences: the capability, who uses it, why now. Plain language.>
|
|
18
|
+
|
|
19
|
+
## Scope
|
|
20
|
+
|
|
21
|
+
- <bullet the concrete behaviours in scope — each becomes acceptance criteria>
|
|
22
|
+
- <keep it to ONE feature; a second feature = a second doc>
|
|
23
|
+
|
|
24
|
+
## Out of scope
|
|
25
|
+
|
|
26
|
+
- <name what this doc deliberately does NOT cover — the scope-creep fence.
|
|
27
|
+
The lane HARD-STOPS to /change-request if the plan needs anything here>
|
|
28
|
+
|
|
29
|
+
## Constraints & contracts
|
|
30
|
+
|
|
31
|
+
- <APIs / schemas / integrations it must respect or extend, if known>
|
|
32
|
+
- <performance, compliance, or platform constraints, if any>
|
|
33
|
+
|
|
34
|
+
## Acceptance criteria
|
|
35
|
+
|
|
36
|
+
<!-- The lane's adversarial doc-review will ATTACK this list and fold in the
|
|
37
|
+
negative/abuse cases it finds. Start with the positives you care about. -->
|
|
38
|
+
- <given/when/then or checklist form — every line must be testable>
|
|
39
|
+
|
|
40
|
+
## Done when
|
|
41
|
+
|
|
42
|
+
- Every acceptance criterion has a passing test (blind TDD).
|
|
43
|
+
- Every scenario in the generated TEST-PLAN.md passes UAT.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# iStartSoftFlow — headless runner image (Docker lane).
|
|
2
|
+
# Installed by `npx create-issflow --docker` as Dockerfile.issflow.
|
|
3
|
+
# Built + run by scripts/feature-docker.js — you rarely use this file directly.
|
|
4
|
+
#
|
|
5
|
+
# The container IS the sandbox: an ephemeral, unprivileged workspace where
|
|
6
|
+
# `claude --dangerously-skip-permissions` is acceptable because the blast radius
|
|
7
|
+
# is the mounted repo only. Never mount the Docker socket or your $HOME into it.
|
|
8
|
+
#
|
|
9
|
+
# The image is tool-only; YOUR project's runtime deps are not baked in. If the
|
|
10
|
+
# feature lane must run your test suite and it needs more than Node (Python, Go,
|
|
11
|
+
# a DB client, …), extend this image: `FROM issflow-runner` + your toolchain.
|
|
12
|
+
|
|
13
|
+
FROM node:20-bookworm-slim
|
|
14
|
+
|
|
15
|
+
# git — branches/commits are the lane's deliverable. ca-certificates — API TLS.
|
|
16
|
+
RUN apt-get update \
|
|
17
|
+
&& apt-get install -y --no-install-recommends git ca-certificates \
|
|
18
|
+
&& rm -rf /var/lib/apt/lists/* \
|
|
19
|
+
&& npm install -g @anthropic-ai/claude-code
|
|
20
|
+
|
|
21
|
+
# sanity: the kit's lifecycle hooks are `node .claude/hooks/*.js` — a runner
|
|
22
|
+
# image WITHOUT node runs claude fine but every hook silently dies (no
|
|
23
|
+
# session-start context, no feature gate). Fail the build, not the run.
|
|
24
|
+
RUN node --version && claude --version
|
|
25
|
+
|
|
26
|
+
# unprivileged runner; /work is the mounted repo.
|
|
27
|
+
RUN useradd --create-home --shell /bin/sh runner
|
|
28
|
+
USER runner
|
|
29
|
+
WORKDIR /work
|
|
30
|
+
|
|
31
|
+
# the mounted repo is owned by the HOST uid, not `runner` — without this every
|
|
32
|
+
# git command fails with "detected dubious ownership in repository".
|
|
33
|
+
RUN git config --global --add safe.directory '*'
|
|
34
|
+
|
|
35
|
+
# headless marker — /feature degrades every hard-stop to a BLOCKED report + clean exit.
|
|
36
|
+
ENV ISSFLOW_HEADLESS=1
|
|
37
|
+
|
|
38
|
+
# identity for the commits the lane makes (override via docker run -e).
|
|
39
|
+
ENV GIT_AUTHOR_NAME="issflow-runner" \
|
|
40
|
+
GIT_AUTHOR_EMAIL="issflow-runner@local" \
|
|
41
|
+
GIT_COMMITTER_NAME="issflow-runner" \
|
|
42
|
+
GIT_COMMITTER_EMAIL="issflow-runner@local"
|
|
43
|
+
|
|
44
|
+
ENTRYPOINT ["claude"]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// iStartSoftFlow — run the feature lane headless in Docker.
|
|
3
|
+
// Installed by `npx create-issflow --docker` as scripts/feature-docker.js.
|
|
4
|
+
// Pure Node, cross-platform (macOS / Windows / Linux). Needs: docker, ANTHROPIC_API_KEY.
|
|
5
|
+
//
|
|
6
|
+
// node scripts/feature-docker.js docs/features/<slug>/FEATURE.md [--rebuild] [--worktree]
|
|
7
|
+
//
|
|
8
|
+
// What it does: build the runner image (Dockerfile.issflow) if missing, then run
|
|
9
|
+
// `claude -p "/feature <doc>"` inside an ephemeral container with THIS repo
|
|
10
|
+
// mounted at /work. The container is the sandbox — the run edits, tests, and
|
|
11
|
+
// commits on feature/<slug> in your working tree. Push/PR happen only if the
|
|
12
|
+
// doc's `> Automation:` header authorizes them AND credentials are available
|
|
13
|
+
// in the container; otherwise the branch stays local and pushing is yours.
|
|
14
|
+
//
|
|
15
|
+
// --worktree: parallel lane. Mounts a FRESH `git worktree` (../<repo>-feature-<slug>,
|
|
16
|
+
// on branch feature/<slug>) instead of your working tree, so 2–3 features can run
|
|
17
|
+
// concurrently without touching your checkout or each other. Each run needs its
|
|
18
|
+
// own doc + slug. Results come back as commits on feature/<slug> (visible from the
|
|
19
|
+
// main repo); remove the dir with `git worktree remove` after merge.
|
|
20
|
+
'use strict';
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const { spawnSync } = require('child_process');
|
|
24
|
+
|
|
25
|
+
const IMAGE = process.env.ISSFLOW_IMAGE || 'issflow-runner';
|
|
26
|
+
const argv = process.argv.slice(2);
|
|
27
|
+
const rebuild = argv.includes('--rebuild');
|
|
28
|
+
const useWorktree = argv.includes('--worktree');
|
|
29
|
+
const doc = argv.find((a) => !a.startsWith('--'));
|
|
30
|
+
|
|
31
|
+
const die = (msg) => { console.error('feature-docker: ' + msg); process.exit(1); };
|
|
32
|
+
const run = (cmd, args, opts = {}) => spawnSync(cmd, args, { stdio: 'inherit', ...opts });
|
|
33
|
+
|
|
34
|
+
if (!doc) die('usage: node scripts/feature-docker.js <path/to/FEATURE.md> [--rebuild] [--worktree]');
|
|
35
|
+
const repo = process.cwd();
|
|
36
|
+
if (!fs.existsSync(path.join(repo, doc))) die(`Feature doc not found: ${doc} (run from the repo root)`);
|
|
37
|
+
if (!fs.existsSync(path.join(repo, '.claude', 'commands', 'feature.md'))) die('no .claude/commands/feature.md here — run from a repo with the kit installed');
|
|
38
|
+
if (!process.env.ANTHROPIC_API_KEY) die('ANTHROPIC_API_KEY is not set');
|
|
39
|
+
|
|
40
|
+
const dockerOk = spawnSync('docker', ['version'], { stdio: 'ignore' });
|
|
41
|
+
if (dockerOk.error || dockerOk.status !== 0) die('docker is not available');
|
|
42
|
+
|
|
43
|
+
const haveImage = spawnSync('docker', ['image', 'inspect', IMAGE], { stdio: 'ignore' }).status === 0;
|
|
44
|
+
if (!haveImage || rebuild) {
|
|
45
|
+
const df = fs.existsSync(path.join(repo, 'Dockerfile.issflow'))
|
|
46
|
+
? 'Dockerfile.issflow'
|
|
47
|
+
: path.join('.claude', 'templates', 'automation', 'Dockerfile');
|
|
48
|
+
console.log(`feature-docker: building ${IMAGE} from ${df} …`);
|
|
49
|
+
if (run('docker', ['build', '-t', IMAGE, '-f', df, '.']).status !== 0) die('image build failed');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// preflight: the kit's hooks (session-start, the feature gate) are Node
|
|
53
|
+
// scripts. An image without node — e.g. claude installed as a native binary,
|
|
54
|
+
// or a bring-your-own ISSFLOW_IMAGE built on a non-Node base — runs claude
|
|
55
|
+
// but every hook dies silently, so the gate enforcement is gone. Refuse early.
|
|
56
|
+
const nodeCheck = spawnSync('docker', ['run', '--rm', '--entrypoint', 'node', IMAGE, '--version'], { stdio: 'ignore' });
|
|
57
|
+
if (nodeCheck.status !== 0) die(
|
|
58
|
+
`image "${IMAGE}" has no usable node — the kit's lifecycle hooks are Node scripts and cannot run. ` +
|
|
59
|
+
`Use the shipped image (delete it + rerun with --rebuild), or base your custom image on Node >= 18 ` +
|
|
60
|
+
`(e.g. FROM issflow-runner).`
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// --worktree: isolate the run in its own checkout so features run in parallel.
|
|
64
|
+
let mountDir = repo;
|
|
65
|
+
if (useWorktree) {
|
|
66
|
+
const slug = path.basename(path.dirname(path.resolve(repo, doc))) || 'feature';
|
|
67
|
+
const wt = path.join(path.dirname(repo), `${path.basename(repo)}-feature-${slug}`);
|
|
68
|
+
const branch = `feature/${slug}`;
|
|
69
|
+
if (!fs.existsSync(wt)) {
|
|
70
|
+
console.log(`feature-docker: creating worktree ${wt} on ${branch}`);
|
|
71
|
+
const branchExists = spawnSync('git', ['rev-parse', '--verify', branch], { stdio: 'ignore' }).status === 0;
|
|
72
|
+
const wtArgs = branchExists ? ['worktree', 'add', wt, branch] : ['worktree', 'add', '-b', branch, wt];
|
|
73
|
+
if (run('git', wtArgs).status !== 0) die('git worktree add failed');
|
|
74
|
+
}
|
|
75
|
+
// the doc may only exist in the main tree so far — carry it into the worktree.
|
|
76
|
+
const wtDoc = path.join(wt, doc);
|
|
77
|
+
if (!fs.existsSync(wtDoc)) {
|
|
78
|
+
fs.mkdirSync(path.dirname(wtDoc), { recursive: true });
|
|
79
|
+
fs.copyFileSync(path.resolve(repo, doc), wtDoc);
|
|
80
|
+
}
|
|
81
|
+
mountDir = wt;
|
|
82
|
+
console.log(`feature-docker: parallel lane — this run is isolated in ${wt}; your checkout stays untouched.`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log(`feature-docker: running /feature ${doc} in ${IMAGE}`);
|
|
86
|
+
const args = ['run', '--rm', '--entrypoint', 'claude'];
|
|
87
|
+
if (useWorktree) {
|
|
88
|
+
// a worktree's git metadata references BOTH trees by absolute HOST path
|
|
89
|
+
// (.git file -> main .git/worktrees/<n>; gitdir file -> back to the worktree).
|
|
90
|
+
// Mount both at their host paths so every pointer resolves in-container.
|
|
91
|
+
// (Path-identical mounts: Linux/macOS; on Windows use the default mode.)
|
|
92
|
+
args.push('-v', `${repo}:${repo}`, '-v', `${mountDir}:${mountDir}`, '-w', mountDir);
|
|
93
|
+
} else {
|
|
94
|
+
args.push('-v', `${mountDir}:/work`, '-w', '/work');
|
|
95
|
+
}
|
|
96
|
+
args.push(
|
|
97
|
+
'-e', 'ANTHROPIC_API_KEY',
|
|
98
|
+
'-e', 'ISSFLOW_HEADLESS=1'
|
|
99
|
+
);
|
|
100
|
+
// pass git identity through when the host has one configured.
|
|
101
|
+
for (const k of ['GIT_AUTHOR_NAME', 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_NAME', 'GIT_COMMITTER_EMAIL'])
|
|
102
|
+
if (process.env[k]) args.push('-e', k);
|
|
103
|
+
args.push(IMAGE, '-p', `/feature ${doc}`, '--dangerously-skip-permissions');
|
|
104
|
+
|
|
105
|
+
const res = run('docker', args);
|
|
106
|
+
if (res.status !== 0) die(`run exited with status ${res.status} — see output above; blockers land in docs/features/<slug>/BLOCKED.md`);
|
|
107
|
+
console.log('feature-docker: done. Review the feature branch, then run /uat with the TEST-PLAN.');
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# iStartSoftFlow — headless feature lane (GitHub Actions).
|
|
2
|
+
# Installed by `npx create-issflow --ci` as .github/workflows/issflow-feature.yml.
|
|
3
|
+
#
|
|
4
|
+
# Two triggers:
|
|
5
|
+
# 1. Label an issue `feature:approved` — the issue TITLE becomes the slug and
|
|
6
|
+
# the issue BODY becomes docs/features/<slug>/FEATURE.md. The body must
|
|
7
|
+
# already carry the `> Approval:` / `> Automation:` headers (/feature step 0b);
|
|
8
|
+
# the label is the human act that fires the run.
|
|
9
|
+
# 2. Manual dispatch with the path of a Feature doc already committed.
|
|
10
|
+
#
|
|
11
|
+
# The run pushes feature/<slug> and opens a PR when the doc says
|
|
12
|
+
# `> Automation: push+pr`. It NEVER merges and NEVER deploys — UAT + merge stay human.
|
|
13
|
+
#
|
|
14
|
+
# Setup: repo secret ANTHROPIC_API_KEY. Grant Actions "Read and write permissions"
|
|
15
|
+
# (Settings → Actions → General → Workflow permissions) so the run can push + open PRs.
|
|
16
|
+
|
|
17
|
+
name: issflow-feature
|
|
18
|
+
|
|
19
|
+
on:
|
|
20
|
+
issues:
|
|
21
|
+
types: [labeled]
|
|
22
|
+
workflow_dispatch:
|
|
23
|
+
inputs:
|
|
24
|
+
feature_doc:
|
|
25
|
+
description: Path to the approved Feature doc (docs/features/<slug>/FEATURE.md)
|
|
26
|
+
required: true
|
|
27
|
+
type: string
|
|
28
|
+
|
|
29
|
+
permissions:
|
|
30
|
+
contents: write
|
|
31
|
+
pull-requests: write
|
|
32
|
+
issues: write
|
|
33
|
+
|
|
34
|
+
jobs:
|
|
35
|
+
feature:
|
|
36
|
+
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'feature:approved'
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
timeout-minutes: 90
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
with:
|
|
42
|
+
fetch-depth: 0
|
|
43
|
+
|
|
44
|
+
# Issue trigger: materialize the issue body as the Feature doc.
|
|
45
|
+
- name: Write Feature doc from issue
|
|
46
|
+
if: github.event_name == 'issues'
|
|
47
|
+
env:
|
|
48
|
+
ISSUE_TITLE: ${{ github.event.issue.title }}
|
|
49
|
+
ISSUE_BODY: ${{ github.event.issue.body }}
|
|
50
|
+
run: |
|
|
51
|
+
slug=$(printf '%s' "$ISSUE_TITLE" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+|-+$//g')
|
|
52
|
+
test -n "$slug"
|
|
53
|
+
mkdir -p "docs/features/$slug"
|
|
54
|
+
printf '%s\n' "$ISSUE_BODY" > "docs/features/$slug/FEATURE.md"
|
|
55
|
+
echo "FEATURE_DOC=docs/features/$slug/FEATURE.md" >> "$GITHUB_ENV"
|
|
56
|
+
|
|
57
|
+
- name: Resolve Feature doc path
|
|
58
|
+
if: github.event_name == 'workflow_dispatch'
|
|
59
|
+
run: echo "FEATURE_DOC=${{ inputs.feature_doc }}" >> "$GITHUB_ENV"
|
|
60
|
+
|
|
61
|
+
- name: Run the feature lane
|
|
62
|
+
uses: anthropics/claude-code-action@v1
|
|
63
|
+
env:
|
|
64
|
+
ISSFLOW_HEADLESS: "1"
|
|
65
|
+
with:
|
|
66
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
67
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
68
|
+
prompt: "/feature ${{ env.FEATURE_DOC }}"
|
|
69
|
+
# Broad allowlist via the documented --allowedTools flag (the ephemeral
|
|
70
|
+
# runner is the sandbox). The Stop hook (feature-gate.js) still enforces
|
|
71
|
+
# the gate checklist inside the run.
|
|
72
|
+
claude_args: "--allowedTools Bash,Edit,Write,Read,Glob,Grep,Task,WebFetch,WebSearch,TodoWrite"
|
|
73
|
+
|
|
74
|
+
# The lane's own delivery step (git push / PR) runs inside Claude per the
|
|
75
|
+
# doc's `> Automation:` header. This job only reports.
|
|
76
|
+
- name: Report back to the issue
|
|
77
|
+
if: github.event_name == 'issues' && always()
|
|
78
|
+
env:
|
|
79
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
80
|
+
run: |
|
|
81
|
+
gh issue comment ${{ github.event.issue.number }} \
|
|
82
|
+
--body "issflow-feature run finished: ${{ job.status }}. See the run log + the feature/<slug> branch. Next: /uat with docs/features/<slug>/TEST-PLAN.md."
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# iStartSoftFlow — scheduled goal pass (GitHub Actions).
|
|
2
|
+
# Installed by `npx create-issflow --ci` as .github/workflows/issflow-goal.yml.
|
|
3
|
+
#
|
|
4
|
+
# Runs ONE `/goal run` pass headless: pick next actionable unit -> route lane ->
|
|
5
|
+
# tick -> repeat until Done-when / budget / hard-stop. The goal's `> Approved:`
|
|
6
|
+
# line in docs/GOALS.md is the recorded consent that arms the run; a lane
|
|
7
|
+
# hard-stop writes BLOCKED.md and exits cleanly. It never merges, never deploys.
|
|
8
|
+
#
|
|
9
|
+
# DISARMED BY DEFAULT: uncomment `schedule:` to run on cron. Manual dispatch
|
|
10
|
+
# always works. Setup: secret ANTHROPIC_API_KEY + Actions write permissions
|
|
11
|
+
# (same as issflow-feature.yml).
|
|
12
|
+
|
|
13
|
+
name: issflow-goal
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
inputs:
|
|
18
|
+
goal_id:
|
|
19
|
+
description: Goal id to run (blank = the single active goal)
|
|
20
|
+
required: false
|
|
21
|
+
type: string
|
|
22
|
+
# schedule:
|
|
23
|
+
# - cron: '0 1 * * 1-5' # 01:00 UTC weekdays — one pass per night
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
contents: write
|
|
27
|
+
pull-requests: write
|
|
28
|
+
issues: write
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
goal:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
timeout-minutes: 120
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
with:
|
|
37
|
+
fetch-depth: 0
|
|
38
|
+
|
|
39
|
+
- name: Run one goal pass
|
|
40
|
+
uses: anthropics/claude-code-action@v1
|
|
41
|
+
env:
|
|
42
|
+
ISSFLOW_HEADLESS: "1"
|
|
43
|
+
with:
|
|
44
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
45
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
46
|
+
prompt: "/goal run ${{ inputs.goal_id }}"
|
|
47
|
+
# Documented --allowedTools flag; the ephemeral runner is the sandbox.
|
|
48
|
+
claude_args: "--allowedTools Bash,Edit,Write,Read,Glob,Grep,Task,WebFetch,WebSearch,TodoWrite"
|