claudeos-core 2.5.0 → 2.5.2
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 +88 -0
- package/README.de.md +4 -2
- package/README.es.md +4 -2
- package/README.fr.md +4 -2
- package/README.hi.md +4 -2
- package/README.ja.md +4 -2
- package/README.ko.md +4 -2
- package/README.md +4 -2
- package/README.ru.md +4 -2
- package/README.vi.md +4 -2
- package/README.zh-CN.md +4 -2
- package/lib/env-parser.js +198 -4
- package/package.json +92 -92
- package/pass-json-validator/index.js +1 -1
- package/pass-prompts/templates/java-spring/pass1.md +10 -2
- package/pass-prompts/templates/java-spring/pass3.md +2 -1
- package/plan-installer/index.js +17 -1
- package/plan-installer/jvm-detect.js +562 -0
- package/plan-installer/scanners/scan-java.js +82 -14
- package/plan-installer/stack-detector.js +471 -34
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,
|
|
@@ -253,6 +301,108 @@ function isSensitiveVarName(name) {
|
|
|
253
301
|
* Scheme, host, port, path and query are preserved so consumers can still
|
|
254
302
|
* identify the DB engine / host. Non-URL values pass through unchanged.
|
|
255
303
|
*/
|
|
304
|
+
// The userinfo rule maskUrlCredentials() applies. Shared so the backstop below
|
|
305
|
+
// can ask "did this already get masked?" instead of guessing from the result.
|
|
306
|
+
const USERINFO_RE = /^([a-z][a-z0-9+.:-]*:\/\/)([^/?#\s]*)@([^@/?#\s]+)/i;
|
|
307
|
+
// Schemes whose values routinely carry an `@` inside a PATH: scoped npm
|
|
308
|
+
// packages, image variants, `/users/@me`. A DSN scheme essentially never does.
|
|
309
|
+
const WEB_SCHEME_RE = /^(?:https?|wss?)$/i;
|
|
310
|
+
// A token that can follow the `@` terminating an authority: a host, an IPv6
|
|
311
|
+
// literal in brackets, optionally `:port`, then end-of-value or a delimiter.
|
|
312
|
+
const HOST_AFTER_AT_RE = /^(?:\[[0-9A-Fa-f:.]+\]|[A-Za-z0-9._~%-]+)(?::\d+)?(?:[/?#]|$)/;
|
|
313
|
+
// A complete `host[:port]` with a NUMERIC port.
|
|
314
|
+
const HOST_PORT_RE = /^(?:[A-Za-z0-9._~-]+)(?::\d+)?$/;
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* v2.5.2 — True when a `scheme://…` value carries userinfo that
|
|
318
|
+
* maskUrlCredentials() could not rewrite, because the password contains a raw
|
|
319
|
+
* `/`, `?`, `#` or space and so pushed the real authority past the point where
|
|
320
|
+
* the userinfo rule is forced to stop.
|
|
321
|
+
*
|
|
322
|
+
* The earlier implementation guessed from the TRUNCATED authority — the text
|
|
323
|
+
* before the first `/?#` — and asked whether the part after its `:` was
|
|
324
|
+
* all-digits. That inverted the test for the most common leak of all: in
|
|
325
|
+
* `postgres://user:12345/6@db/app` the truncated authority is `user:12345`,
|
|
326
|
+
* whose tail IS all digits, so a password merely BEGINNING with digits (which
|
|
327
|
+
* base64-generated passwords routinely do) was waved through. It also could
|
|
328
|
+
* not see a password holding a space, since the userinfo rule's own character
|
|
329
|
+
* class excludes whitespace.
|
|
330
|
+
*
|
|
331
|
+
* This version instead locates the `@` that actually terminates an authority
|
|
332
|
+
* and decides from there. `user:12345` and `a.com:8080` are syntactically
|
|
333
|
+
* indistinguishable, so where ambiguity is irreducible the scheme breaks the
|
|
334
|
+
* tie: for a DSN the value is treated as credentials, for http/https/ws/wss as
|
|
335
|
+
* a path.
|
|
336
|
+
*
|
|
337
|
+
* postgres://u:p/w@host/db true password holds `/`
|
|
338
|
+
* postgres://user:12345/6@db/app true digit-leading password
|
|
339
|
+
* postgres://u:pa ss@host/db true password holds a space
|
|
340
|
+
* redis://:pw?x@host/0 true password holds `?`
|
|
341
|
+
* postgres://u:p@host/db false the userinfo rule handles it
|
|
342
|
+
* https://cdn.example.com/npm/@x/y false no credential shape at all
|
|
343
|
+
* http://a.com:8080/img/@2x.png false web scheme, complete host:port
|
|
344
|
+
* http://[::1]:8080/img/@2x.png false bracketed IPv6 is a host
|
|
345
|
+
* mongodb://h:port/db?x=a@b false the `@` sits in the query
|
|
346
|
+
* https://api:${PORT}/v1/@me false unexpanded template
|
|
347
|
+
*/
|
|
348
|
+
function hasUnmaskedUrlCredentials(value) {
|
|
349
|
+
if (typeof value !== "string") return false;
|
|
350
|
+
const m = value.match(/^([a-z][a-z0-9+.-]*(?::[a-z][a-z0-9+.-]*)*):\/\/(.*)$/i);
|
|
351
|
+
if (!m) return false;
|
|
352
|
+
const rest = m[2];
|
|
353
|
+
if (!rest || !rest.includes("@")) return false;
|
|
354
|
+
// An unexpanded `${VAR}` is a template, not a live secret. parseEnvContent
|
|
355
|
+
// deliberately does not expand these, so redacting one loses a host for
|
|
356
|
+
// nothing.
|
|
357
|
+
if (/\$\{[^}]*\}/.test(rest)) return false;
|
|
358
|
+
// Already masked by the userinfo rule — nothing is hidden.
|
|
359
|
+
if (USERINFO_RE.test(value)) return false;
|
|
360
|
+
|
|
361
|
+
const scheme = m[1].split(":")[0].toLowerCase(); // `jdbc:postgresql` → `jdbc`
|
|
362
|
+
// What a strict parser would read as the authority: everything up to the
|
|
363
|
+
// first `/`, `?` or `#`.
|
|
364
|
+
const head = rest.slice(0, rest.search(/[/?#]/) === -1 ? rest.length : rest.search(/[/?#]/));
|
|
365
|
+
// A bracketed IPv6 literal can only ever be a host, never userinfo.
|
|
366
|
+
if (head.startsWith("[")) return false;
|
|
367
|
+
// No `user:secret` shape at all. The empty-user form (`redis://:pw@host`)
|
|
368
|
+
// starts with `:`, so it is admitted here.
|
|
369
|
+
if (!head.includes(":")) return false;
|
|
370
|
+
// For a web scheme, a head that is ALREADY a complete `host:port` means the
|
|
371
|
+
// authority ended there and the `@` belongs to the path.
|
|
372
|
+
if (WEB_SCHEME_RE.test(scheme) && HOST_PORT_RE.test(head)) return false;
|
|
373
|
+
|
|
374
|
+
// The `@` that would terminate the real authority: the last one followed by
|
|
375
|
+
// something that can be a host.
|
|
376
|
+
// The loop stops at `i > 0`, NOT `i !== -1`. `String.prototype.lastIndexOf`
|
|
377
|
+
// clamps a negative `fromIndex` to 0 rather than returning -1, so an `@`
|
|
378
|
+
// sitting at index 0 that fails the host test re-finds itself forever and
|
|
379
|
+
// hangs `init` at 100% CPU with no error (`postgres://@ :x` reproduces it).
|
|
380
|
+
// Stopping at index 0 loses nothing: an `@` there means empty userinfo.
|
|
381
|
+
for (let i = rest.lastIndexOf("@"); i > 0; i = rest.lastIndexOf("@", i - 1)) {
|
|
382
|
+
// `continue`, NOT `return false`. Abandoning the whole search on the first
|
|
383
|
+
// `@` that turns out to be query content let a DSN carrying an email or a
|
|
384
|
+
// redirect URL in its query string defeat the backstop entirely:
|
|
385
|
+
// `postgres://app:pa/ss@db/app?redirect=user@host` starts at the LAST `@`,
|
|
386
|
+
// decides it is query content, and never examines the earlier `@` that
|
|
387
|
+
// terminates the real authority.
|
|
388
|
+
if (!HOST_AFTER_AT_RE.test(rest.slice(i + 1))) continue;
|
|
389
|
+
// If a `?` precedes this `@` AND a `/` precedes that `?`, then a path had
|
|
390
|
+
// already begun before the query started, so the authority was long since
|
|
391
|
+
// over and this `@` is query content (`mongodb://h:port/db?x=a@b`). When
|
|
392
|
+
// the `?` comes before any `/` it is inside the password instead
|
|
393
|
+
// (`redis://:pw?x@host/0`).
|
|
394
|
+
const q = rest.indexOf("?");
|
|
395
|
+
if (q !== -1 && q < i && rest.slice(0, q).includes("/")) continue;
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const REDACTED = "***REDACTED***";
|
|
402
|
+
// Scalar env-derived fields (host, apiTarget) are rendered directly into
|
|
403
|
+
// generated docs, so the sentinel must never become their value.
|
|
404
|
+
const nullIfRedacted = (v) => (v === REDACTED ? null : v);
|
|
405
|
+
|
|
256
406
|
function maskUrlCredentials(value) {
|
|
257
407
|
if (typeof value !== "string") return value;
|
|
258
408
|
// Scheme may itself contain `:` (`jdbc:postgresql://`, `jdbc:mysql://`).
|
|
@@ -270,9 +420,36 @@ function maskUrlCredentials(value) {
|
|
|
270
420
|
// The parameter NAME is kept, the value becomes `***`.
|
|
271
421
|
const PARAM_RE = /([?&;](?:password|passwd|pwd|pass|secret|token|access[_-]?key|secret[_-]?key|api[_-]?key|sas|signature)=)[^&;\s]*/gi;
|
|
272
422
|
if (/^[a-z][a-z0-9+.:-]*:\/\//i.test(value)) {
|
|
273
|
-
|
|
274
|
-
|
|
423
|
+
const userinfoMasked = USERINFO_RE.test(value);
|
|
424
|
+
const masked = value
|
|
425
|
+
.replace(USERINFO_RE, "$1***:***@$3")
|
|
275
426
|
.replace(PARAM_RE, "$1***");
|
|
427
|
+
// v2.5.2 — last-resort backstop for a password containing a raw `/`, `?`
|
|
428
|
+
// or `#` (`postgres://u:p/w@host/db`). The rule above deliberately does
|
|
429
|
+
// not rewrite an `@` that appears after the first `/` — such an `@`
|
|
430
|
+
// normally belongs to the path (`https://cdn.example.com/npm/@scope/pkg`)
|
|
431
|
+
// and masking it would replace the real host. The value therefore passed
|
|
432
|
+
// through verbatim, and because its key is `DATABASE_URL` the key-name
|
|
433
|
+
// rule did not backstop it either: it was the one combination that could
|
|
434
|
+
// still write a plaintext password into project-analysis.json, which
|
|
435
|
+
// Pass 3/4 prompts instruct the model to read.
|
|
436
|
+
//
|
|
437
|
+
// Base64-generated passwords contain `/` routinely, so this is not an
|
|
438
|
+
// exotic shape. When the value carries the tell (see
|
|
439
|
+
// hasUnmaskedUrlCredentials) the WHOLE value is dropped rather than
|
|
440
|
+
// rewritten — the host cannot be located reliably once the authority is
|
|
441
|
+
// ambiguous, and a lost host is a far cheaper failure than a leaked
|
|
442
|
+
// credential. Callers are told which key it was via
|
|
443
|
+
// envInfo.credentialWarnings.
|
|
444
|
+
//
|
|
445
|
+
// Gated on the USERINFO rule not having fired — NOT on `masked === value`.
|
|
446
|
+
// PARAM_RE may rewrite a query parameter on the same value
|
|
447
|
+
// (`postgres://u:p/w@host/db?sslmode=require&password=x`); a
|
|
448
|
+
// "did anything change" gate would then skip the backstop and let `p/w`
|
|
449
|
+
// through verbatim. Whether the userinfo rule matched is the only signal
|
|
450
|
+
// that says the authority itself was masked.
|
|
451
|
+
if (!userinfoMasked && hasUnmaskedUrlCredentials(value)) return "***REDACTED***";
|
|
452
|
+
return masked;
|
|
276
453
|
}
|
|
277
454
|
// Scheme-less credentials are recognized ONLY in the Go/MySQL DSN shape
|
|
278
455
|
// (`user:pw@tcp(host:3306)/db`, `user:pw@unix(/path)/db`); the password may
|
|
@@ -329,9 +506,25 @@ function readStackEnvInfo(root) {
|
|
|
329
506
|
return {
|
|
330
507
|
source: file,
|
|
331
508
|
vars: redactSensitiveVars(vars),
|
|
509
|
+
// v2.5.2 — the keys whose value `maskUrlCredentials` dropped whole rather
|
|
510
|
+
// than partially masking. The credential is not leaked, but the user loses
|
|
511
|
+
// the host for that key, so `init` names the keys in its Phase 1 summary
|
|
512
|
+
// instead of silently swallowing them. Key NAMES only, never any part of
|
|
513
|
+
// the value.
|
|
514
|
+
//
|
|
515
|
+
// Derived from what `maskUrlCredentials` ACTUALLY returned, so the list can
|
|
516
|
+
// never name a key whose value was kept (masked) rather than dropped. The
|
|
517
|
+
// `!== REDACTED` guard covers an env value that is literally the sentinel.
|
|
518
|
+
credentialWarnings: Object.keys(vars).filter(
|
|
519
|
+
k => !isSensitiveVarName(k) && vars[k] !== REDACTED && maskUrlCredentials(vars[k]) === REDACTED
|
|
520
|
+
),
|
|
332
521
|
port: extractPort(vars),
|
|
333
|
-
|
|
334
|
-
|
|
522
|
+
// A value the backstop dropped whole must not travel on as the literal
|
|
523
|
+
// sentinel: `host` / `apiTarget` are rendered straight into CLAUDE.md §3,
|
|
524
|
+
// and the scaffold's only sentinel guard covers `envInfo.vars`. Null makes
|
|
525
|
+
// the row simply absent, which is what the scaffold already handles.
|
|
526
|
+
host: nullIfRedacted(maskUrlCredentials(extractHost(vars))),
|
|
527
|
+
apiTarget: nullIfRedacted(maskUrlCredentials(extractApiTarget(vars))),
|
|
335
528
|
};
|
|
336
529
|
}
|
|
337
530
|
|
|
@@ -346,6 +539,7 @@ module.exports = {
|
|
|
346
539
|
isSensitiveVarName,
|
|
347
540
|
redactSensitiveVars,
|
|
348
541
|
maskUrlCredentials,
|
|
542
|
+
hasUnmaskedUrlCredentials,
|
|
349
543
|
// Exported for test visibility:
|
|
350
544
|
ENV_FILE_ORDER,
|
|
351
545
|
PORT_VAR_KEYS,
|
package/package.json
CHANGED
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "claudeos-core",
|
|
3
|
-
"version": "2.5.
|
|
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.2",
|
|
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 */ }
|
|
@@ -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
|
|
25
|
-
frameworkVersion field
|
|
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
|
|
|
@@ -68,7 +68,8 @@ Generation targets:
|
|
|
68
68
|
Content within each section adapts to this project based on pass2-merged.json.
|
|
69
69
|
The scaffold's validation checklist MUST pass.
|
|
70
70
|
|
|
71
|
-
Stack-specific hints for this project (Java Spring Boot
|
|
71
|
+
Stack-specific hints for this project (Java Spring — Boot, or Spring Framework
|
|
72
|
+
without Boot when `project-analysis.json` says `framework: "spring-framework"`):
|
|
72
73
|
- Project type for Section 1 PROJECT_CONTEXT: "Backend Application" or "REST API Server"
|
|
73
74
|
- Architecture diagram (Section 4): layered architecture (Controller → Service → Mapper/Repository)
|
|
74
75
|
- Section 2 should include: JDK version, Gradle/Maven, DB, session/cache, server port
|
package/plan-installer/index.js
CHANGED
|
@@ -52,7 +52,23 @@ async function main() {
|
|
|
52
52
|
console.log(` Database: ${stack.database || "none"}`);
|
|
53
53
|
}
|
|
54
54
|
console.log(` ORM: ${stack.orm || "none"}`);
|
|
55
|
-
console.log(` PackageMgr: ${stack.packageManager || "none"}
|
|
55
|
+
console.log(` PackageMgr: ${stack.packageManager || "none"}`);
|
|
56
|
+
// v2.5.2 — a URL value whose password holds a raw `/`, `?` or `#` cannot
|
|
57
|
+
// have its userinfo rewritten without risking the host, so the whole value
|
|
58
|
+
// is dropped. Say so: silently losing a DATABASE_URL would otherwise look
|
|
59
|
+
// like a detection bug. Key names only, never any part of the value.
|
|
60
|
+
// Both env sources are reported. A sub-directory SPA keeps its own
|
|
61
|
+
// `frontend/.env`, read into `stack.frontendEnvInfo`, and reading only
|
|
62
|
+
// `stack.envInfo` swallowed exactly the drop this warning exists to announce.
|
|
63
|
+
for (const info of [stack.envInfo, stack.frontendEnvInfo]) {
|
|
64
|
+
const credWarn = (info && info.credentialWarnings) || [];
|
|
65
|
+
if (!credWarn.length) continue;
|
|
66
|
+
console.warn(`\n ⚠️ Credential-shaped value dropped from ${credWarn.join(", ")} (${info.source}).`);
|
|
67
|
+
console.warn(" The password contains a raw '/', '?', '#' or space, which makes the URL's host");
|
|
68
|
+
console.warn(" ambiguous, so the value was redacted whole rather than partially masked.");
|
|
69
|
+
console.warn(" Percent-encode the password (e.g. '/' as %2F) to keep the host visible.");
|
|
70
|
+
}
|
|
71
|
+
console.log("");
|
|
56
72
|
|
|
57
73
|
// Phase 2: Structure scan
|
|
58
74
|
console.log(" [Phase 2] Scanning structure...");
|