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,143 @@
1
+ #!/usr/bin/env bash
2
+ # @file: Run gennady lint on phase artifacts; parse output for success/failure (gennady exit-code unreliable).
3
+ # @consumers: phase agents (STEP_5_VERIFY); audit subagents (STEP_1 pre-pass cross-check)
4
+ # @contract: AX_BASH_NO_SILENT_EMPTY — never produces empty stdout. On miss → actionable instruction.
5
+ #
6
+ # Why this wrapper exists:
7
+ # - gennady CLI lives outside the project (canonical: ~/Developer/gennady/cli/gennady.ts).
8
+ # - gennady requires `node --experimental-strip-types` (Node 22+) — the bare `node` invocation is non-obvious.
9
+ # - gennady returns exit code 0 even when lint reports errors (the failure signal is the literal token
10
+ # "[linting → failed]" in stdout). We must parse output, not trust exit code.
11
+ # - Success signal is the literal token "[linting → clean] no errors".
12
+ #
13
+ # Usage:
14
+ # lint-artifacts.sh <file1> [<file2> ...]
15
+ #
16
+ # Exit codes:
17
+ # 0 — all files clean (gennady output contains "[linting → clean]" and NOT "[linting → failed]")
18
+ # 1 — gennady CLI not found / unable to invoke
19
+ # 2 — lint reported errors (one or more violations); full output emitted
20
+ # 3 — ambiguous output (neither "clean" nor "failed" token present); investigate gennady CLI changes
21
+ # 4 — bad invocation (no args)
22
+
23
+ set -uo pipefail
24
+
25
+ PROG="lint-artifacts"
26
+ GENNADY_CLI=~/Developer/gennady/cli/gennady.ts
27
+
28
+ if [[ $# -lt 1 ]]; then
29
+ cat <<EOF
30
+ [$PROG] BAD_INVOCATION
31
+ expected: $PROG <file1> [<file2> ...]
32
+ got: $PROG (no args)
33
+
34
+ Required action (ORCHESTRATOR / phase agent):
35
+ Pass at least one TypeScript file path to lint.
36
+ Typically: phase's Target Files list.
37
+ EOF
38
+ exit 4
39
+ fi
40
+
41
+ if [[ ! -f "$GENNADY_CLI" ]]; then
42
+ cat <<EOF
43
+ [$PROG] GENNADY_CLI_NOT_FOUND
44
+ expected at: $GENNADY_CLI
45
+
46
+ Diagnosis: the gennady AST DbC linter is unreachable from this environment.
47
+
48
+ Required action (ORCHESTRATOR):
49
+ 1. Verify the gennady project is checked out at ~/Developer/gennady.
50
+ 2. If gennady moved → update GENNADY_CLI variable in this script.
51
+ 3. If on a CI/sandbox without gennady → this is a HARD blocker; phase cannot verify.
52
+ Report to operator: cannot complete phase without DBC contract verification.
53
+
54
+ DO NOT proceed to EMIT_HANDOFF without gennady lint passing.
55
+ EOF
56
+ exit 1
57
+ fi
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # Run gennady lint and capture output
61
+ # ---------------------------------------------------------------------------
62
+
63
+ # Use a temp file to capture full output; we need both grep on it and to emit on failure.
64
+ tmp_out=$(mktemp -t lint-artifacts.XXXXXX)
65
+ trap 'rm -f "$tmp_out"' EXIT
66
+
67
+ # Run gennady. We intentionally ignore its exit code (unreliable per contract above).
68
+ npx tsx "$GENNADY_CLI" lint "$@" > "$tmp_out" 2>&1 || true
69
+
70
+ has_clean=$(grep -c '\[linting → clean\]' "$tmp_out" 2>/dev/null || echo 0)
71
+ has_failed=$(grep -c '\[linting → failed\]' "$tmp_out" 2>/dev/null || echo 0)
72
+
73
+ # Convert to single-line numbers (grep -c with || echo 0 may produce odd output on some shells)
74
+ has_clean=${has_clean//[^0-9]/}
75
+ has_failed=${has_failed//[^0-9]/}
76
+ has_clean=${has_clean:-0}
77
+ has_failed=${has_failed:-0}
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # Branch on tokens
81
+ # ---------------------------------------------------------------------------
82
+
83
+ if [[ "$has_failed" -gt 0 ]]; then
84
+ # Extract just the error lines (those with file:line: error: format) + final summary
85
+ cat <<EOF
86
+ [$PROG] LINT_FAILED
87
+ files lint'd: $#
88
+ gennady output token: [linting → failed]
89
+
90
+ Full lint output (gennady DBC AST findings):
91
+ EOF
92
+ # Print error lines and the failure summary line
93
+ grep -E '\[linting → failed\]|: error: |^---|^References:|^ ' "$tmp_out" | head -200
94
+ echo
95
+ cat <<EOF
96
+ ---
97
+
98
+ Required action (PHASE AGENT, before EMIT_HANDOFF):
99
+ 1. Read each error: format is <file>:<line>:<col>: error: <ERR_CODE>: <message>
100
+ 2. Common categories:
101
+ - ERR_DBC_LINT_PARAM_MISSING — add @param <name> <coherent-role-description>
102
+ - ERR_DBC_LINT_RETURNS_MISSING — add @returns <business-purpose-of-value>
103
+ - ERR_DBC_ORDER — reorder JSDoc tags per AX_BASE_CONTRACT_SHAPE:
104
+ @purpose → @consumers → @implements → @invariant → @pre →
105
+ @param → @throws → @returns → @post → @sideEffect
106
+ 3. Fix all violations in the target files.
107
+ 4. Re-run this script. Repeat until clean.
108
+ 5. Do NOT EMIT_HANDOFF with lint failures present — that is fabricated DONE.
109
+
110
+ References:
111
+ /Users/k.lebedev/Developer/vkt/ai/directives/coding/typescript-rules.xml
112
+ — AX_TAG_USAGE_MATRIX, AX_BASE_CONTRACT_SHAPE, AX_FLAT_JSDOC_FOR_PROPERTIES
113
+ EOF
114
+ exit 2
115
+ fi
116
+
117
+ if [[ "$has_clean" -gt 0 ]]; then
118
+ # Clean. Emit concise PASS line to keep parent context light.
119
+ file_count=$#
120
+ echo "[$PROG] LINT_PASS"
121
+ echo " files lint'd: $file_count"
122
+ echo " gennady output token: [linting → clean]"
123
+ exit 0
124
+ fi
125
+
126
+ # Neither token found — gennady output format may have changed.
127
+ cat <<EOF
128
+ [$PROG] AMBIGUOUS_OUTPUT
129
+ files lint'd: $#
130
+ gennady output token: (neither "linting → clean" nor "linting → failed" found)
131
+
132
+ Diagnosis: gennady CLI output format may have changed (or there is an unhandled edge case).
133
+
134
+ Captured output:
135
+ $(cat "$tmp_out" | head -50)
136
+
137
+ Required action (ORCHESTRATOR):
138
+ 1. Re-read gennady source: ~/Developer/gennady/cli/gennady.ts
139
+ 2. Check command output tokens in the LintCommand implementation.
140
+ 3. Update this script's parsing to match new tokens.
141
+ 4. Until resolved → treat as a HARD blocker; DO NOT assume PASS.
142
+ EOF
143
+ exit 3
@@ -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