Skip to content

Commit bf7c0be

Browse files
withkynamclaude
andcommitted
Release v3.2.2
Fix legacyDeletions self-contradiction (bare development-protocols/references dir entry removed so the shipped program-goal-charter-template.md survives upgrade; ledger 23->22). Ship the missing validate-agent-frontmatter.mjs validator that a prior publish never copied into the kit. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 7768602 commit bf7c0be

4 files changed

Lines changed: 249 additions & 3 deletions

File tree

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
#!/usr/bin/env node
2+
import fs from "node:fs";
3+
import path from "node:path";
4+
import { execSync } from "node:child_process";
5+
6+
let root;
7+
try {
8+
root = execSync("git rev-parse --show-toplevel", {
9+
stdio: ["pipe", "pipe", "pipe"],
10+
})
11+
.toString()
12+
.trim();
13+
} catch {
14+
root = process.cwd();
15+
}
16+
17+
const strict = process.argv.includes("--strict");
18+
const agentFlagIdx = process.argv.indexOf("--agent");
19+
const singleAgentArg =
20+
agentFlagIdx !== -1 ? process.argv[agentFlagIdx + 1] : null;
21+
22+
const VALID_EFFORT_VALUES = new Set(["low", "medium", "high", "max"]);
23+
24+
const globalFailures = [];
25+
const globalWarnings = [];
26+
const checkedAgents = [];
27+
28+
function parseFrontmatter(text) {
29+
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
30+
if (!match) return null;
31+
const block = match[1];
32+
const result = {};
33+
34+
let currentKey = null;
35+
let currentListLines = null;
36+
37+
for (const raw of block.split(/\r?\n/)) {
38+
// List item continuation
39+
const listItemMatch = raw.match(/^ - (.*)$/);
40+
if (listItemMatch && currentListLines !== null) {
41+
currentListLines.push(listItemMatch[1].trim());
42+
continue;
43+
}
44+
45+
// Flush pending list
46+
if (currentKey !== null && currentListLines !== null) {
47+
result[currentKey] = currentListLines;
48+
currentKey = null;
49+
currentListLines = null;
50+
}
51+
52+
// Inline list key: [a, b]
53+
const inlineListMatch = raw.match(/^([A-Za-z0-9_-]+):\s*\[(.*)\]\s*$/);
54+
if (inlineListMatch) {
55+
const vals = inlineListMatch[2]
56+
.split(",")
57+
.map((v) => v.trim())
58+
.filter(Boolean);
59+
result[inlineListMatch[1]] = vals;
60+
continue;
61+
}
62+
63+
// Empty list key: []
64+
const emptyListMatch = raw.match(/^([A-Za-z0-9_-]+):\s*\[\]\s*$/);
65+
if (emptyListMatch) {
66+
result[emptyListMatch[1]] = [];
67+
continue;
68+
}
69+
70+
// Key with no value — start of a block list
71+
const blockKeyMatch = raw.match(/^([A-Za-z0-9_-]+):\s*$/);
72+
if (blockKeyMatch) {
73+
currentKey = blockKeyMatch[1];
74+
currentListLines = [];
75+
continue;
76+
}
77+
78+
// Plain key: value
79+
const kvMatch = raw.match(/^([A-Za-z0-9_-]+):\s*(.+)$/);
80+
if (kvMatch) {
81+
result[kvMatch[1]] = kvMatch[2].trim().replace(/^["']|["']$/g, "");
82+
continue;
83+
}
84+
}
85+
86+
// Flush trailing list
87+
if (currentKey !== null && currentListLines !== null) {
88+
result[currentKey] = currentListLines;
89+
}
90+
91+
return result;
92+
}
93+
94+
function checkAgent(agentPath) {
95+
const rel = path.relative(root, agentPath);
96+
const agentName = path.basename(agentPath, ".md");
97+
let agentFailures = 0;
98+
let agentWarnings = 0;
99+
100+
let text;
101+
try {
102+
text = fs.readFileSync(agentPath, "utf8");
103+
} catch (e) {
104+
globalFailures.push(`${rel}: cannot read file — ${e.message}`);
105+
agentFailures++;
106+
checkedAgents.push({ path: rel, failures: agentFailures, warnings: agentWarnings });
107+
return;
108+
}
109+
110+
const fm = parseFrontmatter(text);
111+
if (!fm) {
112+
globalFailures.push(`${rel}: no YAML frontmatter block found`);
113+
agentFailures++;
114+
checkedAgents.push({ path: rel, failures: agentFailures, warnings: agentWarnings });
115+
return;
116+
}
117+
118+
// --- Check: effort ---
119+
if (!("effort" in fm)) {
120+
const msg = `${rel}: missing required field 'effort' (valid: low|medium|high|max)`;
121+
globalFailures.push(msg);
122+
agentFailures++;
123+
} else if (!VALID_EFFORT_VALUES.has(fm.effort)) {
124+
const msg = `${rel}: invalid effort value '${fm.effort}' (valid: low|medium|high|max)`;
125+
globalFailures.push(msg);
126+
agentFailures++;
127+
}
128+
129+
// --- Check: skills ---
130+
if (!("skills" in fm)) {
131+
const msg = `${rel}: missing required field 'skills' (must be a non-empty list)`;
132+
globalFailures.push(msg);
133+
agentFailures++;
134+
} else {
135+
const skillsVal = fm.skills;
136+
if (!Array.isArray(skillsVal)) {
137+
const msg = `${rel}: 'skills' must be a list, got scalar value`;
138+
globalFailures.push(msg);
139+
agentFailures++;
140+
} else if (skillsVal.length === 0) {
141+
const msg = `${rel}: 'skills' list is empty — at minimum vc-context-discovery is expected`;
142+
if (strict) {
143+
globalFailures.push(msg);
144+
agentFailures++;
145+
} else {
146+
globalWarnings.push(msg);
147+
agentWarnings++;
148+
}
149+
}
150+
}
151+
152+
// --- Check: disallowedTools ---
153+
if (!("disallowedTools" in fm)) {
154+
const msg = `${rel}: missing field 'disallowedTools' (should be a list, may be empty [])`;
155+
if (strict) {
156+
globalFailures.push(msg);
157+
agentFailures++;
158+
} else {
159+
globalWarnings.push(msg);
160+
agentWarnings++;
161+
}
162+
} else {
163+
const dtVal = fm.disallowedTools;
164+
if (!Array.isArray(dtVal)) {
165+
const msg = `${rel}: 'disallowedTools' must be a list, got scalar value`;
166+
globalFailures.push(msg);
167+
agentFailures++;
168+
}
169+
}
170+
171+
// --- Check: hooks block ---
172+
// At P2 time: if hooks is present, it must reference agent-write-guard.mjs.
173+
// Absence of hooks is allowed (only WARN if strict — P3/P5 will strengthen this).
174+
// NOTE: the simple frontmatter parser cannot handle nested YAML blocks (hooks > PreToolUse > hooks > command).
175+
// Use a raw text scan of the frontmatter block instead to detect the agent-write-guard.mjs reference.
176+
if ("hooks" in fm) {
177+
const fmMatch = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
178+
const fmRaw = fmMatch ? fmMatch[1] : "";
179+
if (!fmRaw.includes("agent-write-guard.mjs")) {
180+
const msg = `${rel}: 'hooks' block present but does not reference agent-write-guard.mjs`;
181+
globalFailures.push(msg);
182+
agentFailures++;
183+
}
184+
}
185+
// hooks absent: no action at P2 time (P5 will add the FAIL gate)
186+
187+
checkedAgents.push({ path: rel, failures: agentFailures, warnings: agentWarnings });
188+
}
189+
190+
// Determine agent file(s) to check
191+
let agentFiles = [];
192+
193+
if (singleAgentArg) {
194+
// --agent can be an absolute path, relative to cwd, or a basename resolved from .claude/agents/
195+
const resolved = path.isAbsolute(singleAgentArg)
196+
? singleAgentArg
197+
: path.resolve(process.cwd(), singleAgentArg);
198+
199+
if (fs.existsSync(resolved)) {
200+
agentFiles = [resolved];
201+
} else {
202+
// Try resolving as a basename under .claude/agents/
203+
const inAgentsDir = path.join(root, ".claude/agents", singleAgentArg);
204+
const withExt = inAgentsDir.endsWith(".md") ? inAgentsDir : inAgentsDir + ".md";
205+
if (fs.existsSync(withExt)) {
206+
agentFiles = [withExt];
207+
} else {
208+
globalFailures.push(`--agent '${singleAgentArg}': file not found`);
209+
}
210+
}
211+
} else {
212+
// Auto-discover all .md files under .claude/agents/
213+
const agentsDir = path.join(root, ".claude/agents");
214+
if (fs.existsSync(agentsDir)) {
215+
agentFiles = fs
216+
.readdirSync(agentsDir, { withFileTypes: true })
217+
.filter((e) => e.isFile() && e.name.endsWith(".md"))
218+
.map((e) => path.join(agentsDir, e.name))
219+
.sort();
220+
} else {
221+
globalFailures.push(`.claude/agents/ directory not found`);
222+
}
223+
}
224+
225+
for (const agentPath of agentFiles) {
226+
checkAgent(agentPath);
227+
}
228+
229+
const result = {
230+
checkedAgents,
231+
strict,
232+
warnings: globalWarnings,
233+
failures: globalFailures,
234+
};
235+
236+
console.log(JSON.stringify(result, null, 2));
237+
238+
if (globalFailures.length > 0) {
239+
process.exitCode = 1;
240+
}

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
All notable changes to vibecode-pro-max-kit are documented in this file.
44

5+
## [3.2.2] - 2026-06-20
6+
7+
### Fixed
8+
9+
- `legacyDeletions` self-contradiction removed: the ledger carried both the bare directory `process/development-protocols/references` AND the live, shipped file `process/development-protocols/references/program-goal-charter-template.md` underneath it. On `vc-update`/install the bare-dir deletion would recursively wipe the directory — destroying the charter template that `all-development-protocols.md` references. The bare-dir entry is dropped (ledger 23→22); the two genuinely-dead PRD files under that path remain individually listed, so the template survives an upgrade while the dead files are still cleaned up.
10+
- Shipped the missing `validate-agent-frontmatter.mjs` validator. The agent-frontmatter behavior validator existed in the development harness and is referenced by the audit suite, but a prior publish never copied it into the kit — so no install or `vc-update` could ever run it. It is now part of the kit's `.claude/skills/vc-audit-vc/scripts/` set.
11+
512
## [3.2.1] - 2026-06-20
613

714
### Fixed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
<a href="LICENSE"><img src="https://img.shields.io/github/license/withkynam/vibecode-pro-max-kit" alt="License"></a>
8989
<a href="https://github.com/withkynam/vibecode-pro-max-kit/graphs/contributors"><img src="https://img.shields.io/github/contributors/withkynam/vibecode-pro-max-kit" alt="Contributors"></a>
9090
<a href="https://github.com/withkynam/vibecode-pro-max-kit/actions/workflows/validate.yml"><img src="https://img.shields.io/github/actions/workflow/status/withkynam/vibecode-pro-max-kit/validate.yml" alt="CI"></a>
91-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.2.1-2EA043" alt="Version"></a>
91+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/version-3.2.2-2EA043" alt="Version"></a>
9292
<img src="https://img.shields.io/badge/agents-15-orange" alt="Agents">
9393
<img src="https://img.shields.io/badge/skills-33-purple" alt="Skills">
9494
<img src="https://img.shields.io/badge/hooks-10-blue" alt="Hooks">

vc-manifest.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "3.2.1",
2+
"version": "3.2.2",
33
"include": [
44
".claude/agents/**",
55
".claude/skills/**",
@@ -80,7 +80,6 @@
8080
"process/development-protocols/archive/vc-system-behavior-reference_ARCHIVED_09-06-26.md",
8181
"process/general-plans/reports",
8282
"process/general-plans/references",
83-
"process/development-protocols/references",
8483
"process/_seeds/features/_feature-template/reports",
8584
"process/_seeds/features/_feature-template/references",
8685
"process/_seeds/general-plans/reports",

0 commit comments

Comments
 (0)