@antoneeo/agentic-sdlc-skill 1.9.0 → 1.11.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 +24 -0
- package/README.md +2 -1
- package/gemini-extension.json +1 -1
- package/package.json +2 -1
- package/skills/agentic-sdlc-skill/ENFORCEMENT.md +52 -0
- package/skills/agentic-sdlc-skill/SKILL.md +13 -1
- package/skills/agentic-sdlc-skill/dispatch.md +85 -0
- package/skills/agentic-sdlc-skill/guides.md +34 -0
- package/skills/agentic-sdlc-skill/scripts/sdlc_check.py +279 -18
- package/skills/agentic-sdlc-skill/templates.md +48 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
Tutte le modifiche significative a questa skill saranno documentate in questo file.
|
|
4
4
|
|
|
5
|
+
## [1.11.0] - 2026-07-03 (M4: Consolidation & Proactive Activation — self-activating, self-consulting, self-testing skill)
|
|
6
|
+
### Added
|
|
7
|
+
- **SessionStart orientation hook** (`sdlc_check.py orient`): emits a bounded, repo-sourced orientation (README + INDEX + guide router + handoff + Rule-Zero triage) at session start. Zero-execution, **fail-OPEN** (a missing/empty `ai_docs/` never blocks the session), size-capped. Manual per-client wiring in `ENFORCEMENT.md` §4; `--hybrid` points at the devPNT bootstrap instead of duplicating plan/KL. `test_session_start.py` (9 cases).
|
|
8
|
+
- **Guide-layer consumption** (closes the write-only gap — Layer D "point to them"): a **consult trigger** (before operative L2/L3 work, targeted router match, L1 exempt, never blanket) and a **proactive-creation trigger** (propose a guide after user-indication-governed reusable work; never silent, never from model knowledge). Mechanics in `guides.md` §0/§1; hooked from `SKILL.md` Operative Guides + Phase 4/5; reconciled with subagent dispatch in `dispatch.md`.
|
|
9
|
+
- **Worktree/branch hygiene** in the closure discipline (`SKILL.md` Phase 4 isolate-on-branch / Phase 5 merge-decision + cleanup).
|
|
10
|
+
- **Skill eval harness** (dev-only, not shipped): `test_skill_invariants.py` is the deterministic static release gate (`python -m unittest discover -s scripts -p "test_*.py"` — asserts the skill's own doctrine invariants: triggers/hook/worktree present and wired, indexes idempotent, support pointers resolve; zero LLM/network/subprocess). Opt-in behavioral corpus `evals/scenarios/` + `run_behavioral.py` (non-CI, never gates). `ENFORCEMENT.md` §5.
|
|
11
|
+
|
|
12
|
+
### Process note
|
|
13
|
+
- Full governance per unit: M-VISION v2.1 (revised — added the guide-consumption unit) → D-UC + P-TM → per-unit E-ISP (deep review) + E-TDD (light review) → implement → §4.6 code review. The independent-review gate caught real defects at design time (incomplete eval invariant set, the M3↔M4 dispatch interaction, a REPO path off-by-one, a P-TM overclaim of unbuilt guards). Battery 51/51 green. ADR `adr_2026-07-03_skill_eval_harness`; KL architecture v1.4 + principles v1.2.
|
|
14
|
+
|
|
15
|
+
## [1.10.0] - 2026-07-03 (M3: Subagent Execution / Feature A — opt-in executable plan)
|
|
16
|
+
### Added
|
|
17
|
+
- **`dispatch.md`**: subagent-execution doctrine (opt-in for L3). The dispatch loop — validate the plan → per-task brief → economy-tier implementer → one-shot review → ledger — with client-relative model tiers (no provider names), one-shot review slots (no iterative loops), degradation to same-session where subagents do not exist, and guides injected by pointer (never pasted).
|
|
18
|
+
- **`sdlc_check.py plan` subcommand**: `plan validate` (schema check of the executable plan, fail-closed path/guide confinement, sidecar-ledger cross-check — "no valid plan, no dispatch") and `plan brief --task <id>` (emits, to stdout, the task + prior-task interfaces + guide pointers). The validator is **zero-execution**: a task's `verify` command is emitted as text, never run.
|
|
19
|
+
- **Executable-plan template** (`ai_docs/solutions/PLAN_[feature].md`) in `templates.md`: Markdown frontmatter (`status`, `derived-from`) + a fenced `json` task array + the sidecar `PLAN_[feature].ledger.json` shape (`task_id -> {status, verify_result, timestamp}`, git-tracked, survives compaction).
|
|
20
|
+
- **`SKILL.md` §4** opt-in subagent-execution hook + the Hybrid `derived-from` seam (the plan is derived from the accepted E-TDD, never independently authored).
|
|
21
|
+
- `test_plan.py`: stdlib-`unittest` battery for the `plan` subcommand (32 cases: schema, confinement, ledger, fail-fast JSON, zero-execution poka-yoke).
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **`confine_under(base, rel)` extracted** in `sdlc_check.py`: the fail-closed path-confinement pattern (absolute/`..`/resolve-escape → reject), previously inlined twice (the `overrides:` and `distilled_from` checks), is now a single helper reused by both plus the new plan-path / guide-pointer confinement. Behavior-preserving (catches `(ValueError, OSError)`).
|
|
25
|
+
|
|
26
|
+
### Process note
|
|
27
|
+
- 4th live **model-per-dispatch** run (economy implementer from the E-TDD shadow, battery 32/32, deep code review PASS zero BLOCK). Governance: M-VISION → D-UC → P-TM → E-ISP → E-TDD, all through the independent review gate — which killed 3 real BLOCKs at design time (T1 subprocess-invariant misstatement, a missing impacted file, a `confine_under` OSError-crash regression). ADR `adr_2026-07-03_executable_plan_json_in_md`.
|
|
28
|
+
|
|
5
29
|
## [1.9.0] - 2026-07-03 (M2 execution disciplines + Feature B unit 2 agent KB)
|
|
6
30
|
### Added
|
|
7
31
|
- `tdd.md`: TDD discipline (RED/GREEN/REFACTOR, increment rule, AAA test shape, documented exemptions) — the L2/L3 default for implementation work.
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
- **Vision-guided governance**: Standalone projects use `ai_docs/vision/`; Hybrid projects use devPNT `M-VISION` as the milestone north star.
|
|
9
9
|
- **Standalone complete**: works fully with local `ai_docs/` without requiring devPNT.
|
|
10
10
|
- **devPNT symbiosis**: when devPNT is available, Master Plan, Action Plan, M-VISION, and governed artifacts become the authoritative planning layer.
|
|
11
|
-
- **Installed support files**: Claude, Codex, and Gemini receive the full skill folder, including `templates.md`, `guides.md`, `tdd.md`, `debugging.md`, `elicitation.md`, `review.md`, `ENFORCEMENT.md`, and `scripts/sdlc_check.py`; an agent-global KB at `~/.agentic-sdlc` shares the same engine cross-project.
|
|
11
|
+
- **Installed support files**: Claude, Codex, and Gemini receive the full skill folder, including `templates.md`, `guides.md`, `tdd.md`, `debugging.md`, `elicitation.md`, `review.md`, `dispatch.md`, `ENFORCEMENT.md`, and `scripts/sdlc_check.py`; an agent-global KB at `~/.agentic-sdlc` shares the same engine cross-project.
|
|
12
12
|
- **Mechanical checks**: optional validator for document structure, generated feature history, stale audit areas, and protected-path gates.
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
@@ -49,6 +49,7 @@ skills/agentic-sdlc-skill/
|
|
|
49
49
|
├── debugging.md
|
|
50
50
|
├── elicitation.md
|
|
51
51
|
├── review.md
|
|
52
|
+
├── dispatch.md
|
|
52
53
|
├── ENFORCEMENT.md
|
|
53
54
|
└── scripts/
|
|
54
55
|
└── sdlc_check.py
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antoneeo/agentic-sdlc-skill",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.11.0",
|
|
4
4
|
"description": "Documentation-First SDLC protocol for Claude Code, Gemini CLI and Codex with risk triage, Vision governance, installed support files and optional devPNT integration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"skills/agentic-sdlc-skill/debugging.md",
|
|
33
33
|
"skills/agentic-sdlc-skill/elicitation.md",
|
|
34
34
|
"skills/agentic-sdlc-skill/review.md",
|
|
35
|
+
"skills/agentic-sdlc-skill/dispatch.md",
|
|
35
36
|
"skills/agentic-sdlc-skill/ENFORCEMENT.md",
|
|
36
37
|
"skills/agentic-sdlc-skill/scripts/sdlc_check.py",
|
|
37
38
|
"gemini-extension.json",
|
|
@@ -54,3 +54,55 @@ Semantics: exit code 2 + message on stderr ⇒ the write is blocked and the mess
|
|
|
54
54
|
- `ai_docs/`, `tests/` and `test/` are always excluded from blocking.
|
|
55
55
|
- The hook assumes the working directory is the project root (standard behavior of Claude Code hooks).
|
|
56
56
|
- **Hybrid/devPNT projects**: add `--hybrid` to the gate command. Governed designs live in the devPNT DB, so the gate also unlocks when an approved E-TDD shadow (`ai_docs/solutions/SHADOW_*tdd*.md`, exported before implementation — see the SKILL.md shadow discipline) is present. Without the flag the gate would block legitimate governed work. The flag is deliberately explicit: never auto-detected.
|
|
57
|
+
|
|
58
|
+
## 4. SessionStart hook (orientation, optional)
|
|
59
|
+
|
|
60
|
+
Emits the `ai_docs/` orientation — reading guide (`README.md`), manifest (`INDEX.md`), guide router (`reference/INDEX.md`) and last `handoff.md` — plus the Rule-Zero triage reminder to stdout at session start, so the agent begins already oriented instead of reading them only if it remembers to. It is **fail-open**: a missing, unreadable or oversized doc is skipped, the output is size-capped, and it always exits 0 — a broken or empty `ai_docs/` never blocks the session. It is **zero-execution** (it reads and prints, never runs anything) and opt-in.
|
|
61
|
+
|
|
62
|
+
Wire it via each client's SessionStart mechanism — the same command everywhere (add `--hybrid` on devPNT/Hybrid projects):
|
|
63
|
+
|
|
64
|
+
Claude Code — in the project's `.claude/settings.json`:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"hooks": {
|
|
69
|
+
"SessionStart": [
|
|
70
|
+
{
|
|
71
|
+
"hooks": [
|
|
72
|
+
{
|
|
73
|
+
"type": "command",
|
|
74
|
+
"command": "python \"C:\\Users\\<user>\\.claude\\skills\\agentic-sdlc\\scripts\\sdlc_check.py\" orient"
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Codex — in `.codex/hooks.json`, the same `SessionStart` → `{"type":"command","command":"… orient"}` shape (this replaces the legacy static-echo protocol some fixtures still carry).
|
|
84
|
+
|
|
85
|
+
Gemini CLI — wire the same command into its startup-hook mechanism if present; otherwise the step simply degrades to the manual Phase-1 reads (no capability lost).
|
|
86
|
+
|
|
87
|
+
**Usage notes:**
|
|
88
|
+
- The hook assumes the working directory is the project root (standard Claude Code hook behavior); it also accepts `--root <path>`.
|
|
89
|
+
- **Hybrid/devPNT projects**: add `--hybrid` — the hook then appends a one-line pointer to run `devpnt_mcp_get_bootstrap` for the Master Plan / Knowledge Layer and does not replicate them; the filesystem orientation (router + handoff + README) still emits.
|
|
90
|
+
- Like the CI gate (§2), if you copied `sdlc_check.py` into the repo, the hook references that copy — keep it current when you update the skill.
|
|
91
|
+
|
|
92
|
+
## 5. Skill eval battery (release gate)
|
|
93
|
+
|
|
94
|
+
The skill self-tests its own doctrine invariants. Two layers over one scenario corpus:
|
|
95
|
+
|
|
96
|
+
**Static battery — the deterministic release gate.** Run before any publish:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
python -m unittest discover -s skills/agentic-sdlc-skill/scripts -p "test_*.py"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
It aggregates the three test files (`test_plan.py` + `test_session_start.py` + `test_skill_invariants.py`) and asserts the skill's invariants: the M4 triggers/hook/worktree doctrine is present and wired, support-file pointers resolve, and the generated indexes are idempotent. A non-zero exit **blocks the release** — a failing eval is always a real regression, never flakiness: the battery is stdlib-only, makes no model/network/subprocess call (deterministic by construction). If `test_indexes_idempotent` fails, run `sdlc_check.py index` and re-run.
|
|
103
|
+
|
|
104
|
+
**Behavioral corpus — opt-in, non-CI.** `evals/scenarios/*.md` (declarative, model-neutral) + `evals/run_behavioral.py` seed a fixture and print a prompt + pass criteria for a human/agent to run and self-assess. Because live adherence is nondeterministic, this layer **never gates** — it is the reproducible way to check that, e.g., the consult trigger actually fires on a seeded repo (the "demonstrably" artifact).
|
|
105
|
+
|
|
106
|
+
**Optional CI** (same shape as §2, not mandatory): add a `run:` step invoking the `unittest discover` command above.
|
|
107
|
+
|
|
108
|
+
**T10 note:** if you copied `sdlc_check.py` and the `test_*.py` battery into the repo for CI, that copy is authoritative — keep it current when you update the skill.
|
|
@@ -156,6 +156,7 @@ gate) instead of requiring an IN_PROGRESS ANALYSIS.
|
|
|
156
156
|
|
|
157
157
|
- Read `ai_docs/audit/handoff.md` if it exists; if its Date/Branch are inconsistent, treat it as history.
|
|
158
158
|
- Read `ai_docs/README.md` (curated must-reads) and `ai_docs/INDEX.md` (generated manifest of all canonical docs) to know what exists before exploring the code. `solutions/` and `audit/` are not indexed per file: search them with glob/grep.
|
|
159
|
+
- Optional: a SessionStart hook (`ENFORCEMENT.md` §4) can emit this orientation automatically at session start (README + INDEX + guide router + handoff + triage reminder); when it is not wired, do these reads manually as above. The hook is a convenience, never a requirement — it introduces no Python dependency for the process itself and fails open (a missing/empty `ai_docs/` never blocks the session).
|
|
159
160
|
- If `ai_docs/` is missing or incomplete, create the structure and minimal documents by analyzing the project in batches.
|
|
160
161
|
- In Standalone use `ai_docs/audit/audit_plan.md` for mapping and state.
|
|
161
162
|
- In Hybrid prefer the devPNT/KL mapping when available; do not duplicate plan governance.
|
|
@@ -192,18 +193,22 @@ Hybrid L3:
|
|
|
192
193
|
### 4. Development and Testing
|
|
193
194
|
|
|
194
195
|
- Implement only after the documentation gate required by the level.
|
|
196
|
+
- Isolate the work: run an L3 change on its own branch. In Hybrid, prefer a git worktree from the start — a running devPNT server locks `.devpnt/*.db` and blocks in-place branch switches/merges in the primary worktree.
|
|
195
197
|
- Modify surgically, consistently with the plan.
|
|
196
198
|
- Implementation work follows the TDD discipline in `tdd.md` (RED/GREEN/REFACTOR — the L2/L3 default; record the reason when it does not apply).
|
|
199
|
+
- Before implementing (L2/L3; L1 exempt), **consult the guide router** for a guide covering the task and read it first — the consult trigger (`guides.md` §0, summarized under `## Operative Guides`). A targeted description match, not a blanket read.
|
|
197
200
|
- If the environment does not allow automated tests, declare the alternative verification and the reason.
|
|
198
201
|
- For bugs (L2/L3), follow the systematic debugging method in `debugging.md`.
|
|
199
202
|
- Circuit breaker: after 3 consecutive runs without progress on the tests, stop, switch to the systematic method in `debugging.md`, and ask for instructions if still stuck.
|
|
200
203
|
- Update the ANALYSIS Diary or the Action Plan when you complete milestones, hit blockers or change decisions.
|
|
204
|
+
- **Opt-in subagent execution**: for an L3 with an approved design, the orchestrator MAY execute the work via subagents per `dispatch.md`, gated by `sdlc_check.py plan validate` ("no valid plan, no dispatch"); default stays same-session. Hybrid: the executable `PLAN_[feature].md` is `derived-from` the accepted E-TDD, never independently authored.
|
|
201
205
|
|
|
202
206
|
### 5. Closure
|
|
203
207
|
|
|
204
208
|
- Run the relevant tests/lint/smoke checks.
|
|
205
209
|
- For the review itself follow `review.md` (requesting and receiving findings) — the single definition, intended for reuse by the Hybrid review gates (devPNT-side wiring out of this unit's scope).
|
|
206
210
|
- Verify alignment with the local Vision or the devPNT M-VISION.
|
|
211
|
+
- If the work was governed by user-provided indications and is reusable, **PROPOSE distilling a guide** (proactive trigger, `guides.md` §1) — a proposal for the user, never a silent write, never from model knowledge.
|
|
207
212
|
- Update only the documents actually impacted.
|
|
208
213
|
- **Aligned indexes (Poka-Yoke)**: if you created, moved or removed canonical documents (`vision/`, `reference/`, `architecture/`, `functional/`, `strategic/`):
|
|
209
214
|
- regenerate the manifest with `sdlc_check.py index` (writes `ai_docs/INDEX.md`) — never write it by hand;
|
|
@@ -215,6 +220,7 @@ Hybrid L3:
|
|
|
215
220
|
- In Hybrid propose ADR/KL updates when there were architectural decisions.
|
|
216
221
|
- In Standalone, if the project adopts `sdlc_check.py`, run `python <skill_dir>/scripts/sdlc_check.py check --root <project_root>` or the equivalent local copy.
|
|
217
222
|
- Updated documents must travel in the same commit/PR as the code they describe.
|
|
223
|
+
- **Branch/worktree hygiene**: an L3 ran on its own branch (Phase 4) — close it with an explicit merge decision (merge, keep open, or discard) and clean up the branch/worktree; never leave orphan branches. In Hybrid, the running devPNT server locks `.devpnt/*.db`, so the merge is done from a separate git worktree or via a ref-only push, never an in-place branch switch in the primary worktree.
|
|
218
224
|
|
|
219
225
|
## ai_docs documents: two indexes + lifecycle
|
|
220
226
|
|
|
@@ -243,6 +249,11 @@ Legacy note: the validator also accepts the deprecated Italian frontmatter keys
|
|
|
243
249
|
|
|
244
250
|
## Operative Guides
|
|
245
251
|
|
|
252
|
+
Guides are **consulted, created, and proposed** — three moments; the mechanics live once in `guides.md`:
|
|
253
|
+
- **Consult (before acting):** before operative L2/L3 work (L1 exempt), check the guide router for a guide covering the task and read the match first — a targeted description match, never a blanket read. → `guides.md` §0.
|
|
254
|
+
- **Create (from user indications):** the origin+purpose test below.
|
|
255
|
+
- **Propose proactively (after success):** after reusable, user-indication-governed work, PROPOSE distilling a guide — a proposal, never a silent write, never from model knowledge. → `guides.md` §1.
|
|
256
|
+
|
|
246
257
|
Trigger test: the user hands over indications to follow (origin = user, not model
|
|
247
258
|
knowledge) meant to govern how the agent operates (purpose = operative), not just
|
|
248
259
|
inform an answer. Both hold → distill into `ai_docs/reference/GUIDE_[topic].md`.
|
|
@@ -264,6 +275,7 @@ regenerate with `sdlc_check.py index`.
|
|
|
264
275
|
The prompt is not enforcement. When the project needs repeatable guarantees:
|
|
265
276
|
- read `ENFORCEMENT.md`;
|
|
266
277
|
- use `scripts/sdlc_check.py validate --strict` in CI;
|
|
267
|
-
- use `scripts/sdlc_check.py gate` only for security-critical directories, not for the whole repository
|
|
278
|
+
- use `scripts/sdlc_check.py gate` only for security-critical directories, not for the whole repository;
|
|
279
|
+
- (skill development) the self-eval battery `python -m unittest discover -s scripts -p "test_*.py"` guards the skill's own doctrine invariants and is the deterministic release gate — see `ENFORCEMENT.md` §5.
|
|
268
280
|
|
|
269
281
|
The validator is a support, not a universal prerequisite: the skill must stay usable in environments without Python or hooks, declaring what it cannot verify automatically.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Subagent Execution Discipline
|
|
2
|
+
|
|
3
|
+
Opt-in orchestration for L3 work with an approved design: the orchestrator
|
|
4
|
+
drives a `PLAN_[feature].md` through subagents instead of implementing every
|
|
5
|
+
task in the same session. Default stays same-session; this is an escalation,
|
|
6
|
+
never a requirement.
|
|
7
|
+
|
|
8
|
+
## Trigger
|
|
9
|
+
|
|
10
|
+
Only for L3 (an approved E-TDD in Hybrid, or an ANALYSIS Action Plan in
|
|
11
|
+
Standalone). Never for L1/L2 — the plan/ledger machinery is overhead a small
|
|
12
|
+
change does not need. The plan is always `derived-from` the accepted design:
|
|
13
|
+
it is never independently authored, exactly like an E-TDD is never authored
|
|
14
|
+
without an E-ISP.
|
|
15
|
+
|
|
16
|
+
## The loop
|
|
17
|
+
|
|
18
|
+
1. `sdlc_check.py plan validate PLAN_[feature].md` — zero-execution schema +
|
|
19
|
+
confinement + ledger cross-check. Non-zero exit = **no dispatch**. This is
|
|
20
|
+
the hard gate: "no valid plan, no dispatch."
|
|
21
|
+
2. For each task, in plan order:
|
|
22
|
+
- Read the task's status from the sidecar ledger
|
|
23
|
+
(`PLAN_[feature].ledger.json`). `status: done` (exact sentinel) → skip,
|
|
24
|
+
never re-dispatch. Anything else (pending, failed, missing, or a
|
|
25
|
+
corrupt-but-parseable entry) → treat as pending and dispatch.
|
|
26
|
+
- `sdlc_check.py plan brief PLAN_[feature].md --task <id>` — prints the
|
|
27
|
+
task block, the `produces` of prior-order tasks (interfaces), and
|
|
28
|
+
`guides` pointers (paths, never pasted content) to stdout.
|
|
29
|
+
- Spawn the subagent with that brief as its entire context window.
|
|
30
|
+
- Run `task.verify` out of band (the orchestrator executes it — the
|
|
31
|
+
validator only ever prints it, never runs it) plus the one-shot review
|
|
32
|
+
below.
|
|
33
|
+
- Write `{status, verify_result, timestamp}` back to the ledger. The
|
|
34
|
+
validator never writes the ledger — single-writer, orchestrator-owned.
|
|
35
|
+
|
|
36
|
+
**Guide consumption under dispatch.** Selecting each task's `guides` field IS the
|
|
37
|
+
consult trigger (`guides.md` §0) applied at plan-authoring time: the orchestrator
|
|
38
|
+
runs the router lookup (project router `ai_docs/reference/INDEX.md` + the agent-KB
|
|
39
|
+
router) when populating `guides`. A dispatched context-free subagent does **NOT**
|
|
40
|
+
run its own router consult — it reads the guide pointers handed to it in the
|
|
41
|
+
brief. (Proactive guide-creation stays at closure — the same broad final pass
|
|
42
|
+
below — so it needs no separate dispatch hook.)
|
|
43
|
+
|
|
44
|
+
## Model tiers (client-relative, no provider names)
|
|
45
|
+
|
|
46
|
+
- Default dispatch: **economy** implementer tier.
|
|
47
|
+
- After **two** consecutive `verify_result: fail` on the same task: escalate
|
|
48
|
+
to the **deep** tier for the retry (ADR 2026-07-02). Do not escalate on the
|
|
49
|
+
first failure — a single fail is often a brief or environment issue, not a
|
|
50
|
+
capability gap.
|
|
51
|
+
|
|
52
|
+
## Review slots — one-shot, not iterative
|
|
53
|
+
|
|
54
|
+
Exactly three review touches per task, never a loop:
|
|
55
|
+
|
|
56
|
+
1. Inline self-review by the implementer subagent before it reports done
|
|
57
|
+
(the standard critical-review pass, not a separate call).
|
|
58
|
+
2. One reviewer pass per task (Hybrid: reuse the devPNT code-review gate;
|
|
59
|
+
Standalone: the `review.md` discipline).
|
|
60
|
+
3. One broad final pass over the whole plan at closure, after all tasks are
|
|
61
|
+
DONE — catches cross-task drift a per-task review cannot see.
|
|
62
|
+
|
|
63
|
+
If a review FAILs, fix and re-run `verify` — that is a normal loop iteration
|
|
64
|
+
via the ledger's fail path, not an extra review slot.
|
|
65
|
+
|
|
66
|
+
## Ledger protocol summary
|
|
67
|
+
|
|
68
|
+
Read → skip-if-done → dispatch-if-pending → write. The ledger is the only
|
|
69
|
+
memory the loop needs across sessions or context compaction: a resumed
|
|
70
|
+
orchestrator re-reads it and picks up exactly where it left off, never
|
|
71
|
+
re-running a DONE task.
|
|
72
|
+
|
|
73
|
+
## Degradation
|
|
74
|
+
|
|
75
|
+
No subagent-spawning tool available → the orchestrator runs each task in the
|
|
76
|
+
same session, against the same plan and ledger, with the same one-shot review
|
|
77
|
+
slots. No capability is lost, only the parallelism/isolation subagents would
|
|
78
|
+
have added.
|
|
79
|
+
|
|
80
|
+
## Hybrid note
|
|
81
|
+
|
|
82
|
+
The plan's `derived-from` points at the accepted E-TDD document key. Per-task
|
|
83
|
+
review reuses the devPNT independent reviewers (§4.6 code review gate) rather
|
|
84
|
+
than restating review doctrine — see `review.md` for the single definition
|
|
85
|
+
both modes share.
|
|
@@ -17,6 +17,26 @@ when a task needs detail. Two levels, both produced by this pipeline:
|
|
|
17
17
|
A guide that restates the source at length is as wrong as a fragmented one:
|
|
18
18
|
completeness is guaranteed by the book level, economy by the synthesis level.
|
|
19
19
|
|
|
20
|
+
## 0. Consuming a guide (consult before acting)
|
|
21
|
+
|
|
22
|
+
Guides only pay off if they reach the work they govern. **Before operative work,
|
|
23
|
+
proportional to triage** — L1 is exempt; engaged for L2/L3 — ask: *does a guide
|
|
24
|
+
already cover how to do this well?*
|
|
25
|
+
|
|
26
|
+
- **Scan the router, not the guides.** Read the when-to-consult descriptions in
|
|
27
|
+
the guide router `ai_docs/reference/INDEX.md` **and** the agent-KB router
|
|
28
|
+
`~/.agentic-sdlc/ai_docs/reference/INDEX.md` (if present). Match by topic.
|
|
29
|
+
- **On a match, read that guide's synthesis whole** before acting (the snapshot
|
|
30
|
+
stays on demand via the section markers). No match → proceed normally.
|
|
31
|
+
- **Targeted match, never blanket.** Open only the guide whose description fits
|
|
32
|
+
the task — never load every guide, which would reintroduce the exact token
|
|
33
|
+
cost the "point to them" model exists to avoid. The two-level model (compact
|
|
34
|
+
synthesis / verbatim snapshot) already bounds a single guide's cost.
|
|
35
|
+
- **Under subagent dispatch** the consult happens at plan-authoring time (the
|
|
36
|
+
orchestrator populates each task's `guides` field); a context-free subagent
|
|
37
|
+
reads the pointers it was handed and does not run its own router lookup. See
|
|
38
|
+
`dispatch.md`.
|
|
39
|
+
|
|
20
40
|
## 1. When to trigger
|
|
21
41
|
|
|
22
42
|
Trigger test is origin + purpose, not content taxonomy (no "is this technical
|
|
@@ -34,6 +54,20 @@ Never manufacture a guide from model knowledge. If the user asks for "a guide
|
|
|
34
54
|
on X" without handing over source material, ask for the source first — a
|
|
35
55
|
guide with no `distilled_from` is not this pipeline's output.
|
|
36
56
|
|
|
57
|
+
### Proactive trigger (after reusable success)
|
|
58
|
+
|
|
59
|
+
The trigger above is reactive (the user hands material over). It has a proactive
|
|
60
|
+
twin: **after completing work that was governed by user-provided indications and
|
|
61
|
+
is plausibly reusable** — it would prepare a future task — **PROPOSE** distilling
|
|
62
|
+
a guide.
|
|
63
|
+
|
|
64
|
+
- It is a **proposal to the user**, routed into this same pipeline (§2 onward):
|
|
65
|
+
the user confirms topic/scope before any file is written.
|
|
66
|
+
- **Never a silent write, never from model knowledge.** If the work was not
|
|
67
|
+
governed by user-provided material there is nothing to distill — do not invent
|
|
68
|
+
a guide from general knowledge (the `distilled_from` fidelity constraint, §3,
|
|
69
|
+
is absolute). This adds a moment to PROPOSE, not a new writer.
|
|
70
|
+
|
|
37
71
|
## 2. Pipeline
|
|
38
72
|
|
|
39
73
|
0. **Search before creating (DRY — one CURRENT guide per topic).** Before
|
|
@@ -59,6 +59,9 @@ GUIDE_MARKER_RE = re.compile(r"\[(?:source:[^\]]+|not covered by source)\]")
|
|
|
59
59
|
# AGENTIC_SDLC_KB_ROOT env var is a TEST/CI seam only (scenario battery must
|
|
60
60
|
# not touch the real user KB); the documented product path is fixed.
|
|
61
61
|
DEFAULT_KB_ROOT = Path(os.environ.get("AGENTIC_SDLC_KB_ROOT", "")) if os.environ.get("AGENTIC_SDLC_KB_ROOT") else Path.home() / ".agentic-sdlc"
|
|
62
|
+
# Subagent Execution (Feature A): a PLAN_[feature].md task must carry these keys,
|
|
63
|
+
# plus at least one of paths/produces (checked separately in cmd_plan).
|
|
64
|
+
PLAN_TASK_REQUIRED = ("id", "title", "verify")
|
|
62
65
|
|
|
63
66
|
# Deprecated Italian frontmatter keys, mapped to the canonical English ones.
|
|
64
67
|
LEGACY_KEYS = {"stato": "status", "livello": "level",
|
|
@@ -82,6 +85,20 @@ except Exception:
|
|
|
82
85
|
pass
|
|
83
86
|
|
|
84
87
|
|
|
88
|
+
# --- orient (SessionStart hook) ---
|
|
89
|
+
# Fixed, hard-coded doc set (label, path-relative-to-root). No content- or
|
|
90
|
+
# user-derived paths -> no traversal input (P-TM T3); confine_under is
|
|
91
|
+
# defense-in-depth. Emitted at session start by the orient subcommand.
|
|
92
|
+
ORIENT_DOCS = [
|
|
93
|
+
("Reading guide (README)", "ai_docs/README.md"),
|
|
94
|
+
("Canonical manifest (INDEX)", "ai_docs/INDEX.md"),
|
|
95
|
+
("Guide router (when-to-consult)", "ai_docs/reference/INDEX.md"),
|
|
96
|
+
("Last session handoff", "ai_docs/audit/handoff.md"),
|
|
97
|
+
]
|
|
98
|
+
ORIENT_PER_DOC_CHARS = 6000 # per-doc truncation
|
|
99
|
+
ORIENT_MAX_TOTAL_CHARS = 16000 # total ingestion cap (P-TM T2); tunable
|
|
100
|
+
|
|
101
|
+
|
|
85
102
|
# ----------------------------------------------------------------- utilities
|
|
86
103
|
|
|
87
104
|
def utc_now_iso():
|
|
@@ -106,6 +123,25 @@ def require_ai_docs(root, command):
|
|
|
106
123
|
return True
|
|
107
124
|
|
|
108
125
|
|
|
126
|
+
def confine_under(base, rel):
|
|
127
|
+
"""Fail-closed path confinement: resolve `rel` under `base` and require the
|
|
128
|
+
result to stay inside `base`. Returns None (reject) if `rel` is absolute,
|
|
129
|
+
contains a '..' part, or resolves outside `base` (including an OSError
|
|
130
|
+
during resolution, e.g. an unresolvable/reparse-point path on Windows).
|
|
131
|
+
Single source for path confinement (T2/T3): reused by check_kb_collisions'
|
|
132
|
+
`overrides:` check and cmd_validate's `distilled_from` check, and by the
|
|
133
|
+
new `plan` command's paths/consumes/produces/guides confinement."""
|
|
134
|
+
p = Path(rel)
|
|
135
|
+
if p.is_absolute() or ".." in p.parts:
|
|
136
|
+
return None
|
|
137
|
+
try:
|
|
138
|
+
t = (base / rel).resolve()
|
|
139
|
+
t.relative_to(base.resolve())
|
|
140
|
+
return t
|
|
141
|
+
except (ValueError, OSError):
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
|
|
109
145
|
def read_text(path):
|
|
110
146
|
# utf-8-sig: strips a leading BOM (files authored on Windows) so the
|
|
111
147
|
# frontmatter '---' on line 0 stays recognizable; reads plain utf-8 otherwise.
|
|
@@ -407,15 +443,10 @@ def check_kb_collisions(root, project_guides, errors, warnings):
|
|
|
407
443
|
ov = (meta.get("overrides") or "").strip()
|
|
408
444
|
if ov:
|
|
409
445
|
# T6: untrusted cross-root pointer — distilled_from parity, fail closed
|
|
410
|
-
|
|
411
|
-
if
|
|
412
|
-
errors.append(f"{rel}: overrides '{ov}' is absolute
|
|
413
|
-
|
|
414
|
-
try:
|
|
415
|
-
target = (kb_ref / ov).resolve()
|
|
416
|
-
target.relative_to(kb_ref.resolve())
|
|
417
|
-
except (ValueError, OSError):
|
|
418
|
-
errors.append(f"{rel}: overrides '{ov}' escapes the KB reference dir — rejected (fail closed)")
|
|
446
|
+
target = confine_under(kb_ref, ov)
|
|
447
|
+
if target is None:
|
|
448
|
+
errors.append(f"{rel}: overrides '{ov}' is absolute, contains '..', or escapes the KB "
|
|
449
|
+
"reference dir — rejected (fail closed)")
|
|
419
450
|
continue
|
|
420
451
|
if not target.is_file():
|
|
421
452
|
warnings.append(f"{rel}: overrides target '{ov}' not found in KB ({kb_ref})")
|
|
@@ -572,15 +603,9 @@ def cmd_validate(root, strict=False):
|
|
|
572
603
|
+ "; ".join(unmarked[:5]))
|
|
573
604
|
# (c) distilled_from confinement — fail closed (P-TM T6, distilled_from vector)
|
|
574
605
|
df = meta.get("distilled_from", "")
|
|
575
|
-
if df:
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
else:
|
|
579
|
-
target = (root / df).resolve()
|
|
580
|
-
try:
|
|
581
|
-
target.relative_to(root.resolve())
|
|
582
|
-
except ValueError:
|
|
583
|
-
errors.append(f"{rel}: distilled_from '{df}' resolves outside the project root: rejected")
|
|
606
|
+
if df and confine_under(root, df) is None:
|
|
607
|
+
errors.append(f"{rel}: distilled_from '{df}' is absolute, contains '..', or resolves "
|
|
608
|
+
"outside the project root: rejected")
|
|
584
609
|
check_kb_collisions(root, guides, errors, warnings)
|
|
585
610
|
# guide-router alignment (mirror of the root-manifest check)
|
|
586
611
|
gidx = root / "ai_docs" / "reference" / "INDEX.md"
|
|
@@ -806,6 +831,226 @@ def cmd_gate(args):
|
|
|
806
831
|
return 2
|
|
807
832
|
|
|
808
833
|
|
|
834
|
+
# --------------------------------------------------------------------- plan
|
|
835
|
+
# Subagent Execution (Feature A). Zero-execution surface: this section and
|
|
836
|
+
# everything it calls MUST NOT spawn a process (no subprocess/os.system/eval/
|
|
837
|
+
# exec, no git_* helper). It validates a PLAN_[feature].md and prints a task
|
|
838
|
+
# brief as text; the orchestrator (dispatch.md) is the sole executor.
|
|
839
|
+
|
|
840
|
+
_PLAN_JSON_RE = re.compile(r"```json\s*\n(.*?)```", re.DOTALL)
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
def extract_plan_json(text):
|
|
844
|
+
"""Extract the first fenced ```json block from a PLAN_[feature].md body.
|
|
845
|
+
Returns (data, "") on success, or (None, reason) on any failure. Never
|
|
846
|
+
raises: a malformed or missing block is a validation failure, not a crash."""
|
|
847
|
+
m = _PLAN_JSON_RE.search(text or "")
|
|
848
|
+
if not m:
|
|
849
|
+
return None, "no fenced ```json block found in the plan file"
|
|
850
|
+
try:
|
|
851
|
+
data = json.loads(m.group(1))
|
|
852
|
+
except (ValueError, TypeError) as e:
|
|
853
|
+
return None, f"malformed JSON in the plan block: {e}"
|
|
854
|
+
if not isinstance(data, dict):
|
|
855
|
+
return None, "plan JSON block must be a JSON object"
|
|
856
|
+
return data, ""
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
def load_ledger(path):
|
|
860
|
+
"""Read the sidecar ledger {"<task_id>": {"status", "verify_result",
|
|
861
|
+
"timestamp"}}. Absent file -> ({}, ""). Malformed/unreadable -> ({}, reason).
|
|
862
|
+
Never raises, never hangs: the ledger is untrusted state read on every call."""
|
|
863
|
+
if not path.is_file():
|
|
864
|
+
return {}, ""
|
|
865
|
+
try:
|
|
866
|
+
raw = read_text(path)
|
|
867
|
+
data = json.loads(raw)
|
|
868
|
+
except (ValueError, TypeError, OSError) as e:
|
|
869
|
+
return {}, f"ledger '{path}' unreadable/malformed, treating as empty: {e}"
|
|
870
|
+
if not isinstance(data, dict):
|
|
871
|
+
return {}, f"ledger '{path}' is not a JSON object, treating as empty"
|
|
872
|
+
return data, ""
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
def _confine_or_reject(base, rel, label, rel_label, errors):
|
|
876
|
+
t = confine_under(base, rel)
|
|
877
|
+
if t is None:
|
|
878
|
+
errors.append(f"{rel_label}: {label} '{rel}' is absolute, contains '..', or escapes "
|
|
879
|
+
f"'{base}' — rejected (fail closed)")
|
|
880
|
+
return t
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
def _validate_plan_tasks(root, data, rel_label, errors, warnings):
|
|
884
|
+
"""Shared core of `plan validate`/`plan brief`: schema + confinement checks.
|
|
885
|
+
Returns the task list (possibly empty) on success; errors/warnings are
|
|
886
|
+
appended in place. Callers decide the exit code."""
|
|
887
|
+
tasks = data.get("tasks")
|
|
888
|
+
if not isinstance(tasks, list) or not tasks:
|
|
889
|
+
errors.append(f"{rel_label}: 'tasks' must be a non-empty JSON array")
|
|
890
|
+
return []
|
|
891
|
+
ref_dir = root / "ai_docs" / "reference"
|
|
892
|
+
kb_ref = DEFAULT_KB_ROOT / "ai_docs" / "reference"
|
|
893
|
+
seen_ids = set()
|
|
894
|
+
for i, task in enumerate(tasks):
|
|
895
|
+
loc = f"{rel_label}: task[{i}]"
|
|
896
|
+
if not isinstance(task, dict):
|
|
897
|
+
errors.append(f"{loc}: not a JSON object")
|
|
898
|
+
continue
|
|
899
|
+
missing = [k for k in PLAN_TASK_REQUIRED if not task.get(k)]
|
|
900
|
+
if missing:
|
|
901
|
+
errors.append(f"{loc}: missing required field(s): {', '.join(missing)}")
|
|
902
|
+
if not task.get("paths") and not task.get("produces"):
|
|
903
|
+
errors.append(f"{loc}: must declare at least one of 'paths'/'produces'")
|
|
904
|
+
tid = task.get("id")
|
|
905
|
+
if tid:
|
|
906
|
+
if tid in seen_ids:
|
|
907
|
+
errors.append(f"{loc}: duplicate task id '{tid}'")
|
|
908
|
+
seen_ids.add(tid)
|
|
909
|
+
for key in ("paths", "consumes", "produces"):
|
|
910
|
+
for p in (task.get(key) or []):
|
|
911
|
+
_confine_or_reject(root, p, key, loc, errors)
|
|
912
|
+
for g in (task.get("guides") or []):
|
|
913
|
+
in_project = confine_under(ref_dir, g)
|
|
914
|
+
in_kb = confine_under(kb_ref, g)
|
|
915
|
+
if in_project is None and in_kb is None:
|
|
916
|
+
errors.append(f"{loc}: guide '{g}' is not confined under the project reference "
|
|
917
|
+
f"dir ({ref_dir}) or the agent KB reference dir ({kb_ref}) — rejected")
|
|
918
|
+
return tasks
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def cmd_plan(root, args):
|
|
922
|
+
"""Zero-execution: validates/briefs a PLAN_[feature].md. Never spawns a
|
|
923
|
+
process, never calls a git_* helper, never runs the opaque `verify` text —
|
|
924
|
+
it is printed, not executed."""
|
|
925
|
+
plan_path = Path(args.file)
|
|
926
|
+
if not plan_path.is_absolute():
|
|
927
|
+
plan_path = root / plan_path
|
|
928
|
+
if not plan_path.is_file():
|
|
929
|
+
sys.stderr.write(f"[plan] plan file not found: {plan_path}\n")
|
|
930
|
+
return 2
|
|
931
|
+
rel_label = str(plan_path)
|
|
932
|
+
data, reason = extract_plan_json(read_text(plan_path))
|
|
933
|
+
if data is None:
|
|
934
|
+
sys.stderr.write(f"[plan] {rel_label}: {reason}\n")
|
|
935
|
+
return 2
|
|
936
|
+
|
|
937
|
+
errors, warnings = [], []
|
|
938
|
+
tasks = _validate_plan_tasks(root, data, rel_label, errors, warnings)
|
|
939
|
+
|
|
940
|
+
ledger_path = plan_path.with_name(plan_path.stem + ".ledger.json")
|
|
941
|
+
ledger, ledger_reason = load_ledger(ledger_path)
|
|
942
|
+
if ledger_reason:
|
|
943
|
+
warnings.append(ledger_reason)
|
|
944
|
+
if not errors:
|
|
945
|
+
task_ids = {t.get("id") for t in tasks if isinstance(t, dict)}
|
|
946
|
+
for lid in ledger:
|
|
947
|
+
if lid not in task_ids:
|
|
948
|
+
warnings.append(f"ledger id '{lid}' not found in {rel_label}: orphaned entry (not fatal)")
|
|
949
|
+
|
|
950
|
+
for w in warnings:
|
|
951
|
+
sys.stderr.write(f"[warn] {w}\n")
|
|
952
|
+
for e in errors:
|
|
953
|
+
sys.stderr.write(f"[ERROR] {e}\n")
|
|
954
|
+
|
|
955
|
+
if args.plan_cmd == "validate":
|
|
956
|
+
if errors:
|
|
957
|
+
sys.stderr.write(f"\n[plan] validate: {len(errors)} errors, {len(warnings)} warnings.\n")
|
|
958
|
+
return 2
|
|
959
|
+
print(f"[ok] {rel_label}: plan valid ({len(tasks)} task(s), {len(warnings)} warning(s)).")
|
|
960
|
+
return 0
|
|
961
|
+
|
|
962
|
+
# brief
|
|
963
|
+
if errors:
|
|
964
|
+
sys.stderr.write(f"\n[plan] brief: plan is invalid, refusing to brief ({len(errors)} errors).\n")
|
|
965
|
+
return 2
|
|
966
|
+
target = None
|
|
967
|
+
for t in tasks:
|
|
968
|
+
if isinstance(t, dict) and t.get("id") == args.task:
|
|
969
|
+
target = t
|
|
970
|
+
break
|
|
971
|
+
if target is None:
|
|
972
|
+
sys.stderr.write(f"[plan] brief: task id '{args.task}' not found in {rel_label}\n")
|
|
973
|
+
return 2
|
|
974
|
+
|
|
975
|
+
print(f"# Task: {target.get('id')} — {target.get('title', '')}")
|
|
976
|
+
print()
|
|
977
|
+
print("## Task block")
|
|
978
|
+
print(json.dumps(target, indent=2))
|
|
979
|
+
print()
|
|
980
|
+
print("## Produces of prior-order tasks (interfaces)")
|
|
981
|
+
prior_produces = []
|
|
982
|
+
for t in tasks:
|
|
983
|
+
if not isinstance(t, dict):
|
|
984
|
+
continue
|
|
985
|
+
if t.get("id") == target.get("id"):
|
|
986
|
+
break
|
|
987
|
+
prior_produces.extend(t.get("produces") or [])
|
|
988
|
+
if prior_produces:
|
|
989
|
+
for p in prior_produces:
|
|
990
|
+
print(f"- {p}")
|
|
991
|
+
else:
|
|
992
|
+
print("(none)")
|
|
993
|
+
print()
|
|
994
|
+
print("## Guide pointers (paths, not content)")
|
|
995
|
+
guides = target.get("guides") or []
|
|
996
|
+
if guides:
|
|
997
|
+
for g in guides:
|
|
998
|
+
print(f"- {g}")
|
|
999
|
+
else:
|
|
1000
|
+
print("(none)")
|
|
1001
|
+
print()
|
|
1002
|
+
print("## Verify (opaque text — orchestrator runs this out of band, NOT executed here)")
|
|
1003
|
+
print(target.get("verify", ""))
|
|
1004
|
+
return 0
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
def cmd_orient(args):
|
|
1008
|
+
"""SessionStart hook: emit a bounded, repo-sourced ai_docs/ orientation to
|
|
1009
|
+
stdout and ALWAYS return 0 (fail-open, P-TM T8) -- a session hook must never
|
|
1010
|
+
block the session or surface a traceback. Zero-execution (P-TM T1): reads a
|
|
1011
|
+
fixed hard-coded doc set, confine_under each (P-TM T3), size-caps the total
|
|
1012
|
+
(P-TM T2). No subprocess/eval anywhere in this call graph."""
|
|
1013
|
+
try:
|
|
1014
|
+
root = Path(args.root).resolve() if getattr(args, "root", None) else find_project_root()
|
|
1015
|
+
chunks = []
|
|
1016
|
+
total = 0
|
|
1017
|
+
truncated = False
|
|
1018
|
+
for label, rel in ORIENT_DOCS:
|
|
1019
|
+
target = confine_under(root, rel)
|
|
1020
|
+
if target is None or not target.is_file():
|
|
1021
|
+
continue
|
|
1022
|
+
try:
|
|
1023
|
+
text = read_text(target)
|
|
1024
|
+
except OSError:
|
|
1025
|
+
continue
|
|
1026
|
+
remaining = ORIENT_MAX_TOTAL_CHARS - total
|
|
1027
|
+
if remaining <= 0:
|
|
1028
|
+
truncated = True
|
|
1029
|
+
break
|
|
1030
|
+
text = text[:ORIENT_PER_DOC_CHARS]
|
|
1031
|
+
if len(text) > remaining:
|
|
1032
|
+
text = text[:remaining]
|
|
1033
|
+
truncated = True
|
|
1034
|
+
chunks.append((label, text))
|
|
1035
|
+
total += len(text)
|
|
1036
|
+
if not chunks:
|
|
1037
|
+
return 0
|
|
1038
|
+
out = ["=== Agentic SDLC -- session orientation (repo-sourced context, not authored instructions) ==="]
|
|
1039
|
+
for label, text in chunks:
|
|
1040
|
+
out.append(f"\n## {label}\n{text}")
|
|
1041
|
+
if truncated:
|
|
1042
|
+
out.append("\n[orientation truncated to the size cap -- open the files directly for full content]")
|
|
1043
|
+
out.append("\nTriage every request (Rule Zero): L1 trivial - L2 small - L3 significant - Spike. "
|
|
1044
|
+
"When in doubt, pick the higher level.")
|
|
1045
|
+
if getattr(args, "hybrid", False):
|
|
1046
|
+
out.append("\n[devPNT active] Run devpnt_mcp_get_bootstrap for the Master Plan / Knowledge Layer -- "
|
|
1047
|
+
"the orientation above is the filesystem layer, not a bootstrap duplicate.")
|
|
1048
|
+
print("\n".join(out))
|
|
1049
|
+
return 0
|
|
1050
|
+
except Exception:
|
|
1051
|
+
return 0
|
|
1052
|
+
|
|
1053
|
+
|
|
809
1054
|
# --------------------------------------------------------------------- main
|
|
810
1055
|
|
|
811
1056
|
def main(argv=None):
|
|
@@ -836,9 +1081,23 @@ def main(argv=None):
|
|
|
836
1081
|
gp.add_argument("--file", help="file path to evaluate (alternative to --hook)")
|
|
837
1082
|
gp.add_argument("--protected", default="", help="protected prefixes separated by ';' (e.g. \"src/auth;src/crypto\")")
|
|
838
1083
|
|
|
1084
|
+
sub.add_parser("orient", parents=[common, hybrid_opt],
|
|
1085
|
+
help="SessionStart hook: emit ai_docs/ orientation to stdout (fail-open, zero-execution)")
|
|
1086
|
+
|
|
1087
|
+
pp = sub.add_parser("plan", parents=[common],
|
|
1088
|
+
help="Subagent Execution: validate/brief a PLAN_[feature].md (zero-execution)")
|
|
1089
|
+
pp_sub = pp.add_subparsers(dest="plan_cmd", required=True)
|
|
1090
|
+
pv = pp_sub.add_parser("validate", help="schema + confinement + ledger cross-check (exit 2 on error)")
|
|
1091
|
+
pv.add_argument("file", help="path to the PLAN_[feature].md file")
|
|
1092
|
+
pb = pp_sub.add_parser("brief", help="print a task's brief to stdout (verify text is NOT executed)")
|
|
1093
|
+
pb.add_argument("file", help="path to the PLAN_[feature].md file")
|
|
1094
|
+
pb.add_argument("--task", required=True, help="task id to brief")
|
|
1095
|
+
|
|
839
1096
|
args = ap.parse_args(argv)
|
|
840
1097
|
if args.cmd == "gate":
|
|
841
1098
|
return cmd_gate(args)
|
|
1099
|
+
if args.cmd == "orient":
|
|
1100
|
+
return cmd_orient(args)
|
|
842
1101
|
|
|
843
1102
|
root = Path(args.root).resolve() if args.root else find_project_root()
|
|
844
1103
|
if args.cmd == "check":
|
|
@@ -851,6 +1110,8 @@ def main(argv=None):
|
|
|
851
1110
|
return cmd_stale(root, hybrid=args.hybrid)
|
|
852
1111
|
if args.cmd == "mark":
|
|
853
1112
|
return cmd_mark(root, args.paths)
|
|
1113
|
+
if args.cmd == "plan":
|
|
1114
|
+
return cmd_plan(root, args)
|
|
854
1115
|
return 0
|
|
855
1116
|
|
|
856
1117
|
|
|
@@ -189,6 +189,54 @@ Allowed frontmatter states: `PLANNED` | `IN_PROGRESS` | `COMPLETED` | `CANCELLED
|
|
|
189
189
|
<!-- max 1 page. Spike code is NOT mergeable: for production reclassify L2/L3. -->
|
|
190
190
|
```
|
|
191
191
|
|
|
192
|
+
## ai_docs/solutions/PLAN_[feature].md
|
|
193
|
+
|
|
194
|
+
Opt-in, L3 only (see `dispatch.md`): the executable task list an orchestrator
|
|
195
|
+
drives through subagents. It is `derived-from` the accepted E-TDD (Hybrid) or
|
|
196
|
+
the ANALYSIS Action Plan (Standalone) — never independently authored. The
|
|
197
|
+
validator gate is `sdlc_check.py plan validate PLAN_[feature].md` ("no valid
|
|
198
|
+
plan, no dispatch").
|
|
199
|
+
|
|
200
|
+
````markdown
|
|
201
|
+
---
|
|
202
|
+
status: DRAFT
|
|
203
|
+
derived-from: e_tdd_[feature] vX.Y
|
|
204
|
+
---
|
|
205
|
+
# Plan: [Feature]
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{
|
|
209
|
+
"tasks": [
|
|
210
|
+
{
|
|
211
|
+
"id": "T1",
|
|
212
|
+
"title": "Add the confine_under helper",
|
|
213
|
+
"paths": ["skills/agentic-sdlc-skill/scripts/sdlc_check.py"],
|
|
214
|
+
"consumes": [],
|
|
215
|
+
"produces": ["skills/agentic-sdlc-skill/scripts/sdlc_check.py#confine_under"],
|
|
216
|
+
"verify": "python skills/agentic-sdlc-skill/scripts/test_plan.py",
|
|
217
|
+
"guides": ["GUIDE_python_style.md"]
|
|
218
|
+
}
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
````
|
|
223
|
+
|
|
224
|
+
Task fields: `id`/`title`/`verify` are required; at least one of `paths`/
|
|
225
|
+
`produces` is required. `paths` are files the task touches; `consumes`/
|
|
226
|
+
`produces` declare interfaces between tasks (what an earlier task hands to a
|
|
227
|
+
later one); `guides` are pointers (paths, not pasted content) into
|
|
228
|
+
`ai_docs/reference/` or the agent-global KB. All path-shaped fields are
|
|
229
|
+
confined fail-closed under the project root (or the reference/KB root for
|
|
230
|
+
`guides`) — an absolute path or a `..` escape is rejected. `verify` is opaque
|
|
231
|
+
text: the validator only prints it (`plan brief`), never runs it — the
|
|
232
|
+
orchestrator executes it out of band.
|
|
233
|
+
|
|
234
|
+
Sidecar ledger `ai_docs/solutions/PLAN_[feature].ledger.json` (orchestrator-
|
|
235
|
+
owned, validator-read-only): `{ "<task_id>": {"status": "done", "verify_result":
|
|
236
|
+
"pass", "timestamp": "2026-07-03T00:00:00Z"} }`. Only the exact `status: done`
|
|
237
|
+
sentinel skips re-dispatch; any other value (or a missing `status`) is treated
|
|
238
|
+
as pending. A ledger id absent from the plan is a non-fatal orphan warning.
|
|
239
|
+
|
|
192
240
|
## ai_docs/audit/audit_plan.md (Standalone mode only)
|
|
193
241
|
|
|
194
242
|
The `Reference` field (git hash or ISO UTC timestamp) is managed by `sdlc_check.py mark` — do not fill it by hand. Freshness is verified with `sdlc_check.py stale`.
|