@pilotspace/add 1.8.0 → 1.9.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 +45 -0
- package/docs/appendix-c-glossary.md +2 -0
- package/package.json +1 -1
- package/skill/add/SKILL.md +86 -116
- package/skill/add/adopt.md +13 -34
- package/skill/add/advisor.md +9 -10
- package/skill/add/compact-foundation.md +21 -34
- package/skill/add/confidence.md +9 -25
- package/skill/add/deltas.md +12 -28
- package/skill/add/design.md +26 -32
- package/skill/add/fold.md +26 -51
- package/skill/add/graduate.md +18 -54
- package/skill/add/intake.md +30 -34
- package/skill/add/loop.md +30 -34
- package/skill/add/phases/0-ground.md +6 -14
- package/skill/add/phases/0-setup.md +30 -81
- package/skill/add/phases/1-specify.md +7 -22
- package/skill/add/phases/2-scenarios.md +1 -3
- package/skill/add/phases/3-contract.md +8 -22
- package/skill/add/phases/4-tests.md +3 -6
- package/skill/add/phases/5-build.md +8 -22
- package/skill/add/phases/6-verify.md +14 -49
- package/skill/add/phases/7-observe.md +1 -3
- package/skill/add/phases/fast-lane.md +31 -0
- package/skill/add/release.md +36 -90
- package/skill/add/report-template.md +41 -126
- package/skill/add/run.md +89 -159
- package/skill/add/scope.md +40 -89
- package/skill/add/setup-review.md +10 -27
- package/skill/add/soul.md +17 -36
- package/skill/add/streams.md +90 -149
- package/tooling/add.py +222 -7
- package/tooling/templates/TASK.fast.md.tmpl +72 -0
package/tooling/add.py
CHANGED
|
@@ -152,6 +152,47 @@ Outcome:
|
|
|
152
152
|
"""
|
|
153
153
|
|
|
154
154
|
|
|
155
|
+
# Fast-lane fallback: the minimal TASK.md variant (sections {0,1,3,4,5,6}; §2 + §7 dropped).
|
|
156
|
+
# Mirrors templates/TASK.fast.md.tmpl's section set (circuit-breaker parity); a deleted
|
|
157
|
+
# templates/ never hard-fails the fast lane. Keeps the trust floor: §3 freeze-flag + Status,
|
|
158
|
+
# §6 GATE RECORD/Outcome, §0 Anchors, §4 red-before-build, §5 Scope.
|
|
159
|
+
_FALLBACK_TASK_FAST = """# TASK: {title}
|
|
160
|
+
|
|
161
|
+
slug: {slug} · created: {date} · stage: {stage}
|
|
162
|
+
autonomy: auto
|
|
163
|
+
phase: ground
|
|
164
|
+
fast: true
|
|
165
|
+
|
|
166
|
+
## 0 · GROUND
|
|
167
|
+
Touches (files · symbols):
|
|
168
|
+
Anchors the contract cites:
|
|
169
|
+
|
|
170
|
+
## 1 · SPECIFY
|
|
171
|
+
Feature:
|
|
172
|
+
Must:
|
|
173
|
+
Reject:
|
|
174
|
+
Accept:
|
|
175
|
+
Assumptions: ⚠ <most likely wrong> — why; if wrong: <cost>
|
|
176
|
+
|
|
177
|
+
## 3 · CONTRACT
|
|
178
|
+
Least-sure flag surfaced at freeze:
|
|
179
|
+
Status: DRAFT
|
|
180
|
+
|
|
181
|
+
## 4 · TESTS
|
|
182
|
+
Plan:
|
|
183
|
+
Tests live in: `./tests/` · MUST run red before Build.
|
|
184
|
+
|
|
185
|
+
## 5 · BUILD
|
|
186
|
+
Scope (may touch): `./src/`
|
|
187
|
+
|
|
188
|
+
## 6 · VERIFY
|
|
189
|
+
Build expectations (from §1 Accept + §3 CONTRACT):
|
|
190
|
+
### GATE RECORD
|
|
191
|
+
Outcome:
|
|
192
|
+
Reviewed by:
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
|
|
155
196
|
# --- low-level IO (designed for failure: atomic, no silent clobber) ----------
|
|
156
197
|
|
|
157
198
|
def _now() -> str:
|
|
@@ -237,13 +278,14 @@ def _templates_dir() -> Path:
|
|
|
237
278
|
def _render_template(name: str, **subs: str) -> str:
|
|
238
279
|
"""Load templates/<name>.tmpl and substitute {{key}} tokens.
|
|
239
280
|
|
|
240
|
-
Falls back to a built-in minimal template
|
|
281
|
+
Falls back to a built-in minimal template for TASK.md and the fast-lane TASK.fast.md.
|
|
241
282
|
"""
|
|
242
283
|
tmpl = _templates_dir() / f"{name}.tmpl"
|
|
284
|
+
_fallbacks = {"TASK.md": _FALLBACK_TASK, "TASK.fast.md": _FALLBACK_TASK_FAST}
|
|
243
285
|
if tmpl.exists():
|
|
244
286
|
text = tmpl.read_text(encoding="utf-8")
|
|
245
|
-
elif name
|
|
246
|
-
text =
|
|
287
|
+
elif name in _fallbacks:
|
|
288
|
+
text = _fallbacks[name].replace("{title}", "{{title}}").replace(
|
|
247
289
|
"{slug}", "{{slug}}").replace("{date}", "{{date}}").replace("{stage}", "{{stage}}")
|
|
248
290
|
else:
|
|
249
291
|
text = ""
|
|
@@ -543,6 +585,80 @@ def _setup_locked(state: dict) -> bool:
|
|
|
543
585
|
return ("setup" not in state) or (state["setup"].get("locked") is True)
|
|
544
586
|
|
|
545
587
|
|
|
588
|
+
def _milestone_confirmed(state: dict, mslug: str) -> bool:
|
|
589
|
+
"""True when milestone `mslug` is confirmed — i.e. the new-task gate is OPEN.
|
|
590
|
+
|
|
591
|
+
Mirrors `_setup_locked` one level down. A milestone record with NO "confirmed" key is
|
|
592
|
+
GRANDFATHERED-confirmed: every milestone created WITHOUT `--await-confirm` (and every
|
|
593
|
+
pre-existing one) is never gated. Opt-in: `new-milestone --await-confirm` seeds confirmed:false,
|
|
594
|
+
so the gate is active in exactly one case: the record is present AND confirmed is False. An
|
|
595
|
+
unknown milestone is treated as confirmed here (existence is cmd_new_task's separate check)."""
|
|
596
|
+
m = (state.get("milestones") or {}).get(mslug)
|
|
597
|
+
if not isinstance(m, dict) or "confirmed" not in m:
|
|
598
|
+
return True
|
|
599
|
+
return m["confirmed"] is True
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def _section_unfilled(md_text: str, header: str) -> bool:
|
|
603
|
+
"""True iff the `header` section is PRESENT but UNFILLED — empty (no real bullet) or
|
|
604
|
+
still a `<…>` template placeholder. ABSENT section -> False (grandfathered legacy);
|
|
605
|
+
a filled section (>=1 real bullet, no `<…>`) -> False. Pure predicate — the shared
|
|
606
|
+
placeholder test the fill gates use (contract-fill at confirm; build-expectations at build)."""
|
|
607
|
+
body, in_sec, present = [], False, False
|
|
608
|
+
for ln in md_text.splitlines():
|
|
609
|
+
if ln.startswith(header):
|
|
610
|
+
in_sec, present = True, True
|
|
611
|
+
continue
|
|
612
|
+
if in_sec:
|
|
613
|
+
if ln.startswith("#"): # ANY next header (## or ###) ends our section
|
|
614
|
+
break
|
|
615
|
+
if ln.lstrip().startswith(">"): # skip blockquote GUIDANCE — it is not content
|
|
616
|
+
continue
|
|
617
|
+
body.append(ln)
|
|
618
|
+
if not present:
|
|
619
|
+
return False # absent -> grandfather
|
|
620
|
+
text = "\n".join(body).strip()
|
|
621
|
+
if not text:
|
|
622
|
+
return True # present but empty
|
|
623
|
+
return bool(re.search(r"<[^>\n]+>", text)) # a <…> placeholder remains
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def _stamp_gate_record(root: Path, state: dict, slug: str, outcome: str) -> None:
|
|
627
|
+
"""Write-back (gate-record-writeback): mirror the resolved gate verdict into the task's
|
|
628
|
+
§6 `### GATE RECORD`, so the file and state.json never silently diverge (Finding C). Runs
|
|
629
|
+
for EVERY task — the write is ADDITIVE and never refuses, so (unlike the two refusal gates)
|
|
630
|
+
it needs no `--await-confirm` opt-in to protect the census. GRANDFATHER is the safety: a
|
|
631
|
+
GATE RECORD line is rewritten ONLY while it still holds a `<…>` placeholder; a resolved
|
|
632
|
+
(hand-filled) line is byte-untouched. No GATE RECORD block / no placeholder line / an
|
|
633
|
+
unreadable file -> silent no-op, the file stays byte-identical. Called AFTER save_state —
|
|
634
|
+
state is the source of truth; the file only mirrors it, so a write fault never loses a verdict."""
|
|
635
|
+
f = root / "tasks" / slug / "TASK.md"
|
|
636
|
+
try:
|
|
637
|
+
text = f.read_text(encoding="utf-8")
|
|
638
|
+
except OSError:
|
|
639
|
+
return # unreadable -> no-op (never blocks the gate)
|
|
640
|
+
if "### GATE RECORD" not in text:
|
|
641
|
+
return # nothing to mirror into
|
|
642
|
+
actor = _actor_stamp(state)
|
|
643
|
+
today = date.today().isoformat()
|
|
644
|
+
# each rule matches ONLY a line still carrying a `<…>` placeholder -> grandfather a resolved line.
|
|
645
|
+
rules = [
|
|
646
|
+
(r"(?m)^(Outcome:[ \t]*)<[^>\n]*>.*$", f"Outcome: {outcome}"),
|
|
647
|
+
(r"(?m)^Reviewed by:[ \t]*.*<[^>\n]*>.*$",
|
|
648
|
+
f"Reviewed by: {actor['name']} · date: {today}"),
|
|
649
|
+
]
|
|
650
|
+
if outcome == "RISK-ACCEPTED":
|
|
651
|
+
w = ((state.get("tasks") or {}).get(slug) or {}).get("waiver") or {}
|
|
652
|
+
rules.append((r"(?m)^If RISK-ACCEPTED ->.*<[^>\n]*>.*$",
|
|
653
|
+
f"If RISK-ACCEPTED -> owner: {w.get('owner', '?')} · "
|
|
654
|
+
f"ticket: {w.get('ticket', '?')} · expires: {w.get('expires', '?')}"))
|
|
655
|
+
new = text
|
|
656
|
+
for pat, repl in rules:
|
|
657
|
+
new = re.sub(pat, repl, new, count=1)
|
|
658
|
+
if new != text: # no-op = no write (mtime stable)
|
|
659
|
+
_atomic_write(f, new)
|
|
660
|
+
|
|
661
|
+
|
|
546
662
|
def _die(msg: str, code: int = 1) -> None:
|
|
547
663
|
print(f"add: error: {msg}", file=sys.stderr)
|
|
548
664
|
raise SystemExit(code)
|
|
@@ -763,6 +879,12 @@ def cmd_new_task(args: argparse.Namespace) -> None:
|
|
|
763
879
|
milestone = getattr(args, "milestone", None) or _active_milestone(state)
|
|
764
880
|
if milestone and milestone not in state.get("milestones", {}):
|
|
765
881
|
_die("unknown_milestone")
|
|
882
|
+
# confirm-parent gate (OPT-IN): a task may not be detailed before its parent milestone is
|
|
883
|
+
# human-confirmed — but ONLY when the milestone opted in via `new-milestone --await-confirm`.
|
|
884
|
+
# validate-then-write — refuse BEFORE any scaffold/state mutation. A milestone with no
|
|
885
|
+
# `confirmed` key (non-flag + pre-existing) is grandfathered (mirrors the setup-lock).
|
|
886
|
+
if milestone and not _milestone_confirmed(state, milestone):
|
|
887
|
+
_die(f"milestone_unconfirmed: confirm it first — add.py milestone-confirm {milestone}")
|
|
766
888
|
depends_on = _parse_deps(getattr(args, "depends_on", None))
|
|
767
889
|
|
|
768
890
|
# SEED (--from-delta): resolve a prior task's FIRST open SPEC delta into THIS task.
|
|
@@ -796,8 +918,13 @@ def cmd_new_task(args: argparse.Namespace) -> None:
|
|
|
796
918
|
# inherit the project's DECLARED autonomy default (task init-auto-default) — fail-SAFE:
|
|
797
919
|
# absent -> auto, garbled -> conservative; the posture is project-scoped, not hardcoded.
|
|
798
920
|
autonomy = _project_autonomy(root)
|
|
921
|
+
# fast lane (fast-new-task-flag): --fast scaffolds the MINIMAL template instead of the full one.
|
|
922
|
+
# The human opts in explicitly (the engine never guesses ceremony); the freeze floor is held by
|
|
923
|
+
# the freeze-before-build gate's fast arm (cmd_advance), so the lighter shape never drops the trust seam.
|
|
924
|
+
fast = bool(getattr(args, "fast", False))
|
|
799
925
|
rendered = _render_template(
|
|
800
|
-
"TASK.md"
|
|
926
|
+
"TASK.fast.md" if fast else "TASK.md",
|
|
927
|
+
title=title, slug=slug, date=date.today().isoformat(),
|
|
801
928
|
stage=state["stage"], autonomy=autonomy)
|
|
802
929
|
if feature_override: # pre-fill §1 from the seeded delta
|
|
803
930
|
rendered = re.sub(r"(?m)^Feature:.*$",
|
|
@@ -822,6 +949,8 @@ def cmd_new_task(args: argparse.Namespace) -> None:
|
|
|
822
949
|
}
|
|
823
950
|
if from_delta:
|
|
824
951
|
state["tasks"][slug]["from_delta"] = from_delta # lineage: seeded from <prior>
|
|
952
|
+
if fast:
|
|
953
|
+
state["tasks"][slug]["fast"] = True # durable lane marker (absent == not-fast)
|
|
825
954
|
_set_active_task(state, slug, milestone)
|
|
826
955
|
save_state(root, state)
|
|
827
956
|
print(f"created task '{slug}' -> {task_md}")
|
|
@@ -938,7 +1067,29 @@ def cmd_advance(args: argparse.Namespace) -> None:
|
|
|
938
1067
|
# freezes — the unmarked predecessors are never retro-redded). REFUSE writes
|
|
939
1068
|
# nothing (fail-closed); below the build boundary the flag is never checked.
|
|
940
1069
|
if nxt == "build":
|
|
1070
|
+
# the OPTED-IN crossing guards (fast-lane + flow-enforcement): a task whose PARENT
|
|
1071
|
+
# milestone opted into --await-confirm is held to the trust floor at tests->build. A
|
|
1072
|
+
# task under a plain / no milestone is never gated (every existing advance-to-build flow
|
|
1073
|
+
# stays green). validate-then-write — every refusal runs BEFORE the tripwire/scope
|
|
1074
|
+
# snapshots below, writing nothing; the task stays at `tests`.
|
|
1075
|
+
_ms = state["tasks"][slug].get("milestone")
|
|
1076
|
+
_optin = bool(_ms) and (state.get("milestones") or {}).get(_ms, {}).get("await_confirm") is True
|
|
941
1077
|
raw3 = _raw_phase_bodies(root, slug).get(3, "")
|
|
1078
|
+
# freeze-before-build gate (fast-lane): "collapse-never-skip" made REAL — the freeze is
|
|
1079
|
+
# engine-enforced for an opted-in milestone OR any --fast task (the fast arm, fast-new-task-flag:
|
|
1080
|
+
# a fast task is held to the floor under ANY milestone, so the lighter lane never drops the
|
|
1081
|
+
# trust seam). PRECEDES build-expectations: you freeze §3 before pre-declaring §6's outcomes.
|
|
1082
|
+
_freeze_gated = _optin or state["tasks"][slug].get("fast") is True
|
|
1083
|
+
if _freeze_gated and not _contract_frozen(raw3):
|
|
1084
|
+
_die("contract_not_frozen: freeze §3 before crossing into build — approve "
|
|
1085
|
+
f"the contract in {slug}'s TASK.md (Status: FROZEN @ vN)")
|
|
1086
|
+
# build-expectations gate (flow-enforcement): an opted-in task may not enter build until
|
|
1087
|
+
# its §6 `### Build expectations` are pre-declared — so verify checks the build is RIGHT,
|
|
1088
|
+
# not just green. Same opt-in switch as the contract-fill gate, one level out.
|
|
1089
|
+
if _optin:
|
|
1090
|
+
if _section_unfilled(_raw_phase_bodies(root, slug).get(6, ""), "### Build expectations"):
|
|
1091
|
+
_die("build_expectations_unfilled: fill the §6 '### Build expectations' block "
|
|
1092
|
+
f"of {slug}'s TASK.md before crossing into build")
|
|
942
1093
|
if _contract_frozen(raw3):
|
|
943
1094
|
if not _flag_well_formed(raw3):
|
|
944
1095
|
_die("unflagged_freeze: a frozen §3 must surface a well-formed "
|
|
@@ -1126,6 +1277,7 @@ def cmd_gate(args: argparse.Namespace) -> None:
|
|
|
1126
1277
|
state["tasks"][slug]["gate_actor"] = _actor_stamp(state) # WHO recorded the verdict (every outcome)
|
|
1127
1278
|
state["tasks"][slug]["updated"] = _now()
|
|
1128
1279
|
save_state(root, state)
|
|
1280
|
+
_stamp_gate_record(root, state, slug, args.outcome) # mirror the verdict into §6 (Finding C)
|
|
1129
1281
|
print(f"task '{slug}' gate -> {args.outcome}")
|
|
1130
1282
|
# the engine-sourced next step (next-footer-engine): a completing gate hands off to the
|
|
1131
1283
|
# state arm; HARD-STOP routes to "resolve HARD-STOP …" — converging the old bespoke line.
|
|
@@ -1531,7 +1683,10 @@ def cmd_status(args: argparse.Namespace) -> None:
|
|
|
1531
1683
|
if _rel:
|
|
1532
1684
|
print(f" → {RELEASABLE_CUE.format(n=len(_rel))}")
|
|
1533
1685
|
|
|
1534
|
-
|
|
1686
|
+
# fast-lane marker (fast-new-task-flag): tag an ACTIVE fast task so the lane is visible at a
|
|
1687
|
+
# glance. Presentation-only, existence-gated — a plain/absent active task is byte-unchanged.
|
|
1688
|
+
_fast_mark = " · fast" if active and tasks.get(active, {}).get("fast") is True else ""
|
|
1689
|
+
print(f"active : {active or '(none)'}{_fast_mark}")
|
|
1535
1690
|
# parallel streams (parallel-status-view): when >=2 milestones are active, render each as its
|
|
1536
1691
|
# own stream (active task + phase) so a user working N fronts reads them all at once. ADDITIVE —
|
|
1537
1692
|
# the N<=1 output above is byte-identical (the standing additive-cue convention); presentation
|
|
@@ -2577,17 +2732,64 @@ def cmd_new_milestone(args: argparse.Namespace) -> None:
|
|
|
2577
2732
|
_atomic_write(mfile, _render_template(
|
|
2578
2733
|
"MILESTONE.md", title=title, goal=args.goal or "<goal>",
|
|
2579
2734
|
stage=args.stage, date=date.today().isoformat()))
|
|
2580
|
-
|
|
2735
|
+
# confirm-parent gate (OPT-IN, mirrors `init --await-lock`): `--await-confirm` seeds the
|
|
2736
|
+
# milestone UNCONFIRMED so new-task is held until `add.py milestone-confirm`. WITHOUT the flag
|
|
2737
|
+
# NO `confirmed` key is written → grandfathered-confirmed → no gate (so the existing engine
|
|
2738
|
+
# tests stay byte-green). The guided skill flow passes the flag at the human-review point.
|
|
2739
|
+
await_confirm = bool(getattr(args, "await_confirm", False))
|
|
2740
|
+
record = {
|
|
2581
2741
|
"title": title, "goal": args.goal or "", "stage": args.stage,
|
|
2582
2742
|
"status": "active", "created": _now(), "updated": _now(),
|
|
2583
2743
|
}
|
|
2744
|
+
if await_confirm:
|
|
2745
|
+
# `await_confirm` is the STABLE opt-in marker (set ONLY here, at creation). `confirmed`
|
|
2746
|
+
# alone is NOT a reliable opt-in signal: milestone-confirm stamps confirmed:true on a plain
|
|
2747
|
+
# milestone too, so a later build-entry gate must key on `await_confirm`, not `confirmed`.
|
|
2748
|
+
record.update(confirmed=False, confirmed_at=None, confirmed_by=None, await_confirm=True)
|
|
2749
|
+
state["milestones"][slug] = record
|
|
2584
2750
|
_set_active_milestone(state, slug)
|
|
2585
2751
|
save_state(root, state)
|
|
2586
2752
|
print(f"created milestone '{slug}' -> {mfile}")
|
|
2587
|
-
print("active milestone set."
|
|
2753
|
+
print("active milestone set." + ("" if not await_confirm else
|
|
2754
|
+
" (unconfirmed — show the MILESTONE.md, then: add.py milestone-confirm " + slug + ")"))
|
|
2588
2755
|
print(_next_footer(root, state)) # converges the old "Decompose it into tasks: …" hint
|
|
2589
2756
|
|
|
2590
2757
|
|
|
2758
|
+
def cmd_milestone_confirm(args: argparse.Namespace) -> None:
|
|
2759
|
+
"""The human gate that opens new-task for a milestone (confirm-parent). Mirrors `cmd_lock`
|
|
2760
|
+
one level down: the human reviews the filled MILESTONE.md, then RECORDS confirmation here.
|
|
2761
|
+
The engine never self-confirms. Validate-then-write; re-confirm is an idempotent note."""
|
|
2762
|
+
root = _require_root()
|
|
2763
|
+
state = load_state(root)
|
|
2764
|
+
slug = args.slug
|
|
2765
|
+
if slug not in state.get("milestones", {}):
|
|
2766
|
+
_die("unknown_milestone")
|
|
2767
|
+
m = state["milestones"][slug]
|
|
2768
|
+
if m.get("confirmed") is True:
|
|
2769
|
+
print(f"milestone '{slug}' already confirmed (by {m.get('confirmed_by', '?')}).")
|
|
2770
|
+
return
|
|
2771
|
+
# contract-fill gate (flow-enforcement, OPTED-IN only): a milestone that opted into
|
|
2772
|
+
# --await-confirm (carries a `confirmed` key) may not be confirmed until its cross-task
|
|
2773
|
+
# `## Shared / risky contracts` section is filled — so "confirmed" MEANS the contracts
|
|
2774
|
+
# were present at confirm time. A grandfathered no-key milestone keeps the plain stamp
|
|
2775
|
+
# (gate skipped — keeps the census + existing flows green). Validate-then-write.
|
|
2776
|
+
if "confirmed" in m:
|
|
2777
|
+
mfile = root / "milestones" / slug / MILESTONE_FILE
|
|
2778
|
+
md = mfile.read_text(encoding="utf-8") if mfile.exists() else ""
|
|
2779
|
+
if _section_unfilled(md, "## Shared / risky contracts"):
|
|
2780
|
+
_die("milestone_contracts_unfilled: fill the '## Shared / risky contracts' "
|
|
2781
|
+
f"section of {slug}'s MILESTONE.md before confirming")
|
|
2782
|
+
who = getattr(args, "by", None) or getpass.getuser()
|
|
2783
|
+
m["confirmed"] = True
|
|
2784
|
+
m["confirmed_at"] = _now()
|
|
2785
|
+
m["confirmed_by"] = who
|
|
2786
|
+
m["actor"] = _actor_stamp(state) # structured actor alongside the free-text confirmed_by
|
|
2787
|
+
m["updated"] = _now()
|
|
2788
|
+
save_state(root, state)
|
|
2789
|
+
print(f"confirmed milestone '{slug}' (by {who}) — new-task is now open for it.")
|
|
2790
|
+
print(_next_footer(root, state))
|
|
2791
|
+
|
|
2792
|
+
|
|
2591
2793
|
def cmd_ready(args: argparse.Namespace) -> None:
|
|
2592
2794
|
if getattr(args, "json", False):
|
|
2593
2795
|
_, state = _load_state_for_json()
|
|
@@ -5696,6 +5898,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
5696
5898
|
help="with --from-delta: target the UNIQUE open SPEC delta whose text "
|
|
5697
5899
|
"contains SUBSTR (case-insensitive) instead of the first")
|
|
5698
5900
|
pn.add_argument("--force", action="store_true", help="overwrite TASK.md if present")
|
|
5901
|
+
pn.add_argument("--fast", action="store_true",
|
|
5902
|
+
help="opt into the fast lane: scaffold the minimal TASK.fast.md template + "
|
|
5903
|
+
"hold the task to the freeze floor under any milestone")
|
|
5699
5904
|
pn.set_defaults(func=cmd_new_task)
|
|
5700
5905
|
|
|
5701
5906
|
pdd = sub.add_parser("drop-delta",
|
|
@@ -5712,8 +5917,18 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
5712
5917
|
pm.add_argument("--goal", default=None, help="one-sentence outcome")
|
|
5713
5918
|
pm.add_argument("--stage", default="mvp", choices=STAGES)
|
|
5714
5919
|
pm.add_argument("--force", action="store_true", help="overwrite MILESTONE.md if present")
|
|
5920
|
+
pm.add_argument("--await-confirm", action="store_true",
|
|
5921
|
+
help="opt into the confirm-parent gate: seed the milestone unconfirmed so "
|
|
5922
|
+
"new-task is held until `milestone-confirm` (mirrors `init --await-lock`); "
|
|
5923
|
+
"the guided skill flow passes this at the human-review point")
|
|
5715
5924
|
pm.set_defaults(func=cmd_new_milestone)
|
|
5716
5925
|
|
|
5926
|
+
pmc = sub.add_parser("milestone-confirm",
|
|
5927
|
+
help="confirm a milestone (the human gate that opens new-task for it)")
|
|
5928
|
+
pmc.add_argument("slug")
|
|
5929
|
+
pmc.add_argument("--by", default=None, help="free-text confirmer name (defaults to the OS user)")
|
|
5930
|
+
pmc.set_defaults(func=cmd_milestone_confirm)
|
|
5931
|
+
|
|
5717
5932
|
pr = sub.add_parser("ready", help="list tasks whose dependencies are satisfied")
|
|
5718
5933
|
pr.add_argument("--json", action="store_true", help="machine-readable JSON output")
|
|
5719
5934
|
pr.set_defaults(func=cmd_ready)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# TASK: {{title}}
|
|
2
|
+
|
|
3
|
+
slug: {{slug}} · created: {{date}} · stage: {{stage}}
|
|
4
|
+
autonomy: {{autonomy}}
|
|
5
|
+
phase: ground <!-- fast lane: ground -> specify -> contract -> tests -> build -> verify -> observe -> done -->
|
|
6
|
+
fast: true <!-- the fast lane: a small task, collapsed flow + minimal template. Omit --fast for full rigor. -->
|
|
7
|
+
|
|
8
|
+
> Fast lane — one small task, minimal sections, filled top-to-bottom. The trust floor still
|
|
9
|
+
> holds: a FROZEN §3 contract · ≥1 red test before build · a recorded §6 gate (security = HARD-STOP).
|
|
10
|
+
> The acceptance scenario collapses into §1 `Accept:`; OBSERVE is one optional line at the gate.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 0 · GROUND — the real codebase
|
|
15
|
+
|
|
16
|
+
Touches (files · symbols): <path:symbol — what it is / how it is keyed>
|
|
17
|
+
Anchors the contract cites: <the symbols §3 will name>
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 1 · SPECIFY — the rules
|
|
22
|
+
|
|
23
|
+
Feature: <name>
|
|
24
|
+
Must:
|
|
25
|
+
- <required behavior>
|
|
26
|
+
Reject:
|
|
27
|
+
- <bad input / situation> -> "<error_code>"
|
|
28
|
+
Accept: <the one acceptance scenario, Given/When/Then condensed to one line — it drives the §4 test>
|
|
29
|
+
Assumptions: ⚠ <the one most likely wrong> — why; if wrong: <cost> (or "none material — biggest risk: X")
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 3 · CONTRACT — freeze the shape
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
<the shape that freezes: signature / fields · success + each rejection's response>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`Least-sure flag surfaced at freeze:` <[spec|contract|test] the point most likely wrong — why; if wrong: cost>
|
|
40
|
+
Status: DRAFT
|
|
41
|
+
<!-- The freeze IS the one approval. Approved -> Status: FROZEN @ vN — approved by <name>.
|
|
42
|
+
Changing a frozen contract = change request back to SPECIFY. -->
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 4 · TESTS — failing-first (red)
|
|
47
|
+
|
|
48
|
+
Plan: test_<accept> — assert the §1 Accept line's Then (behavior, not internals).
|
|
49
|
+
Tests live in: `./tests/` · MUST run red (missing implementation) before Build.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 5 · BUILD — AI writes code
|
|
54
|
+
|
|
55
|
+
Scope (may touch): `./src/` <every file the build may write — declared before the §3 freeze>
|
|
56
|
+
Code lives in: `./src/` · Constraints: change no test, no contract; allow-list packages only.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 6 · VERIFY — evidence + gate
|
|
61
|
+
|
|
62
|
+
- [ ] all tests pass · coverage held · no test or contract altered during build
|
|
63
|
+
- [ ] green was EARNED — no overfit / vacuous asserts / stubbed-away logic
|
|
64
|
+
- [ ] no exposed secrets, injection openings, or unexpected dependencies (security = HARD-STOP)
|
|
65
|
+
|
|
66
|
+
Build expectations (from §1 Accept + §3 CONTRACT): <the observable outcome a correct build must produce — confirmed by <how / where>>
|
|
67
|
+
|
|
68
|
+
### GATE RECORD
|
|
69
|
+
Outcome: <PASS | RISK-ACCEPTED | HARD-STOP>
|
|
70
|
+
Reviewed by: <name> · date: <date>
|
|
71
|
+
<!-- A security finding is ALWAYS HARD-STOP. Record exactly one outcome — no silent pass.
|
|
72
|
+
OBSERVE (optional): one `[SPEC · open]` or competency-delta line here if the loop taught the foundation something. -->
|