@drafthq/draft 2.7.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 (158) hide show
  1. package/.claude-plugin/marketplace.json +38 -0
  2. package/.claude-plugin/plugin.json +26 -0
  3. package/LICENSE +21 -0
  4. package/README.md +272 -0
  5. package/bin/README.md +49 -0
  6. package/cli/bin/draft.js +13 -0
  7. package/cli/src/cli.js +113 -0
  8. package/cli/src/hosts/claude-code.js +46 -0
  9. package/cli/src/hosts/codex.js +33 -0
  10. package/cli/src/hosts/cursor.js +50 -0
  11. package/cli/src/hosts/index.js +24 -0
  12. package/cli/src/hosts/opencode.js +39 -0
  13. package/cli/src/installer.js +61 -0
  14. package/cli/src/lib/fsx.js +34 -0
  15. package/cli/src/lib/graph.js +23 -0
  16. package/cli/src/lib/log.js +32 -0
  17. package/cli/src/lib/paths.js +14 -0
  18. package/core/agents/architect.md +338 -0
  19. package/core/agents/debugger.md +193 -0
  20. package/core/agents/ops.md +104 -0
  21. package/core/agents/planner.md +158 -0
  22. package/core/agents/rca.md +314 -0
  23. package/core/agents/reviewer.md +256 -0
  24. package/core/agents/writer.md +110 -0
  25. package/core/guardrails/README.md +4 -0
  26. package/core/guardrails/code-quality.md +4 -0
  27. package/core/guardrails/dependency-triage.md +4 -0
  28. package/core/guardrails/design-norms.md +4 -0
  29. package/core/guardrails/language-standards.md +4 -0
  30. package/core/guardrails/review-checks.md +4 -0
  31. package/core/guardrails/secure-patterns.md +4 -0
  32. package/core/guardrails/security.md +4 -0
  33. package/core/guardrails.md +22 -0
  34. package/core/knowledge-base.md +127 -0
  35. package/core/methodology.md +1221 -0
  36. package/core/shared/condensation.md +224 -0
  37. package/core/shared/context-verify.md +44 -0
  38. package/core/shared/cross-skill-dispatch.md +127 -0
  39. package/core/shared/discovery-schema.md +75 -0
  40. package/core/shared/draft-context-loading.md +282 -0
  41. package/core/shared/git-report-metadata.md +106 -0
  42. package/core/shared/graph-query.md +239 -0
  43. package/core/shared/graph-usage-report.md +22 -0
  44. package/core/shared/jira-sync.md +170 -0
  45. package/core/shared/parallel-analysis.md +386 -0
  46. package/core/shared/parallel-fanout.md +10 -0
  47. package/core/shared/pattern-learning.md +146 -0
  48. package/core/shared/red-flags.md +58 -0
  49. package/core/shared/template-contract.md +22 -0
  50. package/core/shared/template-hygiene.md +10 -0
  51. package/core/shared/tool-resolver.md +10 -0
  52. package/core/shared/vcs-commands.md +97 -0
  53. package/core/shared/verification-gates.md +47 -0
  54. package/core/templates/CHANGELOG.md +70 -0
  55. package/core/templates/ai-context-export.md +8 -0
  56. package/core/templates/ai-context.md +270 -0
  57. package/core/templates/ai-profile.md +41 -0
  58. package/core/templates/architecture.md +203 -0
  59. package/core/templates/dependency-graph.md +103 -0
  60. package/core/templates/discovery.md +79 -0
  61. package/core/templates/guardrails.md +143 -0
  62. package/core/templates/hld.md +327 -0
  63. package/core/templates/intake-questions.md +403 -0
  64. package/core/templates/jira.md +119 -0
  65. package/core/templates/lld.md +283 -0
  66. package/core/templates/metadata.json +66 -0
  67. package/core/templates/plan.md +130 -0
  68. package/core/templates/product.md +110 -0
  69. package/core/templates/rca.md +86 -0
  70. package/core/templates/root-architecture.md +127 -0
  71. package/core/templates/root-product.md +53 -0
  72. package/core/templates/root-tech-stack.md +117 -0
  73. package/core/templates/service-index.md +55 -0
  74. package/core/templates/session-summary.md +8 -0
  75. package/core/templates/spec.md +165 -0
  76. package/core/templates/tech-matrix.md +101 -0
  77. package/core/templates/tech-stack.md +169 -0
  78. package/core/templates/track-architecture.md +311 -0
  79. package/core/templates/workflow.md +187 -0
  80. package/integrations/agents/AGENTS.md +24384 -0
  81. package/integrations/copilot/.github/copilot-instructions.md +24384 -0
  82. package/integrations/gemini/.gemini.md +26 -0
  83. package/package.json +53 -0
  84. package/scripts/fetch-memory-engine.sh +116 -0
  85. package/scripts/lib.sh +256 -0
  86. package/scripts/tools/_lib.sh +220 -0
  87. package/scripts/tools/adr-index.sh +117 -0
  88. package/scripts/tools/check-graph-usage-report.sh +95 -0
  89. package/scripts/tools/check-scope-conflicts.sh +139 -0
  90. package/scripts/tools/check-skill-line-caps.sh +115 -0
  91. package/scripts/tools/check-template-noop.sh +87 -0
  92. package/scripts/tools/check-track-hygiene.sh +230 -0
  93. package/scripts/tools/classify-files.sh +231 -0
  94. package/scripts/tools/cycle-detect.sh +75 -0
  95. package/scripts/tools/detect-test-framework.sh +135 -0
  96. package/scripts/tools/diff-templates-vs-tracks.sh +176 -0
  97. package/scripts/tools/emit-skill-metrics.sh +71 -0
  98. package/scripts/tools/fix-whitespace.sh +192 -0
  99. package/scripts/tools/freshness-check.sh +143 -0
  100. package/scripts/tools/git-metadata.sh +203 -0
  101. package/scripts/tools/graph-callers.sh +74 -0
  102. package/scripts/tools/graph-impact.sh +93 -0
  103. package/scripts/tools/graph-snapshot.sh +102 -0
  104. package/scripts/tools/hotspot-rank.sh +75 -0
  105. package/scripts/tools/manage-symlinks.sh +85 -0
  106. package/scripts/tools/mermaid-from-graph.sh +92 -0
  107. package/scripts/tools/migrate-track-frontmatter.sh +241 -0
  108. package/scripts/tools/parse-git-log.sh +135 -0
  109. package/scripts/tools/parse-reports.sh +114 -0
  110. package/scripts/tools/render-track.sh +145 -0
  111. package/scripts/tools/run-coverage.sh +153 -0
  112. package/scripts/tools/scan-markers.sh +144 -0
  113. package/scripts/tools/skill-caps.conf +24 -0
  114. package/scripts/tools/validate-frontmatter.sh +125 -0
  115. package/scripts/tools/verify-citations.sh +250 -0
  116. package/scripts/tools/verify-doc-anchors.sh +204 -0
  117. package/scripts/tools/verify-graph-binary.sh +154 -0
  118. package/skills/GRAPH.md +332 -0
  119. package/skills/adr/SKILL.md +374 -0
  120. package/skills/assist-review/SKILL.md +49 -0
  121. package/skills/bughunt/SKILL.md +668 -0
  122. package/skills/bughunt/references/regression-tests.md +399 -0
  123. package/skills/change/SKILL.md +267 -0
  124. package/skills/coverage/SKILL.md +336 -0
  125. package/skills/debug/SKILL.md +201 -0
  126. package/skills/decompose/SKILL.md +656 -0
  127. package/skills/deep-review/SKILL.md +326 -0
  128. package/skills/deploy-checklist/SKILL.md +254 -0
  129. package/skills/discover/SKILL.md +66 -0
  130. package/skills/docs/SKILL.md +42 -0
  131. package/skills/documentation/SKILL.md +197 -0
  132. package/skills/draft/SKILL.md +177 -0
  133. package/skills/draft/context-files.md +57 -0
  134. package/skills/draft/intent-mapping.md +37 -0
  135. package/skills/draft/quality-guide.md +51 -0
  136. package/skills/graph/SKILL.md +107 -0
  137. package/skills/impact/SKILL.md +86 -0
  138. package/skills/implement/SKILL.md +794 -0
  139. package/skills/incident-response/SKILL.md +245 -0
  140. package/skills/index/SKILL.md +848 -0
  141. package/skills/init/SKILL.md +1784 -0
  142. package/skills/init/references/architecture-spec.md +1259 -0
  143. package/skills/integrations/SKILL.md +53 -0
  144. package/skills/jira/SKILL.md +577 -0
  145. package/skills/jira/references/review.md +1322 -0
  146. package/skills/learn/SKILL.md +478 -0
  147. package/skills/new-track/SKILL.md +841 -0
  148. package/skills/ops/SKILL.md +57 -0
  149. package/skills/plan/SKILL.md +60 -0
  150. package/skills/quick-review/SKILL.md +216 -0
  151. package/skills/revert/SKILL.md +178 -0
  152. package/skills/review/SKILL.md +1114 -0
  153. package/skills/standup/SKILL.md +183 -0
  154. package/skills/status/SKILL.md +183 -0
  155. package/skills/tech-debt/SKILL.md +318 -0
  156. package/skills/testing-strategy/SKILL.md +195 -0
  157. package/skills/tour/SKILL.md +38 -0
  158. package/skills/upload/SKILL.md +117 -0
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env bash
2
+ # adr-index.sh — index Architecture Decision Records.
3
+ #
4
+ # Walks --root and emits a JSON {adrs:[{id,title,date,status,path,related_tracks}]}.
5
+ # Default --root is <cwd>/draft/adrs. id is derived from filename prefix (NNN-…) when
6
+ # present, else filename without extension.
7
+ #
8
+ # Usage:
9
+ # scripts/tools/adr-index.sh [--root DIR]
10
+ set -euo pipefail
11
+
12
+ # shellcheck source=_lib.sh
13
+ source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
14
+
15
+ ROOT=""
16
+
17
+ usage() {
18
+ cat <<'EOF'
19
+ adr-index.sh — emit a JSON index of ADR files.
20
+
21
+ Usage:
22
+ scripts/tools/adr-index.sh [--root DIR]
23
+
24
+ Flags:
25
+ --root DIR Directory containing ADR markdown files (default: draft/adrs).
26
+ --help Show this help.
27
+
28
+ Reads YAML frontmatter (title, date, status, related_tracks) from each ADR file.
29
+ Output: {adrs:[{id,title,date,status,path,related_tracks}]}
30
+ EOF
31
+ }
32
+
33
+ while [[ $# -gt 0 ]]; do
34
+ case "$1" in
35
+ --root) ROOT="$2"; shift 2;;
36
+ --help|-h) usage; exit 0;;
37
+ *) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
38
+ esac
39
+ done
40
+
41
+ if [[ -z "$ROOT" ]]; then
42
+ ROOT="draft/adrs"
43
+ fi
44
+
45
+ if [[ ! -d "$ROOT" ]]; then
46
+ printf '{"adrs":[]}\n'
47
+ exit 0
48
+ fi
49
+
50
+ # Related tracks: YAML list under "related_tracks:" — one item per line like " - XYZ".
51
+ get_related_tracks() {
52
+ local file="$1"
53
+ awk '
54
+ NR == 1 && /^---$/ { in_fm = 1; next }
55
+ in_fm && /^---$/ { exit }
56
+ in_fm {
57
+ if ($0 ~ /^related_tracks:/) { in_list = 1; next }
58
+ if (in_list) {
59
+ if ($0 ~ /^[[:space:]]*-[[:space:]]+/) {
60
+ v = $0
61
+ sub(/^[[:space:]]*-[[:space:]]+/, "", v)
62
+ gsub(/^"/, "", v); gsub(/"$/, "", v)
63
+ print v
64
+ } else if ($0 !~ /^[[:space:]]/) {
65
+ in_list = 0
66
+ }
67
+ }
68
+ }
69
+ ' "$file"
70
+ }
71
+
72
+ first=true
73
+ printf '{"adrs":['
74
+ while IFS= read -r -d '' file; do
75
+ base="$(basename "$file")"
76
+ id="${base%.md}"
77
+ if [[ "$base" =~ ^([0-9]+)- ]]; then
78
+ id="${BASH_REMATCH[1]}"
79
+ fi
80
+
81
+ title="$(get_yaml_field "$file" title)"
82
+ date_val="$(get_yaml_field "$file" date)"
83
+ status="$(get_yaml_field "$file" status)"
84
+
85
+ # If no title in frontmatter, fallback to first H1.
86
+ if [[ -z "$title" ]]; then
87
+ title="$(grep -m1 '^# ' "$file" 2>/dev/null | sed 's/^#\s*//' || true)"
88
+ fi
89
+
90
+ tracks=()
91
+ while IFS= read -r tr; do
92
+ [[ -n "$tr" ]] && tracks+=("$tr")
93
+ done < <(get_related_tracks "$file")
94
+
95
+ tr_json='['
96
+ tr_first=true
97
+ for t in "${tracks[@]+"${tracks[@]}"}"; do
98
+ if $tr_first; then tr_first=false; else tr_json+=','; fi
99
+ tr_json+="\"$(json_escape "$t")\""
100
+ done
101
+ tr_json+=']'
102
+
103
+ if $first; then first=false; else printf ','; fi
104
+ printf '\n {"id":"%s","title":"%s","date":"%s","status":"%s","path":"%s","related_tracks":%s}' \
105
+ "$(json_escape "$id")" \
106
+ "$(json_escape "$title")" \
107
+ "$(json_escape "$date_val")" \
108
+ "$(json_escape "$status")" \
109
+ "$(json_escape "$file")" \
110
+ "$tr_json"
111
+ done < <(find "$ROOT" -maxdepth 2 -type f -name '*.md' -print0 2>/dev/null | sort -z)
112
+
113
+ if $first; then
114
+ printf ']}\n'
115
+ else
116
+ printf '\n]}\n'
117
+ fi
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env bash
2
+ # check-graph-usage-report.sh — validate that a skill output / template contains
3
+ # the mandatory Graph Usage Report footer required by core/shared/graph-query.md.
4
+ #
5
+ # Usage:
6
+ # check-graph-usage-report.sh <file> [<file> ...]
7
+ #
8
+ # Behavior:
9
+ # - Exits 0 when every input file contains a well-formed `## Graph Usage Report`
10
+ # section with the five required bullets.
11
+ # - Exits 1 when any file is missing the section or any required bullet.
12
+ # - Exits 2 on usage error (no files supplied, missing argument).
13
+ #
14
+ # A file is exempt if the very first line matches `<!-- graph-usage-report:skip -->`
15
+ # (intended for non-code-touching templates that legitimately have no graph step).
16
+
17
+ set -euo pipefail
18
+
19
+ if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
20
+ cat <<'EOF'
21
+ check-graph-usage-report.sh — validate that a skill output / template contains
22
+ the mandatory Graph Usage Report footer required by core/shared/graph-query.md.
23
+
24
+ Usage:
25
+ check-graph-usage-report.sh <file> [<file> ...]
26
+
27
+ Required bullets under the `## Graph Usage Report` section:
28
+ - Graph files queried:
29
+ - Modules identified via graph:
30
+ - Files identified via graph:
31
+ - Filesystem grep fallbacks:
32
+
33
+ When `Graph files queried:` is `NONE`, a `- Justification:` line with a non-empty
34
+ value is also required.
35
+
36
+ A file is exempt if its first line is `<!-- graph-usage-report:skip -->`.
37
+
38
+ Exit codes:
39
+ 0 every input file is valid
40
+ 1 any input file fails validation
41
+ 2 usage error (no files supplied)
42
+ EOF
43
+ exit 0
44
+ fi
45
+
46
+ if [ "$#" -lt 1 ]; then
47
+ echo "usage: $0 <file> [<file> ...]" >&2
48
+ exit 2
49
+ fi
50
+
51
+ REQUIRED_BULLETS=(
52
+ "- Graph files queried:"
53
+ "- Modules identified via graph:"
54
+ "- Files identified via graph:"
55
+ "- Filesystem grep fallbacks:"
56
+ )
57
+
58
+ fail=0
59
+
60
+ for f in "$@"; do
61
+ if [ ! -f "$f" ]; then
62
+ echo "MISSING: $f does not exist" >&2
63
+ fail=1
64
+ continue
65
+ fi
66
+
67
+ if head -1 "$f" | grep -q '^<!-- graph-usage-report:skip -->'; then
68
+ continue
69
+ fi
70
+
71
+ if ! grep -q '^## Graph Usage Report' "$f"; then
72
+ echo "FAIL: $f — missing '## Graph Usage Report' section" >&2
73
+ fail=1
74
+ continue
75
+ fi
76
+
77
+ section_start=$(grep -n '^## Graph Usage Report' "$f" | head -1 | cut -d: -f1)
78
+ section_body=$(awk -v start="$section_start" 'NR > start { if (/^## /) exit; print }' "$f")
79
+
80
+ for bullet in "${REQUIRED_BULLETS[@]}"; do
81
+ if ! printf '%s\n' "$section_body" | grep -qF -- "$bullet"; then
82
+ echo "FAIL: $f — Graph Usage Report missing bullet: '$bullet'" >&2
83
+ fail=1
84
+ fi
85
+ done
86
+
87
+ if printf '%s\n' "$section_body" | grep -qE -- '^- Graph files queried:[[:space:]]*NONE'; then
88
+ if ! printf '%s\n' "$section_body" | grep -qE -- '^- Justification[^:]*:[[:space:]]*[^[:space:]]'; then
89
+ echo "FAIL: $f — 'Graph files queried: NONE' requires a populated Justification line" >&2
90
+ fail=1
91
+ fi
92
+ fi
93
+ done
94
+
95
+ exit "$fail"
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env bash
2
+ # check-scope-conflicts.sh
3
+ #
4
+ # Walks every track in the repository (or those passed on the command line),
5
+ # reads `scope_includes` / `scope_excludes` from metadata.json (preferred) or
6
+ # the spec.md frontmatter (fallback), and flags any two tracks that share an
7
+ # included scope tag without one excluding the other's tag set.
8
+ #
9
+ # Usage:
10
+ # scripts/tools/check-scope-conflicts.sh # scan ./tracks/*
11
+ # scripts/tools/check-scope-conflicts.sh tracks/foo tracks/bar
12
+ # scripts/tools/check-scope-conflicts.sh --json ...
13
+ #
14
+ # Exit codes:
15
+ # 0 no conflicts
16
+ # 1 conflicts detected
17
+ # 2 usage / runtime error
18
+
19
+ set -euo pipefail
20
+
21
+ if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
22
+ echo "${0##*/} — Foundations quality tool (see core/ docs for full behavior)"
23
+ exit 0
24
+ fi
25
+
26
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
27
+ # shellcheck source=/dev/null
28
+ source "$SCRIPT_DIR/_lib.sh"
29
+
30
+ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
31
+
32
+ EMIT_JSON=0
33
+ TRACK_PATHS=()
34
+
35
+ usage() {
36
+ local stream=2 code=2
37
+ if [[ "${USAGE_HELP_MODE:-0}" == 1 ]]; then stream=1; code=0; fi
38
+ sed -n '2,17p' "$0" >&$stream
39
+ exit "$code"
40
+ }
41
+
42
+ while (($#)); do
43
+ case "$1" in
44
+ -h|--help) USAGE_HELP_MODE=1 usage ;;
45
+ --json) EMIT_JSON=1; shift ;;
46
+ -*) printf 'Unknown flag: %s\n' "$1" >&2; usage ;;
47
+ *) TRACK_PATHS+=("$1"); shift ;;
48
+ esac
49
+ done
50
+
51
+ if ((${#TRACK_PATHS[@]} == 0)); then
52
+ while IFS= read -r p; do TRACK_PATHS+=("$p"); done < <(discover_track_dirs "$REPO_ROOT")
53
+ fi
54
+
55
+ conflict_count=0
56
+ declare -a conflicts=()
57
+ declare -a track_rels=()
58
+ declare -a track_includes=()
59
+ declare -a track_excludes=()
60
+
61
+ record() { conflicts+=("$1|$2|$3"); conflict_count=$((conflict_count + 1)); }
62
+
63
+ for t in "${TRACK_PATHS[@]}"; do
64
+ [[ -d "$t" ]] || { record "$t" "not-a-directory" ""; continue; }
65
+ track_dir="$(cd "$t" && pwd)"
66
+ rel="${track_dir#"$REPO_ROOT/"}"
67
+ inc=""; exc=""
68
+ [[ -f "$track_dir/metadata.json" ]] && {
69
+ inc="$(read_scope_array "$track_dir/metadata.json" scope_includes)"
70
+ exc="$(read_scope_array "$track_dir/metadata.json" scope_excludes)"
71
+ }
72
+ if [[ -z "$inc" && -f "$track_dir/spec.md" ]]; then
73
+ inc="$(read_scope_array "$track_dir/spec.md" scope_includes)"
74
+ exc="$(read_scope_array "$track_dir/spec.md" scope_excludes)"
75
+ fi
76
+ track_rels+=("$rel")
77
+ track_includes+=("$inc")
78
+ track_excludes+=("$exc")
79
+ done
80
+
81
+ for ((i=0; i<${#track_rels[@]}; i++)); do
82
+ for ((j=i+1; j<${#track_rels[@]}; j++)); do
83
+ a="${track_rels[$i]}"
84
+ b="${track_rels[$j]}"
85
+ a_inc="${track_includes[$i]}"
86
+ b_inc="${track_includes[$j]}"
87
+ a_exc="${track_excludes[$i]}"
88
+ b_exc="${track_excludes[$j]}"
89
+ for tag in $a_inc; do
90
+ [[ -z "$tag" ]] && continue
91
+ if [[ " $b_inc " == *" $tag "* ]]; then
92
+ conflict=1
93
+ for ex in $a_exc; do
94
+ [[ -z "$ex" ]] && continue
95
+ if [[ " $b_inc " == *" $ex "* ]]; then conflict=0; break; fi
96
+ done
97
+ if ((conflict)); then
98
+ for ex in $b_exc; do
99
+ [[ -z "$ex" ]] && continue
100
+ if [[ " $a_inc " == *" $ex "* ]]; then conflict=0; break; fi
101
+ done
102
+ fi
103
+ if ((conflict)); then
104
+ record "$a" "scope-conflict" "shares '$tag' with $b without mutual exclusion"
105
+ fi
106
+ fi
107
+ done
108
+ done
109
+ done
110
+
111
+ emit() {
112
+ if ((EMIT_JSON)); then
113
+ printf '{"conflict_count": %d, "conflicts": [\n' "$conflict_count"
114
+ local first=1 c track kind detail
115
+ for c in "${conflicts[@]}"; do
116
+ IFS='|' read -r track kind detail <<< "$c"
117
+ if ((first)); then first=0; else printf ',\n'; fi
118
+ printf ' {"track":"%s","kind":"%s","detail":"%s"}' \
119
+ "$(json_escape "$track")" "$(json_escape "$kind")" \
120
+ "$(json_escape "$detail")"
121
+ done
122
+ printf '\n]}\n'
123
+ else
124
+ if ((conflict_count == 0)); then
125
+ printf 'OK: no scope conflicts across %d track(s).\n' "${#TRACK_PATHS[@]}"
126
+ else
127
+ printf 'SCOPE: %d conflict(s) across %d track(s).\n' \
128
+ "$conflict_count" "${#TRACK_PATHS[@]}" >&2
129
+ local c track kind detail
130
+ for c in "${conflicts[@]}"; do
131
+ IFS='|' read -r track kind detail <<< "$c"
132
+ printf ' [%s] %s — %s\n' "$kind" "$track" "$detail" >&2
133
+ done
134
+ fi
135
+ fi
136
+ }
137
+ emit
138
+
139
+ ((conflict_count == 0))
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env bash
2
+ # check-skill-line-caps.sh
3
+ #
4
+ # Enforces per-skill line-count caps (WS-10 prompt-economy budget). Walks
5
+ # skills/**/SKILL.md and reports any file whose line count exceeds its cap.
6
+ # Caps are sourced from a config file (default scripts/tools/skill-caps.conf)
7
+ # or the SKILL_CAPS env. A skill not listed inherits the GLOBAL_CAP default.
8
+ #
9
+ # Modes:
10
+ # default warn-only (exit 0 regardless; report on stderr)
11
+ # --enforce strict (exit 1 on any over-cap skill)
12
+ #
13
+ # Usage:
14
+ # scripts/tools/check-skill-line-caps.sh # warn-only
15
+ # scripts/tools/check-skill-line-caps.sh --enforce # strict
16
+ # scripts/tools/check-skill-line-caps.sh --json
17
+ #
18
+ # Exit codes:
19
+ # 0 clean (or warn-only mode regardless of findings)
20
+ # 1 --enforce and at least one skill exceeds its cap
21
+ # 2 usage / runtime error
22
+
23
+ set -euo pipefail
24
+
25
+ if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
26
+ echo "${0##*/} — Foundations quality tool (see core/ docs for full behavior)"
27
+ exit 0
28
+ fi
29
+
30
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31
+ # shellcheck source=/dev/null
32
+ source "$SCRIPT_DIR/_lib.sh"
33
+
34
+ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
35
+ CAPS_CONF_DEFAULT="$SCRIPT_DIR/skill-caps.conf"
36
+
37
+ EMIT_JSON=0
38
+ ENFORCE=0
39
+ CAPS_CONF="$CAPS_CONF_DEFAULT"
40
+ SKILLS_DIR="$REPO_ROOT/skills"
41
+
42
+ usage() {
43
+ local stream=2 code=2
44
+ if [[ "${USAGE_HELP_MODE:-0}" == 1 ]]; then stream=1; code=0; fi
45
+ sed -n '2,21p' "$0" >&$stream
46
+ exit "$code"
47
+ }
48
+
49
+ while (($#)); do
50
+ case "$1" in
51
+ -h|--help) USAGE_HELP_MODE=1 usage ;;
52
+ --enforce) ENFORCE=1; shift ;;
53
+ --json) EMIT_JSON=1; shift ;;
54
+ --caps) CAPS_CONF="$2"; shift 2 ;;
55
+ --skills-dir) SKILLS_DIR="$2"; shift 2 ;;
56
+ -*) printf 'Unknown flag: %s\n' "$1" >&2; usage ;;
57
+ *) printf 'Unexpected arg: %s\n' "$1" >&2; usage ;;
58
+ esac
59
+ done
60
+
61
+ GLOBAL_CAP=600
62
+ over_count=0
63
+ declare -a findings=()
64
+ record() { findings+=("$1|$2|$3"); over_count=$((over_count + 1)); }
65
+
66
+ while IFS= read -r path; do
67
+ rel="${path#"$REPO_ROOT/"}"
68
+ name="$(basename "$(dirname "$path")")"
69
+ lines="$(wc -l < "$path" | tr -d ' ')"
70
+ cap="$(skill_line_cap "$name" "$CAPS_CONF" "$GLOBAL_CAP")"
71
+ if (( lines > cap )); then
72
+ record "$name" "$lines" "$cap"
73
+ fi
74
+ done < <(find "$SKILLS_DIR" -mindepth 2 -maxdepth 2 -name 'SKILL.md' | sort)
75
+
76
+ emit() {
77
+ if ((EMIT_JSON)); then
78
+ printf '{"over_cap_count": %d, "global_cap": %d, "findings": [\n' \
79
+ "$over_count" "$GLOBAL_CAP"
80
+ local first=1 v name lines cap
81
+ for v in "${findings[@]}"; do
82
+ IFS='|' read -r name lines cap <<< "$v"
83
+ if ((first)); then first=0; else printf ',\n'; fi
84
+ printf ' {"skill":"%s","lines":%d,"cap":%d}' \
85
+ "$(json_escape "$name")" "$lines" "$cap"
86
+ done
87
+ printf '\n]}\n'
88
+ else
89
+ if ((over_count == 0)); then
90
+ printf 'OK: all skills within cap (default %d).\n' "$GLOBAL_CAP"
91
+ return
92
+ fi
93
+ local mode="warn-only" stream=stderr
94
+ ((ENFORCE)) && mode="enforced"
95
+ if ((ENFORCE)); then
96
+ printf 'SKILL-CAPS: %d skill(s) over cap (%s):\n' \
97
+ "$over_count" "$mode" >&2
98
+ else
99
+ printf 'SKILL-CAPS: %d skill(s) over cap (%s):\n' \
100
+ "$over_count" "$mode"
101
+ fi
102
+ local v name lines cap
103
+ for v in "${findings[@]}"; do
104
+ IFS='|' read -r name lines cap <<< "$v"
105
+ if ((ENFORCE)); then
106
+ printf ' %s: %d lines (cap %d)\n' "$name" "$lines" "$cap" >&2
107
+ else
108
+ printf ' %s: %d lines (cap %d)\n' "$name" "$lines" "$cap"
109
+ fi
110
+ done
111
+ fi
112
+ }
113
+ emit
114
+
115
+ if ((ENFORCE && over_count > 0)); then exit 1; fi
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env bash
2
+ # check-template-noop.sh
3
+ #
4
+ # CI gate enforcing WS-0 contract: any commit that touches skills/** or
5
+ # scripts/tools/** must also touch core/templates/**, or carry the literal
6
+ # tag [template-noop] in the commit message.
7
+ #
8
+ # Rationale: skill-only or validator-only schema changes silently drift from
9
+ # the templates. Forcing them to travel together (or explicitly opt out) keeps
10
+ # templates as the single canonical schema source.
11
+ #
12
+ # Usage:
13
+ # scripts/tools/check-template-noop.sh # HEAD vs HEAD~1
14
+ # scripts/tools/check-template-noop.sh <base> # HEAD vs base
15
+ # scripts/tools/check-template-noop.sh <base>..<head> # range form
16
+ #
17
+ # Exit codes:
18
+ # 0 OK (no relevant changes, or templates also touched, or [template-noop])
19
+ # 1 violation
20
+ # 2 usage / runtime error
21
+
22
+ set -euo pipefail
23
+
24
+ if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
25
+ echo "${0##*/} — Foundations quality tool (see core/ docs for full behavior)"
26
+ exit 0
27
+ fi
28
+
29
+ BASE="${1:-HEAD~1}"
30
+ RANGE="$BASE..HEAD"
31
+ if [[ "$BASE" == *..* ]]; then
32
+ RANGE="$BASE"
33
+ fi
34
+
35
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
36
+ printf 'check-template-noop: not inside a git repository\n' >&2
37
+ exit 2
38
+ fi
39
+
40
+ # Diff name-only across the range.
41
+ changed_files="$(git diff --name-only "$RANGE" 2>/dev/null || true)"
42
+ if [[ -z "$changed_files" ]]; then
43
+ printf 'check-template-noop: no changes in range %s\n' "$RANGE"
44
+ exit 0
45
+ fi
46
+
47
+ touches_skills_or_tools=0
48
+ touches_templates=0
49
+
50
+ while IFS= read -r f; do
51
+ [[ -z "$f" ]] && continue
52
+ case "$f" in
53
+ skills/*|scripts/tools/*)
54
+ # Skip changes to the templates themselves living under scripts.
55
+ touches_skills_or_tools=1 ;;
56
+ core/templates/*)
57
+ touches_templates=1 ;;
58
+ esac
59
+ done <<< "$changed_files"
60
+
61
+ if ((touches_skills_or_tools == 0)); then
62
+ printf 'check-template-noop: no skills/** or scripts/tools/** changes in range %s\n' "$RANGE"
63
+ exit 0
64
+ fi
65
+
66
+ if ((touches_templates == 1)); then
67
+ printf 'check-template-noop: OK — skills/tools changes accompanied by core/templates/** changes.\n'
68
+ exit 0
69
+ fi
70
+
71
+ # Look for [template-noop] tag in any commit message in the range.
72
+ commit_log="$(git log --format=%B "$RANGE" 2>/dev/null || true)"
73
+ if printf '%s' "$commit_log" | grep -Fq '[template-noop]'; then
74
+ printf 'check-template-noop: OK — [template-noop] tag present in commit message.\n'
75
+ exit 0
76
+ fi
77
+
78
+ cat >&2 <<EOF
79
+ check-template-noop: FAIL
80
+ Range: $RANGE
81
+ skills/** or scripts/tools/** changed without touching core/templates/**.
82
+ Either:
83
+ (a) update core/templates/* to reflect the schema change, or
84
+ (b) add [template-noop] to the commit message if this is intentionally a no-op
85
+ for the template schema (e.g. pure bug fix, refactor, perf improvement).
86
+ EOF
87
+ exit 1