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,158 @@
1
+ #!/usr/bin/env bash
2
+ # @file: Scan ticket Execution Log for unresolved BLOCKER entries.
3
+ # @consumers: orchestrator (preflight before phase dispatch); audit; CI hooks.
4
+ # @contract: AX_BASH_NO_SILENT_EMPTY. Implements AX_BLOCKER_RESOLUTION_TRAIL —
5
+ # BLOCKER from earlier Round is ACTIVE unless a later Round explicitly
6
+ # marks it RESOLVED.
7
+ #
8
+ # Heuristic detection:
9
+ # - Lines containing both "🛑" AND "BLOCKED" → blocker entry (with line number, context).
10
+ # - Lines containing both "✅" AND "RESOLVED" → resolution marker.
11
+ # - If RESOLVED markers count >= BLOCKED markers count, AND the last 🛑/✅ marker
12
+ # in the log is ✅ → all blockers considered resolved.
13
+ # - Otherwise → list latest blocker entries as ACTIVE; orchestrator must decide.
14
+ #
15
+ # Usage:
16
+ # check-blockers.sh <ticket-file>
17
+ #
18
+ # Exit codes:
19
+ # 0 — no unresolved blockers (clear to dispatch)
20
+ # 2 — one or more unresolved blockers (orchestrator MUST resolve before dispatch)
21
+ # 4 — bad invocation
22
+ # 1 — file not found
23
+
24
+ set -uo pipefail
25
+
26
+ PROG="check-blockers"
27
+
28
+ if [[ $# -ne 1 ]]; then
29
+ cat <<EOF
30
+ [$PROG] BAD_INVOCATION
31
+ expected: $PROG <ticket-file>
32
+ got: $PROG $*
33
+
34
+ Required action (ORCHESTRATOR):
35
+ Pass exactly one ticket file path.
36
+ EOF
37
+ exit 4
38
+ fi
39
+
40
+ ticket="$1"
41
+
42
+ if [[ ! -f "$ticket" ]]; then
43
+ cat <<EOF
44
+ [$PROG] FILE_NOT_FOUND
45
+ file: $ticket
46
+
47
+ Required action:
48
+ Verify ticket path; task may need re-resolution.
49
+ EOF
50
+ exit 1
51
+ fi
52
+
53
+ # Locate Execution Log section start. Convention: "## 7. Execution Log".
54
+ log_start=$(grep -n "^## 7\. Execution Log" "$ticket" | head -1 | cut -d: -f1)
55
+ if [[ -z "$log_start" ]]; then
56
+ cat <<EOF
57
+ [$PROG] NO_EXECUTION_LOG
58
+ file: $ticket
59
+
60
+ The ticket lacks a "## 7. Execution Log" section. Either the ticket is malformed
61
+ or uses a different section number/title.
62
+
63
+ Required action (ORCHESTRATOR):
64
+ Verify ticket structure follows scaffold.directive convention. If structure is
65
+ correct but section title differs, update this script's heuristic accordingly.
66
+
67
+ Treating as clear (no blockers detectable).
68
+ EOF
69
+ exit 0
70
+ fi
71
+
72
+ # Extract Execution Log content
73
+ log_content=$(tail -n +"$log_start" "$ticket")
74
+
75
+ # Find blocker and resolution lines (with line numbers).
76
+ # Use awk with proper line numbering, output to temp files for portability with macOS bash 3.2 (no mapfile).
77
+ blocker_lines=()
78
+ resolved_lines=()
79
+ blocker_count=0
80
+ resolved_count=0
81
+
82
+ while IFS= read -r line; do
83
+ [[ -n "$line" ]] && blocker_lines+=("$line") && blocker_count=$((blocker_count + 1))
84
+ done < <(grep -n "🛑.*BLOCKED\|BLOCKED.*🛑" "$ticket" 2>/dev/null | awk -F: -v start="$log_start" '$1 >= start { print $0 }')
85
+
86
+ while IFS= read -r line; do
87
+ [[ -n "$line" ]] && resolved_lines+=("$line") && resolved_count=$((resolved_count + 1))
88
+ done < <(grep -n "✅.*RESOLVED\|RESOLVED.*✅" "$ticket" 2>/dev/null | awk -F: -v start="$log_start" '$1 >= start { print $0 }')
89
+
90
+ # Determine last marker (BLOCKER or RESOLUTION) by line number
91
+ last_blocker_line=0
92
+ last_resolved_line=0
93
+ if [[ $blocker_count -gt 0 ]]; then
94
+ last_blocker_line=$(echo "${blocker_lines[$((blocker_count - 1))]}" | cut -d: -f1)
95
+ fi
96
+ if [[ $resolved_count -gt 0 ]]; then
97
+ last_resolved_line=$(echo "${resolved_lines[$((resolved_count - 1))]}" | cut -d: -f1)
98
+ fi
99
+
100
+ # ---------------------------------------------------------------------------
101
+ # Verdict
102
+ # ---------------------------------------------------------------------------
103
+
104
+ if [[ $blocker_count -eq 0 ]]; then
105
+ cat <<EOF
106
+ [$PROG] CLEAR
107
+ ticket: $ticket
108
+ blockers found: 0
109
+ status: no BLOCKER entries in Execution Log; clear to dispatch phase.
110
+ EOF
111
+ exit 0
112
+ fi
113
+
114
+ if [[ $resolved_count -ge $blocker_count ]] && [[ $last_resolved_line -gt $last_blocker_line ]]; then
115
+ cat <<EOF
116
+ [$PROG] CLEAR
117
+ ticket: $ticket
118
+ blockers found: $blocker_count
119
+ resolutions: $resolved_count
120
+ last marker: ✅ RESOLVED on line $last_resolved_line
121
+ status: AX_BLOCKER_RESOLUTION_TRAIL satisfied — all blockers resolved.
122
+ EOF
123
+ exit 0
124
+ fi
125
+
126
+ # Unresolved blockers — emit list
127
+ cat <<EOF
128
+ [$PROG] UNRESOLVED_BLOCKERS
129
+ ticket: $ticket
130
+ blockers found: $blocker_count
131
+ resolutions: $resolved_count
132
+ last 🛑 line: $last_blocker_line
133
+ last ✅ line: $last_resolved_line
134
+ status: AX_BLOCKER_RESOLUTION_TRAIL violated — latest marker is BLOCKER, not RESOLUTION.
135
+
136
+ Active BLOCKER entries (last 5):
137
+ EOF
138
+ # Print last 5 elements portably (no slicing syntax that requires bash 4)
139
+ start_idx=$((blocker_count - 5))
140
+ [[ $start_idx -lt 0 ]] && start_idx=0
141
+ i=$start_idx
142
+ while [[ $i -lt $blocker_count ]]; do
143
+ echo " ${blocker_lines[$i]}" | cut -c1-200
144
+ i=$((i + 1))
145
+ done
146
+
147
+ cat <<EOF
148
+
149
+ Required action (ORCHESTRATOR):
150
+ 1. Read the listed BLOCKER entries in the ticket.
151
+ 2. Verify whether each blocker is still ACTIVE in current environment:
152
+ - If actually still blocking → escalate to operator with unblock options.
153
+ - If actually resolved (e.g. environment fixed, decision made) → append
154
+ Round N entry with "✅ RESOLVED <Round-M BLOCKER ref>: <reason>" line
155
+ to Execution Log; then re-run this script.
156
+ 3. DO NOT dispatch phase agent while any blocker is unresolved.
157
+ EOF
158
+ exit 2
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ // @file: Heuristic npm script classifier — reads package.json, outputs discovered commands as JSON.
3
+ // @consumers: verify.sh
4
+ // @tasks: TSK-55, TSK-61
5
+ //
6
+ // Usage: node classify-scripts.js [project-root]
7
+
8
+ import { readFileSync } from 'node:fs';
9
+ import { resolve } from 'node:path';
10
+
11
+ function hasCategory(body, category) {
12
+ const patterns = {
13
+ tsc: /\b(tsc|tsgo)\b/,
14
+ lint: /\b(eslint|mail-core-lint\b|biome check\b|standard\b)\b/,
15
+ test: /\b(jest|vitest|playwright test|mocha\b|--test\b)\b/,
16
+ format: /\b(prettier|biome format)\b/,
17
+ };
18
+ return patterns[category] ? patterns[category].test(body) : false;
19
+ }
20
+
21
+ function classify(name, body) {
22
+ const isUmbrellaName = /^(check|ci-check|check:all|verify)$/.test(name);
23
+ if (isUmbrellaName && body.length > 0) return ['umbrella'];
24
+
25
+ const tc = hasCategory(body, 'tsc') ? 1 : 0;
26
+ const lc = hasCategory(body, 'lint') ? 1 : 0;
27
+ const sc = hasCategory(body, 'test') ? 1 : 0;
28
+ const hasGennady = /\bgennady\b/.test(body) || /lint:contracts/.test(body);
29
+ const hasChain = body.includes('&&');
30
+ const hasParallel = /[&]/.test(body) && /;\s*wait/.test(body);
31
+
32
+ if (tc + lc + sc >= 2 && (hasChain || hasParallel)) return ['umbrella'];
33
+ if (hasGennady && hasChain) return ['umbrella'];
34
+
35
+ const classes = [];
36
+ if (hasGennady) classes.push('gennady');
37
+
38
+ const typecheckName = /^(type-?check|typecheck|typecheck:|tsc)$/.test(name);
39
+ const typecheckBody = /\b(tsc|tsgo)\b/.test(body) && /--noEmit/.test(body);
40
+ if (
41
+ (typecheckName || typecheckBody || name === 'lint:ts') &&
42
+ !name.startsWith('build:') &&
43
+ !name.startsWith('prepublish')
44
+ )
45
+ classes.push('typecheck');
46
+
47
+ const lintName = /^(lint|lint:all|lint-check|eslint|mc:eslint|stylist:lint|stylelint)$/.test(
48
+ name
49
+ );
50
+ const lintBody = hasCategory(body, 'lint');
51
+ if (
52
+ (lintName || lintBody) &&
53
+ !name.startsWith('lint:fix') &&
54
+ !name.startsWith('lint:contracts') &&
55
+ !name.startsWith('lint:ts') &&
56
+ !hasGennady
57
+ )
58
+ classes.push('lint');
59
+
60
+ const testName = /^(test|test:|mc:test|mc:jest|jest)$/.test(name);
61
+ const testBody = hasCategory(body, 'test') || /\bnode\b.*--test\b/.test(body);
62
+ if (testName || (testBody && !name.startsWith('build:') && !name.startsWith('prepublish')))
63
+ classes.push('test');
64
+
65
+ const formatName = /^format(:?check)?$/.test(name);
66
+ const formatBody = hasCategory(body, 'format');
67
+ if (formatName || formatBody) classes.push('format');
68
+
69
+ return classes.length > 0 ? classes : ['unknown'];
70
+ }
71
+
72
+ function selectBest(entries, cls) {
73
+ const priority = {
74
+ typecheck: { 'type-check': 10, typecheck: 10, tsc: 5 },
75
+ gennady: { 'lint:contracts': 10 },
76
+ lint: { lint: 10, eslint: 7, 'lint:all': 5, 'lint-check': 5, 'mc:eslint': 3 },
77
+ test: { test: 10, 'test:unit': 7, 'mc:test': 5, 'mc:jest': 3, jest: 3 },
78
+ format: { 'format:check': 10, format: 5 },
79
+ };
80
+ const candidates = entries.filter((e) => e.classes.includes(cls));
81
+ if (candidates.length === 0) return undefined;
82
+ const rank = (name) => (priority[cls] && priority[cls][name]) || 1;
83
+ candidates.sort((a, b) => rank(b.name) - rank(a.name));
84
+ return candidates[0] ? candidates[0].name : undefined;
85
+ }
86
+
87
+ // --- Main ---
88
+ const rootArg = process.argv.slice(2).find((a) => !a.startsWith('-'));
89
+ const root = rootArg ? resolve(rootArg) : process.cwd();
90
+
91
+ let pkg;
92
+ try {
93
+ pkg = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf-8'));
94
+ } catch {
95
+ console.error(`[classify-scripts] package.json not found at ${root}`);
96
+ process.exit(1);
97
+ }
98
+
99
+ const entries = Object.entries(pkg.scripts || {}).map(([name, body]) => ({
100
+ name,
101
+ body,
102
+ classes: classify(name, body),
103
+ }));
104
+
105
+ const selected = {};
106
+ for (const cls of ['typecheck', 'gennady', 'lint', 'test', 'format']) {
107
+ const best = selectBest(entries, cls);
108
+ if (best) selected[cls] = best;
109
+ }
110
+
111
+ process.stdout.write(JSON.stringify({ scripts: entries, selected }));
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env -S node --import tsx
2
+ // @file: Heuristic npm script classifier — reads package.json, outputs discovered commands as JSON.
3
+ // @consumers: verify.sh
4
+ // @tasks: TSK-55
5
+ //
6
+ // Usage:
7
+ // classify-scripts.ts [--json] [project-root]
8
+ //
9
+ // Output:
10
+ // --json (default): JSON array [{name, body, classes}]
11
+ // no flag: human-readable summary
12
+
13
+ import { readFileSync } from 'node:fs';
14
+ import { resolve } from 'node:path';
15
+
16
+ type ScriptClass = 'typecheck' | 'gennady' | 'lint' | 'test' | 'format' | 'umbrella' | 'unknown';
17
+
18
+ interface ScriptEntry {
19
+ name: string;
20
+ body: string;
21
+ classes: ScriptClass[];
22
+ }
23
+
24
+ interface ClassifiedProject {
25
+ scripts: ScriptEntry[];
26
+ selected: Partial<Record<ScriptClass, string>>;
27
+ }
28
+
29
+ function hasCategory(body: string, category: string): boolean {
30
+ const patterns: Record<string, RegExp> = {
31
+ tsc: /\b(tsc|tsgo)\b/,
32
+ lint: /\b(eslint|mail-core-lint\b|biome check\b|standard\b)\b/,
33
+ test: /\b(jest|vitest|playwright test|mocha\b|--test\b)\b/,
34
+ format: /\b(prettier|biome format)\b/,
35
+ };
36
+ return patterns[category]?.test(body) ?? false;
37
+ }
38
+
39
+ function classify(name: string, body: string): ScriptClass[] {
40
+ const isUmbrellaName = /^(check|ci-check|check:all|verify)$/.test(name);
41
+ if (isUmbrellaName && body.length > 0) return ['umbrella'];
42
+
43
+ const tscCount = hasCategory(body, 'tsc') ? 1 : 0;
44
+ const lintCount = hasCategory(body, 'lint') ? 1 : 0;
45
+ const testCount = hasCategory(body, 'test') ? 1 : 0;
46
+ const hasGennady = /\bgennady\b/.test(body) || /lint:contracts/.test(body);
47
+ const hasChain = body.includes('&&');
48
+ const hasParallel = /[&]/.test(body) && /;\s*wait/.test(body);
49
+
50
+ if (tscCount + lintCount + testCount >= 2 && (hasChain || hasParallel)) return ['umbrella'];
51
+ if (hasGennady && hasChain) return ['umbrella'];
52
+
53
+ const classes: ScriptClass[] = [];
54
+ if (hasGennady) classes.push('gennady');
55
+
56
+ const typecheckName = /^(type-?check|typecheck|typecheck:|tsc)$/.test(name);
57
+ const typecheckBody = /\b(tsc|tsgo)\b/.test(body) && /--noEmit/.test(body);
58
+ if (
59
+ (typecheckName || typecheckBody || name === 'lint:ts') &&
60
+ !name.startsWith('build:') &&
61
+ !name.startsWith('prepublish')
62
+ )
63
+ classes.push('typecheck');
64
+
65
+ const lintName = /^(lint|lint:all|lint-check|eslint|mc:eslint|stylist:lint|stylelint)$/.test(
66
+ name
67
+ );
68
+ const lintBody = hasCategory(body, 'lint');
69
+ if (
70
+ (lintName || lintBody) &&
71
+ !name.startsWith('lint:fix') &&
72
+ !name.startsWith('lint:contracts') &&
73
+ !name.startsWith('lint:ts') &&
74
+ !hasGennady
75
+ )
76
+ classes.push('lint');
77
+
78
+ const testName = /^(test|test:|mc:test|mc:jest|jest)$/.test(name);
79
+ const testBody = hasCategory(body, 'test') || /\bnode\b.*--test\b/.test(body);
80
+ if (testName || (testBody && !name.startsWith('build:') && !name.startsWith('prepublish')))
81
+ classes.push('test');
82
+
83
+ const formatName = /^format(:?check)?$/.test(name);
84
+ const formatBody = hasCategory(body, 'format');
85
+ if (formatName || formatBody) classes.push('format');
86
+
87
+ return classes.length > 0 ? classes : ['unknown'];
88
+ }
89
+
90
+ function selectBest(entries: ScriptEntry[], cls: ScriptClass): string | undefined {
91
+ const candidates = entries.filter((e) => e.classes.includes(cls));
92
+ if (candidates.length === 0) return undefined;
93
+
94
+ // Prefer shorter, more specific names
95
+ const priority: Record<string, number> = {
96
+ typecheck: { 'type-check': 10, typecheck: 10, tsc: 5 },
97
+ gennady: { 'lint:contracts': 10 },
98
+ lint: { lint: 10, eslint: 7, 'lint:all': 5, 'lint-check': 5, 'mc:eslint': 3 },
99
+ test: { test: 10, 'test:unit': 7, 'mc:test': 5, 'mc:jest': 3, jest: 3 },
100
+ format: { 'format:check': 10, format: 5 },
101
+ };
102
+
103
+ const rank = (name: string) => priority[cls]?.[name] ?? 1;
104
+ candidates.sort((a, b) => rank(b.name) - rank(a.name));
105
+ return candidates[0]?.name;
106
+ }
107
+
108
+ // --- Main ---
109
+ const root = process.argv.slice(2).find((a) => !a.startsWith('-')) || process.cwd();
110
+ const pkgPath = resolve(root, 'package.json');
111
+
112
+ let pkg: { scripts?: Record<string, string> };
113
+ try {
114
+ pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
115
+ } catch {
116
+ console.error(`[classify-scripts] package.json not found at ${pkgPath}`);
117
+ process.exit(1);
118
+ }
119
+
120
+ const entries: ScriptEntry[] = Object.entries(pkg.scripts ?? {}).map(([name, body]) => ({
121
+ name,
122
+ body,
123
+ classes: classify(name, body),
124
+ }));
125
+
126
+ const selected: Partial<Record<ScriptClass, string>> = {};
127
+ for (const cls of ['typecheck', 'gennady', 'lint', 'test', 'format'] as ScriptClass[]) {
128
+ const best = selectBest(entries, cls);
129
+ if (best) selected[cls] = best;
130
+ }
131
+
132
+ const result: ClassifiedProject = { scripts: entries, selected };
133
+ console.log(JSON.stringify(result));
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env bash
2
+ # @file: Extract a named section between <!--SECTION:<NAME>--> / <!--/SECTION:<NAME>--> markers from a markdown file.
3
+ # @consumers: sdd-execute, sdd-execute-batch (L2 orchestrator prompt-building); phase agents (rules extraction)
4
+ # @contract: AX_BASH_NO_SILENT_EMPTY — never produces empty stdout. On miss → emits actionable instruction for the orchestrator + exit ≠ 0.
5
+ #
6
+ # Anchor grammar (canonical, AX_TICKET_ANCHOR_FORMAT):
7
+ # Open marker: <!--SECTION:<NAME>-->
8
+ # Close marker: <!--/SECTION:<NAME>-->
9
+ # <NAME>: ^[A-Z][A-Z0-9_]*$ (uppercase, starts with letter, alnum + underscore only)
10
+ # No quotes, no spaces, no attributes — atomic identifiers only.
11
+ #
12
+ # Canonical names: META, PHASES_OVERVIEW, PHASE_P1, PHASE_P2, PHASE_P1_FIX, BDD, VERIFICATION, TEST_COVERAGE, EXECUTION_LOG.
13
+ #
14
+ # Usage:
15
+ # extract-section.sh <file> <NAME>
16
+ #
17
+ # Examples:
18
+ # extract-section.sh ticket.md META
19
+ # extract-section.sh ticket.md PHASE_P1
20
+ # extract-section.sh ticket.md EXECUTION_LOG
21
+ #
22
+ # Exit codes:
23
+ # 0 — section found, content emitted to stdout
24
+ # 1 — file not found / unreadable
25
+ # 2 — anchor markers not present in file
26
+ # 3 — START found but END missing, or vice versa (corrupted markers), or duplicate occurrences
27
+ # 4 — bad invocation (missing args, invalid NAME)
28
+
29
+ set -uo pipefail
30
+
31
+ PROG="extract-section"
32
+ NAME_REGEX='^[A-Z][A-Z0-9_]*$'
33
+
34
+ if [[ $# -ne 2 ]]; then
35
+ cat <<EOF
36
+ [$PROG] BAD_INVOCATION
37
+ expected: $PROG <file> <NAME>
38
+ got: $PROG $*
39
+
40
+ Required action (ORCHESTRATOR):
41
+ Pass exactly 2 arguments — file path and SECTION name.
42
+ NAME must match: $NAME_REGEX
43
+ Examples of valid names: META, PHASES_OVERVIEW, PHASE_P1, BDD, EXECUTION_LOG.
44
+ EOF
45
+ exit 4
46
+ fi
47
+
48
+ file="$1"
49
+ name="$2"
50
+
51
+ # ---------------------------------------------------------------------------
52
+ # NAME validation
53
+ # ---------------------------------------------------------------------------
54
+
55
+ if [[ ! "$name" =~ $NAME_REGEX ]]; then
56
+ cat <<EOF
57
+ [$PROG] INVALID_NAME
58
+ name: $name
59
+ rule: $NAME_REGEX
60
+
61
+ Diagnosis: section name does not match canonical anchor grammar.
62
+
63
+ Required action (ORCHESTRATOR):
64
+ 1. Use uppercase letters, digits, and underscores only.
65
+ 2. Start with a letter (not a digit or underscore).
66
+ 3. No spaces, no quotes, no attributes inside the name.
67
+
68
+ Canonical names: META, PHASES_OVERVIEW, PHASE_P1, PHASE_P2, PHASE_P1_FIX,
69
+ BDD, VERIFICATION, TEST_COVERAGE, EXECUTION_LOG.
70
+
71
+ If you need attributes (kind, rules, etc.) — put them inside the section content,
72
+ NOT inside the anchor name. The anchor is an identifier only.
73
+ EOF
74
+ exit 4
75
+ fi
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # File existence + readability
79
+ # ---------------------------------------------------------------------------
80
+
81
+ if [[ ! -f "$file" ]]; then
82
+ cat <<EOF
83
+ [$PROG] FILE_NOT_FOUND
84
+ file: $file
85
+
86
+ Required action (ORCHESTRATOR):
87
+ 1. Verify the file path argument (typos, wrong scope, wrong task-id).
88
+ 2. Check whether the ticket/spec exists at the expected location.
89
+ 3. If task ID resolves to a different path — fix the resolution layer, not this script.
90
+
91
+ DO NOT proceed to dispatch phase agent until the file is located.
92
+ EOF
93
+ exit 1
94
+ fi
95
+
96
+ if [[ ! -r "$file" ]]; then
97
+ cat <<EOF
98
+ [$PROG] FILE_NOT_READABLE
99
+ file: $file
100
+ perms: $(stat -f '%Sp' "$file" 2>/dev/null || stat -c '%A' "$file" 2>/dev/null || echo 'unknown')
101
+
102
+ Required action (ORCHESTRATOR):
103
+ Verify file permissions; chmod if needed.
104
+ EOF
105
+ exit 1
106
+ fi
107
+
108
+ # ---------------------------------------------------------------------------
109
+ # Marker scan — pre-flight before extraction
110
+ # ---------------------------------------------------------------------------
111
+
112
+ start_marker="<!--SECTION:${name}-->"
113
+ end_marker="<!--/SECTION:${name}-->"
114
+
115
+ start_count=$(grep -cF "$start_marker" "$file" || true)
116
+ end_count=$(grep -cF "$end_marker" "$file" || true)
117
+
118
+ if [[ "$start_count" -eq 0 && "$end_count" -eq 0 ]]; then
119
+ cat <<EOF
120
+ [$PROG] ANCHOR_NOT_FOUND
121
+ section: $name
122
+ file: $file
123
+ searched markers:
124
+ $start_marker
125
+ $end_marker
126
+
127
+ Diagnosis: file does not contain anchor markers for this section.
128
+
129
+ Possible causes:
130
+ 1. Ticket/spec scaffolded before anchor convention introduced (pre-2026-05-21).
131
+ 2. Anchor was deleted or corrupted during manual edit.
132
+ 3. Wrong section name — verify against canonical names
133
+ (META, PHASES_OVERVIEW, PHASE_P<N>, BDD, VERIFICATION, TEST_COVERAGE, EXECUTION_LOG).
134
+ 4. Section uses non-canonical name — review file structure.
135
+
136
+ Required action (ORCHESTRATOR, not phase agent):
137
+ 1. Read the file directly via Read tool.
138
+ 2. Verify the section exists by markdown header (e.g. ## 1. Meta, ### P1 — impl).
139
+ 3. If section exists in header form but lacks anchors → retrofit anchors per
140
+ AX_TICKET_ANCHOR_FORMAT in ~/Developer/gennady/ai/directives/sdd/scaffold.directive.xml.
141
+ 4. If section does not exist at all → escalate to operator: ticket needs
142
+ re-scaffolding or section content authoring.
143
+
144
+ DO NOT proceed to dispatch a phase agent until anchors are in place.
145
+ EOF
146
+ exit 2
147
+ fi
148
+
149
+ if [[ "$start_count" -ne "$end_count" ]]; then
150
+ cat <<EOF
151
+ [$PROG] ANCHOR_UNBALANCED
152
+ section: $name
153
+ file: $file
154
+ $start_marker count: $start_count
155
+ $end_marker count: $end_count
156
+
157
+ Diagnosis: markers are corrupted — counts of open and close markers do not match.
158
+
159
+ Required action (ORCHESTRATOR):
160
+ 1. Read the file.
161
+ 2. Locate the imbalanced marker via:
162
+ grep -nF '$start_marker' "$file"
163
+ grep -nF '$end_marker' "$file"
164
+ 3. Restore the missing one OR remove the duplicate one.
165
+ 4. Retry this script.
166
+
167
+ DO NOT proceed to dispatch a phase agent until markers are balanced.
168
+ EOF
169
+ exit 3
170
+ fi
171
+
172
+ if [[ "$start_count" -gt 1 ]]; then
173
+ cat <<EOF
174
+ [$PROG] ANCHOR_DUPLICATED
175
+ section: $name
176
+ file: $file
177
+ occurrences: $start_count
178
+
179
+ Diagnosis: the same section appears more than once in this file — ambiguous extraction.
180
+
181
+ Required action (ORCHESTRATOR):
182
+ 1. Read the file and identify duplicate sections.
183
+ 2. Either merge them or rename one to be unique (e.g. PHASE_P1 vs PHASE_P1_FIX).
184
+ 3. Retry this script.
185
+ EOF
186
+ exit 3
187
+ fi
188
+
189
+ # ---------------------------------------------------------------------------
190
+ # Extraction — markers verified present and balanced; emit content between them.
191
+ # Exclude the marker lines themselves from output.
192
+ # ---------------------------------------------------------------------------
193
+
194
+ content=$(awk -v start="$start_marker" -v end="$end_marker" '
195
+ $0 == start { in_block = 1; next }
196
+ $0 == end { in_block = 0; next }
197
+ in_block { print }
198
+ ' "$file")
199
+
200
+ if [[ -z "$content" ]]; then
201
+ cat <<EOF
202
+ [$PROG] ANCHOR_EMPTY
203
+ section: $name
204
+ file: $file
205
+
206
+ Diagnosis: markers are present and balanced, but the section between them is empty.
207
+
208
+ Required action (ORCHESTRATOR):
209
+ 1. Read the file.
210
+ 2. Determine whether the section is intentionally empty (rare) or accidentally
211
+ stripped during refactor.
212
+ 3. If accidental → re-author content.
213
+ 4. If intentional → use a different section reference; this one carries no payload.
214
+ EOF
215
+ exit 2
216
+ fi
217
+
218
+ printf '%s\n' "$content"
219
+ exit 0