claudeos-core 2.3.2 → 2.4.1
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/CHANGELOG.md +790 -74
- package/CODE_OF_CONDUCT.md +15 -0
- package/README.de.md +374 -876
- package/README.es.md +374 -875
- package/README.fr.md +374 -875
- package/README.hi.md +374 -875
- package/README.ja.md +374 -875
- package/README.ko.md +374 -874
- package/README.md +374 -876
- package/README.ru.md +374 -877
- package/README.vi.md +374 -875
- package/README.zh-CN.md +374 -874
- package/SECURITY.md +51 -0
- package/bin/commands/init.js +192 -37
- package/content-validator/index.js +97 -4
- package/health-checker/index.js +44 -10
- package/package.json +92 -90
- package/pass-json-validator/index.js +58 -7
- package/pass-prompts/templates/angular/pass3.md +15 -14
- package/pass-prompts/templates/common/claude-md-scaffold.md +81 -0
- package/pass-prompts/templates/common/pass3-footer.md +104 -0
- package/pass-prompts/templates/java-spring/pass3.md +19 -18
- package/pass-prompts/templates/kotlin-spring/pass3.md +23 -22
- package/pass-prompts/templates/node-express/pass3.md +18 -17
- package/pass-prompts/templates/node-fastify/pass3.md +11 -10
- package/pass-prompts/templates/node-nestjs/pass3.md +11 -10
- package/pass-prompts/templates/node-nextjs/pass3.md +18 -17
- package/pass-prompts/templates/node-vite/pass3.md +11 -10
- package/pass-prompts/templates/python-django/pass3.md +18 -17
- package/pass-prompts/templates/python-fastapi/pass3.md +18 -17
- package/pass-prompts/templates/python-flask/pass3.md +9 -8
- package/pass-prompts/templates/vue-nuxt/pass3.md +9 -8
- package/plan-installer/domain-grouper.js +45 -5
- package/plan-installer/index.js +11 -1
- package/plan-installer/scanners/scan-java.js +98 -2
- package/plan-installer/stack-detector.js +44 -0
package/SECURITY.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
|---------|--------------------|
|
|
7
|
+
| 2.4.x | :white_check_mark: |
|
|
8
|
+
| < 2.4 | :x: |
|
|
9
|
+
|
|
10
|
+
Only the latest minor release line receives security fixes. Users on older versions are encouraged to upgrade.
|
|
11
|
+
|
|
12
|
+
## Reporting a Vulnerability
|
|
13
|
+
|
|
14
|
+
**Please do not file public GitHub issues for security vulnerabilities.**
|
|
15
|
+
|
|
16
|
+
Two private channels are available:
|
|
17
|
+
|
|
18
|
+
1. **Email** — `claudeoscore@gmail.com`
|
|
19
|
+
2. **GitHub Security Advisories** — [Open a private report](https://github.com/claudeos-core/claudeos-core/security/advisories/new) (preferred; provides a private workspace + CVE coordination)
|
|
20
|
+
|
|
21
|
+
### What to include
|
|
22
|
+
|
|
23
|
+
- Affected version (`npx claudeos-core --version`)
|
|
24
|
+
- Reproduction steps or proof-of-concept
|
|
25
|
+
- Impact assessment (data exposure / code execution / DoS / etc.)
|
|
26
|
+
- Suggested fix (if any)
|
|
27
|
+
|
|
28
|
+
### Response timeline
|
|
29
|
+
|
|
30
|
+
| Stage | Target |
|
|
31
|
+
|--------------------|---------|
|
|
32
|
+
| Initial reply | 48 hours |
|
|
33
|
+
| Triage + severity | 7 days |
|
|
34
|
+
| Fix or mitigation | 30 days for high/critical, 90 days for medium/low |
|
|
35
|
+
|
|
36
|
+
We will keep you informed throughout the process and credit you in the release notes (unless you prefer to remain anonymous).
|
|
37
|
+
|
|
38
|
+
## Scope
|
|
39
|
+
|
|
40
|
+
In scope:
|
|
41
|
+
|
|
42
|
+
- The `claudeos-core` npm package and its CLI (`bin/cli.js`)
|
|
43
|
+
- The 4-Pass pipeline orchestrator (`bin/commands/init.js`)
|
|
44
|
+
- All validators (`claude-md-validator/`, `content-validator/`, `pass-json-validator/`)
|
|
45
|
+
- Generated artifacts (CLAUDE.md, rules, skills, guides) when produced by an unmodified release
|
|
46
|
+
|
|
47
|
+
Out of scope:
|
|
48
|
+
|
|
49
|
+
- Vulnerabilities in third-party dependencies (please report upstream; we will track and update)
|
|
50
|
+
- The `claude` CLI itself (report to Anthropic)
|
|
51
|
+
- User-modified template forks
|
package/bin/commands/init.js
CHANGED
|
@@ -205,9 +205,46 @@ async function runPass3Split(ctx) {
|
|
|
205
205
|
return batches;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// v2.4.0 — Stack-type classification helper.
|
|
209
|
+
//
|
|
210
|
+
// Returns `{ backend: Set<string>, frontend: Set<string>, isMultiStack: boolean }`
|
|
211
|
+
// built from `project-analysis.json`. The maps are used by
|
|
212
|
+
// `buildBatchScopeNote` to emit ALWAYS-typed per-domain paths
|
|
213
|
+
// (`70.domains/{type}/{domain}.md`) regardless of single/multi-stack.
|
|
214
|
+
// Uniform layout means single-stack projects pay a 1-folder depth
|
|
215
|
+
// cost in exchange for zero-migration when the other stack is later
|
|
216
|
+
// added, and validators recognize a single pattern. The `isMultiStack`
|
|
217
|
+
// flag is retained for tools that may want to surface the distinction
|
|
218
|
+
// (e.g. user-facing console output) but is no longer used to branch
|
|
219
|
+
// path generation.
|
|
220
|
+
function loadDomainTypeMap() {
|
|
221
|
+
const result = { backend: new Set(), frontend: new Set(), isMultiStack: false };
|
|
222
|
+
try {
|
|
223
|
+
const paPath = path.join(GENERATED_DIR, "project-analysis.json");
|
|
224
|
+
if (fileExists(paPath)) {
|
|
225
|
+
const pa = JSON.parse(readFile(paPath));
|
|
226
|
+
if (Array.isArray(pa.backendDomains)) {
|
|
227
|
+
for (const d of pa.backendDomains) {
|
|
228
|
+
const n = d && (d.name || d);
|
|
229
|
+
if (n) result.backend.add(n);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (Array.isArray(pa.frontendDomains)) {
|
|
233
|
+
for (const d of pa.frontendDomains) {
|
|
234
|
+
const n = d && (d.name || d);
|
|
235
|
+
if (n) result.frontend.add(n);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} catch (_e) { /* tolerate missing/corrupt analysis — fallback to flat paths */ }
|
|
240
|
+
result.isMultiStack = result.backend.size > 0 && result.frontend.size > 0;
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
|
|
208
244
|
const domainOrder = loadDomainOrder();
|
|
209
245
|
const batches = computeBatches(domainOrder);
|
|
210
246
|
const isBatched = batches.length > 1;
|
|
247
|
+
const domainTypeMap = loadDomainTypeMap();
|
|
211
248
|
|
|
212
249
|
if (isBatched) {
|
|
213
250
|
log(` 📦 Batch sub-division enabled: ${domainOrder.length} domains → ${batches.length} batches per stage (3b, 3c)`);
|
|
@@ -285,28 +322,100 @@ async function runPass3Split(ctx) {
|
|
|
285
322
|
// and which common files to include vs skip.
|
|
286
323
|
function buildBatchScopeNote(stageKind, batchIndex, totalBatches, batchDomains) {
|
|
287
324
|
const isLastBatch = batchIndex === totalBatches - 1;
|
|
325
|
+
const isSingleBatch = totalBatches === 1;
|
|
288
326
|
const domainList = batchDomains.map(d => `\`${d}\``).join(", ");
|
|
289
327
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
328
|
+
// v2.4.0 — Always-typed per-domain layout.
|
|
329
|
+
//
|
|
330
|
+
// Every per-domain file lives under a `{type}/` sub-folder
|
|
331
|
+
// (`70.domains/backend/` or `70.domains/frontend/`) regardless of
|
|
332
|
+
// whether the project is single-stack or multi-stack. This is a
|
|
333
|
+
// deliberate uniform-convention choice:
|
|
334
|
+
//
|
|
335
|
+
// - Single-stack projects pay a 1-folder depth cost; in exchange
|
|
336
|
+
// they migrate to multi-stack with ZERO file moves when frontend
|
|
337
|
+
// (or backend) is later added.
|
|
338
|
+
// - LLM never has to decide which form to use → no probabilistic
|
|
339
|
+
// drift between Pass 3 runs.
|
|
340
|
+
// - Validators (content-validator, claude-md-validator) need
|
|
341
|
+
// to recognize only ONE pattern.
|
|
342
|
+
// - Future stack types (mobile, cli, agent, ...) extend naturally
|
|
343
|
+
// by adding new `{type}/` sub-folders.
|
|
344
|
+
//
|
|
345
|
+
// Per-domain type lookup uses `domainTypeMap` from
|
|
346
|
+
// `project-analysis.json`. Domains not in either set fall back to
|
|
347
|
+
// `backend` (the dominant case in real-world projects) — this
|
|
348
|
+
// happens only when the analysis JSON is malformed or absent.
|
|
349
|
+
const typeOf = (name) => {
|
|
350
|
+
if (domainTypeMap.frontend.has(name)) return "frontend";
|
|
351
|
+
// backend is the default when the domain is unknown to both sets.
|
|
352
|
+
// This happens only on malformed analysis JSON; backend is the
|
|
353
|
+
// safer default since it's the dominant project type.
|
|
354
|
+
return "backend";
|
|
355
|
+
};
|
|
356
|
+
const stdPathFor = (name) => `claudeos-core/standard/70.domains/${typeOf(name)}/${name}.md`;
|
|
357
|
+
const rulePathFor = (name) => `.claude/rules/70.domains/${typeOf(name)}/${name}-rules.md`;
|
|
358
|
+
const stagedRulePathFor = (name) => `claudeos-core/generated/.staged-rules/70.domains/${typeOf(name)}/${name}-rules.md`;
|
|
359
|
+
|
|
360
|
+
let note = isSingleBatch
|
|
361
|
+
? `## Per-domain scope (${stageKind}, single-batch run)\n\n`
|
|
362
|
+
: `## Batch scope (${stageKind}-batch ${batchIndex + 1}/${totalBatches})\n\n`;
|
|
363
|
+
if (isSingleBatch) {
|
|
364
|
+
note += `${batchDomains.length} domain(s) will be processed in this single Pass 3b stage.\n`;
|
|
365
|
+
note += `Per-domain files are REQUIRED regardless of domain count — they enable domain-scoped \`paths\` glob targeting for rules.\n\n`;
|
|
366
|
+
} else {
|
|
367
|
+
note += `This Pass 3 stage has been sub-divided into ${totalBatches} batches to avoid context overflow.\n`;
|
|
368
|
+
note += `**You are processing batch ${batchIndex + 1} of ${totalBatches}.**\n\n`;
|
|
369
|
+
}
|
|
293
370
|
|
|
294
371
|
if (stageKind === "3b") {
|
|
295
|
-
note +=
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
note +=
|
|
301
|
-
|
|
372
|
+
note += isSingleBatch
|
|
373
|
+
? `**Domains in scope (all ${batchDomains.length})**: ${domainList}\n\n`
|
|
374
|
+
: `**Domains in THIS batch**: ${domainList}\n\n`;
|
|
375
|
+
// Always show per-domain target paths so the LLM follows the
|
|
376
|
+
// typed sub-folder convention without inferring from domain name.
|
|
377
|
+
note += `**Per-domain target paths (always under \`{type}/\` sub-folder — \`backend\` or \`frontend\`):**\n`;
|
|
378
|
+
for (const d of batchDomains) {
|
|
379
|
+
note += `- \`${d}\` → \`${stdPathFor(d)}\` and \`${rulePathFor(d)}\`\n`;
|
|
380
|
+
}
|
|
381
|
+
note += `\n`;
|
|
382
|
+
note += `**Rules**:\n`;
|
|
383
|
+
if (isSingleBatch) {
|
|
384
|
+
// Single-batch (≤15 domains): common files AND per-domain files in same stage.
|
|
385
|
+
note += `1. Generate ALL common files (CLAUDE.md + standard/00.core/* + standard/10.backend/* + standard/30.security-db/* + standard/40.infra/* + standard/80.verification/* + .claude/rules/00.core/* + .claude/rules/10.backend/* + .claude/rules/30.security-db/* + .claude/rules/40.infra/* + .claude/rules/50.sync/*) per the stack pass3 template.\n`;
|
|
386
|
+
note += `2. **ALSO** create ONE NEW per-domain standard file PER domain listed above at \`claudeos-core/standard/70.domains/{type}/{domain}.md\` (use the exact \`{type}\` shown in the per-domain target paths above). **Expected output: ${batchDomains.length} new file(s) under \`70.domains/{type}/\`** — one per domain. Do NOT inline these into the common standards (00.core/, 10.backend/, etc.); per-domain files are DOMAIN-scoped while common files are TOPIC-based, and they must live at separate paths so per-domain rules can scope to them via \`paths\` glob.\n`;
|
|
387
|
+
note += `3. **ALSO** create ONE NEW per-domain rule file PER domain listed above at \`.claude/rules/70.domains/{type}/{domain}-rules.md\` (via staging-override path \`claudeos-core/generated/.staged-rules/70.domains/{type}/{domain}-rules.md\`). Each rule file MUST have a \`paths\` frontmatter glob scoped to that domain's source directories so the rule auto-loads only when editing the relevant files.\n`;
|
|
388
|
+
note += `4. Per-domain file generation is MANDATORY even for ${batchDomains.length}-domain projects (any size below the 15-domain batch threshold). The convention is uniform across all project sizes for consistency: same dirtree shape regardless of scale.\n`;
|
|
389
|
+
note += `5. Rule B idempotent skip applies — if a per-domain file already exists with substantive content, print \`[SKIP] <path>\` and continue.\n`;
|
|
390
|
+
} else {
|
|
391
|
+
// Multi-batch (>15 domains): common files in 3b-core, per-domain in batch stages.
|
|
392
|
+
note += `1. CLAUDE.md and all common standard/ files (00.core/, 30.security-db/, 40.infra/, etc.) are ALREADY GENERATED by the 3b-core stage. DO NOT regenerate them.\n`;
|
|
393
|
+
note += `2. Create ONE NEW per-domain standard file PER domain listed above at \`claudeos-core/standard/70.domains/{type}/{domain}.md\` (use the exact \`{type}\` shown in the per-domain target paths above — \`backend\` or \`frontend\`). **Expected output: ${batchDomains.length} new files** — one per domain in this batch. Do NOT inline these into the common files generated by 3b-core; common files are TOPIC-based, per-domain files are DOMAIN-scoped and must live at separate paths so per-domain rules can scope to them via \`paths\` glob.\n`;
|
|
394
|
+
note += `3. Create ONE NEW per-domain rule file PER domain listed above at \`.claude/rules/70.domains/{type}/{domain}-rules.md\` (via staging-override path \`claudeos-core/generated/.staged-rules/70.domains/{type}/{domain}-rules.md\`). Each rule file MUST have a \`paths\` frontmatter glob scoped to that domain's source directories so the rule auto-loads only when editing the relevant files. Common rules are already generated by 3b-core — do NOT regenerate.\n`;
|
|
395
|
+
note += `4. DO NOT generate standard/ or rules/ files for domains NOT in the above list — those are/will be processed in other batches.\n`;
|
|
396
|
+
note += `5. Rule B idempotent skip applies ONLY to files at the per-domain paths shown above for THIS batch's domains — i.e. resume after a crash. **Do NOT use Rule B as justification for skipping the entire batch** because common files at OTHER paths exist (those are out of scope for this batch). If a per-domain target file does NOT exist for a batch domain, you MUST generate it; emitting "0 new files" for the whole batch when domains were assigned to you is a critical Pass 3 failure mode.\n`;
|
|
397
|
+
}
|
|
302
398
|
} else if (stageKind === "3c") {
|
|
303
|
-
note +=
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
note +=
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
399
|
+
note += isSingleBatch
|
|
400
|
+
? `**Domains in scope (all ${batchDomains.length})**: ${domainList}\n\n`
|
|
401
|
+
: `**Domains in THIS batch**: ${domainList}\n\n`;
|
|
402
|
+
note += `**Rules**:\n`;
|
|
403
|
+
if (isSingleBatch) {
|
|
404
|
+
// Single-batch (≤15 domains): ALL of 3c — guides + common skills + per-domain skill notes — in one stage.
|
|
405
|
+
note += `1. Generate ALL guide/ files (01.onboarding, 02.usage, 03.troubleshooting, 04.architecture) per the stack pass3 template.\n`;
|
|
406
|
+
note += `2. Generate common skills: \`claudeos-core/skills/00.shared/MANIFEST.md\` + the category orchestrator(s) at \`claudeos-core/skills/{category}/01.scaffold-*-feature.md\` + their sub-skill files under \`{category}/scaffold-*-feature/\`.\n`;
|
|
407
|
+
note += `3. **ALSO** generate \`claudeos-core/skills/{category}/02.domains.md\` orchestrator (sibling to the \`domains/\` sub-folder) — REQUIRED for ALL projects regardless of domain count, mirrors the canonical \`01.scaffold-*-feature.md\` ↔ \`scaffold-*-feature/\` pattern.\n`;
|
|
408
|
+
note += `4. **ALSO** generate per-domain skill notes at \`claudeos-core/skills/{category}/domains/{domain}.md\` for EACH of the ${batchDomains.length} domain(s). **Expected output: ${batchDomains.length} new file(s) under \`{category}/domains/\`** — content describes domain-specific anti-patterns, dependencies, naming quirks. Per-domain skill generation is MANDATORY even for ${batchDomains.length}-domain projects (the convention is uniform across all project sizes).\n`;
|
|
409
|
+
note += `5. MUST register every newly created skill file in \`00.shared/MANIFEST.md\` (orchestrator + sub-skills + 02.domains.md + per-domain notes).\n`;
|
|
410
|
+
note += `6. Rule B idempotent skip applies — if a skill file already exists, print \`[SKIP] <path>\` and move on.\n`;
|
|
411
|
+
} else {
|
|
412
|
+
// Multi-batch (>15 domains): guides+common skills in 3c-core, per-domain in batch stages.
|
|
413
|
+
note += `1. ALL guide/ files (01.onboarding, 02.usage, 03.troubleshooting, 04.architecture) are ALREADY GENERATED by the 3c-core stage. DO NOT regenerate.\n`;
|
|
414
|
+
note += `2. Common skills (00.shared/, orchestrator SKILL.md) and \`02.domains.md\` orchestrator are ALREADY GENERATED by 3c-core. DO NOT regenerate.\n`;
|
|
415
|
+
note += `3. Generate per-domain skill notes ONLY for the domains listed above at \`claudeos-core/skills/{category}/domains/{domain}.md\` — typically under 10.backend-crud/ or 20.frontend-page/.\n`;
|
|
416
|
+
note += `4. DO NOT generate skills for domains NOT in the above list.\n`;
|
|
417
|
+
note += `5. Rule B idempotent skip applies: if a skill file already exists, print \`[SKIP] <path>\` and move on.\n`;
|
|
418
|
+
}
|
|
310
419
|
}
|
|
311
420
|
|
|
312
421
|
if (!isLastBatch) {
|
|
@@ -337,7 +446,7 @@ async function runPass3Split(ctx) {
|
|
|
337
446
|
coreNote += ` - claudeos-core/standard/00.core/*.md (project overview, architecture, conventions)\n`;
|
|
338
447
|
coreNote += ` - claudeos-core/standard/30.security-db/*.md\n`;
|
|
339
448
|
coreNote += ` - claudeos-core/standard/40.infra/*.md\n`;
|
|
340
|
-
coreNote += ` - claudeos-core/standard/
|
|
449
|
+
coreNote += ` - claudeos-core/standard/80.verification/*.md\n`;
|
|
341
450
|
coreNote += ` - claudeos-core/standard/90.optional/*.md\n`;
|
|
342
451
|
coreNote += ` - (stack-specific common sections as defined in the pass3 template)\n`;
|
|
343
452
|
coreNote += `3. ALL rules files (via staging-override path .claude/rules → generated/.staged-rules):\n`;
|
|
@@ -356,11 +465,13 @@ async function runPass3Split(ctx) {
|
|
|
356
465
|
coreNote += ` - claudeos-core/guide/04.architecture/*.md\n`;
|
|
357
466
|
coreNote += `2. COMMON skills only:\n`;
|
|
358
467
|
coreNote += ` - claudeos-core/skills/00.shared/*\n`;
|
|
359
|
-
coreNote += ` - top-level orchestrator SKILL.md files (e.g. \`10.backend-crud/SKILL.md\` without any subfolder)\n
|
|
468
|
+
coreNote += ` - top-level orchestrator SKILL.md files (e.g. \`10.backend-crud/SKILL.md\` without any subfolder)\n`;
|
|
469
|
+
coreNote += `3. **Per-domain orchestrator** (REQUIRED whenever batches are non-empty — i.e. ${totalDomains} domains will be processed): for EACH active skill category that will receive per-domain notes, generate the sibling orchestrator file at \`claudeos-core/skills/{category}/02.domains.md\`. The basename stem (\`domains\`) MUST match the \`domains/\` sub-folder name so \`content-validator\`'s standard orchestrator-stem matching covers all sub-skills directly. The orchestrator's content describes the per-domain notes pattern, lists the ${totalDomains} domains that will be processed, and links to \`00.shared/MANIFEST.md\`. Generate it BEFORE 3c-N batches run so it's already in place when sub-skills land. Numbered \`02.\` because \`01.scaffold-*-feature.md\` already occupies the \`01.\` slot at the category root.\n\n`;
|
|
360
470
|
coreNote += `**What NOT to generate in ${stageKind}-core**:\n`;
|
|
361
471
|
coreNote += `- Per-domain skill sub-directories (e.g. \`10.backend-crud/scaffold-order-feature/\`) — those belong to 3c-1, 3c-2, ... batch stages.\n`;
|
|
472
|
+
coreNote += `- Per-domain note files (\`10.backend-crud/domains/{domain}.md\`) — those belong to 3c-N batch stages. ${stageKind}-core only generates the parent ORCHESTRATOR (\`02.domains.md\`).\n`;
|
|
362
473
|
coreNote += `- Anything under plan/, database/, mcp-guide/ — those belong to 3d.\n\n`;
|
|
363
|
-
coreNote += `**Per-domain
|
|
474
|
+
coreNote += `**Per-domain skill notes will be generated in subsequent 3c-1, 3c-2, ... batch stages, under each category's \`domains/\` sub-folder.**\n`;
|
|
364
475
|
}
|
|
365
476
|
|
|
366
477
|
coreNote += `\nIf you find yourself about to generate a domain-specific file in this stage: STOP. Emit \`[DEFER] <path> — will be generated in 3b-N / 3c-N batch\` and move on.\n\n`;
|
|
@@ -550,15 +661,23 @@ async function runPass3Split(ctx) {
|
|
|
550
661
|
? `domain batch ${bi + 1}/${batches.length} (${batchDomains.length} domains)`
|
|
551
662
|
: "core files (CLAUDE.md + standard + rules)";
|
|
552
663
|
|
|
553
|
-
// Per-
|
|
554
|
-
//
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
664
|
+
// v2.4.0 — Per-domain scope note ALWAYS injected (single OR multi-batch).
|
|
665
|
+
//
|
|
666
|
+
// Previously single-batch projects (≤15 domains) skipped the scope note,
|
|
667
|
+
// and the stack pass3 template alone didn't mandate per-domain file
|
|
668
|
+
// generation under `70.domains/{type}/`. As a result, small projects
|
|
669
|
+
// got common standards but no per-domain files — inconsistent with
|
|
670
|
+
// multi-batch projects and breaking the uniform-convention contract.
|
|
671
|
+
//
|
|
672
|
+
// The note text itself branches on isSingleBatch internally:
|
|
673
|
+
// - Single batch: "Generate common files AND per-domain files"
|
|
674
|
+
// - Multi batch: "Per-domain only (common files done in 3b-core)"
|
|
675
|
+
const batchScopeNote = buildBatchScopeNote("3b", bi, batches.length, batchDomains);
|
|
558
676
|
const baseprompt = buildStagePrompt("pass3b-core-header.md", true);
|
|
559
|
-
const promptWithScope =
|
|
560
|
-
|
|
561
|
-
|
|
677
|
+
const promptWithScope = baseprompt.replace(
|
|
678
|
+
/\n## Scope of this step/,
|
|
679
|
+
`\n${batchScopeNote}\n## Scope of this step`
|
|
680
|
+
);
|
|
562
681
|
|
|
563
682
|
await runStage(stageId, label, promptWithScope, {
|
|
564
683
|
expectsStagedRules: true,
|
|
@@ -632,13 +751,16 @@ async function runPass3Split(ctx) {
|
|
|
632
751
|
? `domain skills batch ${bi + 1}/${batches.length} (${batchDomains.length} domains)`
|
|
633
752
|
: "skills and guides";
|
|
634
753
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
754
|
+
// v2.4.0 — Per-domain skill scope note ALWAYS injected (single OR multi-batch).
|
|
755
|
+
// Same rationale as Pass 3b: small projects need per-domain skill notes
|
|
756
|
+
// (`{category}/domains/{domain}.md`) and the `02.domains.md` orchestrator
|
|
757
|
+
// for uniform layout across all project sizes.
|
|
758
|
+
const batchScopeNote = buildBatchScopeNote("3c", bi, batches.length, batchDomains);
|
|
638
759
|
const baseprompt = buildStagePrompt("pass3c-skills-guide-header.md", true);
|
|
639
|
-
const promptWithScope =
|
|
640
|
-
|
|
641
|
-
|
|
760
|
+
const promptWithScope = baseprompt.replace(
|
|
761
|
+
/\n## Scope of this step/,
|
|
762
|
+
`\n${batchScopeNote}\n## Scope of this step`
|
|
763
|
+
);
|
|
642
764
|
|
|
643
765
|
await runStage(stageId, label, promptWithScope, {
|
|
644
766
|
expectsStagedRules: true, // skills occasionally include rule files
|
|
@@ -897,15 +1019,26 @@ function ensureDirectories() {
|
|
|
897
1019
|
".claude/rules/50.sync",
|
|
898
1020
|
"claudeos-core/generated",
|
|
899
1021
|
"claudeos-core/standard/00.core",
|
|
900
|
-
"claudeos-core/standard/10.backend
|
|
901
|
-
"claudeos-core/standard/20.frontend
|
|
1022
|
+
"claudeos-core/standard/10.backend",
|
|
1023
|
+
"claudeos-core/standard/20.frontend",
|
|
902
1024
|
"claudeos-core/standard/30.security-db",
|
|
903
1025
|
"claudeos-core/standard/40.infra",
|
|
904
|
-
"claudeos-core/standard/
|
|
1026
|
+
"claudeos-core/standard/80.verification",
|
|
905
1027
|
"claudeos-core/standard/90.optional",
|
|
906
1028
|
"claudeos-core/skills/00.shared",
|
|
907
1029
|
"claudeos-core/skills/10.backend-crud/scaffold-crud-feature",
|
|
1030
|
+
// v2.4.0 — `domains/` sub-folder under each per-domain skill category.
|
|
1031
|
+
// Pre-created so Pass 3c-N has a stable destination for per-domain
|
|
1032
|
+
// skill notes (`{category}/domains/{domain}.md`) and so the convention
|
|
1033
|
+
// is visible in the post-init dirtree alongside `scaffold-*-feature/`.
|
|
1034
|
+
// The `02.domains.md` orchestrator (sibling at category root) is
|
|
1035
|
+
// generated by Pass 3c-core, not pre-created — the file content is
|
|
1036
|
+
// project-specific. Empty pre-created `domains/` folders are
|
|
1037
|
+
// harmless on filesystems that allow them and self-document the
|
|
1038
|
+
// convention.
|
|
1039
|
+
"claudeos-core/skills/10.backend-crud/domains",
|
|
908
1040
|
"claudeos-core/skills/20.frontend-page/scaffold-page-feature",
|
|
1041
|
+
"claudeos-core/skills/20.frontend-page/domains",
|
|
909
1042
|
"claudeos-core/skills/50.testing",
|
|
910
1043
|
"claudeos-core/skills/90.experimental",
|
|
911
1044
|
"claudeos-core/guide/01.onboarding",
|
|
@@ -916,6 +1049,28 @@ function ensureDirectories() {
|
|
|
916
1049
|
"claudeos-core/mcp-guide",
|
|
917
1050
|
"claudeos-core/memory",
|
|
918
1051
|
".claude/rules/60.memory",
|
|
1052
|
+
// v2.4.0 — 80.verification rules (mirror of standard/80.verification).
|
|
1053
|
+
// Pre-created to give Pass 3 LLM a stable destination for verification
|
|
1054
|
+
// rules (testing strategy, build verification reminders that auto-load
|
|
1055
|
+
// when editing test files / build configs). Without this, prior runs
|
|
1056
|
+
// would create them at the LEGACY 50.verification path (cross-namespace
|
|
1057
|
+
// contamination from standard/50.verification), now corrected to the
|
|
1058
|
+
// unified 80.* numbering.
|
|
1059
|
+
".claude/rules/80.verification",
|
|
1060
|
+
// v2.4.0 — 70.domains/ canonical per-domain folder, ALWAYS typed.
|
|
1061
|
+
// - PLURAL folder (collection of N per-domain files)
|
|
1062
|
+
// - 70 number to avoid 60.* collision with 60.memory
|
|
1063
|
+
// - ALWAYS uses `{type}/` sub-folder (`backend/` or `frontend/`)
|
|
1064
|
+
// regardless of single/multi-stack. Uniform convention prevents
|
|
1065
|
+
// migration when a single-stack project later adds the other
|
|
1066
|
+
// stack, and gives validators a single pattern to recognize.
|
|
1067
|
+
// - Pre-created so Pass 3 LLM has a stable destination for
|
|
1068
|
+
// `claudeos-core/standard/70.domains/{type}/{domain}.md` and
|
|
1069
|
+
// `.claude/rules/70.domains/{type}/{domain}-rules.md` writes.
|
|
1070
|
+
"claudeos-core/standard/70.domains/backend",
|
|
1071
|
+
"claudeos-core/standard/70.domains/frontend",
|
|
1072
|
+
".claude/rules/70.domains/backend",
|
|
1073
|
+
".claude/rules/70.domains/frontend",
|
|
919
1074
|
];
|
|
920
1075
|
for (const d of dirs) {
|
|
921
1076
|
ensureDir(path.join(PROJECT_ROOT, d));
|
|
@@ -453,10 +453,21 @@ async function main() {
|
|
|
453
453
|
// distinctive signal that essentially never appears in ordinary
|
|
454
454
|
// identifiers (lowercase `xxx` CAN appear in words like `taxXxxRate`,
|
|
455
455
|
// but three uppercase X's do not occur outside placeholder convention).
|
|
456
|
+
// v2.4.0 — Ellipsis (`...`) added as a placeholder marker.
|
|
457
|
+
//
|
|
458
|
+
// LLMs commonly use `...` to denote "any subdirectory" in illustrative
|
|
459
|
+
// path examples, e.g. `src/app/api/.../route.ts` to mean "any API
|
|
460
|
+
// route under app/api/". Treating these as STALE_PATH false-positives
|
|
461
|
+
// because `...` doesn't match the literal directory regex `[^/]+\.`,
|
|
462
|
+
// and because `...` can never be a real directory name (filesystems
|
|
463
|
+
// refuse it on most platforms — `.` and `..` are the only legal
|
|
464
|
+
// dot-only directory names). Three consecutive dots in a path segment
|
|
465
|
+
// are unambiguous placeholder signal.
|
|
456
466
|
const hasPlaceholder = (p) =>
|
|
457
467
|
/\{[^}]+\}/.test(p) || // {domain} style
|
|
458
468
|
/X{3,}/.test(p) || /Xxx/.test(p) || // XXX+ anywhere, or Xxx token
|
|
459
|
-
/\*/.test(p)
|
|
469
|
+
/\*/.test(p) || // glob star
|
|
470
|
+
/\/\.\.\.\//.test(p); // /.../ ellipsis path segment (v2.4.0)
|
|
460
471
|
|
|
461
472
|
// File-level exclusion: some generated rule files are DESIGNED to cite
|
|
462
473
|
// convention-trap paths as teaching examples — they tell the reader
|
|
@@ -480,10 +491,55 @@ async function main() {
|
|
|
480
491
|
// denylist primed the LLM to cite those exact paths as
|
|
481
492
|
// educational examples (the denylist has since been removed;
|
|
482
493
|
// this exclusion is the validator-side defense-in-depth).
|
|
494
|
+
// - 00.core/51.doc-writing-rules.md — the documentation writing
|
|
495
|
+
// rules file (v2.4.0). Same meta-doc class as 52.ai-work-rules.md:
|
|
496
|
+
// it teaches "verify file paths before writing them in documents"
|
|
497
|
+
// and naturally cites example paths (`src/middleware.ts`,
|
|
498
|
+
// `src/app/api/<route>/route.ts`) as illustrations of the rule.
|
|
499
|
+
// Those examples are NOT path claims — they are the lesson. The
|
|
500
|
+
// content-blind validator would otherwise flag every cited example
|
|
501
|
+
// as STALE_PATH on every project that doesn't happen to contain
|
|
502
|
+
// all the cited illustrative files. Excluded for the same reason
|
|
503
|
+
// and via the same mechanism as 52.ai-work-rules.md.
|
|
483
504
|
const PATH_CLAIM_EXCLUDE_FILES = new Set([
|
|
484
505
|
"00.core/52.ai-work-rules.md",
|
|
506
|
+
"00.core/51.doc-writing-rules.md",
|
|
485
507
|
]);
|
|
486
508
|
|
|
509
|
+
// v2.4.0 — Resolve a `src/...` path claim against monorepo workspaces.
|
|
510
|
+
//
|
|
511
|
+
// Pre-v2.4.0 the validator only checked `<ROOT>/<claimed>` directly,
|
|
512
|
+
// which produced false-positive STALE_PATH advisories on Turborepo /
|
|
513
|
+
// pnpm-workspace projects where source files live under
|
|
514
|
+
// `apps/<app-name>/src/...` or `packages/<pkg-name>/src/...`. A rule
|
|
515
|
+
// citing `src/app/layout.tsx` is the natural single-app shorthand
|
|
516
|
+
// even when the actual file is at `apps/<app-name>/src/app/layout.tsx`.
|
|
517
|
+
//
|
|
518
|
+
// Resolution order (first match wins):
|
|
519
|
+
// 1. Direct: `<ROOT>/<claimed>` (single-app project)
|
|
520
|
+
// 2. Monorepo apps: `<ROOT>/apps/*/<claimed>`
|
|
521
|
+
// 3. Monorepo packages: `<ROOT>/packages/*/<claimed>`
|
|
522
|
+
//
|
|
523
|
+
// Returns true on first match, false if no match found anywhere.
|
|
524
|
+
// The monorepo fallback only fires for paths starting with `src/`,
|
|
525
|
+
// which is the conventional workspace-relative form. Non-`src/` paths
|
|
526
|
+
// (e.g., `claudeos-core/skills/...`) are checked direct-only.
|
|
527
|
+
function resolvePathClaim(ROOT, claimed) {
|
|
528
|
+
if (fs.existsSync(path.join(ROOT, claimed))) return true;
|
|
529
|
+
if (!claimed.startsWith("src/")) return false;
|
|
530
|
+
for (const workspace of ["apps", "packages"]) {
|
|
531
|
+
const wsDir = path.join(ROOT, workspace);
|
|
532
|
+
let entries;
|
|
533
|
+
try { entries = fs.readdirSync(wsDir, { withFileTypes: true }); }
|
|
534
|
+
catch (_e) { continue; }
|
|
535
|
+
for (const entry of entries) {
|
|
536
|
+
if (!entry.isDirectory()) continue;
|
|
537
|
+
if (fs.existsSync(path.join(wsDir, entry.name, claimed))) return true;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
|
|
487
543
|
// Strip fenced code blocks (``` and ~~~) so examples inside code
|
|
488
544
|
// blocks don't trigger the check — they're illustrations, not claims.
|
|
489
545
|
function stripFences(text) {
|
|
@@ -536,8 +592,7 @@ async function main() {
|
|
|
536
592
|
seen.add(claimed);
|
|
537
593
|
if (hasPlaceholder(claimed)) continue;
|
|
538
594
|
pathClaimsChecked++;
|
|
539
|
-
|
|
540
|
-
if (!fs.existsSync(absolutePath)) {
|
|
595
|
+
if (!resolvePathClaim(ROOT, claimed)) {
|
|
541
596
|
pathClaimErrors++;
|
|
542
597
|
errors.push({
|
|
543
598
|
file: rel(file),
|
|
@@ -646,8 +701,13 @@ async function main() {
|
|
|
646
701
|
// file of the form `skills/{category}/*{parent}*.md` (excluding
|
|
647
702
|
// the sub-skill itself) counts as a plausible orchestrator.
|
|
648
703
|
function orchestratorFor(subSkillPath) {
|
|
704
|
+
// Sub-skill path forms accepted (v2.4.0 generalization):
|
|
705
|
+
// skills/{category}/{parent-stem}/NN.{name}.md (legacy NN. prefix)
|
|
706
|
+
// skills/{category}/{parent-stem}/SKILL.md (v2.4.0 SKILL.md convention)
|
|
707
|
+
// skills/{category}/{parent-stem}/{name}.md (no NN. prefix)
|
|
708
|
+
// Captures `parent-stem` and the category directory.
|
|
649
709
|
const m = subSkillPath.match(
|
|
650
|
-
/^(claudeos-core\/skills\/[^/]+\/)([^/]+)
|
|
710
|
+
/^(claudeos-core\/skills\/[^/]+\/)([^/]+)\/(?:\d+\.)?[^/]+\.md$/
|
|
651
711
|
);
|
|
652
712
|
if (!m) return null;
|
|
653
713
|
return { categoryDir: m[1], stem: m[2] };
|
|
@@ -657,15 +717,47 @@ async function main() {
|
|
|
657
717
|
// basename (minus leading number + dot) matches the sub-skill
|
|
658
718
|
// parent stem. This accepts `01.scaffold-crud-feature.md`,
|
|
659
719
|
// `scaffold-crud-feature.md`, etc.
|
|
720
|
+
// v2.4.0: a category-level `SKILL.md` (the orchestrator file
|
|
721
|
+
// colocated with `{category}/SKILL.md`) is treated as the
|
|
722
|
+
// orchestrator for ALL sub-skills under that category — this
|
|
723
|
+
// matches the new generator convention where each category has
|
|
724
|
+
// a single top-level orchestrator at `{category}/SKILL.md`.
|
|
660
725
|
if (!ref.startsWith(categoryDir)) return false;
|
|
661
726
|
const tail = ref.slice(categoryDir.length);
|
|
662
727
|
// Must be a sibling file, not a nested path.
|
|
663
728
|
if (tail.includes("/")) return false;
|
|
729
|
+
// v2.4.0 SKILL.md convention: a category-level SKILL.md covers
|
|
730
|
+
// every sub-skill in that category.
|
|
731
|
+
if (tail === "SKILL.md") return true;
|
|
664
732
|
// Strip leading "NN." if present, then compare stem.
|
|
665
733
|
const base = tail.replace(/^\d+\./, "").replace(/\.md$/, "");
|
|
666
734
|
return base === stem;
|
|
667
735
|
}
|
|
668
736
|
|
|
737
|
+
// Pre-compute: is any MANIFEST.md (the global skill registry)
|
|
738
|
+
// referenced anywhere in CLAUDE.md? Used as an additional
|
|
739
|
+
// sub-skill coverage rule below.
|
|
740
|
+
//
|
|
741
|
+
// Observed scenario: Pass 3c sometimes invents new sub-skill
|
|
742
|
+
// folder structures (e.g. `{category}/domains/{domain}.md` for
|
|
743
|
+
// per-domain notes) that weren't anticipated by the orchestrator
|
|
744
|
+
// pattern (which expects `{category}/{parent-stem}/{name}.md` paired
|
|
745
|
+
// with `{category}/{parent-stem}.md`). When this happens, every new
|
|
746
|
+
// sub-skill registration drifts because no sibling orchestrator exists.
|
|
747
|
+
//
|
|
748
|
+
// The architectural intent is that MANIFEST.md IS the registry — if
|
|
749
|
+
// CLAUDE.md §6 tells the reader "see MANIFEST.md for the full list",
|
|
750
|
+
// the reader can navigate to find every sub-skill. So mentioning
|
|
751
|
+
// MANIFEST.md anywhere in CLAUDE.md covers ALL sub-skill paths
|
|
752
|
+
// transitively. Top-level skills (direct `{category}/{file}.md`
|
|
753
|
+
// entries — those that don't match `orchestratorFor`) still require
|
|
754
|
+
// direct mention; this exception only applies to deep paths.
|
|
755
|
+
//
|
|
756
|
+
// Note: `referenced` Set above filters out MANIFEST.md entries (line
|
|
757
|
+
// 666), so we must scan the raw mdStripped text directly to detect
|
|
758
|
+
// MANIFEST.md mention.
|
|
759
|
+
const manifestReferencedGlobally = /`claudeos-core\/skills\/[\w\-./]*MANIFEST\.md`/.test(mdStripped);
|
|
760
|
+
|
|
669
761
|
for (const p of registered) {
|
|
670
762
|
if (referenced.has(p)) continue; // direct mention → OK
|
|
671
763
|
|
|
@@ -676,6 +768,7 @@ async function main() {
|
|
|
676
768
|
isOrchestratorReferenced(ref, oc)
|
|
677
769
|
);
|
|
678
770
|
if (orchestratorMentioned) continue; // covered via orchestrator
|
|
771
|
+
if (manifestReferencedGlobally) continue; // covered via global MANIFEST
|
|
679
772
|
}
|
|
680
773
|
|
|
681
774
|
manifestErrors++;
|
package/health-checker/index.js
CHANGED
|
@@ -61,11 +61,29 @@ function main() {
|
|
|
61
61
|
console.log();
|
|
62
62
|
|
|
63
63
|
// ─── [1-4] Run verification tools sequentially ────────────────────
|
|
64
|
+
//
|
|
65
|
+
// Tool status tiers (3-way):
|
|
66
|
+
// - default : non-zero exit → "fail" (❌, sets hasErr, blocks `health` exit code)
|
|
67
|
+
// - warnOnly:true : non-zero exit → "warn" (⚠️, does not set hasErr)
|
|
68
|
+
// - softFail:true : non-zero exit → "advisory" (ℹ️, does not set hasErr)
|
|
69
|
+
//
|
|
70
|
+
// The `softFail` tier (v2.4.0) was added for `content-validator` after
|
|
71
|
+
// user feedback: its findings are documentation quality notes
|
|
72
|
+
// (STALE_PATH suggestions, MANIFEST_DRIFT, NO_BAD_EXAMPLE) not generation
|
|
73
|
+
// failures, but pre-fix it surfaced as "❌ fail" alongside the
|
|
74
|
+
// "non-fatal" message — a confusing dual signal. `ℹ️ advisory` separates
|
|
75
|
+
// the visual from real structural failures (plan-validator,
|
|
76
|
+
// sync-checker, manifest-generator).
|
|
77
|
+
//
|
|
78
|
+
// `warnOnly` (existing) and `softFail` (new) are functionally similar at
|
|
79
|
+
// the gate level; the tier name encodes intent: warn = "watch this",
|
|
80
|
+
// advisory = "review when convenient". Both keep the health-check gate
|
|
81
|
+
// green.
|
|
64
82
|
const tools = [
|
|
65
|
-
{ name: "plan-validator", script: path.join(TOOLS, "plan-validator/index.js"), desc: "Plan consistency"
|
|
66
|
-
{ name: "sync-checker", script: path.join(TOOLS, "sync-checker/index.js"), desc: "Sync status"
|
|
67
|
-
{ name: "content-validator", script: path.join(TOOLS, "content-validator/index.js"), desc: "Content quality" },
|
|
68
|
-
{ name: "pass-json-validator", script: path.join(TOOLS, "pass-json-validator/index.js"), desc: "JSON format",
|
|
83
|
+
{ name: "plan-validator", script: path.join(TOOLS, "plan-validator/index.js"), desc: "Plan consistency" },
|
|
84
|
+
{ name: "sync-checker", script: path.join(TOOLS, "sync-checker/index.js"), desc: "Sync status" },
|
|
85
|
+
{ name: "content-validator", script: path.join(TOOLS, "content-validator/index.js"), desc: "Content quality", softFail: true },
|
|
86
|
+
{ name: "pass-json-validator", script: path.join(TOOLS, "pass-json-validator/index.js"), desc: "JSON format", warnOnly: true },
|
|
69
87
|
];
|
|
70
88
|
|
|
71
89
|
const results = [];
|
|
@@ -88,6 +106,9 @@ function main() {
|
|
|
88
106
|
if (r.ok) {
|
|
89
107
|
console.log(" ✅");
|
|
90
108
|
results.push({ name: t.name, status: "pass" });
|
|
109
|
+
} else if (t.softFail) {
|
|
110
|
+
console.log(" ℹ️");
|
|
111
|
+
results.push({ name: t.name, status: "advisory" });
|
|
91
112
|
} else if (t.warnOnly) {
|
|
92
113
|
console.log(" ⚠️");
|
|
93
114
|
results.push({ name: t.name, status: "warn" });
|
|
@@ -101,15 +122,28 @@ function main() {
|
|
|
101
122
|
// ─── Results summary ──────────────────────────────────────────
|
|
102
123
|
console.log("\n ══════════════════════════════");
|
|
103
124
|
results.forEach((r) => {
|
|
104
|
-
const icon = r.status === "pass" ? "✅"
|
|
125
|
+
const icon = r.status === "pass" ? "✅"
|
|
126
|
+
: r.status === "fail" ? "❌"
|
|
127
|
+
: r.status === "warn" ? "⚠️"
|
|
128
|
+
: r.status === "advisory" ? "ℹ️"
|
|
129
|
+
: "⏭️";
|
|
105
130
|
console.log(` ${icon} ${r.name.padEnd(22)} ${r.status}`);
|
|
106
131
|
});
|
|
107
132
|
console.log(" ──────────────────────────────");
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
);
|
|
133
|
+
// Summary line: distinguish real failures (block the gate) from
|
|
134
|
+
// advisory/warn results (informational, gate stays green).
|
|
135
|
+
const failCount = results.filter((r) => r.status === "fail").length;
|
|
136
|
+
const advisoryCount = results.filter((r) => r.status === "advisory").length;
|
|
137
|
+
const warnCount = results.filter((r) => r.status === "warn").length;
|
|
138
|
+
if (hasErr) {
|
|
139
|
+
console.log(` ⚠️ ${failCount} failed`);
|
|
140
|
+
} else {
|
|
141
|
+
const tail = [];
|
|
142
|
+
if (advisoryCount) tail.push(`${advisoryCount} advisory`);
|
|
143
|
+
if (warnCount) tail.push(`${warnCount} warning`);
|
|
144
|
+
const suffix = tail.length ? ` (${tail.join(", ")})` : "";
|
|
145
|
+
console.log(` ✅ All systems operational${suffix}`);
|
|
146
|
+
}
|
|
113
147
|
console.log(" ══════════════════════════════\n");
|
|
114
148
|
|
|
115
149
|
// ─── Update stale-report.json ────────────────────────────
|