@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,145 @@
1
+ #!/usr/bin/env bash
2
+ # render-track.sh
3
+ #
4
+ # Render a Draft track's markdown set into a single HTML viewer artifact
5
+ # on demand. Replaces the pre-2.0 pattern of checking generated HTML into
6
+ # the track directory; viewer artifacts are now git-ignored and rebuilt
7
+ # whenever a reader wants them.
8
+ #
9
+ # Usage:
10
+ # scripts/tools/render-track.sh <track_dir> [--out <path>]
11
+ # scripts/tools/render-track.sh <track_dir> --stdout
12
+ #
13
+ # Default output: <track_dir>/track-reader.html (git-ignored).
14
+ #
15
+ # Implementation note: pandoc is preferred if present; otherwise a minimal
16
+ # markdown-to-HTML render is used via awk. No external network calls.
17
+
18
+ set -euo pipefail
19
+
20
+ if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
21
+ echo "${0##*/} — Foundations quality tool (see core/ docs for full behavior)"
22
+ exit 0
23
+ fi
24
+
25
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
26
+ # shellcheck source=/dev/null
27
+ source "$SCRIPT_DIR/_lib.sh"
28
+
29
+ html_escape() {
30
+ local s="$1"
31
+ s="${s//&/&amp;}"
32
+ s="${s//</&lt;}"
33
+ s="${s//>/&gt;}"
34
+ printf '%s' "$s"
35
+ }
36
+
37
+ usage() {
38
+ local stream=2 code=2
39
+ if [[ "${USAGE_HELP_MODE:-0}" == 1 ]]; then stream=1; code=0; fi
40
+ sed -n '2,16p' "$0" >&$stream
41
+ exit "$code"
42
+ }
43
+
44
+ TRACK_DIR=""
45
+ OUT_PATH=""
46
+ TO_STDOUT=0
47
+
48
+ while (($#)); do
49
+ case "$1" in
50
+ -h|--help) USAGE_HELP_MODE=1 usage ;;
51
+ --out) OUT_PATH="$2"; shift 2 ;;
52
+ --stdout) TO_STDOUT=1; shift ;;
53
+ -*) printf 'Unknown flag: %s\n' "$1" >&2; usage ;;
54
+ *) TRACK_DIR="$1"; shift ;;
55
+ esac
56
+ done
57
+
58
+ [[ -z "$TRACK_DIR" ]] && usage
59
+ [[ ! -d "$TRACK_DIR" ]] && { printf 'Not a directory: %s\n' "$TRACK_DIR" >&2; exit 2; }
60
+
61
+ TRACK_DIR="$(cd "$TRACK_DIR" && pwd)"
62
+ track_id="$(basename "$TRACK_DIR")"
63
+
64
+ if (( ! TO_STDOUT )) && [[ -z "$OUT_PATH" ]]; then
65
+ OUT_PATH="$TRACK_DIR/track-reader.html"
66
+ fi
67
+
68
+ # Pre-2.0 cleanup: warn if a stale HTML artifact is checked in.
69
+ if (( ! TO_STDOUT )) && git -C "$TRACK_DIR" ls-files --error-unmatch \
70
+ "track-reader.html" >/dev/null 2>&1; then
71
+ printf 'render-track: WARNING — track-reader.html is checked into git.\n' >&2
72
+ printf ' Remove it: git rm tracks/.../track-reader.html\n' >&2
73
+ printf ' Draft 2.0 makes viewer artifacts git-ignored runtime output.\n' >&2
74
+ fi
75
+
76
+ ORDER=(spec.md plan.md hld.md lld.md discovery.md)
77
+ ALL_MD=("${ORDER[@]}")
78
+ # Append any extras found that are not already in ORDER.
79
+ while IFS= read -r f; do
80
+ name="$(basename "$f")"
81
+ skip=0
82
+ for o in "${ORDER[@]}"; do [[ "$name" == "$o" ]] && skip=1 && break; done
83
+ (( skip )) || ALL_MD+=("$name")
84
+ done < <(find "$TRACK_DIR" -maxdepth 1 -type f -name '*.md' | sort)
85
+
86
+ render_with_pandoc() {
87
+ local title="$1"; shift
88
+ local files=()
89
+ for f in "$@"; do
90
+ [[ -f "$TRACK_DIR/$f" ]] && files+=("$TRACK_DIR/$f")
91
+ done
92
+ (( ${#files[@]} > 0 )) || return 1
93
+ pandoc -s --toc --metadata title="$title" "${files[@]}"
94
+ }
95
+
96
+ render_minimal() {
97
+ local title="$1"; shift
98
+ local files=()
99
+ for f in "$@"; do
100
+ [[ -f "$TRACK_DIR/$f" ]] && files+=("$TRACK_DIR/$f")
101
+ done
102
+ (( ${#files[@]} > 0 )) || return 1
103
+ printf '<!doctype html>\n<html><head><meta charset="utf-8">'
104
+ printf '<title>%s</title>' "$(html_escape "$title")"
105
+ cat <<'CSS'
106
+ <style>
107
+ body{font:14px/1.5 system-ui,sans-serif;max-width:920px;margin:2em auto;padding:0 1em;color:#222}
108
+ h1,h2,h3{font-weight:600}h1{border-bottom:1px solid #ccc;padding-bottom:.3em}
109
+ code{background:#f4f4f4;padding:.1em .3em;border-radius:3px;font-size:.95em}
110
+ pre{background:#f8f8f8;padding:.8em;border-radius:5px;overflow-x:auto}
111
+ table{border-collapse:collapse}td,th{border:1px solid #ddd;padding:.4em .6em}
112
+ nav.toc{background:#fafafa;border:1px solid #eee;padding:.6em 1em;margin:1em 0}
113
+ hr.docsep{margin:3em 0;border:0;border-top:2px dashed #ccc}
114
+ .meta{color:#888;font-size:.9em}
115
+ </style></head><body>
116
+ CSS
117
+ printf '<h1>%s</h1>\n<nav class="toc"><strong>Documents</strong><ul>\n' "$(html_escape "$title")"
118
+ for f in "${files[@]}"; do
119
+ local n; n="$(basename "$f")"
120
+ printf '<li><a href="#%s">%s</a></li>\n' "$(html_escape "$n")" "$(html_escape "$n")"
121
+ done
122
+ printf '</ul></nav>\n'
123
+ for f in "${files[@]}"; do
124
+ local n; n="$(basename "$f")"
125
+ printf '<hr class="docsep"><section id="%s"><p class="meta">— %s —</p>\n<pre><code>' "$(html_escape "$n")" "$(html_escape "$n")"
126
+ sed -e 's/&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/g' "$f"
127
+ printf '</code></pre></section>\n'
128
+ done
129
+ printf '</body></html>\n'
130
+ }
131
+
132
+ OUTPUT=""
133
+ if command -v pandoc >/dev/null 2>&1; then
134
+ OUTPUT="$(render_with_pandoc "Draft Track: $track_id" "${ALL_MD[@]}" || true)"
135
+ fi
136
+ if [[ -z "$OUTPUT" ]]; then
137
+ OUTPUT="$(render_minimal "Draft Track: $track_id" "${ALL_MD[@]}")"
138
+ fi
139
+
140
+ if (( TO_STDOUT )); then
141
+ printf '%s' "$OUTPUT"
142
+ else
143
+ printf '%s' "$OUTPUT" > "$OUT_PATH"
144
+ printf 'Wrote %s (%d bytes)\n' "$OUT_PATH" "${#OUTPUT}"
145
+ fi
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env bash
2
+ # run-coverage.sh — normalized coverage dispatcher.
3
+ #
4
+ # Dispatches to a language-specific coverage runner and emits a normalized
5
+ # JSON report:
6
+ # {language, tool, total:{lines,branches}, per_file:[{path,lines,branches,uncovered_lines}]}
7
+ #
8
+ # The actual coverage run is language-specific and may be slow. Use
9
+ # `--schema-check` in CI to validate output shape without running tests.
10
+ #
11
+ # Usage:
12
+ # scripts/tools/run-coverage.sh <language> [--path DIR]
13
+ # [--schema-check]
14
+ #
15
+ # Languages: python (pytest --cov), go (go test -coverprofile), javascript (nyc), shell (n/a → 2)
16
+ #
17
+ # Exit codes: 0 OK, 1 invocation error, 2 tool unavailable (emits empty-schema JSON).
18
+ set -euo pipefail
19
+
20
+ LANGUAGE=""
21
+ COVERAGE_PATH="."
22
+ SCHEMA_CHECK="false"
23
+
24
+ usage() {
25
+ cat <<'EOF'
26
+ run-coverage.sh — normalized coverage report dispatcher.
27
+
28
+ Usage:
29
+ scripts/tools/run-coverage.sh <language> [--path DIR] [--schema-check]
30
+
31
+ Languages:
32
+ python pytest --cov (requires pytest-cov)
33
+ go go test -coverprofile
34
+ javascript nyc/c8 (via package.json)
35
+ shell not supported — emits schema-valid empty report (exit 2)
36
+
37
+ Flags:
38
+ --path DIR Path to run coverage against (default: cwd).
39
+ --schema-check Emit schema-valid empty JSON without running any tests (for CI).
40
+ --help Show this help.
41
+
42
+ Output schema (always present, even on exit 2):
43
+ {
44
+ "language": "<lang>",
45
+ "tool": "<coverage-tool>",
46
+ "total": {"lines": <float 0..1>, "branches": <float 0..1|null>},
47
+ "per_file": [{"path","lines","branches","uncovered_lines":[<int>...]}]
48
+ }
49
+ EOF
50
+ }
51
+
52
+ while [[ $# -gt 0 ]]; do
53
+ case "$1" in
54
+ --path) COVERAGE_PATH="$2"; shift 2;;
55
+ --schema-check) SCHEMA_CHECK="true"; shift;;
56
+ --help|-h) usage; exit 0;;
57
+ -*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
58
+ *)
59
+ if [[ -z "$LANGUAGE" ]]; then
60
+ LANGUAGE="$1"; shift
61
+ else
62
+ echo "Unexpected positional arg: $1" >&2; exit 1
63
+ fi
64
+ ;;
65
+ esac
66
+ done
67
+
68
+ if [[ -z "$LANGUAGE" ]]; then
69
+ echo "ERROR: language is required (see --help)" >&2
70
+ exit 1
71
+ fi
72
+
73
+ emit_empty() {
74
+ local lang="$1" tool="$2"
75
+ printf '{"language":"%s","tool":"%s","total":{"lines":null,"branches":null},"per_file":[]}\n' \
76
+ "$lang" "$tool"
77
+ }
78
+
79
+ # Schema-only mode: never run the real coverage tool.
80
+ if [[ "$SCHEMA_CHECK" == "true" ]]; then
81
+ case "$LANGUAGE" in
82
+ python) tool="pytest-cov";;
83
+ go) tool="go-cover";;
84
+ javascript) tool="nyc";;
85
+ typescript) tool="nyc";;
86
+ shell) tool="none";;
87
+ *)
88
+ echo "ERROR: unsupported language '$LANGUAGE'" >&2
89
+ exit 1
90
+ ;;
91
+ esac
92
+ emit_empty "$LANGUAGE" "$tool"
93
+ [[ "$LANGUAGE" == "shell" ]] && exit 2
94
+ exit 0
95
+ fi
96
+
97
+ case "$LANGUAGE" in
98
+ python)
99
+ if ! command -v pytest >/dev/null 2>&1; then
100
+ emit_empty python pytest-cov
101
+ exit 2
102
+ fi
103
+ # Run, capture JSON if coverage.py is available.
104
+ tmp_json="$(mktemp)"
105
+ trap 'rm -f "$tmp_json"' EXIT
106
+ (cd "$COVERAGE_PATH" && pytest --cov --cov-report=json:"$tmp_json" >/dev/null 2>&1) || true
107
+ if [[ -s "$tmp_json" ]] && command -v jq >/dev/null 2>&1; then
108
+ jq '{language:"python", tool:"pytest-cov",
109
+ total:{lines: (.totals.percent_covered // 0) / 100, branches: null},
110
+ per_file: (.files | to_entries | map({
111
+ path: .key,
112
+ lines: (.value.summary.percent_covered // 0) / 100,
113
+ branches: null,
114
+ uncovered_lines: (.value.missing_lines // [])
115
+ }))}' "$tmp_json"
116
+ exit 0
117
+ fi
118
+ emit_empty python pytest-cov
119
+ exit 2
120
+ ;;
121
+ go)
122
+ if ! command -v go >/dev/null 2>&1; then
123
+ emit_empty go go-cover
124
+ exit 2
125
+ fi
126
+ tmp_cov="$(mktemp)"
127
+ trap 'rm -f "$tmp_cov"' EXIT
128
+ (cd "$COVERAGE_PATH" && go test -coverprofile="$tmp_cov" ./... >/dev/null 2>&1) || true
129
+ if [[ -s "$tmp_cov" ]]; then
130
+ total=$(go tool cover -func="$tmp_cov" 2>/dev/null | awk '/^total:/ {gsub(/%/, "", $3); print $3/100}')
131
+ total="${total:-null}"
132
+ printf '{"language":"go","tool":"go-cover","total":{"lines":%s,"branches":null},"per_file":[]}\n' \
133
+ "$total"
134
+ exit 0
135
+ fi
136
+ emit_empty go go-cover
137
+ exit 2
138
+ ;;
139
+ javascript|typescript)
140
+ # Real coverage via nyc/c8 is not yet wired — emit empty schema (exit 2)
141
+ # so consumers can degrade gracefully. See --schema-check for CI validation.
142
+ emit_empty "$LANGUAGE" nyc
143
+ exit 2
144
+ ;;
145
+ shell)
146
+ emit_empty shell none
147
+ exit 2
148
+ ;;
149
+ *)
150
+ echo "ERROR: unsupported language '$LANGUAGE'" >&2
151
+ exit 1
152
+ ;;
153
+ esac
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env bash
2
+ # scan-markers.sh — find TODO/FIXME/HACK/XXX/DEPRECATED markers with blame age.
3
+ #
4
+ # Emits a JSON array. Per entry:
5
+ # {path, line, marker, text, sha, author, introduced, age_days}
6
+ #
7
+ # Usage:
8
+ # scripts/tools/scan-markers.sh [--root DIR] [--markers LIST]
9
+ # [--min-age-days N] [--include-untracked]
10
+ #
11
+ # Exit codes: 0 OK (even with zero hits), 1 invocation error, 2 not a git repo
12
+ # (emits [] on stdout so consumers can still parse).
13
+ set -euo pipefail
14
+
15
+ # shellcheck source=_lib.sh
16
+ source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
17
+
18
+ ROOT="."
19
+ MARKERS="TODO,FIXME,HACK,XXX,DEPRECATED"
20
+ MIN_AGE=0
21
+
22
+ usage() {
23
+ cat <<'EOF'
24
+ scan-markers.sh — find code markers (TODO/FIXME/...) with git blame age.
25
+
26
+ Usage:
27
+ scripts/tools/scan-markers.sh [--root DIR] [--markers LIST] [--min-age-days N]
28
+
29
+ Flags:
30
+ --root DIR Root directory to scan (default: cwd).
31
+ --markers LIST Comma-separated marker list (default: TODO,FIXME,HACK,XXX,DEPRECATED).
32
+ --min-age-days N Only emit markers older than N days (default: 0).
33
+ --help Show this help.
34
+
35
+ Output: JSON array of {path, line, marker, text, sha, author, introduced, age_days}.
36
+ EOF
37
+ }
38
+
39
+ while [[ $# -gt 0 ]]; do
40
+ case "$1" in
41
+ --root) ROOT="$2"; shift 2;;
42
+ --markers) MARKERS="$2"; shift 2;;
43
+ --min-age-days) MIN_AGE="$2"; shift 2;;
44
+ --help|-h) usage; exit 0;;
45
+ *) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
46
+ esac
47
+ done
48
+
49
+ if [[ ! -d "$ROOT" ]]; then
50
+ echo "ERROR: --root '$ROOT' is not a directory" >&2
51
+ exit 1
52
+ fi
53
+
54
+ cd "$ROOT"
55
+
56
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
57
+ echo "[]"
58
+ exit 2
59
+ fi
60
+
61
+ # Build a regex like "\b(TODO|FIXME|HACK|XXX|DEPRECATED)\b"
62
+ IFS=',' read -ra MARK_ARRAY <<<"$MARKERS"
63
+ PATTERN_INNER="$(IFS='|'; echo "${MARK_ARRAY[*]}")"
64
+
65
+ TMP="$(mktemp)"
66
+ trap 'rm -f "$TMP"' EXIT
67
+
68
+ # Prefer ripgrep; fall back to git grep.
69
+ if command -v rg >/dev/null 2>&1; then
70
+ rg -n --no-heading -w -e "($PATTERN_INNER)" . >"$TMP" 2>/dev/null || true
71
+ else
72
+ git grep -n -w -E "$PATTERN_INNER" -- . >"$TMP" 2>/dev/null || true
73
+ fi
74
+
75
+ NOW_SEC="$(date -u +%s)"
76
+
77
+ emit_array() {
78
+ local first=true
79
+ printf '['
80
+ while IFS= read -r _ln; do
81
+ path="${_ln%%:*}"; rest="${_ln#*:}"; linenum="${rest%%:*}"; rest="${rest#*:}"
82
+ [[ -z "$path" || -z "$linenum" ]] && continue
83
+ # Skip binaries or tool's own output directory.
84
+ [[ "$path" == */.git/* ]] && continue
85
+
86
+ # Identify which marker matched first in this line (pure-bash, no fork).
87
+ marker=""
88
+ for m in "${MARK_ARRAY[@]}"; do
89
+ if [[ "$rest" == *"$m"* ]]; then
90
+ marker="$m"
91
+ break
92
+ fi
93
+ done
94
+ [[ -z "$marker" ]] && continue
95
+
96
+ # Trim whitespace from text
97
+ text="${rest#"${rest%%[![:space:]]*}"}"
98
+ text="${text%"${text##*[![:space:]]}"}"
99
+ # Limit text length
100
+ if [[ ${#text} -gt 300 ]]; then
101
+ text="${text:0:300}"
102
+ fi
103
+
104
+ # Blame info
105
+ sha="null"
106
+ author=""
107
+ introduced=""
108
+ age_days=0
109
+ if blame_line="$(git blame -L "$linenum,$linenum" --porcelain -- "$path" 2>/dev/null)"; then
110
+ blame_sha="$(echo "$blame_line" | head -1 | awk '{print $1}')"
111
+ if [[ -n "$blame_sha" && "$blame_sha" != "0000000000000000000000000000000000000000" ]]; then
112
+ sha="\"${blame_sha:0:7}\""
113
+ author="$(echo "$blame_line" | awk '/^author / {sub(/^author /,""); print; exit}')"
114
+ ts="$(echo "$blame_line" | awk '/^author-time / {print $2; exit}')"
115
+ if [[ -n "$ts" ]]; then
116
+ introduced="$(date -u -d "@$ts" +%Y-%m-%d 2>/dev/null || date -u -r "$ts" +%Y-%m-%d 2>/dev/null || echo "")"
117
+ age_days=$(( (NOW_SEC - ts) / 86400 ))
118
+ fi
119
+ fi
120
+ fi
121
+
122
+ if [[ "$age_days" -lt "$MIN_AGE" ]]; then
123
+ continue
124
+ fi
125
+
126
+ if $first; then first=false; else printf ','; fi
127
+ printf '\n {"path":"%s","line":%s,"marker":"%s","text":"%s","sha":%s,"author":"%s","introduced":"%s","age_days":%s}' \
128
+ "$(json_escape "$path")" \
129
+ "$linenum" \
130
+ "$marker" \
131
+ "$(json_escape "$text")" \
132
+ "$sha" \
133
+ "$(json_escape "$author")" \
134
+ "$(json_escape "$introduced")" \
135
+ "$age_days"
136
+ done <"$TMP"
137
+ if $first; then
138
+ printf ']\n'
139
+ else
140
+ printf '\n]\n'
141
+ fi
142
+ }
143
+
144
+ emit_array
@@ -0,0 +1,24 @@
1
+ # scripts/tools/skill-caps.conf
2
+ #
3
+ # Per-skill max-line caps consumed by scripts/tools/check-skill-line-caps.sh.
4
+ # Format: <skill-name> <max-lines>
5
+ # Lines starting with '#' or blank lines are ignored.
6
+ # `*` sets the global default cap when a skill is not listed.
7
+ #
8
+ # Initial caps (2026-05-17): warn-only mode. These reflect the WS-10
9
+ # prompt-economy targets. Treat them as aspirational floors; tighten as
10
+ # core/shared/*.md extraction lands. The validator runs in --enforce mode
11
+ # only when invoked with the flag explicitly.
12
+
13
+ # Default for any unlisted skill.
14
+ * 600
15
+
16
+ # Large generators — known oversized today; extraction work continues.
17
+ init 3500
18
+ status 1300
19
+ bughunt 1200
20
+ new-track 1100
21
+ review 1000
22
+ index 900
23
+ implement 900
24
+ decompose 700
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env bash
2
+ # validate-frontmatter.sh — validate YAML frontmatter of a Markdown file.
3
+ #
4
+ # Checks:
5
+ # 1. File starts with a "---" delimiter on line 1.
6
+ # 2. A closing "---" delimiter exists.
7
+ # 3. Every --require field is present as a top-level key.
8
+ # 4. In --mode project-doc: rejects forbidden ephemeral fields (git.*, synced_to_commit)
9
+ # that must live in draft/metadata.json instead of per-file frontmatter (WS-8).
10
+ #
11
+ # Usage:
12
+ # scripts/tools/validate-frontmatter.sh <FILE> [--require FIELD[,FIELD...]]
13
+ # [--mode project-doc]
14
+ #
15
+ # Exit codes: 0 valid, 1 invalid, 2 file not found.
16
+ set -euo pipefail
17
+
18
+ FILE=""
19
+ REQUIRED=""
20
+ MODE=""
21
+
22
+ usage() {
23
+ cat <<'EOF'
24
+ validate-frontmatter.sh — validate a Markdown file's YAML frontmatter.
25
+
26
+ Usage:
27
+ scripts/tools/validate-frontmatter.sh <FILE> [--require FIELD[,FIELD...]]
28
+ [--mode project-doc]
29
+
30
+ Flags:
31
+ --require LIST Comma-separated required top-level keys (default: name,description).
32
+ --mode project-doc Enforce WS-8: reject git.*, synced_to_commit, and dirty fields
33
+ in per-file frontmatter. Use for draft/ project-level artifacts
34
+ (architecture.md, .ai-context.md, product.md, etc.) whose git
35
+ state must live in draft/metadata.json.
36
+ --help Show this help.
37
+
38
+ Exit 0 valid, 1 invalid (writes diagnostics to stderr), 2 file not found.
39
+ EOF
40
+ }
41
+
42
+ REQUIRED="name,description"
43
+
44
+ while [[ $# -gt 0 ]]; do
45
+ case "$1" in
46
+ --require) REQUIRED="$2"; shift 2;;
47
+ --mode) MODE="$2"; shift 2;;
48
+ --help|-h) usage; exit 0;;
49
+ -*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
50
+ *)
51
+ if [[ -z "$FILE" ]]; then FILE="$1"
52
+ else echo "Unexpected arg: $1" >&2; exit 1
53
+ fi
54
+ shift
55
+ ;;
56
+ esac
57
+ done
58
+
59
+ if [[ -z "$FILE" ]]; then
60
+ usage >&2
61
+ exit 1
62
+ fi
63
+
64
+ if [[ ! -f "$FILE" ]]; then
65
+ echo "ERROR: file not found: $FILE" >&2
66
+ exit 2
67
+ fi
68
+
69
+ IFS= read -r first_line <"$FILE" || true
70
+ if [[ "$first_line" != "---" ]]; then
71
+ echo "ERROR: $FILE — missing opening '---' delimiter on line 1" >&2
72
+ exit 1
73
+ fi
74
+
75
+ if ! awk 'NR > 1 && /^---$/ { found=1; exit } END { exit !found }' "$FILE"; then
76
+ echo "ERROR: $FILE — missing closing '---' delimiter" >&2
77
+ exit 1
78
+ fi
79
+
80
+ frontmatter="$(awk '
81
+ NR == 1 && /^---$/ { in_fm = 1; next }
82
+ in_fm && /^---$/ { exit }
83
+ in_fm { print }
84
+ ' "$FILE")"
85
+
86
+ # Validate each required field is present at top-level (no indentation).
87
+ IFS=',' read -ra FIELDS <<<"$REQUIRED"
88
+ MISSING=()
89
+ for field in "${FIELDS[@]}"; do
90
+ field="${field// /}"
91
+ [[ -z "$field" ]] && continue
92
+ if ! printf '%s\n' "$frontmatter" | grep -qE "^${field}:"; then
93
+ MISSING+=("$field")
94
+ fi
95
+ done
96
+
97
+ if [[ ${#MISSING[@]} -gt 0 ]]; then
98
+ echo "ERROR: $FILE — missing required frontmatter fields: ${MISSING[*]}" >&2
99
+ exit 1
100
+ fi
101
+
102
+ # --mode project-doc: enforce WS-8 — no ephemeral git fields in per-file frontmatter.
103
+ # These must live in draft/metadata.json. Any of the patterns below are violations.
104
+ if [[ "$MODE" == "project-doc" ]]; then
105
+ FORBIDDEN=()
106
+ # Top-level git: block
107
+ if printf '%s\n' "$frontmatter" | grep -qE "^git:"; then
108
+ FORBIDDEN+=("git: (use draft/metadata.json)")
109
+ fi
110
+ # Top-level synced_to_commit:
111
+ if printf '%s\n' "$frontmatter" | grep -qE "^synced_to_commit:"; then
112
+ FORBIDDEN+=("synced_to_commit: (use draft/metadata.json)")
113
+ fi
114
+ # Top-level dirty: (legacy field)
115
+ if printf '%s\n' "$frontmatter" | grep -qE "^dirty:"; then
116
+ FORBIDDEN+=("dirty: (use draft/metadata.json:git.dirty)")
117
+ fi
118
+ if [[ ${#FORBIDDEN[@]} -gt 0 ]]; then
119
+ echo "ERROR: $FILE — forbidden ephemeral fields in project-doc frontmatter (WS-8): ${FORBIDDEN[*]}" >&2
120
+ echo " These fields must live in draft/metadata.json, not in per-file YAML frontmatter." >&2
121
+ exit 1
122
+ fi
123
+ fi
124
+
125
+ exit 0