@pilotspace/add 2.3.0 → 2.4.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/CHANGELOG.md +44 -0
- package/GETTING-STARTED.md +19 -19
- package/agents/add-advisor.md +3 -2
- package/bin/cli.js +43 -12
- package/package.json +1 -1
- package/skill/add/SKILL.md +2 -1
- package/skill/add/beyond.md +4 -0
- package/skill/add/intake.md +17 -0
- package/skill/add/strategy.md +54 -0
- package/tooling/add_engine/constants.py +1 -1
- package/tooling/templates/GLOSSARY.md.tmpl +1 -1
- package/tooling/templates/MILESTONE.md.tmpl +1 -1
- package/tooling/templates/PLAN.md.tmpl +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,50 @@ 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
|
+
## [2.4.0] — 2026-07-24
|
|
8
|
+
|
|
9
|
+
Minor: the **strategy-intake** milestone closes — a fitting **persona** becomes ADD's
|
|
10
|
+
adaptive project-management brain — plus the follow-on cleanup that completes the 2.3.0
|
|
11
|
+
**scenarios fold** and adds a CI sweep of every shipped surface. The method changes are all
|
|
12
|
+
skill/agent-surface prose with **zero `add.py` engine change** (the engine records the
|
|
13
|
+
`## Strategy` slot and the gate, never drives the loop or gates on the strategy); this
|
|
14
|
+
release also carries one **concurrency fix** to the installer's lock (see *Fixed*).
|
|
15
|
+
|
|
16
|
+
### Persona-as-PM strategy loop — `strategy.md` (new guide)
|
|
17
|
+
- **DISCUSS → OPTIMIZE → CONVERGE** — a new `strategy.md` guide drives a persona-framed
|
|
18
|
+
loop that fills a milestone's `## Strategy` slot with a sequenced, optimized task DAG,
|
|
19
|
+
converging on the *existing* six-dimension confidence self-score (no new bar invented).
|
|
20
|
+
- **`add-advisor` refute at CONVERGE** — a high-uncertainty milestone spawns the advisor
|
|
21
|
+
in refute mode to break the strategy before it's recorded; advisory (it cannot block),
|
|
22
|
+
reusing the existing refute mode. The advisor's `direction` beat now names a milestone
|
|
23
|
+
strategy as a refutable artifact alongside a task bundle.
|
|
24
|
+
- **risk-proportional depth ladder** — one legible rule: micro/`--tiny` skips the loop at
|
|
25
|
+
zero added per-turn cost · multi-task low-uncertainty runs the loop · high-uncertainty
|
|
26
|
+
adds the refute. It is the skill's judgment, never an engine gate; strategy stays SOFT
|
|
27
|
+
and security stays HARD-STOP.
|
|
28
|
+
- **persona-at-intake** — `intake.md` now loads the **fitting persona** before it sizes a
|
|
29
|
+
request (match-else-seed, advisory), so the persona that owns the intake report also
|
|
30
|
+
shapes the sizing.
|
|
31
|
+
|
|
32
|
+
### Fold completion + shipped-surface sweep
|
|
33
|
+
- the **scenarios fold** finishes — four remaining shipped residuals of the §2→§4 retirement
|
|
34
|
+
are fixed, each defect class pinned by a new guard.
|
|
35
|
+
- a **shipped-surface** CI sweep derives the published file set from the packaging manifests
|
|
36
|
+
and fails on any dead chapter/section reference across every shipped surface.
|
|
37
|
+
- CI/deps: `actions/setup-python` 6→7, `actions/setup-node` 6→7, `@clack/prompts` 1.6.0→1.7.0.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- **stale-lock reclaim could unlink a LIVE lock** (mutual-exclusion violation). The reclaim
|
|
41
|
+
path gated its unlink on inode identity alone, assuming a fresh replacement lock always
|
|
42
|
+
gets a new inode. Linux (ext4/tmpfs) **reuses freed inode numbers**, so a delayed racer
|
|
43
|
+
could match the crashed generation's inode against a *live* holder's replacement lock and
|
|
44
|
+
delete it — putting two processes inside the critical section at once. Reclaim now requires
|
|
45
|
+
the file to be **both** the observed inode **and** still stale, so a live or
|
|
46
|
+
heartbeat-refreshed lock is never mistaken for a dead one. Fixed at all four reclaim sites
|
|
47
|
+
in `_installer.py` and mirrored into the `bin/cli.js` npm twin, which carried the identical
|
|
48
|
+
guard. `O_EXCL`/`wx` remains the sole mutual-exclusion primitive and a genuinely crashed
|
|
49
|
+
lock still self-heals. Surfaced as an intermittent `peak=2` on the Linux CI runner only.
|
|
50
|
+
|
|
7
51
|
## [2.3.0] — 2026-07-24
|
|
8
52
|
|
|
9
53
|
Minor: three waves — a **signal graph** view over the task DAG, the **§2
|
package/GETTING-STARTED.md
CHANGED
|
@@ -200,7 +200,7 @@ python3 .add/tooling/add.py new-task transfer --title "Transfer money between my
|
|
|
200
200
|
> any build. Plain `init` skips that gate, which is fine here: by hand, the
|
|
201
201
|
> human IS the one driving every step.
|
|
202
202
|
|
|
203
|
-
This scaffolds `.add/tasks/transfer/PLAN.md` — **one file holding all
|
|
203
|
+
This scaffolds `.add/tasks/transfer/PLAN.md` — **one file holding all six phase
|
|
204
204
|
sections** — plus empty `tests/` and `src/` folders, and makes it the active task
|
|
205
205
|
at phase `specify`. Open it in your editor; you'll fill it top to bottom.
|
|
206
206
|
|
|
@@ -234,21 +234,6 @@ Confirm every assumption (no FX, no daily limit in v1). Then:
|
|
|
234
234
|
python3 .add/tooling/add.py advance
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
-
### Phase 2 — Scenarios (https://pilotspace.github.io/ADD/04-step-2-scenarios/)
|
|
238
|
-
|
|
239
|
-
In **§2**, turn each rule into a Given/When/Then. For every rejection, assert what
|
|
240
|
-
must stay **unchanged**:
|
|
241
|
-
|
|
242
|
-
```gherkin
|
|
243
|
-
Scenario: insufficient funds
|
|
244
|
-
Given A has 20, mine
|
|
245
|
-
When I transfer 50 from A to B
|
|
246
|
-
Then it is rejected "insufficient_funds"
|
|
247
|
-
And no balance changes
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
Then `python3 .add/tooling/add.py advance`.
|
|
251
|
-
|
|
252
237
|
### Phase 3 — Contract (https://pilotspace.github.io/ADD/05-step-3-plan/)
|
|
253
238
|
|
|
254
239
|
In **§3**, fix the external shape and **freeze** it (`Status: FROZEN @ v1`):
|
|
@@ -264,9 +249,24 @@ A frozen contract is the decision point that makes the AI build safe. Then advan
|
|
|
264
249
|
|
|
265
250
|
### Phase 4 — Tests, red first (https://pilotspace.github.io/ADD/06-step-4-tests/)
|
|
266
251
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
252
|
+
**§4 · TESTS & SCENARIOS** is where pass/fail cases live — with the tests that
|
|
253
|
+
prove them. Write one test per §1 Must/Reject. For every rejection, also assert
|
|
254
|
+
what must stay **unchanged**.
|
|
255
|
+
|
|
256
|
+
A readable case, for a human who needs one:
|
|
257
|
+
|
|
258
|
+
```gherkin
|
|
259
|
+
Scenario: insufficient funds
|
|
260
|
+
Given A has 20, mine
|
|
261
|
+
When I transfer 50 from A to B
|
|
262
|
+
Then it is rejected "insufficient_funds"
|
|
263
|
+
And no balance changes
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The test plan is the canonical encoding — a Given/When/Then block is for a human
|
|
267
|
+
reader, never ceremony. Write the tests into `.add/tasks/transfer/tests/`, then
|
|
268
|
+
**run them and confirm they FAIL** — there's no code yet. A test that passes now
|
|
269
|
+
is testing nothing. This is red/green TDD: red before green. Then advance.
|
|
270
270
|
|
|
271
271
|
### Phase 5 — Build (https://pilotspace.github.io/ADD/07-step-5-build/)
|
|
272
272
|
|
package/agents/add-advisor.md
CHANGED
|
@@ -25,8 +25,9 @@ beat keeps moving. Personas carry the expertise; you carry independent, first-pr
|
|
|
25
25
|
when uncertain — catch the plausible-but-wrong before the human or the gate does.
|
|
26
26
|
|
|
27
27
|
Every mode serves EVERY beat — the spawn names the beat + mode, and you calibrate to it:
|
|
28
|
-
**direction** (propose the bundle plan · refute the draft
|
|
29
|
-
|
|
28
|
+
**direction** (propose the bundle plan · refute the draft — a task bundle OR a high-uncertainty
|
|
29
|
+
milestone **strategy** — so the human freezes the stronger shape), **build** (decide approach
|
|
30
|
+
forks mid-flight · pressure-test a strategy or a support-
|
|
30
31
|
worker slice partition against the frozen contract), **verify** (refute the earned-green ·
|
|
31
32
|
judge whether the evidence supports the verdict). You never need the beat to be direction
|
|
32
33
|
to be useful; you never need it to be verify to be skeptical.
|
package/bin/cli.js
CHANGED
|
@@ -1209,6 +1209,28 @@ function agedReclaimTickets(dir, lockFileName, staleAfterSeconds, nowMs) {
|
|
|
1209
1209
|
}).sort();
|
|
1210
1210
|
}
|
|
1211
1211
|
|
|
1212
|
+
// True iff `p` is STILL the exact stale generation identified by `observedIno` AND still stale —
|
|
1213
|
+
// the guard that gates a reclaim UNLINK. Mirror of _installer.py:_still_stale_generation.
|
|
1214
|
+
//
|
|
1215
|
+
// Inode NUMBER alone is NOT a stable identity across a delete+recreate: Linux (ext4/tmpfs)
|
|
1216
|
+
// aggressively REUSES freed inode numbers, so a live holder's fresh replacement lock can reuse the
|
|
1217
|
+
// crashed file's inode. A reclaimer that trusts inode identity alone then unlinks a LIVE lock whose
|
|
1218
|
+
// inode merely coincides with the crashed generation it observed — two holders end up inside the
|
|
1219
|
+
// critical section at once (the peak=2 double-hold; reproduced on Linux CI, never on macOS APFS,
|
|
1220
|
+
// which does not reuse inodes in a short window).
|
|
1221
|
+
//
|
|
1222
|
+
// Re-verifying that the CURRENT file is itself still stale (mtime age > `staleAfterSeconds`)
|
|
1223
|
+
// distinguishes a live reused-inode holder (age ~0, or heartbeat-refreshed) from the crashed
|
|
1224
|
+
// generation we meant to reclaim. The "wx" create remains the SOLE mutual-exclusion primitive;
|
|
1225
|
+
// this only prevents a live file being mistaken for a dead one.
|
|
1226
|
+
function stillStaleGeneration(p, observedIno, staleAfterSeconds, nowMs) {
|
|
1227
|
+
const now = nowMs === undefined ? Date.now() : nowMs;
|
|
1228
|
+
let cur = null;
|
|
1229
|
+
try { cur = fs.statSync(p); }
|
|
1230
|
+
catch (_e) { return false; } // vanished — nothing to reclaim
|
|
1231
|
+
return cur.ino === observedIno && (now - cur.mtimeMs) / 1000 > staleAfterSeconds;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1212
1234
|
// prune-data: reclaim ORPHANED snapshots under <home>/data. An orphan is a <home>/data/<key>
|
|
1213
1235
|
// whose key is owned by NO LIVE registry entry (LIVE = a registered path that still EXISTS on
|
|
1214
1236
|
// disk) — so unregistered AND registered-but-vanished are BOTH orphans (the explicit reclaim;
|
|
@@ -1433,9 +1455,10 @@ function acquireUpdateLock(home, { timeout = null } = {}, env = process.env) {
|
|
|
1433
1455
|
// brand-new, not-yet-stale ticket for the SAME generation, so A and C would BOTH
|
|
1434
1456
|
// believe they are the sole reclaimer — the exact double-hold bug this ticket
|
|
1435
1457
|
// mechanism was built to prevent, reintroduced one level down).
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1458
|
+
// Same reuse guard as the main lock: only unlink a ticket STILL at its observed inode
|
|
1459
|
+
// AND still aged past the ticket-stale threshold (a freed inode number can be reused
|
|
1460
|
+
// by a fresh, live ticket for a new generation).
|
|
1461
|
+
if (stillStaleGeneration(ticketPath, tst.ino, LOCK_TICKET_STALE_SECONDS)) {
|
|
1439
1462
|
try { fs.unlinkSync(ticketPath); } catch (_e) {} // already gone — harmless
|
|
1440
1463
|
}
|
|
1441
1464
|
try { ticketFd = fs.openSync(ticketPath, "wx"); } catch (_e) {} // someone else won it
|
|
@@ -1462,9 +1485,12 @@ function acquireUpdateLock(home, { timeout = null } = {}, env = process.env) {
|
|
|
1462
1485
|
// generation we ticketed for; otherwise our ticket is moot — leave the (unrelated,
|
|
1463
1486
|
// currently live) file completely alone and let the loop's own open/EEXIST/age logic
|
|
1464
1487
|
// re-evaluate reality fresh on the next iteration.
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1488
|
+
// Re-verify STALENESS, not just inode identity: a freed inode number is REUSED by
|
|
1489
|
+
// Linux (ext4/tmpfs), so a live holder's fresh reused-inode lock must never be
|
|
1490
|
+
// unlinked on a bare inode match. Only remove it if it is STILL the observed inode
|
|
1491
|
+
// AND still stale — a live/heartbeated file (age < staleAfterMs) is spared, closing
|
|
1492
|
+
// the peak=2 double-hold.
|
|
1493
|
+
if (stillStaleGeneration(lockPath, st.ino, staleAfterMs / 1000)) {
|
|
1468
1494
|
try { fs.unlinkSync(lockPath); } catch (_e) {} // already gone — harmless
|
|
1469
1495
|
}
|
|
1470
1496
|
} finally {
|
|
@@ -1596,9 +1622,10 @@ function acquireProjectLock(addDir, env = process.env) {
|
|
|
1596
1622
|
// mechanism was built to prevent, reintroduced one level down). Exactly one extra
|
|
1597
1623
|
// self-heal attempt — never a second, matching M7's own "no poll, ever" discipline
|
|
1598
1624
|
// already governing the main lock's own reclaim above.
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1625
|
+
// Same reuse guard as the main lock: only unlink a ticket STILL at its observed inode
|
|
1626
|
+
// AND still aged past the ticket-stale threshold (a freed inode number can be reused
|
|
1627
|
+
// by a fresh, live ticket for a new generation).
|
|
1628
|
+
if (stillStaleGeneration(ticketPath, tst.ino, PROJECT_LOCK_TICKET_STALE_SECONDS)) {
|
|
1602
1629
|
try { fs.unlinkSync(ticketPath); } catch (_e) {} // already gone — harmless
|
|
1603
1630
|
}
|
|
1604
1631
|
try { ticketFd = fs.openSync(ticketPath, "wx"); } catch (_e) {} // someone else won it
|
|
@@ -1624,9 +1651,12 @@ function acquireProjectLock(addDir, env = process.env) {
|
|
|
1624
1651
|
// moot — leave the (unrelated, currently live) file completely alone and let the
|
|
1625
1652
|
// single retry below observe reality fresh (EEXIST -> fail-fast, per M7 — this lock
|
|
1626
1653
|
// never polls a live holder).
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1654
|
+
// Re-verify STALENESS, not just inode identity: a freed inode number is REUSED by
|
|
1655
|
+
// Linux (ext4/tmpfs), so a live holder's fresh reused-inode lock must never be
|
|
1656
|
+
// unlinked on a bare inode match. Only remove it if it is STILL the observed inode
|
|
1657
|
+
// AND still stale — a live/heartbeated file (age < staleAfterMs) is spared, closing
|
|
1658
|
+
// the peak=2 double-hold.
|
|
1659
|
+
if (stillStaleGeneration(lockPath, st.ino, staleAfterMs / 1000)) {
|
|
1630
1660
|
try { fs.unlinkSync(lockPath); } catch (_e) {} // already gone — harmless
|
|
1631
1661
|
}
|
|
1632
1662
|
} finally {
|
|
@@ -1846,6 +1876,7 @@ if (require.main === module) {
|
|
|
1846
1876
|
}
|
|
1847
1877
|
|
|
1848
1878
|
module.exports = {
|
|
1879
|
+
stillStaleGeneration: stillStaleGeneration,
|
|
1849
1880
|
detectAgent: detectAgent,
|
|
1850
1881
|
detectAgentEnriched: detectAgentEnriched,
|
|
1851
1882
|
readinessLine: readinessLine,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pilotspace/add",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "ADD (AI-Driven Development) 2.0. The agent is the hands; ADD is the memory, judgment, and conscience — a minimal, state-tracked skill: one freeze per feature, trust from passing tests, state on disk so context rot never survives a new session.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"add": "bin/cli.js"
|
package/skill/add/SKILL.md
CHANGED
|
@@ -15,7 +15,7 @@ argument-hint: "status | init | continue | --todo <text> | [describe new short g
|
|
|
15
15
|
license: MIT
|
|
16
16
|
metadata:
|
|
17
17
|
author: add
|
|
18
|
-
version: "2.
|
|
18
|
+
version: "2.4.0"
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
# ADD — memory · judgment · conscience (the agent is the hands)
|
|
@@ -100,6 +100,7 @@ confirmable delta the human confirms rewrites `SOUL.md` (the human is the only w
|
|
|
100
100
|
- §3 FROZEN → auto-gated run `run.md` · subagent roster + pipelines (agent-call-preferred, the
|
|
101
101
|
default execution mode) → `phases/verify.md` · self-score → `phases/direction.md`
|
|
102
102
|
- UI/experience surface → UDD loop `design.md` · milestone goal unmet at `milestone-done` → `loop.md`
|
|
103
|
+
- multi-task / high-uncertainty milestone → persona-framed strategy loop `strategy.md` (fills the `## Strategy` slot; micro/`--tiny` → skip)
|
|
103
104
|
- graduation · release · monorepo green-bars → persona-owned playbooks, `beyond.md` ·
|
|
104
105
|
the persona loop (`.add/personas/`) → `docs/18-personas.md` · `sensitivity:`/`advisor-gate-relax` → `phases/verify.md`
|
|
105
106
|
|
package/skill/add/beyond.md
CHANGED
|
@@ -20,6 +20,10 @@ prose. Load it (or the one guide you need) only when a trigger fires.
|
|
|
20
20
|
Tool-agnostic; the engine never renders.
|
|
21
21
|
- Tasks all done but the milestone **goal** unmet → `milestone-done` holds it open; the loop turns open
|
|
22
22
|
deltas + extras into the next tasks until the goal is met — `loop.md`.
|
|
23
|
+
- **A multi-task / high-uncertainty milestone** needs its task DAG sequenced before the tasks are built →
|
|
24
|
+
the persona-framed **strategy loop** (DISCUSS → OPTIMIZE → CONVERGE): the persona loaded at intake fills
|
|
25
|
+
the milestone's `## Strategy` slot with the optimized plan, converging on the six-dimension self-score —
|
|
26
|
+
`strategy.md`. SOFT/advisory, never a gate; a micro / `--tiny` milestone skips it (drafted-blank).
|
|
23
27
|
- **Graduating mvp → production** — co-specify interview → draft ≥1 production milestone →
|
|
24
28
|
human confirm → then `stage production`. Guarded (`stage_no_roadmap`); the FINAL step, never a
|
|
25
29
|
bare flip. The quality-auditor persona carries the readiness playbook.
|
package/skill/add/intake.md
CHANGED
|
@@ -4,6 +4,23 @@ Before a task exists, ADD turns a raw request into correctly-sized, versioned sc
|
|
|
4
4
|
**intake level** (the per-task flow is phases 0–7; intake is the step *before* a task). You
|
|
5
5
|
(the AI) **propose**; the human **confirms**. Never create scope without a confirmed proposal.
|
|
6
6
|
|
|
7
|
+
## Load the fitting persona first
|
|
8
|
+
|
|
9
|
+
Intake is a decision — size it WITH the project's expertise, not generically. Before you
|
|
10
|
+
analyze or size, load the fitting persona (the PM / product-direction lens), exactly as
|
|
11
|
+
`design.md` loads the design-fit persona:
|
|
12
|
+
|
|
13
|
+
- **match** a persona in `.add/personas/` by role/flow (frontmatter, else description-match)
|
|
14
|
+
— e.g. a `product-lead` / `method-product-owner` lens;
|
|
15
|
+
- **none fits?** seed from `.add/personas-teacher/` via the add agent in persona mode, then
|
|
16
|
+
load — offered, **never required**;
|
|
17
|
+
- the persona shapes the framing, the latent-requirement read, and the sizing tradeoffs. It
|
|
18
|
+
is **advisory**: it never lowers the `ask_human` floor, the frozen-scope tie-break, or the
|
|
19
|
+
security-always-escalates rule.
|
|
20
|
+
|
|
21
|
+
No fitting persona and none seeded? Proceed generically — intake still runs. The persona already
|
|
22
|
+
OWNS the intake report (`gate-udd.md`); loading it here makes it own the SIZING too, so the two agree.
|
|
23
|
+
|
|
7
24
|
## Analyze the request before you size it
|
|
8
25
|
|
|
9
26
|
A raw request is rarely a task yet — it is intent wrapped in prose. ADD's first job is not to
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Strategy — the persona-framed PM loop that fills the `## Strategy` slot
|
|
2
|
+
|
|
3
|
+
A multi-task or high-uncertainty milestone earns a plan over its tasks *before* they are
|
|
4
|
+
built. This guide drives that plan: a short **DISCUSS → OPTIMIZE → CONVERGE** loop that fills
|
|
5
|
+
the milestone's `## Strategy` slot (`MILESTONE.md`) with a sequenced, optimized task DAG.
|
|
6
|
+
|
|
7
|
+
**Trigger:** a milestone with several tasks or real uncertainty, at intake or when it activates.
|
|
8
|
+
**Skip:** a micro / `--tiny` milestone — a **drafted-blank** slot is valid, run nothing (risk-proportional).
|
|
9
|
+
|
|
10
|
+
The persona already loaded at intake (`intake.md`) **drives** this loop — you don't re-select
|
|
11
|
+
one. It frames the sequencing WITH the project's PM lens; it stays advisory throughout.
|
|
12
|
+
|
|
13
|
+
## Run the loop (with the intake persona)
|
|
14
|
+
|
|
15
|
+
1. **DISCUSS** — surface the task DAG: the dependencies, the shared/risky contracts, the
|
|
16
|
+
tradeoffs. Reflect the milestone goal, name what's in and out. Ask **one load-bearing
|
|
17
|
+
question per live lens** — an interview toward ~95% confidence, not a survey.
|
|
18
|
+
2. **OPTIMIZE** — sequence the DAG. Fill the slot's four facets (defined there, not restated
|
|
19
|
+
here): **approach** (risk-first | dependency-first | first-slice-unblocks — and WHY) ·
|
|
20
|
+
the **freeze-first** contract(s) · the **parallel waves** behind those frozen contracts ·
|
|
21
|
+
the **first unblocking slice**. Name the alternative decompositions you rejected.
|
|
22
|
+
3. **CONVERGE** — before you record, pressure-test the plan. If the milestone is
|
|
23
|
+
**high-uncertainty** (the sequencing is contested, or the self-score won't clear its bar),
|
|
24
|
+
spawn `add-advisor` in **refute** mode to try to **break** the strategy — the approach, the
|
|
25
|
+
freeze-first choice, the wave partition. Fold what survives; concede what holds. A
|
|
26
|
+
low-uncertainty / micro / `--tiny` milestone **skips** the spawn — no forced ceremony.
|
|
27
|
+
Then self-score with the existing six-dimension confidence self-score (`phases/direction.md`);
|
|
28
|
+
refine until it clears its bar (no dimension < 0.9 ≈ ~95% confident). Do **not** invent a
|
|
29
|
+
second threshold — that bar IS convergence. Record the converged plan in the `## Strategy` slot.
|
|
30
|
+
|
|
31
|
+
The refute is **advisory**: the advisor hands back the concrete break — it **cannot block**.
|
|
32
|
+
The human still confirms the strategy at the human decision point, through the persona-owned
|
|
33
|
+
gate (`gate-udd.md`) — the persona decides the report's shape and cadence. Security stays **HARD-STOP**.
|
|
34
|
+
|
|
35
|
+
## It stays SOFT
|
|
36
|
+
|
|
37
|
+
The `## Strategy` slot is the **preferred** plan, exactly like a task's §5 Build-strategy: the
|
|
38
|
+
build loop may deviate and records what it actually did. It is **advisory** — **never a new
|
|
39
|
+
gate**, and it never lowers a floor. Security stays **HARD-STOP** everywhere. A milestone is
|
|
40
|
+
never blocked on reaching a confidence bar; the bar guides the draft, it does not gate the work.
|
|
41
|
+
|
|
42
|
+
## How deep? (risk-proportional)
|
|
43
|
+
|
|
44
|
+
Loop depth scales with the milestone's risk/size — more risk/size, more depth:
|
|
45
|
+
|
|
46
|
+
- **micro / `--tiny`** → skip the loop. A drafted-blank `## Strategy` is valid; the loop runs
|
|
47
|
+
nothing — **zero added per-turn cost**.
|
|
48
|
+
- **multi-task, low-uncertainty** → run DISCUSS → OPTIMIZE → CONVERGE; no advisor.
|
|
49
|
+
- **high-uncertainty** (contested sequencing, or the self-score won't clear its bar) → the full
|
|
50
|
+
loop **plus** the `add-advisor` refute at CONVERGE.
|
|
51
|
+
|
|
52
|
+
This is the skill's judgment, reusing the Trigger / Skip and CONVERGE signals above — **never an
|
|
53
|
+
engine gate** on `## Strategy`. The ladder is **SOFT**: a run may go deeper or shallower and
|
|
54
|
+
records what it did. Security stays **HARD-STOP**.
|
|
@@ -82,7 +82,7 @@ def book_url(chapter: str) -> str:
|
|
|
82
82
|
# `add.py guide` copy: per-phase (concrete next action, book chapter to read).
|
|
83
83
|
# Keep the action wording aligned with each phase's EXIT line in the TASK template.
|
|
84
84
|
PHASE_GUIDE = {
|
|
85
|
-
"direction": ("draft the Direction bundle top-to-bottom — §1 rules (Must / Reject + named codes / After, assumptions ranked lowest-confidence first) · §
|
|
85
|
+
"direction": ("draft the Direction bundle top-to-bottom — §1 rules (Must / Reject + named codes / After, assumptions ranked lowest-confidence first) · §3 the change PLAN: ground the real code, draft the contract, and DESCRIBE what this task will do (scope · ordered batches · approach — the plan-of-action the freeze report shows the human) · §4 red suite failing for the right reason, one case per rule (scenarios live here with the tests); then the ONE approval: freeze --by <name> --cross",
|
|
86
86
|
"03-step-1-specify.md"),
|
|
87
87
|
"build": ("write the minimum code to pass the tests; change no test and no contract",
|
|
88
88
|
"07-step-5-build.md"),
|
|
@@ -29,7 +29,7 @@ binding verdict: a §6 Advisor 3-lens verdict with Binding:yes — engine-enforc
|
|
|
29
29
|
bounded self-heal: the verify-gate loop (heal-then-escalate) that gives a CONFIRMED cheat — mechanical tamper (the tripwire) or a reported earned-green failure (`add.py heal --reason`) — a chance to redo honestly before it stops: the engine returns the task to build, COUNTS the attempt, CAPS it at 3 (`HEAL_CAP`), and on the next confirmed cheat forces a HARD-STOP that escalates to the human. The counter is MONOTONIC — it never auto-resets (cmd_phase is unguarded, so a reset would be a zero-human cap bypass). The engine counts/caps/escalates; the AGENT does the honest re-build (never the engine). An honest build passes even at the cap (the cap bites a CONTINUED cheat, never a recovery); a gamed green is never auto-passed, never RISK-ACCEPTED-waived.
|
|
30
30
|
onboarding: the install -> first-milestone path (formerly "on-ramp").
|
|
31
31
|
primary flow: the solid forward path of the flow diagram — a phase starts only when its input exists (formerly "forward spine").
|
|
32
|
-
ground: the
|
|
32
|
+
ground: the grounding pass that opens the direction phase — the AI gathers the real current codebase a task touches (files, symbols, signatures, conventions) into a §0 grounding map; AI-owned, adds no gate (the one approval stays at the contract freeze).
|
|
33
33
|
grounding map / anchors: the §0 GROUND artifact — the real files/symbols/conventions a task touches plus the anchors the frozen contract cites; task-specific delta only (defers to PROJECT.md / CONVENTIONS.md), surfaced by status/check (measure, never block).
|
|
34
34
|
cross-cutting concern: a concern running through every step rather than being one step — security, testing, observability, cost (formerly "spine / continuous concern").
|
|
35
35
|
working state: everything an agent loads each session — skill router, active phase, PROJECT/MILESTONE/TASK, state.json (formerly "state surface").
|
|
@@ -40,7 +40,7 @@ Issues/Risks (shared): <traps in the shared code that feed each task's §1 expec
|
|
|
40
40
|
## Exit criteria (observable; map each to the task that delivers it)
|
|
41
41
|
- [ ] User can <observable behavior — the SEEN outcome only, NOT the task's plan line> (← <slug>)
|
|
42
42
|
|
|
43
|
-
## Strategy (AI-drafted WITH the human — the optimized task plan; SOFT/advisory like a task's Build-strategy; drafted-blank for a micro/--
|
|
43
|
+
## Strategy (AI-drafted WITH the human — the optimized task plan; SOFT/advisory like a task's Build-strategy; drafted-blank for a micro/--tiny milestone)
|
|
44
44
|
> The persona-led strategy over THIS milestone's tasks — sequencing, freeze-first contracts,
|
|
45
45
|
> parallel waves, the first unblocking slice, tradeoffs named. SOFT: the preferred plan; the
|
|
46
46
|
> loop may deviate and records what it did. Drafted-blank is valid (risk-proportional).
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
slug: {{slug}} · created: {{date}} · stage: {{stage}}
|
|
4
4
|
milestone: {{milestone}}
|
|
5
5
|
autonomy: {{autonomy}} <!-- manual<conservative<auto — lower for high-risk (`add.py autonomy set`); a `component: <name>` line joins that root to §3 Scope; task edges: `--depends-on`/`--extends`/`--relates-to`; high-risk/method-defining? declare `risk: high` on the slug line; headless agent-crossed freeze? declare `gate_mode: ai-plan-verify` here (human floor: security|data|architecture never AI-frozen) -->
|
|
6
|
-
phase: direction <!-- direction→build→verify→done; direction drafts §1–§4 (rules ·
|
|
6
|
+
phase: direction <!-- direction→build→verify→done; direction drafts §1–§4 (rules · change plan · red suite) to the ONE freeze -->
|
|
7
7
|
|
|
8
8
|
> One file = one task — an ATOMIC node: persist the interface (contract · red suite · scope · verdict); reason everything else in-context, don't write essays. The phase marker above is the single source of truth (`add.py phase`).
|
|
9
9
|
|