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.
- package/ai/directives/coding/result-conventions.xml +25 -7
- package/ai/directives/sdd/audit.directive.xml +1 -1
- package/ai/directives/sdd/critic-protocol.xml +78 -0
- package/ai/directives/sdd/critic.directive.xml +158 -0
- package/ai/directives/sdd/phase-execution-protocol.xml +11 -9
- package/ai/skills/alt-opinion/SKILL.md +79 -0
- package/ai/skills/alt-opinion/opinion.prompt.md +11 -0
- package/ai/skills/alt-opinion/synth.prompt.md +8 -0
- package/ai/skills/sdd-audit/SKILL.md +13 -0
- package/ai/skills/sdd-check/SKILL.md +139 -0
- package/ai/skills/sdd-continue/SKILL.md +13 -0
- package/ai/skills/sdd-critic/SKILL.md +12 -0
- package/ai/skills/sdd-discover/SKILL.md +13 -0
- package/ai/skills/sdd-execute/SKILL.md +205 -0
- package/ai/skills/sdd-execute/scripts/README.md +84 -0
- package/ai/skills/sdd-execute/scripts/check-blockers.sh +158 -0
- package/ai/skills/sdd-execute/scripts/classify-scripts.js +111 -0
- package/ai/skills/sdd-execute/scripts/classify-scripts.ts +133 -0
- package/ai/skills/sdd-execute/scripts/extract-section.sh +219 -0
- package/ai/skills/sdd-execute/scripts/lint-artifacts.sh +143 -0
- package/ai/skills/sdd-execute/scripts/scan.sh +385 -0
- package/ai/skills/sdd-execute/scripts/sdd +114 -0
- package/ai/skills/sdd-execute/scripts/verify.sh +139 -0
- package/ai/skills/sdd-execute-batch/SKILL.md +213 -0
- package/ai/skills/sdd-fix/SKILL.md +13 -0
- package/ai/skills/sdd-infra/SKILL.md +13 -0
- package/ai/skills/sdd-module-decomposition/SKILL.md +13 -0
- package/ai/skills/sdd-scaffold/SKILL.md +13 -0
- package/ai/skills/sdd-setup/SKILL.md +13 -0
- package/dist/ai/directives/coding/result-conventions.xml +25 -7
- package/dist/ai/directives/sdd/audit.directive.xml +1 -1
- package/dist/ai/directives/sdd/critic-protocol.xml +78 -0
- package/dist/ai/directives/sdd/critic.directive.xml +158 -0
- package/dist/ai/directives/sdd/phase-execution-protocol.xml +11 -9
- package/dist/ai/skills/alt-opinion/SKILL.md +79 -0
- package/dist/ai/skills/alt-opinion/opinion.prompt.md +11 -0
- package/dist/ai/skills/alt-opinion/synth.prompt.md +8 -0
- package/dist/ai/skills/sdd-audit/SKILL.md +13 -0
- package/dist/ai/skills/sdd-check/SKILL.md +139 -0
- package/dist/ai/skills/sdd-continue/SKILL.md +13 -0
- package/dist/ai/skills/sdd-critic/SKILL.md +12 -0
- package/dist/ai/skills/sdd-discover/SKILL.md +13 -0
- package/dist/ai/skills/sdd-execute/SKILL.md +205 -0
- package/dist/ai/skills/sdd-execute/scripts/README.md +84 -0
- package/dist/ai/skills/sdd-execute/scripts/check-blockers.sh +158 -0
- package/dist/ai/skills/sdd-execute/scripts/classify-scripts.js +111 -0
- package/dist/ai/skills/sdd-execute/scripts/classify-scripts.ts +133 -0
- package/dist/ai/skills/sdd-execute/scripts/extract-section.sh +219 -0
- package/dist/ai/skills/sdd-execute/scripts/lint-artifacts.sh +143 -0
- package/dist/ai/skills/sdd-execute/scripts/scan.sh +385 -0
- package/dist/ai/skills/sdd-execute/scripts/sdd +114 -0
- package/dist/ai/skills/sdd-execute/scripts/verify.sh +139 -0
- package/dist/ai/skills/sdd-execute-batch/SKILL.md +213 -0
- package/dist/ai/skills/sdd-fix/SKILL.md +13 -0
- package/dist/ai/skills/sdd-infra/SKILL.md +13 -0
- package/dist/ai/skills/sdd-module-decomposition/SKILL.md +13 -0
- package/dist/ai/skills/sdd-scaffold/SKILL.md +13 -0
- package/dist/ai/skills/sdd-setup/SKILL.md +13 -0
- package/dist/chunks/_commonjsHelpers-DaMA6jEr.js +8 -0
- package/dist/chunks/{devtools-B-7ugZhF.js → devtools-CPZ0IUNM.js} +1 -1
- package/dist/chunks/help-5qCaicff.js +6 -0
- package/dist/chunks/help-B2ETH0j6.js +6 -0
- package/dist/chunks/help-BCDURaCQ.js +6 -0
- package/dist/chunks/help-BvgYOSyQ.js +6 -0
- package/dist/chunks/help-CSPhOoI9.js +8 -0
- package/dist/chunks/help-CV1vpwrh.js +6 -0
- package/dist/chunks/help-CaBNA6Qr.js +12 -0
- package/dist/chunks/help-CjkR6SB9.js +6 -0
- package/dist/chunks/help-Coi1l5u9.js +8 -0
- package/dist/chunks/help-CsuLpZn-.js +8 -0
- package/dist/chunks/help-DL_QBMwy.js +8 -0
- package/dist/chunks/help-DqBZ2xcp.js +6 -0
- package/dist/chunks/help-_Y_TN4DZ.js +6 -0
- package/dist/chunks/{help.cmd-B_G7EWzF.js → help.cmd-BseRtoBF.js} +10 -2
- package/dist/chunks/{index-zBnnuvLA.js → index-BU1AXJGA.js} +12 -7
- package/dist/chunks/{index-CXuhZzS3.js → index-BXWDYM_9.js} +1 -1
- package/dist/chunks/index-BgKyH0dB.js +658 -0
- package/dist/chunks/{index-CaahtXiM.js → index-BghL1-Nk.js} +2 -2
- package/dist/chunks/{index-BeL1Zcbg.js → index-BlqEQEGy.js} +1 -1
- package/dist/chunks/{index-D1qsi0Uc.js → index-CDM74qdR.js} +3 -3
- package/dist/chunks/{index-D9ceRUyB.js → index-CGsIRefC.js} +2 -2
- package/dist/chunks/{index-CiEM-8nJ.js → index-CIvkaAQ3.js} +2 -2
- package/dist/chunks/{index-UbHoePfr.js → index-CkczJ__m.js} +10 -10
- package/dist/chunks/index-CtJKSOL0.js +21 -0
- package/dist/chunks/index-DEzym4Ae.js +369 -0
- package/dist/chunks/{index-CVR66voe.js → index-DoaxOhQ8.js} +401 -393
- package/dist/chunks/index-Du1xR-qO.js +752 -0
- package/dist/chunks/{index-DU6jD7SS.js → index-OtehG7uM.js} +1 -1
- package/dist/chunks/index-ZM_FonZj.js +156 -0
- package/dist/chunks/{index-g1LXlp77.js → index-k0nToACp.js} +2 -2
- package/dist/chunks/{run-review-command.logic-DpkRoEi8.js → run-review-command.logic-BeYQin-I.js} +9 -9
- package/dist/chunks/services-IgZptnTi.js +2732 -0
- package/dist/chunks/shared-DL_1LG0K.js +753 -0
- package/dist/cli/cmd/agent-mon/help.d.ts +4 -0
- package/dist/cli/cmd/agents-rules/agents-rules.cmd.d.ts +8 -0
- package/dist/cli/cmd/agents-rules/index.d.ts +1 -0
- package/dist/cli/cmd/alt-opinion/help.d.ts +4 -0
- package/dist/cli/cmd/cat/help.d.ts +4 -0
- package/dist/cli/cmd/commit/help.d.ts +4 -0
- package/dist/cli/cmd/lint/checks/anchor-class-body.check.d.ts +11 -0
- package/dist/cli/cmd/lint/checks/invariant-count.check.d.ts +11 -0
- package/dist/cli/cmd/lint/checks/utils/glob-match.d.ts +15 -0
- package/dist/cli/cmd/lint/checks/utils/strip-strings-comments.d.ts +7 -0
- package/dist/cli/cmd/lint/help.d.ts +4 -0
- package/dist/cli/cmd/lint/lint.cmd.d.ts +1 -1
- package/dist/cli/cmd/lint/lint.types.d.ts +6 -0
- package/dist/cli/cmd/orient/core/build-index.d.ts +8 -0
- package/dist/cli/cmd/orient/core/damerau-levenshtein.d.ts +26 -0
- package/dist/cli/cmd/orient/core/extract-header.d.ts +8 -0
- package/dist/cli/cmd/orient/core/hints.d.ts +9 -0
- package/dist/cli/cmd/orient/core/query-consumer.d.ts +10 -0
- package/dist/cli/cmd/orient/core/query-entity.d.ts +11 -0
- package/dist/cli/cmd/orient/core/query-graph.d.ts +18 -0
- package/dist/cli/cmd/orient/core/query-keyword.d.ts +10 -0
- package/dist/cli/cmd/orient/core/query-spec.d.ts +15 -0
- package/dist/cli/cmd/orient/core/query-task.d.ts +8 -0
- package/dist/cli/cmd/orient/core/scan-files.d.ts +8 -0
- package/dist/cli/cmd/orient/help.d.ts +5 -0
- package/dist/cli/cmd/orient/index.d.ts +1 -0
- package/dist/cli/cmd/orient/orient.cmd.d.ts +7 -0
- package/dist/cli/cmd/orient/orient.types.d.ts +136 -0
- package/dist/cli/cmd/orient/render/render-detail.d.ts +10 -0
- package/dist/cli/cmd/orient/render/render-file-list.d.ts +15 -0
- package/dist/cli/cmd/orient/render/render-graph.d.ts +13 -0
- package/dist/cli/cmd/orient/render/render-search.d.ts +8 -0
- package/dist/cli/cmd/orient/render/render-specs.d.ts +13 -0
- package/dist/cli/cmd/orient/render/render-tree.d.ts +13 -0
- package/dist/cli/cmd/remote-console/help.d.ts +4 -0
- package/dist/cli/cmd/resolve-conflicts/help.d.ts +4 -0
- package/dist/cli/cmd/review/help.d.ts +4 -0
- package/dist/cli/cmd/review-issues/help.d.ts +4 -0
- package/dist/cli/cmd/review-verify/help.d.ts +4 -0
- package/dist/cli/cmd/sync/help.d.ts +4 -0
- package/dist/cli/cmd/sync/sync-core.d.ts +3 -2
- package/dist/cli/cmd/sync/sync-formatter.d.ts +8 -9
- package/dist/cli/cmd/sync/sync.cmd.d.ts +2 -21
- package/dist/cli/cmd/sync-skills/index.d.ts +1 -0
- package/dist/cli/cmd/sync-skills/sync-skills-core.d.ts +50 -0
- package/dist/cli/cmd/sync-skills/sync-skills-formatter.d.ts +13 -0
- package/dist/cli/cmd/sync-skills/sync-skills.cmd.d.ts +14 -0
- package/dist/cli/cmd/sync-skills/sync-skills.types.d.ts +56 -0
- package/dist/cli/cmd/vcs-reply/help.d.ts +4 -0
- package/dist/gennady.js +73 -18
- package/dist/index.js +5 -5
- package/dist/services/dbc/linter/implementations/ts/dbc-ts-ast-adapter.d.ts +3 -2
- package/dist/shared/common/sync/path-normalizer.d.ts +22 -0
- package/dist/shared/common/sync/sync-core.shared.d.ts +14 -0
- package/dist/shared/common/sync/sync-deps.type.d.ts +66 -0
- package/dist/shared/common/sync/sync-formatter.shared.d.ts +19 -0
- package/package.json +6 -5
- package/dist/chunks/index-DJpVmyp2.js +0 -176
- package/dist/chunks/index-KxSZKmZn.js +0 -540
- package/dist/chunks/services-CaLOhuLV.js +0 -2889
- 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
|