@friedbotstudio/create-baseline 0.3.0 → 0.4.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/README.md +10 -4
- package/bin/cli.js +197 -119
- package/obj/template/.claude/skills/changelog/SKILL.md +69 -0
- package/obj/template/.claude/skills/changelog/changelog.mjs +163 -0
- package/obj/template/.claude/skills/changelog/classifier.mjs +49 -0
- package/obj/template/.claude/skills/changelog/state-writer.mjs +19 -0
- package/obj/template/.claude/skills/changelog/tests/consent-expired_test.sh +126 -0
- package/obj/template/.claude/skills/changelog/tests/golden-path_test.sh +191 -0
- package/obj/template/.claude/skills/changelog/tests/idempotent-reentry_test.sh +121 -0
- package/obj/template/.claude/skills/changelog/tests/keepachangelog-unreleased-preserved_test.mjs +149 -0
- package/obj/template/.claude/skills/changelog/tests/non-git-shortcircuit_test.sh +98 -0
- package/obj/template/.claude/skills/changelog/tests/preview-only_test.sh +96 -0
- package/obj/template/.claude/skills/changelog/tests/run.sh +28 -0
- package/obj/template/.claude/skills/changelog/unreleased-writer.mjs +155 -0
- package/obj/template/.claude/skills/changelog/version-preview.mjs +124 -0
- package/obj/template/.claude/skills/commit/SKILL.md +1 -1
- package/obj/template/.claude/skills/harness/SKILL.md +3 -1
- package/obj/template/.claude/skills/triage/SKILL.md +6 -5
- package/obj/template/CLAUDE.md +2 -2
- package/obj/template/docs/init/seed.md +4 -4
- package/obj/template/manifest.json +21 -7
- package/package.json +5 -2
- package/src/CLAUDE.template.md +2 -2
- package/src/cli/merge.js +15 -10
- package/src/cli/tui/doctor.js +56 -0
- package/src/cli/tui/install.js +79 -0
- package/src/cli/tui/meta.js +30 -0
- package/src/cli/tui/tokens.js +38 -0
- package/src/cli/tui/upgrade.js +100 -0
- package/src/seed.template.md +4 -4
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Fixture-based test for AC-003: when a project is not a git repository,
|
|
3
|
+
# /triage SHALL auto-except changelog alongside commit and the swarm-* phases,
|
|
4
|
+
# AND no Run /changelog task SHALL be seeded.
|
|
5
|
+
#
|
|
6
|
+
# Static-analysis test: reads the LIVE .claude/skills/triage/SKILL.md content
|
|
7
|
+
# (which the implement worker edits) and asserts on its content. Until the
|
|
8
|
+
# implement worker updates triage SKILL.md, this test fails RED.
|
|
9
|
+
|
|
10
|
+
set -uo pipefail
|
|
11
|
+
|
|
12
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
13
|
+
REPO_ROOT="$(cd "$HERE/../../../.." && pwd)"
|
|
14
|
+
TRIAGE_SKILL="$REPO_ROOT/.claude/skills/triage/SKILL.md"
|
|
15
|
+
|
|
16
|
+
PASS=0; FAIL=0; FAILED=()
|
|
17
|
+
|
|
18
|
+
fail() { echo " FAIL: $*"; return 1; }
|
|
19
|
+
|
|
20
|
+
assert_file_contains() {
|
|
21
|
+
local path="$1" needle="$2" msg="$3"
|
|
22
|
+
if grep -qF "$needle" "$path" 2>/dev/null; then return 0; fi
|
|
23
|
+
fail "$msg :: file $path missing: $needle"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
assert_file_matches() {
|
|
27
|
+
local path="$1" pattern="$2" msg="$3"
|
|
28
|
+
if grep -qE "$pattern" "$path" 2>/dev/null; then return 0; fi
|
|
29
|
+
fail "$msg :: file $path missing pattern: $pattern"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
run() {
|
|
33
|
+
local name="$1"
|
|
34
|
+
echo "RUN $name"
|
|
35
|
+
if "$name"; then
|
|
36
|
+
PASS=$((PASS+1)); echo "PASS $name"
|
|
37
|
+
else
|
|
38
|
+
FAIL=$((FAIL+1)); FAILED+=("$name"); echo "FAIL $name"
|
|
39
|
+
fi
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# --- AC-003 — static-analysis on triage SKILL.md -----------------------------
|
|
43
|
+
|
|
44
|
+
test_when_triage_skill_md_describes_nongit_then_changelog_in_exceptions_list() {
|
|
45
|
+
[ -f "$TRIAGE_SKILL" ] || { fail "AC-003 triage/SKILL.md missing"; return 1; }
|
|
46
|
+
# The non-git auto-except list MUST mention 'changelog' alongside the
|
|
47
|
+
# existing 'swarm-plan', 'approve-swarm', 'swarm-dispatch', 'grant-commit',
|
|
48
|
+
# 'commit' entries. The exact phrasing may vary; assert presence of the
|
|
49
|
+
# changelog token within reasonable proximity of the other tokens.
|
|
50
|
+
if ! grep -qE '"swarm-plan".*"swarm-dispatch".*"commit"' "$TRIAGE_SKILL"; then
|
|
51
|
+
fail "AC-003 baseline non-git auto-except list not found in triage SKILL.md"
|
|
52
|
+
return 1
|
|
53
|
+
fi
|
|
54
|
+
assert_file_contains "$TRIAGE_SKILL" '"changelog"' \
|
|
55
|
+
"AC-003 triage SKILL.md non-git auto-except list must include \"changelog\"" \
|
|
56
|
+
|| return 1
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
test_when_triage_task_templates_include_changelog_row_between_grant_commit_and_commit() {
|
|
60
|
+
[ -f "$TRIAGE_SKILL" ] || { fail "AC-003 triage/SKILL.md missing"; return 1; }
|
|
61
|
+
# Each non-chore template (tdd-entry, spec-entry, intake-entry) AND the
|
|
62
|
+
# chore template SHOULD include a Run /changelog task row between
|
|
63
|
+
# "Wait for /grant-commit" and "Run /commit". The exact prose varies but the
|
|
64
|
+
# ordering must hold.
|
|
65
|
+
# Strategy: extract the prose between "Wait for /grant-commit" and "Run /commit"
|
|
66
|
+
# blocks and require "changelog" to appear within that slice.
|
|
67
|
+
if ! python3 - "$TRIAGE_SKILL" <<'PY'
|
|
68
|
+
import re, sys
|
|
69
|
+
path = sys.argv[1]
|
|
70
|
+
text = open(path).read()
|
|
71
|
+
# Find every occurrence of "Wait for /grant-commit" followed by content up to "Run /commit"
|
|
72
|
+
matches = list(re.finditer(
|
|
73
|
+
r'Wait for /grant-commit[\s\S]*?Run /commit', text))
|
|
74
|
+
if not matches:
|
|
75
|
+
sys.exit('no "Wait for /grant-commit" → "Run /commit" sequence found in triage SKILL.md')
|
|
76
|
+
# Every such slice must mention "changelog".
|
|
77
|
+
missing = [i for i, m in enumerate(matches) if 'changelog' not in m.group(0).lower()]
|
|
78
|
+
if missing:
|
|
79
|
+
sys.exit(f'{len(missing)} task-seeding slice(s) missing changelog: indices {missing}')
|
|
80
|
+
PY
|
|
81
|
+
then
|
|
82
|
+
fail "AC-003 triage SKILL.md task-seeding templates must include changelog between grant-commit and commit"
|
|
83
|
+
return 1
|
|
84
|
+
fi
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# --- runner -------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
run test_when_triage_skill_md_describes_nongit_then_changelog_in_exceptions_list
|
|
90
|
+
run test_when_triage_task_templates_include_changelog_row_between_grant_commit_and_commit
|
|
91
|
+
|
|
92
|
+
echo "----"
|
|
93
|
+
echo "Passed: $PASS Failed: $FAIL"
|
|
94
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
95
|
+
echo "Failed tests:"
|
|
96
|
+
for t in "${FAILED[@]}"; do echo " - $t"; done
|
|
97
|
+
fi
|
|
98
|
+
exit $((FAIL > 0))
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Fixture-based integration test for AC-012: the changelog actuator's
|
|
3
|
+
# --preview-only mode prints projected next semver + draft fragment to stdout,
|
|
4
|
+
# requires no commit_consent gesture, and writes no files.
|
|
5
|
+
#
|
|
6
|
+
# Pre-implement RED: actuator does not exist; test fails on missing file.
|
|
7
|
+
|
|
8
|
+
set -uo pipefail
|
|
9
|
+
|
|
10
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
11
|
+
REPO_ROOT="$(cd "$HERE/../../../.." && pwd)"
|
|
12
|
+
ACTUATOR="$REPO_ROOT/.claude/skills/changelog/changelog.mjs"
|
|
13
|
+
|
|
14
|
+
PASS=0; FAIL=0; FAILED=()
|
|
15
|
+
|
|
16
|
+
fail() { echo " FAIL: $*"; return 1; }
|
|
17
|
+
|
|
18
|
+
# Build a tempdir project WITHOUT commit_consent (preview must work without it).
|
|
19
|
+
seed_preview_project() {
|
|
20
|
+
local proj="$1"
|
|
21
|
+
mkdir -p "$proj/.claude/state"
|
|
22
|
+
cd "$proj"
|
|
23
|
+
git init -q
|
|
24
|
+
git config user.email "test@example.com"
|
|
25
|
+
git config user.name "Test"
|
|
26
|
+
git commit --allow-empty -q -m "chore: initial"
|
|
27
|
+
git tag v0.1.0
|
|
28
|
+
echo "preview" > thing.txt
|
|
29
|
+
git add thing.txt
|
|
30
|
+
git commit -q -m "feat: preview path"
|
|
31
|
+
# NO commit_consent file.
|
|
32
|
+
# NO workflow.json — preview must not require one.
|
|
33
|
+
cat > "$proj/.releaserc.json" <<'EOF'
|
|
34
|
+
{
|
|
35
|
+
"branches": ["main"],
|
|
36
|
+
"plugins": [
|
|
37
|
+
["@semantic-release/commit-analyzer", { "preset": "angular" }]
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
EOF
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
run() {
|
|
44
|
+
local name="$1"
|
|
45
|
+
echo "RUN $name"
|
|
46
|
+
if "$name"; then
|
|
47
|
+
PASS=$((PASS+1)); echo "PASS $name"
|
|
48
|
+
else
|
|
49
|
+
FAIL=$((FAIL+1)); FAILED+=("$name"); echo "FAIL $name"
|
|
50
|
+
fi
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# --- AC-012 -------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
test_when_preview_only_flag_then_stdout_projection_no_writes() {
|
|
56
|
+
local proj; proj="$(mktemp -d)"; trap "rm -rf $proj" RETURN
|
|
57
|
+
seed_preview_project "$proj"
|
|
58
|
+
if [ ! -f "$ACTUATOR" ]; then
|
|
59
|
+
fail "AC-012 actuator not yet at $ACTUATOR — expected pre-implement RED state"
|
|
60
|
+
return 1
|
|
61
|
+
fi
|
|
62
|
+
local out
|
|
63
|
+
out="$(node "$ACTUATOR" --preview-only --slug demo --project-root "$proj" 2>/tmp/preview-stderr.$$)"
|
|
64
|
+
local ec=$?
|
|
65
|
+
if [ "$ec" -ne 0 ]; then
|
|
66
|
+
fail "AC-012 preview-only must exit 0; got $ec; stderr: $(cat /tmp/preview-stderr.$$)"
|
|
67
|
+
return 1
|
|
68
|
+
fi
|
|
69
|
+
# stdout matches Projected: <semver>.
|
|
70
|
+
if ! printf '%s' "$out" | grep -qE 'Projected:\s*[0-9]+\.[0-9]+\.[0-9]+'; then
|
|
71
|
+
fail "AC-012 stdout must contain Projected: <semver>; got: $out"
|
|
72
|
+
return 1
|
|
73
|
+
fi
|
|
74
|
+
# No state file written.
|
|
75
|
+
if [ -f "$proj/.claude/state/changelog/demo.json" ]; then
|
|
76
|
+
fail "AC-012 preview-only must NOT write state file"
|
|
77
|
+
return 1
|
|
78
|
+
fi
|
|
79
|
+
# CHANGELOG.md is either absent (we never created one) or unchanged.
|
|
80
|
+
if [ -f "$proj/CHANGELOG.md" ]; then
|
|
81
|
+
fail "AC-012 preview-only must NOT create CHANGELOG.md when absent"
|
|
82
|
+
return 1
|
|
83
|
+
fi
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# --- runner -------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
run test_when_preview_only_flag_then_stdout_projection_no_writes
|
|
89
|
+
|
|
90
|
+
echo "----"
|
|
91
|
+
echo "Passed: $PASS Failed: $FAIL"
|
|
92
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
93
|
+
echo "Failed tests:"
|
|
94
|
+
for t in "${FAILED[@]}"; do echo " - $t"; done
|
|
95
|
+
fi
|
|
96
|
+
exit $((FAIL > 0))
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Aggregate test runner for .claude/skills/changelog/.
|
|
3
|
+
# Invokes each sibling *_test.sh AND any *_test.mjs (node --test) and exits
|
|
4
|
+
# non-zero if any fail.
|
|
5
|
+
|
|
6
|
+
set -uo pipefail
|
|
7
|
+
|
|
8
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
FAIL=0
|
|
10
|
+
|
|
11
|
+
for t in "$HERE"/*_test.sh; do
|
|
12
|
+
[ -f "$t" ] || continue
|
|
13
|
+
echo "=== $(basename "$t") ==="
|
|
14
|
+
bash "$t" || FAIL=$((FAIL+1))
|
|
15
|
+
done
|
|
16
|
+
|
|
17
|
+
for t in "$HERE"/*_test.mjs; do
|
|
18
|
+
[ -f "$t" ] || continue
|
|
19
|
+
echo "=== $(basename "$t") ==="
|
|
20
|
+
node --test "$t" || FAIL=$((FAIL+1))
|
|
21
|
+
done
|
|
22
|
+
|
|
23
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
24
|
+
echo "changelog/tests: $FAIL suite(s) failed"
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
echo "changelog/tests: all suites passed"
|
|
28
|
+
exit 0
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// CHANGELOG.md curation under ## [Unreleased].
|
|
2
|
+
//
|
|
3
|
+
// Two exports:
|
|
4
|
+
// appendUnderUnreleased(changelogPath, entries) — per-commit RMW.
|
|
5
|
+
// reinsertUnreleasedHeading(changelogPath) — AC-013 fallback for the
|
|
6
|
+
// case where @semantic-release/changelog has prepended versioned notes
|
|
7
|
+
// above the `# Changelog` and `## [Unreleased]` headings. Restores the
|
|
8
|
+
// canonical order: `# Changelog\n\n## [Unreleased]\n\n<rest>`.
|
|
9
|
+
//
|
|
10
|
+
// File shape we maintain (keepachangelog 1.0.0):
|
|
11
|
+
//
|
|
12
|
+
// # Changelog
|
|
13
|
+
//
|
|
14
|
+
// ## [Unreleased]
|
|
15
|
+
//
|
|
16
|
+
// ### Added
|
|
17
|
+
// - <entry>
|
|
18
|
+
//
|
|
19
|
+
// ### Fixed
|
|
20
|
+
// - <entry>
|
|
21
|
+
//
|
|
22
|
+
// ## [0.1.0] - 2026-01-01
|
|
23
|
+
//
|
|
24
|
+
// ...
|
|
25
|
+
|
|
26
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
27
|
+
import { existsSync } from 'node:fs';
|
|
28
|
+
|
|
29
|
+
const UNRELEASED_HEADING = '## [Unreleased]';
|
|
30
|
+
const CHANGELOG_HEADING = '# Changelog';
|
|
31
|
+
|
|
32
|
+
const CATEGORY_ORDER = ['Added', 'Changed', 'Deprecated', 'Removed', 'Fixed', 'Security'];
|
|
33
|
+
|
|
34
|
+
function groupBySection(entries) {
|
|
35
|
+
const map = new Map();
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
if (!map.has(entry.section)) map.set(entry.section, []);
|
|
38
|
+
map.get(entry.section).push(entry);
|
|
39
|
+
}
|
|
40
|
+
return map;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function renderUnreleasedBody(entries) {
|
|
44
|
+
if (entries.length === 0) return '';
|
|
45
|
+
const grouped = groupBySection(entries);
|
|
46
|
+
const lines = [];
|
|
47
|
+
for (const section of CATEGORY_ORDER) {
|
|
48
|
+
const items = grouped.get(section);
|
|
49
|
+
if (!items || items.length === 0) continue;
|
|
50
|
+
lines.push('');
|
|
51
|
+
lines.push(`### ${section}`);
|
|
52
|
+
lines.push('');
|
|
53
|
+
for (const item of items) {
|
|
54
|
+
const prefix = item.breaking ? '**BREAKING:** ' : '';
|
|
55
|
+
lines.push(`- ${prefix}${item.body}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Split text into { preamble, unreleasedBody, rest } where preamble is
|
|
62
|
+
// everything up to and including the `## [Unreleased]` line, unreleasedBody
|
|
63
|
+
// is the content between that and the next `##` heading, and rest is from
|
|
64
|
+
// the next `##` heading onward.
|
|
65
|
+
function splitAroundUnreleased(text) {
|
|
66
|
+
const unreleasedIdx = text.indexOf(UNRELEASED_HEADING);
|
|
67
|
+
if (unreleasedIdx < 0) return null;
|
|
68
|
+
const afterHeading = text.indexOf('\n', unreleasedIdx);
|
|
69
|
+
const headingEnd = afterHeading < 0 ? text.length : afterHeading + 1;
|
|
70
|
+
// Find the next `## ` heading after the Unreleased heading.
|
|
71
|
+
const restMatch = text.slice(headingEnd).match(/\n## [^\n]+\n/);
|
|
72
|
+
const restOffset = restMatch ? headingEnd + restMatch.index + 1 : text.length;
|
|
73
|
+
return {
|
|
74
|
+
preamble: text.slice(0, headingEnd),
|
|
75
|
+
unreleasedBody: text.slice(headingEnd, restOffset),
|
|
76
|
+
rest: text.slice(restOffset),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function defaultChangelogText() {
|
|
81
|
+
return `# Changelog\n\n## [Unreleased]\n\n`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function appendUnderUnreleased(changelogPath, entries) {
|
|
85
|
+
let text;
|
|
86
|
+
if (existsSync(changelogPath)) {
|
|
87
|
+
text = await readFile(changelogPath, 'utf8');
|
|
88
|
+
} else {
|
|
89
|
+
text = defaultChangelogText();
|
|
90
|
+
}
|
|
91
|
+
if (!text.includes(CHANGELOG_HEADING)) {
|
|
92
|
+
text = `${CHANGELOG_HEADING}\n\n${text}`;
|
|
93
|
+
}
|
|
94
|
+
if (!text.includes(UNRELEASED_HEADING)) {
|
|
95
|
+
text = text.replace(
|
|
96
|
+
new RegExp(`(${CHANGELOG_HEADING}\\n)`, ''),
|
|
97
|
+
`$1\n${UNRELEASED_HEADING}\n\n`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const parts = splitAroundUnreleased(text);
|
|
101
|
+
if (!parts) {
|
|
102
|
+
// Defensive: should be unreachable after the insertions above.
|
|
103
|
+
throw new Error(`could not locate ${UNRELEASED_HEADING} in ${changelogPath}`);
|
|
104
|
+
}
|
|
105
|
+
const body = renderUnreleasedBody(entries);
|
|
106
|
+
const merged = `${parts.preamble}${body}\n${parts.rest.startsWith('\n') ? parts.rest : '\n' + parts.rest}`;
|
|
107
|
+
await writeFile(changelogPath, merged, 'utf8');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function reinsertUnreleasedHeading(changelogPath) {
|
|
111
|
+
const text = await readFile(changelogPath, 'utf8');
|
|
112
|
+
// If the first `##` heading in the file is already `## [Unreleased]`, the
|
|
113
|
+
// file is structurally correct; do nothing.
|
|
114
|
+
const firstH2 = text.match(/^## .+$/m);
|
|
115
|
+
if (firstH2 && firstH2[0].includes('[Unreleased]')) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// Otherwise: find the existing Unreleased heading (which may sit deeper in
|
|
119
|
+
// the file because @semantic-release/changelog prepended notes above it)
|
|
120
|
+
// and lift it to the canonical top position.
|
|
121
|
+
const lines = text.split('\n');
|
|
122
|
+
// Identify the Unreleased section's start and the next `##` start.
|
|
123
|
+
let unreleasedStart = -1;
|
|
124
|
+
let unreleasedEnd = -1;
|
|
125
|
+
for (let i = 0; i < lines.length; i++) {
|
|
126
|
+
if (unreleasedStart < 0 && lines[i] === UNRELEASED_HEADING) {
|
|
127
|
+
unreleasedStart = i;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (unreleasedStart >= 0 && /^## /.test(lines[i])) {
|
|
131
|
+
unreleasedEnd = i;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (unreleasedStart < 0) {
|
|
136
|
+
// No Unreleased heading anywhere; insert a fresh one at the top.
|
|
137
|
+
const top = `${CHANGELOG_HEADING}\n\n${UNRELEASED_HEADING}\n\n`;
|
|
138
|
+
await writeFile(changelogPath, top + text, 'utf8');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (unreleasedEnd < 0) unreleasedEnd = lines.length;
|
|
142
|
+
const unreleasedBlock = lines.slice(unreleasedStart, unreleasedEnd);
|
|
143
|
+
const without = lines.slice(0, unreleasedStart).concat(lines.slice(unreleasedEnd));
|
|
144
|
+
// Strip any leading blank lines from the without-block so the result starts
|
|
145
|
+
// with the `# Changelog` heading (we'll insert one if absent).
|
|
146
|
+
const withoutText = without.join('\n');
|
|
147
|
+
const head = withoutText.startsWith(CHANGELOG_HEADING)
|
|
148
|
+
? withoutText
|
|
149
|
+
: `${CHANGELOG_HEADING}\n\n${withoutText.replace(/^\n+/, '')}`;
|
|
150
|
+
const restored = head.replace(
|
|
151
|
+
new RegExp(`^(${CHANGELOG_HEADING})\\n\\n?`),
|
|
152
|
+
`$1\n\n${unreleasedBlock.join('\n')}\n\n`,
|
|
153
|
+
);
|
|
154
|
+
await writeFile(changelogPath, restored, 'utf8');
|
|
155
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Projected-version preview via semantic-release JS API.
|
|
2
|
+
//
|
|
3
|
+
// Returns { version, type, commits } where commits is the analyzer's list
|
|
4
|
+
// of conventional-parsed commits between the last release tag and HEAD.
|
|
5
|
+
|
|
6
|
+
import { execFileSync } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
// Parse conventional-commit subject lines: type(scope)?: subject + breaking-suffix.
|
|
9
|
+
function parseConventional(subject) {
|
|
10
|
+
const match = subject.match(/^([a-z]+)(?:\(([^)]+)\))?(!)?:\s*(.+)$/i);
|
|
11
|
+
if (!match) return { type: 'unknown', scope: null, breaking: false, subject };
|
|
12
|
+
return {
|
|
13
|
+
type: match[1].toLowerCase(),
|
|
14
|
+
scope: match[2] || null,
|
|
15
|
+
breaking: Boolean(match[3]),
|
|
16
|
+
subject: match[4],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// List commits between the latest tag and HEAD via plain git (no semantic-release
|
|
21
|
+
// dep needed for the commit list itself; semantic-release is only used for the
|
|
22
|
+
// projected version computation).
|
|
23
|
+
function listCommitsSinceLastTag(cwd) {
|
|
24
|
+
let lastTag;
|
|
25
|
+
try {
|
|
26
|
+
lastTag = execFileSync('git', ['describe', '--tags', '--abbrev=0'], {
|
|
27
|
+
cwd, encoding: 'utf8',
|
|
28
|
+
}).trim();
|
|
29
|
+
} catch {
|
|
30
|
+
lastTag = null;
|
|
31
|
+
}
|
|
32
|
+
const range = lastTag ? `${lastTag}..HEAD` : 'HEAD';
|
|
33
|
+
let raw;
|
|
34
|
+
try {
|
|
35
|
+
raw = execFileSync('git', ['log', range, '--format=%H%x09%s%x09%b%x00'], {
|
|
36
|
+
cwd, encoding: 'utf8',
|
|
37
|
+
});
|
|
38
|
+
} catch {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
return raw.split('\0').filter(Boolean).map((entry) => {
|
|
42
|
+
const [sha, subject, body] = entry.split('\t');
|
|
43
|
+
const parsed = parseConventional(subject || '');
|
|
44
|
+
const breakingBody = /^BREAKING CHANGE:/m.test(body || '');
|
|
45
|
+
return {
|
|
46
|
+
sha,
|
|
47
|
+
subject: parsed.subject,
|
|
48
|
+
body: (body || '').trim(),
|
|
49
|
+
type: parsed.type,
|
|
50
|
+
scope: parsed.scope,
|
|
51
|
+
breaking: parsed.breaking || breakingBody,
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Use semantic-release's JS API to compute the next version.
|
|
57
|
+
async function semanticReleaseDryRun(cwd) {
|
|
58
|
+
const mod = await import('semantic-release');
|
|
59
|
+
const semanticRelease = mod.default || mod;
|
|
60
|
+
const noopWritable = { write: () => true, end: () => {} };
|
|
61
|
+
const result = await semanticRelease(
|
|
62
|
+
{
|
|
63
|
+
dryRun: true,
|
|
64
|
+
ci: false,
|
|
65
|
+
branches: ['main', 'master'],
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
cwd,
|
|
69
|
+
env: { ...process.env },
|
|
70
|
+
stdout: noopWritable,
|
|
71
|
+
stderr: noopWritable,
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
if (result && result.nextRelease) {
|
|
75
|
+
return { version: result.nextRelease.version, type: result.nextRelease.type };
|
|
76
|
+
}
|
|
77
|
+
return { version: null, type: null };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Fallback: derive a projection locally if semantic-release rejects the run
|
|
81
|
+
// (e.g., no .releaserc.json, no remote, no commits since last tag).
|
|
82
|
+
function localProjection(cwd, commits) {
|
|
83
|
+
let lastTag;
|
|
84
|
+
try {
|
|
85
|
+
lastTag = execFileSync('git', ['describe', '--tags', '--abbrev=0'], {
|
|
86
|
+
cwd, encoding: 'utf8',
|
|
87
|
+
}).trim();
|
|
88
|
+
} catch {
|
|
89
|
+
lastTag = 'v0.0.0';
|
|
90
|
+
}
|
|
91
|
+
const baseSemver = lastTag.replace(/^v/, '');
|
|
92
|
+
const baseParts = baseSemver.split('.').map((s) => parseInt(s, 10) || 0);
|
|
93
|
+
let bumpType = null;
|
|
94
|
+
for (const commit of commits) {
|
|
95
|
+
if (commit.breaking) { bumpType = bumpType === 'major' ? 'major' : 'minor'; continue; }
|
|
96
|
+
if (commit.type === 'feat') { bumpType = bumpType === 'major' ? 'major' : (bumpType === 'minor' ? 'minor' : 'minor'); continue; }
|
|
97
|
+
if (commit.type === 'fix' || commit.type === 'perf' || commit.type === 'refactor') {
|
|
98
|
+
if (!bumpType) bumpType = 'patch';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!bumpType) return { version: baseSemver, type: null };
|
|
102
|
+
const [maj, min, pat] = baseParts;
|
|
103
|
+
if (bumpType === 'major') return { version: `${maj + 1}.0.0`, type: 'major' };
|
|
104
|
+
if (bumpType === 'minor') return { version: `${maj}.${min + 1}.0`, type: 'minor' };
|
|
105
|
+
return { version: `${maj}.${min}.${pat + 1}`, type: 'patch' };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function previewProjectedVersion(cwd) {
|
|
109
|
+
const commits = listCommitsSinceLastTag(cwd);
|
|
110
|
+
let projection;
|
|
111
|
+
try {
|
|
112
|
+
projection = await semanticReleaseDryRun(cwd);
|
|
113
|
+
if (!projection.version) {
|
|
114
|
+
projection = localProjection(cwd, commits);
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
projection = localProjection(cwd, commits);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
version: projection.version || '0.0.0',
|
|
121
|
+
type: projection.type,
|
|
122
|
+
commits,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -5,7 +5,7 @@ description: Workflow Phase 11 — Commit Preparation and Execution. Stages and
|
|
|
5
5
|
argument-hint: "[optional commit message; otherwise drafted from the spec/intake]"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Prereq:
|
|
8
|
+
Prereq: ALL of `archive` AND `memory-flush` AND `changelog` in `completed` (Phase 10.5 archives slug artifacts; Phase 10.6 curates `_pending.md`; Phase 11.5 appends keepachangelog entries under `## [Unreleased]` in `CHANGELOG.md`) AND a valid consent token at `.claude/state/commit_consent` (the Git Commit Guard hook enforces this independently). On any workflow where `memory-flush` or `changelog` is in `exceptions` (e.g. non-git projects auto-except both), this skill SHALL refuse to proceed unless those exceptions are explicit in `workflow.json`.
|
|
9
9
|
|
|
10
10
|
**Applicability.** This skill applies only when the project is a git repository. Non-git projects auto-except `commit` at `/triage` time (CLAUDE.md Article IV); the workflow ends after `/archive`.
|
|
11
11
|
|
|
@@ -106,7 +106,7 @@ The phases the harness loops through, in order:
|
|
|
106
106
|
```
|
|
107
107
|
intake → scout → research → spec → /approve-spec → tdd → simplify →
|
|
108
108
|
security → integrate → document → archive → memory-flush →
|
|
109
|
-
/grant-commit → commit
|
|
109
|
+
/grant-commit → changelog → commit
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
- Phases listed in `workflow.json → exceptions` are skipped.
|
|
@@ -158,6 +158,8 @@ On each `/harness` invocation, read `workflow.json` and decide whether to enter
|
|
|
158
158
|
| `completed` contains `spec` **and** approval token present, but `tdd`/`swarm-dispatch` not in `completed` | Enter loop; decide swarm-vs-solo at first iteration; invoke the next phase |
|
|
159
159
|
| `completed` contains `swarm-plan` but no `swarm_approvals/<slug>.approval` | Enter loop; loop exits with `state: yielded` (approve-swarm gate) |
|
|
160
160
|
| `completed` contains `archive` but no `commit_consent` (git project) | Enter loop; loop exits with `state: yielded` (grant-commit gate) |
|
|
161
|
+
| `completed` contains `grant-commit` consent (token fresh) but no `changelog` | Enter loop; invoke `Skill(changelog)` (Phase 11.5); on success continue to commit |
|
|
162
|
+
| `completed` contains `changelog` but no commit yet (git project) | Enter loop; invoke `Skill(commit)` (Phase 11) |
|
|
161
163
|
| Phase skill returned an error this invocation | Loop exits with phase-failure reason; user investigates |
|
|
162
164
|
|
|
163
165
|
## Constraints
|
|
@@ -17,7 +17,7 @@ Triage the user's request and set up `.claude/state/workflow.json` so downstream
|
|
|
17
17
|
# Steps
|
|
18
18
|
|
|
19
19
|
1. Restate the request back to the user in 1-2 sentences, and name the entry phase you've chosen and why.
|
|
20
|
-
2. **Git-repo detection (mandatory).** Run `git rev-parse --is-inside-work-tree 2>/dev/null` at the project root. If the exit status is non-zero, the project is not a git repository: gate C / `commit` are inapplicable AND the swarm path is unavailable because worktree isolation (the swarm contract's physical safety mechanism) requires git (CLAUDE.md Article IV "Phase 6c and Phase 11 are git-conditional", Article VII). Append `"swarm-plan"`, `"approve-swarm"`, `"swarm-dispatch"`, `"grant-commit"`, and `"commit"` to the exceptions array you'll write in step 4. Tell the user: "Non-git project detected — `swarm-plan`, `approve-swarm`, `swarm-dispatch`, `grant-commit`, and `commit` auto-excepted. Phase 6 routes to solo `/tdd`. Workflow ends after `/archive`. Persistence outside git is your responsibility."
|
|
20
|
+
2. **Git-repo detection (mandatory).** Run `git rev-parse --is-inside-work-tree 2>/dev/null` at the project root. If the exit status is non-zero, the project is not a git repository: gate C / `commit` are inapplicable AND the swarm path is unavailable because worktree isolation (the swarm contract's physical safety mechanism) requires git (CLAUDE.md Article IV "Phase 6c and Phase 11 are git-conditional", Article VII). Append `"swarm-plan"`, `"approve-swarm"`, `"swarm-dispatch"`, `"grant-commit"`, `"changelog"`, and `"commit"` to the exceptions array you'll write in step 4. `"changelog"` is auto-excepted alongside `"commit"` because Phase 11.5 is a pre-commit curator with no purpose outside a commit-bearing workflow. Tell the user: "Non-git project detected — `swarm-plan`, `approve-swarm`, `swarm-dispatch`, `grant-commit`, `changelog`, and `commit` auto-excepted. Phase 6 routes to solo `/tdd`. Workflow ends after `/archive`. Persistence outside git is your responsibility."
|
|
21
21
|
3. If the user has not confirmed yet, ask: "Entry phase = <X>. Exceptions = <Y>. Proceed? (or tell me a different entry)"
|
|
22
22
|
4. On confirmation, write `.claude/state/workflow.json`:
|
|
23
23
|
```json
|
|
@@ -36,18 +36,19 @@ Triage the user's request and set up `.claude/state/workflow.json` so downstream
|
|
|
36
36
|
The `source_backlog_keys` field is optional. When the user's request explicitly names one or more backlog entries this workflow picks up (the common framing is a `Source:` line listing backlog keys), populate the array with those keys. `/commit` (Phase 11) reads this field and invokes `sweep.py --mode stamp-closure` after the commit lands, stamping each named entry with `status: picked-up` + `superseded-at: <today>` so the next `/memory-flush` Step 0a auto-closes them. Absent / empty array → `/commit` skips the stamp step entirely (backward-compatible for any workflow that pre-dates the field). `/triage` does NOT auto-detect backlog keys from free-form prose — the user populates the field (or names them in the triage prompt and you populate it during step 4).
|
|
37
37
|
5. **Seed the workflow tasklist.** Use the `TaskCreate` tool to register one task per non-excepted phase plus each applicable consent gate. The tasks are the running checklist that `/harness` (or any direct phase invocation) reads to decide the next action; consent-gate tasks block the workflow until the user runs the corresponding command. **When `grant-commit` and `commit` are in exceptions (non-git project), do NOT seed those two tasks** — the workflow ends after `/archive`. Use these canonical templates:
|
|
38
38
|
|
|
39
|
-
**For `chore` track** (single phase + memory-flush + commit gate):
|
|
39
|
+
**For `chore` track** (single phase + memory-flush + changelog + commit gate):
|
|
40
40
|
- `Run /chore for <slug>` — activeForm: "Running chore", metadata: `{"phase": "chore"}`
|
|
41
41
|
- `Run /memory-flush for <slug>` — activeForm: "Running memory-flush", metadata: `{"phase": "memory-flush"}`, addBlockedBy previous
|
|
42
42
|
- `Wait for /grant-commit` — metadata: `{"phase": "grant-commit", "needs_user": true}`, addBlockedBy previous
|
|
43
|
+
- `Run /changelog for <slug>` — activeForm: "Running changelog", metadata: `{"phase": "changelog"}`, addBlockedBy previous
|
|
43
44
|
- `Run /commit for <slug>` — activeForm: "Running commit", metadata: `{"phase": "commit"}`, addBlockedBy previous
|
|
44
45
|
|
|
45
46
|
**For `tdd`-entry quickfix track** (skip intake/scout/research/spec/review):
|
|
46
|
-
- `Run /tdd`, `Run /simplify`, `Run /security` (only if not in exceptions), `Run /integrate`, `Run /document`, `Run /archive`, `Run /memory-flush`, `Wait for /grant-commit` (`needs_user`), `Run /commit` — each with `addBlockedBy` set to the previous task's id.
|
|
47
|
+
- `Run /tdd`, `Run /simplify`, `Run /security` (only if not in exceptions), `Run /integrate`, `Run /document`, `Run /archive`, `Run /memory-flush`, `Wait for /grant-commit` (`needs_user`), `Run /changelog`, `Run /commit` — each with `addBlockedBy` set to the previous task's id.
|
|
47
48
|
|
|
48
|
-
**For `spec`-entry track** (skip upstream):
|
|
49
|
+
**For `spec`-entry track** (skip upstream): `Run /spec`, `Wait for /approve-spec <path>` (`needs_user`), `Run /tdd`, `Run /simplify`, `Run /security` (unless excepted), `Run /integrate`, `Run /document`, `Run /archive`, `Run /memory-flush`, `Wait for /grant-commit` (`needs_user`), `Run /changelog`, `Run /commit` — each with `addBlockedBy` set to the previous task's id.
|
|
49
50
|
|
|
50
|
-
**For `intake`-entry full track**: `Run /intake`, `Run /brd` (only if stakeholder-heavy), `Run /scout`, `Run /research`, `Run /spec`, `Wait for /approve-spec <path>` (`needs_user`), `Run /tdd` OR (`Run /swarm-plan`, `Wait for /approve-swarm <slug>` (`needs_user`), `Run /swarm-dispatch`), `Run /simplify`, `Run /security` (unless excepted), `Run /integrate`, `Run /document`, `Run /archive`, `Run /memory-flush`, `Wait for /grant-commit` (`needs_user`), `Run /commit`. **On non-git projects the swarm branch SHALL NOT be seeded** — only `Run /tdd` goes in the list, regardless of expected component count. Swarm-vs-solo is a Phase-6 main-context decision (per CLAUDE.md Article V) only on git projects; non-git workflows resolve to solo at triage time because `swarm-plan`, `approve-swarm`, and `swarm-dispatch` are already in `exceptions`.
|
|
51
|
+
**For `intake`-entry full track**: `Run /intake`, `Run /brd` (only if stakeholder-heavy), `Run /scout`, `Run /research`, `Run /spec`, `Wait for /approve-spec <path>` (`needs_user`), `Run /tdd` OR (`Run /swarm-plan`, `Wait for /approve-swarm <slug>` (`needs_user`), `Run /swarm-dispatch`), `Run /simplify`, `Run /security` (unless excepted), `Run /integrate`, `Run /document`, `Run /archive`, `Run /memory-flush`, `Wait for /grant-commit` (`needs_user`), `Run /changelog`, `Run /commit`. **On non-git projects the swarm branch SHALL NOT be seeded** — only `Run /tdd` goes in the list, regardless of expected component count. Swarm-vs-solo is a Phase-6 main-context decision (per CLAUDE.md Article V) only on git projects; non-git workflows resolve to solo at triage time because `swarm-plan`, `approve-swarm`, and `swarm-dispatch` are already in `exceptions`. On non-git projects `changelog` is also auto-excepted alongside `commit` (Phase 11.5 only has purpose with a downstream commit).
|
|
51
52
|
|
|
52
53
|
For every task: `subject` is imperative ("Run /scout for <slug>" / "Wait for /approve-spec <path>"); `description` names the phase + the slug; `metadata.phase` carries the phase name; consent-gate tasks set `metadata.needs_user: true`. Wire `addBlockedBy` so each task blocks until its predecessor completes — this surfaces the workflow's true dependency graph and prevents `/harness` from racing past a gate.
|
|
53
54
|
|
package/obj/template/CLAUDE.md
CHANGED
|
@@ -40,7 +40,7 @@ On every new session, before any work, you SHALL:
|
|
|
40
40
|
|
|
41
41
|
1. **Read** `.claude/project.json` and check the `configured` field.
|
|
42
42
|
2. **If `configured: false`** — `/init-project` has not run. The repository is in a sanctioned operating state called **project-agnostic mode**: hooks are active but `test_runner` and `lint_runner` run in guide mode and nothing is tailored to the user's stack. You SHALL greet the user with this exact framing:
|
|
43
|
-
> "This repo has the Claude Code baseline installed (22 hooks, 1 subagent,
|
|
43
|
+
> "This repo has the Claude Code baseline installed (22 hooks, 1 subagent, 37 skills). It's in **project-agnostic mode** — `test_runner` and `lint_runner` are in guide mode and nothing is tailored to your stack. Run **`/init-project`** to scout the codebase, run the recommender, and generate a config. Skip it if you want baseline-only behavior, but you'll miss stack-specific tailoring."
|
|
44
44
|
You SHALL then proceed with whatever the user asks. Project-agnostic mode is **allowed** — the user is not required to run `/init-project` to use the baseline. The `setup_guard` hook surfaces a one-shot reminder on Write/Edit/MultiEdit (rate-limited to 10 minutes); it does **not** block writes. Other guards (commit, env, spec-approval, verify-pass, track, swarm-boundary) remain hard regardless of `configured` state.
|
|
45
45
|
3. **If `configured: true`** — read `docs/init/seed.md` §16 if present so you know what was added. Tell the user:
|
|
46
46
|
> "Configured for `<stack>`. Run `/triage \"<request>\"` to start a workflow, or `/harness` for the full pipeline."
|
|
@@ -292,7 +292,7 @@ Cryptographic supply-chain attestation, signed lock files, and per-skill aggrega
|
|
|
292
292
|
|---|---|
|
|
293
293
|
| `.claude/hooks/` | 22 hook scripts (17 write/run-boundary + 4 lifecycle + 1 input-boundary). Bash + python3, no jq. |
|
|
294
294
|
| `.claude/agents/` | 1 baseline subagent: `swarm-worker` (rendered from `src/agents/swarm-worker.template.md`) |
|
|
295
|
-
| `.claude/skills/` |
|
|
295
|
+
| `.claude/skills/` | 37 skills: artifact (4) + phases (11) + workers (5) + spec helpers (4) + orchestration (3) + memory (1) + shared globals (7) + audit (1) + alt tracks (1) |
|
|
296
296
|
| `.claude/commands/` | 5 consent/bootstrap gates: `approve-spec`, `approve-swarm`, `grant-commit`, `grant-push`, `init-project` |
|
|
297
297
|
| `.claude/memory/` | 7 canonical knowledge files + `_pending.md` (staging) + `_resume.md` (continuity snapshot) + `README.md` |
|
|
298
298
|
| `.claude/project.json` | per-project config (test/lint cmd, TDD globs, destructive patterns, swarm config, additions). Populated by `/init-project`. |
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
**Mandatory binding language.** Each numbered section (§) below specifies a binding requirement for the baseline. Implementations SHALL conform; `CLAUDE.md` Articles SHALL reference the corresponding §; project amendments (per `CLAUDE.md` Art. X) SHALL NOT contradict any § here.
|
|
13
13
|
|
|
14
|
-
The baseline turns soft engineering rules (no unauthorized commits, no stubs, no mocks of internal code, no self-approved specs) into structural guarantees enforced by write-boundary hooks. Eleven workflow phases plus one stripped-down chore track (skips TDD; runs verify + archive mandatorily, simplify/integrate/document conditionally), seventeen write/run-boundary guards plus four lifecycle hooks plus one input-boundary hook (twenty-two hook scripts total — twenty `.sh` + two `.mjs` after the JS-port pilot), thirty-
|
|
14
|
+
The baseline turns soft engineering rules (no unauthorized commits, no stubs, no mocks of internal code, no self-approved specs) into structural guarantees enforced by write-boundary hooks. Eleven workflow phases plus one stripped-down chore track (skips TDD; runs verify + archive mandatorily, simplify/integrate/document conditionally), seventeen write/run-boundary guards plus four lifecycle hooks plus one input-boundary hook (twenty-two hook scripts total — twenty `.sh` + two `.mjs` after the JS-port pilot), thirty-seven skills, one subagent, and four consent gates. Decisions live in main context; the lone subagent (`swarm-worker`) executes pre-decided recipes in parallel worktrees during `/swarm-dispatch`. Every artifact is archived; every third-party API is looked up against live docs. Project memory accumulates across sessions in `.claude/memory/` — auto-extracted by a Stop hook, curated in main context via `/memory-flush`, self-healing via re-verification.
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -110,7 +110,7 @@ Applies to every language. Mappings for TSX, Node, Python, Go, Rust ship inside
|
|
|
110
110
|
│ │ └── lib/common.sh # shared helpers
|
|
111
111
|
│ ├── agents/ # 1 subagent: swarm-worker (rendered from src/agents/swarm-worker.template.md)
|
|
112
112
|
│ ├── commands/ # 5 consent/bootstrap gates (user-only — structurally)
|
|
113
|
-
│ ├── skills/ #
|
|
113
|
+
│ ├── skills/ # 37 skills: artifact (4) + phases (11) + workers (5) + spec helpers (4) + orchestration (3) + memory (1) + shared globals (7) + audit (1) + alt tracks (1)
|
|
114
114
|
│ ├── memory/ # project memory: 7 canonical files + _pending.md (gitignored body) + README.md
|
|
115
115
|
│ └── state/ # runtime: workflow.json, approvals, swarm plans, verdicts, logs
|
|
116
116
|
├── src/ # pristine ship-time templates (overlay source for `npx @friedbotstudio/create-baseline`)
|
|
@@ -181,7 +181,7 @@ The baseline ships exactly one subagent. The architectural reason: subagents los
|
|
|
181
181
|
|
|
182
182
|
**Automated re-rendering by `/init-project`.** Step 6.4 re-renders `swarm-worker.md` from the template, driven by the recommender's `additions.swarm_worker_skills`. The recommender does **not** propose new subagent types — only stack-skill additions for the existing worker. Specialization happens via skills loaded into the worker's context, not via parallel agent personas; new decision-making roles belong in skills, which run in main context.
|
|
183
183
|
|
|
184
|
-
### §4.3 Skills (
|
|
184
|
+
### §4.3 Skills (37)
|
|
185
185
|
|
|
186
186
|
Each at `.claude/skills/<name>/SKILL.md`, frontmatter `name` + `description`, plus optional `template.md` (artifact skills) or helper scripts.
|
|
187
187
|
|
|
@@ -516,7 +516,7 @@ Seed-level requirement: no stale workflow artifacts in the working tree after co
|
|
|
516
516
|
|
|
517
517
|
**Step 4:** Write `src/agents/swarm-worker.template.md` (canonical-body store, per §4.2) — the only subagent template. Then render `.claude/agents/swarm-worker.md` from it with default tokens. The template carries four tokens — `{{NAME}}`, `{{DESCRIPTION}}`, `{{SKILLS}}`, `{{ROLE_LINE}}`. Default `SKILLS` is the YAML list block ` - scenario\n - implement` (the worker's two mandatory sub-skills). Render-parity holds at this stage. `/init-project` later re-renders the worker with stack-aware tokens when the recommender flags stack-specific skills to preload via `additions.swarm_worker_skills`.
|
|
518
518
|
|
|
519
|
-
**Step 5:** Write `.claude/skills/` for the
|
|
519
|
+
**Step 5:** Write `.claude/skills/` for the 37 skills (§4.3) — 29 workflow/worker/orchestration/memory/alt-track skills you author (the +1 over 28 is the `changelog` Phase 11.5 skill) plus 7 shared globals plus 1 audit skill. The breakdown: artifact drafting (4) + workflow phases (10) + phase workers (5: `scenario`, `implement`, `verify`, `prose`, `design-ui`) + spec helpers (4: `spec-lint`, `spec-render`, `spec-diagram-review`, `spec-traceability-review`) + orchestration (3: `harness`, `swarm-plan`, `swarm-dispatch`) + memory (1: `memory-flush`) + shared globals (7: `claude-automation-recommender`, `code-structure`, `humanizer`, `documentation`, `technical-tutorials`, `copywriting`, `impeccable`) + drift defender (1: `audit-baseline`) + alternate tracks (1: `chore`). The vendored `claude-automation-recommender` (Apache 2.0, from `claude-code-setup`), the writing/quality globals, and the design global ship unchanged with their licenses intact. Artifact skills (intake, brd, spec, rca) each ship a `template.md`. Helper scripts: swarm-plan gets `validate.sh`, swarm-dispatch gets `swarm_merge.sh`, spec-render gets `render.sh`, spec-lint gets `lint.sh`, archive gets `archive.sh`, audit-baseline gets `audit.sh`. All helper scripts `chmod +x`.
|
|
520
520
|
|
|
521
521
|
**Step 6:** Write `.claude/commands/*.md` for the 4 gates (§4.4). All carry `disable-model-invocation: true` as belt-and-braces; structural user-only is enforced by their directory.
|
|
522
522
|
|