create-sdd-project 0.18.0 → 0.18.1

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/lib/meta.js CHANGED
@@ -281,6 +281,44 @@ function expectedSmartDiffTrackedPaths(aiTools, projectType) {
281
281
  paths.add(`${dir}/skills/development-workflow/references/merge-checklist.md`);
282
282
  }
283
283
 
284
+ // v0.18.1: shipped slash-commands — preserve user customizations across
285
+ // upgrades. Closes v0.18.0 known limitation where audit-merge.md was
286
+ // wholesale-overwritten (notable since teams may tune drift recipes for
287
+ // their PR/ticket conventions). Same hash decision tree as agents +
288
+ // standards: missing/force → write, hash match → replace, hash mismatch
289
+ // → preserve + .new backup, no hash → fallback content compare.
290
+ const COMMAND_FILES_CLAUDE = [
291
+ 'audit-merge.md',
292
+ 'context-prompt.md',
293
+ 'review-plan.md',
294
+ 'review-project.md',
295
+ 'review-spec.md',
296
+ ];
297
+ const COMMAND_FILES_GEMINI = [
298
+ // Each Claude command has a Gemini twin: a thin TOML wrapper + a body
299
+ // -instructions.md. Both must be tracked since users may edit either.
300
+ 'audit-merge.toml',
301
+ 'audit-merge-instructions.md',
302
+ 'context-prompt.toml',
303
+ 'context-prompt-instructions.md',
304
+ 'review-plan.toml',
305
+ 'review-plan-instructions.md',
306
+ 'review-project.toml',
307
+ 'review-project-instructions.md',
308
+ 'review-spec.toml',
309
+ 'review-spec-instructions.md',
310
+ ];
311
+ if (aiTools !== 'gemini') {
312
+ for (const cmd of COMMAND_FILES_CLAUDE) {
313
+ paths.add(`.claude/commands/${cmd}`);
314
+ }
315
+ }
316
+ if (aiTools !== 'claude') {
317
+ for (const cmd of COMMAND_FILES_GEMINI) {
318
+ paths.add(`.gemini/commands/${cmd}`);
319
+ }
320
+ }
321
+
284
322
  return paths;
285
323
  }
286
324
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sdd-project",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "description": "Create a new SDD DevFlow project with AI-assisted development workflow",
5
5
  "bin": {
6
6
  "create-sdd-project": "bin/cli.js"
@@ -55,14 +55,14 @@ Eleven empirically-validated drift patterns. Failures are NOT blockers for the c
55
55
  **12. P1 — PR body test count stale.** The PR body's "npm test" line should match the terminal test count in the ticket (AC / DoD / Completion Log last entry). Agents commonly open the PR at Step 4 and add tests during Step 5 review — the PR body number becomes stale.
56
56
  ```bash
57
57
  PR_BODY=$(gh pr view --json body -q .body)
58
- PR_TESTS=$(echo "$PR_BODY" | grep -iE "(npm test|tests?.*(pass|green))" | grep -oE "[0-9]+/[0-9]+" | head -1)
59
- TICKET_TESTS=$(grep -iE "(npm test|tests?.*(pass|green))" "$TICKET" | grep -oE "[0-9]+/[0-9]+" | tail -1)
58
+ PR_TESTS=$(echo "$PR_BODY" | grep -iE "(npm test|tests?[^|]*[0-9]|[*: ]tests?[*: ]+[0-9])" | grep -oE "[0-9]+/[0-9]+" | head -1)
59
+ TICKET_TESTS=$(grep -iE "(npm test|tests?[^|]*[0-9]|[*: ]tests?[*: ]+[0-9])" "$TICKET" | grep -oE "[0-9]+/[0-9]+" | tail -1)
60
60
  [ -n "$PR_TESTS" ] && [ -n "$TICKET_TESTS" ] && [ "$PR_TESTS" != "$TICKET_TESTS" ] && flag "P1 drift: PR body $PR_TESTS vs ticket $TICKET_TESTS"
61
61
  ```
62
62
 
63
63
  **13. P2 — Merge Checklist Evidence rows aspirational.** Rows marked `[x]` with future-tense Evidence ("will land", "to be created", "pending", "next commit", "TBD") — the row claims done but the work hasn't happened yet.
64
64
  ```bash
65
- awk '/^## Merge Checklist Evidence/,/^## /' "$TICKET" \
65
+ awk '/^## Merge Checklist Evidence/{flag=1; next} /^## /{flag=0} flag' "$TICKET" \
66
66
  | grep -E '^\|.*\[x\].*(to be |will |pending|TBD|Will be |to be created|next commit|aspirational)' \
67
67
  && flag "P2 drift: aspirational row(s) found"
68
68
  ```
@@ -82,7 +82,7 @@ done < /tmp/pm_items.txt
82
82
 
83
83
  **15. P4 — Remote branch orphan after "deleted".** Workflow Step 6 claims `[x] branch deleted` but origin still has the branch.
84
84
  ```bash
85
- BRANCH=$(grep -E "^\*\*[Bb]ranch:\*\*" "$TICKET" | head -1 | sed -E 's/^\*\*[Bb]ranch:\*\*[[:space:]]*([^[:space:]|]+).*/\1/')
85
+ BRANCH=$(grep -oE '\*\*[Bb]ranch:\*\*[[:space:]]*[^[:space:]|()]+' "$TICKET" | head -1 | sed -E 's/^\*\*[Bb]ranch:\*\*[[:space:]]*//')
86
86
  git fetch origin --prune --quiet
87
87
  git ls-remote --heads origin "$BRANCH" 2>/dev/null | grep -q refs/heads && flag "P4 drift: remote branch $BRANCH still exists (run: git push origin --delete $BRANCH)"
88
88
  ```
@@ -91,7 +91,10 @@ git ls-remote --heads origin "$BRANCH" 2>/dev/null | grep -q refs/heads && flag
91
91
  ```bash
92
92
  FROZEN_COUNT=0
93
93
  for t in docs/tickets/*.md; do
94
- status=$(grep -E "^\*\*Status:\*\*" "$t" | head -1 | sed -E 's/^\*\*Status:\*\*[[:space:]]*([A-Za-z ]+)[[:space:]]*\|.*/\1/' | sed -E 's/[[:space:]]+$//')
94
+ status=$(grep -E "^\*\*Status:\*\*" "$t" | head -1 \
95
+ | sed -E 's/^\*\*Status:\*\*[[:space:]]*\*?\*?//' \
96
+ | sed -E 's/[[:space:]]*\*?\*?[[:space:]]*\|.*//' \
97
+ | sed -E 's/[[:space:]]+$//')
95
98
  [ "$status" = "Done" ] && continue
96
99
  ticket_id=$(basename "$t" .md | sed -E 's/-[a-z].*//')
97
100
  git log --all --oneline --grep="$ticket_id" | grep -q . && FROZEN_COUNT=$((FROZEN_COUNT+1))
@@ -126,15 +129,15 @@ COMPLETION=$(awk '/^## Completion Log/,/^## Merge Checklist/' "$TICKET")
126
129
  CHECKED_STEPS=$(echo "$WORKFLOW" | grep -E "^- \[x\] Step [0-9]+:" | sed -E 's/^- \[x\] Step ([0-9]+):.*/\1/' | sort -u)
127
130
  while read -r step_num; do
128
131
  [ -z "$step_num" ] && continue
129
- echo "$COMPLETION" | grep -qE "Step[[:space:]]+$step_num([^0-9]|$)" || flag "P8 drift: Step $step_num [x] but no Completion Log entry"
132
+ echo "$COMPLETION" | grep -qE "^\|[^|]*\|[[:space:]]*Step[[:space:]]+$step_num([^0-9]|$)" || flag "P8 drift: Step $step_num [x] but no dedicated Completion Log entry"
130
133
  done <<< "$CHECKED_STEPS"
131
134
  ```
132
135
 
133
136
  **20. P9 — Tracker header "Last Updated" stale.** The `**Last Updated:**` header and the `**Active Feature:**` detail should agree on step number (e.g., both say 5/6). Mismatch suggests the header wasn't refreshed after state transitions.
134
137
  ```bash
135
138
  TRACKER=docs/project_notes/product-tracker.md
136
- HEADER_STEP=$(grep -oE 'Step [0-9]+/6' "$TRACKER" | head -1)
137
- DETAIL_STEP=$(grep -A 1 '^\*\*Active Feature:\*\*' "$TRACKER" | grep -oE 'Step [0-9]+/6' | head -1)
139
+ HEADER_STEP=$(grep '^\*\*Last Updated:\*\*' "$TRACKER" | grep -oE '(Step )?[0-9]+/6' | head -1 | sed -E 's/^Step //')
140
+ DETAIL_STEP=$(grep -A 1 '^\*\*Active Feature:\*\*' "$TRACKER" | grep -oE '(Step )?[0-9]+/6' | head -1 | sed -E 's/^Step //')
138
141
  [ -n "$HEADER_STEP" ] && [ -n "$DETAIL_STEP" ] && [ "$HEADER_STEP" != "$DETAIL_STEP" ] \
139
142
  && flag "P9 drift: tracker header says $HEADER_STEP, Active Feature says $DETAIL_STEP"
140
143
  ```
@@ -151,7 +154,10 @@ awk -F'|' '/^\| [0-9]{4}-[0-9]{2}-[0-9]{2}/ {
151
154
 
152
155
  **22. P11 — Tracker Features table status vs ticket Status mismatch.** Ticket Status=Ready for Merge / Review → tracker expects `in-progress`. Ticket Status=Done → tracker expects `done`. Mismatch means one side wasn't updated after the state change.
153
156
  ```bash
154
- TICKET_STATUS=$(grep -E "^\*\*Status:\*\*" "$TICKET" | head -1 | sed -E 's/^\*\*Status:\*\*[[:space:]]*([A-Za-z ]+)[[:space:]]*\|.*/\1/' | sed -E 's/[[:space:]]+$//')
157
+ TICKET_STATUS=$(grep -E "^\*\*Status:\*\*" "$TICKET" | head -1 \
158
+ | sed -E 's/^\*\*Status:\*\*[[:space:]]*\*?\*?//' \
159
+ | sed -E 's/[[:space:]]*\*?\*?[[:space:]]*\|.*//' \
160
+ | sed -E 's/[[:space:]]+$//')
155
161
  FEATURE_ID=$(basename "$TICKET" .md | sed -E 's/-[a-z].*//')
156
162
  TRACKER_STATUS=$(grep -F "$FEATURE_ID" docs/project_notes/product-tracker.md | grep -oE "\| (in-progress|done|pending|blocked) \|" | head -1 | sed -E 's/\| ([a-z-]+) \|/\1/')
157
163
  case "$TICKET_STATUS" in
@@ -163,6 +169,20 @@ esac
163
169
  && flag "P11 drift: ticket Status='$TICKET_STATUS' expects tracker='$EXPECTED' but tracker='$TRACKER_STATUS'"
164
170
  ```
165
171
 
172
+ ### Execution discipline (added v0.18.1)
173
+
174
+ For each of the 11 drift checks (P1–P11), if you declare PASS, **include the literal command output** (or its absence — explicit "no rows matched", "extracted: feature/foo", "FROZEN_COUNT=0") as evidence in your report. A bare "PASS" without supporting output is treated as **NOT EXECUTED** by the auditor — re-run with output captured.
175
+
176
+ Recommended pattern:
177
+
178
+ ```
179
+ P1 PR body test count stale | PASS | PR_TESTS=4110/4110, TICKET_TESTS=4110/4110 (matched)
180
+ P2 Aspirational rows | PASS | awk … | grep … (no rows matched)
181
+ P5 Frozen ticket Status | PASS | FROZEN_COUNT=0
182
+ ```
183
+
184
+ This prevents two failure modes empirically observed during the v0.18.1 origin audit (fx F-H9 + F-H10): (a) the agent abbreviates execution and reports CLEAN by inference from MEMORY/design knowledge; (b) buggy recipes return empty output silently and the agent treats empty as PASS without verifying the recipe ran. Both are caught when literal output is required.
185
+
166
186
  ### Output Format
167
187
 
168
188
  Report two tables — one for **structural (blocking)** compliance, one for **drift (advisory)**. Emit two verdicts plus a combined summary line.
@@ -55,48 +55,52 @@ Eleven empirically-validated drift patterns. Failures are NOT blockers for the c
55
55
  **12. P1 — PR body test count stale.** Ratio must co-occur with test/pass/green marker to avoid AC/DoD ratios (14/14, 7/7).
56
56
  ```bash
57
57
  PR_BODY=$(gh pr view --json body -q .body)
58
- PR_TESTS=$(echo "$PR_BODY" | grep -iE "(npm test|tests?.*(pass|green))" | grep -oE "[0-9]+/[0-9]+" | head -1)
59
- TICKET_TESTS=$(grep -iE "(npm test|tests?.*(pass|green))" "$TICKET" | grep -oE "[0-9]+/[0-9]+" | tail -1)
60
- [ -n "$PR_TESTS" ] && [ -n "$TICKET_TESTS" ] && [ "$PR_TESTS" != "$TICKET_TESTS" ] && flag "P1: PR body $PR_TESTS vs ticket $TICKET_TESTS"
58
+ PR_TESTS=$(echo "$PR_BODY" | grep -iE "(npm test|tests?[^|]*[0-9]|[*: ]tests?[*: ]+[0-9])" | grep -oE "[0-9]+/[0-9]+" | head -1)
59
+ TICKET_TESTS=$(grep -iE "(npm test|tests?[^|]*[0-9]|[*: ]tests?[*: ]+[0-9])" "$TICKET" | grep -oE "[0-9]+/[0-9]+" | tail -1)
60
+ [ -n "$PR_TESTS" ] && [ -n "$TICKET_TESTS" ] && [ "$PR_TESTS" != "$TICKET_TESTS" ] && flag "P1 drift: PR body $PR_TESTS vs ticket $TICKET_TESTS"
61
61
  ```
62
62
 
63
63
  **13. P2 — Merge Checklist Evidence aspirational.** `[x]` rows with future-tense text.
64
64
  ```bash
65
- awk '/^## Merge Checklist Evidence/,/^## /' "$TICKET" \
65
+ awk '/^## Merge Checklist Evidence/{flag=1; next} /^## /{flag=0} flag' "$TICKET" \
66
66
  | grep -E '^\|.*\[x\].*(to be |will |pending|TBD|Will be |to be created|next commit|aspirational)' \
67
- && flag "P2: aspirational row(s)"
67
+ && flag "P2 drift: aspirational row(s) found"
68
68
  ```
69
69
 
70
70
  **14. P3 — Post-merge actions not logged** (post-merge only).
71
71
  ```bash
72
+ # Strip checkbox prefix before comparison; use grep -Fq fixed-string match.
72
73
  grep -E "^- \[ \].*(post-merge|operator|prod rollout|pending verification)" "$TICKET" \
73
74
  | sed -E 's/^- \[ \] //' > /tmp/pm_items.txt
74
75
  COMPLETION=$(awk '/^## Completion Log/,/^## Merge Checklist/' "$TICKET")
75
76
  while IFS= read -r item; do
76
77
  [ -z "$item" ] && continue
77
78
  KEY=$(echo "$item" | cut -c1-40)
78
- echo "$COMPLETION" | grep -Fq "$KEY" || flag "P3: '$item' not logged"
79
+ echo "$COMPLETION" | grep -Fq "$KEY" || flag "P3 drift: post-merge '$item' not in Completion Log"
79
80
  done < /tmp/pm_items.txt
80
81
  ```
81
82
 
82
83
  **15. P4 — Remote branch orphan.**
83
84
  ```bash
84
- BRANCH=$(grep -E "^\*\*[Bb]ranch:\*\*" "$TICKET" | head -1 | sed -E 's/^\*\*[Bb]ranch:\*\*[[:space:]]*([^[:space:]|]+).*/\1/')
85
+ BRANCH=$(grep -oE '\*\*[Bb]ranch:\*\*[[:space:]]*[^[:space:]|()]+' "$TICKET" | head -1 | sed -E 's/^\*\*[Bb]ranch:\*\*[[:space:]]*//')
85
86
  git fetch origin --prune --quiet
86
- git ls-remote --heads origin "$BRANCH" 2>/dev/null | grep -q refs/heads && flag "P4: branch $BRANCH still on origin"
87
+ git ls-remote --heads origin "$BRANCH" 2>/dev/null | grep -q refs/heads && flag "P4 drift: remote branch $BRANCH still exists (run: git push origin --delete $BRANCH)"
87
88
  ```
88
89
 
89
90
  **16. P5 — Frozen ticket Status post-merge.** Multi-word status via sed char class, not `\w+`.
90
91
  ```bash
91
92
  FROZEN_COUNT=0
92
93
  for t in docs/tickets/*.md; do
93
- status=$(grep -E "^\*\*Status:\*\*" "$t" | head -1 | sed -E 's/^\*\*Status:\*\*[[:space:]]*([A-Za-z ]+)[[:space:]]*\|.*/\1/' | sed -E 's/[[:space:]]+$//')
94
+ status=$(grep -E "^\*\*Status:\*\*" "$t" | head -1 \
95
+ | sed -E 's/^\*\*Status:\*\*[[:space:]]*\*?\*?//' \
96
+ | sed -E 's/[[:space:]]*\*?\*?[[:space:]]*\|.*//' \
97
+ | sed -E 's/[[:space:]]+$//')
94
98
  [ "$status" = "Done" ] && continue
95
99
  ticket_id=$(basename "$t" .md | sed -E 's/-[a-z].*//')
96
100
  git log --all --oneline --grep="$ticket_id" | grep -q . && FROZEN_COUNT=$((FROZEN_COUNT+1))
97
101
  done
98
- [ "$FROZEN_COUNT" -ge 2 ] && flag "P5 SYSTEMIC: $FROZEN_COUNT frozen tickets"
99
- [ "$FROZEN_COUNT" -eq 1 ] && flag "P5: 1 frozen ticket"
102
+ [ "$FROZEN_COUNT" -ge 2 ] && flag "P5 drift (SYSTEMIC): $FROZEN_COUNT frozen tickets — Status not updated post-merge"
103
+ [ "$FROZEN_COUNT" -eq 1 ] && flag "P5 drift: 1 frozen ticket"
100
104
  ```
101
105
 
102
106
  **17. P6 — AC count off-by-N.**
@@ -104,7 +108,7 @@ done
104
108
  ACTUAL=$(awk '/^## Acceptance Criteria/,/^## Definition of Done/' "$TICKET" | grep -cE "^- \[[x ]\]")
105
109
  CLAIMED=$(grep -oE 'all [0-9]+ marked|AC: [0-9]+/[0-9]+' "$TICKET" | head -1 | grep -oE "[0-9]+" | head -1)
106
110
  [ -n "$CLAIMED" ] && [ "$CLAIMED" != "$ACTUAL" ] && [ $((ACTUAL - CLAIMED)) -ge 2 -o $((CLAIMED - ACTUAL)) -ge 2 ] \
107
- && flag "P6: claim $CLAIMED vs actual $ACTUAL"
111
+ && flag "P6 drift: claim '$CLAIMED' vs actual AC count $ACTUAL"
108
112
  ```
109
113
 
110
114
  **18. P7 — Test count drift within ticket (final-sections only).**
@@ -112,8 +116,9 @@ CLAIMED=$(grep -oE 'all [0-9]+ marked|AC: [0-9]+/[0-9]+' "$TICKET" | head -1 | g
112
116
  TERMINAL=$(awk '/^## Completion Log/,/^## Merge Checklist/' "$TICKET" | grep -iE "(test|pass|green)" | grep -oE "[0-9]+/[0-9]+" | tail -1)
113
117
  AC=$(awk '/^## Acceptance Criteria/,/^## Definition of Done/' "$TICKET")
114
118
  DOD=$(awk '/^## Definition of Done/,/^## Workflow Checklist/' "$TICKET")
115
- for n in $(printf '%s\n%s\n' "$AC" "$DOD" | grep -iE "(test|pass|green)" | grep -oE "[0-9]+/[0-9]+" | sort -u); do
116
- [ -n "$TERMINAL" ] && [ "$n" != "$TERMINAL" ] && flag "P7: final $n vs terminal $TERMINAL"
119
+ FINAL_NUMS=$(printf '%s\n%s\n' "$AC" "$DOD" | grep -iE "(test|pass|green)" | grep -oE "[0-9]+/[0-9]+" | sort -u)
120
+ for n in $FINAL_NUMS; do
121
+ [ -n "$TERMINAL" ] && [ "$n" != "$TERMINAL" ] && flag "P7 drift: final-section count $n vs terminal $TERMINAL"
117
122
  done
118
123
  ```
119
124
 
@@ -124,17 +129,17 @@ COMPLETION=$(awk '/^## Completion Log/,/^## Merge Checklist/' "$TICKET")
124
129
  CHECKED_STEPS=$(echo "$WORKFLOW" | grep -E "^- \[x\] Step [0-9]+:" | sed -E 's/^- \[x\] Step ([0-9]+):.*/\1/' | sort -u)
125
130
  while read -r step_num; do
126
131
  [ -z "$step_num" ] && continue
127
- echo "$COMPLETION" | grep -qE "Step[[:space:]]+$step_num([^0-9]|$)" || flag "P8: Step $step_num [x] but no log entry"
132
+ echo "$COMPLETION" | grep -qE "^\|[^|]*\|[[:space:]]*Step[[:space:]]+$step_num([^0-9]|$)" || flag "P8 drift: Step $step_num [x] but no dedicated Completion Log entry"
128
133
  done <<< "$CHECKED_STEPS"
129
134
  ```
130
135
 
131
136
  **20. P9 — Tracker header stale.**
132
137
  ```bash
133
138
  TRACKER=docs/project_notes/product-tracker.md
134
- HEADER_STEP=$(grep -oE 'Step [0-9]+/6' "$TRACKER" | head -1)
135
- DETAIL_STEP=$(grep -A 1 '^\*\*Active Feature:\*\*' "$TRACKER" | grep -oE 'Step [0-9]+/6' | head -1)
139
+ HEADER_STEP=$(grep '^\*\*Last Updated:\*\*' "$TRACKER" | grep -oE '(Step )?[0-9]+/6' | head -1 | sed -E 's/^Step //')
140
+ DETAIL_STEP=$(grep -A 1 '^\*\*Active Feature:\*\*' "$TRACKER" | grep -oE '(Step )?[0-9]+/6' | head -1 | sed -E 's/^Step //')
136
141
  [ -n "$HEADER_STEP" ] && [ -n "$DETAIL_STEP" ] && [ "$HEADER_STEP" != "$DETAIL_STEP" ] \
137
- && flag "P9: header $HEADER_STEP vs detail $DETAIL_STEP"
142
+ && flag "P9 drift: tracker header says $HEADER_STEP, Active Feature says $DETAIL_STEP"
138
143
  ```
139
144
 
140
145
  **21. P10 — Duplicate Completion Log rows.**
@@ -143,12 +148,16 @@ awk -F'|' '/^\| [0-9]{4}-[0-9]{2}-[0-9]{2}/ {
143
148
  key = $2 "|" $3 "|" substr($4, 1, 80)
144
149
  gsub(/^[[:space:]]+|[[:space:]]+$/, "", key)
145
150
  print key
146
- }' "$TICKET" | sort | uniq -d | while read -r dup; do flag "P10: duplicate row: $dup"; done
151
+ }' "$TICKET" | sort | uniq -d \
152
+ | while read -r dup; do flag "P10 drift: duplicate Completion Log row: $dup"; done
147
153
  ```
148
154
 
149
155
  **22. P11 — Tracker Features table status vs ticket Status mismatch.**
150
156
  ```bash
151
- TICKET_STATUS=$(grep -E "^\*\*Status:\*\*" "$TICKET" | head -1 | sed -E 's/^\*\*Status:\*\*[[:space:]]*([A-Za-z ]+)[[:space:]]*\|.*/\1/' | sed -E 's/[[:space:]]+$//')
157
+ TICKET_STATUS=$(grep -E "^\*\*Status:\*\*" "$TICKET" | head -1 \
158
+ | sed -E 's/^\*\*Status:\*\*[[:space:]]*\*?\*?//' \
159
+ | sed -E 's/[[:space:]]*\*?\*?[[:space:]]*\|.*//' \
160
+ | sed -E 's/[[:space:]]+$//')
152
161
  FEATURE_ID=$(basename "$TICKET" .md | sed -E 's/-[a-z].*//')
153
162
  TRACKER_STATUS=$(grep -F "$FEATURE_ID" docs/project_notes/product-tracker.md | grep -oE "\| (in-progress|done|pending|blocked) \|" | head -1 | sed -E 's/\| ([a-z-]+) \|/\1/')
154
163
  case "$TICKET_STATUS" in
@@ -157,9 +166,23 @@ case "$TICKET_STATUS" in
157
166
  *) EXPECTED="" ;;
158
167
  esac
159
168
  [ -n "$EXPECTED" ] && [ -n "$TRACKER_STATUS" ] && [ "$TRACKER_STATUS" != "$EXPECTED" ] \
160
- && flag "P11: Status='$TICKET_STATUS' expects tracker='$EXPECTED' but tracker='$TRACKER_STATUS'"
169
+ && flag "P11 drift: ticket Status='$TICKET_STATUS' expects tracker='$EXPECTED' but tracker='$TRACKER_STATUS'"
161
170
  ```
162
171
 
172
+ ### Execution discipline (added v0.18.1)
173
+
174
+ For each of the 11 drift checks (P1–P11), if you declare PASS, **include the literal command output** (or its absence — explicit "no rows matched", "extracted: feature/foo", "FROZEN_COUNT=0") as evidence in your report. A bare "PASS" without supporting output is treated as **NOT EXECUTED** by the auditor — re-run with output captured.
175
+
176
+ Recommended pattern:
177
+
178
+ ```
179
+ P1 PR body test count stale | PASS | PR_TESTS=4110/4110, TICKET_TESTS=4110/4110 (matched)
180
+ P2 Aspirational rows | PASS | awk … | grep … (no rows matched)
181
+ P5 Frozen ticket Status | PASS | FROZEN_COUNT=0
182
+ ```
183
+
184
+ This prevents two failure modes empirically observed during the v0.18.1 origin audit (fx F-H9 + F-H10): (a) the agent abbreviates execution and reports CLEAN by inference from MEMORY/design knowledge; (b) buggy recipes return empty output silently and the agent treats empty as PASS without verifying the recipe ran. Both are caught when literal output is required.
185
+
163
186
  ### Output Format
164
187
 
165
188
  Report two tables — one for **structural (blocking)** compliance, one for **drift (advisory)**. Emit two verdicts plus a combined summary line.