@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.
- package/.claude-plugin/marketplace.json +38 -0
- package/.claude-plugin/plugin.json +26 -0
- package/LICENSE +21 -0
- package/README.md +272 -0
- package/bin/README.md +49 -0
- package/cli/bin/draft.js +13 -0
- package/cli/src/cli.js +113 -0
- package/cli/src/hosts/claude-code.js +46 -0
- package/cli/src/hosts/codex.js +33 -0
- package/cli/src/hosts/cursor.js +50 -0
- package/cli/src/hosts/index.js +24 -0
- package/cli/src/hosts/opencode.js +39 -0
- package/cli/src/installer.js +61 -0
- package/cli/src/lib/fsx.js +34 -0
- package/cli/src/lib/graph.js +23 -0
- package/cli/src/lib/log.js +32 -0
- package/cli/src/lib/paths.js +14 -0
- package/core/agents/architect.md +338 -0
- package/core/agents/debugger.md +193 -0
- package/core/agents/ops.md +104 -0
- package/core/agents/planner.md +158 -0
- package/core/agents/rca.md +314 -0
- package/core/agents/reviewer.md +256 -0
- package/core/agents/writer.md +110 -0
- package/core/guardrails/README.md +4 -0
- package/core/guardrails/code-quality.md +4 -0
- package/core/guardrails/dependency-triage.md +4 -0
- package/core/guardrails/design-norms.md +4 -0
- package/core/guardrails/language-standards.md +4 -0
- package/core/guardrails/review-checks.md +4 -0
- package/core/guardrails/secure-patterns.md +4 -0
- package/core/guardrails/security.md +4 -0
- package/core/guardrails.md +22 -0
- package/core/knowledge-base.md +127 -0
- package/core/methodology.md +1221 -0
- package/core/shared/condensation.md +224 -0
- package/core/shared/context-verify.md +44 -0
- package/core/shared/cross-skill-dispatch.md +127 -0
- package/core/shared/discovery-schema.md +75 -0
- package/core/shared/draft-context-loading.md +282 -0
- package/core/shared/git-report-metadata.md +106 -0
- package/core/shared/graph-query.md +239 -0
- package/core/shared/graph-usage-report.md +22 -0
- package/core/shared/jira-sync.md +170 -0
- package/core/shared/parallel-analysis.md +386 -0
- package/core/shared/parallel-fanout.md +10 -0
- package/core/shared/pattern-learning.md +146 -0
- package/core/shared/red-flags.md +58 -0
- package/core/shared/template-contract.md +22 -0
- package/core/shared/template-hygiene.md +10 -0
- package/core/shared/tool-resolver.md +10 -0
- package/core/shared/vcs-commands.md +97 -0
- package/core/shared/verification-gates.md +47 -0
- package/core/templates/CHANGELOG.md +70 -0
- package/core/templates/ai-context-export.md +8 -0
- package/core/templates/ai-context.md +270 -0
- package/core/templates/ai-profile.md +41 -0
- package/core/templates/architecture.md +203 -0
- package/core/templates/dependency-graph.md +103 -0
- package/core/templates/discovery.md +79 -0
- package/core/templates/guardrails.md +143 -0
- package/core/templates/hld.md +327 -0
- package/core/templates/intake-questions.md +403 -0
- package/core/templates/jira.md +119 -0
- package/core/templates/lld.md +283 -0
- package/core/templates/metadata.json +66 -0
- package/core/templates/plan.md +130 -0
- package/core/templates/product.md +110 -0
- package/core/templates/rca.md +86 -0
- package/core/templates/root-architecture.md +127 -0
- package/core/templates/root-product.md +53 -0
- package/core/templates/root-tech-stack.md +117 -0
- package/core/templates/service-index.md +55 -0
- package/core/templates/session-summary.md +8 -0
- package/core/templates/spec.md +165 -0
- package/core/templates/tech-matrix.md +101 -0
- package/core/templates/tech-stack.md +169 -0
- package/core/templates/track-architecture.md +311 -0
- package/core/templates/workflow.md +187 -0
- package/integrations/agents/AGENTS.md +24384 -0
- package/integrations/copilot/.github/copilot-instructions.md +24384 -0
- package/integrations/gemini/.gemini.md +26 -0
- package/package.json +53 -0
- package/scripts/fetch-memory-engine.sh +116 -0
- package/scripts/lib.sh +256 -0
- package/scripts/tools/_lib.sh +220 -0
- package/scripts/tools/adr-index.sh +117 -0
- package/scripts/tools/check-graph-usage-report.sh +95 -0
- package/scripts/tools/check-scope-conflicts.sh +139 -0
- package/scripts/tools/check-skill-line-caps.sh +115 -0
- package/scripts/tools/check-template-noop.sh +87 -0
- package/scripts/tools/check-track-hygiene.sh +230 -0
- package/scripts/tools/classify-files.sh +231 -0
- package/scripts/tools/cycle-detect.sh +75 -0
- package/scripts/tools/detect-test-framework.sh +135 -0
- package/scripts/tools/diff-templates-vs-tracks.sh +176 -0
- package/scripts/tools/emit-skill-metrics.sh +71 -0
- package/scripts/tools/fix-whitespace.sh +192 -0
- package/scripts/tools/freshness-check.sh +143 -0
- package/scripts/tools/git-metadata.sh +203 -0
- package/scripts/tools/graph-callers.sh +74 -0
- package/scripts/tools/graph-impact.sh +93 -0
- package/scripts/tools/graph-snapshot.sh +102 -0
- package/scripts/tools/hotspot-rank.sh +75 -0
- package/scripts/tools/manage-symlinks.sh +85 -0
- package/scripts/tools/mermaid-from-graph.sh +92 -0
- package/scripts/tools/migrate-track-frontmatter.sh +241 -0
- package/scripts/tools/parse-git-log.sh +135 -0
- package/scripts/tools/parse-reports.sh +114 -0
- package/scripts/tools/render-track.sh +145 -0
- package/scripts/tools/run-coverage.sh +153 -0
- package/scripts/tools/scan-markers.sh +144 -0
- package/scripts/tools/skill-caps.conf +24 -0
- package/scripts/tools/validate-frontmatter.sh +125 -0
- package/scripts/tools/verify-citations.sh +250 -0
- package/scripts/tools/verify-doc-anchors.sh +204 -0
- package/scripts/tools/verify-graph-binary.sh +154 -0
- package/skills/GRAPH.md +332 -0
- package/skills/adr/SKILL.md +374 -0
- package/skills/assist-review/SKILL.md +49 -0
- package/skills/bughunt/SKILL.md +668 -0
- package/skills/bughunt/references/regression-tests.md +399 -0
- package/skills/change/SKILL.md +267 -0
- package/skills/coverage/SKILL.md +336 -0
- package/skills/debug/SKILL.md +201 -0
- package/skills/decompose/SKILL.md +656 -0
- package/skills/deep-review/SKILL.md +326 -0
- package/skills/deploy-checklist/SKILL.md +254 -0
- package/skills/discover/SKILL.md +66 -0
- package/skills/docs/SKILL.md +42 -0
- package/skills/documentation/SKILL.md +197 -0
- package/skills/draft/SKILL.md +177 -0
- package/skills/draft/context-files.md +57 -0
- package/skills/draft/intent-mapping.md +37 -0
- package/skills/draft/quality-guide.md +51 -0
- package/skills/graph/SKILL.md +107 -0
- package/skills/impact/SKILL.md +86 -0
- package/skills/implement/SKILL.md +794 -0
- package/skills/incident-response/SKILL.md +245 -0
- package/skills/index/SKILL.md +848 -0
- package/skills/init/SKILL.md +1784 -0
- package/skills/init/references/architecture-spec.md +1259 -0
- package/skills/integrations/SKILL.md +53 -0
- package/skills/jira/SKILL.md +577 -0
- package/skills/jira/references/review.md +1322 -0
- package/skills/learn/SKILL.md +478 -0
- package/skills/new-track/SKILL.md +841 -0
- package/skills/ops/SKILL.md +57 -0
- package/skills/plan/SKILL.md +60 -0
- package/skills/quick-review/SKILL.md +216 -0
- package/skills/revert/SKILL.md +178 -0
- package/skills/review/SKILL.md +1114 -0
- package/skills/standup/SKILL.md +183 -0
- package/skills/status/SKILL.md +183 -0
- package/skills/tech-debt/SKILL.md +318 -0
- package/skills/testing-strategy/SKILL.md +195 -0
- package/skills/tour/SKILL.md +38 -0
- package/skills/upload/SKILL.md +117 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# mermaid-from-graph.sh — emit Mermaid diagrams from the knowledge graph.
|
|
3
|
+
#
|
|
4
|
+
# Backed by the codebase-memory-mcp engine. Two diagrams:
|
|
5
|
+
# module-deps : file co-change coupling (FILE_CHANGES_WITH edges) as a flowchart.
|
|
6
|
+
# proto-map : detected service routes (Route nodes) as a flowchart.
|
|
7
|
+
#
|
|
8
|
+
# When the engine is unavailable, emits an empty diagram stub and exits 2 so
|
|
9
|
+
# consuming skills can degrade gracefully.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# scripts/tools/mermaid-from-graph.sh [--repo DIR] [--diagram module-deps|proto-map]
|
|
13
|
+
#
|
|
14
|
+
# Exit codes: 0 OK, 1 invocation error, 2 graph engine/data unavailable.
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
# shellcheck source=_lib.sh
|
|
18
|
+
source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
|
|
19
|
+
|
|
20
|
+
REPO="."
|
|
21
|
+
DIAGRAM="module-deps"
|
|
22
|
+
|
|
23
|
+
usage() {
|
|
24
|
+
cat <<'EOF'
|
|
25
|
+
mermaid-from-graph.sh — emit Mermaid diagrams from the knowledge graph.
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
scripts/tools/mermaid-from-graph.sh [--repo DIR] [--diagram module-deps|proto-map]
|
|
29
|
+
|
|
30
|
+
Flags:
|
|
31
|
+
--repo DIR Repository root (default: cwd).
|
|
32
|
+
--diagram NAME module-deps (default) or proto-map.
|
|
33
|
+
--help Show this help.
|
|
34
|
+
|
|
35
|
+
Exit 0 with diagram output, exit 2 with an empty stub when the engine is unavailable.
|
|
36
|
+
EOF
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
while [[ $# -gt 0 ]]; do
|
|
40
|
+
case "$1" in
|
|
41
|
+
--repo) REPO="$2"; shift 2;;
|
|
42
|
+
--diagram) DIAGRAM="$2"; shift 2;;
|
|
43
|
+
--help|-h) usage; exit 0;;
|
|
44
|
+
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
45
|
+
esac
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
if [[ ! -d "$REPO" ]]; then
|
|
49
|
+
echo "ERROR: --repo '$REPO' is not a directory" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
54
|
+
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
55
|
+
|
|
56
|
+
stub() {
|
|
57
|
+
cat <<'EOF'
|
|
58
|
+
```mermaid
|
|
59
|
+
%% graph data unavailable — index the repo with the graph engine first
|
|
60
|
+
flowchart LR
|
|
61
|
+
empty["graph not built"]
|
|
62
|
+
```
|
|
63
|
+
EOF
|
|
64
|
+
exit 2
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
find_memory_bin "$REPO_ABS" "$SELF_REPO" || stub
|
|
68
|
+
command -v jq >/dev/null 2>&1 || stub
|
|
69
|
+
|
|
70
|
+
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
71
|
+
[[ -n "$PROJECT" ]] || stub
|
|
72
|
+
|
|
73
|
+
render_module_deps() {
|
|
74
|
+
local q="MATCH (a:File)-[r:FILE_CHANGES_WITH]->(b:File) RETURN a.name AS src, b.name AS dst, r.coupling_score AS score ORDER BY r.coupling_score DESC LIMIT 40"
|
|
75
|
+
local res; res="$(memory_cli query_graph "{\"project\":\"$PROJECT\",\"query\":\"$q\"}" || echo '{}')"
|
|
76
|
+
local edges; edges="$(echo "${res:-{\}}" | jq -r '(.rows // [])[] | " \"" + (.[0]|tostring) + "\" --> \"" + (.[1]|tostring) + "\""' 2>/dev/null || true)"
|
|
77
|
+
if [[ -z "$edges" ]]; then return 1; fi
|
|
78
|
+
printf '```mermaid\nflowchart LR\n%s\n```\n' "$edges"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
render_proto_map() {
|
|
82
|
+
local res; res="$(memory_cli get_architecture "{\"project\":\"$PROJECT\",\"aspects\":[\"routes\"]}" || echo '{}')"
|
|
83
|
+
local edges; edges="$(echo "${res:-{\}}" | jq -r '(.routes // [])[] | " \"" + ((.method // "")|tostring) + " " + ((.path // "")|tostring) + "\" --> \"" + ((.handler // "?")|tostring) + "\""' 2>/dev/null || true)"
|
|
84
|
+
if [[ -z "$edges" ]]; then return 1; fi
|
|
85
|
+
printf '```mermaid\nflowchart LR\n%s\n```\n' "$edges"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
case "$DIAGRAM" in
|
|
89
|
+
module-deps) render_module_deps || stub ;;
|
|
90
|
+
proto-map) render_proto_map || stub ;;
|
|
91
|
+
*) echo "Unknown --diagram '$DIAGRAM' (expected module-deps|proto-map)" >&2; exit 1 ;;
|
|
92
|
+
esac
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# migrate-track-frontmatter.sh
|
|
3
|
+
#
|
|
4
|
+
# Idempotent rewriter that migrates a pre-2.0 Draft track to the WS-8
|
|
5
|
+
# metadata-as-source-of-truth shape:
|
|
6
|
+
#
|
|
7
|
+
# - Strips ephemeral fields from per-file YAML frontmatter in spec.md,
|
|
8
|
+
# hld.md, lld.md, plan.md: `git.*`, `synced_to_commit`, `classification.*`,
|
|
9
|
+
# `status`, `scope_includes`, `scope_excludes`.
|
|
10
|
+
# - Promotes them into metadata.json (creating fields if absent; preserving
|
|
11
|
+
# existing values).
|
|
12
|
+
# - Markdown frontmatter retains only: project, module, track_id,
|
|
13
|
+
# generated_by, generated_at, links.
|
|
14
|
+
#
|
|
15
|
+
# Idempotent: re-running on a 2.0 track produces zero diff.
|
|
16
|
+
# Safe: emits <file>.bak alongside each rewritten file unless --no-backup.
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# scripts/tools/migrate-track-frontmatter.sh tracks/foo
|
|
20
|
+
# scripts/tools/migrate-track-frontmatter.sh --dry-run tracks/foo
|
|
21
|
+
# scripts/tools/migrate-track-frontmatter.sh --no-backup tracks/foo
|
|
22
|
+
#
|
|
23
|
+
# Exit codes:
|
|
24
|
+
# 0 success or no-op
|
|
25
|
+
# 1 migration error
|
|
26
|
+
# 2 usage / runtime error
|
|
27
|
+
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
|
|
30
|
+
if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
|
|
31
|
+
echo "${0##*/} — Foundations quality tool (see core/ docs for full behavior)"
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
36
|
+
# shellcheck source=/dev/null
|
|
37
|
+
source "$SCRIPT_DIR/_lib.sh"
|
|
38
|
+
|
|
39
|
+
DRY_RUN=0
|
|
40
|
+
BACKUP=1
|
|
41
|
+
TRACK_DIRS=()
|
|
42
|
+
|
|
43
|
+
usage() {
|
|
44
|
+
local stream=2 code=2
|
|
45
|
+
if [[ "${USAGE_HELP_MODE:-0}" == 1 ]]; then stream=1; code=0; fi
|
|
46
|
+
sed -n '2,22p' "$0" >&$stream
|
|
47
|
+
exit "$code"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
while (($#)); do
|
|
51
|
+
case "$1" in
|
|
52
|
+
-h|--help) USAGE_HELP_MODE=1 usage ;;
|
|
53
|
+
--dry-run) DRY_RUN=1; shift ;;
|
|
54
|
+
--no-backup) BACKUP=0; shift ;;
|
|
55
|
+
-*) printf 'Unknown flag: %s\n' "$1" >&2; usage ;;
|
|
56
|
+
*) TRACK_DIRS+=("$1"); shift ;;
|
|
57
|
+
esac
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
(( ${#TRACK_DIRS[@]} == 0 )) && usage
|
|
61
|
+
|
|
62
|
+
# Ephemeral keys to strip from per-file YAML frontmatter.
|
|
63
|
+
EPHEMERAL_KEYS=(
|
|
64
|
+
"git"
|
|
65
|
+
"synced_to_commit"
|
|
66
|
+
"classification"
|
|
67
|
+
"status"
|
|
68
|
+
"scope_includes"
|
|
69
|
+
"scope_excludes"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Stable keys that survive in markdown frontmatter.
|
|
73
|
+
STABLE_KEYS=(
|
|
74
|
+
"project"
|
|
75
|
+
"module"
|
|
76
|
+
"track_id"
|
|
77
|
+
"generated_by"
|
|
78
|
+
"generated_at"
|
|
79
|
+
"links"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Strip ephemeral blocks from a markdown file's YAML frontmatter.
|
|
83
|
+
# Approach: read the file; rewrite the frontmatter section so any line whose
|
|
84
|
+
# first token (before colon) matches an ephemeral key — plus any indented
|
|
85
|
+
# continuation lines — is dropped.
|
|
86
|
+
strip_frontmatter() {
|
|
87
|
+
local file="$1"
|
|
88
|
+
awk -v ephemeral_re="^($(IFS='|'; echo "${EPHEMERAL_KEYS[*]}"))(:|[[:space:]])" '
|
|
89
|
+
BEGIN { state = "before"; skip = 0 }
|
|
90
|
+
state == "before" {
|
|
91
|
+
print
|
|
92
|
+
if ($0 == "---") state = "in_fm"
|
|
93
|
+
next
|
|
94
|
+
}
|
|
95
|
+
state == "in_fm" {
|
|
96
|
+
if ($0 == "---") { state = "after"; print; skip = 0; next }
|
|
97
|
+
# Detect a top-level ephemeral key: starts at column 1.
|
|
98
|
+
if ($0 ~ ephemeral_re && $0 !~ /^[[:space:]]/) {
|
|
99
|
+
skip = 1
|
|
100
|
+
next
|
|
101
|
+
}
|
|
102
|
+
# Indented continuation lines belong to the previous block.
|
|
103
|
+
if (skip == 1 && $0 ~ /^[[:space:]]/) { next }
|
|
104
|
+
# Non-indented line — end of any previous ephemeral block.
|
|
105
|
+
skip = 0
|
|
106
|
+
print
|
|
107
|
+
next
|
|
108
|
+
}
|
|
109
|
+
state == "after" { print }
|
|
110
|
+
' "$file"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Promote a JSON field into metadata.json at the top level if it does not
|
|
114
|
+
# already exist. Prefers Python for robust JSON manipulation; falls back to
|
|
115
|
+
# awk for environments without Python.
|
|
116
|
+
#
|
|
117
|
+
# The awk fallback inserts before the FIRST line that is a bare "}" with no
|
|
118
|
+
# leading whitespace, which is the outer object's close. The previous
|
|
119
|
+
# implementation matched any "}" line and risked inserting fields into
|
|
120
|
+
# nested objects (e.g. inside `impact`).
|
|
121
|
+
ensure_meta_field() {
|
|
122
|
+
local meta="$1" key="$2" default_value="$3"
|
|
123
|
+
if grep -Eq "^[[:space:]]*\"$key\"[[:space:]]*:" "$meta"; then
|
|
124
|
+
return 0
|
|
125
|
+
fi
|
|
126
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
127
|
+
python3 - "$meta" "$key" "$default_value" <<'PY'
|
|
128
|
+
import json, sys, ast, tempfile, os
|
|
129
|
+
path, key, raw = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
130
|
+
with open(path) as f:
|
|
131
|
+
data = json.load(f)
|
|
132
|
+
if key in data:
|
|
133
|
+
sys.exit(0)
|
|
134
|
+
# Parse the default value: try JSON first (handles [], {}, true, numbers,
|
|
135
|
+
# quoted strings); fall back to literal string on failure.
|
|
136
|
+
try:
|
|
137
|
+
value = json.loads(raw)
|
|
138
|
+
except Exception:
|
|
139
|
+
value = raw.strip('"')
|
|
140
|
+
data[key] = value
|
|
141
|
+
fd, tmp = tempfile.mkstemp(dir=os.path.dirname(path) or ".")
|
|
142
|
+
with os.fdopen(fd, "w") as f:
|
|
143
|
+
json.dump(data, f, indent=2)
|
|
144
|
+
f.write("\n")
|
|
145
|
+
os.replace(tmp, path)
|
|
146
|
+
PY
|
|
147
|
+
else
|
|
148
|
+
# awk fallback: insert before the LAST line that is a closing `}` at
|
|
149
|
+
# column 1. That is the outer object's closing brace.
|
|
150
|
+
awk -v key="$key" -v val="$default_value" '
|
|
151
|
+
{ lines[NR] = $0 }
|
|
152
|
+
END {
|
|
153
|
+
# Find last bare-} line.
|
|
154
|
+
last_brace = 0
|
|
155
|
+
for (i = NR; i > 0; i--) {
|
|
156
|
+
if (lines[i] == "}") { last_brace = i; break }
|
|
157
|
+
}
|
|
158
|
+
for (i = 1; i <= NR; i++) {
|
|
159
|
+
if (i == last_brace) {
|
|
160
|
+
# Inject before the close.
|
|
161
|
+
# Ensure previous content line ends with a comma.
|
|
162
|
+
if (i > 1) {
|
|
163
|
+
prev = lines[i - 1]
|
|
164
|
+
sub(/[[:space:]]*$/, "", prev)
|
|
165
|
+
if (prev !~ /[,{[]$/) prev = prev ","
|
|
166
|
+
lines[i - 1] = prev
|
|
167
|
+
}
|
|
168
|
+
print " \"" key "\": " val
|
|
169
|
+
}
|
|
170
|
+
print lines[i]
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
' "$meta" > "${meta}.tmp" && mv "${meta}.tmp" "$meta"
|
|
174
|
+
fi
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
migrate_one_track() {
|
|
178
|
+
local track_dir="$1"
|
|
179
|
+
if [[ ! -d "$track_dir" ]]; then
|
|
180
|
+
printf 'migrate: not a directory: %s\n' "$track_dir" >&2
|
|
181
|
+
return 1
|
|
182
|
+
fi
|
|
183
|
+
track_dir="$(cd "$track_dir" && pwd)"
|
|
184
|
+
|
|
185
|
+
local meta="$track_dir/metadata.json"
|
|
186
|
+
if [[ ! -f "$meta" ]]; then
|
|
187
|
+
printf 'migrate: %s has no metadata.json — creating minimal\n' "$track_dir" >&2
|
|
188
|
+
if (( ! DRY_RUN )); then
|
|
189
|
+
cat > "$meta" <<EOF
|
|
190
|
+
{
|
|
191
|
+
"id": "$(basename "$track_dir")",
|
|
192
|
+
"title": "_TBD_title_",
|
|
193
|
+
"type": "feature",
|
|
194
|
+
"status": "draft",
|
|
195
|
+
"template_version": "2.0.0",
|
|
196
|
+
"created": "_TBD_created_",
|
|
197
|
+
"updated": "_TBD_updated_",
|
|
198
|
+
"scope_includes": [],
|
|
199
|
+
"scope_excludes": [],
|
|
200
|
+
"phases": { "total": 0, "completed": 0 },
|
|
201
|
+
"tasks": { "total": 0, "completed": 0 }
|
|
202
|
+
}
|
|
203
|
+
EOF
|
|
204
|
+
fi
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
if (( ! DRY_RUN )); then
|
|
208
|
+
ensure_meta_field "$meta" "template_version" '"2.0.0"'
|
|
209
|
+
ensure_meta_field "$meta" "scope_includes" '[]'
|
|
210
|
+
ensure_meta_field "$meta" "scope_excludes" '[]'
|
|
211
|
+
ensure_meta_field "$meta" "pre_deploy_status" '"unrun"'
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
local changed=0
|
|
215
|
+
for f in spec.md hld.md lld.md plan.md discovery.md; do
|
|
216
|
+
local path="$track_dir/$f"
|
|
217
|
+
[[ -f "$path" ]] || continue
|
|
218
|
+
local before; before="$(cat "$path")"
|
|
219
|
+
local after; after="$(strip_frontmatter "$path")"
|
|
220
|
+
if [[ "$before" != "$after" ]]; then
|
|
221
|
+
changed=1
|
|
222
|
+
if (( DRY_RUN )); then
|
|
223
|
+
printf 'migrate: would strip ephemeral frontmatter from %s\n' "$path"
|
|
224
|
+
else
|
|
225
|
+
(( BACKUP )) && cp "$path" "$path.bak"
|
|
226
|
+
local _tmp; _tmp="$(mktemp "${path}.XXXXXX")"; printf '%s' "$after" > "$_tmp" && mv -f "$_tmp" "$path"
|
|
227
|
+
printf 'migrate: stripped ephemeral frontmatter from %s\n' "$path"
|
|
228
|
+
fi
|
|
229
|
+
fi
|
|
230
|
+
done
|
|
231
|
+
|
|
232
|
+
if (( changed == 0 )); then
|
|
233
|
+
printf 'migrate: %s already at 2.0 — no-op\n' "$track_dir"
|
|
234
|
+
fi
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
rc=0
|
|
238
|
+
for t in "${TRACK_DIRS[@]}"; do
|
|
239
|
+
migrate_one_track "$t" || rc=$?
|
|
240
|
+
done
|
|
241
|
+
exit "$rc"
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# parse-git-log.sh — parse conventional commits into structured JSONL.
|
|
3
|
+
#
|
|
4
|
+
# Output one JSON object per commit:
|
|
5
|
+
# {sha, type, scope, track_id, subject, author, timestamp, files_changed}
|
|
6
|
+
#
|
|
7
|
+
# Conventional commit subject: "type(scope): subject"
|
|
8
|
+
# type may end in "!" to denote breaking change.
|
|
9
|
+
#
|
|
10
|
+
# track_id:
|
|
11
|
+
# - extracted from the scope if it matches --scope-pattern (default none)
|
|
12
|
+
# - OR from the subject if a literal `[TRACK-123]` / `(TRACK-123)` appears
|
|
13
|
+
# - else null
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# scripts/tools/parse-git-log.sh [--since RANGE] [--limit N]
|
|
17
|
+
# [--scope-pattern REGEX] [--branch REF]
|
|
18
|
+
#
|
|
19
|
+
# Exit codes: 0 OK, 1 invocation error.
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
# shellcheck source=_lib.sh
|
|
23
|
+
source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
|
|
24
|
+
|
|
25
|
+
SINCE=""
|
|
26
|
+
LIMIT=""
|
|
27
|
+
SCOPE_PATTERN=""
|
|
28
|
+
BRANCH="HEAD"
|
|
29
|
+
|
|
30
|
+
usage() {
|
|
31
|
+
cat <<'EOF'
|
|
32
|
+
parse-git-log.sh — parse conventional commits into JSONL.
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
scripts/tools/parse-git-log.sh [--since RANGE] [--limit N]
|
|
36
|
+
[--scope-pattern REGEX] [--branch REF]
|
|
37
|
+
|
|
38
|
+
Flags:
|
|
39
|
+
--since RANGE Passed to git log --since (e.g. "7d", "2 weeks ago").
|
|
40
|
+
--limit N Max number of commits (git log -n N).
|
|
41
|
+
--scope-pattern RE Extended regex; if a commit's scope matches, it becomes the track_id.
|
|
42
|
+
--branch REF Branch/ref to inspect (default: HEAD).
|
|
43
|
+
--help Show this help.
|
|
44
|
+
|
|
45
|
+
Output: JSONL with one record per commit.
|
|
46
|
+
Fields: sha, type, scope, track_id, subject, author, timestamp, files_changed
|
|
47
|
+
EOF
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
while [[ $# -gt 0 ]]; do
|
|
51
|
+
case "$1" in
|
|
52
|
+
--since) SINCE="$2"; shift 2;;
|
|
53
|
+
--limit) LIMIT="$2"; shift 2;;
|
|
54
|
+
--scope-pattern) SCOPE_PATTERN="$2"; shift 2;;
|
|
55
|
+
--branch) BRANCH="$2"; shift 2;;
|
|
56
|
+
--help|-h) usage; exit 0;;
|
|
57
|
+
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
58
|
+
esac
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
62
|
+
echo "ERROR: not inside a git repository" >&2
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# Format: delimiter-separated metadata line, then --name-only file list, blank line separator.
|
|
67
|
+
GIT_ARGS=(log --pretty=tformat:'COMMIT%x1f%H%x1f%an%x1f%aI%x1f%s' --name-only --no-merges)
|
|
68
|
+
[[ -n "$SINCE" ]] && GIT_ARGS+=(--since="$SINCE")
|
|
69
|
+
[[ -n "$LIMIT" ]] && GIT_ARGS+=(-n "$LIMIT")
|
|
70
|
+
GIT_ARGS+=("$BRANCH")
|
|
71
|
+
|
|
72
|
+
# Read a commit block (metadata line + files) and emit one JSON record.
|
|
73
|
+
process_commit() {
|
|
74
|
+
local sha="$1" author="$2" ts="$3" subject="$4" files_changed="$5"
|
|
75
|
+
local type scope breaking clean_subject cc_re track_id token inner scope_val
|
|
76
|
+
[[ -z "$sha" ]] && return
|
|
77
|
+
|
|
78
|
+
# Parse conventional commit: type(scope)!: subject OR type: subject
|
|
79
|
+
type="null"
|
|
80
|
+
scope="null"
|
|
81
|
+
breaking="false"
|
|
82
|
+
clean_subject="$subject"
|
|
83
|
+
cc_re='^([a-zA-Z]+)(\(([^)]+)\))?(!)?: (.+)$'
|
|
84
|
+
if [[ "$subject" =~ $cc_re ]]; then
|
|
85
|
+
type="\"${BASH_REMATCH[1]}\""
|
|
86
|
+
if [[ -n "${BASH_REMATCH[3]:-}" ]]; then
|
|
87
|
+
scope="\"$(json_escape "${BASH_REMATCH[3]}")\""
|
|
88
|
+
fi
|
|
89
|
+
[[ -n "${BASH_REMATCH[4]:-}" ]] && breaking="true"
|
|
90
|
+
clean_subject="${BASH_REMATCH[5]}"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Track ID detection
|
|
94
|
+
track_id="null"
|
|
95
|
+
if [[ -n "$SCOPE_PATTERN" && "$scope" != "null" ]]; then
|
|
96
|
+
scope_val="${scope#?}"
|
|
97
|
+
scope_val="${scope_val%?}" # strip surrounding quotes
|
|
98
|
+
if [[ "$scope_val" =~ $SCOPE_PATTERN ]]; then
|
|
99
|
+
track_id="\"$(json_escape "$scope_val")\""
|
|
100
|
+
fi
|
|
101
|
+
fi
|
|
102
|
+
# Look for [TRACK-XXX] or (TRACK-XXX) tokens in subject
|
|
103
|
+
if [[ "$track_id" == "null" ]]; then
|
|
104
|
+
token="$(printf '%s' "$clean_subject" | grep -oE '(\[[A-Z]+-[0-9]+\]|\([A-Z]+-[0-9]+\))' | head -1 || true)"
|
|
105
|
+
if [[ -n "$token" ]]; then
|
|
106
|
+
inner="${token#?}"
|
|
107
|
+
inner="${inner%?}"
|
|
108
|
+
track_id="\"$(json_escape "$inner")\""
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
printf '{"sha":"%s","type":%s,"scope":%s,"breaking":%s,"track_id":%s,"subject":"%s","author":"%s","timestamp":"%s","files_changed":%s}\n' \
|
|
113
|
+
"$sha" "$type" "$scope" "$breaking" "$track_id" \
|
|
114
|
+
"$(json_escape "$clean_subject")" \
|
|
115
|
+
"$(json_escape "$author")" \
|
|
116
|
+
"$ts" "$files_changed"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Single git log stream: each commit is `COMMIT<US>sha<US>author<US>ts<US>subject`
|
|
120
|
+
# followed by its file paths (one per line) and a blank separator.
|
|
121
|
+
cur_sha=""; cur_author=""; cur_ts=""; cur_subject=""; cur_files=0
|
|
122
|
+
while IFS= read -r line; do
|
|
123
|
+
if [[ "$line" == COMMIT$'\x1f'* ]]; then
|
|
124
|
+
if [[ -n "$cur_sha" ]]; then
|
|
125
|
+
process_commit "$cur_sha" "$cur_author" "$cur_ts" "$cur_subject" "$cur_files"
|
|
126
|
+
fi
|
|
127
|
+
IFS=$'\x1f' read -r _ cur_sha cur_author cur_ts cur_subject <<<"$line"
|
|
128
|
+
cur_files=0
|
|
129
|
+
elif [[ -n "$line" ]]; then
|
|
130
|
+
cur_files=$((cur_files + 1))
|
|
131
|
+
fi
|
|
132
|
+
done < <(git "${GIT_ARGS[@]}")
|
|
133
|
+
if [[ -n "$cur_sha" ]]; then
|
|
134
|
+
process_commit "$cur_sha" "$cur_author" "$cur_ts" "$cur_subject" "$cur_files"
|
|
135
|
+
fi
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# parse-reports.sh — parse Draft reports (bughunt/review/tech-debt/...) and emit a structured summary.
|
|
3
|
+
#
|
|
4
|
+
# For each `*-report-*.md` under --root, extract YAML frontmatter fields and
|
|
5
|
+
# count severity markers in the report body.
|
|
6
|
+
#
|
|
7
|
+
# Output: JSON array of records:
|
|
8
|
+
# {path, report_type, track_id, generated_at, severity:{critical,high,medium,low,info}}
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# scripts/tools/parse-reports.sh [--root DIR]
|
|
12
|
+
#
|
|
13
|
+
# Exit codes: 0 OK (even if no reports), 1 invocation error.
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
# shellcheck source=_lib.sh
|
|
17
|
+
source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
|
|
18
|
+
|
|
19
|
+
ROOT="."
|
|
20
|
+
|
|
21
|
+
usage() {
|
|
22
|
+
cat <<'EOF'
|
|
23
|
+
parse-reports.sh — summarize Draft reports under a directory.
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
scripts/tools/parse-reports.sh [--root DIR]
|
|
27
|
+
|
|
28
|
+
Flags:
|
|
29
|
+
--root DIR Directory to scan for *-report-*.md (default: cwd).
|
|
30
|
+
--help Show this help.
|
|
31
|
+
|
|
32
|
+
Output: JSON array of {path, report_type, track_id, generated_at, severity}.
|
|
33
|
+
EOF
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
while [[ $# -gt 0 ]]; do
|
|
37
|
+
case "$1" in
|
|
38
|
+
--root) ROOT="$2"; shift 2;;
|
|
39
|
+
--help|-h) usage; exit 0;;
|
|
40
|
+
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
41
|
+
esac
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
if [[ ! -d "$ROOT" ]]; then
|
|
45
|
+
echo "ERROR: --root '$ROOT' is not a directory" >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Extract YAML frontmatter fields + severity counts in a single awk pass over each file.
|
|
50
|
+
# Emits tab-separated: track_id<TAB>generated_at<TAB>critical<TAB>high<TAB>medium<TAB>low<TAB>info
|
|
51
|
+
parse_report_fields() {
|
|
52
|
+
local file="$1"
|
|
53
|
+
awk '
|
|
54
|
+
BEGIN { in_fm = 0; past_fm = 0 }
|
|
55
|
+
NR == 1 && /^---$/ { in_fm = 1; next }
|
|
56
|
+
in_fm && /^---$/ { in_fm = 0; past_fm = 1; next }
|
|
57
|
+
in_fm {
|
|
58
|
+
if ($0 ~ /^track_id:[[:space:]]*/) {
|
|
59
|
+
v = $0; sub(/^track_id:[[:space:]]*/, "", v)
|
|
60
|
+
if (v ~ /^".*"$/) { v = substr(v, 2, length(v)-2) }
|
|
61
|
+
sub(/[[:space:]]+$/, "", v)
|
|
62
|
+
track_id = v
|
|
63
|
+
} else if ($0 ~ /^generated_at:[[:space:]]*/) {
|
|
64
|
+
v = $0; sub(/^generated_at:[[:space:]]*/, "", v)
|
|
65
|
+
if (v ~ /^".*"$/) { v = substr(v, 2, length(v)-2) }
|
|
66
|
+
sub(/[[:space:]]+$/, "", v)
|
|
67
|
+
generated_at = v
|
|
68
|
+
}
|
|
69
|
+
next
|
|
70
|
+
}
|
|
71
|
+
past_fm {
|
|
72
|
+
# Lowercase copy for severity detection.
|
|
73
|
+
l = tolower($0)
|
|
74
|
+
if (l ~ /(^|[^a-z])(severity:[[:space:]]*critical|\|[[:space:]]*critical[[:space:]]*\||^-[[:space:]]+critical:)/) crit++
|
|
75
|
+
if (l ~ /(^|[^a-z])(severity:[[:space:]]*high|\|[[:space:]]*high[[:space:]]*\||^-[[:space:]]+high:)/) high++
|
|
76
|
+
if (l ~ /(^|[^a-z])(severity:[[:space:]]*medium|\|[[:space:]]*medium[[:space:]]*\||^-[[:space:]]+medium:)/) med++
|
|
77
|
+
if (l ~ /(^|[^a-z])(severity:[[:space:]]*low|\|[[:space:]]*low[[:space:]]*\||^-[[:space:]]+low:)/) low++
|
|
78
|
+
if (l ~ /(^|[^a-z])(severity:[[:space:]]*info|\|[[:space:]]*info[[:space:]]*\||^-[[:space:]]+info:)/) info++
|
|
79
|
+
}
|
|
80
|
+
END {
|
|
81
|
+
printf "%s\t%s\t%d\t%d\t%d\t%d\t%d", track_id, generated_at, crit+0, high+0, med+0, low+0, info+0
|
|
82
|
+
}
|
|
83
|
+
' "$file"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
first=true
|
|
87
|
+
printf '['
|
|
88
|
+
while IFS= read -r -d '' file; do
|
|
89
|
+
base="$(basename "$file")"
|
|
90
|
+
report_type=""
|
|
91
|
+
if [[ "$base" =~ ^([a-z][a-z0-9-]+)-report- ]]; then
|
|
92
|
+
report_type="${BASH_REMATCH[1]}"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
fields="$(parse_report_fields "$file")"
|
|
96
|
+
IFS=$'\t' read -r track_id generated_at crit high med low info <<<"$fields" || true
|
|
97
|
+
[[ "$track_id" == "null" ]] && track_id=""
|
|
98
|
+
|
|
99
|
+
rel="${file#"$ROOT/"}"
|
|
100
|
+
|
|
101
|
+
if $first; then first=false; else printf ','; fi
|
|
102
|
+
printf '\n {"path":"%s","report_type":"%s","track_id":%s,"generated_at":"%s","severity":{"critical":%s,"high":%s,"medium":%s,"low":%s,"info":%s}}' \
|
|
103
|
+
"$(json_escape "$rel")" \
|
|
104
|
+
"$(json_escape "$report_type")" \
|
|
105
|
+
"$([[ -n "$track_id" ]] && echo "\"$(json_escape "$track_id")\"" || echo "null")" \
|
|
106
|
+
"$(json_escape "$generated_at")" \
|
|
107
|
+
"${crit:-0}" "${high:-0}" "${med:-0}" "${low:-0}" "${info:-0}"
|
|
108
|
+
done < <(find "$ROOT" -type f -name '*-report-*.md' -print0 2>/dev/null | sort -z)
|
|
109
|
+
|
|
110
|
+
if $first; then
|
|
111
|
+
printf ']\n'
|
|
112
|
+
else
|
|
113
|
+
printf '\n]\n'
|
|
114
|
+
fi
|