beaver-build 1.0.8 → 2.0.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 +389 -383
- package/dist/index.js +1354 -63
- package/package.json +55 -57
package/dist/index.js
CHANGED
|
@@ -38,6 +38,12 @@ var init_constants = __esm({
|
|
|
38
38
|
value: "NUXT",
|
|
39
39
|
description: "Upcoming...",
|
|
40
40
|
disabled: true
|
|
41
|
+
},
|
|
42
|
+
HarnessOnly: {
|
|
43
|
+
display: "Apply AI Harness",
|
|
44
|
+
value: "HARNESS_ONLY",
|
|
45
|
+
description: "Add Claude Code harness to an existing project",
|
|
46
|
+
disabled: false
|
|
41
47
|
}
|
|
42
48
|
};
|
|
43
49
|
}
|
|
@@ -288,10 +294,6 @@ var init_package_json = __esm({
|
|
|
288
294
|
if (cart.testing === "PLAYWRIGHT") {
|
|
289
295
|
scripts["test:e2e"] = "playwright test";
|
|
290
296
|
}
|
|
291
|
-
if (cart.ai === "CLAUDE") {
|
|
292
|
-
scripts["docs:index"] = "node .claude/scripts/build-docs-index.mjs";
|
|
293
|
-
scripts["docs:lint"] = "node .claude/scripts/lint-docs-frontmatter.mjs";
|
|
294
|
-
}
|
|
295
297
|
return JSON.stringify(
|
|
296
298
|
{
|
|
297
299
|
name: cart.projectName,
|
|
@@ -771,10 +773,55 @@ declare module '*.css' {
|
|
|
771
773
|
});
|
|
772
774
|
|
|
773
775
|
// src/scaffold/shared/claude-setup.ts
|
|
774
|
-
var STATUS_ENUM, LANG_ENUM, docsTemplateMdTemplate, docsReadmeTemplate, docsIndexPlaceholderTemplate, docsSharedMjsTemplate, buildDocsIndexMjsTemplate, lintDocsFrontmatterMjsTemplate, docsFirstReminderShTemplate, claudeSettingsTemplate, docsSkillTemplate, docsWriterAgentTemplate, agentMemorySeedTemplate, buildClaudeFileMap;
|
|
776
|
+
var AGENTS, TOOL_OVERRIDES, agentTools, agentMemoryFrontmatter, claudeHarnessTableTemplate, STATUS_ENUM, LANG_ENUM, docsTemplateMdTemplate, docsReadmeTemplate, docsIndexPlaceholderTemplate, docsSharedMjsTemplate, buildDocsIndexMjsTemplate, lintDocsFrontmatterMjsTemplate, docsFirstReminderShTemplate, TEST_WRITER_DEF, agentGuardMjsTemplate, claudeSettingsTemplate, docsSkillTemplate, docsWriterAgentTemplate, plannerAgentTemplate, advisorAgentTemplate, scoutAgentTemplate, plansReadmeTemplate, backlogReadmeTemplate, agentMemorySeedTemplate, validateStructureMjsTemplate, validatePlansMjsTemplate, buildClaudeFileMap;
|
|
775
777
|
var init_claude_setup = __esm({
|
|
776
778
|
"src/scaffold/shared/claude-setup.ts"() {
|
|
777
779
|
"use strict";
|
|
780
|
+
AGENTS = [
|
|
781
|
+
{
|
|
782
|
+
name: "dev",
|
|
783
|
+
model: "sonnet",
|
|
784
|
+
writeScope: ["src/", "package.json", "tsconfig.json", "vite.config.ts", "biome.json", "eslint.config.js", ".github/"],
|
|
785
|
+
memory: true
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
name: "docs-writer",
|
|
789
|
+
model: "haiku",
|
|
790
|
+
writeScope: ["docs/"],
|
|
791
|
+
memory: true
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
name: "planner",
|
|
795
|
+
model: "sonnet",
|
|
796
|
+
writeScope: ["plans/"],
|
|
797
|
+
memory: true
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
name: "advisor",
|
|
801
|
+
model: "opus",
|
|
802
|
+
writeScope: [],
|
|
803
|
+
memory: true
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
name: "scout",
|
|
807
|
+
model: "haiku",
|
|
808
|
+
writeScope: [],
|
|
809
|
+
memory: false
|
|
810
|
+
}
|
|
811
|
+
];
|
|
812
|
+
TOOL_OVERRIDES = {
|
|
813
|
+
advisor: "Read, Grep, Glob, Bash, WebFetch, WebSearch, Skill, TodoWrite",
|
|
814
|
+
scout: "Read, Grep, Glob, Skill"
|
|
815
|
+
};
|
|
816
|
+
agentTools = (agent) => {
|
|
817
|
+
if (TOOL_OVERRIDES[agent.name]) return TOOL_OVERRIDES[agent.name];
|
|
818
|
+
return agent.writeScope.length > 0 ? "Read, Grep, Glob, Write, Edit, Skill, TodoWrite" : "Read, Grep, Glob, Skill";
|
|
819
|
+
};
|
|
820
|
+
agentMemoryFrontmatter = (agent) => agent.memory ? "\nmemory: project" : "";
|
|
821
|
+
claudeHarnessTableTemplate = () => `| Brainstorming / trade-off analysis / "what's the best approach?" before any change | \`advisor\` | read-only; deepest source mental model; recommends, never edits |
|
|
822
|
+
| Quick factual lookup about the code/docs (answer + \`path:line\`) | \`scout\` | read-only; cheap; for facts, not design reasoning |
|
|
823
|
+
| Decomposing a story into a resumable plan | \`planner\` | owns \`plans/\`; writes phase files only, never code |
|
|
824
|
+
| Analyzing requirements, writing/updating feature docs | \`docs-writer\` | owns \`docs/\`; rebuilds INDEX.md after every change |`;
|
|
778
825
|
STATUS_ENUM = ["active", "draft", "deprecated"];
|
|
779
826
|
LANG_ENUM = ["en", "vi"];
|
|
780
827
|
docsTemplateMdTemplate = (flowEnum3, layerEnum3) => `---
|
|
@@ -831,14 +878,14 @@ Frontmatter-indexed task docs. \`INDEX.md\` is auto-generated \u2014 never hand-
|
|
|
831
878
|
|
|
832
879
|
1. Copy \`docs/_template.md\`, fill ALL frontmatter fields (the index and lint are built from it).
|
|
833
880
|
2. Save to the right directory per the table above.
|
|
834
|
-
3. \`
|
|
835
|
-
4. \`
|
|
881
|
+
3. \`node .claude/scripts/build-docs-index.mjs\` \u2014 regenerates \`INDEX.md\`.
|
|
882
|
+
4. \`node .claude/scripts/lint-docs-frontmatter.mjs\` \u2014 must pass before committing.
|
|
836
883
|
5. Commit the doc and \`INDEX.md\` together.
|
|
837
884
|
`;
|
|
838
885
|
docsIndexPlaceholderTemplate = () => `# Docs Index
|
|
839
886
|
|
|
840
887
|
> Auto-generated by \`.claude/scripts/build-docs-index.mjs\` \u2014 do not edit by hand.
|
|
841
|
-
> Run \`
|
|
888
|
+
> Run \`node .claude/scripts/build-docs-index.mjs\` to regenerate.
|
|
842
889
|
`;
|
|
843
890
|
docsSharedMjsTemplate = (flowEnum3, layerEnum3) => `// Shared helpers for docs tooling \u2014 single source of truth for the frontmatter schema.
|
|
844
891
|
import { readdirSync, readFileSync } from 'fs';
|
|
@@ -903,7 +950,7 @@ export function walkDocs(dir = DOCS_DIR, records = []) {
|
|
|
903
950
|
}
|
|
904
951
|
`;
|
|
905
952
|
buildDocsIndexMjsTemplate = () => `// Regenerates docs/INDEX.md from frontmatter. Deterministic (sorted) so diffs stay clean.
|
|
906
|
-
// Run via:
|
|
953
|
+
// Run via: node .claude/scripts/build-docs-index.mjs
|
|
907
954
|
import { writeFileSync } from 'fs';
|
|
908
955
|
import { join } from 'path';
|
|
909
956
|
import { walkDocs, DOCS_DIR } from './_docs-shared.mjs';
|
|
@@ -981,7 +1028,7 @@ writeFileSync(join(DOCS_DIR, 'INDEX.md'), lines.join('\\n').replace(/\\n+$/, '\\
|
|
|
981
1028
|
console.log('docs/INDEX.md updated \u2014 ' + records.length + ' doc(s), ' + withFm + ' with frontmatter.');
|
|
982
1029
|
`;
|
|
983
1030
|
lintDocsFrontmatterMjsTemplate = () => `// Validates every doc against the frontmatter schema. Exits non-zero on violation \u2192 CI-ready.
|
|
984
|
-
// Run via:
|
|
1031
|
+
// Run via: node .claude/scripts/lint-docs-frontmatter.mjs
|
|
985
1032
|
import { existsSync } from 'fs';
|
|
986
1033
|
import { join } from 'path';
|
|
987
1034
|
import { walkDocs, ENUMS, REQUIRED_FIELDS, DOCS_DIR } from './_docs-shared.mjs';
|
|
@@ -1052,6 +1099,88 @@ EOF
|
|
|
1052
1099
|
fi
|
|
1053
1100
|
exit 0
|
|
1054
1101
|
`;
|
|
1102
|
+
TEST_WRITER_DEF = {
|
|
1103
|
+
name: "test-writer",
|
|
1104
|
+
model: "haiku",
|
|
1105
|
+
writeScope: ["src/", "test/", "tests/", "e2e/", "playwright/"],
|
|
1106
|
+
memory: true
|
|
1107
|
+
};
|
|
1108
|
+
agentGuardMjsTemplate = (agents) => {
|
|
1109
|
+
const scopesObj = {};
|
|
1110
|
+
for (const agent of agents) {
|
|
1111
|
+
scopesObj[agent.name] = agent.writeScope;
|
|
1112
|
+
}
|
|
1113
|
+
const scopesJson = JSON.stringify(scopesObj, null, 2);
|
|
1114
|
+
return `// PreToolUse guard \u2014 registry-driven path enforcement.
|
|
1115
|
+
// Generated from the AGENTS registry; do not edit by hand.
|
|
1116
|
+
// Each agent may only write to its declared writeScope prefixes plus its own
|
|
1117
|
+
// .claude/agent-memory/<name>/ directory (implicit, added at runtime below).
|
|
1118
|
+
// Agents NOT in WRITE_SCOPES (unknown agents, main thread) pass through.
|
|
1119
|
+
// NOTE: writeScope is a heuristic backstop, not a precise ACL. Projects that
|
|
1120
|
+
// place test files under src/__tests__ instead of test/ or tests/ will need to
|
|
1121
|
+
// extend the test-writer scope \u2014 this guard is lenient for dev rather than tight.
|
|
1122
|
+
import { readFileSync } from 'fs';
|
|
1123
|
+
|
|
1124
|
+
const WRITE_SCOPES = ${scopesJson};
|
|
1125
|
+
|
|
1126
|
+
let payload;
|
|
1127
|
+
try {
|
|
1128
|
+
payload = JSON.parse(readFileSync(0, 'utf-8'));
|
|
1129
|
+
} catch {
|
|
1130
|
+
process.exit(0);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
const agentType = payload.agent_type;
|
|
1134
|
+
// Unknown agent or main thread \u2014 pass through.
|
|
1135
|
+
if (!agentType || !(agentType in WRITE_SCOPES)) process.exit(0);
|
|
1136
|
+
|
|
1137
|
+
const filePath = payload.tool_input?.file_path;
|
|
1138
|
+
if (!filePath) process.exit(0);
|
|
1139
|
+
|
|
1140
|
+
// Normalize to a project-relative path when an absolute path is given.
|
|
1141
|
+
const cwd = payload.cwd ?? '';
|
|
1142
|
+
let rel = filePath;
|
|
1143
|
+
if (filePath.startsWith('/') && cwd && filePath.startsWith(cwd + '/')) {
|
|
1144
|
+
rel = filePath.slice(cwd.length + 1);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
const allowedPrefixes = WRITE_SCOPES[agentType];
|
|
1148
|
+
const memoryPrefix = \`.claude/agent-memory/\${agentType}/\`;
|
|
1149
|
+
|
|
1150
|
+
// Always allow an agent to write its own memory dir (even read-only agents).
|
|
1151
|
+
if (rel.startsWith(memoryPrefix)) process.exit(0);
|
|
1152
|
+
|
|
1153
|
+
// Read-only agents (empty writeScope): deny all other writes with a specific message.
|
|
1154
|
+
if (allowedPrefixes.length === 0) {
|
|
1155
|
+
process.stdout.write(
|
|
1156
|
+
JSON.stringify({
|
|
1157
|
+
hookSpecificOutput: {
|
|
1158
|
+
hookEventName: 'PreToolUse',
|
|
1159
|
+
permissionDecision: 'deny',
|
|
1160
|
+
permissionDecisionReason: \`read-only agent may not write any file. \${agentType} attempted to write \${filePath}. Route implementation to the dev agent instead.\`,
|
|
1161
|
+
},
|
|
1162
|
+
})
|
|
1163
|
+
);
|
|
1164
|
+
process.exit(0);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// Allow if path is within the agent's declared scope.
|
|
1168
|
+
const allowed = allowedPrefixes.some((prefix) => rel.startsWith(prefix));
|
|
1169
|
+
|
|
1170
|
+
if (allowed) process.exit(0);
|
|
1171
|
+
|
|
1172
|
+
process.stdout.write(
|
|
1173
|
+
JSON.stringify({
|
|
1174
|
+
hookSpecificOutput: {
|
|
1175
|
+
hookEventName: 'PreToolUse',
|
|
1176
|
+
permissionDecision: 'deny',
|
|
1177
|
+
permissionDecisionReason: \`\${agentType} may only write under [\${allowedPrefixes.join(', ')}] (and \${memoryPrefix}). Blocked write to \${filePath}. Route to the correct agent instead.\`,
|
|
1178
|
+
},
|
|
1179
|
+
})
|
|
1180
|
+
);
|
|
1181
|
+
process.exit(0);
|
|
1182
|
+
`;
|
|
1183
|
+
};
|
|
1055
1184
|
claudeSettingsTemplate = () => JSON.stringify(
|
|
1056
1185
|
{
|
|
1057
1186
|
permissions: {
|
|
@@ -1078,6 +1207,17 @@ exit 0
|
|
|
1078
1207
|
}
|
|
1079
1208
|
]
|
|
1080
1209
|
}
|
|
1210
|
+
],
|
|
1211
|
+
PreToolUse: [
|
|
1212
|
+
{
|
|
1213
|
+
matcher: "Write|Edit|MultiEdit",
|
|
1214
|
+
hooks: [
|
|
1215
|
+
{
|
|
1216
|
+
type: "command",
|
|
1217
|
+
command: 'node "$CLAUDE_PROJECT_DIR/.claude/scripts/agent-guard.mjs"'
|
|
1218
|
+
}
|
|
1219
|
+
]
|
|
1220
|
+
}
|
|
1081
1221
|
]
|
|
1082
1222
|
},
|
|
1083
1223
|
sandbox: {
|
|
@@ -1087,8 +1227,8 @@ exit 0
|
|
|
1087
1227
|
null,
|
|
1088
1228
|
2
|
|
1089
1229
|
);
|
|
1090
|
-
docsSkillTemplate = (projectName,
|
|
1091
|
-
name: ${
|
|
1230
|
+
docsSkillTemplate = (projectName, slug4, flowEnum3, layerEnum3) => `---
|
|
1231
|
+
name: ${slug4}-docs
|
|
1092
1232
|
description: How to find and write knowledge-base docs for ${projectName}. Use when asked "is there a doc about\u2026", "explain the X feature/flow", "document this", "write a spec", or the Vietnamese equivalents ("c\xF3 t\xE0i li\u1EC7u v\u1EC1\u2026", "gi\u1EA3i th\xEDch flow\u2026", "vi\u1EBFt docs/spec\u2026"). Also use before modifying any documented feature.
|
|
1093
1233
|
---
|
|
1094
1234
|
|
|
@@ -1112,14 +1252,14 @@ description: How to find and write knowledge-base docs for ${projectName}. Use w
|
|
|
1112
1252
|
3. Keywords: lowercase, prefer real symbol/file names an engineer would grep for.
|
|
1113
1253
|
4. Rebuild + validate, then commit doc and INDEX.md together:
|
|
1114
1254
|
\`\`\`bash
|
|
1115
|
-
|
|
1255
|
+
node .claude/scripts/build-docs-index.mjs && node .claude/scripts/lint-docs-frontmatter.mjs
|
|
1116
1256
|
\`\`\`
|
|
1117
1257
|
`;
|
|
1118
|
-
docsWriterAgentTemplate = (projectName,
|
|
1258
|
+
docsWriterAgentTemplate = (projectName, slug4, agent) => `---
|
|
1119
1259
|
name: docs-writer
|
|
1120
1260
|
description: "Documentation agent for ${projectName} \u2014 analyzes requirements from any source (conversation, tickets, Slack) and maintains docs/. <example>user: 'Here are the requirements for the profile page, write them up' \u2192 docs-writer <commentary>synthesizing requirements into a feature spec</commentary></example> <example>user: 'We just finished the auth refactor, document what changed' \u2192 docs-writer <commentary>post-task knowledge capture</commentary></example> <example>user: 'Update the home spec \u2014 the hero section was redesigned' \u2192 docs-writer <commentary>doc maintenance</commentary></example>"
|
|
1121
|
-
model:
|
|
1122
|
-
|
|
1261
|
+
model: ${agent.model}${agentMemoryFrontmatter(agent)}
|
|
1262
|
+
tools: ${agentTools(agent)}
|
|
1123
1263
|
---
|
|
1124
1264
|
|
|
1125
1265
|
You are the documentation agent for ${projectName}. You own \`docs/\`.
|
|
@@ -1128,7 +1268,7 @@ You are the documentation agent for ${projectName}. You own \`docs/\`.
|
|
|
1128
1268
|
|
|
1129
1269
|
1. Read \`.claude/agent-memory/docs-writer/MEMORY.md\`.
|
|
1130
1270
|
2. Read \`docs/README.md\` (placement + naming rules) and \`docs/INDEX.md\` (what already exists).
|
|
1131
|
-
3. Load the \`${
|
|
1271
|
+
3. Load the \`${slug4}-docs\` skill.
|
|
1132
1272
|
|
|
1133
1273
|
## Workflow
|
|
1134
1274
|
|
|
@@ -1136,7 +1276,7 @@ You are the documentation agent for ${projectName}. You own \`docs/\`.
|
|
|
1136
1276
|
2. Check INDEX.md for an existing doc to update before creating a new one.
|
|
1137
1277
|
3. Copy \`docs/_template.md\`; fill ALL frontmatter fields (feature, flow, layer, status, lang, keywords, updated). Specs describe WHAT, not HOW.
|
|
1138
1278
|
4. Save per docs/README.md rules: \`docs/features/<feature>/<feature>.spec.en.md\` for feature specs, \`<topic>.en.md\` for findings, \`docs/architecture/\` for cross-cutting topics.
|
|
1139
|
-
5. Run \`
|
|
1279
|
+
5. Run \`node .claude/scripts/build-docs-index.mjs\` then \`node .claude/scripts/lint-docs-frontmatter.mjs\` \u2014 both must succeed.
|
|
1140
1280
|
6. If the feature introduces new domain nouns, add them to the \`trigger\` list in \`.claude/scripts/docs-first-reminder.sh\`.
|
|
1141
1281
|
7. Report created/updated paths. Append lessons to \`.claude/agent-memory/docs-writer/MEMORY.md\`.
|
|
1142
1282
|
|
|
@@ -1145,28 +1285,675 @@ You are the documentation agent for ${projectName}. You own \`docs/\`.
|
|
|
1145
1285
|
- Never hand-edit \`docs/INDEX.md\`.
|
|
1146
1286
|
- Never edit application code \u2014 you write markdown and run the docs scripts only.
|
|
1147
1287
|
- Never commit or push.
|
|
1288
|
+
`;
|
|
1289
|
+
plannerAgentTemplate = (projectName, agent) => `---
|
|
1290
|
+
name: planner
|
|
1291
|
+
description: "Planning agent for ${projectName} \u2014 analyzes a request/story and produces a professional, detailed, resumable implementation plan under plans/. Splits work into phase files so a failure in one phase never breaks the whole flow; execution can resume from the unfinished phase. <example>user: 'Break this story into a step-by-step plan we can resume' \u2192 planner <commentary>resumable multi-phase plan</commentary></example> <example>user: 'Plan the rollout for the new feature' \u2192 planner <commentary>decompose a large feature into ordered, verifiable phases</commentary></example> <example>user: 'Fix this small bug' \u2192 dev, NOT planner <commentary>small, single-pass change \u2014 code directly</commentary></example> <example>user: 'Write a spec for X' \u2192 docs-writer, NOT planner <commentary>specs describe WHAT; plans describe HOW/when</commentary></example>"
|
|
1292
|
+
model: ${agent.model}${agentMemoryFrontmatter(agent)}
|
|
1293
|
+
tools: ${agentTools(agent)}
|
|
1294
|
+
---
|
|
1295
|
+
|
|
1296
|
+
You are the planning agent for ${projectName}. You own \`plans/\`. You produce the plan; the \`dev\` agent executes it.
|
|
1297
|
+
|
|
1298
|
+
## Onboarding protocol (in order, before writing any plan)
|
|
1299
|
+
|
|
1300
|
+
1. Read \`.claude/agent-memory/planner/MEMORY.md\` \u2014 accumulated planning gotchas.
|
|
1301
|
+
2. Read \`plans/README.md\` \u2014 folder layout, file naming, and frontmatter contract.
|
|
1302
|
+
3. Read \`docs/INDEX.md\` and the relevant \`docs/features/<feature>/\` spec(s) \u2014 the spec is your source of truth for WHAT. If no relevant spec exists, STOP and tell the user to run the docs-writer agent first.
|
|
1303
|
+
4. Skim the code under change only enough to anchor the plan in real file paths.
|
|
1304
|
+
|
|
1305
|
+
## Workflow
|
|
1306
|
+
|
|
1307
|
+
1. Restate the request and surface ambiguity. If interpretations conflict, ask \u2014 do not guess.
|
|
1308
|
+
2. Decompose the work into the **minimum** set of ordered phases. Each phase is independently completable and leaves the repo in a working state. Do not invent speculative phases.
|
|
1309
|
+
3. Write \`plans/<slug>/00-overview.md\` (goal, scope, non-goals, and the **Ordered phases** tracker table \u2014 see below) and one \`plans/<slug>/NN-<phase>.md\` per phase.
|
|
1310
|
+
4. Every phase file MUST be resumable on its own \u2014 see Phase file contract. Reference real source paths and the relevant \`docs/\` spec.
|
|
1311
|
+
5. Report the created plan paths and the recommended starting phase. Append durable planning lessons to \`.claude/agent-memory/planner/MEMORY.md\`.
|
|
1312
|
+
|
|
1313
|
+
## Phase file contract (this is what makes plans resumable)
|
|
1314
|
+
|
|
1315
|
+
Each \`NN-<phase>.md\` begins with frontmatter:
|
|
1316
|
+
|
|
1317
|
+
\`\`\`
|
|
1318
|
+
---
|
|
1319
|
+
phase: NN
|
|
1320
|
+
title: <short title>
|
|
1321
|
+
status: pending # pending | in-progress | done | blocked
|
|
1322
|
+
depends_on: [<NN>, ...]
|
|
1323
|
+
---
|
|
1324
|
+
\`\`\`
|
|
1325
|
+
|
|
1326
|
+
Body, in this order:
|
|
1327
|
+
- **Goal** \u2014 one sentence; what "done" means.
|
|
1328
|
+
- **Steps** \u2014 a \`- [ ]\` checklist; each item is a concrete, single action tied to a real path.
|
|
1329
|
+
- **Verify** \u2014 explicit, runnable success criteria. A phase is only \`done\` when these pass.
|
|
1330
|
+
- **Notes / risks** \u2014 gotchas, rollback hints.
|
|
1331
|
+
|
|
1332
|
+
The executor resumes by finding the first phase whose \`status\` is not \`done\` and continuing at its first unchecked step.
|
|
1333
|
+
|
|
1334
|
+
## Progress tracker (00-overview.md is the single index)
|
|
1335
|
+
|
|
1336
|
+
\`00-overview.md\` is the only tracker \u2014 never add a separate \`index.md\` per folder. End it with an **Ordered phases** table that doubles as the resume-from view:
|
|
1337
|
+
|
|
1338
|
+
\`\`\`
|
|
1339
|
+
| # | Phase | Status | Steps | Updated |
|
|
1340
|
+
|---|---|---|---|---|
|
|
1341
|
+
| 01 | <phase> | pending | 0/<n> | <ISO date> |
|
|
1342
|
+
\`\`\`
|
|
1343
|
+
|
|
1344
|
+
- **Status** mirrors each phase file's frontmatter \`status\`; **Steps** is \`<checked>/<total>\` of that phase's \`- [ ]\` checklist; **Updated** is the ISO date the row last changed.
|
|
1345
|
+
- This table is the one place that aggregates across phases. Keep it in sync whenever a phase's status, step count, or block state changes; a \`blocked\` row carries its \`backlog/<id>\` link inline.
|
|
1346
|
+
|
|
1347
|
+
## Backlog integration
|
|
1348
|
+
|
|
1349
|
+
- A phase that the executor parks gets \`status: blocked\` and a link to a \`backlog/<id>\` entry (see \`backlog/README.md\`). Treat blocked phases as paused, not failed \u2014 they don't invalidate completed work.
|
|
1350
|
+
- When a backlog item is revived, read it for context and fold its **Suggested direction** into new or amended phases here. Backlog holds context; \`plans/\` holds the executable plan.
|
|
1351
|
+
|
|
1352
|
+
## Hard rules
|
|
1353
|
+
|
|
1354
|
+
- Write ONLY under \`plans/\` (and your own \`.claude/agent-memory/planner/\`). Never edit source code, never write code, never run/modify the build. Your toolset has no Bash, and a PreToolUse hook hard-blocks any write outside \`plans/\` \u2014 if you feel the urge to implement, produce the plan and hand off to \`dev\` instead.
|
|
1355
|
+
- Never write feature specs \u2014 that is docs-writer's job (specs = WHAT, plans = HOW/when). Flag when a spec is missing instead of writing one.
|
|
1356
|
+
- Plans are consumable artifacts: keep them concrete and current, not aspirational prose.
|
|
1357
|
+
- Never edit \`docs/INDEX.md\` or any docs/ file.
|
|
1358
|
+
- Never commit or push \u2014 a human does that.
|
|
1359
|
+
- Non-blocking follow-up work (spec gaps, deferred tasks, adjacent improvements) must be filed as a \`backlog/<NNNN>-<slug>.md\` entry (see \`backlog/README.md\`). Do NOT leave follow-up work as prose in a plan's overview or phase files \u2014 prose in done plans is archived and lost.
|
|
1360
|
+
`;
|
|
1361
|
+
advisorAgentTemplate = (projectName, slug4, agent) => `---
|
|
1362
|
+
name: advisor
|
|
1363
|
+
description: "Read-only brainstorming & advisory agent for ${projectName} \u2014 the engineer who understands the source logic most deeply. Reads the code, reasons about trade-offs, and returns the best, most optimal recommendation; never edits anything. <example>user: 'Should this be one flat interface or split apart? Talk me through it' \u2192 advisor <commentary>design brainstorm / trade-off analysis, no code change</commentary></example> <example>user: 'What is the cleanest way to add this option without bloating things?' \u2192 advisor <commentary>wants the optimal approach before any implementation</commentary></example> <example>user: 'Add the option' \u2192 dev, NOT advisor <commentary>actual implementation belongs to dev</commentary></example> <example>user: 'Break this into a resumable plan' \u2192 planner, NOT advisor <commentary>plan artifacts belong to planner</commentary></example>"
|
|
1364
|
+
model: ${agent.model}${agentMemoryFrontmatter(agent)}
|
|
1365
|
+
tools: ${agentTools(agent)}
|
|
1366
|
+
---
|
|
1367
|
+
|
|
1368
|
+
You are the advisor for ${projectName}. You are the engineer who holds the **deepest, most accurate mental model of the source** and the trade-offs baked into it. Your job is to read, reason, and recommend \u2014 to brainstorm with the user and hand them the single best path forward. You do not write code, docs, or plans; \`dev\`, \`docs-writer\`, and \`planner\` do that after you've clarified the thinking.
|
|
1369
|
+
|
|
1370
|
+
## Onboarding protocol (in order, before advising)
|
|
1371
|
+
|
|
1372
|
+
1. Read \`.claude/agent-memory/advisor/MEMORY.md\` \u2014 accumulated architectural insights and recurring trade-offs.
|
|
1373
|
+
2. Read \`docs/INDEX.md\` and the relevant \`docs/features/<feature>/\` spec(s) for the area in question \u2014 the spec is the source of truth for WHAT.
|
|
1374
|
+
3. Load the \`${slug4}-conventions\` skill for the project's patterns and rules.
|
|
1375
|
+
4. Read the actual source under discussion. Never advise from memory or assumption when the file is one Read away \u2014 ground every claim in a real path/line.
|
|
1376
|
+
|
|
1377
|
+
## Workflow
|
|
1378
|
+
|
|
1379
|
+
1. Restate the question and the real goal behind it. If interpretations conflict, surface them \u2014 don't silently pick one.
|
|
1380
|
+
2. Read enough source to be certain. Trace the real data flow rather than guessing at it.
|
|
1381
|
+
3. Brainstorm: lay out the viable options with their concrete trade-offs (simplicity, coupling, correctness, maintenance cost).
|
|
1382
|
+
4. **Give a recommendation, not a survey.** Name the single best option, say why it wins, and cite the files/lines that justify it. Note the cheapest next step and which agent owns it (\`dev\` / \`planner\` / \`docs-writer\`).
|
|
1383
|
+
5. Append durable architectural insights to \`.claude/agent-memory/advisor/MEMORY.md\`.
|
|
1384
|
+
|
|
1385
|
+
## What "best advice" means here
|
|
1386
|
+
|
|
1387
|
+
- Favor the **minimum** change that solves the problem. Call out over-engineering explicitly.
|
|
1388
|
+
- Optimal \u2260 clever. Prefer the option a senior engineer would call obvious over the one that's impressive.
|
|
1389
|
+
- When the user's instinct is wrong, say so plainly and explain the cost \u2014 pushing back is the job, not friction.
|
|
1390
|
+
|
|
1391
|
+
## Hard rules
|
|
1392
|
+
|
|
1393
|
+
- **Read-only. Never edit, create, or delete any file** \u2014 not source, not \`docs/\`, not \`plans/\`, not \`backlog/\`. The only file you ever write is your own \`.claude/agent-memory/advisor/MEMORY.md\` (insights). If a change is warranted, recommend it and route it to the owning agent.
|
|
1394
|
+
- Never run the build, never commit, never push.
|
|
1395
|
+
- Ground claims in real paths/lines; if you haven't read it, say so instead of asserting.
|
|
1396
|
+
- Hand off, don't implement: end with a clear, actionable recommendation and who should execute it.
|
|
1397
|
+
`;
|
|
1398
|
+
scoutAgentTemplate = (projectName, slug4, agent) => `---
|
|
1399
|
+
name: scout
|
|
1400
|
+
description: "Fast, cheap read-only Q&A & lookup agent for ${projectName} \u2014 reads, synthesizes, and answers in a few sentences with path:line citations. Use for factual questions about the codebase/docs, not design reasoning (\u2192 advisor) or implementation (\u2192 dev). <example>user: 'What version does this project pin for X?' \u2192 scout <commentary>factual lookup, answer + cite the source</commentary></example> <example>user: 'Is there a spec for feature X yet?' \u2192 scout <commentary>doc existence check via docs/INDEX.md</commentary></example> <example>user: 'Should this be split apart? Talk me through the trade-offs' \u2192 advisor, NOT scout <commentary>design reasoning belongs to advisor</commentary></example> <example>user: 'Add the option' \u2192 dev, NOT scout <commentary>implementation belongs to dev</commentary></example>"
|
|
1401
|
+
model: ${agent.model}
|
|
1402
|
+
tools: ${agentTools(agent)}
|
|
1403
|
+
---
|
|
1404
|
+
|
|
1405
|
+
You are scout for ${projectName}. You answer factual questions about the codebase and docs **quickly and cheaply** \u2014 read what you need, synthesize, and reply in a few sentences. You do not reason about design trade-offs (that is \`advisor\`), and you never edit anything (that is \`dev\` / \`docs-writer\` / \`planner\`).
|
|
1406
|
+
|
|
1407
|
+
## Lookup protocol (DOCS-FIRST)
|
|
1408
|
+
|
|
1409
|
+
1. If the question touches a documented feature, start at \`docs/INDEX.md\` and read the relevant \`docs/features/<feature>/\` spec before opening source. Load the \`${slug4}-docs\` skill when you need to locate a doc.
|
|
1410
|
+
2. Otherwise grep/glob to the right file fast. Trace the real data flow rather than guessing.
|
|
1411
|
+
3. Read only the lines you need to be certain \u2014 excerpts, not whole files. Stop as soon as you can answer.
|
|
1412
|
+
|
|
1413
|
+
## Answer style
|
|
1414
|
+
|
|
1415
|
+
- **Lead with the answer**, then a one-line why/where. Keep it to a few sentences.
|
|
1416
|
+
- **Always cite** the real \`path:line\` you got it from. If you didn't read it, say so \u2014 never assert from memory.
|
|
1417
|
+
- If the question is ambiguous, ask one short clarifying question instead of guessing.
|
|
1418
|
+
|
|
1419
|
+
## Hard rules
|
|
1420
|
+
|
|
1421
|
+
- **Read-only.** Never create, edit, or delete any file. No Bash, no build, no commit.
|
|
1422
|
+
- **Stay in your lane \u2014 hand off, don't expand scope:**
|
|
1423
|
+
- Design / trade-off / "what's the best approach?" \u2192 recommend \`advisor\`.
|
|
1424
|
+
- Implementation, bug fix, new option \u2192 recommend \`dev\`.
|
|
1425
|
+
- Writing or updating docs \u2192 recommend \`docs-writer\`; multi-phase plan \u2192 \`planner\`.
|
|
1426
|
+
- If answering would require running code or reasoning through a non-trivial design decision, stop and route it rather than overreaching.
|
|
1427
|
+
`;
|
|
1428
|
+
plansReadmeTemplate = () => `# plans/
|
|
1429
|
+
|
|
1430
|
+
Resumable, phase-by-phase implementation plans authored by the \`planner\` agent and executed by the \`dev\` agent.
|
|
1431
|
+
|
|
1432
|
+
A plan is a **consumable** artifact (short-lived, kept in sync with reality), distinct from \`docs/\` which holds long-lived feature specs (WHAT). Plans describe **HOW and in what order** work gets done.
|
|
1433
|
+
|
|
1434
|
+
## Layout
|
|
1435
|
+
|
|
1436
|
+
\`\`\`
|
|
1437
|
+
plans/
|
|
1438
|
+
<slug>/
|
|
1439
|
+
00-overview.md goal, scope, non-goals, + the progress tracker (see below)
|
|
1440
|
+
01-<phase>.md one file per phase
|
|
1441
|
+
02-<phase>.md
|
|
1442
|
+
...
|
|
1443
|
+
\`\`\`
|
|
1444
|
+
|
|
1445
|
+
- \`<slug>\` is kebab-case, derived from the feature/story.
|
|
1446
|
+
- Phases are numbered \`NN-\` so they sort in execution order.
|
|
1447
|
+
|
|
1448
|
+
\`00-overview.md\` is the single tracker for the plan \u2014 there is **no separate \`index.md\`**. Its **Ordered phases** table is the at-a-glance "where are we" view; each phase file's \`status\` frontmatter is the per-phase source of truth. The table aggregates across phases, so keep it in sync whenever a phase's status or step count changes.
|
|
1449
|
+
|
|
1450
|
+
## Progress tracker (the Ordered phases table)
|
|
1451
|
+
|
|
1452
|
+
\`00-overview.md\` ends with this table \u2014 it is what an executor reads first to know where to resume:
|
|
1453
|
+
|
|
1454
|
+
\`\`\`
|
|
1455
|
+
| # | Phase | Status | Steps | Updated |
|
|
1456
|
+
|---|---|---|---|---|
|
|
1457
|
+
| 01 | <phase> | done | 5/5 | 2026-01-01 |
|
|
1458
|
+
| 02 | <phase> | in-progress | 2/6 | 2026-01-01 |
|
|
1459
|
+
\`\`\`
|
|
1460
|
+
|
|
1461
|
+
- **Status** \u2014 mirrors the phase file's frontmatter \`status\` (\`pending | in-progress | done | blocked\`).
|
|
1462
|
+
- **Steps** \u2014 \`<checked>/<total>\` of the phase's \`- [ ]\` checklist; the quick "how far in" signal.
|
|
1463
|
+
- **Updated** \u2014 ISO date the row last changed.
|
|
1464
|
+
|
|
1465
|
+
A \`blocked\` row should also carry the \`backlog/<id>\` link inline (e.g. \`blocked \u2192 backlog/0003\`).
|
|
1466
|
+
|
|
1467
|
+
## Phase file frontmatter
|
|
1468
|
+
|
|
1469
|
+
\`\`\`
|
|
1470
|
+
---
|
|
1471
|
+
phase: NN
|
|
1472
|
+
title: <short title>
|
|
1473
|
+
status: pending # pending | in-progress | done | blocked
|
|
1474
|
+
depends_on: [<NN>, ...]
|
|
1475
|
+
---
|
|
1476
|
+
\`\`\`
|
|
1477
|
+
|
|
1478
|
+
Body sections, in order: **Goal**, **Steps** (\`- [ ]\` checklist), **Verify** (runnable success criteria), **Notes / risks**.
|
|
1479
|
+
|
|
1480
|
+
## Resuming a plan
|
|
1481
|
+
|
|
1482
|
+
1. Open \`00-overview.md\` and read the **Ordered phases** table for overall progress.
|
|
1483
|
+
2. Find the first phase whose \`status\` is not \`done\`; its **Steps** cell shows how far in it is.
|
|
1484
|
+
3. Continue at its first unchecked \`- [ ]\` step in the phase file.
|
|
1485
|
+
4. After each step, update the phase's **Steps** count and **Updated** date in the table. When a phase's \`Verify\` passes, set its frontmatter \`status: done\` and flip the table row to \`done\`.
|
|
1486
|
+
|
|
1487
|
+
A failure in one phase never invalidates completed phases \u2014 fix and resume from the unfinished phase.
|
|
1488
|
+
|
|
1489
|
+
## Blocked phases \u2192 backlog
|
|
1490
|
+
|
|
1491
|
+
If a phase can't proceed and the cause isn't fixable right now (missing info, needs a user decision, environment/out-of-scope), don't loop. Apply the **park rule** in \`backlog/README.md\`: set the phase \`status: blocked\`, file a \`backlog/<id>\` entry, link both ways (\`[[backlog/<id>]]\` from the phase; the entry's \`source:\` points back to the phase file), then move on to the next workable phase.
|
|
1492
|
+
|
|
1493
|
+
## Plan lifecycle and archival
|
|
1494
|
+
|
|
1495
|
+
When all phases are \`done\`, the plan becomes a completed artifact \u2014 it can either:
|
|
1496
|
+
- **Remain in \`plans/\`** as historical record (git preserves it; useful for auditing how the work was actually decomposed).
|
|
1497
|
+
- **Be archived** to \`plans/.archive/<slug>/\` (keep it searchable but out of the active directory).
|
|
1498
|
+
- **Be deleted** if context is not valuable (rare; prefer archival so git history is preserved).
|
|
1499
|
+
|
|
1500
|
+
Choose based on project norms. The key: **plans are owned by whoever executed them** (usually \`dev\`) and the decision to archive/delete is theirs. Do not leave a stale plan in \`plans/\` \u2014 either keep it active (if next phases are coming) or archive it. The \`00-overview.md\` progress table is the arbiter: if all rows are \`done\` and no new work is queued, the plan is complete.
|
|
1501
|
+
`;
|
|
1502
|
+
backlogReadmeTemplate = () => `# backlog/
|
|
1503
|
+
|
|
1504
|
+
Unfinished work: blockers that can't be solved right now, technical debt, or anything **deliberately deferred** so it doesn't derail the current flow.
|
|
1505
|
+
|
|
1506
|
+
How it differs from \`docs/\` and \`plans/\`:
|
|
1507
|
+
|
|
1508
|
+
| | \`docs/\` | \`plans/\` | \`backlog/\` |
|
|
1509
|
+
|---|---|---|---|
|
|
1510
|
+
| Nature | WHAT \u2014 long-lived spec | HOW/when \u2014 phases to run | deferred work / blockers / tech debt |
|
|
1511
|
+
| Lifecycle | long-lived | consumable, deleted when done | append-only log, lives until \`resolved\` |
|
|
1512
|
+
| Author | docs-writer | planner | any agent that hits a blocker (usually \`dev\`) |
|
|
1513
|
+
|
|
1514
|
+
A backlog item is **not** a plan. When an item is revived, \`planner\` reads it and produces new phases under \`plans/\` \u2014 backlog holds context, not the executable plan.
|
|
1515
|
+
|
|
1516
|
+
## Park rule (this is what stops the token-wasting loop)
|
|
1517
|
+
|
|
1518
|
+
Applies while executing a step/phase and hitting a problem:
|
|
1519
|
+
|
|
1520
|
+
> If a step **fails twice** and the cause is **not fixable right now** \u2014 missing info, needs a user decision, environment error, or out of scope \u2014 then **STOP, do not retry a third time**:
|
|
1521
|
+
> 1. Set the owning phase \`status: blocked\` (see \`plans/README.md\`).
|
|
1522
|
+
> 2. File a new entry in \`backlog/\` (template below); its **Tried** section must list what already failed so it isn't repeated.
|
|
1523
|
+
> 3. Link both ways: the phase file points to \`backlog/<id>\`, the entry points back to the phase.
|
|
1524
|
+
> 4. Move on to the next workable phase/task. Tell the user it was parked \u2014 don't silently skip it.
|
|
1525
|
+
|
|
1526
|
+
If the error is fixable on the spot, just fix it \u2014 this rule is only for real blockers.
|
|
1527
|
+
|
|
1528
|
+
## Layout
|
|
1529
|
+
|
|
1530
|
+
One item = one file: \`backlog/<NNN>-<slug>.md\` (\`NNN\` ascending so items sort and are easy to reference, e.g. "backlog/004").
|
|
1531
|
+
|
|
1532
|
+
## Frontmatter
|
|
1533
|
+
|
|
1534
|
+
\`\`\`
|
|
1535
|
+
---
|
|
1536
|
+
id: NNN
|
|
1537
|
+
title: <short title>
|
|
1538
|
+
status: open # open | resolved | wontfix
|
|
1539
|
+
source: plans/<slug>/NN-<phase>.md # or "conversation", or the originating file path
|
|
1540
|
+
severity: low # low | medium | high
|
|
1541
|
+
created: YYYY-MM-DD
|
|
1542
|
+
---
|
|
1543
|
+
\`\`\`
|
|
1544
|
+
|
|
1545
|
+
Body, in order:
|
|
1546
|
+
|
|
1547
|
+
- **Symptom** \u2014 what happened and where (real file paths).
|
|
1548
|
+
- **Tried** \u2014 what was run/changed and how it failed. The most important section: prevents repeating failed attempts.
|
|
1549
|
+
- **Why parked** \u2014 why it can't be solved now (missing info / needs user / environment / out of scope).
|
|
1550
|
+
- **Suggested direction** \u2014 the next step if any, or the question the user needs to answer.
|
|
1551
|
+
|
|
1552
|
+
Link to other phases/specs/items with \`[[path]]\`.
|
|
1553
|
+
|
|
1554
|
+
## Lifecycle
|
|
1555
|
+
|
|
1556
|
+
- When solved: set \`status: resolved\` and add a one-line conclusion at the end of the body \u2014 **don't delete the file** (keep the history). To drop it entirely, set \`status: wontfix\` with a reason.
|
|
1557
|
+
- When revived: hand the context to \`planner\` to create new phases under \`plans/\`; the entry stays \`open\` until the work is actually done.
|
|
1148
1558
|
`;
|
|
1149
1559
|
agentMemorySeedTemplate = (agentName) => `# ${agentName} \u2014 Agent Memory
|
|
1150
1560
|
|
|
1151
|
-
Append durable, non-obvious gotchas and patterns discovered while working \u2014 one bullet each, newest first. Link related docs/ files. Read this file at the start of every session.
|
|
1561
|
+
Append durable, non-obvious gotchas and patterns discovered while working \u2014 one bullet each, newest first. Link related docs/ files. Read this file at the start of every session.
|
|
1562
|
+
|
|
1563
|
+
_(no entries yet)_
|
|
1564
|
+
`;
|
|
1565
|
+
validateStructureMjsTemplate = (agents) => {
|
|
1566
|
+
const scopesObj = {};
|
|
1567
|
+
for (const agent of agents) {
|
|
1568
|
+
scopesObj[agent.name] = agent.writeScope;
|
|
1569
|
+
}
|
|
1570
|
+
const scopesJson = JSON.stringify(scopesObj, null, 2);
|
|
1571
|
+
return `// Mechanically checks derived invariants between the AGENTS registry, emitted
|
|
1572
|
+
// agent .md files, and the guard. Generated from the AGENTS registry at scaffold
|
|
1573
|
+
// time \u2014 do not edit by hand.
|
|
1574
|
+
// Run via: node .claude/scripts/validate-structure.mjs
|
|
1575
|
+
import { readdirSync, readFileSync } from 'fs';
|
|
1576
|
+
import { join } from 'path';
|
|
1577
|
+
|
|
1578
|
+
// Baked at scaffold time from the AGENTS registry.
|
|
1579
|
+
const WRITE_SCOPES = ${scopesJson};
|
|
1580
|
+
|
|
1581
|
+
// Minimal frontmatter parser (no external deps \u2014 inlined from _docs-shared.mjs pattern).
|
|
1582
|
+
function parseFrontmatter(content) {
|
|
1583
|
+
const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);
|
|
1584
|
+
if (!match) return null;
|
|
1585
|
+
const meta = {};
|
|
1586
|
+
for (const line of match[1].split('\\n')) {
|
|
1587
|
+
const idx = line.indexOf(':');
|
|
1588
|
+
if (idx === -1) continue;
|
|
1589
|
+
const key = line.slice(0, idx).trim();
|
|
1590
|
+
let value = line.slice(idx + 1).trim();
|
|
1591
|
+
if (!key) continue;
|
|
1592
|
+
// Strip inline comments.
|
|
1593
|
+
value = value.replace(/\\s+#.*$/, '').trim();
|
|
1594
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
1595
|
+
const inner = value.slice(1, -1).trim();
|
|
1596
|
+
meta[key] = inner === ''
|
|
1597
|
+
? []
|
|
1598
|
+
: inner.split(',').map((item) => item.trim().replace(/^['"]|['"]$/g, ''));
|
|
1599
|
+
} else {
|
|
1600
|
+
meta[key] = value.replace(/^['"]|['"]$/g, '');
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return meta;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
const AGENTS_DIR = '.claude/agents';
|
|
1607
|
+
const errors = [];
|
|
1608
|
+
|
|
1609
|
+
// 1. Read and parse all agent .md files.
|
|
1610
|
+
let agentFiles;
|
|
1611
|
+
try {
|
|
1612
|
+
agentFiles = readdirSync(AGENTS_DIR).filter((f) => f.endsWith('.md'));
|
|
1613
|
+
} catch {
|
|
1614
|
+
console.error('validate-structure: cannot read ' + AGENTS_DIR);
|
|
1615
|
+
process.exit(1);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
const parsed = [];
|
|
1619
|
+
for (const file of agentFiles) {
|
|
1620
|
+
const fullPath = join(AGENTS_DIR, file);
|
|
1621
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
1622
|
+
const fm = parseFrontmatter(content);
|
|
1623
|
+
if (!fm) {
|
|
1624
|
+
errors.push(file + ': missing frontmatter block');
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
const name = fm.name ?? file.replace(/\\.md$/, '');
|
|
1628
|
+
const toolsRaw = fm.tools ?? '';
|
|
1629
|
+
parsed.push({ file, name, toolsRaw });
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// 2. Check read-only constraint: if writeScope is empty, tools must not include Write or Edit.
|
|
1633
|
+
for (const { file, name, toolsRaw } of parsed) {
|
|
1634
|
+
if (!(name in WRITE_SCOPES)) continue; // unknown agent \u2014 skip
|
|
1635
|
+
const scope = WRITE_SCOPES[name];
|
|
1636
|
+
if (scope.length > 0) continue; // write-capable agent \u2014 constraint does not apply
|
|
1637
|
+
// Read-only agent: assert no Write/Edit in tools list.
|
|
1638
|
+
const tools = toolsRaw.split(',').map((t) => t.trim());
|
|
1639
|
+
for (const tool of tools) {
|
|
1640
|
+
if (tool === 'Write' || tool === 'Edit') {
|
|
1641
|
+
errors.push(
|
|
1642
|
+
file + ': read-only agent "' + name + '" must not have "' + tool +
|
|
1643
|
+
'" in its tools: list (writeScope is empty)'
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// 3. Check uniqueness of primary owned directories (first element of writeScope).
|
|
1650
|
+
const primaryDirOwners = {};
|
|
1651
|
+
for (const [agentName, scope] of Object.entries(WRITE_SCOPES)) {
|
|
1652
|
+
if (scope.length === 0) continue; // read-only agents have no primary dir
|
|
1653
|
+
const primaryDir = scope[0];
|
|
1654
|
+
if (primaryDirOwners[primaryDir]) {
|
|
1655
|
+
errors.push(
|
|
1656
|
+
'agents "' + primaryDirOwners[primaryDir] + '" and "' + agentName +
|
|
1657
|
+
'" share the same primary owned directory "' + primaryDir + '"'
|
|
1658
|
+
);
|
|
1659
|
+
} else {
|
|
1660
|
+
primaryDirOwners[primaryDir] = agentName;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
if (errors.length > 0) {
|
|
1665
|
+
console.error('validate-structure: failed with ' + errors.length + ' error(s):');
|
|
1666
|
+
for (const error of errors) console.error(' - ' + error);
|
|
1667
|
+
process.exit(1);
|
|
1668
|
+
}
|
|
1669
|
+
console.log('validate-structure: passed.');
|
|
1670
|
+
`;
|
|
1671
|
+
};
|
|
1672
|
+
validatePlansMjsTemplate = () => `// Mechanically checks plan/backlog consistency across four invariants.
|
|
1673
|
+
// Run via: node .claude/scripts/validate-plans.mjs
|
|
1674
|
+
import { readdirSync, readFileSync, existsSync } from 'fs';
|
|
1675
|
+
import { join } from 'path';
|
|
1676
|
+
|
|
1677
|
+
const PLANS_DIR = 'plans';
|
|
1678
|
+
const BACKLOG_DIR = 'backlog';
|
|
1679
|
+
|
|
1680
|
+
// Minimal frontmatter parser \u2014 same pattern as validate-structure.mjs (no external deps).
|
|
1681
|
+
function parseFrontmatter(content) {
|
|
1682
|
+
const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);
|
|
1683
|
+
if (!match) return null;
|
|
1684
|
+
const meta = {};
|
|
1685
|
+
for (const line of match[1].split('\\n')) {
|
|
1686
|
+
const idx = line.indexOf(':');
|
|
1687
|
+
if (idx === -1) continue;
|
|
1688
|
+
const key = line.slice(0, idx).trim();
|
|
1689
|
+
let value = line.slice(idx + 1).trim();
|
|
1690
|
+
if (!key) continue;
|
|
1691
|
+
value = value.replace(/\\s+#.*$/, '').trim();
|
|
1692
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
1693
|
+
const inner = value.slice(1, -1).trim();
|
|
1694
|
+
meta[key] = inner === ''
|
|
1695
|
+
? []
|
|
1696
|
+
: inner.split(',').map((item) => item.trim().replace(/^['"]|['"]$/g, ''));
|
|
1697
|
+
} else {
|
|
1698
|
+
meta[key] = value.replace(/^['"]|['"]$/g, '');
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
return meta;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// Parse the "Ordered phases" Markdown table from an overview file body.
|
|
1705
|
+
// Returns array of { num, slug, status } where num is zero-padded (e.g. "01").
|
|
1706
|
+
// Only parses rows from the section headed "## Ordered phases" (case-insensitive).
|
|
1707
|
+
function parseOrderedPhasesTable(content) {
|
|
1708
|
+
const rows = [];
|
|
1709
|
+
// Find the "Ordered phases" section and extract only that table.
|
|
1710
|
+
const sectionMatch = content.match(/##\\s+Ordered phases\\b[\\s\\S]*?(?=\\n##\\s|\\n---\\n|$)/i);
|
|
1711
|
+
if (!sectionMatch) return rows;
|
|
1712
|
+
const section = sectionMatch[0];
|
|
1713
|
+
for (const line of section.split('\\n')) {
|
|
1714
|
+
// Match data rows: | 01 | slug | status | ... |
|
|
1715
|
+
if (!/^\\|\\s*\\d+\\s*\\|/.test(line)) continue;
|
|
1716
|
+
const cells = line.split('|').map((c) => c.trim()).filter(Boolean);
|
|
1717
|
+
if (cells.length < 3) continue;
|
|
1718
|
+
const num = cells[0].padStart(2, '0');
|
|
1719
|
+
const slug = cells[1];
|
|
1720
|
+
const status = cells[2].toLowerCase();
|
|
1721
|
+
rows.push({ num, slug, status });
|
|
1722
|
+
}
|
|
1723
|
+
return rows;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// Collect active plan dirs (skip .archive and README.md).
|
|
1727
|
+
function getActivePlanDirs() {
|
|
1728
|
+
let entries;
|
|
1729
|
+
try {
|
|
1730
|
+
entries = readdirSync(PLANS_DIR, { withFileTypes: true });
|
|
1731
|
+
} catch {
|
|
1732
|
+
return [];
|
|
1733
|
+
}
|
|
1734
|
+
return entries
|
|
1735
|
+
.filter((e) => e.isDirectory() && e.name !== '.archive' && !e.name.startsWith('.'))
|
|
1736
|
+
.map((e) => e.name);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
// Collect all plan dirs including .archive.
|
|
1740
|
+
function getAllPlanDirs() {
|
|
1741
|
+
let entries;
|
|
1742
|
+
try {
|
|
1743
|
+
const archiveEntries = readdirSync(join(PLANS_DIR, '.archive'), { withFileTypes: true });
|
|
1744
|
+
const activeEntries = readdirSync(PLANS_DIR, { withFileTypes: true });
|
|
1745
|
+
return [
|
|
1746
|
+
...activeEntries.filter((e) => e.isDirectory() && !e.name.startsWith('.')).map((e) => ({ name: e.name, archived: false })),
|
|
1747
|
+
...archiveEntries.filter((e) => e.isDirectory()).map((e) => ({ name: e.name, archived: true })),
|
|
1748
|
+
];
|
|
1749
|
+
} catch {
|
|
1750
|
+
return getActivePlanDirs().map((name) => ({ name, archived: false }));
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
const errors = [];
|
|
1755
|
+
const warnings = [];
|
|
1756
|
+
|
|
1757
|
+
// \u2500\u2500 Check A \u2014 Phase table \u2194 frontmatter status consistency \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1758
|
+
for (const planDir of getActivePlanDirs()) {
|
|
1759
|
+
const overviewPath = join(PLANS_DIR, planDir, '00-overview.md');
|
|
1760
|
+
if (!existsSync(overviewPath)) continue;
|
|
1761
|
+
const overviewContent = readFileSync(overviewPath, 'utf-8');
|
|
1762
|
+
const rows = parseOrderedPhasesTable(overviewContent);
|
|
1763
|
+
for (const { num, slug, status: tableStatus } of rows) {
|
|
1764
|
+
const phaseFile = join(PLANS_DIR, planDir, \`\${num}-\${slug}.md\`);
|
|
1765
|
+
if (!existsSync(phaseFile)) {
|
|
1766
|
+
warnings.push(\`WARN \${phaseFile}: phase file not found (table row \${num} references it)\`);
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1769
|
+
const phaseContent = readFileSync(phaseFile, 'utf-8');
|
|
1770
|
+
const fm = parseFrontmatter(phaseContent);
|
|
1771
|
+
if (!fm) {
|
|
1772
|
+
warnings.push(\`WARN \${phaseFile}: missing frontmatter \u2014 cannot compare with table status\`);
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
const fmStatus = (fm.status ?? '').toLowerCase();
|
|
1776
|
+
if (fmStatus !== tableStatus) {
|
|
1777
|
+
warnings.push(
|
|
1778
|
+
\`WARN \${phaseFile}: phase frontmatter status "\${fmStatus}" but table row says "\${tableStatus}"\`
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// \u2500\u2500 Check B \u2014 All-done plans not yet archived \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1785
|
+
for (const planDir of getActivePlanDirs()) {
|
|
1786
|
+
const overviewPath = join(PLANS_DIR, planDir, '00-overview.md');
|
|
1787
|
+
if (!existsSync(overviewPath)) continue;
|
|
1788
|
+
const overviewContent = readFileSync(overviewPath, 'utf-8');
|
|
1789
|
+
const rows = parseOrderedPhasesTable(overviewContent);
|
|
1790
|
+
if (rows.length === 0) continue;
|
|
1791
|
+
if (rows.every(({ status }) => status === 'done')) {
|
|
1792
|
+
warnings.push(
|
|
1793
|
+
\`WARN plans/\${planDir}/00-overview.md: all phases done \u2014 consider archiving to plans/.archive/\`
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
// \u2500\u2500 Check C \u2014 Backlog ID uniqueness and sequence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1799
|
+
const REQUIRED_BACKLOG_FIELDS = ['id', 'title', 'status', 'source', 'severity', 'created'];
|
|
1800
|
+
const VALID_BACKLOG_STATUSES = ['open', 'resolved', 'wontfix'];
|
|
1801
|
+
const seenIds = new Set();
|
|
1802
|
+
|
|
1803
|
+
let backlogFiles;
|
|
1804
|
+
try {
|
|
1805
|
+
backlogFiles = readdirSync(BACKLOG_DIR).filter((f) => /^\\d{4}-.*\\.md$/.test(f));
|
|
1806
|
+
} catch {
|
|
1807
|
+
backlogFiles = [];
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
for (const file of backlogFiles) {
|
|
1811
|
+
const filePath = join(BACKLOG_DIR, file);
|
|
1812
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
1813
|
+
const fm = parseFrontmatter(content);
|
|
1814
|
+
const prefix = file.slice(0, 4); // "0001"
|
|
1815
|
+
|
|
1816
|
+
if (!fm) {
|
|
1817
|
+
errors.push(\`ERROR backlog/\${file}: missing frontmatter block\`);
|
|
1818
|
+
continue;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
// Required fields
|
|
1822
|
+
for (const field of REQUIRED_BACKLOG_FIELDS) {
|
|
1823
|
+
if (fm[field] === undefined || fm[field] === '') {
|
|
1824
|
+
errors.push(\`ERROR backlog/\${file}: missing required frontmatter field "\${field}"\`);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
// ID uniqueness
|
|
1829
|
+
const id = fm.id ?? '';
|
|
1830
|
+
if (id) {
|
|
1831
|
+
if (seenIds.has(id)) {
|
|
1832
|
+
errors.push(\`ERROR backlog/\${file}: duplicate id "\${id}"\`);
|
|
1833
|
+
} else {
|
|
1834
|
+
seenIds.add(id);
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
// ID/filename prefix match
|
|
1838
|
+
if (id.padStart(4, '0') !== prefix) {
|
|
1839
|
+
errors.push(\`ERROR backlog/\${file}: filename prefix "\${prefix}" does not match id "\${id}"\`);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// Status enum
|
|
1844
|
+
const status = fm.status ?? '';
|
|
1845
|
+
if (status && !VALID_BACKLOG_STATUSES.includes(status)) {
|
|
1846
|
+
errors.push(
|
|
1847
|
+
\`ERROR backlog/\${file}: invalid status "\${status}" (allowed: \${VALID_BACKLOG_STATUSES.join(', ')})\`
|
|
1848
|
+
);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
// \u2500\u2500 Check D \u2014 Two-way links between blocked phases and backlog entries \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1853
|
+
// Collect all phase files (non-overview, non-archive).
|
|
1854
|
+
function collectPhaseFiles() {
|
|
1855
|
+
const result = [];
|
|
1856
|
+
for (const planDir of getActivePlanDirs()) {
|
|
1857
|
+
let files;
|
|
1858
|
+
try {
|
|
1859
|
+
files = readdirSync(join(PLANS_DIR, planDir)).filter(
|
|
1860
|
+
(f) => f.endsWith('.md') && f !== '00-overview.md'
|
|
1861
|
+
);
|
|
1862
|
+
} catch {
|
|
1863
|
+
continue;
|
|
1864
|
+
}
|
|
1865
|
+
for (const f of files) {
|
|
1866
|
+
result.push({ path: join(PLANS_DIR, planDir, f), rel: \`plans/\${planDir}/\${f}\` });
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
return result;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
for (const { path: phaseFilePath, rel } of collectPhaseFiles()) {
|
|
1873
|
+
const content = readFileSync(phaseFilePath, 'utf-8');
|
|
1874
|
+
const fm = parseFrontmatter(content);
|
|
1875
|
+
if (!fm || fm.status !== 'blocked') continue;
|
|
1876
|
+
|
|
1877
|
+
// Look for a backlog link in the body.
|
|
1878
|
+
const bodyMatch = content.match(/^---\\n[\\s\\S]*?\\n---\\n([\\s\\S]*)$/);
|
|
1879
|
+
const body = bodyMatch ? bodyMatch[1] : content;
|
|
1880
|
+
const backlogLinkMatch = body.match(/backlog\\/(\\d{4})/g);
|
|
1881
|
+
|
|
1882
|
+
if (!backlogLinkMatch) {
|
|
1883
|
+
warnings.push(\`WARN \${rel}: status blocked but no backlog link found in body\`);
|
|
1884
|
+
continue;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
for (const linkStr of backlogLinkMatch) {
|
|
1888
|
+
const id = linkStr.replace('backlog/', '');
|
|
1889
|
+
// Find the backlog file with this prefix.
|
|
1890
|
+
const matchingFile = (backlogFiles ?? []).find((f) => f.startsWith(id));
|
|
1891
|
+
if (!matchingFile) {
|
|
1892
|
+
warnings.push(\`WARN \${rel}: references \${linkStr} but no backlog/\${id}-*.md file found\`);
|
|
1893
|
+
continue;
|
|
1894
|
+
}
|
|
1895
|
+
const backlogContent = readFileSync(join(BACKLOG_DIR, matchingFile), 'utf-8');
|
|
1896
|
+
const backlogFm = parseFrontmatter(backlogContent);
|
|
1897
|
+
const source = backlogFm?.source ?? '';
|
|
1898
|
+
if (!source.includes(rel.replace(\`plans/\`, ''))) {
|
|
1899
|
+
warnings.push(
|
|
1900
|
+
\`WARN backlog/\${matchingFile}: source field "\${source}" does not reference \${rel}\`
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
// \u2500\u2500 Report \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1907
|
+
const totalErrors = errors.length;
|
|
1908
|
+
const totalWarnings = warnings.length;
|
|
1909
|
+
|
|
1910
|
+
if (totalErrors > 0 || totalWarnings > 0) {
|
|
1911
|
+
console.log(
|
|
1912
|
+
\`validate-plans: \${totalErrors} error(s), \${totalWarnings} warning(s):\`
|
|
1913
|
+
);
|
|
1914
|
+
for (const e of errors) console.error(' ' + e);
|
|
1915
|
+
for (const w of warnings) console.warn(' ' + w);
|
|
1916
|
+
}
|
|
1152
1917
|
|
|
1153
|
-
|
|
1918
|
+
if (totalErrors > 0) {
|
|
1919
|
+
console.error(\`validate-plans: failed with \${totalErrors} error(s).\`);
|
|
1920
|
+
process.exit(1);
|
|
1921
|
+
} else if (totalWarnings > 0) {
|
|
1922
|
+
console.log(\`validate-plans: passed with \${totalWarnings} warning(s).\`);
|
|
1923
|
+
} else {
|
|
1924
|
+
console.log('validate-plans: passed.');
|
|
1925
|
+
}
|
|
1154
1926
|
`;
|
|
1155
1927
|
buildClaudeFileMap = (params) => {
|
|
1156
|
-
const { projectName, slug, flowEnum: flowEnum3, layerEnum: layerEnum3 } = params;
|
|
1928
|
+
const { projectName, slug: slug4, flowEnum: flowEnum3, layerEnum: layerEnum3 } = params;
|
|
1929
|
+
const agentDef = (name) => {
|
|
1930
|
+
const found = AGENTS.find((a) => a.name === name);
|
|
1931
|
+
if (!found) throw new Error(`Agent "${name}" not found in AGENTS registry`);
|
|
1932
|
+
return found;
|
|
1933
|
+
};
|
|
1157
1934
|
const files = [
|
|
1158
1935
|
{ relativePath: "CLAUDE.md", content: params.claudeMd },
|
|
1159
1936
|
{ relativePath: ".claude/settings.json", content: claudeSettingsTemplate() },
|
|
1160
1937
|
{ relativePath: ".claude/scripts/_docs-shared.mjs", content: docsSharedMjsTemplate(flowEnum3, layerEnum3) },
|
|
1161
1938
|
{ relativePath: ".claude/scripts/build-docs-index.mjs", content: buildDocsIndexMjsTemplate() },
|
|
1162
1939
|
{ relativePath: ".claude/scripts/lint-docs-frontmatter.mjs", content: lintDocsFrontmatterMjsTemplate() },
|
|
1940
|
+
{ relativePath: ".claude/scripts/validate-structure.mjs", content: validateStructureMjsTemplate([...AGENTS, ...params.testing ? [TEST_WRITER_DEF] : []]) },
|
|
1941
|
+
{ relativePath: ".claude/scripts/validate-plans.mjs", content: validatePlansMjsTemplate() },
|
|
1163
1942
|
{ relativePath: ".claude/scripts/docs-first-reminder.sh", content: docsFirstReminderShTemplate(params.reminderTrigger) },
|
|
1164
|
-
{ relativePath:
|
|
1165
|
-
{ relativePath: `.claude/skills/${
|
|
1943
|
+
{ relativePath: ".claude/scripts/agent-guard.mjs", content: agentGuardMjsTemplate([...AGENTS, ...params.testing ? [TEST_WRITER_DEF] : []]) },
|
|
1944
|
+
{ relativePath: `.claude/skills/${slug4}-conventions/SKILL.md`, content: params.conventionsSkill },
|
|
1945
|
+
{ relativePath: `.claude/skills/${slug4}-docs/SKILL.md`, content: docsSkillTemplate(projectName, slug4, flowEnum3, layerEnum3) },
|
|
1166
1946
|
{ relativePath: ".claude/agents/dev.md", content: params.devAgent },
|
|
1167
|
-
{ relativePath: ".claude/agents/docs-writer.md", content: docsWriterAgentTemplate(projectName,
|
|
1947
|
+
{ relativePath: ".claude/agents/docs-writer.md", content: docsWriterAgentTemplate(projectName, slug4, agentDef("docs-writer")) },
|
|
1948
|
+
{ relativePath: ".claude/agents/planner.md", content: plannerAgentTemplate(projectName, agentDef("planner")) },
|
|
1949
|
+
{ relativePath: ".claude/agents/advisor.md", content: advisorAgentTemplate(projectName, slug4, agentDef("advisor")) },
|
|
1950
|
+
{ relativePath: ".claude/agents/scout.md", content: scoutAgentTemplate(projectName, slug4, agentDef("scout")) },
|
|
1168
1951
|
{ relativePath: ".claude/agent-memory/dev/MEMORY.md", content: agentMemorySeedTemplate("dev") },
|
|
1169
1952
|
{ relativePath: ".claude/agent-memory/docs-writer/MEMORY.md", content: agentMemorySeedTemplate("docs-writer") },
|
|
1953
|
+
{ relativePath: ".claude/agent-memory/planner/MEMORY.md", content: agentMemorySeedTemplate("planner") },
|
|
1954
|
+
{ relativePath: ".claude/agent-memory/advisor/MEMORY.md", content: agentMemorySeedTemplate("advisor") },
|
|
1955
|
+
{ relativePath: "plans/README.md", content: plansReadmeTemplate() },
|
|
1956
|
+
{ relativePath: "backlog/README.md", content: backlogReadmeTemplate() },
|
|
1170
1957
|
{ relativePath: "docs/README.md", content: docsReadmeTemplate() },
|
|
1171
1958
|
{ relativePath: "docs/INDEX.md", content: docsIndexPlaceholderTemplate() },
|
|
1172
1959
|
{ relativePath: "docs/_template.md", content: docsTemplateMdTemplate(flowEnum3, layerEnum3) },
|
|
@@ -1176,7 +1963,7 @@ _(no entries yet)_
|
|
|
1176
1963
|
files.push(
|
|
1177
1964
|
{ relativePath: ".claude/agents/test-writer.md", content: params.testing.testWriterAgent },
|
|
1178
1965
|
{ relativePath: ".claude/agent-memory/test-writer/MEMORY.md", content: agentMemorySeedTemplate("test-writer") },
|
|
1179
|
-
{ relativePath: `.claude/skills/${
|
|
1966
|
+
{ relativePath: `.claude/skills/${slug4}-test-author/SKILL.md`, content: params.testing.testAuthorSkill }
|
|
1180
1967
|
);
|
|
1181
1968
|
}
|
|
1182
1969
|
return files;
|
|
@@ -1213,7 +2000,7 @@ var init_claude_setup2 = __esm({
|
|
|
1213
2000
|
const hasVitest = cart.testing === "VITEST";
|
|
1214
2001
|
const hasPlaywright = cart.testing === "PLAYWRIGHT";
|
|
1215
2002
|
const hasTesting = cart.testing !== "NOT_USING";
|
|
1216
|
-
const
|
|
2003
|
+
const slug4 = projectSlug(cart);
|
|
1217
2004
|
const stack = [
|
|
1218
2005
|
"React 19, TypeScript 5.8, Vite 6",
|
|
1219
2006
|
hasRouter ? "TanStack Router v1.144.0 (file-based routes in src/routes/)" : null,
|
|
@@ -1230,8 +2017,9 @@ var init_claude_setup2 = __esm({
|
|
|
1230
2017
|
cart.linter !== "NOT_USING" ? "- `npm run lint` \u2014 lint code" : null,
|
|
1231
2018
|
hasVitest ? "- `npm run test:run` \u2014 run unit/component tests once (`npm test` for watch)" : null,
|
|
1232
2019
|
hasPlaywright ? "- `npm run test:e2e` \u2014 run Playwright E2E tests" : null,
|
|
1233
|
-
"- `
|
|
1234
|
-
"- `
|
|
2020
|
+
"- `node .claude/scripts/build-docs-index.mjs` \u2014 regenerate docs/INDEX.md (run after any doc change)",
|
|
2021
|
+
"- `node .claude/scripts/lint-docs-frontmatter.mjs` \u2014 validate docs frontmatter (CI-ready, exits non-zero on violation)",
|
|
2022
|
+
"- `node .claude/scripts/validate-plans.mjs` \u2014 check plan/backlog consistency (table\u2194frontmatter, archived, ID gaps, two-way links)"
|
|
1235
2023
|
].filter(Boolean);
|
|
1236
2024
|
const fsdArchitecture = `\`\`\`
|
|
1237
2025
|
app \u2192 app shell, providers, global setup
|
|
@@ -1357,18 +2145,20 @@ ${testSection}## Agent Routing
|
|
|
1357
2145
|
|
|
1358
2146
|
| Task / trigger | Agent | Notes |
|
|
1359
2147
|
|---|---|---|
|
|
1360
|
-
|
|
1361
|
-
|
|
|
2148
|
+
${claudeHarnessTableTemplate()}
|
|
2149
|
+
| Feature work or bug fix in \`src/\` | \`dev\` | MUST read relevant \`docs/features/\` spec before coding |${testWriterRow}
|
|
2150
|
+
|
|
2151
|
+
**PARK RULE (anti-loop):** when executing a step/phase, if it fails twice and the cause isn't fixable right now (missing info, needs a user decision, environment, or out-of-scope), STOP \u2014 don't retry a third time. Set the phase \`status: blocked\`, file a \`backlog/<id>\` entry (record what was already tried so it isn't repeated), link both ways, tell the user it was parked, and move on to the next workable item. See \`backlog/README.md\`.
|
|
1362
2152
|
|
|
1363
2153
|
Each agent has persistent memory at \`.claude/agent-memory/<agent>/MEMORY.md\` \u2014 agents read it on start and append new gotchas. Do NOT use the general assistant for work an agent owns \u2014 always delegate.
|
|
1364
2154
|
|
|
1365
2155
|
## Task Documentation Convention
|
|
1366
2156
|
|
|
1367
|
-
After any non-trivial fix or new pattern: copy \`docs/_template.md\`, fill the frontmatter, save as \`docs/features/<feature>/<topic>.en.md\` (or \`docs/architecture/\` for cross-cutting topics), then run \`
|
|
2157
|
+
After any non-trivial fix or new pattern: copy \`docs/_template.md\`, fill the frontmatter, save as \`docs/features/<feature>/<topic>.en.md\` (or \`docs/architecture/\` for cross-cutting topics), then run \`node .claude/scripts/build-docs-index.mjs\` and commit the doc together with \`INDEX.md\`. Validate with \`node .claude/scripts/lint-docs-frontmatter.mjs\`.
|
|
1368
2158
|
|
|
1369
2159
|
## Further Reading + DOCS-FIRST RULE
|
|
1370
2160
|
|
|
1371
|
-
Skills: \`.claude/skills/${
|
|
2161
|
+
Skills: \`.claude/skills/${slug4}-conventions\` (architecture depth), \`.claude/skills/${slug4}-docs\` (how to query the knowledge base)${hasTesting ? `, \`.claude/skills/${slug4}-test-author\` (test conventions)` : ""}.
|
|
1372
2162
|
|
|
1373
2163
|
**DOCS-FIRST RULE:** for any request to describe, explain, or modify a documented feature, you MUST grep \`docs/\` frontmatter and read the relevant docs BEFORE opening source files \u2014 and state what the docs already covered. Start at \`docs/INDEX.md\`.
|
|
1374
2164
|
|
|
@@ -1412,9 +2202,9 @@ _(Trade-offs made and alternatives rejected.)_
|
|
|
1412
2202
|
};
|
|
1413
2203
|
conventionsSkillTemplate = (cart) => {
|
|
1414
2204
|
const isFsd = cart.layout === "FSD";
|
|
1415
|
-
const
|
|
2205
|
+
const slug4 = projectSlug(cart);
|
|
1416
2206
|
return `---
|
|
1417
|
-
name: ${
|
|
2207
|
+
name: ${slug4}-conventions
|
|
1418
2208
|
description: Coding conventions and architecture rules for ${cart.projectName}. Use when writing or reviewing ANY code under src/ \u2014 components, pages, hooks${cart.stateManagement === "ZUSTAND" ? ", stores" : ""}${cart.router === "TANSTACK_ROUTER" ? ", routes" : ""}, or styling. Triggers on tasks mentioning components, pages, layout, naming, imports, or project structure.
|
|
1419
2209
|
---
|
|
1420
2210
|
|
|
@@ -1465,10 +2255,10 @@ Check the reference implementation (\`src/pages/home${isFsd ? "/" : ".tsx"}\`) f
|
|
|
1465
2255
|
`;
|
|
1466
2256
|
};
|
|
1467
2257
|
testAuthorSkillTemplate = (cart) => {
|
|
1468
|
-
const
|
|
2258
|
+
const slug4 = projectSlug(cart);
|
|
1469
2259
|
const hasVitest = cart.testing === "VITEST";
|
|
1470
2260
|
return `---
|
|
1471
|
-
name: ${
|
|
2261
|
+
name: ${slug4}-test-author
|
|
1472
2262
|
description: Centralized test conventions for ${cart.projectName}. Use when asked to "write a test", "add a spec", "cover this with tests", or fix a failing test. All test code lives under test/ \u2014 never inside src/.
|
|
1473
2263
|
---
|
|
1474
2264
|
|
|
@@ -1494,7 +2284,7 @@ ${hasVitest ? `- Unit tests import via the \`@/\` alias and explicit vitest impo
|
|
|
1494
2284
|
`;
|
|
1495
2285
|
};
|
|
1496
2286
|
devAgentTemplate = (cart) => {
|
|
1497
|
-
const
|
|
2287
|
+
const slug4 = projectSlug(cart);
|
|
1498
2288
|
const hasTesting = cart.testing !== "NOT_USING";
|
|
1499
2289
|
const testWriterExample = hasTesting ? " <example>user: 'Cover the login form with tests' \u2192 test-writer, NOT dev <commentary>test authoring belongs to test-writer</commentary></example>" : "";
|
|
1500
2290
|
const delegationRule = hasTesting ? "- Never write tests (delegate to test-writer) or docs (delegate to docs-writer); flag when they are needed." : "- Never write docs (delegate to docs-writer); flag when they are needed.";
|
|
@@ -1511,7 +2301,7 @@ You are the implementation agent for ${cart.projectName}.
|
|
|
1511
2301
|
|
|
1512
2302
|
1. Read \`.claude/agent-memory/dev/MEMORY.md\` \u2014 your accumulated gotchas.
|
|
1513
2303
|
2. Read \`docs/INDEX.md\` and the relevant \`docs/features/<feature>/\` spec for the task.
|
|
1514
|
-
3. Load the \`${
|
|
2304
|
+
3. Load the \`${slug4}-conventions\` skill for layer rules and patterns.
|
|
1515
2305
|
4. Read the code under change.
|
|
1516
2306
|
|
|
1517
2307
|
If no relevant feature spec exists, STOP and tell the user to run the docs-writer agent first.
|
|
@@ -1523,6 +2313,10 @@ If no relevant feature spec exists, STOP and tell the user to run the docs-write
|
|
|
1523
2313
|
3. Verify (build${hasTesting ? " + run the relevant tests" : ""}); report results faithfully.
|
|
1524
2314
|
4. Append newly discovered gotchas/patterns to \`.claude/agent-memory/dev/MEMORY.md\`.
|
|
1525
2315
|
|
|
2316
|
+
## Park rule (anti-loop)
|
|
2317
|
+
|
|
2318
|
+
If a step fails twice and the cause isn't fixable right now (missing info, needs a user decision, environment, out-of-scope), STOP \u2014 do not retry a third time. File a \`backlog/<id>\` entry per \`backlog/README.md\` (its **Tried** section must list what already failed so it isn't repeated), set the owning phase \`status: blocked\` and link both ways, tell the user it was parked, then continue with the next workable task. Don't loop, don't silently skip.
|
|
2319
|
+
|
|
1526
2320
|
## Hard rules
|
|
1527
2321
|
|
|
1528
2322
|
- Never commit or push \u2014 a human does that.
|
|
@@ -1531,7 +2325,7 @@ ${delegationRule}
|
|
|
1531
2325
|
`;
|
|
1532
2326
|
};
|
|
1533
2327
|
testWriterAgentTemplate = (cart) => {
|
|
1534
|
-
const
|
|
2328
|
+
const slug4 = projectSlug(cart);
|
|
1535
2329
|
return `---
|
|
1536
2330
|
name: test-writer
|
|
1537
2331
|
description: "Test authoring agent for ${cart.projectName} \u2014 writes tests and their paired spec.md contracts, only under test/. Runs after docs-writer has produced the feature spec. <example>user: 'Cover the home page with tests' \u2192 test-writer <commentary>test authoring from an existing feature spec</commentary></example> <example>user: 'Add edge-case tests for the date validator' \u2192 test-writer <commentary>narrow test scope, reads the spec for edge cases</commentary></example> <example>user: 'Fix the validation bug the test caught' \u2192 dev, NOT test-writer <commentary>app-code changes belong to dev</commentary></example>"
|
|
@@ -1545,7 +2339,7 @@ You are the test-writing agent for ${cart.projectName}. You write ONLY under \`t
|
|
|
1545
2339
|
|
|
1546
2340
|
1. Read \`.claude/agent-memory/test-writer/MEMORY.md\`.
|
|
1547
2341
|
2. Read the feature spec: \`docs/features/<feature>/<feature>.spec.en.md\` \u2014 requirements and edge cases drive the test plan. If it does not exist, STOP and request docs-writer first.
|
|
1548
|
-
3. Load the \`${
|
|
2342
|
+
3. Load the \`${slug4}-test-author\` skill and \`test/README.md\`.
|
|
1549
2343
|
|
|
1550
2344
|
## Workflow
|
|
1551
2345
|
|
|
@@ -2478,10 +3272,6 @@ var init_package_json2 = __esm({
|
|
|
2478
3272
|
} else if (cart.linter === "ESLINT") {
|
|
2479
3273
|
scripts["lint"] = "eslint .";
|
|
2480
3274
|
}
|
|
2481
|
-
if (cart.ai === "CLAUDE") {
|
|
2482
|
-
scripts["docs:index"] = "node .claude/scripts/build-docs-index.mjs";
|
|
2483
|
-
scripts["docs:lint"] = "node .claude/scripts/lint-docs-frontmatter.mjs";
|
|
2484
|
-
}
|
|
2485
3275
|
return JSON.stringify(
|
|
2486
3276
|
{
|
|
2487
3277
|
name: cart.projectName,
|
|
@@ -2680,7 +3470,7 @@ var init_claude_setup3 = __esm({
|
|
|
2680
3470
|
claudeMdTemplate2 = (cart) => {
|
|
2681
3471
|
const hasZustand = cart.stateManagement === "ZUSTAND";
|
|
2682
3472
|
const hasQuery = cart.query === "TANSTACK_QUERY";
|
|
2683
|
-
const
|
|
3473
|
+
const slug4 = projectSlug2(cart);
|
|
2684
3474
|
const stack = [
|
|
2685
3475
|
"React 19, TypeScript 5.8, Vite 6",
|
|
2686
3476
|
"Chrome Extension Manifest V3 (popup-only)",
|
|
@@ -2694,8 +3484,9 @@ var init_claude_setup3 = __esm({
|
|
|
2694
3484
|
"- `npm run build` \u2014 type-check + production build into `dist/`",
|
|
2695
3485
|
"- `npm run build-extension` \u2014 build + copy `manifest.json` into `dist/`; load `dist/` as an unpacked extension at chrome://extensions",
|
|
2696
3486
|
cart.linter !== "NOT_USING" ? "- `npm run lint` \u2014 lint code" : null,
|
|
2697
|
-
"- `
|
|
2698
|
-
"- `
|
|
3487
|
+
"- `node .claude/scripts/build-docs-index.mjs` \u2014 regenerate docs/INDEX.md (run after any doc change)",
|
|
3488
|
+
"- `node .claude/scripts/lint-docs-frontmatter.mjs` \u2014 validate docs frontmatter (CI-ready, exits non-zero on violation)",
|
|
3489
|
+
"- `node .claude/scripts/validate-plans.mjs` \u2014 check plan/backlog consistency (table\u2194frontmatter, archived, ID gaps, two-way links)"
|
|
2699
3490
|
].filter(Boolean);
|
|
2700
3491
|
const keyPatterns = [
|
|
2701
3492
|
`**Popup lifecycle \u2014 the popup unmounts when it closes:**
|
|
@@ -2776,18 +3567,20 @@ ${keyPatterns.join("\n\n")}
|
|
|
2776
3567
|
|
|
2777
3568
|
| Task / trigger | Agent | Notes |
|
|
2778
3569
|
|---|---|---|
|
|
3570
|
+
${claudeHarnessTableTemplate()}
|
|
2779
3571
|
| Feature work or bug fix in \`src/\` or \`manifest.json\` | \`dev\` | MUST read relevant \`docs/features/\` spec before coding |
|
|
2780
|
-
|
|
3572
|
+
|
|
3573
|
+
**PARK RULE (anti-loop):** when executing a step/phase, if it fails twice and the cause isn't fixable right now (missing info, needs a user decision, environment, or out-of-scope), STOP \u2014 don't retry a third time. Set the phase \`status: blocked\`, file a \`backlog/<id>\` entry (record what was already tried so it isn't repeated), link both ways, tell the user it was parked, and move on to the next workable item. See \`backlog/README.md\`.
|
|
2781
3574
|
|
|
2782
3575
|
Each agent has persistent memory at \`.claude/agent-memory/<agent>/MEMORY.md\` \u2014 agents read it on start and append new gotchas. Do NOT use the general assistant for work an agent owns \u2014 always delegate.
|
|
2783
3576
|
|
|
2784
3577
|
## Task Documentation Convention
|
|
2785
3578
|
|
|
2786
|
-
After any non-trivial fix or new pattern: copy \`docs/_template.md\`, fill the frontmatter, save as \`docs/features/<feature>/<topic>.en.md\` (or \`docs/architecture/\` for cross-cutting topics), then run \`
|
|
3579
|
+
After any non-trivial fix or new pattern: copy \`docs/_template.md\`, fill the frontmatter, save as \`docs/features/<feature>/<topic>.en.md\` (or \`docs/architecture/\` for cross-cutting topics), then run \`node .claude/scripts/build-docs-index.mjs\` and commit the doc together with \`INDEX.md\`. Validate with \`node .claude/scripts/lint-docs-frontmatter.mjs\`.
|
|
2787
3580
|
|
|
2788
3581
|
## Further Reading + DOCS-FIRST RULE
|
|
2789
3582
|
|
|
2790
|
-
Skills: \`.claude/skills/${
|
|
3583
|
+
Skills: \`.claude/skills/${slug4}-conventions\` (architecture depth), \`.claude/skills/${slug4}-docs\` (how to query the knowledge base).
|
|
2791
3584
|
|
|
2792
3585
|
**DOCS-FIRST RULE:** for any request to describe, explain, or modify a documented feature, you MUST grep \`docs/\` frontmatter and read the relevant docs BEFORE opening source files \u2014 and state what the docs already covered. Start at \`docs/INDEX.md\`.
|
|
2793
3586
|
|
|
@@ -2831,10 +3624,10 @@ _(Trade-offs made and alternatives rejected.)_
|
|
|
2831
3624
|
`;
|
|
2832
3625
|
};
|
|
2833
3626
|
conventionsSkillTemplate2 = (cart) => {
|
|
2834
|
-
const
|
|
3627
|
+
const slug4 = projectSlug2(cart);
|
|
2835
3628
|
const hasZustand = cart.stateManagement === "ZUSTAND";
|
|
2836
3629
|
return `---
|
|
2837
|
-
name: ${
|
|
3630
|
+
name: ${slug4}-conventions
|
|
2838
3631
|
description: Coding conventions and architecture rules for ${cart.projectName} (Chrome extension, MV3 popup). Use when writing or reviewing ANY code under src/ \u2014 components, hooks${hasZustand ? ", stores" : ""}, or styling \u2014 or when touching manifest.json. Triggers on tasks mentioning popup, manifest, components, naming, imports, or project structure.
|
|
2839
3632
|
---
|
|
2840
3633
|
|
|
@@ -2881,7 +3674,7 @@ Check \`src/App.tsx\` first, then the docs (\`docs/INDEX.md\`), then ask.
|
|
|
2881
3674
|
`;
|
|
2882
3675
|
};
|
|
2883
3676
|
devAgentTemplate2 = (cart) => {
|
|
2884
|
-
const
|
|
3677
|
+
const slug4 = projectSlug2(cart);
|
|
2885
3678
|
return `---
|
|
2886
3679
|
name: dev
|
|
2887
3680
|
description: "Implementation agent for ${cart.projectName} \u2014 all feature work and bug fixes under src/ and manifest.json. <example>user: 'Add a settings toggle to the popup' \u2192 dev <commentary>feature work in src/; dev reads the feature spec first, then implements</commentary></example> <example>user: 'The popup crashes on empty data \u2014 fix it' \u2192 dev <commentary>bug fix inside a documented feature</commentary></example> <example>user: 'Write a spec for the options page' \u2192 docs-writer, NOT dev <commentary>doc authoring belongs to docs-writer</commentary></example>"
|
|
@@ -2895,7 +3688,7 @@ You are the implementation agent for ${cart.projectName}.
|
|
|
2895
3688
|
|
|
2896
3689
|
1. Read \`.claude/agent-memory/dev/MEMORY.md\` \u2014 your accumulated gotchas.
|
|
2897
3690
|
2. Read \`docs/INDEX.md\` and the relevant \`docs/features/<feature>/\` spec for the task.
|
|
2898
|
-
3. Load the \`${
|
|
3691
|
+
3. Load the \`${slug4}-conventions\` skill for structure rules and patterns.
|
|
2899
3692
|
4. Read the code under change.
|
|
2900
3693
|
|
|
2901
3694
|
If no relevant feature spec exists, STOP and tell the user to run the docs-writer agent first.
|
|
@@ -2907,6 +3700,10 @@ If no relevant feature spec exists, STOP and tell the user to run the docs-write
|
|
|
2907
3700
|
3. Verify (build; for manifest/extension changes run \`npm run build-extension\` and report what to check at chrome://extensions); report results faithfully.
|
|
2908
3701
|
4. Append newly discovered gotchas/patterns to \`.claude/agent-memory/dev/MEMORY.md\`.
|
|
2909
3702
|
|
|
3703
|
+
## Park rule (anti-loop)
|
|
3704
|
+
|
|
3705
|
+
If a step fails twice and the cause isn't fixable right now (missing info, needs a user decision, environment, out-of-scope), STOP \u2014 do not retry a third time. File a \`backlog/<id>\` entry per \`backlog/README.md\` (its **Tried** section must list what already failed so it isn't repeated), set the owning phase \`status: blocked\` and link both ways, tell the user it was parked, then continue with the next workable task. Don't loop, don't silently skip.
|
|
3706
|
+
|
|
2910
3707
|
## Hard rules
|
|
2911
3708
|
|
|
2912
3709
|
- Never commit or push \u2014 a human does that.
|
|
@@ -3125,6 +3922,484 @@ var init_chrome_extension2 = __esm({
|
|
|
3125
3922
|
}
|
|
3126
3923
|
});
|
|
3127
3924
|
|
|
3925
|
+
// src/options/harness-only/constants/index.ts
|
|
3926
|
+
var HARNESS_MENU_PROJECT_TYPE;
|
|
3927
|
+
var init_constants4 = __esm({
|
|
3928
|
+
"src/options/harness-only/constants/index.ts"() {
|
|
3929
|
+
"use strict";
|
|
3930
|
+
HARNESS_MENU_PROJECT_TYPE = {
|
|
3931
|
+
ReactVite: {
|
|
3932
|
+
display: "React + Vite",
|
|
3933
|
+
value: "REACT_VITE",
|
|
3934
|
+
description: "React + Vite project",
|
|
3935
|
+
disabled: false
|
|
3936
|
+
},
|
|
3937
|
+
ChromeExtension: {
|
|
3938
|
+
display: "Chrome Extension",
|
|
3939
|
+
value: "CHROME_EXTENSION",
|
|
3940
|
+
description: "Chrome Extension project",
|
|
3941
|
+
disabled: false
|
|
3942
|
+
},
|
|
3943
|
+
Generic: {
|
|
3944
|
+
display: "Other (Generic)",
|
|
3945
|
+
value: "GENERIC",
|
|
3946
|
+
description: "Any other project",
|
|
3947
|
+
disabled: false
|
|
3948
|
+
}
|
|
3949
|
+
};
|
|
3950
|
+
}
|
|
3951
|
+
});
|
|
3952
|
+
|
|
3953
|
+
// src/scaffold/harness-only/templates/react-vite-skeleton.ts
|
|
3954
|
+
var slug, claudeMdTemplate3, conventionsSkillTemplate3, devAgentTemplate3, seedDocsTemplate, getReactViteHarnessFileMap;
|
|
3955
|
+
var init_react_vite_skeleton = __esm({
|
|
3956
|
+
"src/scaffold/harness-only/templates/react-vite-skeleton.ts"() {
|
|
3957
|
+
"use strict";
|
|
3958
|
+
init_claude_setup();
|
|
3959
|
+
slug = (cart) => cart.projectName.toLowerCase().replace(/_/g, "-");
|
|
3960
|
+
claudeMdTemplate3 = (cart) => `# ${cart.projectName}
|
|
3961
|
+
|
|
3962
|
+
> This project uses a Claude Code harness for AI-assisted development.
|
|
3963
|
+
> Run \`claude /init\` to auto-generate a detailed CLAUDE.md tailored to this codebase.
|
|
3964
|
+
|
|
3965
|
+
## Quick start
|
|
3966
|
+
|
|
3967
|
+
\`\`\`bash
|
|
3968
|
+
npm run dev
|
|
3969
|
+
npm run build
|
|
3970
|
+
\`\`\`
|
|
3971
|
+
|
|
3972
|
+
## Docs
|
|
3973
|
+
|
|
3974
|
+
- \`docs/INDEX.md\` \u2014 knowledge base index (auto-generated)
|
|
3975
|
+
- \`node .claude/scripts/build-docs-index.mjs\` \u2014 regenerate index after adding a doc
|
|
3976
|
+
- \`node .claude/scripts/lint-docs-frontmatter.mjs\` \u2014 validate docs frontmatter
|
|
3977
|
+
`;
|
|
3978
|
+
conventionsSkillTemplate3 = (cart) => `---
|
|
3979
|
+
name: ${slug(cart)}-conventions
|
|
3980
|
+
description: Coding conventions and architecture rules for ${cart.projectName} (React + Vite). Use when writing or reviewing ANY code in this project.
|
|
3981
|
+
---
|
|
3982
|
+
|
|
3983
|
+
# ${cart.projectName} \u2014 Conventions
|
|
3984
|
+
|
|
3985
|
+
## Stack
|
|
3986
|
+
|
|
3987
|
+
React 19, TypeScript, Vite
|
|
3988
|
+
|
|
3989
|
+
## Architecture
|
|
3990
|
+
|
|
3991
|
+
This project uses a feature-slice structure. Check the source layout and run \`claude /init\` to generate detailed conventions.
|
|
3992
|
+
|
|
3993
|
+
## General rules
|
|
3994
|
+
|
|
3995
|
+
- TypeScript strict mode \u2014 no \`any\`, no unchecked assertions
|
|
3996
|
+
- Co-locate component, hook, and type in the same folder
|
|
3997
|
+
- Barrel exports via \`index.ts\`
|
|
3998
|
+
`;
|
|
3999
|
+
devAgentTemplate3 = (cart) => `---
|
|
4000
|
+
name: dev
|
|
4001
|
+
description: "Implementation agent for ${cart.projectName}. Use for feature work and bug fixes."
|
|
4002
|
+
model: sonnet
|
|
4003
|
+
---
|
|
4004
|
+
|
|
4005
|
+
You are the implementation agent for ${cart.projectName} (React + Vite project).
|
|
4006
|
+
|
|
4007
|
+
## Onboarding
|
|
4008
|
+
|
|
4009
|
+
1. Read \`.claude/agent-memory/dev/MEMORY.md\`.
|
|
4010
|
+
2. Read \`CLAUDE.md\` for project overview and commands.
|
|
4011
|
+
3. Load the \`${slug(cart)}-conventions\` skill.
|
|
4012
|
+
|
|
4013
|
+
## Park rule (anti-loop)
|
|
4014
|
+
|
|
4015
|
+
If a step fails twice and the cause isn't fixable right now (missing info, needs a user decision, environment, out-of-scope), STOP \u2014 do not retry a third time. File a \`backlog/<id>\` entry per \`backlog/README.md\` (its **Tried** section must list what already failed so it isn't repeated), set the owning phase \`status: blocked\` and link both ways, tell the user it was parked, then continue with the next workable task. Don't loop, don't silently skip.
|
|
4016
|
+
|
|
4017
|
+
## Hard rules
|
|
4018
|
+
|
|
4019
|
+
- Never commit or push.
|
|
4020
|
+
- Run lint and type-check before reporting a task done.
|
|
4021
|
+
- Docs-first: check \`docs/INDEX.md\` before modifying a documented feature.
|
|
4022
|
+
`;
|
|
4023
|
+
seedDocsTemplate = (cart) => [
|
|
4024
|
+
{
|
|
4025
|
+
relativePath: "docs/features/home/home.spec.en.md",
|
|
4026
|
+
content: `---
|
|
4027
|
+
title: Home \u2014 Feature Spec
|
|
4028
|
+
feature: home
|
|
4029
|
+
flow: ui
|
|
4030
|
+
layer: _cross
|
|
4031
|
+
status: draft
|
|
4032
|
+
lang: en
|
|
4033
|
+
related: []
|
|
4034
|
+
keywords: [home]
|
|
4035
|
+
updated: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
|
|
4036
|
+
---
|
|
4037
|
+
|
|
4038
|
+
# Home
|
|
4039
|
+
|
|
4040
|
+
## Context
|
|
4041
|
+
|
|
4042
|
+
Starting feature doc for ${cart.projectName}. Replace with real content.
|
|
4043
|
+
|
|
4044
|
+
## Solution / Pattern
|
|
4045
|
+
|
|
4046
|
+
_To be documented._
|
|
4047
|
+
`
|
|
4048
|
+
}
|
|
4049
|
+
];
|
|
4050
|
+
getReactViteHarnessFileMap = (cart) => buildClaudeFileMap({
|
|
4051
|
+
projectName: cart.projectName,
|
|
4052
|
+
slug: slug(cart),
|
|
4053
|
+
flowEnum: ["ui", "data", "infra", "architecture", "_meta"],
|
|
4054
|
+
layerEnum: ["app", "pages", "features", "entities", "shared", "_cross"],
|
|
4055
|
+
reminderTrigger: "home|landing",
|
|
4056
|
+
claudeMd: claudeMdTemplate3(cart),
|
|
4057
|
+
conventionsSkill: conventionsSkillTemplate3(cart),
|
|
4058
|
+
devAgent: devAgentTemplate3(cart),
|
|
4059
|
+
seedDocs: seedDocsTemplate(cart)
|
|
4060
|
+
});
|
|
4061
|
+
}
|
|
4062
|
+
});
|
|
4063
|
+
|
|
4064
|
+
// src/scaffold/harness-only/templates/chrome-extension-skeleton.ts
|
|
4065
|
+
var slug2, claudeMdTemplate4, conventionsSkillTemplate4, devAgentTemplate4, seedDocsTemplate2, getChromeExtensionHarnessFileMap;
|
|
4066
|
+
var init_chrome_extension_skeleton = __esm({
|
|
4067
|
+
"src/scaffold/harness-only/templates/chrome-extension-skeleton.ts"() {
|
|
4068
|
+
"use strict";
|
|
4069
|
+
init_claude_setup();
|
|
4070
|
+
slug2 = (cart) => cart.projectName.toLowerCase().replace(/_/g, "-");
|
|
4071
|
+
claudeMdTemplate4 = (cart) => `# ${cart.projectName}
|
|
4072
|
+
|
|
4073
|
+
> This project uses a Claude Code harness for AI-assisted development.
|
|
4074
|
+
> Run \`claude /init\` to auto-generate a detailed CLAUDE.md tailored to this codebase.
|
|
4075
|
+
|
|
4076
|
+
## Quick start
|
|
4077
|
+
|
|
4078
|
+
\`\`\`bash
|
|
4079
|
+
npm run dev
|
|
4080
|
+
npm run build-extension # loads dist/ as unpacked extension at chrome://extensions
|
|
4081
|
+
\`\`\`
|
|
4082
|
+
|
|
4083
|
+
## Docs
|
|
4084
|
+
|
|
4085
|
+
- \`docs/INDEX.md\` \u2014 knowledge base index (auto-generated)
|
|
4086
|
+
- \`node .claude/scripts/build-docs-index.mjs\` \u2014 regenerate index after adding a doc
|
|
4087
|
+
- \`node .claude/scripts/lint-docs-frontmatter.mjs\` \u2014 validate docs frontmatter
|
|
4088
|
+
`;
|
|
4089
|
+
conventionsSkillTemplate4 = (cart) => `---
|
|
4090
|
+
name: ${slug2(cart)}-conventions
|
|
4091
|
+
description: Coding conventions and architecture rules for ${cart.projectName} (Chrome Extension). Use when writing or reviewing ANY code in this project.
|
|
4092
|
+
---
|
|
4093
|
+
|
|
4094
|
+
# ${cart.projectName} \u2014 Conventions
|
|
4095
|
+
|
|
4096
|
+
## Stack
|
|
4097
|
+
|
|
4098
|
+
React 19, TypeScript, Vite, Chrome Extension Manifest V3
|
|
4099
|
+
|
|
4100
|
+
## Architecture
|
|
4101
|
+
|
|
4102
|
+
Popup-centric structure. Check the source layout and run \`claude /init\` to generate detailed conventions.
|
|
4103
|
+
|
|
4104
|
+
## General rules
|
|
4105
|
+
|
|
4106
|
+
- TypeScript strict mode \u2014 no \`any\`, no unchecked assertions
|
|
4107
|
+
- Manifest permissions: request only what's needed
|
|
4108
|
+
- No cross-origin requests from content scripts without explicit host permissions
|
|
4109
|
+
`;
|
|
4110
|
+
devAgentTemplate4 = (cart) => `---
|
|
4111
|
+
name: dev
|
|
4112
|
+
description: "Implementation agent for ${cart.projectName}. Use for feature work and bug fixes."
|
|
4113
|
+
model: sonnet
|
|
4114
|
+
---
|
|
4115
|
+
|
|
4116
|
+
You are the implementation agent for ${cart.projectName} (Chrome Extension project).
|
|
4117
|
+
|
|
4118
|
+
## Onboarding
|
|
4119
|
+
|
|
4120
|
+
1. Read \`.claude/agent-memory/dev/MEMORY.md\`.
|
|
4121
|
+
2. Read \`CLAUDE.md\` for project overview and commands.
|
|
4122
|
+
3. Load the \`${slug2(cart)}-conventions\` skill.
|
|
4123
|
+
|
|
4124
|
+
## Park rule (anti-loop)
|
|
4125
|
+
|
|
4126
|
+
If a step fails twice and the cause isn't fixable right now (missing info, needs a user decision, environment, out-of-scope), STOP \u2014 do not retry a third time. File a \`backlog/<id>\` entry per \`backlog/README.md\` (its **Tried** section must list what already failed so it isn't repeated), set the owning phase \`status: blocked\` and link both ways, tell the user it was parked, then continue with the next workable task. Don't loop, don't silently skip.
|
|
4127
|
+
|
|
4128
|
+
## Hard rules
|
|
4129
|
+
|
|
4130
|
+
- Never commit or push.
|
|
4131
|
+
- Run lint and type-check before reporting a task done.
|
|
4132
|
+
- Docs-first: check \`docs/INDEX.md\` before modifying a documented feature.
|
|
4133
|
+
`;
|
|
4134
|
+
seedDocsTemplate2 = (cart) => [
|
|
4135
|
+
{
|
|
4136
|
+
relativePath: "docs/features/popup/popup.spec.en.md",
|
|
4137
|
+
content: `---
|
|
4138
|
+
title: Popup \u2014 Feature Spec
|
|
4139
|
+
feature: popup
|
|
4140
|
+
flow: ui
|
|
4141
|
+
layer: popup
|
|
4142
|
+
status: draft
|
|
4143
|
+
lang: en
|
|
4144
|
+
related: []
|
|
4145
|
+
keywords: [popup]
|
|
4146
|
+
updated: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
|
|
4147
|
+
---
|
|
4148
|
+
|
|
4149
|
+
# Popup
|
|
4150
|
+
|
|
4151
|
+
## Context
|
|
4152
|
+
|
|
4153
|
+
Starting feature doc for ${cart.projectName}. Replace with real content.
|
|
4154
|
+
|
|
4155
|
+
## Solution / Pattern
|
|
4156
|
+
|
|
4157
|
+
_To be documented._
|
|
4158
|
+
`
|
|
4159
|
+
}
|
|
4160
|
+
];
|
|
4161
|
+
getChromeExtensionHarnessFileMap = (cart) => buildClaudeFileMap({
|
|
4162
|
+
projectName: cart.projectName,
|
|
4163
|
+
slug: slug2(cart),
|
|
4164
|
+
flowEnum: ["ui", "data", "extension", "infra", "_meta"],
|
|
4165
|
+
layerEnum: ["popup", "components", "hooks", "lib", "utils", "_cross"],
|
|
4166
|
+
reminderTrigger: "popup|manifest",
|
|
4167
|
+
claudeMd: claudeMdTemplate4(cart),
|
|
4168
|
+
conventionsSkill: conventionsSkillTemplate4(cart),
|
|
4169
|
+
devAgent: devAgentTemplate4(cart),
|
|
4170
|
+
seedDocs: seedDocsTemplate2(cart)
|
|
4171
|
+
});
|
|
4172
|
+
}
|
|
4173
|
+
});
|
|
4174
|
+
|
|
4175
|
+
// src/scaffold/harness-only/templates/generic-skeleton.ts
|
|
4176
|
+
var slug3, claudeMdTemplate5, conventionsSkillTemplate5, devAgentTemplate5, seedDocsTemplate3, getGenericHarnessFileMap;
|
|
4177
|
+
var init_generic_skeleton = __esm({
|
|
4178
|
+
"src/scaffold/harness-only/templates/generic-skeleton.ts"() {
|
|
4179
|
+
"use strict";
|
|
4180
|
+
init_claude_setup();
|
|
4181
|
+
slug3 = (cart) => cart.projectName.toLowerCase().replace(/_/g, "-");
|
|
4182
|
+
claudeMdTemplate5 = (cart) => `# ${cart.projectName}
|
|
4183
|
+
|
|
4184
|
+
> This project uses a Claude Code harness for AI-assisted development.
|
|
4185
|
+
> Run \`claude /init\` to auto-generate a detailed CLAUDE.md tailored to this codebase.
|
|
4186
|
+
|
|
4187
|
+
## Docs
|
|
4188
|
+
|
|
4189
|
+
- \`docs/INDEX.md\` \u2014 knowledge base index (auto-generated)
|
|
4190
|
+
- \`node .claude/scripts/build-docs-index.mjs\` \u2014 regenerate index after adding a doc
|
|
4191
|
+
- \`node .claude/scripts/lint-docs-frontmatter.mjs\` \u2014 validate docs frontmatter
|
|
4192
|
+
`;
|
|
4193
|
+
conventionsSkillTemplate5 = (cart) => `---
|
|
4194
|
+
name: ${slug3(cart)}-conventions
|
|
4195
|
+
description: Coding conventions for ${cart.projectName}. Run \`claude /init\` to replace with project-specific content.
|
|
4196
|
+
---
|
|
4197
|
+
|
|
4198
|
+
# ${cart.projectName} \u2014 Conventions
|
|
4199
|
+
|
|
4200
|
+
> Run \`claude /init\` in this project to generate detailed, codebase-specific conventions.
|
|
4201
|
+
`;
|
|
4202
|
+
devAgentTemplate5 = (cart) => `---
|
|
4203
|
+
name: dev
|
|
4204
|
+
description: "Implementation agent for ${cart.projectName}."
|
|
4205
|
+
model: sonnet
|
|
4206
|
+
---
|
|
4207
|
+
|
|
4208
|
+
You are the implementation agent for ${cart.projectName}.
|
|
4209
|
+
|
|
4210
|
+
## Onboarding
|
|
4211
|
+
|
|
4212
|
+
1. Read \`.claude/agent-memory/dev/MEMORY.md\`.
|
|
4213
|
+
2. Read \`CLAUDE.md\` for project overview.
|
|
4214
|
+
3. Load the \`${slug3(cart)}-conventions\` skill.
|
|
4215
|
+
|
|
4216
|
+
## Park rule (anti-loop)
|
|
4217
|
+
|
|
4218
|
+
If a step fails twice and the cause isn't fixable right now (missing info, needs a user decision, environment, out-of-scope), STOP \u2014 do not retry a third time. File a \`backlog/<id>\` entry per \`backlog/README.md\` (its **Tried** section must list what already failed so it isn't repeated), set the owning phase \`status: blocked\` and link both ways, tell the user it was parked, then continue with the next workable task. Don't loop, don't silently skip.
|
|
4219
|
+
|
|
4220
|
+
## Hard rules
|
|
4221
|
+
|
|
4222
|
+
- Never commit or push.
|
|
4223
|
+
- Docs-first: check \`docs/INDEX.md\` before modifying a documented feature.
|
|
4224
|
+
`;
|
|
4225
|
+
seedDocsTemplate3 = (cart) => [
|
|
4226
|
+
{
|
|
4227
|
+
relativePath: "docs/features/home/home.spec.en.md",
|
|
4228
|
+
content: `---
|
|
4229
|
+
title: Home \u2014 Feature Spec
|
|
4230
|
+
feature: home
|
|
4231
|
+
flow: ui
|
|
4232
|
+
layer: _cross
|
|
4233
|
+
status: draft
|
|
4234
|
+
lang: en
|
|
4235
|
+
related: []
|
|
4236
|
+
keywords: [home]
|
|
4237
|
+
updated: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
|
|
4238
|
+
---
|
|
4239
|
+
|
|
4240
|
+
# Home
|
|
4241
|
+
|
|
4242
|
+
## Context
|
|
4243
|
+
|
|
4244
|
+
Starting feature doc for ${cart.projectName}. Replace with real content.
|
|
4245
|
+
|
|
4246
|
+
## Solution / Pattern
|
|
4247
|
+
|
|
4248
|
+
_To be documented._
|
|
4249
|
+
`
|
|
4250
|
+
}
|
|
4251
|
+
];
|
|
4252
|
+
getGenericHarnessFileMap = (cart) => buildClaudeFileMap({
|
|
4253
|
+
projectName: cart.projectName,
|
|
4254
|
+
slug: slug3(cart),
|
|
4255
|
+
flowEnum: ["ui", "data", "infra", "_meta"],
|
|
4256
|
+
layerEnum: ["src", "_cross"],
|
|
4257
|
+
reminderTrigger: "home",
|
|
4258
|
+
claudeMd: claudeMdTemplate5(cart),
|
|
4259
|
+
conventionsSkill: conventionsSkillTemplate5(cart),
|
|
4260
|
+
devAgent: devAgentTemplate5(cart),
|
|
4261
|
+
seedDocs: seedDocsTemplate3(cart)
|
|
4262
|
+
});
|
|
4263
|
+
}
|
|
4264
|
+
});
|
|
4265
|
+
|
|
4266
|
+
// src/scaffold/harness-only/index.ts
|
|
4267
|
+
var harness_only_exports = {};
|
|
4268
|
+
__export(harness_only_exports, {
|
|
4269
|
+
scaffoldHarnessOnly: () => scaffoldHarnessOnly
|
|
4270
|
+
});
|
|
4271
|
+
import path4 from "path";
|
|
4272
|
+
import chalk6 from "chalk";
|
|
4273
|
+
import { createSpinner as createSpinner4 } from "nanospinner";
|
|
4274
|
+
var getFileMap, scaffoldHarnessOnly;
|
|
4275
|
+
var init_harness_only = __esm({
|
|
4276
|
+
"src/scaffold/harness-only/index.ts"() {
|
|
4277
|
+
"use strict";
|
|
4278
|
+
init_constants();
|
|
4279
|
+
init_utils();
|
|
4280
|
+
init_errors();
|
|
4281
|
+
init_react_vite_skeleton();
|
|
4282
|
+
init_chrome_extension_skeleton();
|
|
4283
|
+
init_generic_skeleton();
|
|
4284
|
+
getFileMap = (cart) => {
|
|
4285
|
+
switch (cart.projectType) {
|
|
4286
|
+
case "REACT_VITE":
|
|
4287
|
+
return getReactViteHarnessFileMap(cart);
|
|
4288
|
+
case "CHROME_EXTENSION":
|
|
4289
|
+
return getChromeExtensionHarnessFileMap(cart);
|
|
4290
|
+
default:
|
|
4291
|
+
return getGenericHarnessFileMap(cart);
|
|
4292
|
+
}
|
|
4293
|
+
};
|
|
4294
|
+
scaffoldHarnessOnly = async (cart) => {
|
|
4295
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.HarnessOnly.value) return;
|
|
4296
|
+
const { targetDirectory, projectName } = cart;
|
|
4297
|
+
if (!await dirExists(targetDirectory)) {
|
|
4298
|
+
throw new ScaffoldError(`Directory "${path4.relative(process.cwd(), targetDirectory)}" does not exist.`);
|
|
4299
|
+
}
|
|
4300
|
+
const spinner = createSpinner4(`Applying harness to ${chalk6.cyan(projectName)}...`).start();
|
|
4301
|
+
try {
|
|
4302
|
+
const fileMap = getFileMap(cart);
|
|
4303
|
+
for (const { relativePath, content } of fileMap) {
|
|
4304
|
+
await writeProjectFile(targetDirectory, relativePath, content);
|
|
4305
|
+
}
|
|
4306
|
+
spinner.success({
|
|
4307
|
+
text: chalk6.green(`AI harness applied to ${chalk6.bold(projectName)} successfully!`)
|
|
4308
|
+
});
|
|
4309
|
+
console.log("");
|
|
4310
|
+
console.log(chalk6.whiteBright("Next steps:"));
|
|
4311
|
+
console.log(chalk6.cyan(` cd ${path4.relative(process.cwd(), targetDirectory) || "."}`));
|
|
4312
|
+
console.log(chalk6.cyan(" claude /init") + chalk6.gray(" \u2190 let Claude generate a detailed CLAUDE.md for your codebase"));
|
|
4313
|
+
console.log("");
|
|
4314
|
+
} catch (err) {
|
|
4315
|
+
spinner.error({ text: chalk6.red("Harness setup failed.") });
|
|
4316
|
+
if (err instanceof ScaffoldError) {
|
|
4317
|
+
console.error(chalk6.red(err.message));
|
|
4318
|
+
} else if (isNodeError(err)) {
|
|
4319
|
+
console.error(chalk6.red(`File system error (${err.code}): ${err.message}`));
|
|
4320
|
+
} else {
|
|
4321
|
+
console.error(chalk6.red("An unexpected error occurred."), err);
|
|
4322
|
+
}
|
|
4323
|
+
process.exit(1);
|
|
4324
|
+
}
|
|
4325
|
+
};
|
|
4326
|
+
}
|
|
4327
|
+
});
|
|
4328
|
+
|
|
4329
|
+
// src/options/harness-only/index.ts
|
|
4330
|
+
var harness_only_exports2 = {};
|
|
4331
|
+
__export(harness_only_exports2, {
|
|
4332
|
+
flowHarnessOnly: () => flowHarnessOnly
|
|
4333
|
+
});
|
|
4334
|
+
import { select as select3, input as input3 } from "@inquirer/prompts";
|
|
4335
|
+
import path5 from "path";
|
|
4336
|
+
import chalk7 from "chalk";
|
|
4337
|
+
var selectFromMenu3, menuTargetDirectory, menuProjectName3, menuProjectType, flowHarnessOnly;
|
|
4338
|
+
var init_harness_only2 = __esm({
|
|
4339
|
+
"src/options/harness-only/index.ts"() {
|
|
4340
|
+
"use strict";
|
|
4341
|
+
init_constants();
|
|
4342
|
+
init_constants4();
|
|
4343
|
+
init_utils();
|
|
4344
|
+
selectFromMenu3 = async (menuOptions, message) => {
|
|
4345
|
+
const keys = Object.keys(menuOptions);
|
|
4346
|
+
const choices = keys.filter((key) => !menuOptions[key].disabled).map((key) => ({
|
|
4347
|
+
name: menuOptions[key].display,
|
|
4348
|
+
value: menuOptions[key].value,
|
|
4349
|
+
description: menuOptions[key].description
|
|
4350
|
+
}));
|
|
4351
|
+
const answer = await select3({ message: chalk7.whiteBright(message), choices });
|
|
4352
|
+
return answer;
|
|
4353
|
+
};
|
|
4354
|
+
menuTargetDirectory = async (cart) => {
|
|
4355
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.HarnessOnly.value) return;
|
|
4356
|
+
const raw = await input3({
|
|
4357
|
+
message: chalk7.whiteBright("Target directory (existing project):"),
|
|
4358
|
+
default: ".",
|
|
4359
|
+
validate: async (value) => {
|
|
4360
|
+
const resolved = path5.resolve(process.cwd(), value.trim());
|
|
4361
|
+
if (!await dirExists(resolved)) {
|
|
4362
|
+
return `Directory "${value.trim()}" does not exist`;
|
|
4363
|
+
}
|
|
4364
|
+
return true;
|
|
4365
|
+
},
|
|
4366
|
+
transformer: (value) => value.trim()
|
|
4367
|
+
});
|
|
4368
|
+
cart.targetDirectory = path5.resolve(process.cwd(), raw.trim());
|
|
4369
|
+
};
|
|
4370
|
+
menuProjectName3 = async (cart) => {
|
|
4371
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.HarnessOnly.value) return;
|
|
4372
|
+
const defaultName = path5.basename(cart.targetDirectory);
|
|
4373
|
+
cart.projectName = await input3({
|
|
4374
|
+
message: chalk7.whiteBright("Project name:"),
|
|
4375
|
+
default: defaultName,
|
|
4376
|
+
validate: (value) => {
|
|
4377
|
+
if (!value.trim()) return "Project name cannot be empty";
|
|
4378
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(value.trim()))
|
|
4379
|
+
return "Only letters, numbers, hyphens, and underscores allowed";
|
|
4380
|
+
return true;
|
|
4381
|
+
},
|
|
4382
|
+
transformer: (value) => value.trim()
|
|
4383
|
+
});
|
|
4384
|
+
};
|
|
4385
|
+
menuProjectType = async (cart) => {
|
|
4386
|
+
if (!cart || cart.type !== MENU_OPTIONS_LEVEL_1.HarnessOnly.value) return;
|
|
4387
|
+
cart.projectType = await selectFromMenu3(
|
|
4388
|
+
HARNESS_MENU_PROJECT_TYPE,
|
|
4389
|
+
"Project type:"
|
|
4390
|
+
);
|
|
4391
|
+
};
|
|
4392
|
+
flowHarnessOnly = async () => {
|
|
4393
|
+
const cart = { type: MENU_OPTIONS_LEVEL_1.HarnessOnly.value };
|
|
4394
|
+
await menuTargetDirectory(cart);
|
|
4395
|
+
await menuProjectName3(cart);
|
|
4396
|
+
await menuProjectType(cart);
|
|
4397
|
+
const { scaffoldHarnessOnly: scaffoldHarnessOnly2 } = await Promise.resolve().then(() => (init_harness_only(), harness_only_exports));
|
|
4398
|
+
await scaffoldHarnessOnly2(cart);
|
|
4399
|
+
};
|
|
4400
|
+
}
|
|
4401
|
+
});
|
|
4402
|
+
|
|
3128
4403
|
// src/commands/update.ts
|
|
3129
4404
|
import { exec } from "child_process";
|
|
3130
4405
|
import { promisify } from "util";
|
|
@@ -3172,8 +4447,8 @@ var runUpdate = async (currentVersion) => {
|
|
|
3172
4447
|
|
|
3173
4448
|
// src/options/index.ts
|
|
3174
4449
|
init_constants();
|
|
3175
|
-
import { select as
|
|
3176
|
-
import
|
|
4450
|
+
import { select as select4, Separator as Separator3 } from "@inquirer/prompts";
|
|
4451
|
+
import chalk8 from "chalk";
|
|
3177
4452
|
var menu = async () => {
|
|
3178
4453
|
const keys = Object.keys(MENU_OPTIONS_LEVEL_1);
|
|
3179
4454
|
const enabledKeys = keys.filter((key) => !MENU_OPTIONS_LEVEL_1[key].disabled);
|
|
@@ -3190,8 +4465,8 @@ var menu = async () => {
|
|
|
3190
4465
|
disabled: MENU_OPTIONS_LEVEL_1.Nuxt.description
|
|
3191
4466
|
}
|
|
3192
4467
|
];
|
|
3193
|
-
const answer = await
|
|
3194
|
-
message:
|
|
4468
|
+
const answer = await select4({
|
|
4469
|
+
message: chalk8.whiteBright("How can I help you \u{1F468}\u200D\u{1F373}"),
|
|
3195
4470
|
choices
|
|
3196
4471
|
});
|
|
3197
4472
|
const cart = { type: answer };
|
|
@@ -3201,6 +4476,9 @@ var menu = async () => {
|
|
|
3201
4476
|
} else if (answer === MENU_OPTIONS_LEVEL_1.ChromeExtension.value) {
|
|
3202
4477
|
const { flowChromeExtension: flowChromeExtension2 } = await Promise.resolve().then(() => (init_chrome_extension2(), chrome_extension_exports2));
|
|
3203
4478
|
await flowChromeExtension2(cart);
|
|
4479
|
+
} else if (answer === MENU_OPTIONS_LEVEL_1.HarnessOnly.value) {
|
|
4480
|
+
const { flowHarnessOnly: flowHarnessOnly2 } = await Promise.resolve().then(() => (init_harness_only2(), harness_only_exports2));
|
|
4481
|
+
await flowHarnessOnly2();
|
|
3204
4482
|
}
|
|
3205
4483
|
return cart;
|
|
3206
4484
|
};
|
|
@@ -3218,13 +4496,13 @@ var typeWriter = async (text, colorFunc, speed = 50) => {
|
|
|
3218
4496
|
};
|
|
3219
4497
|
|
|
3220
4498
|
// src/utils/check-node-version.ts
|
|
3221
|
-
import
|
|
4499
|
+
import chalk9 from "chalk";
|
|
3222
4500
|
var MIN_NODE_MAJOR = 20;
|
|
3223
4501
|
var checkNodeVersion = () => {
|
|
3224
4502
|
const major = parseInt(process.version.slice(1).split(".")[0], 10);
|
|
3225
4503
|
if (major < MIN_NODE_MAJOR) {
|
|
3226
4504
|
console.error(
|
|
3227
|
-
|
|
4505
|
+
chalk9.red(
|
|
3228
4506
|
`\u2716 beaver requires Node.js v${MIN_NODE_MAJOR} or higher.
|
|
3229
4507
|
You are running ${process.version}.
|
|
3230
4508
|
Please upgrade Node.js: https://nodejs.org`
|
|
@@ -3242,7 +4520,7 @@ var getUserName = () => {
|
|
|
3242
4520
|
};
|
|
3243
4521
|
|
|
3244
4522
|
// src/index.ts
|
|
3245
|
-
import
|
|
4523
|
+
import chalk10 from "chalk";
|
|
3246
4524
|
import { readFileSync } from "fs";
|
|
3247
4525
|
import { dirname, join } from "path";
|
|
3248
4526
|
import { fileURLToPath } from "url";
|
|
@@ -3275,6 +4553,7 @@ Commands:
|
|
|
3275
4553
|
Options:
|
|
3276
4554
|
-v, --version Show version
|
|
3277
4555
|
-h, --help Show help
|
|
4556
|
+
--ai Apply AI harness to an existing project
|
|
3278
4557
|
`);
|
|
3279
4558
|
process.exit(0);
|
|
3280
4559
|
}
|
|
@@ -3282,13 +4561,25 @@ Options:
|
|
|
3282
4561
|
await runUpdate(getVersion());
|
|
3283
4562
|
process.exit(0);
|
|
3284
4563
|
}
|
|
3285
|
-
|
|
4564
|
+
if (args.includes("--ai")) {
|
|
4565
|
+
try {
|
|
4566
|
+
const { flowHarnessOnly: flowHarnessOnly2 } = await Promise.resolve().then(() => (init_harness_only2(), harness_only_exports2));
|
|
4567
|
+
await flowHarnessOnly2();
|
|
4568
|
+
} catch (err) {
|
|
4569
|
+
if (err instanceof Error) {
|
|
4570
|
+
console.error(chalk10.red(err.message));
|
|
4571
|
+
}
|
|
4572
|
+
process.exit(1);
|
|
4573
|
+
}
|
|
4574
|
+
process.exit(0);
|
|
4575
|
+
}
|
|
4576
|
+
await typeWriter(`Hi! ${getUserName()} \u{1F646}`, chalk10.whiteBright, 50);
|
|
3286
4577
|
await sleep(500);
|
|
3287
4578
|
try {
|
|
3288
4579
|
await menu();
|
|
3289
4580
|
} catch (err) {
|
|
3290
4581
|
if (err instanceof Error) {
|
|
3291
|
-
console.error(
|
|
4582
|
+
console.error(chalk10.red(err.message));
|
|
3292
4583
|
}
|
|
3293
4584
|
process.exit(1);
|
|
3294
4585
|
}
|