@pilotspace/add 1.7.0 → 1.7.1
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/CHANGELOG.md +27 -0
- package/bin/cli.js +190 -8
- package/docs/08-step-6-verify.md +4 -0
- package/package.json +1 -1
- package/skill/add/SKILL.md +4 -2
- package/skill/add/intake.md +5 -0
- package/skill/add/phases/6-verify.md +9 -0
- package/skill/add/report-template.md +41 -13
- package/skill/add/scope.md +40 -0
- package/tooling/templates/TASK.md.tmpl +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,33 @@ All notable changes to the ADD method (`@pilotspace/add` on npm,
|
|
|
4
4
|
`pilotspace-add` on PyPI) are documented here. The format follows
|
|
5
5
|
[Keep a Changelog](https://keepachangelog.com/); versions follow semver.
|
|
6
6
|
|
|
7
|
+
## [1.7.1] — 2026-06-18
|
|
8
|
+
|
|
9
|
+
Installer depth and method quality release: the onboarding installer gains
|
|
10
|
+
brand-aware prompts, readiness detection, and intent handoff; SOUL.md is now
|
|
11
|
+
seeded on first install and update; scope drafting and build verification gain
|
|
12
|
+
new depth guards. All additive; no breaking changes.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **Brand-aware guided installer (`installer-smarts`)** — `add init` now prompts
|
|
16
|
+
for the user's brand/project name and detects whether ADD is already present
|
|
17
|
+
(readiness check); defaults to global scope and captures the user's first
|
|
18
|
+
intent, handing it off to the first `/add` session via `.add/.intent`.
|
|
19
|
+
- **SOUL.md seeded on install and update (`installer-soul-seed`)** — `add init`
|
|
20
|
+
and `add update` now seed `.add/SOUL.md` from the bundled voice template if it
|
|
21
|
+
does not yet exist, so the voice file is present from the first session without
|
|
22
|
+
waiting for `add.py init`.
|
|
23
|
+
- **Build expectations in VERIFY (`verify-expectations`)** — the §6 VERIFY step
|
|
24
|
+
gains a Build-expectations block: the AI pre-declares observable outcomes derived
|
|
25
|
+
from §2 scenarios + §3 contract, and verify confirms them — so a build is checked
|
|
26
|
+
correct, not merely test-green.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- **Scope drafting quality guard (`scope-drafting-quality`)** — the scope guide now
|
|
30
|
+
requires the goal to be grounded in current project assets and the milestone map
|
|
31
|
+
before the goal sentence is drafted; a draft well-formedness gate catches
|
|
32
|
+
incomplete MILESTONE.md shapes early.
|
|
33
|
+
|
|
7
34
|
## [1.7.0] — 2026-06-18
|
|
8
35
|
|
|
9
36
|
The installer & onboarding release: standing up — or repairing — ADD is now one
|
package/bin/cli.js
CHANGED
|
@@ -119,6 +119,63 @@ function detectAgent(env) {
|
|
|
119
119
|
return generic;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
// A PATH lookup (no spawn): is an executable named `cmd` on PATH? Fail-soft -> null.
|
|
123
|
+
// Injectable into the enriched detector so the dev machine's installed agents never pollute tests.
|
|
124
|
+
function whichSync(cmd) {
|
|
125
|
+
try {
|
|
126
|
+
const dirs = (process.env.PATH || "").split(path.delimiter).filter(Boolean);
|
|
127
|
+
const exts = process.platform === "win32"
|
|
128
|
+
? (process.env.PATHEXT || ".EXE;.CMD;.BAT").split(";")
|
|
129
|
+
: [""];
|
|
130
|
+
for (const dir of dirs) {
|
|
131
|
+
for (const ext of exts) {
|
|
132
|
+
const hit = path.join(dir, cmd + ext);
|
|
133
|
+
if (fs.existsSync(hit)) return hit;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch (_e) { /* fail-soft */ }
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ADDITIVE enrichment for the INTERACTIVE default — never replaces detectAgent (which stays
|
|
141
|
+
// env-only; test_agent_detect pins it, the non-interactive write uses it). Precedence:
|
|
142
|
+
// env signal (authoritative) > a CLAUDE.md in the target (repo signal; AGENTS.md is ambiguous,
|
|
143
|
+
// so it does NOT pick) > an installed agent CLI (machine signal; PATH lookup only) > generic.
|
|
144
|
+
// Pure + fail-soft: a throwing probe reads as absent. `which` is injectable for hermetic tests.
|
|
145
|
+
function detectAgentEnriched(env, target, which) {
|
|
146
|
+
which = which || whichSync;
|
|
147
|
+
const base = detectAgent(env);
|
|
148
|
+
if (base.id !== "generic") return base; // env signal wins
|
|
149
|
+
const byId = {};
|
|
150
|
+
for (const p of AGENT_PROFILES) byId[p.id] = p;
|
|
151
|
+
try {
|
|
152
|
+
if (target && fs.existsSync(path.join(target, "CLAUDE.md"))) return byId.claude; // repo signal
|
|
153
|
+
} catch (_e) { /* fall through */ }
|
|
154
|
+
for (const id of ["claude", "codex", "opencode"]) {
|
|
155
|
+
try { if (which(id)) return byId[id]; } catch (_e) { /* probe absent */ } // machine signal
|
|
156
|
+
}
|
|
157
|
+
return base; // generic
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Fail-soft pre-flight summary for the INTERACTIVE path (the caller gates):
|
|
161
|
+
// "Pre-flight: git <✓|–> · python3 <✓|–> · agent: <label>". Each probe is a PATH lookup;
|
|
162
|
+
// a failure reads as absent. Never throws.
|
|
163
|
+
function readinessLine(env, target, which) {
|
|
164
|
+
env = env || process.env;
|
|
165
|
+
which = which || whichSync;
|
|
166
|
+
const caps = terminalCaps(env, process.stdout);
|
|
167
|
+
const tick = caps.unicode ? "✓" : "+";
|
|
168
|
+
const cross = caps.unicode ? "–" : "-";
|
|
169
|
+
const sep = caps.unicode ? " · " : " | ";
|
|
170
|
+
const have = (cmd) => { try { return !!which(cmd); } catch (_e) { return false; } };
|
|
171
|
+
let label;
|
|
172
|
+
try { label = detectAgentEnriched(env, target, which).label; }
|
|
173
|
+
catch (_e) { label = "your AI agent"; }
|
|
174
|
+
const mark = (ok) => (ok ? tick : cross);
|
|
175
|
+
return "Pre-flight: git " + mark(have("git")) + sep +
|
|
176
|
+
"python3 " + mark(have("python3")) + sep + "agent: " + label;
|
|
177
|
+
}
|
|
178
|
+
|
|
122
179
|
function agentPointerBlock(profile) {
|
|
123
180
|
return (
|
|
124
181
|
GUIDE_BEGIN + "\n" +
|
|
@@ -194,10 +251,73 @@ async function loadClack() {
|
|
|
194
251
|
return import("@clack/prompts");
|
|
195
252
|
}
|
|
196
253
|
|
|
197
|
-
//
|
|
254
|
+
// --- brand + feature showcase (interactive path only; fail-soft) -------------
|
|
255
|
+
// Wordmark + value line + the 7-step Specify->Observe loop, rendered BEFORE the first
|
|
256
|
+
// prompt on the interactive path only — so the non-interactive byte stream is unchanged.
|
|
257
|
+
// The 7 labels are the real ADD phases (grounded in the method, never invented). Fail-soft:
|
|
258
|
+
// any draw error is swallowed so a banner can never abort the install. No color is emitted
|
|
259
|
+
// (default accent: none); the glyphs / tagline / accent are a SWAPPABLE content slot.
|
|
260
|
+
const BRAND_LOOP = ["Specify", "Scenarios", "Contract", "Tests", "Build", "Verify", "Observe"];
|
|
261
|
+
|
|
262
|
+
function terminalCaps(env, stream) {
|
|
263
|
+
const width = Number(env.COLUMNS) || (stream && stream.columns) || 80;
|
|
264
|
+
const enc = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
|
|
265
|
+
const unicode = /utf-?8/i.test(enc) && !env.ADD_INSTALLER_ASCII;
|
|
266
|
+
return { width: width, unicode: unicode };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function brandLines(caps) {
|
|
270
|
+
const head = (caps.unicode && caps.width >= 40)
|
|
271
|
+
? [
|
|
272
|
+
" █████╗ ██████╗ ██████╗",
|
|
273
|
+
"██╔══██╗██╔══██╗██╔══██╗",
|
|
274
|
+
"███████║██║ ██║██║ ██║",
|
|
275
|
+
"██╔══██║██║ ██║██║ ██║",
|
|
276
|
+
"██║ ██║██████╔╝██████╔╝",
|
|
277
|
+
"╚═╝ ╚═╝╚═════╝ ╚═════╝ ",
|
|
278
|
+
]
|
|
279
|
+
: ["ADD"]; // plain-ASCII wordmark fallback
|
|
280
|
+
const arrow = caps.unicode ? " → " : " -> ";
|
|
281
|
+
const dash = caps.unicode ? " — " : " - ";
|
|
282
|
+
return head.concat([
|
|
283
|
+
"AI-Driven Development",
|
|
284
|
+
"",
|
|
285
|
+
"Spec-and-tests-first development" + dash + "any agent, through the CLI, no lost context.",
|
|
286
|
+
"The loop ADD drives with you:",
|
|
287
|
+
" " + BRAND_LOOP.join(arrow),
|
|
288
|
+
"",
|
|
289
|
+
]);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function renderBrand(env, stream) {
|
|
293
|
+
try {
|
|
294
|
+
env = env || process.env;
|
|
295
|
+
stream = stream || process.stdout;
|
|
296
|
+
stream.write(brandLines(terminalCaps(env, stream)).join("\n") + "\n");
|
|
297
|
+
} catch (_e) { /* fail-soft: a banner must never abort the install */ }
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// The two install-scope choices — global-first (recommended) vs self-contained. PURE +
|
|
301
|
+
// exported (the pip _scope_options twin) so the recommended pick + its why are hermetically
|
|
302
|
+
// testable; the interactive scope SELECT renders these.
|
|
303
|
+
function scopeOptions() {
|
|
304
|
+
return [
|
|
305
|
+
{ value: "global", label: "Global home + this project",
|
|
306
|
+
hint: "a shared ~/.add + ~/.claude/skills/add reused by every project (this project still gets its own copy)",
|
|
307
|
+
recommended: true },
|
|
308
|
+
{ value: "project", label: "This project only",
|
|
309
|
+
hint: "self-contained + git-tracked: nothing is written outside this folder" },
|
|
310
|
+
];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Returns { cancelled, target, profile, global }. A cancel happens BEFORE any file is written, so a
|
|
198
314
|
// cancelled run leaves the target untouched. Without a real TTY to read (the forced
|
|
199
|
-
// test seam), we cannot prompt — abort safely rather than hang.
|
|
200
|
-
|
|
315
|
+
// test seam), we cannot prompt — abort safely rather than hang. `askScope` is false when an
|
|
316
|
+
// explicit --global already chose the scope (honored, not re-asked).
|
|
317
|
+
async function runClackPreamble(clack, target, detected, askScope) {
|
|
318
|
+
renderBrand(process.env, process.stdout); // brand + showcase BEFORE the first prompt
|
|
319
|
+
try { log(readinessLine(process.env, target)); } // pre-flight: git · python3 · agent (fail-soft)
|
|
320
|
+
catch (_e) { /* the pre-flight line is informational — never block the install */ }
|
|
201
321
|
clack.intro("ADD — AI-Driven Development");
|
|
202
322
|
if (!process.stdin.isTTY) return { cancelled: true, target: target };
|
|
203
323
|
const chosen = await clack.text({
|
|
@@ -207,6 +327,19 @@ async function runClackPreamble(clack, target, detected) {
|
|
|
207
327
|
if (clack.isCancel(chosen)) return { cancelled: true, target: target };
|
|
208
328
|
const ok = await clack.confirm({ message: "Write the ADD skill + tooling + book here?" });
|
|
209
329
|
if (clack.isCancel(ok) || !ok) return { cancelled: true, target: target };
|
|
330
|
+
// global-first SCOPE step (after the target confirm, before agent-detect) — recommended
|
|
331
|
+
// global home, explicit pick; skipped when --global already chose. global stays ADDITIVE.
|
|
332
|
+
let scopeGlobal = false;
|
|
333
|
+
if (askScope) {
|
|
334
|
+
const opts = scopeOptions();
|
|
335
|
+
const scope = await clack.select({
|
|
336
|
+
message: "Install scope?",
|
|
337
|
+
options: opts.map((o) => ({ value: o.value, label: o.label, hint: o.hint })),
|
|
338
|
+
initialValue: opts.find((o) => o.recommended).value,
|
|
339
|
+
});
|
|
340
|
+
if (clack.isCancel(scope)) return { cancelled: true, target: target };
|
|
341
|
+
scopeGlobal = scope === "global";
|
|
342
|
+
}
|
|
210
343
|
// agent-detect STEP (seeded delta: a STEP in THIS flow, via the clack ui layer) — the
|
|
211
344
|
// user confirms or overrides the detected agent before any file is written.
|
|
212
345
|
const picked = await clack.select({
|
|
@@ -216,13 +349,37 @@ async function runClackPreamble(clack, target, detected) {
|
|
|
216
349
|
});
|
|
217
350
|
if (clack.isCancel(picked)) return { cancelled: true, target: target };
|
|
218
351
|
const profile = AGENT_PROFILES.find((p) => p.id === picked) || detected;
|
|
219
|
-
|
|
352
|
+
// LAST optional step — a one-line build intent for `/add` to read. Fully optional: a clack
|
|
353
|
+
// cancel or an empty answer SKIPS (intent ""); the install has already been confirmed, so this
|
|
354
|
+
// never aborts. A NOTE only — it never triggers init.
|
|
355
|
+
let intent = "";
|
|
356
|
+
const typed = await clack.text({
|
|
357
|
+
message: "What do you want to build first? (optional — Enter to skip)",
|
|
358
|
+
placeholder: "", defaultValue: "",
|
|
359
|
+
});
|
|
360
|
+
if (!clack.isCancel(typed) && typed) intent = String(typed).trim();
|
|
361
|
+
return { cancelled: false, target: String(chosen || target), profile: profile, global: scopeGlobal, intent: intent };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Persist `intent` as a NOTE at <target>/.add/.intent for `/add` to read — iff non-empty.
|
|
365
|
+
// DEFERRED-INIT: inert text only; never runs add.py/init, never touches state.json. Fail-soft
|
|
366
|
+
// (a write error is swallowed — the note is best-effort, never a reason to fail the install).
|
|
367
|
+
// Returns whether the note was written. Twin of _installer.py:_write_intent_note.
|
|
368
|
+
function writeIntentNote(target, intent) {
|
|
369
|
+
const text = (intent || "").trim();
|
|
370
|
+
if (!text) return false;
|
|
371
|
+
try {
|
|
372
|
+
const addDir = path.join(target, ".add");
|
|
373
|
+
fs.mkdirSync(addDir, { recursive: true }); // .add/ exists post-drop; recursive mkdir is a no-op then
|
|
374
|
+
fs.writeFileSync(path.join(addDir, ".intent"), text + "\n");
|
|
375
|
+
return true;
|
|
376
|
+
} catch (_e) { return false; }
|
|
220
377
|
}
|
|
221
378
|
|
|
222
379
|
// The drop — now a RECONCILE: restore missing managed trees + refresh present ones
|
|
223
380
|
// (sweep orphans) + report per-tree status. Byte-compatible handoff with the prior
|
|
224
381
|
// installer. The interactive path resolves a target then calls straight into this.
|
|
225
|
-
function dropFiles(args, target, profile) {
|
|
382
|
+
function dropFiles(args, target, profile, intent) {
|
|
226
383
|
profile = profile || detectAgent(process.env);
|
|
227
384
|
log("Installing ADD into " + target);
|
|
228
385
|
reconcile(args, target);
|
|
@@ -232,6 +389,9 @@ function dropFiles(args, target, profile) {
|
|
|
232
389
|
// Best-effort + fail-soft — never aborts the successful drop above.
|
|
233
390
|
writeAgentPointer(target, profile);
|
|
234
391
|
|
|
392
|
+
// Optional build-intent NOTE for `/add` to read — "" (skip / non-interactive) -> no-op.
|
|
393
|
+
writeIntentNote(target, intent);
|
|
394
|
+
|
|
235
395
|
// NO step 4: the installer DROPS FILES ONLY. Initialisation is deferred to the AI
|
|
236
396
|
// (via `/add`) or a CLI user — a pre-run plain `add.py init` would grandfather-lock
|
|
237
397
|
// the v12 lock-down gate before `/add` runs (see file header). So no Python is run here.
|
|
@@ -255,12 +415,18 @@ async function cmdInit(args) {
|
|
|
255
415
|
|
|
256
416
|
let chosenTarget = target;
|
|
257
417
|
let profile = detectAgent(process.env); // default: non-interactive / fallback
|
|
418
|
+
let intent = ""; // build-intent NOTE — stays "" on the non-interactive path
|
|
258
419
|
if (interactive(args)) {
|
|
259
420
|
let clack = null;
|
|
260
421
|
try { clack = await loadClack(); }
|
|
261
422
|
catch (_e) { warn("clack unavailable — falling back to plain-text install"); }
|
|
262
423
|
if (clack) {
|
|
263
|
-
|
|
424
|
+
// enriched seed (env > CLAUDE.md > installed CLI) for the agent-select default; the user
|
|
425
|
+
// still confirms/overrides before any write. The non-interactive write below stays env-only.
|
|
426
|
+
const detected = detectAgentEnriched(process.env, target);
|
|
427
|
+
// an explicit --global/--global-data already chose the scope — don't re-ask it.
|
|
428
|
+
const askScope = !(args.global || args.globalData);
|
|
429
|
+
const outcome = await runClackPreamble(clack, target, detected, askScope);
|
|
264
430
|
if (outcome.cancelled) {
|
|
265
431
|
// the exit code IS the contract; a closed-pipe stdout (EPIPE) must not
|
|
266
432
|
// mask the cancel — guard the courtesy message, never let it throw.
|
|
@@ -271,13 +437,15 @@ async function cmdInit(args) {
|
|
|
271
437
|
chosenTarget = path.resolve(outcome.target);
|
|
272
438
|
if (!fs.existsSync(chosenTarget)) fail("target directory does not exist: " + chosenTarget);
|
|
273
439
|
if (outcome.profile) profile = outcome.profile; // honor the user's override
|
|
440
|
+
if (outcome.global) args.global = true; // honor the interactive scope pick (additive)
|
|
441
|
+
intent = outcome.intent || ""; // optional build-intent NOTE (written after the drop)
|
|
274
442
|
}
|
|
275
443
|
}
|
|
276
444
|
if (args.globalData) args.global = true; // --global-data implies --global (need a home)
|
|
277
445
|
// OPT-IN global home, BEFORE the per-project drop (fail-closed if the home is unwritable
|
|
278
446
|
// or its registry is corrupt — the package + the self-contained default stay usable).
|
|
279
447
|
if (args.global) installGlobal(args, chosenTarget);
|
|
280
|
-
dropFiles(args, chosenTarget, profile);
|
|
448
|
+
dropFiles(args, chosenTarget, profile, intent);
|
|
281
449
|
// OPT-IN data persist, AFTER the drop (one-way snapshot of existing user-data).
|
|
282
450
|
if (args.globalData) installGlobalData(chosenTarget);
|
|
283
451
|
}
|
|
@@ -597,4 +765,18 @@ async function main() {
|
|
|
597
765
|
}
|
|
598
766
|
}
|
|
599
767
|
|
|
600
|
-
|
|
768
|
+
// Run ONLY when invoked directly (the bin / npx entry). When `require()`d — the test harness
|
|
769
|
+
// imports the pure detectors — main() must NOT fire (it would parse argv + install). This guard
|
|
770
|
+
// changes no runtime behavior on the real CLI path; the non-interactive output stays byte-identical.
|
|
771
|
+
if (require.main === module) {
|
|
772
|
+
main().catch((e) => fail(e && e.message ? e.message : String(e)));
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
module.exports = {
|
|
776
|
+
detectAgent: detectAgent,
|
|
777
|
+
detectAgentEnriched: detectAgentEnriched,
|
|
778
|
+
readinessLine: readinessLine,
|
|
779
|
+
whichSync: whichSync,
|
|
780
|
+
scopeOptions: scopeOptions,
|
|
781
|
+
writeIntentNote: writeIntentNote,
|
|
782
|
+
};
|
package/docs/08-step-6-verify.md
CHANGED
|
@@ -25,9 +25,12 @@ Verify can be resolved two ways, set per task by the `autonomy:` header (see [go
|
|
|
25
25
|
|
|
26
26
|
## Part one — confirm the evidence
|
|
27
27
|
|
|
28
|
+
Before the build, the task pre-declares its **build expectations** — the observable outcomes a correct build must produce, read off §2's scenarios and §3's contract. Confirm each one here against evidence you can see, not a restated test name: this is what makes the gate check the build is *right*, not merely that the suite is green.
|
|
29
|
+
|
|
28
30
|
- [ ] All tests pass.
|
|
29
31
|
- [ ] Coverage did not decrease.
|
|
30
32
|
- [ ] No test or contract was altered during the build.
|
|
33
|
+
- [ ] Every pre-declared build expectation is confirmed by real evidence (not merely a green test).
|
|
31
34
|
|
|
32
35
|
If any of these is false, stop here and return to the build; there is nothing to verify yet.
|
|
33
36
|
|
|
@@ -71,6 +74,7 @@ A security finding is always a `HARD-STOP`; it is never waved through with a wai
|
|
|
71
74
|
## The verification checklist
|
|
72
75
|
|
|
73
76
|
- [ ] All tests pass (the evidence).
|
|
77
|
+
- [ ] Every pre-declared build expectation is confirmed by observable evidence.
|
|
74
78
|
- [ ] Concurrency/timing of the risky operation is safe.
|
|
75
79
|
- [ ] No exposed secrets, injection openings, or unexpected dependencies.
|
|
76
80
|
- [ ] Layering and dependencies follow `CONVENTIONS.md`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pilotspace/add",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "ADD (AI-Driven Development) — a minimal, state-tracked Claude Code skill that drives every feature through Specify → Scenarios → Contract → Tests → Build → Verify → Observe. Ships the AIDD book as its trust layer.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"add": "bin/cli.js"
|
package/skill/add/SKILL.md
CHANGED
|
@@ -63,7 +63,9 @@ python3 .add/tooling/add.py status
|
|
|
63
63
|
(the `soul-self-improve` path proposes voice deltas the human confirms).
|
|
64
64
|
|
|
65
65
|
- **No `.add/state.json` yet** (a fresh install drops tooling + docs but does *not* init — so `status` says
|
|
66
|
-
`no .add/ project found`) → enter **autonomous setup**:
|
|
66
|
+
`no .add/ project found`) → enter **autonomous setup**: first, if `.add/.intent` exists, read it — the
|
|
67
|
+
one-line first-build intent the user gave the installer (a NOTE, never an init trigger) — and let it seed
|
|
68
|
+
your kickoff suggestion (`phases/0-setup.md`); then YOU run init yourself —
|
|
67
69
|
`add.py init --name "<inferred>" --stage <picked> --await-lock` (don't tell the human to) — then read
|
|
68
70
|
`phases/0-setup.md` and draft the foundation + first scope + first contract through to the human baseline approval.
|
|
69
71
|
- **A task is active** → open `.add/tasks/<active>/TASK.md`, look at its `phase:`
|
|
@@ -110,7 +112,7 @@ See `run.md`.
|
|
|
110
112
|
|
|
111
113
|
Whenever you present a decision point to the human in chat (intake · bundle approval · gate ·
|
|
112
114
|
milestone close), follow `report-template.md` — open with the ARC (goal · done · plan,
|
|
113
|
-
engine-sourced), then SUMMARY → DECISION →
|
|
115
|
+
engine-sourced), then SUMMARY → DECISION → FLAGS → DECIDED → EVIDENCE → NEXT, show-before-ask, never
|
|
114
116
|
pre-stamp a decision point — and the question is a summary, never the artifact.
|
|
115
117
|
|
|
116
118
|
In **observe**, also emit **lessons learned** — learnings tagged by which of the five
|
package/skill/add/intake.md
CHANGED
|
@@ -31,6 +31,11 @@ First ask "does this change already-frozen scope?" → if yes, it is a `change-r
|
|
|
31
31
|
→ `new-major`; a slice of a live theme → `sub-milestone`; fits the active milestone
|
|
32
32
|
→ `task`.
|
|
33
33
|
|
|
34
|
+
**One-task gap rule.** If the request is ONE task but does NOT fit the active milestone's
|
|
35
|
+
stated scope, do NOT force it into `sub-milestone` (which requires "too big for one task").
|
|
36
|
+
Instead: create a new micro-milestone to house it (`new-milestone` + `new-task`). The
|
|
37
|
+
micro-milestone gives the task ledger attribution and clear exit criteria without inflating scope.
|
|
38
|
+
|
|
34
39
|
## What you emit (the proposal)
|
|
35
40
|
|
|
36
41
|
Present the proposal to the human via `report-template.md` — open with the ARC (goal · done ·
|
|
@@ -12,11 +12,20 @@ sufficient. Fill **§6** in TASK.md including the GATE RECORD.
|
|
|
12
12
|
> or whenever residue is found, this phase is **human-led** and the checks below
|
|
13
13
|
> are the human's.
|
|
14
14
|
|
|
15
|
+
## Before you build — declare the build expectations
|
|
16
|
+
|
|
17
|
+
Fill the §6 **Build expectations** block BEFORE you start Build: the OBSERVABLE outcomes a
|
|
18
|
+
correct build must produce, derived from §2 SCENARIOS + §3 CONTRACT. At this gate, confirm each
|
|
19
|
+
one against real evidence (the `confirmed by` column) — so verify proves the build is *correct*,
|
|
20
|
+
not merely that the suite is green. An expectation with no evidence is not yet verified; passing
|
|
21
|
+
tests on inputs you thought of never substitute for the outcome you promised.
|
|
22
|
+
|
|
15
23
|
## Part one — confirm the evidence
|
|
16
24
|
|
|
17
25
|
- [ ] All tests pass.
|
|
18
26
|
- [ ] Coverage did not decrease.
|
|
19
27
|
- [ ] No test or contract was altered during build.
|
|
28
|
+
- [ ] Every §6 Build expectation is confirmed by real evidence (not just a green test).
|
|
20
29
|
|
|
21
30
|
If any is false, stop and return to Build — there is nothing to verify yet.
|
|
22
31
|
|
|
@@ -8,12 +8,12 @@ replacement for the digest.
|
|
|
8
8
|
Use it every time you report at or near a decision point: an intake proposal, a
|
|
9
9
|
bundle approval, a verify gate, a task completion, a milestone close.
|
|
10
10
|
|
|
11
|
-
## The decision arc — rendered first, above the
|
|
11
|
+
## The decision arc — rendered first, above the report blocks
|
|
12
12
|
|
|
13
13
|
Every report at a human gate opens with the **ARC** — three labelled lines that
|
|
14
14
|
place the decision in the work's whole arc, so the human confirms with sight of
|
|
15
15
|
where this is going, not just the step in front of them. Render it first, then a
|
|
16
|
-
separator, then the
|
|
16
|
+
separator, then the report blocks below:
|
|
17
17
|
|
|
18
18
|
```
|
|
19
19
|
ARC goal: <the milestone / project goal this decision serves>
|
|
@@ -48,14 +48,19 @@ and `add.py` output disagree, the engine wins — fix the arc, not the engine.
|
|
|
48
48
|
- **intake** — `goal:` the sized request · `done:` classified new-major,
|
|
49
49
|
rationale stated · `plan:` create the milestone → first contract → goal.
|
|
50
50
|
|
|
51
|
-
## The
|
|
51
|
+
## The report blocks, in order
|
|
52
|
+
|
|
53
|
+
The blocks below are the CORE set, rendered in order at every decision-point
|
|
54
|
+
report. Render every one (write "none" rather than dropping a block); add MORE
|
|
55
|
+
blocks when a specific report needs them (see "Beyond the core blocks" below).
|
|
52
56
|
|
|
53
57
|
```
|
|
54
|
-
SUMMARY one line: intent + target + where we are
|
|
55
|
-
DECISION what you need from the human (or "none — FYI")
|
|
56
|
-
|
|
58
|
+
SUMMARY one line: intent + target + where we are + what we done
|
|
59
|
+
DECISION what you need from the human (or "none — FYI") — exactly one
|
|
60
|
+
FLAGS lowest-confidence first: why + cost-if-wrong
|
|
61
|
+
DECIDED highest-confidence first: the autonomous calls you made + why each was safe
|
|
57
62
|
EVIDENCE small table: tests · gates · parity · check — engine-sourced
|
|
58
|
-
NEXT the
|
|
63
|
+
NEXT the recommended next actions, ranked (top ▶ highlighted, bolded) + what each unlocks
|
|
59
64
|
```
|
|
60
65
|
|
|
61
66
|
1. **SUMMARY** — one line carrying intent + target + position, e.g.
|
|
@@ -67,16 +72,35 @@ NEXT the single next action + what it unlocks
|
|
|
67
72
|
description (see "guided choice" below). Exactly one decision per report, or an
|
|
68
73
|
explicit "none — FYI". If a decision exists, ask it after everything below has
|
|
69
74
|
been shown (show-before-ask).
|
|
70
|
-
3.
|
|
75
|
+
3. **FLAGS** — lowest-confidence first, each with *why* confidence is lowest and the
|
|
71
76
|
*cost if wrong*. Where TASK.md markers exist (`⚠` / `- [~]` / `- [ ]`),
|
|
72
77
|
quote them verbatim and keep their document order — extraction ≠ judgment.
|
|
73
|
-
4. **
|
|
78
|
+
4. **DECIDED** — the counterpart to FLAGS: the high-confidence calls you settled
|
|
79
|
+
autonomously WITHOUT a gate, highest-confidence first, each with *why* it was
|
|
80
|
+
safe (the change-type or evidence that earned the autonomy). FLAGS surfaces the
|
|
81
|
+
lowest-confidence calls so the human can challenge them; DECIDED surfaces the
|
|
82
|
+
highest-confidence ones so the human can see what you closed without asking. "none" when you made no
|
|
83
|
+
autonomous calls. NEVER list a security / residue / lowered-autonomy call here —
|
|
84
|
+
those always escalate in DECISION (see the Hard rules), never auto-decided.
|
|
85
|
+
5. **EVIDENCE** — engine-sourced facts pasted from `add.py` output, never
|
|
74
86
|
re-typed from memory. If your prose and the engine disagree, the engine
|
|
75
87
|
wins: fix the engine or the data, not the sentence.
|
|
76
|
-
|
|
77
|
-
|
|
88
|
+
6. **NEXT** — the recommended next actions as a ranked list, the top one marked
|
|
89
|
+
`▶` and each with what it unlocks. NEXT is **informational, not a second gate**:
|
|
90
|
+
it shows the menu of what's next and never adds a decision — the one decision
|
|
91
|
+
stays the DECISION block. Mirror the rollup's DECIDE NEXT line for the top
|
|
92
|
+
action when it is right; overrule it only with a stated reason (e.g. planned
|
|
78
93
|
tasks the state file cannot see yet).
|
|
79
94
|
|
|
95
|
+
### Beyond the core blocks — add more when the decision needs it
|
|
96
|
+
|
|
97
|
+
The six blocks above are the CORE set. When a specific report needs more than they
|
|
98
|
+
carry — a `RISK` ledger, a `DIFF`, a `SCOPE` map, a `COST` estimate — add an extra
|
|
99
|
+
block in the same shape (SCREAMING-CASE label · one-line intent · engine-sourced
|
|
100
|
+
where it can be), placed AFTER EVIDENCE and BEFORE NEXT so NEXT always closes the
|
|
101
|
+
report. Add a block only when it carries what the core six don't; never pad to look
|
|
102
|
+
thorough — and never drop a core block; render "none" instead.
|
|
103
|
+
|
|
80
104
|
### The DECISION block as a guided choice
|
|
81
105
|
|
|
82
106
|
When the human must choose, render block 2 as a **guided decision** — never a bare next step:
|
|
@@ -120,7 +144,7 @@ intent + what "yes" means + the flag count — pointing at the report above.
|
|
|
120
144
|
**recommended pick** (`▶ … (recommended)`, exactly one) + its 1–3 described alternatives, every
|
|
121
145
|
option carrying a one-line description; never a bare next step. `confidence.md` informs the pick;
|
|
122
146
|
the human overrides freely. Not at `[you drive]` autonomous steps.
|
|
123
|
-
- **Reconcile the count.** Before the ask, your
|
|
147
|
+
- **Reconcile the count.** Before the ask, your FLAGS must reconcile with
|
|
124
148
|
`add.py report --decide`'s open-item count. If your prose calls an item
|
|
125
149
|
resolved while the digest still counts it open, the engine wins — fix the data
|
|
126
150
|
(the TASK.md markers the digest reads), not the sentence. A report whose flag
|
|
@@ -133,10 +157,14 @@ intent + what "yes" means + the flag count — pointing at the report above.
|
|
|
133
157
|
- **Honest scope.** "Done" means the request, not the last task: report
|
|
134
158
|
"task 2/3", never "done" while approved scope remains.
|
|
135
159
|
- **The question is a summary, never the artifact.** Every approval ask carries
|
|
136
|
-
two layers: a compact SUMMARY · DECISION ·
|
|
160
|
+
two layers: a compact SUMMARY · DECISION · FLAGS block sits in chat
|
|
137
161
|
immediately before the ask (positional), and the question text itself is a
|
|
138
162
|
summary of two lines at most — intent + what "yes" means + the flag count —
|
|
139
163
|
pointing at the report above (compositional). The full bundle, diff, or
|
|
140
164
|
artifact lives only in the chat report; a question that re-carries it buries
|
|
141
165
|
the decision.
|
|
166
|
+
- **NEXT is not a second gate.** NEXT lists recommended next actions (ranked, top
|
|
167
|
+
`▶`); the single decision stays the DECISION block — NEXT never carries an approval ask.
|
|
168
|
+
- **DECIDED never holds a gate-class call.** A security / residue / lowered-autonomy
|
|
169
|
+
call is escalated in DECISION, never reported as already auto-decided in DECIDED.
|
|
142
170
|
</constraints>
|
package/skill/add/scope.md
CHANGED
|
@@ -20,6 +20,25 @@ scope drafting honors intake's classification — it never re-sizes a request:
|
|
|
20
20
|
means one drafting pass, NOT auto-creation. Nothing is written to disk — single draft or the
|
|
21
21
|
whole batch — until the human confirms. You propose; you wait.
|
|
22
22
|
|
|
23
|
+
## Position the goal — ground in assets, relate to the milestone map (do this FIRST)
|
|
24
|
+
|
|
25
|
+
Before you draft the goal sentence — before Diverge below — position the request in what
|
|
26
|
+
already exists. You cannot write a correct goal without knowing where it sits.
|
|
27
|
+
|
|
28
|
+
1. **Ground in current assets.** Read the goal input against `PROJECT.md` (domain · spec ·
|
|
29
|
+
UI/UX), the code it touches, and the existing docs — so the goal is grounded in what the
|
|
30
|
+
project already is, not in what you assume.
|
|
31
|
+
2. **Relate to the milestone map.** Read every existing goal — `.add/milestones/*/MILESTONE.md`
|
|
32
|
+
and `.add/archive/*` — and name THIS request's relationship to them: *extends* theme X ·
|
|
33
|
+
*depends-on* Y · *overlaps* Z. Record that relationship in the `rationale` line you will write.
|
|
34
|
+
3. **If the goal is already delivered** by an existing milestone, do NOT fork it — reject
|
|
35
|
+
`duplicate_goal` and route the request as a `task` or `change-request` (create nothing).
|
|
36
|
+
|
|
37
|
+
This COMPLEMENTS `intake.md`'s bucket test (which weighs only whether a live milestone's goal
|
|
38
|
+
covers the request); positioning also grounds the goal in current assets and relates it to the
|
|
39
|
+
prior milestone map (`.add/milestones/*` + `.add/archive/*`) — which classification does not do.
|
|
40
|
+
Point at intake for the bucket; do not restate it here.
|
|
41
|
+
|
|
23
42
|
## Brainstorm before you draft — co-specify at milestone level
|
|
24
43
|
|
|
25
44
|
Don't draft a MILESTONE.md from thin input. Run the same three-move co-specify as a
|
|
@@ -45,6 +64,8 @@ Render the draft as a guided choice — the recommended scope + its described al
|
|
|
45
64
|
|
|
46
65
|
- **goal** — ONE sentence, an outcome not an output ("a user can size any request", not "write
|
|
47
66
|
intake.md"). If it needs an "and", it is probably two milestones.
|
|
67
|
+
- **rationale** — the confirmed intake bucket + WHY (the theme · slice · fit), AND the milestone
|
|
68
|
+
relationship you captured in "Position the goal" above. One or two header lines; never in state.json.
|
|
48
69
|
- **Scope In/Out** — the explicit anti-creep deferral list. Naming what is OUT is as important
|
|
49
70
|
as what is IN; an empty Out list usually means the scope is not yet thought through.
|
|
50
71
|
- **Shared decisions & glossary deltas** — cross-cutting rules every task must honor, named from
|
|
@@ -54,6 +75,22 @@ Render the draft as a guided choice — the recommended scope + its described al
|
|
|
54
75
|
by phase; keep each task one-file-sized. Order by dependency, not by guesswork.
|
|
55
76
|
- **Exit criteria** — observable, and **every exit criterion maps to a declared task slug**
|
|
56
77
|
(no dangling criterion). Each line answers "which task delivers this, and how would we see it?"
|
|
78
|
+
- **Close — ship review** + **Release steps** — leave these as the template at draft time: they are
|
|
79
|
+
**drafted-blank** here and filled LATER (Close at `milestone-done`, the close flow; Release steps when
|
|
80
|
+
the ship path is known, owned by `release.md`) — not scope drafting. Named here so you know the full
|
|
81
|
+
9-section shape and neither fill them early nor read a fresh draft as "incomplete".
|
|
82
|
+
|
|
83
|
+
## Draft well-formedness gate (a draft passes ALL of these before you propose it)
|
|
84
|
+
|
|
85
|
+
A scope draft is well-formed — ready to show the human — only when:
|
|
86
|
+
- [ ] the goal is ONE outcome sentence (no "and" — that is two milestones)
|
|
87
|
+
- [ ] every exit criterion maps to a declared task slug (no dangling criterion)
|
|
88
|
+
- [ ] `rationale` records the bucket + the milestone relationship from "Position the goal"
|
|
89
|
+
- [ ] `Close — ship review` and `Release steps` are left as the template (drafted-blank)
|
|
90
|
+
- [ ] the In/Out list names what is deferred (an empty Out list is unfinished thinking)
|
|
91
|
+
|
|
92
|
+
Propose only a well-formed draft — an incomplete one is the gap that lets a milestone reach
|
|
93
|
+
task breakdown half-formed.
|
|
57
94
|
|
|
58
95
|
## Reject codes (emit `{ reject, rationale }`, create nothing)
|
|
59
96
|
|
|
@@ -65,6 +102,9 @@ Render the draft as a guided choice — the recommended scope + its described al
|
|
|
65
102
|
a malformed milestone. With no engine lint, you are the first check and the human is the backstop.
|
|
66
103
|
- `no_milestone` — intake routed the request to `task` or `change-request`; scope drafting
|
|
67
104
|
creates NO milestone. Honor the classification; do not invent milestone-sized scope.
|
|
105
|
+
- `duplicate_goal` — the goal is already delivered by an existing milestone (live or in
|
|
106
|
+
`.add/archive/`). Do NOT fork it into a parallel milestone; route the request as a `task` or
|
|
107
|
+
`change-request` and create nothing. (Raised by the "Position the goal" step.)
|
|
68
108
|
</reject_codes>
|
|
69
109
|
|
|
70
110
|
## Worked example (from this repo's own history)
|
|
@@ -133,6 +133,13 @@ Constraints: do NOT change any test or the contract; allow-list packages only; a
|
|
|
133
133
|
- [ ] layering & dependencies follow CONVENTIONS.md
|
|
134
134
|
- [ ] a person reviewed and approved the change
|
|
135
135
|
|
|
136
|
+
### Build expectations — what "correct" looks like (fill BEFORE build; confirm each at the gate)
|
|
137
|
+
> Pre-declare the OBSERVABLE outcomes a correct build must produce — derived from §2 SCENARIOS
|
|
138
|
+
> + §3 CONTRACT — so this gate checks the build is RIGHT, not merely that tests are green. Each
|
|
139
|
+
> row is evidence you can SEE, not a restatement of a test name.
|
|
140
|
+
- [ ] <observable outcome a correct build must produce> — confirmed by <how / where>
|
|
141
|
+
- [ ] <another observable outcome> — confirmed by <evidence seen>
|
|
142
|
+
|
|
136
143
|
### Deep checks — do not skim (fill the path that applies; the resolver judges which)
|
|
137
144
|
- [ ] WIRING (code) — every new symbol is referenced; record where / how confirmed
|
|
138
145
|
- [ ] DEAD-CODE (code) — no new unused or orphaned symbol introduced
|