nubos-pilot 0.1.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/agents/np-ai-researcher.md +140 -0
- package/agents/np-code-fixer.md +363 -0
- package/agents/np-code-reviewer.md +351 -0
- package/agents/np-domain-researcher.md +136 -0
- package/agents/np-eval-auditor.md +167 -0
- package/agents/np-eval-planner.md +153 -0
- package/agents/np-executor.md +72 -0
- package/agents/np-framework-selector.md +171 -0
- package/agents/np-nyquist-auditor.md +185 -0
- package/agents/np-plan-checker.md +165 -0
- package/agents/np-planner.md +199 -0
- package/agents/np-researcher.md +150 -0
- package/agents/np-security-auditor.md +206 -0
- package/agents/np-ui-auditor.md +369 -0
- package/agents/np-ui-checker.md +192 -0
- package/agents/np-ui-researcher.md +324 -0
- package/agents/np-verifier.md +79 -0
- package/bin/check-coverage.cjs +40 -0
- package/bin/check-workflows.cjs +171 -0
- package/bin/check-workflows.test.cjs +208 -0
- package/bin/install.js +500 -0
- package/bin/np-tools/_commands.cjs +70 -0
- package/bin/np-tools/add-tests.cjs +171 -0
- package/bin/np-tools/add-tests.test.cjs +122 -0
- package/bin/np-tools/add-todo.cjs +108 -0
- package/bin/np-tools/add-todo.test.cjs +112 -0
- package/bin/np-tools/agent-skills.cjs +14 -0
- package/bin/np-tools/agent-skills.test.cjs +42 -0
- package/bin/np-tools/ai-integration-phase.cjs +109 -0
- package/bin/np-tools/ai-integration-phase.test.cjs +123 -0
- package/bin/np-tools/askuser.cjs +53 -0
- package/bin/np-tools/askuser.test.cjs +49 -0
- package/bin/np-tools/autonomous.cjs +69 -0
- package/bin/np-tools/autonomous.test.cjs +74 -0
- package/bin/np-tools/checkpoint.cjs +101 -0
- package/bin/np-tools/checkpoint.test.cjs +119 -0
- package/bin/np-tools/code-review.cjs +133 -0
- package/bin/np-tools/code-review.test.cjs +96 -0
- package/bin/np-tools/commit-task.cjs +120 -0
- package/bin/np-tools/commit-task.test.cjs +160 -0
- package/bin/np-tools/commit.cjs +103 -0
- package/bin/np-tools/commit.test.cjs +93 -0
- package/bin/np-tools/config.cjs +101 -0
- package/bin/np-tools/config.test.cjs +71 -0
- package/bin/np-tools/discuss-phase-power.cjs +265 -0
- package/bin/np-tools/discuss-phase-power.test.cjs +242 -0
- package/bin/np-tools/discuss-phase.cjs +132 -0
- package/bin/np-tools/discuss-phase.test.cjs +148 -0
- package/bin/np-tools/dispatch.cjs +116 -0
- package/bin/np-tools/doctor.cjs +242 -0
- package/bin/np-tools/eval-review.cjs +116 -0
- package/bin/np-tools/eval-review.test.cjs +123 -0
- package/bin/np-tools/execute-phase.cjs +182 -0
- package/bin/np-tools/execute-phase.test.cjs +116 -0
- package/bin/np-tools/execute-plan.cjs +124 -0
- package/bin/np-tools/execute-plan.test.cjs +82 -0
- package/bin/np-tools/help.cjs +28 -0
- package/bin/np-tools/help.test.cjs +29 -0
- package/bin/np-tools/init-dispatch.test.cjs +91 -0
- package/bin/np-tools/metrics.cjs +97 -0
- package/bin/np-tools/metrics.test.cjs +188 -0
- package/bin/np-tools/new-milestone.cjs +288 -0
- package/bin/np-tools/new-milestone.test.cjs +166 -0
- package/bin/np-tools/new-project.cjs +284 -0
- package/bin/np-tools/new-project.test.cjs +165 -0
- package/bin/np-tools/next.cjs +7 -0
- package/bin/np-tools/next.test.cjs +30 -0
- package/bin/np-tools/park.cjs +48 -0
- package/bin/np-tools/park.test.cjs +50 -0
- package/bin/np-tools/pause-work.cjs +24 -0
- package/bin/np-tools/pause-work.test.cjs +74 -0
- package/bin/np-tools/phase.cjs +71 -0
- package/bin/np-tools/phase.test.cjs +81 -0
- package/bin/np-tools/plan-diff.cjs +57 -0
- package/bin/np-tools/plan-diff.test.cjs +134 -0
- package/bin/np-tools/plan-milestone-gaps.cjs +115 -0
- package/bin/np-tools/plan-milestone-gaps.test.cjs +122 -0
- package/bin/np-tools/plan-phase.cjs +350 -0
- package/bin/np-tools/plan-phase.test.cjs +263 -0
- package/bin/np-tools/progress.cjs +7 -0
- package/bin/np-tools/progress.test.cjs +44 -0
- package/bin/np-tools/queue.cjs +213 -0
- package/bin/np-tools/research-phase.cjs +144 -0
- package/bin/np-tools/research-phase.test.cjs +154 -0
- package/bin/np-tools/reset-slice.cjs +17 -0
- package/bin/np-tools/reset-slice.test.cjs +96 -0
- package/bin/np-tools/resolve-model.cjs +110 -0
- package/bin/np-tools/resolve-model.test.cjs +200 -0
- package/bin/np-tools/resume-work.cjs +76 -0
- package/bin/np-tools/resume-work.test.cjs +91 -0
- package/bin/np-tools/skip.cjs +48 -0
- package/bin/np-tools/skip.test.cjs +66 -0
- package/bin/np-tools/slug.cjs +34 -0
- package/bin/np-tools/slug.test.cjs +46 -0
- package/bin/np-tools/state.cjs +16 -0
- package/bin/np-tools/state.test.cjs +40 -0
- package/bin/np-tools/stats.cjs +151 -0
- package/bin/np-tools/stats.test.cjs +118 -0
- package/bin/np-tools/triage.cjs +128 -0
- package/bin/np-tools/ui-phase.cjs +108 -0
- package/bin/np-tools/ui-phase.test.cjs +121 -0
- package/bin/np-tools/ui-review.cjs +108 -0
- package/bin/np-tools/ui-review.test.cjs +120 -0
- package/bin/np-tools/undo-task.cjs +31 -0
- package/bin/np-tools/undo-task.test.cjs +117 -0
- package/bin/np-tools/undo.cjs +43 -0
- package/bin/np-tools/undo.test.cjs +120 -0
- package/bin/np-tools/unpark.cjs +48 -0
- package/bin/np-tools/unpark.test.cjs +50 -0
- package/bin/np-tools/verify-work.cjs +186 -0
- package/bin/np-tools/verify-work.test.cjs +97 -0
- package/docs/adr/0001-no-daemon-invariant.md +82 -0
- package/docs/adr/0002-zero-runtime-dependencies.md +90 -0
- package/docs/adr/0003-max-six-unit-types.md +85 -0
- package/docs/adr/0004-atomic-commit-per-unit.md +102 -0
- package/docs/adr/0005-three-orthogonal-file-trees.md +98 -0
- package/docs/adr/0006-yaml-dependency-amendment.md +60 -0
- package/docs/adr/README.md +27 -0
- package/docs/agent-frontmatter-schema.md +84 -0
- package/docs/phase-artifact-schemas.md +292 -0
- package/docs/phase-directory-layout.md +82 -0
- package/lib/__tests__/README.md +1 -0
- package/lib/agents.cjs +98 -0
- package/lib/agents.test.cjs +286 -0
- package/lib/askuser.cjs +36 -0
- package/lib/askuser.test.cjs +310 -0
- package/lib/checkpoint.cjs +135 -0
- package/lib/checkpoint.test.cjs +184 -0
- package/lib/core.cjs +165 -0
- package/lib/core.test.cjs +405 -0
- package/lib/fixtures/README.md +1 -0
- package/lib/fixtures/phase-tree/README.md +1 -0
- package/lib/fixtures/plans/cycle/PLAN.md +16 -0
- package/lib/fixtures/plans/cycle/tasks/T-01.md +20 -0
- package/lib/fixtures/plans/cycle/tasks/T-02.md +20 -0
- package/lib/fixtures/plans/cycle/tasks/T-03.md +20 -0
- package/lib/fixtures/plans/linear/PLAN.md +16 -0
- package/lib/fixtures/plans/linear/tasks/T-01.md +20 -0
- package/lib/fixtures/plans/linear/tasks/T-02.md +20 -0
- package/lib/fixtures/plans/linear/tasks/T-03.md +20 -0
- package/lib/fixtures/plans/parallel/PLAN.md +16 -0
- package/lib/fixtures/plans/parallel/tasks/T-01.md +20 -0
- package/lib/fixtures/plans/parallel/tasks/T-02.md +20 -0
- package/lib/fixtures/plans/parallel/tasks/T-03.md +20 -0
- package/lib/fixtures/plans/wave-conflict/PLAN.md +16 -0
- package/lib/fixtures/plans/wave-conflict/tasks/T-01.md +20 -0
- package/lib/fixtures/plans/wave-conflict/tasks/T-02.md +20 -0
- package/lib/fixtures/roadmap/ROADMAP-malformed.md +3 -0
- package/lib/fixtures/roadmap/ROADMAP-minimal.md +51 -0
- package/lib/fixtures/roadmap/roadmap-malformed.yaml +7 -0
- package/lib/fixtures/roadmap/roadmap-minimal.yaml +40 -0
- package/lib/fixtures/roadmap/roadmap-ten-phases.yaml +101 -0
- package/lib/fixtures/templates/phase-context.md +6 -0
- package/lib/fixtures/templates/plan-skeleton.md +6 -0
- package/lib/frontmatter.cjs +251 -0
- package/lib/frontmatter.test.cjs +177 -0
- package/lib/gaps.cjs +197 -0
- package/lib/gaps.test.cjs +200 -0
- package/lib/git.cjs +207 -0
- package/lib/git.test.cjs +305 -0
- package/lib/install/agents-md.cjs +77 -0
- package/lib/install/backup.cjs +70 -0
- package/lib/install/codex-toml.cjs +440 -0
- package/lib/install/managed-block.cjs +30 -0
- package/lib/install/manifest.cjs +148 -0
- package/lib/install/mcp-writer.cjs +127 -0
- package/lib/install/runtime-detect.cjs +44 -0
- package/lib/install/staging.cjs +149 -0
- package/lib/metrics-aggregate.cjs +229 -0
- package/lib/metrics-aggregate.test.cjs +192 -0
- package/lib/metrics.cjs +120 -0
- package/lib/metrics.test.cjs +182 -0
- package/lib/model-aliases.regression.test.cjs +16 -0
- package/lib/model-profiles.cjs +42 -0
- package/lib/model-profiles.test.cjs +61 -0
- package/lib/next.cjs +236 -0
- package/lib/next.test.cjs +194 -0
- package/lib/phase.cjs +95 -0
- package/lib/phase.test.cjs +189 -0
- package/lib/plan-checker-contract.test.cjs +72 -0
- package/lib/plan-diff.cjs +173 -0
- package/lib/plan-diff.test.cjs +217 -0
- package/lib/plan.cjs +85 -0
- package/lib/plan.test.cjs +263 -0
- package/lib/progress.cjs +95 -0
- package/lib/progress.test.cjs +116 -0
- package/lib/researcher-contract.test.cjs +61 -0
- package/lib/roadmap-render.cjs +206 -0
- package/lib/roadmap-render.test.cjs +121 -0
- package/lib/roadmap.cjs +416 -0
- package/lib/roadmap.test.cjs +371 -0
- package/lib/runtime/_contract.test.cjs +61 -0
- package/lib/runtime/_readline.cjs +119 -0
- package/lib/runtime/_readline.test.cjs +126 -0
- package/lib/runtime/claude.cjs +48 -0
- package/lib/runtime/claude.test.cjs +101 -0
- package/lib/runtime/codex.cjs +35 -0
- package/lib/runtime/codex.test.cjs +114 -0
- package/lib/runtime/gemini.cjs +35 -0
- package/lib/runtime/gemini.test.cjs +109 -0
- package/lib/runtime/index.cjs +49 -0
- package/lib/runtime/index.test.cjs +181 -0
- package/lib/runtime/opencode.cjs +35 -0
- package/lib/runtime/opencode.test.cjs +124 -0
- package/lib/state.cjs +205 -0
- package/lib/state.test.cjs +264 -0
- package/lib/surface-audit.test.cjs +46 -0
- package/lib/tasks.cjs +327 -0
- package/lib/tasks.test.cjs +389 -0
- package/lib/template.cjs +66 -0
- package/lib/template.test.cjs +159 -0
- package/lib/undo.cjs +179 -0
- package/lib/undo.test.cjs +261 -0
- package/lib/verify.cjs +116 -0
- package/lib/verify.test.cjs +187 -0
- package/np-tools.cjs +303 -0
- package/package.json +39 -0
- package/templates/AI-SPEC.md +90 -0
- package/templates/CONTEXT.md +32 -0
- package/templates/PLAN.md +69 -0
- package/templates/PROJECT.md +60 -0
- package/templates/REQUIREMENTS.md +38 -0
- package/templates/SECURITY.md +61 -0
- package/templates/UI-SPEC.md +64 -0
- package/templates/VALIDATION.md +76 -0
- package/templates/claude/payload/README.md +11 -0
- package/templates/opencode/opencode.json +6 -0
- package/templates/opencode/payload/AGENTS.md +9 -0
- package/workflows/add-backlog.md +212 -0
- package/workflows/add-tests.md +69 -0
- package/workflows/add-todo.md +222 -0
- package/workflows/ai-integration-phase.md +230 -0
- package/workflows/autonomous.md +94 -0
- package/workflows/cleanup.md +325 -0
- package/workflows/code-review-fix.md +435 -0
- package/workflows/code-review.md +447 -0
- package/workflows/discuss-phase-assumptions.md +269 -0
- package/workflows/discuss-phase-power.md +139 -0
- package/workflows/discuss-phase.md +386 -0
- package/workflows/dispatch.md +9 -0
- package/workflows/doctor.md +10 -0
- package/workflows/eval-review.md +243 -0
- package/workflows/execute-phase.md +142 -0
- package/workflows/execute-plan.md +82 -0
- package/workflows/help.md +8 -0
- package/workflows/new-milestone.md +166 -0
- package/workflows/new-project.md +213 -0
- package/workflows/next.md +8 -0
- package/workflows/note.md +244 -0
- package/workflows/park.md +29 -0
- package/workflows/pause-work.md +34 -0
- package/workflows/plan-milestone-gaps.md +233 -0
- package/workflows/plan-phase.md +351 -0
- package/workflows/progress.md +8 -0
- package/workflows/queue.md +9 -0
- package/workflows/research-phase.md +327 -0
- package/workflows/reset-slice.md +39 -0
- package/workflows/resume-work.md +79 -0
- package/workflows/review.md +489 -0
- package/workflows/secure-phase.md +209 -0
- package/workflows/session-report.md +243 -0
- package/workflows/skip.md +29 -0
- package/workflows/state.md +7 -0
- package/workflows/stats.md +170 -0
- package/workflows/thread.md +214 -0
- package/workflows/triage.md +9 -0
- package/workflows/ui-phase.md +246 -0
- package/workflows/ui-review.md +222 -0
- package/workflows/undo-task.md +42 -0
- package/workflows/undo.md +55 -0
- package/workflows/unpark.md +29 -0
- package/workflows/validate-phase.md +231 -0
- package/workflows/verify-work.md +83 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
---
|
|
2
|
+
command: np:secure-phase
|
|
3
|
+
description: Threat-mitigation audit on a completed phase. Reads PLAN.md threat_model + implementation, spawns np-security-auditor (opus) to score each threat as MITIGATED/PARTIAL/UNMITIGATED/N/A, writes SECURITY.md sidecar from templates/SECURITY.md skeleton. One atomic docs commit.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# np:secure-phase
|
|
7
|
+
|
|
8
|
+
Produces `{phase_dir}/{padded}-SECURITY.md` via a single `np-security-auditor`
|
|
9
|
+
(opus) spawn. Runs AFTER `/np:execute-phase` has landed code — the audit
|
|
10
|
+
needs SUMMARY.md and a `<threat_model>` block in PLAN.md.
|
|
11
|
+
|
|
12
|
+
The workflow `cp`s `templates/SECURITY.md` into the sidecar BEFORE
|
|
13
|
+
spawning the agent; the auditor substitutes placeholders (`{N}`,
|
|
14
|
+
`{phase-slug}`, `{date}`) and appends per-threat scoring. The Task
|
|
15
|
+
spawn is wrapped in the Plan 09-05 metrics + resolve-model pattern
|
|
16
|
+
(D-06, D-01); `RUNTIME` is detected once and re-used by `metrics
|
|
17
|
+
record`. Prompts route through `np-tools.cjs askuser` (INST-03).
|
|
18
|
+
|
|
19
|
+
Pre-Phase-9 phases without `<threat_model>` degrade gracefully: the
|
|
20
|
+
auditor produces a best-effort audit inferred from ADRs + code patterns
|
|
21
|
+
(T-10-04-04 accept).
|
|
22
|
+
|
|
23
|
+
## Initialize
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
PHASE="$1"
|
|
27
|
+
if [[ -z "$PHASE" ]]; then
|
|
28
|
+
echo "Usage: /np:secure-phase <phase-number>" >&2
|
|
29
|
+
exit 2
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
INIT=$(node np-tools.cjs init secure-phase "$PHASE")
|
|
33
|
+
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
|
34
|
+
RUNTIME=$(node -e "console.log(require('./lib/runtime/index.cjs').detect().runtime)")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Parse JSON for: `padded_phase`, `phase_dir`, `phase_found`.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
PADDED=$(echo "$INIT" | jq -r '.padded_phase // .padded')
|
|
41
|
+
PHASE_DIR=$(echo "$INIT" | jq -r '.phase_dir')
|
|
42
|
+
PHASE_FOUND=$(echo "$INIT" | jq -r '.phase_found')
|
|
43
|
+
SECURITY_PATH="${PHASE_DIR}/${PADDED}-SECURITY.md"
|
|
44
|
+
SUMMARY_PATH="${PHASE_DIR}/${PADDED}-SUMMARY.md"
|
|
45
|
+
PLAN_PATH_GLOB="${PHASE_DIR}/${PADDED}-*-PLAN.md"
|
|
46
|
+
TEMPLATE_PATH="templates/SECURITY.md"
|
|
47
|
+
PLAN_ID="${PADDED}-secure-phase"
|
|
48
|
+
TASK_ID="${PADDED}-secure-phase"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Pre-Flight Gates
|
|
52
|
+
|
|
53
|
+
<pre_flight>
|
|
54
|
+
|
|
55
|
+
### Gate 1 — Phase found + SUMMARY.md present
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
if [[ "$PHASE_FOUND" != "true" ]]; then
|
|
59
|
+
echo "Error: Phase $PHASE not found in roadmap or on disk." >&2
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
if [[ ! -f "$SUMMARY_PATH" ]]; then
|
|
63
|
+
echo "Error: Phase $PHASE has no SUMMARY.md at $SUMMARY_PATH." >&2
|
|
64
|
+
echo "Run /np:execute-phase $PHASE before auditing." >&2
|
|
65
|
+
exit 1
|
|
66
|
+
fi
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Gate 2 — SECURITY.md already exists
|
|
70
|
+
|
|
71
|
+
If a prior audit is present, let the user choose Re-run / View / Skip.
|
|
72
|
+
The `cp` step only runs in the Re-run branch — View or Skip never
|
|
73
|
+
overwrites a user-edited sidecar (T-10-04-01 mitigation).
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
RERUN="false"
|
|
77
|
+
if [[ -f "$SECURITY_PATH" ]]; then
|
|
78
|
+
CHOICE=$(node np-tools.cjs askuser --json '{
|
|
79
|
+
"type": "select",
|
|
80
|
+
"header": "Existing SECURITY.md",
|
|
81
|
+
"question": "SECURITY.md already exists for Phase '"$PHASE"'. What would you like to do?",
|
|
82
|
+
"options": [
|
|
83
|
+
{"label": "Re-run — replace the current audit", "description": "Re-runs np-security-auditor and overwrites the existing file."},
|
|
84
|
+
{"label": "View — display current audit and exit", "description": "Reads the file and exits without changes."},
|
|
85
|
+
{"label": "Skip — keep current audit and exit", "description": "Leaves the file untouched."}
|
|
86
|
+
]
|
|
87
|
+
}')
|
|
88
|
+
case "$CHOICE" in
|
|
89
|
+
"View"*) cat "$SECURITY_PATH"; exit 0 ;;
|
|
90
|
+
"Skip"*) exit 0 ;;
|
|
91
|
+
"Re-run"*) RERUN="true" ;;
|
|
92
|
+
esac
|
|
93
|
+
fi
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Gate 3 — Template present
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
if [[ ! -f "$TEMPLATE_PATH" ]]; then
|
|
100
|
+
echo "Error: $TEMPLATE_PATH missing; Plan 10-01-T03 should have ported it." >&2
|
|
101
|
+
echo "Re-run 'npx nubos-pilot install' or restore templates/SECURITY.md from source." >&2
|
|
102
|
+
exit 1
|
|
103
|
+
fi
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
</pre_flight>
|
|
107
|
+
|
|
108
|
+
## Load Template
|
|
109
|
+
|
|
110
|
+
Copy `templates/SECURITY.md` into the sidecar ONLY when absent OR user
|
|
111
|
+
chose Re-run. The agent substitutes `{N}` / `{phase-slug}` / `{date}`
|
|
112
|
+
at write time — the workflow never pre-substitutes.
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
if [[ ! -f "$SECURITY_PATH" || "$RERUN" == "true" ]]; then
|
|
116
|
+
cp "$TEMPLATE_PATH" "$SECURITY_PATH"
|
|
117
|
+
fi
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Extract Threat Model
|
|
121
|
+
|
|
122
|
+
The `np-security-auditor` agent reads every
|
|
123
|
+
`${PHASE_DIR}/${PADDED}-*-PLAN.md`, extracts the `<threat_model>`
|
|
124
|
+
block, and consolidates into a unified audit. The workflow passes only
|
|
125
|
+
the glob + phase dir — the agent parses via `Read` / `Grep`.
|
|
126
|
+
|
|
127
|
+
## Spawn np-security-auditor (opus)
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
START=$(node np-tools.cjs metrics start-timestamp)
|
|
131
|
+
MODEL=$(node np-tools.cjs resolve-model np-security-auditor --profile balanced)
|
|
132
|
+
# Spawn agent=np-security-auditor model=$MODEL
|
|
133
|
+
# input: plan_path_glob=$PLAN_PATH_GLOB, summary_path=$SUMMARY_PATH,
|
|
134
|
+
# security_path=$SECURITY_PATH, template_path=$TEMPLATE_PATH,
|
|
135
|
+
# phase_dir=$PHASE_DIR, phase=$PHASE, padded=$PADDED
|
|
136
|
+
# output: $SECURITY_PATH with scored threats (MITIGATED / PARTIAL /
|
|
137
|
+
# UNMITIGATED / N/A), using templates/SECURITY.md as skeleton.
|
|
138
|
+
Task(
|
|
139
|
+
subagent_type="np-security-auditor",
|
|
140
|
+
model="$MODEL",
|
|
141
|
+
prompt="<files_to_read>${PLAN_PATH_GLOB} ${SUMMARY_PATH} ${TEMPLATE_PATH} CLAUDE.md PROJECT.md</files_to_read><config>plan_path_glob=$PLAN_PATH_GLOB,summary_path=$SUMMARY_PATH,security_path=$SECURITY_PATH,template_path=$TEMPLATE_PATH,phase_dir=$PHASE_DIR,phase=$PHASE,padded=$PADDED</config>"
|
|
142
|
+
)
|
|
143
|
+
END=$(node np-tools.cjs metrics end-timestamp)
|
|
144
|
+
node np-tools.cjs metrics record \
|
|
145
|
+
--agent np-security-auditor --tier opus --resolved-model "$MODEL" \
|
|
146
|
+
--phase "$PHASE" --plan "$PLAN_ID" --task "$TASK_ID" \
|
|
147
|
+
--started "$START" --ended "$END" \
|
|
148
|
+
--tokens-in "${TOKENS_IN:-0}" --tokens-out "${TOKENS_OUT:-0}" \
|
|
149
|
+
--retry-count 0 --status ok --runtime "$RUNTIME"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Validation Gate
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
if [[ ! -f "$SECURITY_PATH" ]]; then
|
|
156
|
+
CHOICE=$(node np-tools.cjs askuser --json '{
|
|
157
|
+
"type": "select",
|
|
158
|
+
"header": "SECURITY.md missing",
|
|
159
|
+
"question": "np-security-auditor did not write SECURITY.md. What would you like to do?",
|
|
160
|
+
"options": [
|
|
161
|
+
{"label": "Re-run np-security-auditor", "description": "Spawn the auditor once more."},
|
|
162
|
+
{"label": "Abort", "description": "Exit without committing."}
|
|
163
|
+
]
|
|
164
|
+
}')
|
|
165
|
+
case "$CHOICE" in "Abort") exit 1 ;; esac
|
|
166
|
+
fi
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Commit
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
node np-tools.cjs commit "docs(${PADDED}): add security audit report" --files "$SECURITY_PATH"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
One atomic docs commit per ADR-0004. The commit helper routes through
|
|
176
|
+
`lib/git.cjs.assertCommittablePaths` (gitignore-guard) before staging.
|
|
177
|
+
|
|
178
|
+
## Scope Guardrail
|
|
179
|
+
|
|
180
|
+
<scope_guardrail>
|
|
181
|
+
**Do:**
|
|
182
|
+
- Run `np-security-auditor` exactly once per invocation (single-pass audit).
|
|
183
|
+
- Emit a metrics record AFTER the Task spawn (D-06).
|
|
184
|
+
- Resolve MODEL via `np-tools.cjs resolve-model np-security-auditor --profile balanced` — no hardcoded IDs.
|
|
185
|
+
- Use `np-tools.cjs askuser` for every prompt (INST-03 invariant).
|
|
186
|
+
- `cp templates/SECURITY.md` into the sidecar BEFORE spawning the agent.
|
|
187
|
+
- Only overwrite existing SECURITY.md on Re-run choice (T-10-04-01).
|
|
188
|
+
- Abort early when phase_dir or SUMMARY.md is absent.
|
|
189
|
+
- Treat implementation files as READ-ONLY (D-20 SC-5).
|
|
190
|
+
|
|
191
|
+
**Don't:**
|
|
192
|
+
- Run this workflow on a phase without SUMMARY.md.
|
|
193
|
+
- Invoke host-specific prompt tools directly — route through `np-tools.cjs askuser`.
|
|
194
|
+
- Overwrite a user-edited SECURITY.md without the Re-run gate (T-10-04-01).
|
|
195
|
+
- Construct phase paths from raw `$1` — consume `padded_phase` / `phase_dir`
|
|
196
|
+
from `np-tools.cjs init` (SAFE_PHASE_RE enforced upstream, T-10-04-03).
|
|
197
|
+
- Skip the metrics record block (D-06).
|
|
198
|
+
- Inject raw secret values into the agent prompt (T-10-04-02 defence-in-depth).
|
|
199
|
+
- Spawn any additional agent beyond `np-security-auditor`.
|
|
200
|
+
</scope_guardrail>
|
|
201
|
+
|
|
202
|
+
## Output
|
|
203
|
+
|
|
204
|
+
- `{phase_dir}/{padded}-SECURITY.md` — threat register with
|
|
205
|
+
MITIGATED / PARTIAL / UNMITIGATED / N/A scoring, Trust Boundaries,
|
|
206
|
+
Accepted Risks log, Security Audit Trail. Frontmatter carries
|
|
207
|
+
`threats_total`, `mitigated`, `partial`, `unmitigated`, `threats_open`.
|
|
208
|
+
- 1 metrics record in `.nubos-pilot/metrics/phase-${PHASE}.jsonl`.
|
|
209
|
+
- One atomic `docs(${PADDED}): add security audit report` git commit.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
---
|
|
2
|
+
command: np:session-report
|
|
3
|
+
description: Generate session report from metrics since .nubos-pilot/reports/.last-session pointer. Pointer update is file-lock-guarded (Pitfall 8). Output filename is ISO-8601-prefixed (YYYY-MM-DDTHHMM-session-report.md) for deterministic sort and no overwrite (D-17). Uses lib/metrics-aggregate.cjs.aggregateSession (D-18). One atomic docs commit.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# np:session-report
|
|
7
|
+
|
|
8
|
+
Implements UTIL-07a. Produces a post-session markdown report
|
|
9
|
+
summarising metrics, commits, and progress since the last report.
|
|
10
|
+
Three deliberate design choices:
|
|
11
|
+
|
|
12
|
+
- **D-16 pointer file** — persists
|
|
13
|
+
`.nubos-pilot/reports/.last-session` (ISO-8601 timestamp) so each
|
|
14
|
+
report covers exactly "since last report" regardless of clock time
|
|
15
|
+
(rather than a rolling 24h window that would double-count overlaps).
|
|
16
|
+
- **D-17 ISO-prefixed filename** — emits
|
|
17
|
+
`YYYY-MM-DDTHHMM-session-report.md` so reports never overwrite and
|
|
18
|
+
sort deterministically.
|
|
19
|
+
- **D-18 aggregation helper** — metrics come from
|
|
20
|
+
`lib/metrics-aggregate.cjs.aggregateSession` (Plan 10-01-T02);
|
|
21
|
+
workflow does not parse JSONL itself.
|
|
22
|
+
|
|
23
|
+
Pointer read + aggregation + write are wrapped in
|
|
24
|
+
`lib/core.cjs.withFileLock` (10s timeout per Pitfall 8, T-10-06-02
|
|
25
|
+
mitigation). Two concurrent `/np:session-report` invocations
|
|
26
|
+
serialise on the pointer so neither produces an overlapping report.
|
|
27
|
+
|
|
28
|
+
Pure-CRUD workflow — no agent spawn, no resolve-model, no metrics
|
|
29
|
+
record. Pitfall 9 / `workflow-missing-metrics` is exempt.
|
|
30
|
+
|
|
31
|
+
## Initialize
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
SINCE_OVERRIDE=""
|
|
35
|
+
for arg in "$@"; do
|
|
36
|
+
case "$arg" in
|
|
37
|
+
--since=*) SINCE_OVERRIDE="${arg#--since=}" ;;
|
|
38
|
+
esac
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
STATE_DIR=$(node -e "console.log(require('./lib/core.cjs').projectStateDir(process.cwd()))")
|
|
42
|
+
REPORTS_DIR="${STATE_DIR}/reports"
|
|
43
|
+
POINTER="${REPORTS_DIR}/.last-session"
|
|
44
|
+
mkdir -p "$REPORTS_DIR"
|
|
45
|
+
|
|
46
|
+
NOW_ISO=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
47
|
+
LOCAL_FILENAME_TS=$(date +"%Y-%m-%dT%H%M")
|
|
48
|
+
REPORT_PATH="${REPORTS_DIR}/${LOCAL_FILENAME_TS}-session-report.md"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The filename format is `YYYY-MM-DDTHHMM-session-report.md` (D-17 —
|
|
52
|
+
4-char HHMM, no seconds, local time) so reports sort
|
|
53
|
+
lexicographically by invocation order.
|
|
54
|
+
|
|
55
|
+
## Pointer Read + Aggregation
|
|
56
|
+
|
|
57
|
+
Pointer read and metrics aggregation run inside a single
|
|
58
|
+
`withFileLock` call so a concurrent invocation cannot interleave
|
|
59
|
+
between "read pointer" and "write new pointer" (T-10-06-02 / Pitfall
|
|
60
|
+
8 mitigation). The lock times out at 10 000 ms; callers that wait
|
|
61
|
+
longer hit `lock-timeout` from `lib/core.cjs.NubosPilotError`.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
REPORT_JSON=$(node -e '
|
|
65
|
+
const fs = require("node:fs");
|
|
66
|
+
const { withFileLock } = require("./lib/core.cjs");
|
|
67
|
+
const { aggregateSession } = require("./lib/metrics-aggregate.cjs");
|
|
68
|
+
const pointer = process.argv[1];
|
|
69
|
+
const override = process.argv[2] || "";
|
|
70
|
+
const done = withFileLock(pointer, async () => {
|
|
71
|
+
let since = override || "";
|
|
72
|
+
if (!override && fs.existsSync(pointer)) {
|
|
73
|
+
since = fs.readFileSync(pointer, "utf-8").trim();
|
|
74
|
+
}
|
|
75
|
+
return aggregateSession(since || null, { cwd: process.cwd() });
|
|
76
|
+
}, { timeoutMs: 10000 });
|
|
77
|
+
Promise.resolve(done).then((r) => process.stdout.write(JSON.stringify(r)));
|
|
78
|
+
' "$POINTER" "$SINCE_OVERRIDE")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The `aggregateSession` helper returns
|
|
82
|
+
`{since_iso, record_count, by_phase, total_tokens_in, total_tokens_out,
|
|
83
|
+
partial_tokens, total_duration_ms, error_count, phases_touched}`.
|
|
84
|
+
Null token values (non-claude runtimes per Phase 9 D-09) pass through
|
|
85
|
+
and are rendered as `—` in the output table.
|
|
86
|
+
|
|
87
|
+
## Render Report Body
|
|
88
|
+
|
|
89
|
+
Use the `Write` tool to create `$REPORT_PATH` with the body below
|
|
90
|
+
(not a bash heredoc per CLAUDE.md). Render values from
|
|
91
|
+
`$REPORT_JSON` using Node to produce the table rows (null-safe with
|
|
92
|
+
`—` for any null token fields, per D-09 / D-15).
|
|
93
|
+
|
|
94
|
+
```markdown
|
|
95
|
+
# Session Report — <NOW_ISO>
|
|
96
|
+
|
|
97
|
+
**Since:** <since_iso or "project inception">
|
|
98
|
+
**Records:** <record_count>
|
|
99
|
+
**Phases touched:** <phases_touched joined with comma>
|
|
100
|
+
**Total duration:** <total_duration_ms> ms
|
|
101
|
+
**Errors:** <error_count>
|
|
102
|
+
|
|
103
|
+
## By Phase
|
|
104
|
+
|
|
105
|
+
| Phase | Records | Tokens In | Tokens Out | Errors | Retry Sum |
|
|
106
|
+
|-------|---------|-----------|------------|--------|-----------|
|
|
107
|
+
| <phase> | <record_count> | <tokens_in or "—"> | <tokens_out or "—"> | <error_count> | <retry_count_sum> |
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
To produce the rendered body deterministically, the agent invokes a
|
|
111
|
+
short Node snippet that consumes `$REPORT_JSON` on stdin and emits
|
|
112
|
+
the markdown table rows — then feeds the full text to the `Write`
|
|
113
|
+
tool. The snippet shape:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
BODY=$(node -e '
|
|
117
|
+
const j = JSON.parse(process.argv[1]);
|
|
118
|
+
const fmt = (v) => v === null || v === undefined ? "—" : String(v);
|
|
119
|
+
const rows = Object.entries(j.by_phase || {}).sort()
|
|
120
|
+
.map(([k, p]) => `| ${k} | ${p.record_count} | ${fmt(p.total_tokens_in)} | ${fmt(p.total_tokens_out)} | ${p.error_count} | ${p.retry_count_sum} |`)
|
|
121
|
+
.join("\n");
|
|
122
|
+
process.stdout.write(rows);
|
|
123
|
+
' "$REPORT_JSON")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Update Pointer
|
|
127
|
+
|
|
128
|
+
AFTER the report file is written via `Write`, update the pointer
|
|
129
|
+
inside a second `withFileLock` call so a crash between "write report"
|
|
130
|
+
and "update pointer" leaves the pointer STALE — the next run
|
|
131
|
+
re-covers the missing period (safe-by-default).
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
node -e '
|
|
135
|
+
const { withFileLock, atomicWriteFileSync } = require("./lib/core.cjs");
|
|
136
|
+
withFileLock(
|
|
137
|
+
process.argv[1],
|
|
138
|
+
() => atomicWriteFileSync(process.argv[1], process.argv[2]),
|
|
139
|
+
{ timeoutMs: 10000 }
|
|
140
|
+
);
|
|
141
|
+
' "$POINTER" "$NOW_ISO"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Using `atomicWriteFileSync` ensures the pointer update is crash-safe
|
|
145
|
+
(ADR-0004) — a mid-write crash leaves the OLD pointer intact, not a
|
|
146
|
+
truncated file.
|
|
147
|
+
|
|
148
|
+
## Commit
|
|
149
|
+
|
|
150
|
+
Both the new report and the updated pointer land in a single atomic
|
|
151
|
+
docs commit per ADR-0004. Route through `node np-tools.cjs commit`
|
|
152
|
+
so `lib/git.cjs.assertCommittablePaths()` validates the paths.
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
node np-tools.cjs commit "docs(10): add session report — ${LOCAL_FILENAME_TS}" \
|
|
156
|
+
--files "$REPORT_PATH" "$POINTER"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Report
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
Session report: $REPORT_PATH
|
|
163
|
+
Since: <since_iso from JSON>
|
|
164
|
+
Records: <record_count>
|
|
165
|
+
Pointer: $POINTER (updated to $NOW_ISO)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Scope Guardrail
|
|
169
|
+
|
|
170
|
+
<scope_guardrail>
|
|
171
|
+
**Do:**
|
|
172
|
+
- Wrap pointer reads AND pointer writes in `withFileLock` (10s
|
|
173
|
+
timeout per Pitfall 8, T-10-06-02 mitigation).
|
|
174
|
+
- Use local time `YYYY-MM-DDTHHMM` for the filename prefix (D-17 —
|
|
175
|
+
no seconds; deterministic sort; no overwrite).
|
|
176
|
+
- Render `—` for null token fields (Phase 9 D-09 non-claude runtimes).
|
|
177
|
+
- Commit BOTH the report file AND the updated pointer in a single
|
|
178
|
+
atomic commit (ADR-0004).
|
|
179
|
+
- Delegate all JSONL parsing to `lib/metrics-aggregate.cjs` (D-18
|
|
180
|
+
schema single-source-of-truth).
|
|
181
|
+
|
|
182
|
+
**Don't:**
|
|
183
|
+
- Use a 24h rolling window (rejected per D-16 — two invocations in 12
|
|
184
|
+
hours would double-count the overlap).
|
|
185
|
+
- Overwrite `SESSION_REPORT.md` (rejected per D-17 — previous reports
|
|
186
|
+
would be lost on every run).
|
|
187
|
+
- Bypass `aggregateSession` for raw JSONL reads — schema guarantees
|
|
188
|
+
come from the aggregator.
|
|
189
|
+
- Update the pointer BEFORE the report file write succeeds — a crash
|
|
190
|
+
between the two would skip a session.
|
|
191
|
+
- Invoke host-specific prompt tools directly (the BARE_ASKUSER lint
|
|
192
|
+
in `bin/check-workflows.cjs` blocks them) — route through
|
|
193
|
+
`node np-tools.cjs askuser --json '…'`.
|
|
194
|
+
- Add a `metrics record` block. No Task/Spawn site; Pitfall 9 /
|
|
195
|
+
`workflow-missing-metrics` is exempt.
|
|
196
|
+
</scope_guardrail>
|
|
197
|
+
|
|
198
|
+
## Output
|
|
199
|
+
|
|
200
|
+
- `.nubos-pilot/reports/YYYY-MM-DDTHHMM-session-report.md` — rendered
|
|
201
|
+
markdown with session summary, per-phase table (null tokens as
|
|
202
|
+
`—`), and metadata header.
|
|
203
|
+
- `.nubos-pilot/reports/.last-session` — pointer file updated to the
|
|
204
|
+
current ISO-8601 UTC timestamp (atomic write; file-locked).
|
|
205
|
+
- One atomic git commit
|
|
206
|
+
`docs(10): add session report — <local-ts>` containing both files
|
|
207
|
+
(ADR-0004).
|
|
208
|
+
|
|
209
|
+
## Success Criteria
|
|
210
|
+
|
|
211
|
+
- [ ] `--since=<ISO>` argv override honoured when present.
|
|
212
|
+
- [ ] Reports directory created via `projectStateDir` +
|
|
213
|
+
`mkdir -p` (no direct project-state reads).
|
|
214
|
+
- [ ] Pointer read AND pointer write both wrapped in `withFileLock`
|
|
215
|
+
with `timeoutMs: 10000` (Pitfall 8 / T-10-06-02).
|
|
216
|
+
- [ ] Metrics aggregation via `lib/metrics-aggregate.cjs.aggregateSession`
|
|
217
|
+
(D-18 — workflow never parses JSONL directly).
|
|
218
|
+
- [ ] Filename format `YYYY-MM-DDTHHMM-session-report.md` (D-17 —
|
|
219
|
+
no overwrite, deterministic sort).
|
|
220
|
+
- [ ] Null token fields rendered as `—` in the Phase table (D-09 /
|
|
221
|
+
D-15).
|
|
222
|
+
- [ ] Pointer update happens AFTER report write succeeds (stale
|
|
223
|
+
pointer on crash is safer than skipped session).
|
|
224
|
+
- [ ] Pointer written via `atomicWriteFileSync` (ADR-0004 crash-safety).
|
|
225
|
+
- [ ] Single atomic commit via `np-tools.cjs commit` containing both
|
|
226
|
+
report file and pointer.
|
|
227
|
+
- [ ] Lint clean under `bin/check-workflows.cjs` — no BARE_ASKUSER
|
|
228
|
+
violations, no DIRECT_READ matches.
|
|
229
|
+
|
|
230
|
+
## Related Workflows
|
|
231
|
+
|
|
232
|
+
- **`/np:stats`** — stats snapshot (read-only, no pointer, no commit).
|
|
233
|
+
- **`/np:cleanup`** — archive completed milestones (distinct
|
|
234
|
+
milestone-level aggregation).
|
|
235
|
+
|
|
236
|
+
## Design Notes
|
|
237
|
+
|
|
238
|
+
D-16 pointer file replaces any rolling-window approach with
|
|
239
|
+
deterministic "since last report" semantics. D-17 ISO-prefixed
|
|
240
|
+
filename makes reports append-only and deterministically sortable.
|
|
241
|
+
D-18 delegates metrics aggregation to `lib/metrics-aggregate.cjs`
|
|
242
|
+
(landed Plan 10-01-T02). Pitfall 8 mitigation wraps pointer access
|
|
243
|
+
in `withFileLock`.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
command: np:skip
|
|
3
|
+
description: Mark a task as skipped (lifecycle CRUD). The task is excluded from wave-selection until it is unparked or its status is set back to pending.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /np:skip
|
|
7
|
+
|
|
8
|
+
<objective>
|
|
9
|
+
Flip the task's frontmatter `status` field to `skipped`. The wave-selector
|
|
10
|
+
treats `skipped` like `done` for advancement purposes, so the next wave
|
|
11
|
+
can proceed without this task. No commit is made; the task file is rewritten
|
|
12
|
+
in place.
|
|
13
|
+
</objective>
|
|
14
|
+
|
|
15
|
+
## Execution
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
TASK_ID="$1"
|
|
19
|
+
if [ -z "$TASK_ID" ]; then
|
|
20
|
+
echo "Usage: /np:skip <task-id>" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
node np-tools.cjs skip "$TASK_ID"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Scope Guardrail
|
|
27
|
+
|
|
28
|
+
**Do:** flip task status to `skipped` via `lib/tasks.setTaskStatus`.
|
|
29
|
+
**Don't:** revert commits; modify other frontmatter fields.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
command: np:stats
|
|
3
|
+
description: Stats output — phases-table (name/plans/completed/status/%) + metrics aggregation (tokens-in/out per phase, avg duration by tier, retry_count_sum, error_rate). Consumes node np-tools.cjs stats json. Null-token runtimes render as `—` (Phase 9 D-09). Read-only — no commits, no STATE mutation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# np:stats
|
|
7
|
+
|
|
8
|
+
Implements UTIL-07b. Renders an on-demand snapshot combining a
|
|
9
|
+
phases-table (phase / plans total / complete / status / percent)
|
|
10
|
+
with metrics aggregation (tokens-in / tokens-out per phase, avg
|
|
11
|
+
duration by tier, retry_count_sum, error_rate). Read-only surface
|
|
12
|
+
per D-20 SC-5 — no files written, no state mutated, no git commit.
|
|
13
|
+
|
|
14
|
+
The workflow delegates ALL data collection to
|
|
15
|
+
`bin/np-tools/stats.cjs` (landed Plan 10-01-T04), which emits a
|
|
16
|
+
`schema_version:1` JSON envelope on stdout. This workflow consumes
|
|
17
|
+
that JSON and renders markdown. No JSONL parsing inline.
|
|
18
|
+
|
|
19
|
+
Pure read-only workflow — no agent spawn, no resolve-model, no
|
|
20
|
+
metrics record. Pitfall 9 / `workflow-missing-metrics` is exempt.
|
|
21
|
+
|
|
22
|
+
## Initialize
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
STATS_JSON=$(node np-tools.cjs stats json)
|
|
26
|
+
if [[ -z "$STATS_JSON" ]]; then
|
|
27
|
+
echo "No stats available (empty project?)" >&2
|
|
28
|
+
exit 0
|
|
29
|
+
fi
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The stats CLI produces the full payload — `{schema_version, milestone,
|
|
33
|
+
phases, plans_total, plans_complete, percent, git, last_activity,
|
|
34
|
+
metrics_by_phase}`. An empty project yields an empty JSON; the
|
|
35
|
+
workflow short-circuits gracefully rather than producing an empty
|
|
36
|
+
table.
|
|
37
|
+
|
|
38
|
+
## Render
|
|
39
|
+
|
|
40
|
+
Render the JSON as markdown via a `node -e` one-liner. Null token
|
|
41
|
+
cells render as `—` (Phase 9 D-09) so non-claude runtimes don't show
|
|
42
|
+
misleading zeros. Progress bar is a 20-char block-string
|
|
43
|
+
`[████░░░░░░…]` (ADR-0002 — no cli-progress dep).
|
|
44
|
+
|
|
45
|
+
Progress bar helper:
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
const filled = Math.round(percent / 5);
|
|
49
|
+
const bar = "█".repeat(filled) + "░".repeat(20 - filled);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Null-safe token rendering:
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
const fmt = (v) => v === null || v === undefined ? "—" : v.toLocaleString();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Rendered output sections: Project Stats header (milestone / progress
|
|
59
|
+
bar / last activity / commits / start date), Phases table (phase /
|
|
60
|
+
name / plans / complete / status / percent), Metrics by Phase table
|
|
61
|
+
(records / tokens / tier-avg durations / errors). Example row for a
|
|
62
|
+
phase with no metrics: `| 10 | — | — | — | — | — | — | — |`.
|
|
63
|
+
|
|
64
|
+
The full render is a single `node -e` call consuming `$STATS_JSON`:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
node -e '
|
|
68
|
+
const j = JSON.parse(process.argv[1]);
|
|
69
|
+
const fmt = (v) => v === null || v === undefined ? "—" : (typeof v === "number" ? v.toLocaleString() : String(v));
|
|
70
|
+
const filled = Math.round((j.percent || 0) / 5);
|
|
71
|
+
const bar = "█".repeat(filled) + "░".repeat(20 - filled);
|
|
72
|
+
const lines = [];
|
|
73
|
+
lines.push("## Project Stats");
|
|
74
|
+
lines.push("");
|
|
75
|
+
lines.push(`**Milestone:** ${fmt(j.milestone && j.milestone.version)} — ${fmt(j.milestone && j.milestone.name)}`);
|
|
76
|
+
lines.push(`**Progress:** [${bar}] ${j.percent || 0}% (${j.plans_complete}/${j.plans_total} plans)`);
|
|
77
|
+
lines.push(`**Last activity:** ${fmt(j.last_activity)}`);
|
|
78
|
+
lines.push(`**Commits:** ${fmt(j.git && j.git.commits)}`);
|
|
79
|
+
lines.push(`**Project started:** ${fmt(j.git && j.git.first_commit_at)}`);
|
|
80
|
+
lines.push("");
|
|
81
|
+
lines.push("### Phases");
|
|
82
|
+
lines.push("");
|
|
83
|
+
lines.push("| Phase | Name | Plans | Completed | Status | % |");
|
|
84
|
+
lines.push("|-------|------|-------|-----------|--------|---|");
|
|
85
|
+
for (const ph of (j.phases || [])) {
|
|
86
|
+
const pct = ph.plans_total > 0 ? Math.round(ph.plans_complete / ph.plans_total * 100) : 0;
|
|
87
|
+
lines.push(`| ${ph.number} | ${ph.name} | ${ph.plans_total} | ${ph.plans_complete} | ${ph.status} | ${pct}% |`);
|
|
88
|
+
}
|
|
89
|
+
lines.push("");
|
|
90
|
+
lines.push("### Metrics by Phase");
|
|
91
|
+
lines.push("");
|
|
92
|
+
lines.push("| Phase | Records | Tokens In | Tokens Out | Avg Opus ms | Avg Sonnet ms | Avg Haiku ms | Errors |");
|
|
93
|
+
lines.push("|-------|---------|-----------|------------|-------------|---------------|--------------|--------|");
|
|
94
|
+
for (const ph of (j.phases || [])) {
|
|
95
|
+
const m = (j.metrics_by_phase || {})[ph.number];
|
|
96
|
+
if (!m || m.record_count === 0) {
|
|
97
|
+
lines.push(`| ${ph.number} | — | — | — | — | — | — | — |`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const t = m.avg_duration_ms_by_tier || {};
|
|
101
|
+
lines.push(`| ${ph.number} | ${m.record_count} | ${fmt(m.total_tokens_in)} | ${fmt(m.total_tokens_out)} | ${fmt(t.opus)} | ${fmt(t.sonnet)} | ${fmt(t.haiku)} | ${m.error_count} |`);
|
|
102
|
+
}
|
|
103
|
+
process.stdout.write(lines.join("\n") + "\n");
|
|
104
|
+
' "$STATS_JSON"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## No Commit
|
|
108
|
+
|
|
109
|
+
Stats is read-only (D-20 SC-5). No files are written, no state is
|
|
110
|
+
mutated, no git commit is made. The markdown goes directly to stdout
|
|
111
|
+
and is rendered by the agent CLI.
|
|
112
|
+
|
|
113
|
+
## Scope Guardrail
|
|
114
|
+
|
|
115
|
+
<scope_guardrail>
|
|
116
|
+
**Do:**
|
|
117
|
+
- Consume `node np-tools.cjs stats json` — trust its
|
|
118
|
+
`schema_version: 1` output shape (Plan 10-01-T04 contract).
|
|
119
|
+
- Render `tokens_in` / `tokens_out` as `—` when null (Phase 9 D-09
|
|
120
|
+
non-claude runtimes; D-15 hybrid-output decision).
|
|
121
|
+
- Render the progress bar as a 20-char `[████░░…]` string (ADR-0002
|
|
122
|
+
— no cli-progress dep).
|
|
123
|
+
- Keep the workflow read-only — no files written, no STATE mutated,
|
|
124
|
+
no git commit (D-20 SC-5).
|
|
125
|
+
|
|
126
|
+
**Don't:**
|
|
127
|
+
- Re-implement JSONL aggregation inline — `lib/metrics-aggregate.cjs`
|
|
128
|
+
owns the schema (D-18).
|
|
129
|
+
- Write any files — this workflow is a render, not a producer.
|
|
130
|
+
- Add a git commit — there is nothing to commit.
|
|
131
|
+
- Invoke host-specific prompt tools directly (the BARE_ASKUSER lint
|
|
132
|
+
in `bin/check-workflows.cjs` blocks them) — route through
|
|
133
|
+
`node np-tools.cjs askuser --json '…'` if prompts are ever added.
|
|
134
|
+
- Add a `metrics record` block. No Task/Spawn site; Pitfall 9 /
|
|
135
|
+
`workflow-missing-metrics` is exempt.
|
|
136
|
+
</scope_guardrail>
|
|
137
|
+
|
|
138
|
+
## Output
|
|
139
|
+
|
|
140
|
+
- Markdown snapshot on stdout with Project Stats header, Phases
|
|
141
|
+
table, and Metrics by Phase table.
|
|
142
|
+
- No files created. No state mutated. No git commit.
|
|
143
|
+
|
|
144
|
+
## Success Criteria
|
|
145
|
+
|
|
146
|
+
- [ ] Data sourced exclusively from `node np-tools.cjs stats json`
|
|
147
|
+
(Plan 10-01-T04) — no inline JSONL parsing.
|
|
148
|
+
- [ ] Null `tokens_in` / `tokens_out` render as `—` (D-09 / D-15).
|
|
149
|
+
- [ ] Progress bar is a 20-char block-string (ADR-0002).
|
|
150
|
+
- [ ] Phases table contains phase / name / plans_total / completed
|
|
151
|
+
/ status / percent.
|
|
152
|
+
- [ ] Metrics-by-phase table shows records / tokens / tier-avg
|
|
153
|
+
durations / errors per phase.
|
|
154
|
+
- [ ] Zero file writes, zero state mutations, zero commits (D-20
|
|
155
|
+
SC-5).
|
|
156
|
+
- [ ] Lint clean under `bin/check-workflows.cjs` — no BARE_ASKUSER
|
|
157
|
+
violations, no DIRECT_READ matches.
|
|
158
|
+
|
|
159
|
+
## Related Workflows
|
|
160
|
+
|
|
161
|
+
- **`/np:progress`** — base-workflow percent snapshot (subset).
|
|
162
|
+
- **`/np:session-report`** — commits a rendered report with
|
|
163
|
+
since-last-session metrics (the producer pair for `/np:stats`).
|
|
164
|
+
|
|
165
|
+
## Design Notes
|
|
166
|
+
|
|
167
|
+
Phases-table + metrics aggregation combined per D-15. Stats CLI
|
|
168
|
+
(`bin/np-tools/stats.cjs`) is the single data source (D-20 SC-5).
|
|
169
|
+
Null-token semantics from Phase 9 D-09. Progress bar uses block
|
|
170
|
+
characters (ADR-0002) instead of a cli-progress dep.
|