claudeos-core 2.4.4 → 2.5.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.de.md +12 -10
  3. package/README.es.md +12 -10
  4. package/README.fr.md +12 -10
  5. package/README.hi.md +12 -10
  6. package/README.ja.md +12 -10
  7. package/README.ko.md +12 -10
  8. package/README.md +12 -10
  9. package/README.ru.md +12 -10
  10. package/README.vi.md +12 -10
  11. package/README.zh-CN.md +12 -10
  12. package/bin/commands/init.js +121 -24
  13. package/bin/commands/lint.js +2 -0
  14. package/bin/commands/memory.js +10 -3
  15. package/content-validator/index.js +82 -13
  16. package/lib/env-parser.js +98 -12
  17. package/lib/memory-scaffold.js +35 -16
  18. package/manifest-generator/index.js +15 -4
  19. package/package.json +92 -92
  20. package/pass-json-validator/index.js +1 -1
  21. package/pass-prompts/templates/angular/pass3.md +2 -1
  22. package/pass-prompts/templates/common/claude-md-scaffold.md +1 -1
  23. package/pass-prompts/templates/common/pass3a-facts.md +11 -9
  24. package/pass-prompts/templates/common/pass4.md +3 -3
  25. package/pass-prompts/templates/java-spring/pass1.md +10 -2
  26. package/pass-prompts/templates/java-spring/pass3.md +5 -4
  27. package/pass-prompts/templates/kotlin-spring/pass3.md +2 -2
  28. package/pass-prompts/templates/node-express/pass3.md +1 -1
  29. package/pass-prompts/templates/node-fastify/pass3.md +1 -0
  30. package/pass-prompts/templates/node-nestjs/pass3.md +1 -0
  31. package/pass-prompts/templates/node-nextjs/pass3.md +1 -1
  32. package/pass-prompts/templates/node-vite/pass3.md +1 -0
  33. package/pass-prompts/templates/python-django/pass3.md +1 -1
  34. package/pass-prompts/templates/python-fastapi/pass3.md +1 -1
  35. package/pass-prompts/templates/python-flask/pass3.md +1 -0
  36. package/pass-prompts/templates/vue-nuxt/pass3.md +1 -0
  37. package/plan-installer/domain-grouper.js +4 -1
  38. package/plan-installer/index.js +26 -7
  39. package/plan-installer/jvm-detect.js +562 -0
  40. package/plan-installer/pass3-context-builder.js +10 -0
  41. package/plan-installer/prompt-generator.js +18 -2
  42. package/plan-installer/scanners/scan-frontend.js +67 -6
  43. package/plan-installer/scanners/scan-java.js +214 -15
  44. package/plan-installer/scanners/scan-kotlin.js +68 -3
  45. package/plan-installer/scanners/scan-node.js +115 -0
  46. package/plan-installer/scanners/scan-python.js +56 -0
  47. package/plan-installer/source-paths.js +61 -0
  48. package/plan-installer/stack-detector.js +726 -51
  49. package/plan-installer/structure-scanner.js +15 -4
package/lib/env-parser.js CHANGED
@@ -220,6 +220,54 @@ function extractApiTarget(vars) {
220
220
  const SENSITIVE_VAR_PATTERNS = [
221
221
  /password/i,
222
222
  /passwd/i,
223
+ // Abbreviated form (`DB_PASS`, `MYSQL_PASS`, bare `PASS`). Anchored to a
224
+ // name-segment boundary (start / `_` / `-`) rather than a bare substring,
225
+ // so `BYPASS_AUTH`, `PASSENGER_NAME` and `COMPASS_URL` are NOT swept up.
226
+ // maskUrlCredentials' PARAM_RE has accepted `pass` as a connection
227
+ // parameter name since v2.5.0; this closes the same gap on the env-key
228
+ // side, where the value is copied verbatim into project-analysis.json.
229
+ //
230
+ // KNOWN, ACCEPTED OVER-MATCH: a leading `PASS_` segment also matches
231
+ // benign names such as `PASS_RATE`. Narrowing to a trailing segment
232
+ // (`/(^|[_-])pass$/i`) would fix that but drop `DB_PASS_2` / `PASS_FILE`.
233
+ // The two failure modes are not symmetric — over-redaction costs one
234
+ // config fact in project-analysis.json, under-redaction writes a live
235
+ // credential into a file the Pass 3/4 prompts tell the model to read — so
236
+ // the broader rule stands. Do not narrow it without re-reading this.
237
+ /(^|[_-])pass([_-]|$)/i,
238
+ // Same segment-anchored treatment for the `PW` abbreviation
239
+ // (`DB_PW`, `ADMIN_PW`, `ROOT_PW`).
240
+ /(^|[_-])pw([_-]|$)/i,
241
+ /passphrase/i,
242
+ // Trailing-segment only: `PASSWORD_PEPPER` / `PEPPER` are the secret,
243
+ // `PEPPER_ROUNDS` is a cost parameter.
244
+ /(^|[_-])pepper$/i,
245
+ // Anchored so `SSH_KEYSCAN_HOSTS` (a host list) is not swept up, while
246
+ // `SSH_KEY` / `SSH_KEY_PATH` still are.
247
+ /ssh[_-]?key([_-]|$)/i,
248
+ /sign(ing)?[_-]?key([_-]|$)/i,
249
+ // Specific well-known secret-bearing `*_KEY` names. Deliberately NOT a
250
+ // blanket `/(^|[_-])key([_-]|$)/i`: that would also redact `ROUTING_KEY`,
251
+ // `PARTITION_KEY`, `SORT_KEY`, `IDEMPOTENCY_KEY` and `FOREIGN_KEY_CHECKS`,
252
+ // which are architecture facts this tool exists to document. Each name is
253
+ // anchored at its trailing boundary so `MASTER_KEYSPACE` (Cassandra) and
254
+ // `SERVER_KEYSTORE_PATH` are not swept up.
255
+ //
256
+ // This is a curated list and will always trail real-world naming. If a
257
+ // leak is found, add the specific name here rather than widening to a
258
+ // blanket `key` rule.
259
+ /master[_-]?key([_-]|$)/i,
260
+ /deploy[_-]?key([_-]|$)/i,
261
+ /license[_-]?key([_-]|$)/i,
262
+ /server[_-]?key([_-]|$)/i,
263
+ // Bare `SERVICE_ACCOUNT` (the JSON blob itself) and its secret-bearing
264
+ // suffixes only. `SERVICE_ACCOUNT_EMAIL` / `_NAME` / `_ID` identify the
265
+ // account, they do not authenticate as it — those are config facts.
266
+ // Two patterns, not one with an optional group: an optional group matches
267
+ // empty and the trailing `[_-]` then swallows the separator, which makes
268
+ // `SERVICE_ACCOUNT_EMAIL` match after all.
269
+ /service[_-]?account$/i,
270
+ /service[_-]?account[_-](keys?|json|file|secret|token|creds?|credentials?)([_-]|$)/i,
223
271
  /secret/i,
224
272
  /api[_-]?key/i,
225
273
  /access[_-]?key/i,
@@ -247,28 +295,65 @@ function isSensitiveVarName(name) {
247
295
  return SENSITIVE_VAR_PATTERNS.some(re => re.test(name));
248
296
  }
249
297
 
298
+ /**
299
+ * Mask the userinfo component of a URL-shaped value:
300
+ * postgres://app:s3cret@db.internal:5432/app → postgres://***:***@db.internal:5432/app
301
+ * Scheme, host, port, path and query are preserved so consumers can still
302
+ * identify the DB engine / host. Non-URL values pass through unchanged.
303
+ */
304
+ function maskUrlCredentials(value) {
305
+ if (typeof value !== "string") return value;
306
+ // Scheme may itself contain `:` (`jdbc:postgresql://`, `jdbc:mysql://`).
307
+ // Userinfo is everything between `://` and the LAST `@` of the authority
308
+ // part, so a password containing `@` (`p@ss`) is masked whole. It may NOT
309
+ // contain `/`, `?` or `#`: an `@` that appears after the first `/` belongs
310
+ // to the path or query (`https://cdn.example.com/npm/@scope/pkg`,
311
+ // `/users/@me`, `?redirect=user@host`) and must never be rewritten —
312
+ // masking it would replace the real host with a path fragment. A raw `/`
313
+ // inside a password is not a valid URL and is deliberately left alone.
314
+ // Credentials carried as connection PARAMETERS rather than userinfo:
315
+ // jdbc:postgresql://db/app?user=app&password=s3cret
316
+ // mongodb://host/db?authSource=admin&password=x
317
+ // sqlserver://host;databaseName=app;user=sa;password=x
318
+ // The parameter NAME is kept, the value becomes `***`.
319
+ const PARAM_RE = /([?&;](?:password|passwd|pwd|pass|secret|token|access[_-]?key|secret[_-]?key|api[_-]?key|sas|signature)=)[^&;\s]*/gi;
320
+ if (/^[a-z][a-z0-9+.:-]*:\/\//i.test(value)) {
321
+ return value
322
+ .replace(/^([a-z][a-z0-9+.:-]*:\/\/)([^/?#\s]*)@([^@/?#\s]+)/i, "$1***:***@$3")
323
+ .replace(PARAM_RE, "$1***");
324
+ }
325
+ // Scheme-less credentials are recognized ONLY in the Go/MySQL DSN shape
326
+ // (`user:pw@tcp(host:3306)/db`, `user:pw@unix(/path)/db`); the password may
327
+ // itself contain `@` (`p@ss`) — everything up to the `@` before `tcp(`/`unix(`
328
+ // is userinfo. A generic `a:b@c` rule would corrupt `mailto:ops@example.com`
329
+ // or `0:30@daily`.
330
+ return value
331
+ .replace(/^([^:@/\s]+):(.*)@(?=(?:tcp|unix)\()/, "***:***@")
332
+ .replace(PARAM_RE, "$1***");
333
+ }
334
+
250
335
  /**
251
336
  * Redacts sensitive values in an env vars map. Returns a new object;
252
337
  * original is not mutated. Preserves keys so "variable exists" signal
253
338
  * is kept, but replaces values with a sentinel string.
254
339
  *
255
- * Whitelist exception: DATABASE_URL is kept as-is because stack-detector's
256
- * db-identification path has always used it and existing project-analysis
257
- * consumers depend on reading it. (The DB URL contains credentials, but
258
- * this has been the established behavior since v1.x and changing it would
259
- * be a breaking change. Downstream consumers that write CLAUDE.md content
260
- * from vars should still redact it at their layer.)
340
+ * v2.5.0 the former DATABASE_URL whitelist is gone. Its stated
341
+ * justification ("stack-detector's db-identification path depends on it")
342
+ * was stale: stack-detector scans the raw .env text with includes() and
343
+ * never reads envInfo.vars. Meanwhile the unredacted value typically
344
+ * `postgres://user:password@host/db` landed verbatim in
345
+ * project-analysis.json, which Pass 3/4 prompts instruct the LLM to read.
346
+ * Every URL-shaped value (DATABASE_URL, REDIS_URL, MONGO_URI, AMQP_URL, …)
347
+ * now has its userinfo masked while keeping scheme/host/path intact.
261
348
  */
262
349
  function redactSensitiveVars(vars) {
263
350
  if (!vars || typeof vars !== "object") return vars;
264
351
  const out = {};
265
352
  for (const [k, v] of Object.entries(vars)) {
266
- if (k === "DATABASE_URL") {
267
- out[k] = v; // documented whitelist for stack-detector back-compat
268
- } else if (isSensitiveVarName(k)) {
353
+ if (isSensitiveVarName(k)) {
269
354
  out[k] = "***REDACTED***";
270
355
  } else {
271
- out[k] = v;
356
+ out[k] = maskUrlCredentials(v);
272
357
  }
273
358
  }
274
359
  return out;
@@ -293,8 +378,8 @@ function readStackEnvInfo(root) {
293
378
  source: file,
294
379
  vars: redactSensitiveVars(vars),
295
380
  port: extractPort(vars),
296
- host: extractHost(vars),
297
- apiTarget: extractApiTarget(vars),
381
+ host: maskUrlCredentials(extractHost(vars)),
382
+ apiTarget: maskUrlCredentials(extractApiTarget(vars)),
298
383
  };
299
384
  }
300
385
 
@@ -308,6 +393,7 @@ module.exports = {
308
393
  readStackEnvInfo,
309
394
  isSensitiveVarName,
310
395
  redactSensitiveVars,
396
+ maskUrlCredentials,
311
397
  // Exported for test visibility:
312
398
  ENV_FILE_ORDER,
313
399
  PORT_VAR_KEYS,
@@ -10,6 +10,7 @@
10
10
 
11
11
  const path = require("path");
12
12
  const fs = require("fs");
13
+ const crypto = require("crypto");
13
14
  const { ensureDir, existsSafe, writeFileSafe, readFileSafe } = require("./safe-fs");
14
15
 
15
16
  // ─── Language labels — single source of truth: lib/language-config.js ──
@@ -58,6 +59,18 @@ function writeCache(cacheFile, cache) {
58
59
  } catch (_e) { /* best-effort */ }
59
60
  }
60
61
 
62
+ // v2.5.0 — Cache entries are keyed by content name AND a hash of the English
63
+ // source. Pre-v2.5.0 the key was the bare name (`MEMORY_FILES.compaction.md`),
64
+ // so whenever a static-fallback text changed between releases, a project that
65
+ // already had `fallback-cache-<lang>.json` kept serving the translation of the
66
+ // OLD English text forever. Hashing the source makes a text change a cache
67
+ // miss automatically; stale entries under the old key shape are simply never
68
+ // read again.
69
+ function cacheKeyFor(contentKey, englishContent) {
70
+ const h = crypto.createHash("sha1").update(String(englishContent), "utf8").digest("hex").slice(0, 12);
71
+ return `${contentKey}@${h}`;
72
+ }
73
+
61
74
  // Translate English static content to the requested language via Claude CLI.
62
75
  //
63
76
  // Behavior contract:
@@ -81,6 +94,15 @@ function translateIfNeeded(englishContent, lang, contentKey, cacheFile) {
81
94
  );
82
95
  }
83
96
 
97
+ // Cache hit → return immediately. Checked BEFORE the env-skip below: a hit
98
+ // never shells out, so the skip has nothing to prevent, and this keeps the
99
+ // cache path unit-testable under CLAUDEOS_SKIP_TRANSLATION=1.
100
+ const cache = readCache(cacheFile);
101
+ const key = cacheKeyFor(contentKey, englishContent);
102
+ if (cache[key] && typeof cache[key] === "string" && cache[key].trim().length > 0) {
103
+ return cache[key];
104
+ }
105
+
84
106
  // M2: CI / deterministic-test escape hatch. When
85
107
  // CLAUDEOS_SKIP_TRANSLATION=1 is set, throw instead of shelling out to
86
108
  // `claude -p`. This makes the "translation fails without real Claude CLI"
@@ -95,12 +117,6 @@ function translateIfNeeded(englishContent, lang, contentKey, cacheFile) {
95
117
  );
96
118
  }
97
119
 
98
- // Cache hit → return immediately
99
- const cache = readCache(cacheFile);
100
- if (cache[contentKey] && typeof cache[contentKey] === "string" && cache[contentKey].trim().length > 0) {
101
- return cache[contentKey];
102
- }
103
-
104
120
  // Lazy-load CLI utils to avoid a circular dependency at module-load time.
105
121
  let runClaudeCapture;
106
122
  try {
@@ -256,8 +272,9 @@ Translate now. Output only the translated document.`;
256
272
  );
257
273
  }
258
274
 
259
- // Success — save to cache for future init runs
260
- cache[contentKey] = cleaned;
275
+ // Success — save to cache for future init runs (content-hashed key; must
276
+ // be recomputed here — this function does not share translateIfNeeded's scope).
277
+ cache[cacheKeyFor(contentKey, englishContent)] = cleaned;
261
278
  writeCache(cacheFile, cache);
262
279
 
263
280
  const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
@@ -282,7 +299,7 @@ _Format: \`## <pattern-id>\` with Frequency / Last Seen / Importance / Fix._
282
299
 
283
300
  "compaction.md": `# Compaction Strategy
284
301
 
285
- _4-stage compaction rules for \`decision-log.md\` and \`failure-patterns.md\`._
302
+ _4-stage compaction rules for \`failure-patterns.md\`. \`decision-log.md\` is append-only and never compacted._
286
303
  _Run via \`npx claudeos-core memory compact\`._
287
304
 
288
305
  ## Preservation Priority
@@ -431,8 +448,8 @@ Before analysis or evaluation, review this table and do not repeat these pattern
431
448
  | 8 | Making quantitative judgments based on unverified numbers, then reversing when challenged | No speculation-based quantitative claims |
432
449
  | 9 | Judging a standard document as "low practical value" without reading it | No judgment before verification |
433
450
  | 10 | Proposing to merge/reduce same-layer intentional duplication | Different perspectives on the same topic in different files is intentional. Rule accessibility > token saving |
434
- | 11 | Suggesting an import or package name without verifying it exists in the dependency manifest | Hallucinated module/named export. Verify by reading \`package.json\` / \`pom.xml\` / \`build.gradle\` / \`pyproject.toml\` / \`requirements.txt\` before recommending an import |
435
- | 12 | Mixing API signatures from different major versions of the same library | Verify by checking the manifest AND lockfile for the exact installed version (lockfile pins the truth): \`package-lock.json\`/\`pnpm-lock.yaml\`/\`yarn.lock\`, \`gradle.lockfile\`/\`gradle/libs.versions.toml\`, \`poetry.lock\`/\`Pipfile.lock\`/\`uv.lock\`, or fall back to the manifest (\`pom.xml\`/\`build.gradle\`/\`package.json\`/\`pyproject.toml\`) |
451
+ | 11 | Suggesting an import or package name without verifying it exists in the dependency manifest | Hallucinated module/named export. Verify by reading \`package.json\` / \`pom.xml\` / \`build.gradle\` / \`build.gradle.kts\` / \`pyproject.toml\` / \`requirements.txt\` before recommending an import |
452
+ | 12 | Mixing API signatures from different major versions of the same library | Verify by checking the manifest AND lockfile for the exact installed version (lockfile pins the truth): \`package-lock.json\`/\`pnpm-lock.yaml\`/\`yarn.lock\`, \`gradle.lockfile\`/\`gradle/libs.versions.toml\`, \`poetry.lock\`/\`Pipfile.lock\`/\`uv.lock\`, or fall back to the manifest (\`pom.xml\`/\`build.gradle\`/\`build.gradle.kts\`/\`package.json\`/\`pyproject.toml\`) |
436
453
  | 13 | Editing one environment config file without checking sibling parity | Verify by \`Glob\` for the config family before a partial edit. Backend: \`.env*\`, \`application-*.yml/.properties\`, \`*settings.py\`. Frontend: \`environment*.ts\` (Angular), \`next.config.*\`, \`vite.config.*\`, \`nuxt.config.*\`, \`.env.local\`/\`.env.production\` |
437
454
  | 14 | Mixing server/client component boundaries (SSR frameworks) — using client-only APIs (\`useState\`, \`useEffect\`, \`window\`, \`localStorage\`) in a server component, or server-only operations (DB access, secret reads, filesystem) in a client component | Read the file's boundary directive before adding code: Next.js App Router (\`"use client"\` opt-in), Nuxt (server vs client composables), Remix (\`loader\`/\`action\` exports). N/A for pure SPA or pure backend projects |
438
455
  | 15 | Inventing component prop names or function arguments without reading the target's interface | Read the target's prop type definition (\`interface Props\`, \`defineProps<>\`, function signature) or method signature before invoking. Hallucinated props may compile in loose-TS code and crash at runtime |
@@ -450,9 +467,8 @@ Before analysis or evaluation, review this table and do not repeat these pattern
450
467
  - **Do not modify onboarding/guide documents (\`claudeos-core/guide/\`) unless specifically requested.**
451
468
  - Do not clean up surrounding code during bug fixes, or add unnecessary refactoring during feature additions.
452
469
  - **Empty directories may be intentional placeholders — verify markers before flagging or removing.** Markers of intent: a \`.gitkeep\` / \`KEEP_EMPTY.md\` file inside, the directory listed in CLAUDE.md as planned, or referenced by an active plan/standard/skills document. If none of these exist, an empty directory may be neglect — ask the user before deleting.
453
- - **\`plan/\` master documents are internal sync management tools.** Do not suggest removing them. "DO NOT Read" means "AI should not read directly", not "the file is unnecessary".
454
470
  - **Do not duplicate into memory what is already directly verifiable in code, config files, or rule documents.** Configuration values, paths, and names already recorded in CLAUDE.md, rules, or standard must not be redundantly stored in memory. Examples — backend: port numbers, pool sizes, handler names, transaction propagation modes; frontend: dev server port, build output dir, env var prefix (\`VITE_\`/\`NEXT_PUBLIC_\`/\`REACT_APP_\`), route definitions, bundle size budgets.
455
- - **Do not directly read internal document directories (\`guide/\`, \`plan/\`, \`generated/\`, \`mcp-guide/\`) for routine context loading.** \`.claude/rules/\` and \`claudeos-core/standard/\` already contain the essential content. *Exception: read directly when the user explicitly asks about these contents, or when debugging an issue requires inspecting them.*
471
+ - **Do not directly read internal document directories (\`guide/\`, \`generated/\`, \`mcp-guide/\`) for routine context loading.** \`.claude/rules/\` and \`claudeos-core/standard/\` already contain the essential content. *Exception: read directly when the user explicitly asks about these contents, or when debugging an issue requires inspecting them.*
456
472
  - **Established codebase conventions take precedence over textbook-ideal patterns.** Propose modernization, refactoring, or "current best practices" migration ONLY when the user explicitly requests it (e.g., "modernize", "migrate to v3", "refactor to current best practices"). Otherwise, follow the existing pattern even if you would write it differently in a greenfield project.
457
473
 
458
474
  ## Project Architecture — Hands Off
@@ -464,7 +480,7 @@ This project uses the CLAUDE.md → rules → standard 3-layer architecture, plu
464
480
  - **Memory (\`claudeos-core/memory/\`, on-demand) and Rules (\`.claude/rules/\`, auto-loaded by path match) are never simultaneously loaded by default.** Similar content between them is NOT duplication. Do not propose "memory cleanup", "dedupe memory vs rules", or "consolidate memory into rules" — they have different roles (on-demand history/context vs auto-loaded enforcement).
465
481
  - **Multi-rule load provides reinforcement.** Do not label co-loaded defensive rules as "redundant" — the reinforcement effect is intentional and the context cost is negligible.
466
482
  - **Rules \`paths: ["**/*"]\` — do not propose conditional path conversion for core rules.** When developing new features (no matching files exist yet), conditional paths would prevent rules from loading, risking non-standard code generation.
467
- - **\`00.standard-reference.md\` index additions avoid unless specifically requested.** Each rules file already links to its corresponding standard via the \`## Reference\` section. Adding paths to the index only consumes additional tokens per conversation.
483
+ - **\`00.standard-reference.md\` is a paths-only index of \`claudeos-core/standard/\`.** When a standard file is added, add its path there (one line, no description). Do not add rule files, skill files, memory files, or "DO NOT Read" lists to it those belong elsewhere, and every extra line is reloaded on every edit.
468
484
  - **Minor wording or item count differences across layers are NOT "inconsistency risks".** If no information is missing, trivial expression differences are not a problem.
469
485
 
470
486
  ## Planned References — No "Missing" Judgment
@@ -577,8 +593,8 @@ paths:
577
593
 
578
594
  # Compaction Strategy (\`memory/compaction.md\`)
579
595
 
580
- Reference document defining the 4-stage compaction policy.
581
- Executed by \`npx claudeos-core memory compact\`.
596
+ Reference document defining the 4-stage compaction policy for \`failure-patterns.md\`.
597
+ Executed by \`npx claudeos-core memory compact\`. \`decision-log.md\` is never compacted (append-only).
582
598
 
583
599
  ## Rules
584
600
 
@@ -1055,4 +1071,7 @@ module.exports = {
1055
1071
  scaffoldMasterPlans,
1056
1072
  scaffoldDocWritingGuide,
1057
1073
  scaffoldSkillsManifest,
1074
+ // Exported for test visibility (v2.5.0: content-hashed translation cache).
1075
+ translateIfNeeded,
1076
+ _cacheKeyFor: cacheKeyFor,
1058
1077
  };
@@ -166,10 +166,21 @@ async function main() {
166
166
  // Deterministically reconcile MANIFEST.md ↔ CLAUDE.md §6 cross-references
167
167
  // that LLM stages routinely drift on. Failure here is logged but not
168
168
  // fatal — manifest-generator's primary outputs above are already on disk.
169
- try {
170
- syncSkillsCatalog(ROOT);
171
- } catch (e) {
172
- console.log(` ⚠️ skills-sync: unexpected error (${e.message || e})`);
169
+ //
170
+ // OPT-IN ONLY. This step WRITES to CLAUDE.md and MANIFEST.md. It must not
171
+ // run from `npx claudeos-core health` (documented as a read-only gate that
172
+ // users wire into CI / pre-commit) a health check that dirties the
173
+ // working tree is a trap. `init` passes `--sync-skills` after Pass 3/4;
174
+ // everything else gets a pure metadata generation run. The flag is the ONLY
175
+ // switch — an environment variable would be inherited by `health`'s child
176
+ // process and silently re-enable writes from a shell that exported it.
177
+ const syncRequested = process.argv.includes("--sync-skills");
178
+ if (syncRequested) {
179
+ try {
180
+ syncSkillsCatalog(ROOT);
181
+ } catch (e) {
182
+ console.log(` ⚠️ skills-sync: unexpected error (${e.message || e})`);
183
+ }
173
184
  }
174
185
 
175
186
  // ─── Initialize stale-report.json (preserve existing sub-tool results) ──
package/package.json CHANGED
@@ -1,92 +1,92 @@
1
- {
2
- "name": "claudeos-core",
3
- "version": "2.4.4",
4
- "description": "Auto-generate Claude Code documentation from your actual source code — Standards, Rules, Skills, and Guides tailored to your project",
5
- "main": "bin/cli.js",
6
- "bin": {
7
- "claudeos-core": "bin/cli.js"
8
- },
9
- "files": [
10
- "bin/",
11
- "lib/",
12
- "claude-md-validator/",
13
- "content-validator/",
14
- "health-checker/",
15
- "manifest-generator/",
16
- "pass-json-validator/",
17
- "pass-prompts/",
18
- "plan-installer/",
19
- "plan-validator/",
20
- "sync-checker/",
21
- "bootstrap.sh",
22
- "README.md",
23
- "README.ko.md",
24
- "LICENSE",
25
- "CHANGELOG.md",
26
- "CONTRIBUTING.md",
27
- "CODE_OF_CONDUCT.md",
28
- "SECURITY.md",
29
- "README.zh-CN.md",
30
- "README.ja.md",
31
- "README.es.md",
32
- "README.vi.md",
33
- "README.hi.md",
34
- "README.ru.md",
35
- "README.fr.md",
36
- "README.de.md"
37
- ],
38
- "scripts": {
39
- "init": "node bin/cli.js init",
40
- "lint": "node bin/cli.js lint",
41
- "health": "node bin/cli.js health",
42
- "validate": "node bin/cli.js validate",
43
- "refresh": "node bin/cli.js refresh",
44
- "restore": "node bin/cli.js restore",
45
- "pretest": "node -e \"try{require('glob')}catch(e){process.exit(1)}\" || npm install",
46
- "test": "node scripts/run-tests.js",
47
- "test:health": "node health-checker/index.js"
48
- },
49
- "keywords": [
50
- "claude-code",
51
- "automation",
52
- "code-analysis",
53
- "CLAUDE.md",
54
- "standards",
55
- "rules",
56
- "skills",
57
- "scaffolding",
58
- "i18n",
59
- "multi-language",
60
- "spring-boot",
61
- "kotlin",
62
- "exposed",
63
- "jooq",
64
- "cqrs",
65
- "bff",
66
- "multi-module",
67
- "monorepo",
68
- "nextjs",
69
- "express",
70
- "fastify",
71
- "angular",
72
- "django",
73
- "fastapi"
74
- ],
75
- "author": "claudeos-core <claudeoscore@gmail.com> (https://github.com/claudeos-core)",
76
- "license": "ISC",
77
- "repository": {
78
- "type": "git",
79
- "url": "git+https://github.com/claudeos-core/claudeos-core.git"
80
- },
81
- "homepage": "https://github.com/claudeos-core/claudeos-core#readme",
82
- "bugs": {
83
- "url": "https://github.com/claudeos-core/claudeos-core/issues"
84
- },
85
- "engines": {
86
- "node": ">=18.0.0"
87
- },
88
- "dependencies": {
89
- "glob": "^13.0.6",
90
- "gray-matter": "^4.0.3"
91
- }
92
- }
1
+ {
2
+ "name": "claudeos-core",
3
+ "version": "2.5.1",
4
+ "description": "Auto-generate Claude Code documentation from your actual source code — Standards, Rules, Skills, and Guides tailored to your project",
5
+ "main": "bin/cli.js",
6
+ "bin": {
7
+ "claudeos-core": "bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "lib/",
12
+ "claude-md-validator/",
13
+ "content-validator/",
14
+ "health-checker/",
15
+ "manifest-generator/",
16
+ "pass-json-validator/",
17
+ "pass-prompts/",
18
+ "plan-installer/",
19
+ "plan-validator/",
20
+ "sync-checker/",
21
+ "bootstrap.sh",
22
+ "README.md",
23
+ "README.ko.md",
24
+ "LICENSE",
25
+ "CHANGELOG.md",
26
+ "CONTRIBUTING.md",
27
+ "CODE_OF_CONDUCT.md",
28
+ "SECURITY.md",
29
+ "README.zh-CN.md",
30
+ "README.ja.md",
31
+ "README.es.md",
32
+ "README.vi.md",
33
+ "README.hi.md",
34
+ "README.ru.md",
35
+ "README.fr.md",
36
+ "README.de.md"
37
+ ],
38
+ "scripts": {
39
+ "init": "node bin/cli.js init",
40
+ "lint": "node bin/cli.js lint",
41
+ "health": "node bin/cli.js health",
42
+ "validate": "node bin/cli.js validate",
43
+ "refresh": "node bin/cli.js refresh",
44
+ "restore": "node bin/cli.js restore",
45
+ "pretest": "node -e \"try{require('glob')}catch(e){process.exit(1)}\" || npm install",
46
+ "test": "node scripts/run-tests.js",
47
+ "test:health": "node health-checker/index.js"
48
+ },
49
+ "keywords": [
50
+ "claude-code",
51
+ "automation",
52
+ "code-analysis",
53
+ "CLAUDE.md",
54
+ "standards",
55
+ "rules",
56
+ "skills",
57
+ "scaffolding",
58
+ "i18n",
59
+ "multi-language",
60
+ "spring-boot",
61
+ "kotlin",
62
+ "exposed",
63
+ "jooq",
64
+ "cqrs",
65
+ "bff",
66
+ "multi-module",
67
+ "monorepo",
68
+ "nextjs",
69
+ "express",
70
+ "fastify",
71
+ "angular",
72
+ "django",
73
+ "fastapi"
74
+ ],
75
+ "author": "claudeos-core <claudeoscore@gmail.com> (https://github.com/claudeos-core)",
76
+ "license": "ISC",
77
+ "repository": {
78
+ "type": "git",
79
+ "url": "git+https://github.com/claudeos-core/claudeos-core.git"
80
+ },
81
+ "homepage": "https://github.com/claudeos-core/claudeos-core#readme",
82
+ "bugs": {
83
+ "url": "https://github.com/claudeos-core/claudeos-core/issues"
84
+ },
85
+ "engines": {
86
+ "node": ">=18.0.0"
87
+ },
88
+ "dependencies": {
89
+ "glob": "^13.0.6",
90
+ "gray-matter": "^4.0.3"
91
+ }
92
+ }
@@ -228,7 +228,7 @@ async function main() {
228
228
  const framework = paData.stack?.framework;
229
229
  const language = paData.stack?.language;
230
230
  const architecture = paData.stack?.architecture;
231
- isBackend = !frontend || ["express", "nestjs", "fastify", "django", "fastapi", "flask", "spring-boot"].includes(framework);
231
+ isBackend = !frontend || ["express", "nestjs", "fastify", "django", "fastapi", "flask", "spring-boot", "spring-framework"].includes(framework);
232
232
  isKotlin = language === "kotlin";
233
233
  isKotlinCqrs = isKotlin && (architecture === "cqrs" || paData.stack?.multiModule);
234
234
  } catch (_e) { /* If project-analysis parsing fails, conservatively assume backend */ }
@@ -85,6 +85,7 @@ Generation targets:
85
85
  - `60.memory/*` rules: forward reference — Pass 4 will generate 4 files (01.decision-log, 02.failure-patterns, 03.compaction, 04.auto-rule-update), each with file-specific `paths`. Pass 3 must STILL list ```.claude/rules/60.memory/*``` as a row in CLAUDE.md Section 6 Rules table so developers/Claude see the category exists.
86
86
  - `70.domains/*` rules (multi-domain projects only): per-domain rules at `.claude/rules/70.domains/{type}/{domain}-rules.md` (where `{type}` is `backend` or `frontend`, ALWAYS present even in single-stack projects for uniform layout + zero-migration future-proofing), each with a `paths:` glob scoped to that domain's source directories so the rule auto-loads only when editing files within the relevant domain. Folder name is PLURAL (`domains/`) — collection of N per-domain files — and each file inside uses the SINGULAR domain name (`{domain}-rules.md`). DO NOT use `60.domains/` (collides with `60.memory/`) and DO NOT skip the `{type}/` sub-folder. See pass3-footer.md "Per-domain folder convention" for the full rationale.
87
87
  - MUST generate `.claude/rules/00.core/00.standard-reference.md` as a directory of all standard files
88
+ (paths only, grouped by category). Include the FORWARD REFERENCE `claudeos-core/standard/00.core/04.doc-writing-guide.md` under `## Core` — Pass 4 generates it; omitting it leaves an index gap the moment Pass 4 runs. Do NOT add a "DO NOT Read" section here — CLAUDE.md Section 7 is the single source of truth.
88
89
 
89
90
  4. .claude/rules/50.sync/ (2 sync rules)
90
91
  - 01.doc-sync.md — Bidirectional standard ↔ rules sync reminder (both directions in ONE rule).
@@ -94,7 +95,7 @@ Generation targets:
94
95
  - 02.skills-sync.md — Remind AI to update MANIFEST.md when skills are modified
95
96
 
96
97
  5. claudeos-core/skills/ (active domains only)
97
- - 20.frontend-page/01.scaffold-feature-module.md (orchestrator — Angular feature module scaffolding)
98
+ - 20.frontend-page/01.scaffold-page-feature.md (orchestrator — Angular feature module scaffolding; stem MUST match the `scaffold-page-feature/` sub-folder so content-validator pairs them)
98
99
  - 20.frontend-page/scaffold-page-feature/01~08 (sub-skills: module, component, service, routing, template, test, style, index)
99
100
  - 00.shared/MANIFEST.md (skill registry)
100
101
 
@@ -277,7 +277,7 @@ Unlike rules that auto-load via `paths` glob, this layer is referenced **on-dema
277
277
  2. **Skim recent decisions**: Skim recent entries in `decision-log.md` to avoid overwriting architectural decisions that have already been agreed upon.
278
278
  3. **Record new decisions**: When making a significant design decision (choosing between competing patterns, adopting or rejecting a library, fixing a convention, etc.), append to `decision-log.md`.
279
279
  4. **Record repeated errors**: If the same error occurs ≥2 times and the root cause is non-obvious, register it in `failure-patterns.md` with a new pattern-id.
280
- 5. **Periodic compaction**: When a memory file approaches 400 lines or has not been tidied up for over a month, run `npx claudeos-core memory compact`.
280
+ 5. **Periodic compaction**: When `failure-patterns.md` approaches 400 lines or has not been tidied up for over a month, run `npx claudeos-core memory compact` (`decision-log.md` is append-only and is never compacted).
281
281
  6. **Review rule-update proposals**: Review proposals in `auto-rule-update.md` with confidence ≥ 0.70. When accepting, edit the corresponding rule file and log the decision in `decision-log.md`.
282
282
 
283
283
  **Session Resume (after auto-compact or restart)**: Claude Code's auto-compact feature may truncate session context mid-work, and a restarted session starts with a fresh context window. When resuming work:
@@ -125,11 +125,15 @@ IMPORT, not redefine.
125
125
  - ...
126
126
  - ...
127
127
 
128
- ## Allowed Source Paths (v2.3.x+ — MANDATORY)
128
+ ## Allowed Source Paths (v2.5.0+ — WRITTEN BY THE ORCHESTRATOR)
129
129
 
130
- Copy the **entire** `allowedSourcePaths` section from `pass3-context.json`
131
- verbatim into this pass3a-facts.md. Do NOT summarize it, do NOT truncate
132
- it, do NOT reword it. The shape to copy:
130
+ **Do NOT write this section yourself.** After you finish, the Node.js
131
+ orchestrator appends `## Allowed Source Paths` to this file directly from
132
+ `project-analysis.json` byte-exact, every run. Anything you write under
133
+ that heading will be replaced. Skip it and save the tokens.
134
+
135
+ The remainder of this section documents the shape the orchestrator emits,
136
+ so Pass 3b/3c/3d know what to expect:
133
137
 
134
138
  - Header: whether the list is in `full` mode (individual file paths) or
135
139
  `rollup` mode (parent directories, used when the project exceeds the
@@ -175,11 +179,9 @@ fall back to pass2-merged.json verification per file)` instead.
175
179
  3. **Exact values only.** Every class name, method name, package path, and
176
180
  file path must be verbatim from the analysis data. If a value is not
177
181
  captured in the analysis, write `(not in analysis)` — do NOT guess.
178
- 4. **Allowed Source Paths section is COPIED, not extracted.** Do not apply
179
- judgment, ranking, or "relevance filtering" to the allowlist. The
180
- whole point is that Pass 3b/3c/3d get the complete enumeration; any
181
- path you drop here becomes a path they can fabricate later without
182
- the downstream validator catching it until after Pass 3 completes.
182
+ 4. **Do not write the `## Allowed Source Paths` section.** The orchestrator
183
+ injects it verbatim from `project-analysis.json` once this step ends
184
+ (v2.5.0+). It is deterministic by design no LLM copying involved.
183
185
  5. **Do NOT write any other files.** CLAUDE.md, standard/, rules/, etc.
184
186
  come in later Pass 3 steps. Writing them here is a bug.
185
187
  6. **Do NOT read source code.** All information comes from the three JSON
@@ -191,7 +191,7 @@ Baseline template (translate per above, then append the project-specific section
191
191
  ```markdown
192
192
  # Compaction Strategy
193
193
 
194
- _4-stage compaction rules for `decision-log.md` and `failure-patterns.md`._
194
+ _4-stage compaction rules for `failure-patterns.md`. `decision-log.md` is append-only and never compacted._
195
195
  _Run via `npx claudeos-core memory compact`._
196
196
 
197
197
  ## Preservation Priority
@@ -266,7 +266,7 @@ Body (write in **{{LANG_NAME}}**) must cover:
266
266
  Frontmatter: `name: AI Work Rules`, `paths: ["**/*"]`
267
267
  Body (write in **{{LANG_NAME}}**) must cover:
268
268
  - Accuracy over token saving; verify before claiming
269
- - 13 hallucination prevention patterns (see memory-scaffold static fallback for reference list)
269
+ - 17 hallucination prevention patterns (see memory-scaffold static fallback `RULE_FILES_00["52.ai-work-rules.md"]` for the reference table)
270
270
  - No unsolicited suggestions; ask when unsure
271
271
  - Memory vs Rules — no duplication judgment (Memory is on-demand, Rules are auto-loaded)
272
272
  - Planned references — no "missing" judgment
@@ -374,7 +374,7 @@ After all files are written, create:
374
374
  "standardFiles": [
375
375
  "claudeos-core/standard/00.core/XX.doc-writing-guide.md"
376
376
  ],
377
- "claudeMdAppended": true,
377
+ "claudeMdAppended": false,
378
378
  "seededDecisions": <integer count of seed entries written to decision-log.md>
379
379
  }
380
380
  ```
@@ -21,8 +21,16 @@ Required reads (if the file exists):
21
21
  the variable inside `ext { ... }` or `<properties>`. Record the
22
22
  ACTUAL Java version — do NOT infer "Java 17+" from the Spring
23
23
  Boot version.
24
- - Spring Boot version: verify it matches `project-analysis.json`'s
25
- frameworkVersion field; if they disagree, trust the build file.
24
+ - Spring version: verify it matches `project-analysis.json`'s
25
+ frameworkVersion field (Spring Boot, or the Spring Framework line when
26
+ `framework` is `"spring-framework"` — a pre-Boot project); if they
27
+ disagree, trust the build file. For a non-Boot project, also read
28
+ `WEB-INF/web.xml` and the Spring XML configs it references
29
+ (`applicationContext*.xml`, `*-servlet.xml`, `*.properties`) — they
30
+ hold what `application.yml` holds in a Boot project, and step 2 below
31
+ will find no `application.yml`. Do NOT describe Boot features
32
+ (auto-configuration, starters, actuator, `@SpringBootApplication`)
33
+ for a project that does not declare Boot.
26
34
  - Dependencies that indicate specific patterns (MyBatis/iBatis/JPA,
27
35
  multiple DB drivers, Jasypt, JWT library, Logback extras).
28
36