gennady 0.7.0 → 0.8.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.
Files changed (154) hide show
  1. package/ai/directives/coding/result-conventions.xml +25 -7
  2. package/ai/directives/sdd/audit.directive.xml +1 -1
  3. package/ai/directives/sdd/critic-protocol.xml +78 -0
  4. package/ai/directives/sdd/critic.directive.xml +158 -0
  5. package/ai/directives/sdd/phase-execution-protocol.xml +11 -9
  6. package/ai/skills/alt-opinion/SKILL.md +79 -0
  7. package/ai/skills/alt-opinion/opinion.prompt.md +11 -0
  8. package/ai/skills/alt-opinion/synth.prompt.md +8 -0
  9. package/ai/skills/sdd-audit/SKILL.md +13 -0
  10. package/ai/skills/sdd-check/SKILL.md +139 -0
  11. package/ai/skills/sdd-continue/SKILL.md +13 -0
  12. package/ai/skills/sdd-critic/SKILL.md +12 -0
  13. package/ai/skills/sdd-discover/SKILL.md +13 -0
  14. package/ai/skills/sdd-execute/SKILL.md +205 -0
  15. package/ai/skills/sdd-execute/scripts/README.md +84 -0
  16. package/ai/skills/sdd-execute/scripts/check-blockers.sh +158 -0
  17. package/ai/skills/sdd-execute/scripts/classify-scripts.js +111 -0
  18. package/ai/skills/sdd-execute/scripts/classify-scripts.ts +133 -0
  19. package/ai/skills/sdd-execute/scripts/extract-section.sh +219 -0
  20. package/ai/skills/sdd-execute/scripts/lint-artifacts.sh +143 -0
  21. package/ai/skills/sdd-execute/scripts/scan.sh +385 -0
  22. package/ai/skills/sdd-execute/scripts/sdd +114 -0
  23. package/ai/skills/sdd-execute/scripts/verify.sh +139 -0
  24. package/ai/skills/sdd-execute-batch/SKILL.md +213 -0
  25. package/ai/skills/sdd-fix/SKILL.md +13 -0
  26. package/ai/skills/sdd-infra/SKILL.md +13 -0
  27. package/ai/skills/sdd-module-decomposition/SKILL.md +13 -0
  28. package/ai/skills/sdd-scaffold/SKILL.md +13 -0
  29. package/ai/skills/sdd-setup/SKILL.md +13 -0
  30. package/dist/ai/directives/coding/result-conventions.xml +25 -7
  31. package/dist/ai/directives/sdd/audit.directive.xml +1 -1
  32. package/dist/ai/directives/sdd/critic-protocol.xml +78 -0
  33. package/dist/ai/directives/sdd/critic.directive.xml +158 -0
  34. package/dist/ai/directives/sdd/phase-execution-protocol.xml +11 -9
  35. package/dist/ai/skills/alt-opinion/SKILL.md +79 -0
  36. package/dist/ai/skills/alt-opinion/opinion.prompt.md +11 -0
  37. package/dist/ai/skills/alt-opinion/synth.prompt.md +8 -0
  38. package/dist/ai/skills/sdd-audit/SKILL.md +13 -0
  39. package/dist/ai/skills/sdd-check/SKILL.md +139 -0
  40. package/dist/ai/skills/sdd-continue/SKILL.md +13 -0
  41. package/dist/ai/skills/sdd-critic/SKILL.md +12 -0
  42. package/dist/ai/skills/sdd-discover/SKILL.md +13 -0
  43. package/dist/ai/skills/sdd-execute/SKILL.md +205 -0
  44. package/dist/ai/skills/sdd-execute/scripts/README.md +84 -0
  45. package/dist/ai/skills/sdd-execute/scripts/check-blockers.sh +158 -0
  46. package/dist/ai/skills/sdd-execute/scripts/classify-scripts.js +111 -0
  47. package/dist/ai/skills/sdd-execute/scripts/classify-scripts.ts +133 -0
  48. package/dist/ai/skills/sdd-execute/scripts/extract-section.sh +219 -0
  49. package/dist/ai/skills/sdd-execute/scripts/lint-artifacts.sh +143 -0
  50. package/dist/ai/skills/sdd-execute/scripts/scan.sh +385 -0
  51. package/dist/ai/skills/sdd-execute/scripts/sdd +114 -0
  52. package/dist/ai/skills/sdd-execute/scripts/verify.sh +139 -0
  53. package/dist/ai/skills/sdd-execute-batch/SKILL.md +213 -0
  54. package/dist/ai/skills/sdd-fix/SKILL.md +13 -0
  55. package/dist/ai/skills/sdd-infra/SKILL.md +13 -0
  56. package/dist/ai/skills/sdd-module-decomposition/SKILL.md +13 -0
  57. package/dist/ai/skills/sdd-scaffold/SKILL.md +13 -0
  58. package/dist/ai/skills/sdd-setup/SKILL.md +13 -0
  59. package/dist/chunks/_commonjsHelpers-DaMA6jEr.js +8 -0
  60. package/dist/chunks/{devtools-B-7ugZhF.js → devtools-CPZ0IUNM.js} +1 -1
  61. package/dist/chunks/help-5qCaicff.js +6 -0
  62. package/dist/chunks/help-B2ETH0j6.js +6 -0
  63. package/dist/chunks/help-BCDURaCQ.js +6 -0
  64. package/dist/chunks/help-BvgYOSyQ.js +6 -0
  65. package/dist/chunks/help-CSPhOoI9.js +8 -0
  66. package/dist/chunks/help-CV1vpwrh.js +6 -0
  67. package/dist/chunks/help-CaBNA6Qr.js +12 -0
  68. package/dist/chunks/help-CjkR6SB9.js +6 -0
  69. package/dist/chunks/help-Coi1l5u9.js +8 -0
  70. package/dist/chunks/help-CsuLpZn-.js +8 -0
  71. package/dist/chunks/help-DL_QBMwy.js +8 -0
  72. package/dist/chunks/help-DqBZ2xcp.js +6 -0
  73. package/dist/chunks/help-_Y_TN4DZ.js +6 -0
  74. package/dist/chunks/{help.cmd-B_G7EWzF.js → help.cmd-BseRtoBF.js} +10 -2
  75. package/dist/chunks/{index-zBnnuvLA.js → index-BU1AXJGA.js} +12 -7
  76. package/dist/chunks/{index-CXuhZzS3.js → index-BXWDYM_9.js} +1 -1
  77. package/dist/chunks/index-BgKyH0dB.js +658 -0
  78. package/dist/chunks/{index-CaahtXiM.js → index-BghL1-Nk.js} +2 -2
  79. package/dist/chunks/{index-BeL1Zcbg.js → index-BlqEQEGy.js} +1 -1
  80. package/dist/chunks/{index-D1qsi0Uc.js → index-CDM74qdR.js} +3 -3
  81. package/dist/chunks/{index-D9ceRUyB.js → index-CGsIRefC.js} +2 -2
  82. package/dist/chunks/{index-CiEM-8nJ.js → index-CIvkaAQ3.js} +2 -2
  83. package/dist/chunks/{index-UbHoePfr.js → index-CkczJ__m.js} +10 -10
  84. package/dist/chunks/index-CtJKSOL0.js +21 -0
  85. package/dist/chunks/index-DEzym4Ae.js +369 -0
  86. package/dist/chunks/{index-CVR66voe.js → index-DoaxOhQ8.js} +401 -393
  87. package/dist/chunks/index-Du1xR-qO.js +752 -0
  88. package/dist/chunks/{index-DU6jD7SS.js → index-OtehG7uM.js} +1 -1
  89. package/dist/chunks/index-ZM_FonZj.js +156 -0
  90. package/dist/chunks/{index-g1LXlp77.js → index-k0nToACp.js} +2 -2
  91. package/dist/chunks/{run-review-command.logic-DpkRoEi8.js → run-review-command.logic-BeYQin-I.js} +9 -9
  92. package/dist/chunks/services-IgZptnTi.js +2732 -0
  93. package/dist/chunks/shared-DL_1LG0K.js +753 -0
  94. package/dist/cli/cmd/agent-mon/help.d.ts +4 -0
  95. package/dist/cli/cmd/agents-rules/agents-rules.cmd.d.ts +8 -0
  96. package/dist/cli/cmd/agents-rules/index.d.ts +1 -0
  97. package/dist/cli/cmd/alt-opinion/help.d.ts +4 -0
  98. package/dist/cli/cmd/cat/help.d.ts +4 -0
  99. package/dist/cli/cmd/commit/help.d.ts +4 -0
  100. package/dist/cli/cmd/lint/checks/anchor-class-body.check.d.ts +11 -0
  101. package/dist/cli/cmd/lint/checks/invariant-count.check.d.ts +11 -0
  102. package/dist/cli/cmd/lint/checks/utils/glob-match.d.ts +15 -0
  103. package/dist/cli/cmd/lint/checks/utils/strip-strings-comments.d.ts +7 -0
  104. package/dist/cli/cmd/lint/help.d.ts +4 -0
  105. package/dist/cli/cmd/lint/lint.cmd.d.ts +1 -1
  106. package/dist/cli/cmd/lint/lint.types.d.ts +6 -0
  107. package/dist/cli/cmd/orient/core/build-index.d.ts +8 -0
  108. package/dist/cli/cmd/orient/core/damerau-levenshtein.d.ts +26 -0
  109. package/dist/cli/cmd/orient/core/extract-header.d.ts +8 -0
  110. package/dist/cli/cmd/orient/core/hints.d.ts +9 -0
  111. package/dist/cli/cmd/orient/core/query-consumer.d.ts +10 -0
  112. package/dist/cli/cmd/orient/core/query-entity.d.ts +11 -0
  113. package/dist/cli/cmd/orient/core/query-graph.d.ts +18 -0
  114. package/dist/cli/cmd/orient/core/query-keyword.d.ts +10 -0
  115. package/dist/cli/cmd/orient/core/query-spec.d.ts +15 -0
  116. package/dist/cli/cmd/orient/core/query-task.d.ts +8 -0
  117. package/dist/cli/cmd/orient/core/scan-files.d.ts +8 -0
  118. package/dist/cli/cmd/orient/help.d.ts +5 -0
  119. package/dist/cli/cmd/orient/index.d.ts +1 -0
  120. package/dist/cli/cmd/orient/orient.cmd.d.ts +7 -0
  121. package/dist/cli/cmd/orient/orient.types.d.ts +136 -0
  122. package/dist/cli/cmd/orient/render/render-detail.d.ts +10 -0
  123. package/dist/cli/cmd/orient/render/render-file-list.d.ts +15 -0
  124. package/dist/cli/cmd/orient/render/render-graph.d.ts +13 -0
  125. package/dist/cli/cmd/orient/render/render-search.d.ts +8 -0
  126. package/dist/cli/cmd/orient/render/render-specs.d.ts +13 -0
  127. package/dist/cli/cmd/orient/render/render-tree.d.ts +13 -0
  128. package/dist/cli/cmd/remote-console/help.d.ts +4 -0
  129. package/dist/cli/cmd/resolve-conflicts/help.d.ts +4 -0
  130. package/dist/cli/cmd/review/help.d.ts +4 -0
  131. package/dist/cli/cmd/review-issues/help.d.ts +4 -0
  132. package/dist/cli/cmd/review-verify/help.d.ts +4 -0
  133. package/dist/cli/cmd/sync/help.d.ts +4 -0
  134. package/dist/cli/cmd/sync/sync-core.d.ts +3 -2
  135. package/dist/cli/cmd/sync/sync-formatter.d.ts +8 -9
  136. package/dist/cli/cmd/sync/sync.cmd.d.ts +2 -21
  137. package/dist/cli/cmd/sync-skills/index.d.ts +1 -0
  138. package/dist/cli/cmd/sync-skills/sync-skills-core.d.ts +50 -0
  139. package/dist/cli/cmd/sync-skills/sync-skills-formatter.d.ts +13 -0
  140. package/dist/cli/cmd/sync-skills/sync-skills.cmd.d.ts +14 -0
  141. package/dist/cli/cmd/sync-skills/sync-skills.types.d.ts +56 -0
  142. package/dist/cli/cmd/vcs-reply/help.d.ts +4 -0
  143. package/dist/gennady.js +73 -18
  144. package/dist/index.js +5 -5
  145. package/dist/services/dbc/linter/implementations/ts/dbc-ts-ast-adapter.d.ts +3 -2
  146. package/dist/shared/common/sync/path-normalizer.d.ts +22 -0
  147. package/dist/shared/common/sync/sync-core.shared.d.ts +14 -0
  148. package/dist/shared/common/sync/sync-deps.type.d.ts +66 -0
  149. package/dist/shared/common/sync/sync-formatter.shared.d.ts +19 -0
  150. package/package.json +6 -5
  151. package/dist/chunks/index-DJpVmyp2.js +0 -176
  152. package/dist/chunks/index-KxSZKmZn.js +0 -540
  153. package/dist/chunks/services-CaLOhuLV.js +0 -2889
  154. package/dist/chunks/shared-Bjy30TeM.js +0 -665
@@ -0,0 +1,385 @@
1
+ #!/usr/bin/env bash
2
+ # @file: Emit comprehensive SDD project snapshot in a single tool call.
3
+ # @consumers: sdd-check, sdd-continue, sdd-execute-batch (preflight + triage).
4
+ # @contract: AX_BASH_NO_SILENT_EMPTY. One-shot rich-context emitter so agents
5
+ # avoid running multiple ad-hoc find/grep commands.
6
+ #
7
+ # Output sections (stable, machine-readable):
8
+ # [HEADER] — scan metadata (root, timestamp, version)
9
+ # [TASKS] — TSV: path, status, last_round, blocker, placeholders, warnings
10
+ # [TRACKERS] — TSV: tracker_path, done, todo, in_progress, blocked, total
11
+ # [SPECS] — TSV: spec_path, scope, modules_listed, modules_missing
12
+ # [WARNINGS] — severity \t location \t message (collected during scan)
13
+ # [SUMMARY] — key=value totals
14
+ #
15
+ # Usage:
16
+ # scan.sh [project-root] # defaults to current directory
17
+ #
18
+ # Exit codes:
19
+ # 0 — snapshot emitted (may include WARNINGS — agent must inspect)
20
+ # 2 — structural failure (root not found, tasks/ missing, etc.); STDOUT has
21
+ # actionable diagnostic instructions
22
+ # 4 — bad invocation
23
+
24
+ set -uo pipefail
25
+
26
+ PROG="scan"
27
+ VERSION="1"
28
+
29
+ # ---------------------------------------------------------------------------
30
+ # Argument parsing
31
+ # ---------------------------------------------------------------------------
32
+
33
+ if [[ $# -gt 1 ]]; then
34
+ cat <<EOF
35
+ [$PROG] BAD_INVOCATION
36
+ expected: $PROG [project-root]
37
+ got: $PROG $*
38
+
39
+ Required action:
40
+ Pass zero or one argument — the project root. Defaults to current directory.
41
+ EOF
42
+ exit 4
43
+ fi
44
+
45
+ ROOT="${1:-.}"
46
+
47
+ if [[ ! -d "$ROOT" ]]; then
48
+ cat <<EOF
49
+ [$PROG] BAD_ROOT
50
+ path: $ROOT
51
+ reason: not a directory or does not exist
52
+
53
+ Required action:
54
+ - Verify you passed a project root that exists.
55
+ - From within a project, call: $PROG (no arg) or $PROG .
56
+ EOF
57
+ exit 2
58
+ fi
59
+
60
+ # Resolve to absolute path for stable output
61
+ ROOT_ABS="$(cd "$ROOT" && pwd)"
62
+
63
+ # ---------------------------------------------------------------------------
64
+ # Sanity: does this look like an SDD project?
65
+ # ---------------------------------------------------------------------------
66
+
67
+ has_tasks=0
68
+ has_specs=0
69
+ [[ -d "$ROOT_ABS/tasks" ]] && has_tasks=1
70
+ [[ -d "$ROOT_ABS/specs" ]] && has_specs=1
71
+
72
+ if [[ $has_tasks -eq 0 && $has_specs -eq 0 ]]; then
73
+ cat <<EOF
74
+ [$PROG] NOT_AN_SDD_PROJECT
75
+ root: $ROOT_ABS
76
+ reason: neither tasks/ nor specs/ directory found
77
+
78
+ Required action:
79
+ - If this is meant to be an SDD project, run /sdd-scaffold to bootstrap it.
80
+ - If you ran from the wrong directory, pass the project root explicitly:
81
+ $PROG /path/to/project
82
+ - If your project uses non-standard layout (tasks elsewhere), this tool
83
+ does not support that — convention is tasks/ + specs/ under root.
84
+ EOF
85
+ exit 2
86
+ fi
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # Buffers (using temp files for portability with macOS bash 3.2)
90
+ # ---------------------------------------------------------------------------
91
+
92
+ TMPDIR_BUF="$(mktemp -d -t sdd-scan.XXXXXX)" || { echo "[$PROG] MKTEMP_FAILED"; exit 2; }
93
+ trap 'rm -rf "$TMPDIR_BUF"' EXIT
94
+
95
+ WARN_FILE="$TMPDIR_BUF/warnings"
96
+ : > "$WARN_FILE"
97
+
98
+ emit_warn() {
99
+ # severity \t location \t message
100
+ printf '%s\t%s\t%s\n' "$1" "$2" "$3" >> "$WARN_FILE"
101
+ }
102
+
103
+ # ---------------------------------------------------------------------------
104
+ # HEADER
105
+ # ---------------------------------------------------------------------------
106
+
107
+ printf '# sdd scan v%s\n' "$VERSION"
108
+ printf 'ROOT=%s\n' "$ROOT_ABS"
109
+ printf 'SCANNED_AT=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
110
+ printf 'HAS_TASKS=%d\n' "$has_tasks"
111
+ printf 'HAS_SPECS=%d\n' "$has_specs"
112
+
113
+ # ---------------------------------------------------------------------------
114
+ # Helpers
115
+ # ---------------------------------------------------------------------------
116
+
117
+ # Extract Meta.Status from a ticket. Echoes one of:
118
+ # DONE | TODO | IN_PROGRESS | BLOCKED | UNKNOWN
119
+ extract_status() {
120
+ local f="$1"
121
+ # Look in first 60 lines for "Status:" with [flag] STATE
122
+ local line
123
+ line=$(head -60 "$f" 2>/dev/null | grep -m1 -E '^\s*-?\s*\*?\*?Status:\*?\*?\s*\[.\]' || true)
124
+ if [[ -z "$line" ]]; then
125
+ echo "UNKNOWN"
126
+ return
127
+ fi
128
+ # Extract flag char between [ and ]
129
+ local flag
130
+ flag=$(echo "$line" | sed -nE 's/.*\[(.)\].*/\1/p')
131
+ case "$flag" in
132
+ x|X) echo "DONE" ;;
133
+ ' ') echo "TODO" ;;
134
+ '~') echo "IN_PROGRESS" ;;
135
+ '!') echo "BLOCKED" ;;
136
+ *) echo "UNKNOWN" ;;
137
+ esac
138
+ }
139
+
140
+ # Count Round headers in Execution Log. Echoes count (0 if none).
141
+ count_rounds() {
142
+ local f="$1"
143
+ grep -E '^### Round [0-9]+' "$f" 2>/dev/null | wc -l | tr -d ' '
144
+ }
145
+
146
+ # Detect blocker state. Echoes:
147
+ # none — no 🛑 BLOCKED entries
148
+ # active — at least one 🛑 BLOCKED with no matching later ✅ RESOLVED
149
+ # resolved — all 🛑 entries followed by ✅ RESOLVED (last marker is ✅)
150
+ blocker_state() {
151
+ local f="$1"
152
+ local log_start
153
+ log_start=$(grep -n "^## 7\. Execution Log" "$f" 2>/dev/null | head -1 | cut -d: -f1)
154
+ [[ -z "$log_start" ]] && { echo "none"; return; }
155
+
156
+ local blockers resolved last_b last_r
157
+ blockers=$(grep -nE '🛑.*BLOCKED|BLOCKED.*🛑' "$f" 2>/dev/null \
158
+ | awk -F: -v s="$log_start" '$1>=s' | wc -l | tr -d ' ')
159
+ resolved=$(grep -nE '✅.*RESOLVED|RESOLVED.*✅' "$f" 2>/dev/null \
160
+ | awk -F: -v s="$log_start" '$1>=s' | wc -l | tr -d ' ')
161
+
162
+ [[ "$blockers" -eq 0 ]] && { echo "none"; return; }
163
+
164
+ last_b=$(grep -nE '🛑.*BLOCKED|BLOCKED.*🛑' "$f" 2>/dev/null \
165
+ | awk -F: -v s="$log_start" '$1>=s {print $1}' | tail -1)
166
+ last_r=$(grep -nE '✅.*RESOLVED|RESOLVED.*✅' "$f" 2>/dev/null \
167
+ | awk -F: -v s="$log_start" '$1>=s {print $1}' | tail -1)
168
+ last_b="${last_b:-0}"
169
+ last_r="${last_r:-0}"
170
+
171
+ if [[ "$resolved" -ge "$blockers" && "$last_r" -gt "$last_b" ]]; then
172
+ echo "resolved"
173
+ else
174
+ echo "active"
175
+ fi
176
+ }
177
+
178
+ # Count placeholder markers (unfilled scaffold values) in a ticket.
179
+ count_placeholders() {
180
+ local f="$1"
181
+ grep -E '<YYYY-MM-DD>|<TBD>|`\[<ts>\]`|<command>|<scope>|<scenario>|<test-file>::<case>|<list or' "$f" 2>/dev/null | wc -l | tr -d ' '
182
+ }
183
+
184
+ # Per-ticket warnings (string, comma-separated, "-" if none)
185
+ ticket_warnings() {
186
+ local f="$1"
187
+ local w=()
188
+ # Required structural sections
189
+ grep -q '^## 1\. Meta' "$f" 2>/dev/null || w+=("no-meta-section")
190
+ grep -q '^## 7\. Execution Log' "$f" 2>/dev/null || w+=("no-execlog-section")
191
+ # Anchor closure sanity
192
+ local opens closes
193
+ opens=$(grep -E '^[[:space:]]*<!--SECTION:[A-Z][A-Z0-9_]*-->' "$f" 2>/dev/null | wc -l | tr -d ' ')
194
+ closes=$(grep -E '^[[:space:]]*<!--/SECTION:[A-Z][A-Z0-9_]*-->' "$f" 2>/dev/null | wc -l | tr -d ' ')
195
+ if [[ "$opens" -ne "$closes" ]]; then
196
+ w+=("anchors-mismatch:$opens-open/$closes-close")
197
+ fi
198
+ if [[ ${#w[@]} -eq 0 ]]; then
199
+ echo "-"
200
+ else
201
+ (IFS=,; echo "${w[*]}")
202
+ fi
203
+ }
204
+
205
+ # ---------------------------------------------------------------------------
206
+ # [TASKS]
207
+ # ---------------------------------------------------------------------------
208
+
209
+ printf '\n[TASKS]\n'
210
+ printf '# path\tstatus\tlast_round\tblocker\tplaceholders\twarnings\n'
211
+
212
+ if [[ $has_tasks -eq 0 ]]; then
213
+ printf '# (tasks/ directory not present)\n'
214
+ emit_warn "WARN" "tasks/" "directory missing; no tickets to scan"
215
+ else
216
+ # -L follows symlinks (project convention; see AGENTS.md in symlinked projects)
217
+ TASK_FILES=$(find -L "$ROOT_ABS/tasks" -name '*.task-*.md' -type f 2>/dev/null | sort || true)
218
+ if [[ -z "$TASK_FILES" ]]; then
219
+ printf '# (no *.task-*.md files found under tasks/)\n'
220
+ emit_warn "INFO" "tasks/" "no ticket files matching '*.task-*.md' — project may be pre-scaffold"
221
+ else
222
+ while IFS= read -r f; do
223
+ [[ -z "$f" ]] && continue
224
+ rel="${f#$ROOT_ABS/}"
225
+ status=$(extract_status "$f")
226
+ rounds=$(count_rounds "$f")
227
+ blocker=$(blocker_state "$f")
228
+ placeholders=$(count_placeholders "$f")
229
+ warns=$(ticket_warnings "$f")
230
+
231
+ # Promote suspicious combinations into the WARNINGS section
232
+ if [[ "$status" == "DONE" && "$blocker" == "active" ]]; then
233
+ emit_warn "ERROR" "$rel" "status=DONE but unresolved BLOCKER in Execution Log"
234
+ fi
235
+ if [[ "$status" == "DONE" && "$placeholders" -gt 0 ]]; then
236
+ emit_warn "WARN" "$rel" "status=DONE but $placeholders scaffold placeholder(s) remain"
237
+ fi
238
+ if [[ "$status" == "UNKNOWN" ]]; then
239
+ emit_warn "WARN" "$rel" "Meta.Status not parseable (expected '[x] DONE' / '[ ] TODO' / '[~] IN_PROGRESS' / '[!] BLOCKED')"
240
+ fi
241
+ if [[ "$warns" != "-" ]]; then
242
+ emit_warn "WARN" "$rel" "ticket structure: $warns"
243
+ fi
244
+
245
+ printf '%s\t%s\t%s\t%s\t%s\t%s\n' \
246
+ "$rel" "$status" "$rounds" "$blocker" "$placeholders" "$warns"
247
+ done <<< "$TASK_FILES"
248
+ fi
249
+ fi
250
+
251
+ # ---------------------------------------------------------------------------
252
+ # [TRACKERS]
253
+ # ---------------------------------------------------------------------------
254
+
255
+ printf '\n[TRACKERS]\n'
256
+ printf '# tracker_path\tdone\ttodo\tin_progress\tblocked\ttotal\n'
257
+
258
+ if [[ $has_tasks -eq 0 ]]; then
259
+ printf '# (tasks/ missing)\n'
260
+ else
261
+ TRACKERS=$(find -L "$ROOT_ABS/tasks" -name 'README.md' -type f 2>/dev/null | sort || true)
262
+ if [[ -z "$TRACKERS" ]]; then
263
+ printf '# (no README.md trackers found under tasks/)\n'
264
+ emit_warn "WARN" "tasks/" "no tracker README.md files — convention is tasks/README.md per scope"
265
+ else
266
+ while IFS= read -r tr; do
267
+ [[ -z "$tr" ]] && continue
268
+ rel="${tr#$ROOT_ABS/}"
269
+ # Count tracker rows matching TSK-NN pattern with status cell
270
+ # Match both bare and link form: `| TSK-NN |` or `| [TSK-NN](...) |`
271
+ done_c=$(grep -E '\|[[:space:]]*\[?TSK-[0-9]+.*`?\[x\]`?[[:space:]]+DONE' "$tr" 2>/dev/null | wc -l | tr -d ' ')
272
+ todo_c=$(grep -E '\|[[:space:]]*\[?TSK-[0-9]+.*`?\[ \]`?[[:space:]]+TODO' "$tr" 2>/dev/null | wc -l | tr -d ' ')
273
+ inpg_c=$(grep -E '\|[[:space:]]*\[?TSK-[0-9]+.*`?\[~\]`?[[:space:]]+IN_PROGRESS' "$tr" 2>/dev/null | wc -l | tr -d ' ')
274
+ blkd_c=$(grep -E '\|[[:space:]]*\[?TSK-[0-9]+.*`?\[!\]`?[[:space:]]+BLOCKED' "$tr" 2>/dev/null | wc -l | tr -d ' ')
275
+ total=$((done_c + todo_c + inpg_c + blkd_c))
276
+ if [[ "$total" -eq 0 ]]; then
277
+ # Tracker README might be the top-level overview without ticket rows — fine, but mark with hint
278
+ emit_warn "INFO" "$rel" "no TSK rows detected (top-level overview tracker?)"
279
+ fi
280
+ printf '%s\t%d\t%d\t%d\t%d\t%d\n' "$rel" "$done_c" "$todo_c" "$inpg_c" "$blkd_c" "$total"
281
+ done <<< "$TRACKERS"
282
+ fi
283
+ fi
284
+
285
+ # ---------------------------------------------------------------------------
286
+ # [SPECS]
287
+ # ---------------------------------------------------------------------------
288
+
289
+ printf '\n[SPECS]\n'
290
+ printf '# spec_path\tscope\tmodules_listed\tmodules_missing\n'
291
+
292
+ if [[ $has_specs -eq 0 ]]; then
293
+ printf '# (specs/ missing)\n'
294
+ else
295
+ SPECS=$(find -L "$ROOT_ABS/specs" -name '*.spec.md' -type f 2>/dev/null | sort || true)
296
+ if [[ -z "$SPECS" ]]; then
297
+ printf '# (no *.spec.md files under specs/)\n'
298
+ emit_warn "INFO" "specs/" "no spec files — run /sdd-discover to bootstrap"
299
+ else
300
+ while IFS= read -r sp; do
301
+ [[ -z "$sp" ]] && continue
302
+ rel="${sp#$ROOT_ABS/}"
303
+ # Derive scope from path: specs/<scope>/<scope>.spec.md or specs/<scope>/<module>/<module>.spec.md
304
+ scope=$(echo "$rel" | awk -F/ '{print $2}')
305
+ # Count module references: paths like (../<module>/<module>.spec.md) or relative links to *.spec.md
306
+ modules=$(grep -oE '[a-z][a-z0-9-]*\.spec\.md' "$sp" 2>/dev/null | sort -u | wc -l | tr -d ' ')
307
+ # Count missing referenced specs (link target does not exist relative to this spec)
308
+ missing=0
309
+ base_dir=$(dirname "$sp")
310
+ while IFS= read -r link; do
311
+ [[ -z "$link" ]] && continue
312
+ # Resolve relative
313
+ if [[ -e "$base_dir/$link" ]]; then
314
+ :
315
+ else
316
+ missing=$((missing + 1))
317
+ fi
318
+ # Only consider real markdown links: ](path.spec.md...) — preceded by ']'.
319
+ # Skip inline-code paths like (`path.spec.md`) — those are descriptive references, not links.
320
+ done < <(grep -oE '\]\([^)`]+\.spec\.md[^)]*\)' "$sp" 2>/dev/null \
321
+ | sed -E 's/^\]\(//; s/[#)].*$//' | sort -u)
322
+ if [[ "$missing" -gt 0 ]]; then
323
+ emit_warn "ERROR" "$rel" "$missing referenced *.spec.md link(s) do not resolve on disk"
324
+ fi
325
+ printf '%s\t%s\t%d\t%d\n' "$rel" "${scope:-unknown}" "$modules" "$missing"
326
+ done <<< "$SPECS"
327
+ fi
328
+ fi
329
+
330
+ # ---------------------------------------------------------------------------
331
+ # [WARNINGS]
332
+ # ---------------------------------------------------------------------------
333
+
334
+ printf '\n[WARNINGS]\n'
335
+ printf '# severity\tlocation\tmessage\n'
336
+ if [[ -s "$WARN_FILE" ]]; then
337
+ cat "$WARN_FILE"
338
+ else
339
+ printf '# (none)\n'
340
+ fi
341
+
342
+ # ---------------------------------------------------------------------------
343
+ # [SUMMARY]
344
+ # ---------------------------------------------------------------------------
345
+
346
+ # Aggregate from emitted [TASKS] block by re-walking files (cheap, ~ms).
347
+ sum_done=0; sum_todo=0; sum_inpg=0; sum_blkd=0; sum_unk=0
348
+ sum_active_blockers=0; sum_total_tasks=0
349
+ if [[ $has_tasks -eq 1 && -n "${TASK_FILES:-}" ]]; then
350
+ while IFS= read -r f; do
351
+ [[ -z "$f" ]] && continue
352
+ sum_total_tasks=$((sum_total_tasks + 1))
353
+ s=$(extract_status "$f")
354
+ case "$s" in
355
+ DONE) sum_done=$((sum_done+1)) ;;
356
+ TODO) sum_todo=$((sum_todo+1)) ;;
357
+ IN_PROGRESS) sum_inpg=$((sum_inpg+1)) ;;
358
+ BLOCKED) sum_blkd=$((sum_blkd+1)) ;;
359
+ *) sum_unk=$((sum_unk+1)) ;;
360
+ esac
361
+ b=$(blocker_state "$f")
362
+ [[ "$b" == "active" ]] && sum_active_blockers=$((sum_active_blockers+1))
363
+ done <<< "$TASK_FILES"
364
+ fi
365
+
366
+ warn_count=$(grep -E '^WARN' "$WARN_FILE" 2>/dev/null | wc -l | tr -d ' ')
367
+ err_count=$(grep -E '^ERROR' "$WARN_FILE" 2>/dev/null | wc -l | tr -d ' ')
368
+ info_count=$(grep -E '^INFO' "$WARN_FILE" 2>/dev/null | wc -l | tr -d ' ')
369
+
370
+ printf '\n[SUMMARY]\n'
371
+ printf 'tasks_total=%d\n' "$sum_total_tasks"
372
+ printf 'tasks_done=%d\n' "$sum_done"
373
+ printf 'tasks_todo=%d\n' "$sum_todo"
374
+ printf 'tasks_in_progress=%d\n' "$sum_inpg"
375
+ printf 'tasks_blocked=%d\n' "$sum_blkd"
376
+ printf 'tasks_unknown_status=%d\n' "$sum_unk"
377
+ printf 'active_blockers=%d\n' "$sum_active_blockers"
378
+ printf 'warnings=%d\n' "$warn_count"
379
+ printf 'errors=%d\n' "$err_count"
380
+ printf 'info=%d\n' "$info_count"
381
+
382
+ # Exit 0 always when snapshot emitted. WARNINGS/ERRORS are *data* the agent must
383
+ # interpret — they are not invocation failures. Use exit 2 only when we cannot
384
+ # emit a snapshot (handled above).
385
+ exit 0
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env bash
2
+ # @file: SDD command dispatcher — single entry point for all SDD helper scripts.
3
+ # @consumers: phase agents, audit agents, orchestrator skills
4
+ # @contract: AX_BASH_NO_SILENT_EMPTY. Stable surface: `sdd <subcommand> <args>`.
5
+ #
6
+ # Subcommands:
7
+ # sdd extract <file> <NAME> — extract <!--SECTION:NAME--> ... <!--/SECTION:NAME--> block
8
+ # sdd lint <file>... — run gennady DBC AST lint, parse output reliably
9
+ # sdd verify <file>... — comprehensive gate: typecheck + gennady lint + forbidden_grep
10
+ # sdd check-blockers <ticket-file> — scan Execution Log for unresolved BLOCKER entries
11
+ # sdd scan [project-root] — emit comprehensive project snapshot (single rich call)
12
+ # sdd help — print this help
13
+ #
14
+ # Why this wrapper exists:
15
+ # - Single permission rule pattern: `Bash(scripts/sdd/sdd *)` covers ALL SDD operations.
16
+ # - Stable API across project lifetimes — script names underneath can change without breaking agent patterns.
17
+ # - Consistent error contract (AX_BASH_NO_SILENT_EMPTY) across all subcommands.
18
+ #
19
+ # Exit codes (forwarded from subcommand):
20
+ # 0 — success
21
+ # 1-N — subcommand-specific failure (see each subcommand's documented codes)
22
+ # 127 — unknown subcommand
23
+
24
+ set -uo pipefail
25
+
26
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ PROG="sdd"
28
+ SUBCMD="${1:-help}"
29
+ shift 2>/dev/null || true
30
+
31
+ case "$SUBCMD" in
32
+ extract)
33
+ exec "$SCRIPT_DIR/extract-section.sh" "$@"
34
+ ;;
35
+ lint)
36
+ exec "$SCRIPT_DIR/lint-artifacts.sh" "$@"
37
+ ;;
38
+ verify)
39
+ exec "$SCRIPT_DIR/verify.sh" "$@"
40
+ ;;
41
+ check-blockers)
42
+ exec "$SCRIPT_DIR/check-blockers.sh" "$@"
43
+ ;;
44
+ scan)
45
+ exec "$SCRIPT_DIR/scan.sh" "$@"
46
+ ;;
47
+ help|--help|-h)
48
+ cat <<'EOF'
49
+ sdd — SDD command dispatcher
50
+
51
+ USAGE
52
+ sdd <subcommand> <args>
53
+
54
+ SUBCOMMANDS
55
+ extract <file> <NAME> Extract anchored section from a markdown file.
56
+ NAME format: ^[A-Z][A-Z0-9_]*$
57
+ Examples: META, PHASES_OVERVIEW, PHASE_P1, BDD, VERIFICATION,
58
+ TEST_COVERAGE, EXECUTION_LOG
59
+
60
+ lint <file>... Run gennady DBC AST lint on listed TypeScript files.
61
+ Output parsed for "[linting → clean]" / "[linting → failed]".
62
+ Exit code reliable (unlike raw gennady).
63
+
64
+ verify <file>... Comprehensive verification gate — runs ALL of:
65
+ (1) npm run typecheck
66
+ (2) gennady DBC lint on listed files
67
+ (3) npm run lint
68
+ (4) npm run test
69
+ (5) npm run format check
70
+ RUN-ALL: every gate executes; failures accumulate.
71
+ SUPPRESS-ON-SUCCESS: passing gates produce zero output.
72
+ On all-pass: single line. On failure: only failed gates
73
+ with captured output shown.
74
+
75
+ scan [root] Emit one rich snapshot of an SDD project:
76
+ [HEADER] [TASKS] [TRACKERS] [SPECS] [WARNINGS] [SUMMARY]
77
+ Designed so triage/check skills make ONE call instead of
78
+ many ad-hoc find/grep commands. Suspicious states
79
+ (DONE+placeholders, DONE+active-blocker, anchor mismatch,
80
+ unparseable Meta.Status, broken spec links) are surfaced
81
+ in [WARNINGS]. Defaults to current directory.
82
+
83
+ check-blockers <ticket> Scan ticket Execution Log for unresolved BLOCKER entries.
84
+ A BLOCKER is considered RESOLVED if a later Round contains
85
+ a "✅ RESOLVED" entry referencing it. Returns list of
86
+ unresolved blockers, or PASS if all resolved / none.
87
+
88
+ help Print this help.
89
+
90
+ PERMISSION HINT
91
+ Single project permission rule covers all subcommands:
92
+ "Bash(scripts/sdd/sdd *)"
93
+
94
+ CONTRACT
95
+ All subcommands honor AX_BASH_NO_SILENT_EMPTY — empty output is never produced;
96
+ instead actionable diagnostic instructions are emitted with non-zero exit.
97
+ EOF
98
+ exit 0
99
+ ;;
100
+ "")
101
+ echo "[$PROG] BAD_INVOCATION: no subcommand"
102
+ echo " Run: sdd help"
103
+ exit 127
104
+ ;;
105
+ *)
106
+ cat <<EOF
107
+ [$PROG] UNKNOWN_SUBCOMMAND: $SUBCMD
108
+
109
+ Available subcommands: extract, lint, verify, check-blockers, scan, help.
110
+ Run: sdd help
111
+ EOF
112
+ exit 127
113
+ ;;
114
+ esac
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env bash
2
+ # @file: Smart verification gate — auto-discovers npm scripts via heuristic, runs them.
3
+ # @consumers: phase agents (STEP_5_VERIFY); orchestrators; CI hooks
4
+ # @contract: AX_BASH_NO_SILENT_EMPTY. All discovered gates MUST pass for exit 0.
5
+ # RUN-ALL: every gate executes regardless of previous failures.
6
+ # SUPPRESS-ON-SUCCESS: passing gates produce zero output; only failures are shown.
7
+ # On all-pass: single line "[verify] ALL_GATES_PASS (N/N)". Exit 0.
8
+ # On any-fail: each failed gate dumps its command + exit code + captured output. Exit 1.
9
+ #
10
+ # Usage:
11
+ # verify.sh <file1> [<file2> ...]
12
+ #
13
+ # Exit codes:
14
+ # 0 — all gates PASS
15
+ # 1 — one or more gates failed
16
+ # 4 — bad invocation
17
+ # 5 — environment failure
18
+
19
+ set -uo pipefail
20
+
21
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
+ PROG="verify"
23
+
24
+ # -----------------------------------------------------------
25
+ # Preflight
26
+ # -----------------------------------------------------------
27
+
28
+ if [[ $# -lt 1 ]]; then
29
+ cat <<EOF
30
+ [$PROG] BAD_INVOCATION
31
+ expected: $PROG <file1> [<file2> ...]
32
+ got: $PROG (no args)
33
+ EOF
34
+ exit 4
35
+ fi
36
+
37
+ if ! command -v npm &>/dev/null; then
38
+ echo "[$PROG] ENV_MISSING: npm not in PATH"
39
+ exit 5
40
+ fi
41
+
42
+ if ! command -v node &>/dev/null; then
43
+ echo "[$PROG] ENV_MISSING: node not in PATH"
44
+ exit 5
45
+ fi
46
+
47
+ for file in "$@"; do
48
+ if [[ ! -f "$file" ]]; then
49
+ echo "[$PROG] FILE_NOT_FOUND: $file"
50
+ exit 4
51
+ fi
52
+ done
53
+
54
+ # -----------------------------------------------------------
55
+ # Step 1: Discover scripts via heuristic (silent)
56
+ # -----------------------------------------------------------
57
+
58
+ CLASSIFIER="$SCRIPT_DIR/classify-scripts.js"
59
+
60
+ if [[ ! -f "$CLASSIFIER" ]]; then
61
+ echo "[$PROG] ENV_MISSING: classify-scripts.js not found at $CLASSIFIER"
62
+ exit 5
63
+ fi
64
+
65
+ classification=$(node "$CLASSIFIER" 2>/dev/null) || {
66
+ echo "[$PROG] ENV_FAIL: classify-scripts.js failed"
67
+ exit 5
68
+ }
69
+
70
+ # Extract selected scripts: "cls:name" lines
71
+ discovered=$(echo "$classification" | python3 -c "
72
+ import json,sys
73
+ d=json.load(sys.stdin)
74
+ sel=d.get('selected',{})
75
+ for cls in ['typecheck','gennady','lint','test','format']:
76
+ v=sel.get(cls,'')
77
+ if v:
78
+ print(f'{cls}:{v}')
79
+ " 2>/dev/null)
80
+
81
+ if [[ -z "$discovered" ]]; then
82
+ echo "[$PROG] NO_SCRIPTS_DISCOVERED"
83
+ exit 0
84
+ fi
85
+
86
+ # -----------------------------------------------------------
87
+ # Step 2: Run each gate — accumulate failures, suppress success
88
+ # -----------------------------------------------------------
89
+
90
+ total=0
91
+ passed=0
92
+ failures=""
93
+ FILES=("$@") # save original file args for file-specific commands
94
+
95
+ run_cmd() {
96
+ local label="$1"
97
+ local display_name="$2"
98
+ shift 2
99
+ local cmd=("$@")
100
+
101
+ total=$((total + 1))
102
+
103
+ local output exit_code
104
+ set +e
105
+ output=$("${cmd[@]}" 2>&1)
106
+ exit_code=$?
107
+ set -e
108
+
109
+ if [[ $exit_code -eq 0 ]]; then
110
+ passed=$((passed + 1))
111
+ return 0
112
+ else
113
+ failures+=$(printf '\n[%s] ❌ FAIL gate: %s\n command: %s\n exit: %s\n\n--- captured output ---\n%s\n--- end ---\n' "$PROG" "$label" "${cmd[*]}" "$exit_code" "$output")
114
+ return 1
115
+ fi
116
+ }
117
+
118
+ # Run ALL gates regardless of failures
119
+ while IFS=: read cls name; do
120
+ case "$cls" in
121
+ typecheck) run_cmd "typecheck" "npm run $name" npm run "$name" || true ;;
122
+ gennady) run_cmd "gennady DBC lint" "gennady lint ${#FILES[@]} files" npx tsx ~/Developer/gennady/cli/gennady.ts lint "${FILES[@]}" || true ;;
123
+ lint) run_cmd "lint" "npm run $name" npm run "$name" || true ;;
124
+ test) run_cmd "test" "npm run $name" npm run "$name" || true ;;
125
+ format) run_cmd "format check" "npm run $name" npm run "$name" || true ;;
126
+ esac
127
+ done <<< "$discovered"
128
+
129
+ # -----------------------------------------------------------
130
+ # Report: suppressed on success, full on failure
131
+ # -----------------------------------------------------------
132
+
133
+ if [[ -n "$failures" ]]; then
134
+ echo "$failures"
135
+ exit 1
136
+ fi
137
+
138
+ echo "[$PROG] ALL_GATES_PASS ($passed/$total)"
139
+ exit 0