@dtlabs/ai-toolkit 0.10.1 → 0.12.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 +108 -0
- package/bin/cli.js +1457 -143
- package/docs/installation.md +74 -8
- package/docs/reference.md +60 -12
- package/lib/asset-catalog.js +52 -0
- package/lib/execution-ledger.js +515 -0
- package/package.json +9 -7
- package/{.claude → src/claude}/agents/define-feature.md +36 -60
- package/{.claude → src/claude}/agents/install-toolkit.md +30 -103
- package/{.claude → src/claude}/scripts/wb-render.js +101 -2
- package/{.claude → src/claude}/scripts/wb-validate.js +32 -2
- package/{.claude → src/claude}/skills/implement-feature/SKILL.md +4 -2
- package/{.claude → src/claude}/workflows/am-phase1.js +9 -12
- package/{.claude → src/claude}/workflows/pm-phase1.js +25 -75
- package/{.claude → src/claude}/workflows/pm-phase2.js +57 -108
- package/{.claude → src/claude}/workflows/pm-phase3.js +162 -75
- package/.claude/scripts/.gitkeep +0 -0
- /package/{.claude → src/claude}/agents/concurrency-safety-assessment.md +0 -0
- /package/{.claude → src/claude}/agents/dependency-injection-refactoring.md +0 -0
- /package/{.claude → src/claude}/agents/dependency-supply-chain-security.md +0 -0
- /package/{.claude → src/claude}/agents/developer-backend.md +0 -0
- /package/{.claude → src/claude}/agents/developer-frontend.md +0 -0
- /package/{.claude → src/claude}/agents/developer-testing.md +0 -0
- /package/{.claude → src/claude}/agents/domain-model-refactoring.md +0 -0
- /package/{.claude → src/claude}/agents/generate-requirements.md +0 -0
- /package/{.claude → src/claude}/agents/generate-tech-spec.md +0 -0
- /package/{.claude → src/claude}/agents/generate-work-breakdown.md +0 -0
- /package/{.claude → src/claude}/agents/generic-software-assessment.md +0 -0
- /package/{.claude → src/claude}/agents/god-class-decomposition.md +0 -0
- /package/{.claude → src/claude}/agents/init-agents-md.md +0 -0
- /package/{.claude → src/claude}/agents/intervention-documentation-standard.md +0 -0
- /package/{.claude → src/claude}/agents/layered-architecture-assessment.md +0 -0
- /package/{.claude → src/claude}/agents/review-solution.md +0 -0
- /package/{.claude → src/claude}/agents/security-hardening.md +0 -0
- /package/{.claude → src/claude}/agents/validate-feature-docs.md +0 -0
- /package/{.claude → src/claude}/agents/validate-work-breakdown-semantic.md +0 -0
- /package/{.claude → src/claude}/commands/assessment-status.md +0 -0
- /package/{.claude → src/claude}/commands/check-docs.md +0 -0
- /package/{.claude → src/claude}/commands/check-interventions.md +0 -0
- /package/{.claude → src/claude}/commands/feature-status.md +0 -0
- /package/{.claude → src/claude}/commands/next-intervention.md +0 -0
- /package/{.claude → src/claude}/commands/next-task.md +0 -0
- /package/{.claude → src/claude}/commands/pr-description.md +0 -0
- /package/{.claude → src/claude}/skills/assess-codebase/SKILL.md +0 -0
- /package/{.claude → src/claude}/skills/define-feature/SKILL.md +0 -0
- /package/{.claude → src/claude}/skills/hi-gaia/SKILL.md +0 -0
- /package/{.claude → src/claude}/skills/init-agents/SKILL.md +0 -0
- /package/{.claude → src/claude}/skills/install-toolkit/SKILL.md +0 -0
- /package/{.claude → src/claude}/workflows/am-phase2.js +0 -0
|
@@ -80,56 +80,53 @@ Ask the user to **confirm or correct the draft**. Then grill ONLY on the `❓` g
|
|
|
80
80
|
|
|
81
81
|
## Phase 1 — Setup
|
|
82
82
|
|
|
83
|
-
### 1a.
|
|
83
|
+
### 1a. Resolve the features root (MANDATORY — single source of truth)
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
Before any file-system operation, run the following command **once** via Bash and capture its stdout into `featuresRoot`:
|
|
86
86
|
|
|
87
|
+
```bash
|
|
88
|
+
featuresRoot=$(ai-toolkit resolve-features-root) && echo "$featuresRoot"
|
|
87
89
|
```
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
|
|
91
|
+
If the command exits non-zero, stop immediately and report the error to the user — do not proceed.
|
|
92
|
+
|
|
93
|
+
This single captured value (`featuresRoot`) is the **sole source of truth** for the features root in every subsequent step. Do **not** hard-code `docs/features/`, `internal_docs/features/`, or any other path. Both the directory under which `feature.md` is written (step 1d) and the `--dir` argument passed to the ledger facade must use this exact value — they must always agree, with no divergent or duplicate roots.
|
|
94
|
+
|
|
95
|
+
### 1b. Discover the next FTR number
|
|
96
|
+
|
|
97
|
+
Scan `{featuresRoot}` for existing folders matching the pattern `FTR-[0-9]+*`. Extract the highest number and increment by 1. If no folders exist, start at `FTR-001`.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
{featuresRoot}/FTR-001-user-management/ → max = 1
|
|
101
|
+
{featuresRoot}/FTR-002-product-catalog/ → max = 2
|
|
90
102
|
→ next = FTR-003
|
|
91
103
|
```
|
|
92
104
|
|
|
93
|
-
###
|
|
105
|
+
### 1c. Ask for a feature name
|
|
94
106
|
|
|
95
107
|
Ask the user: **"What is the name of this feature?"** (short, descriptive, in their language). Use it to build the folder slug in kebab-case.
|
|
96
108
|
|
|
97
109
|
Example: "Supplier Onboarding" → `FTR-003-supplier-onboarding`
|
|
98
110
|
|
|
99
|
-
###
|
|
111
|
+
### 1d. Create feature directory and open ledger entry
|
|
100
112
|
|
|
101
|
-
Once you know the PREFIX (from
|
|
113
|
+
Once you know the PREFIX (from 1b) and the feature slug (from 1c), do the following **before asking any grilling questions**:
|
|
102
114
|
|
|
103
|
-
1. Compute the
|
|
104
|
-
- `featureDir` = `
|
|
105
|
-
- `
|
|
115
|
+
1. Compute `featureDir` using the captured `featuresRoot`:
|
|
116
|
+
- `featureDir` = `{featuresRoot}/{PREFIX}-{slug}` (e.g. `{featuresRoot}/FTR-003-supplier-onboarding`)
|
|
117
|
+
- This path is also the `--dir` value for every `ai-toolkit ledger` call that follows.
|
|
106
118
|
|
|
107
119
|
2. Create the feature directory via Bash:
|
|
108
120
|
```bash
|
|
109
|
-
mkdir -p {featureDir}
|
|
121
|
+
mkdir -p "{featureDir}"
|
|
110
122
|
```
|
|
111
123
|
|
|
112
|
-
3.
|
|
124
|
+
3. Open the ledger entry via the facade. Run the following command via Bash (quote the `--dir` path to handle spaces):
|
|
113
125
|
```bash
|
|
114
|
-
|
|
126
|
+
ai-toolkit ledger open --dir "{featureDir}" --prefix {PREFIX} --agent define-feature:define --phase define --model sonnet --attempt 1
|
|
115
127
|
```
|
|
116
128
|
|
|
117
|
-
|
|
118
|
-
```json
|
|
119
|
-
[
|
|
120
|
-
{
|
|
121
|
-
"agent": "define-feature:define",
|
|
122
|
-
"phase": "define",
|
|
123
|
-
"model": "sonnet",
|
|
124
|
-
"status": "running",
|
|
125
|
-
"phase_delta_tokens": 0,
|
|
126
|
-
"started_at": "<started_at from step 3>",
|
|
127
|
-
"completed_at": null
|
|
128
|
-
}
|
|
129
|
-
]
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
The ledger records that the define-feature agent is now running. Proceed to Phase 2.
|
|
129
|
+
The ledger now records that the define-feature agent is running. Proceed to Phase 2.
|
|
133
130
|
|
|
134
131
|
---
|
|
135
132
|
|
|
@@ -254,12 +251,12 @@ Synthesize all answers into a structured `feature.md` file.
|
|
|
254
251
|
### Output path
|
|
255
252
|
|
|
256
253
|
```
|
|
257
|
-
|
|
254
|
+
{featuresRoot}/{PREFIX}-{slug}/feature.md
|
|
258
255
|
```
|
|
259
256
|
|
|
260
|
-
Example: `
|
|
257
|
+
Example: `{featuresRoot}/FTR-003-supplier-onboarding/feature.md`
|
|
261
258
|
|
|
262
|
-
The directory was created in Phase
|
|
259
|
+
The directory was created in Phase 1d using `featuresRoot`. If for any reason it doesn't exist, create it now using the same `featuresRoot` path — do not substitute a different root.
|
|
263
260
|
|
|
264
261
|
### feature.md template
|
|
265
262
|
|
|
@@ -352,34 +349,13 @@ For each entity: name, key fields (name, type, constraints), relationships.
|
|
|
352
349
|
|
|
353
350
|
### 4b. Finalize ledger entry
|
|
354
351
|
|
|
355
|
-
After writing `feature.md`,
|
|
352
|
+
After writing `feature.md`, close the ledger entry via the facade. Run the following command via Bash (quote the `--dir` path to handle spaces):
|
|
356
353
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
```
|
|
354
|
+
```bash
|
|
355
|
+
ai-toolkit ledger close --dir "{featureDir}" --prefix {PREFIX} --agent define-feature:define --attempt 1
|
|
356
|
+
```
|
|
361
357
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
3. Write the updated ledger file using the Write tool, replacing the entry's fields:
|
|
365
|
-
- `"status"`: `"done"`
|
|
366
|
-
- `"completed_at"`: the timestamp captured in step 1
|
|
367
|
-
- `"phase_delta_tokens"`: leave as `0` — token measurement is not available to the agent directly
|
|
368
|
-
|
|
369
|
-
Example final ledger content:
|
|
370
|
-
```json
|
|
371
|
-
[
|
|
372
|
-
{
|
|
373
|
-
"agent": "define-feature:define",
|
|
374
|
-
"phase": "define",
|
|
375
|
-
"model": "sonnet",
|
|
376
|
-
"status": "done",
|
|
377
|
-
"phase_delta_tokens": 0,
|
|
378
|
-
"started_at": "<original started_at>",
|
|
379
|
-
"completed_at": "<completed_at from step 1>"
|
|
380
|
-
}
|
|
381
|
-
]
|
|
382
|
-
```
|
|
358
|
+
**Important:** `--tokens` is intentionally omitted. The ledger facade records `phase_delta_tokens` as `null` when `--tokens` is not supplied. `define-feature` is an interactive/LLM activity whose token consumption cannot be observed from within the agent itself — do **not** pass `0` or any estimate.
|
|
383
359
|
|
|
384
360
|
---
|
|
385
361
|
|
|
@@ -392,7 +368,7 @@ After writing `feature.md`:
|
|
|
392
368
|
3. Tell the user the next step:
|
|
393
369
|
|
|
394
370
|
```
|
|
395
|
-
✅ feature.md written at
|
|
371
|
+
✅ feature.md written at {featuresRoot}/{PREFIX}-{slug}/feature.md
|
|
396
372
|
|
|
397
373
|
Summary:
|
|
398
374
|
Actors: N
|
|
@@ -400,7 +376,7 @@ Summary:
|
|
|
400
376
|
Acceptance criteria: N (Must: N, Should: N, Could: N)
|
|
401
377
|
Open questions: N
|
|
402
378
|
|
|
403
|
-
Next step: run /implement-feature
|
|
379
|
+
Next step: run /implement-feature {featuresRoot}/{PREFIX}-{slug}/feature.md
|
|
404
380
|
to start the full pipeline (requirements → spec → implementation → PR).
|
|
405
381
|
```
|
|
406
382
|
|
|
@@ -6,7 +6,7 @@ model: sonnet
|
|
|
6
6
|
|
|
7
7
|
# Install Toolkit
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Installs the ai-toolkit into a destination project so it can use `/implement-feature`, `/init-agents`, all developer agents, and all slash commands without needing the toolkit repo locally.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -15,130 +15,57 @@ Copies the toolkit's agents, skills, commands, and procedures into a destination
|
|
|
15
15
|
The user provides:
|
|
16
16
|
1. **Path to the destination project root** (optional) — defaults to the current working directory if not specified
|
|
17
17
|
2. **`--force`** flag (optional) — overwrite files that already exist in the destination
|
|
18
|
+
3. **`--global`** flag (optional) — install into the global Claude Code config directory (`~/.claude/`) instead of a project
|
|
18
19
|
|
|
19
20
|
---
|
|
20
21
|
|
|
21
|
-
## Step 1 — Validate inputs
|
|
22
|
-
|
|
23
|
-
1. Determine the **toolkit root**: the current working directory (this agent runs from the toolkit repo).
|
|
24
|
-
2. Verify the toolkit root contains `.claude/agents/` — if not, abort: "Error: must be run from the ai-toolkit directory."
|
|
25
|
-
3. Verify the destination path exists — if not, abort: "Error: destination path not found: {path}"
|
|
26
|
-
4. Check if destination is a git repository (look for `.git/`) — warn if not, but continue.
|
|
27
|
-
5. **Version check**:
|
|
28
|
-
- Read `package.json` from the toolkit root to get the current version (e.g. `0.2.0`)
|
|
29
|
-
- Check if `.claude/.ai-toolkit-version` exists in the destination:
|
|
30
|
-
- **File missing** → first install, no prompt needed, continue
|
|
31
|
-
- **Same version** → inform the user and ask: "Toolkit v{x} is already installed. Re-install anyway?"
|
|
32
|
-
- If No: abort cleanly
|
|
33
|
-
- **Different version** → show both versions and ask:
|
|
34
|
-
```
|
|
35
|
-
Version check:
|
|
36
|
-
Installed : v{installed}
|
|
37
|
-
Available : v{current}
|
|
38
|
-
|
|
39
|
-
Proceed and update from v{installed} to v{current}?
|
|
40
|
-
```
|
|
41
|
-
- If No: abort cleanly with "Your current installation was not changed."
|
|
22
|
+
## Step 1 — Validate inputs
|
|
42
23
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
| Source (toolkit) | Destination | Purpose |
|
|
50
|
-
|------------------|-------------|---------|
|
|
51
|
-
| `.claude/agents/` | `{dest}/.claude/agents/` | All spawnable subagents |
|
|
52
|
-
| `.claude/skills/` | `{dest}/.claude/skills/` | All user-invocable skills (except `install-toolkit/`) |
|
|
53
|
-
| `.claude/commands/` | `{dest}/.claude/commands/` | All slash commands |
|
|
54
|
-
| `.claude/workflows/` | `{dest}/.claude/workflows/` | Claude Code Workflow scripts for orchestrated pipelines |
|
|
55
|
-
| `.claude/scripts/` | `{dest}/.claude/scripts/` | CLI scripts for work breakdown validation and rendering |
|
|
56
|
-
| `docs/procedures/` | `{dest}/docs/procedures/` | Generic procedures (only if destination has no override) |
|
|
57
|
-
|
|
58
|
-
For each file, compare source content against destination using an MD5/hash check and determine status:
|
|
59
|
-
- **NEW** — file does not exist in destination → copy automatically, no prompt
|
|
60
|
-
- **SAME** — file exists and content is identical → skip silently, no prompt
|
|
61
|
-
- **MODIFIED** — file exists but content differs → show to user, ask per-file
|
|
62
|
-
|
|
63
|
-
Build and display the plan before executing:
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
📦 Install Plan → {destination}
|
|
67
|
-
──────────────────────────────────────────────────
|
|
68
|
-
.claude/agents/
|
|
69
|
-
✅ NEW generate-requirements.md
|
|
70
|
-
⚠️ MODIFIED developer-backend.md ← content differs
|
|
71
|
-
⏭ SAME review-solution.md
|
|
72
|
-
|
|
73
|
-
.claude/skills/
|
|
74
|
-
✅ NEW implement-feature/SKILL.md
|
|
75
|
-
⏭ SAME init-agents/SKILL.md
|
|
76
|
-
|
|
77
|
-
.claude/workflows/
|
|
78
|
-
✅ NEW pm-phase1.js
|
|
79
|
-
✅ NEW pm-phase2.js
|
|
80
|
-
✅ NEW pm-phase3.js
|
|
81
|
-
✅ NEW am-phase1.js
|
|
82
|
-
✅ NEW am-phase2.js
|
|
83
|
-
|
|
84
|
-
.claude/scripts/
|
|
85
|
-
✅ NEW wb-validate.js
|
|
86
|
-
✅ NEW wb-render.js
|
|
87
|
-
|
|
88
|
-
docs/procedures/
|
|
89
|
-
✅ NEW code-generation.md
|
|
90
|
-
⏭ SAME testing.md
|
|
91
|
-
──────────────────────────────────────────────────
|
|
92
|
-
New: N | Modified: N | Unchanged: N
|
|
93
|
-
```
|
|
24
|
+
1. Determine destination:
|
|
25
|
+
- If `--global` was passed: destination is `~/.claude/` (user-global config dir)
|
|
26
|
+
- Otherwise: destination is the path provided, or the current working directory if omitted
|
|
27
|
+
2. Verify the destination path exists — if not, abort: "Error: destination path not found: {path}"
|
|
28
|
+
3. Check if destination is a git repository (look for `.git/`) — warn if not, but continue.
|
|
94
29
|
|
|
95
30
|
---
|
|
96
31
|
|
|
97
|
-
## Step
|
|
98
|
-
|
|
99
|
-
**Phase A — New files** (no prompt needed): copy all NEW files, creating directories as needed.
|
|
32
|
+
## Step 2 — Run installer
|
|
100
33
|
|
|
101
|
-
|
|
34
|
+
Invoke the ai-toolkit CLI installer. The CLI handles source resolution (no CWD assumption), version checking, asset catalog enumeration, and file copy:
|
|
102
35
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
Overwrite? (y/N):
|
|
36
|
+
```bash
|
|
37
|
+
ai-toolkit install --project "{dest}" # for a project install
|
|
38
|
+
# or
|
|
39
|
+
ai-toolkit install --global # for a global install
|
|
108
40
|
```
|
|
109
41
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
**Phase C — Same files**: skip silently (no output for these).
|
|
113
|
-
|
|
114
|
-
Use shell commands appropriate for the OS. On Windows with bash:
|
|
42
|
+
If `--force` was passed:
|
|
115
43
|
```bash
|
|
116
|
-
|
|
117
|
-
cp "{source_file}" "{dest_file}"
|
|
44
|
+
ai-toolkit install --project "{dest}" --force
|
|
118
45
|
```
|
|
119
46
|
|
|
120
|
-
|
|
47
|
+
The installer will:
|
|
48
|
+
- Compare installed version vs available version and ask for confirmation if already installed at the same version
|
|
49
|
+
- Enumerate all installable assets from the asset catalog (`src/claude/`)
|
|
50
|
+
- Copy NEW files automatically; prompt for MODIFIED files (or overwrite silently if `--force`)
|
|
51
|
+
- Skip SAME files silently
|
|
52
|
+
- Never copy `install-toolkit` skill or `settings.json` / `settings.local.json`
|
|
53
|
+
- Never clobber `docs/procedures/` overrides that exist in the destination
|
|
54
|
+
- Stamp the installed version in `{dest}/.claude/.ai-toolkit-version`
|
|
121
55
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
**For `docs/procedures/`**: copy only files that don't already exist in the destination (regardless of `--force`) — project-specific procedure overrides must never be clobbered.
|
|
56
|
+
Show the installer's output to the user without modification.
|
|
125
57
|
|
|
126
58
|
---
|
|
127
59
|
|
|
128
|
-
## Step
|
|
60
|
+
## Step 3 — Verify result
|
|
129
61
|
|
|
130
|
-
After
|
|
131
|
-
1.
|
|
132
|
-
2. Verify
|
|
133
|
-
3. Write the installed version to `.claude/.ai-toolkit-version` in the destination:
|
|
134
|
-
```bash
|
|
135
|
-
echo "{current_version}" > "{dest}/.claude/.ai-toolkit-version"
|
|
136
|
-
```
|
|
137
|
-
This file is read on future installs to show the version comparison prompt.
|
|
62
|
+
After the installer exits:
|
|
63
|
+
1. Confirm exit code was 0 — if non-zero, report the error and stop
|
|
64
|
+
2. Verify `.claude/.ai-toolkit-version` was written in the destination
|
|
138
65
|
|
|
139
66
|
---
|
|
140
67
|
|
|
141
|
-
## Step
|
|
68
|
+
## Step 4 — Verify subagent spawn depth (verify & advise ONLY — never write)
|
|
142
69
|
|
|
143
70
|
The orchestrated pipelines (`/implement-feature`, `/assess-codebase`) require the
|
|
144
71
|
environment variable `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH` to be set to `2` or higher.
|
|
@@ -105,6 +105,38 @@ function sanitizeField(str) {
|
|
|
105
105
|
.trim()
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// ── Verification-command helpers (LOSSLESS — never sanitized) ─────────────────
|
|
109
|
+
//
|
|
110
|
+
// Verification commands are executable and MUST survive verbatim: operators like
|
|
111
|
+
// `||`, shell pipes `|`, and regex alternations `grep -E 'a|b|c'` are destroyed by
|
|
112
|
+
// sanitizeField (which maps `|` → space to protect table/CSV column structure).
|
|
113
|
+
// These commands are therefore rendered in a dedicated section as fenced code
|
|
114
|
+
// blocks whose content is byte-equivalent to the source JSON. The task table shows
|
|
115
|
+
// only a reference (a count + anchor link), never a sanitized copy of a command.
|
|
116
|
+
|
|
117
|
+
function getVerificationCommands(task) {
|
|
118
|
+
const v = task && (task.verificationCommands ?? task.verification?.commands)
|
|
119
|
+
return Array.isArray(v) ? v : []
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Stable, deterministic HTML anchor id for a task's authoritative detail block.
|
|
123
|
+
// The summary table links here; the "Task Details" section renders every field.
|
|
124
|
+
function taskAnchor(id) {
|
|
125
|
+
const s = typeof id === 'string' ? id : String(id ?? '')
|
|
126
|
+
return 'task-' + s.replace(/[^A-Za-z0-9_-]/g, '-')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Render a single command inside a fenced code block, byte-equivalent to `cmd`.
|
|
130
|
+
// The fence length is chosen to exceed the longest backtick run in the content so a
|
|
131
|
+
// command that itself contains backticks can never prematurely close the fence.
|
|
132
|
+
function fencedCommandBlock(cmd) {
|
|
133
|
+
const s = typeof cmd === 'string' ? cmd : String(cmd ?? '')
|
|
134
|
+
const runs = s.match(/`+/g) || []
|
|
135
|
+
const maxRun = runs.reduce((m, r) => Math.max(m, r.length), 0)
|
|
136
|
+
const fence = '`'.repeat(Math.max(3, maxRun + 1))
|
|
137
|
+
return `${fence}\n${s}\n${fence}`
|
|
138
|
+
}
|
|
139
|
+
|
|
108
140
|
function buildCommitSubject(task) {
|
|
109
141
|
if (!task.commit || typeof task.commit !== 'object') {
|
|
110
142
|
return 'chore: implement task'
|
|
@@ -189,8 +221,15 @@ function renderMarkdown(wb, prefix, phaseDepsMap) {
|
|
|
189
221
|
const est = mins !== null ? String(mins) : '—'
|
|
190
222
|
const depArr = Array.isArray(task.dependsOn) && task.dependsOn.length > 0 ? task.dependsOn : null
|
|
191
223
|
const deps = depArr ? depArr.join(', ') : '—'
|
|
192
|
-
|
|
193
|
-
|
|
224
|
+
// The summary table is a digest only. Verification commands are NOT rendered
|
|
225
|
+
// in-table (sanitizeField would destroy their operators). The cell links to the
|
|
226
|
+
// authoritative per-task "Task Details" section, which carries every field
|
|
227
|
+
// (outcome, acceptance criteria, estimates, grouping rationale, commit, and the
|
|
228
|
+
// lossless verification commands). See renderTaskDetails / fencedCommandBlock.
|
|
229
|
+
const verifCmds = getVerificationCommands(task)
|
|
230
|
+
const verif = verifCmds.length > 0
|
|
231
|
+
? `${verifCmds.length} cmd — [details](#${taskAnchor(task.id)})`
|
|
232
|
+
: `[details](#${taskAnchor(task.id)})`
|
|
194
233
|
return `| ${id} | ${title} | ${outcome} | ${domain} | ${est} | ${deps} | ${verif} |`
|
|
195
234
|
}
|
|
196
235
|
|
|
@@ -263,6 +302,66 @@ function renderMarkdown(wb, prefix, phaseDepsMap) {
|
|
|
263
302
|
}
|
|
264
303
|
}
|
|
265
304
|
|
|
305
|
+
// ── Task Details (AUTHORITATIVE) ──
|
|
306
|
+
// The summary tables above are a digest. This section is the authoritative,
|
|
307
|
+
// self-contained representation of every task: it renders each field integrally so
|
|
308
|
+
// the Work Breakdown Markdown (plus the dispatch CSV) is a complete deliverable —
|
|
309
|
+
// the intermediate JSON is not required by any downstream consumer.
|
|
310
|
+
//
|
|
311
|
+
// Free-text fields (title, outcome, grouping rationale, commit subject) are rendered
|
|
312
|
+
// OUTSIDE any table and are therefore preserved verbatim — including pipes, which
|
|
313
|
+
// sanitizeField would otherwise map to spaces to protect table/CSV columns.
|
|
314
|
+
// Verification commands are emitted as fenced code blocks that are byte-equivalent
|
|
315
|
+
// to the source; the fence length exceeds any backtick run in the command so a
|
|
316
|
+
// command containing backticks can never break out of its block.
|
|
317
|
+
const raw = v => (typeof v === 'string' ? v : String(v ?? ''))
|
|
318
|
+
const getTokens = task => {
|
|
319
|
+
const t = task.estimate?.tokens ?? task.tokens
|
|
320
|
+
return (t !== undefined && t !== null) ? String(t) : '—'
|
|
321
|
+
}
|
|
322
|
+
const listOrDash = arr =>
|
|
323
|
+
(Array.isArray(arr) && arr.length > 0) ? arr.join(', ') : '—'
|
|
324
|
+
|
|
325
|
+
L.push('## Task Details')
|
|
326
|
+
L.push('')
|
|
327
|
+
L.push('> Authoritative per-task detail. Every field is rendered integrally so this document, together with the dispatch CSV, is a complete deliverable requiring no separate JSON. Verification commands are preserved **verbatim** in fenced code blocks — operators such as `||`, shell pipes `|`, and regex alternations (`grep -E \'a|b|c\'`) survive byte-for-byte. Each command is an independent fenced block.')
|
|
328
|
+
L.push('')
|
|
329
|
+
for (const phase of wb.phases) {
|
|
330
|
+
for (const task of (Array.isArray(phase.tasks) ? phase.tasks : [])) {
|
|
331
|
+
const cmds = getVerificationCommands(task)
|
|
332
|
+
const mins = getAgentMinutes(task)
|
|
333
|
+
L.push(`<a id="${taskAnchor(task.id)}"></a>`)
|
|
334
|
+
L.push(`### ${raw(task.id)}`)
|
|
335
|
+
L.push('')
|
|
336
|
+
L.push(`- **Task ID:** ${raw(task.id)}`)
|
|
337
|
+
L.push(`- **Title:** ${raw(task.title)}`)
|
|
338
|
+
L.push(`- **Outcome:** ${raw(task.outcome)}`)
|
|
339
|
+
L.push(`- **Domain:** ${raw(task.domain)}`)
|
|
340
|
+
L.push(`- **Agent type:** ${raw(task.agentType)}`)
|
|
341
|
+
L.push(`- **Dependencies:** ${listOrDash(task.dependsOn)}`)
|
|
342
|
+
L.push(`- **Acceptance criteria:** ${listOrDash(task.acceptanceCriteria)}`)
|
|
343
|
+
L.push(`- **Estimate — agent minutes:** ${mins !== null ? String(mins) : '—'}`)
|
|
344
|
+
L.push(`- **Estimate — tokens:** ${getTokens(task)}`)
|
|
345
|
+
L.push(`- **Output count:** ${task.outputCount != null ? String(task.outputCount) : '—'}`)
|
|
346
|
+
L.push(`- **Grouping rationale:** ${task.groupingRationale != null ? raw(task.groupingRationale) : '—'}`)
|
|
347
|
+
L.push(`- **Commit type:** ${task.commit && task.commit.type != null ? raw(task.commit.type) : '—'}`)
|
|
348
|
+
L.push(`- **Commit scope:** ${task.commit && task.commit.scope != null ? raw(task.commit.scope) : '—'}`)
|
|
349
|
+
L.push(`- **Commit subject:** ${task.commit && task.commit.subject != null ? raw(task.commit.subject) : '—'}`)
|
|
350
|
+
L.push('')
|
|
351
|
+
L.push('**Verification commands:**')
|
|
352
|
+
L.push('')
|
|
353
|
+
if (cmds.length === 0) {
|
|
354
|
+
L.push('_No verification commands._')
|
|
355
|
+
L.push('')
|
|
356
|
+
} else {
|
|
357
|
+
for (const cmd of cmds) {
|
|
358
|
+
L.push(fencedCommandBlock(cmd))
|
|
359
|
+
L.push('')
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
266
365
|
// ── Statistics ──
|
|
267
366
|
L.push('## Statistics')
|
|
268
367
|
L.push('')
|
|
@@ -59,6 +59,36 @@ const ERRORS = {
|
|
|
59
59
|
EMPTY_PHASE: 'empty_phase', // check 23
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// ── Markdown table row splitter ─────────────────────────────────────────────
|
|
63
|
+
// Splits a Markdown table row into cells on unescaped pipe separators only.
|
|
64
|
+
// A backslash-escaped pipe (\|) is treated as a literal pipe *inside* the cell
|
|
65
|
+
// rather than a column separator and is unescaped in the returned cell, so a
|
|
66
|
+
// criterion such as `ai-toolkit ledger open\|close\|fail\|skip` stays in a
|
|
67
|
+
// single cell and does not shift the "Related UC" column.
|
|
68
|
+
// Returns the raw split including the leading/trailing empty cells produced by
|
|
69
|
+
// the row's outer pipes, matching the previous `line.split('|')` contract so
|
|
70
|
+
// callers can keep using `.slice(1, -1)`.
|
|
71
|
+
function splitTableRow(line) {
|
|
72
|
+
const cells = []
|
|
73
|
+
let current = ''
|
|
74
|
+
for (let i = 0; i < line.length; i++) {
|
|
75
|
+
const ch = line[i]
|
|
76
|
+
if (ch === '\\' && line[i + 1] === '|') {
|
|
77
|
+
current += '|' // unescape: keep the literal pipe inside this cell
|
|
78
|
+
i++ // consume the escaped '|'
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
if (ch === '|') {
|
|
82
|
+
cells.push(current)
|
|
83
|
+
current = ''
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
current += ch
|
|
87
|
+
}
|
|
88
|
+
cells.push(current)
|
|
89
|
+
return cells
|
|
90
|
+
}
|
|
91
|
+
|
|
62
92
|
// ── AC table parser (prerequisite for checks 19–21) ─────────────────────────
|
|
63
93
|
|
|
64
94
|
function parseAcTable(requirementsPath) {
|
|
@@ -122,7 +152,7 @@ function parseAcTable(requirementsPath) {
|
|
|
122
152
|
}
|
|
123
153
|
|
|
124
154
|
// ── Validate column headers ────────────────────────────────────────────────
|
|
125
|
-
const headerCells = lines[tableHeaderIdx]
|
|
155
|
+
const headerCells = splitTableRow(lines[tableHeaderIdx]).slice(1, -1).map(c => c.trim())
|
|
126
156
|
if (headerCells.length < 3) {
|
|
127
157
|
process.stderr.write('Error: AC table header has fewer than 3 columns\n')
|
|
128
158
|
process.exit(2)
|
|
@@ -143,7 +173,7 @@ function parseAcTable(requirementsPath) {
|
|
|
143
173
|
if (!line.trim().startsWith('|')) break
|
|
144
174
|
|
|
145
175
|
// Skip separator rows: every cell is only dashes, colons, or spaces
|
|
146
|
-
const rawCells = line
|
|
176
|
+
const rawCells = splitTableRow(line).slice(1, -1)
|
|
147
177
|
if (rawCells.every(c => /^[\s\-:]+$/.test(c))) continue
|
|
148
178
|
|
|
149
179
|
const cells = rawCells.map(c => c.trim())
|
|
@@ -204,6 +204,8 @@ From the `<usage>` block of the pm-phase3 result, read:
|
|
|
204
204
|
|
|
205
205
|
Read `{PREFIX}-Token-Estimate.md`. Append the orchestrator row and grand total:
|
|
206
206
|
|
|
207
|
+
> **Null-compatibility — actuals reading and cost calculation:** Treat a token value of `null`, `0`, or `not_available` as **data unavailable** — never as a real, observable zero consumption. Render unavailable values as `—` in the Actual tokens and Actual cost columns; exclude them from sums, averages, and grand totals. Preventing resume clobber and legacy misinterpretation requires that no unavailable measurement is coerced into a real zero.
|
|
208
|
+
|
|
207
209
|
```markdown
|
|
208
210
|
| project-manager/pm-phase3 (orchestrator) | — | sonnet | 80,000 (estimated) | {subagent_tokens} (actual) | ±{delta} | {duration} |
|
|
209
211
|
|
|
@@ -213,14 +215,14 @@ Read `{PREFIX}-Token-Estimate.md`. Append the orchestrator row and grand total:
|
|
|
213
215
|
|
|
214
216
|
| Agent | Task / Scope | Model | Est. tokens | Actual tokens | Delta | Est. cost ($) | Actual cost ($) | Duration |
|
|
215
217
|
|-------|-------------|-------|------------|---------------|-------|--------------|----------------|----------|
|
|
216
|
-
{one row per entry in token_ledger — from phase 1, 2, and 3}
|
|
218
|
+
{one row per entry in token_ledger — from phase 1, 2, and 3; for any entry where actual tokens is null, 0, or not_available show — in Actual tokens and Actual cost and exclude that row from totals}
|
|
217
219
|
| project-manager/pm-phase3 (orchestrator) | — | sonnet | 80,000 | {subagent_tokens} | ±{delta} | $0.4320 | ${actual_cost} | {duration} |
|
|
218
220
|
|
|
219
221
|
## Estimation accuracy by agent type
|
|
220
222
|
|
|
221
223
|
| Model | Count | Avg est. tokens | Avg actual tokens | Avg delta | Trend |
|
|
222
224
|
|-------|-------|----------------|------------------|-----------|-------|
|
|
223
|
-
{one row per model tier (haiku, sonnet) — exclude
|
|
225
|
+
{one row per model tier (haiku, sonnet) — exclude rows where actual tokens is null, 0, or not_available from averages}
|
|
224
226
|
|
|
225
227
|
## Grand Total
|
|
226
228
|
|
|
@@ -2,7 +2,7 @@ export const meta = {
|
|
|
2
2
|
name: 'am-phase1',
|
|
3
3
|
description: 'Assessment pipeline phase 1: discover assessment agents → scope filter → parallel assessment → intervention-documentation-standard → Token-Estimate → Effort-Estimate → findings_gate_payload.',
|
|
4
4
|
phases: [
|
|
5
|
-
{ title: 'Discovery', detail: '
|
|
5
|
+
{ title: 'Discovery', detail: 'Run ai-toolkit list-assets --category agents --format json, apply scope filter' },
|
|
6
6
|
{ title: 'Assessment', detail: 'Dispatch all assessment agents in parallel' },
|
|
7
7
|
{ title: 'Interventions', detail: 'Run intervention-documentation-standard on all findings' },
|
|
8
8
|
{ title: 'Estimates', detail: 'Write Token-Estimate and Effort-Estimate files' },
|
|
@@ -66,17 +66,14 @@ const SCOPE_AGENT_MAP = {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
const discovery = await agent(
|
|
69
|
-
`
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- description (from the description: field)
|
|
78
|
-
|
|
79
|
-
Return { "assessment_agents": [ { "name": "...", "description": "..." }, ... ] }`,
|
|
69
|
+
`Run this command and parse its output:\n` +
|
|
70
|
+
`ai-toolkit list-assets --category agents --format json\n\n` +
|
|
71
|
+
`The command outputs a JSON array of installed agent file paths (e.g. [".claude/agents/foo.md", ...]).\n` +
|
|
72
|
+
`For each path in the array, read the file and extract the YAML frontmatter fields: name, description.\n\n` +
|
|
73
|
+
`An assessment agent is one whose file name or description contains one of: "assessment", "audit", "analysis".\n` +
|
|
74
|
+
`Exclude orchestrators (files containing "manager" in their name).\n` +
|
|
75
|
+
`Exclude remediation-only agents (files containing "refactoring", "hardening", "decomposition" in their name UNLESS they also contain "assessment" or "audit").\n\n` +
|
|
76
|
+
`Return { "assessment_agents": [ { "name": "...", "description": "..." }, ... ] }`,
|
|
80
77
|
{
|
|
81
78
|
label: 'discover-assessment-agents',
|
|
82
79
|
phase: 'Discovery',
|