scene-capability-engine 3.3.23 → 3.3.24

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 CHANGED
@@ -7,6 +7,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+ - Errorbook registry health command for centralized registry governance:
12
+ - `sce errorbook health-registry`
13
+ - validates registry config readability, source/index reachability, and index bucket-to-shard resolution
14
+ - New script gate:
15
+ - `node scripts/errorbook-registry-health-gate.js`
16
+ - supports strict mode via `SCE_REGISTRY_HEALTH_STRICT=1`
17
+
18
+ ### Changed
19
+ - `prepublishOnly` now runs `gate:errorbook-registry-health` in advisory mode before `errorbook-release` gate.
20
+ - Autonomous execution defaults are now hard-set to autonomous progression:
21
+ - `lib/auto/config-schema.js` default mode changed to `aggressive`
22
+ - default checkpoints now skip phase/final review pauses (`phaseCompletion=false`, `finalReview=false`)
23
+ - default safety confirmations for production/external/destructive are disabled in autonomous mode baseline
24
+ - `sce auto run/create` CLI defaults now use `--mode aggressive`
25
+ - Steering hard rule strengthened: when the same issue fails target validation for 2 consecutive fix rounds, the 3rd round must switch to debug-log-driven diagnosis first (no blind patching without evidence).
26
+ - `errorbook record` now enforces the same policy operationally:
27
+ - from the 3rd repeated record attempt of the same fingerprint, debug evidence becomes mandatory
28
+ - accepted signals include `--verification "debug: ..."`, `debug-evidence` tag, or debug trace/log references
29
+ - Spec workflow now enforces domain-first scene modeling:
30
+ - `spec bootstrap` auto-generates mandatory artifacts:
31
+ - `.sce/specs/<spec>/custom/problem-domain-map.md`
32
+ - `.sce/specs/<spec>/custom/scene-spec.md`
33
+ - `.sce/specs/<spec>/custom/problem-domain-chain.json`
34
+ - `spec gate` hard-fail rule `domain_scene_modeling` now validates both markdown structure and machine-readable chain payload:
35
+ - `problem/ontology/hypotheses/risks/decision_execution_path/correction_loop/verification`
36
+ - Added explicit domain modeling command set (also available via `sce spec domain ...` route):
37
+ - `sce spec-domain init --spec <id>`
38
+ - `sce spec-domain validate --spec <id> [--fail-on-error]`
39
+ - `sce spec-domain refresh --spec <id>`
40
+ - Studio plan/generate now consume domain modeling context for correction guidance:
41
+ - `sce studio plan` supports `--spec <id>` to ingest `.sce/specs/<spec>/custom/problem-domain-chain.json` deterministically
42
+ - when `--spec` is omitted, `plan` auto-selects the latest scene-matching chain (`scene_id`)
43
+ - `sce studio generate` writes chain-aware metadata and `generate` stage report at `.sce/reports/studio/generate-<job-id>.json`
44
+ - `sce studio verify` / `sce studio release` now include domain-chain metadata in reports and pass `spec_id` into auto errorbook failure capture
45
+
46
+ ## [3.3.23] - 2026-02-27
47
+
48
+ ### Added
49
+ - Adoption/default template coverage now includes central registry and orchestrator configs across init/adopt/upgrade flows:
50
+ - `.sce/config/errorbook-registry.json`
51
+ - `.sce/config/orchestrator.json`
52
+ - Adoption config classification and backup critical-file handling now include:
53
+ - `config/errorbook-registry.json`
54
+ - `config/orchestrator.json`
55
+
56
+ ### Changed
57
+ - Errorbook registry template defaults are now enabled out of the box:
58
+ - `enabled: true`
59
+ - `sources[central].enabled: true`
60
+ - Central registry defaults point to the official shared registry:
61
+ - `https://raw.githubusercontent.com/heguangyong/sce-errorbook-registry/main/registry/errorbook-registry.json`
62
+ - `https://raw.githubusercontent.com/heguangyong/sce-errorbook-registry/main/registry/errorbook-registry.index.json`
63
+
10
64
  ## [3.3.22] - 2026-02-27
11
65
 
12
66
  ### Added
@@ -19,6 +19,7 @@ const { registerSteeringCommands } = require('../lib/commands/steering');
19
19
  const { registerSpecBootstrapCommand } = require('../lib/commands/spec-bootstrap');
20
20
  const { registerSpecPipelineCommand } = require('../lib/commands/spec-pipeline');
21
21
  const { registerSpecGateCommand } = require('../lib/commands/spec-gate');
22
+ const { registerSpecDomainCommand } = require('../lib/commands/spec-domain');
22
23
  const { registerValueCommands } = require('../lib/commands/value');
23
24
  const VersionChecker = require('../lib/version/version-checker');
24
25
  const {
@@ -55,6 +56,7 @@ const program = new Command();
55
56
  * - `sce spec bootstrap ...` -> `sce spec-bootstrap ...`
56
57
  * - `sce spec pipeline ...` -> `sce spec-pipeline ...`
57
58
  * - `sce spec gate ...` -> `sce spec-gate ...`
59
+ * - `sce spec domain ...` -> `sce spec-domain ...`
58
60
  * - `sce spec create <name> ...` -> `sce create-spec <name> ...`
59
61
  * - `sce spec <name> ...` -> `sce create-spec <name> ...` (legacy)
60
62
  *
@@ -89,6 +91,11 @@ function normalizeSpecCommandArgs(argv) {
89
91
  return normalized;
90
92
  }
91
93
 
94
+ if (commandToken === 'domain') {
95
+ normalized.splice(commandIndex, 2, 'spec-domain');
96
+ return normalized;
97
+ }
98
+
92
99
  if (commandToken === 'create') {
93
100
  normalized.splice(commandIndex, 2, 'create-spec');
94
101
  return normalized;
@@ -332,6 +339,9 @@ registerSpecPipelineCommand(program);
332
339
  // Spec gate command
333
340
  registerSpecGateCommand(program);
334
341
 
342
+ // Spec domain modeling command
343
+ registerSpecDomainCommand(program);
344
+
335
345
  // 系统诊断命令
336
346
  program
337
347
  .command('doctor')
@@ -562,8 +562,16 @@ your-project/
562
562
  │ │ ├── ENVIRONMENT.md
563
563
  │ │ ├── CURRENT_CONTEXT.md
564
564
  │ │ └── RULES_GUIDE.md
565
+ │ ├── config/ # Runtime/adoption baseline configs
566
+ │ │ ├── studio-security.json
567
+ │ │ ├── orchestrator.json
568
+ │ │ └── errorbook-registry.json
565
569
  │ ├── tools/ # Ultrawork tools
566
570
  │ │ └── ultrawork_enhancer.py
571
+ │ ├── hooks/ # Default hooks
572
+ │ │ ├── check-spec-on-create.sce.hook
573
+ │ │ ├── run-tests-on-save.sce.hook
574
+ │ │ └── sync-tasks-on-edit.sce.hook
567
575
  │ ├── backups/ # Automatic backups
568
576
  │ │ └── adopt-{timestamp}/
569
577
  │ └── README.md
@@ -433,7 +433,7 @@ sce auto stop
433
433
 
434
434
  ## Execution Modes
435
435
 
436
- ### Conservative Mode (Default)
436
+ ### Conservative Mode
437
437
 
438
438
  **Best for**: Production features, critical systems, first-time users
439
439
 
@@ -479,7 +479,7 @@ sce auto stop
479
479
  }
480
480
  ```
481
481
 
482
- ### Aggressive Mode
482
+ ### Aggressive Mode (Default)
483
483
 
484
484
  **Best for**: Rapid prototyping, experimental features, experienced users
485
485
 
@@ -511,13 +511,13 @@ Location: `.sce/auto/config.json`
511
511
  ```json
512
512
  {
513
513
  "version": "1.0.0",
514
- "mode": "balanced",
514
+ "mode": "aggressive",
515
515
  "checkpoints": {
516
516
  "requirementsReview": false,
517
517
  "designReview": false,
518
518
  "tasksReview": false,
519
- "phaseCompletion": true,
520
- "finalReview": true,
519
+ "phaseCompletion": false,
520
+ "finalReview": false,
521
521
  "errorThreshold": 3
522
522
  },
523
523
  "errorRecovery": {
@@ -527,9 +527,9 @@ Location: `.sce/auto/config.json`
527
527
  "learningEnabled": true
528
528
  },
529
529
  "safety": {
530
- "requireProductionConfirmation": true,
531
- "requireExternalResourceConfirmation": true,
532
- "requireDestructiveOperationConfirmation": true,
530
+ "requireProductionConfirmation": false,
531
+ "requireExternalResourceConfirmation": false,
532
+ "requireDestructiveOperationConfirmation": false,
533
533
  "allowedOperations": [],
534
534
  "blockedOperations": []
535
535
  },
@@ -2,8 +2,8 @@
2
2
 
3
3
  > Quick reference for all `sce` commands
4
4
 
5
- **Version**: 2.0.0
6
- **Last Updated**: 2026-02-26
5
+ **Version**: 3.3.23
6
+ **Last Updated**: 2026-02-27
7
7
 
8
8
  ---
9
9
 
@@ -57,6 +57,10 @@ sce create-spec 01-00-feature-name
57
57
 
58
58
  # Bootstrap full Spec draft (requirements/design/tasks)
59
59
  sce spec bootstrap --name 01-00-feature-name --scene scene.customer-order-inventory --non-interactive
60
+ # Bootstrap now also generates mandatory scene artifacts:
61
+ # - .sce/specs/<spec>/custom/problem-domain-map.md
62
+ # - .sce/specs/<spec>/custom/scene-spec.md
63
+ # - .sce/specs/<spec>/custom/problem-domain-chain.json (machine-readable chain model)
60
64
 
61
65
  # Run pipeline for one Spec
62
66
  sce spec pipeline run --spec 01-00-feature-name --scene scene.customer-order-inventory
@@ -64,6 +68,11 @@ sce spec pipeline run --spec 01-00-feature-name --scene scene.customer-order-inv
64
68
  # Run gate for one Spec
65
69
  sce spec gate run --spec 01-00-feature-name --scene scene.customer-order-inventory --json
66
70
 
71
+ # Maintain domain modeling artifacts explicitly
72
+ sce spec domain init --spec 01-00-feature-name --scene scene.customer-order-inventory --json
73
+ sce spec domain validate --spec 01-00-feature-name --fail-on-error --json
74
+ sce spec domain refresh --spec 01-00-feature-name --scene scene.customer-order-inventory --json
75
+
67
76
  # Multi-Spec mode defaults to orchestrate routing
68
77
  sce spec bootstrap --specs "spec-a,spec-b" --max-parallel 3
69
78
  sce spec pipeline run --specs "spec-a,spec-b" --max-parallel 3
@@ -77,6 +86,11 @@ Spec session governance:
77
86
  - `spec bootstrap|pipeline run|gate run` must bind to an active scene primary session (`--scene <scene-id>` or implicit binding from latest/unique active scene).
78
87
  - When multiple active scenes exist, you must pass `--scene` explicitly.
79
88
  - Multi-Spec orchestrate fallback (`--specs ...`) follows the same scene binding and writes per-spec child-session archive records.
89
+ - `spec bootstrap` always generates problem-domain and scene-spec artifacts to force domain-first exploration.
90
+ - `spec gate` now hard-fails when either of the following is missing or structurally incomplete:
91
+ - `.sce/specs/<spec>/custom/problem-domain-map.md`
92
+ - `.sce/specs/<spec>/custom/scene-spec.md`
93
+ - `.sce/specs/<spec>/custom/problem-domain-chain.json`
80
94
 
81
95
  ### Value Metrics
82
96
 
@@ -362,6 +376,9 @@ sce errorbook export --status promoted --min-quality 75 --out .sce/errorbook/exp
362
376
  # Sync central registry (GitHub raw URL or local file) to local cache
363
377
  sce errorbook sync-registry --source https://raw.githubusercontent.com/heguangyong/sce-errorbook-registry/main/registry/errorbook-registry.json --json
364
378
 
379
+ # Validate registry config/source/index health
380
+ sce errorbook health-registry --json
381
+
365
382
  # Promote only after strict gate checks pass
366
383
  sce errorbook promote <entry-id> --json
367
384
 
@@ -389,11 +406,17 @@ sce errorbook release-gate --min-risk high --fail-on-block --json
389
406
 
390
407
  # Git managed hard gate (default in prepublish and studio release preflight)
391
408
  node scripts/git-managed-gate.js --fail-on-violation --json
409
+
410
+ # Registry health gate (advisory by default; strict when env enabled)
411
+ node scripts/errorbook-registry-health-gate.js --json
412
+ SCE_REGISTRY_HEALTH_STRICT=1 node scripts/errorbook-registry-health-gate.js --json
392
413
  ```
393
414
 
394
415
  Curated quality policy (`宁缺毋滥,优胜略汰`) defaults:
395
416
  - `record` requires: `title`, `symptom`, `root_cause`, and at least one `fix_action`.
396
417
  - Fingerprint dedup is automatic; repeated records merge evidence and increment occurrence count.
418
+ - Repeated-failure hard rule: from attempt `#3` of the same fingerprint (two failed rounds already happened), record must include debug evidence.
419
+ Recommended forms: `--verification "debug: ..."` or tag `debug-evidence` or debug trace/log file references.
397
420
  - `promote` enforces strict gate:
398
421
  - `root_cause` present
399
422
  - `fix_actions` non-empty
@@ -414,6 +437,10 @@ Curated quality policy (`宁缺毋滥,优胜略汰`) defaults:
414
437
  - `sync-registry` pulls external registry JSON into local cache (`.sce/errorbook/registry-cache.json`) for unified `find` retrieval.
415
438
  - `find --include-registry --registry-mode remote` supports direct remote query for large registries (no full local sync required).
416
439
  - Recommended for large registries: maintain a remote index file (`registry/errorbook-registry.index.json`) and shard files, then provide `index_url` in registry config.
440
+ - Since `v3.3.23`, `sce init` / `sce adopt` default baseline includes enabled central registry config in `.sce/config/errorbook-registry.json`.
441
+ - `health-registry` validates config readability, source/index accessibility, and index-to-shard resolution before release.
442
+ - `gate:errorbook-registry-health` runs in advisory mode by default during `prepublishOnly`.
443
+ Set `SCE_REGISTRY_HEALTH_STRICT=1` to fail release when registry health reports errors.
417
444
  - `git-managed-gate` blocks release when:
418
445
  - worktree has uncommitted changes
419
446
  - branch has no upstream
@@ -428,6 +455,8 @@ Curated quality policy (`宁缺毋滥,优胜略汰`) defaults:
428
455
  ```bash
429
456
  # Build a plan from chat/session context (scene is mandatory and becomes the primary session anchor)
430
457
  sce studio plan --scene scene.customer-order-inventory --from-chat session-20260226 --goal "customer+order+inventory demo" --json
458
+ # Recommended: bind spec explicitly so Studio can ingest problem-domain-chain deterministically
459
+ sce studio plan --scene scene.customer-order-inventory --spec 01-00-customer-order-inventory --from-chat session-20260226 --goal "customer+order+inventory demo" --json
431
460
 
432
461
  # Generate patch bundle metadata (scene is inherited from plan)
433
462
  sce studio generate --target 331 --json
@@ -460,11 +489,15 @@ SCE_STUDIO_REQUIRE_AUTH=1 SCE_STUDIO_AUTH_PASSWORD=top-secret sce studio apply -
460
489
 
461
490
  Stage guardrails are enforced by default:
462
491
  - `plan` requires `--scene`; SCE binds one active primary session per scene
492
+ - `plan --spec <id>` (recommended) ingests `.sce/specs/<spec>/custom/problem-domain-chain.json` into studio job context
493
+ - when `--spec` is omitted, `plan` auto-resolves the latest matching spec chain by `scene_id` when available
463
494
  - successful `release` auto-archives current scene session and auto-opens the next scene cycle session
464
495
  - `generate` requires `plan`
496
+ - `generate` consumes the plan-stage domain-chain context and writes chain-aware metadata/report (`.sce/reports/studio/generate-<job-id>.json`)
465
497
  - `apply` requires `generate`
466
498
  - `verify` requires `apply`
467
499
  - `release` requires `verify`
500
+ - `verify` / `release` reports and failure auto-records inherit `spec_id + domain-chain` context for better root-cause traceability
468
501
 
469
502
  Studio gate execution defaults:
470
503
  - `verify --profile standard` runs executable gates (unit test script when available, interactive governance report when present, scene package publish-batch dry-run when handoff manifest exists)
@@ -544,6 +577,7 @@ Multi-agent merge governance default:
544
577
  ```bash
545
578
  # One-command close-loop execution:
546
579
  # goal -> auto master/sub decomposition -> collab metadata -> orchestration -> terminal result
580
+ # default behavior is enforced autonomous progression (no per-step confirmation pauses)
547
581
  sce auto close-loop "build autonomous close-loop and master/sub orchestration"
548
582
  # default sub-spec count is auto-selected by goal complexity (typically 3-5)
549
583
 
@@ -87,6 +87,7 @@ Notes:
87
87
  - `url` must be a raw JSON URL (`raw.githubusercontent.com`) or use a local file path.
88
88
  - `search_mode` supports `cache|remote|hybrid` (recommended: `remote` for very large registries).
89
89
  - Local cache file is used by cache/hybrid mode.
90
+ - Since `v3.3.23`, `sce init` / `sce adopt` template baselines include this config by default (central source enabled).
90
91
 
91
92
  ## 4) Daily Workflow
92
93
 
@@ -106,6 +107,11 @@ sce errorbook sync-registry --source https://raw.githubusercontent.com/heguangyo
106
107
  ```bash
107
108
  sce errorbook find --query "approve order timeout" --include-registry --json
108
109
  sce errorbook find --query "approve order timeout" --include-registry --registry-mode remote --json
110
+
111
+ # Validate central registry health (config/source/index/shard)
112
+ sce errorbook health-registry --json
113
+ # Optional strict gate in CI/release
114
+ SCE_REGISTRY_HEALTH_STRICT=1 node scripts/errorbook-registry-health-gate.js --json
109
115
  ```
110
116
 
111
117
  ## 5) Governance Rules
@@ -114,3 +120,9 @@ sce errorbook find --query "approve order timeout" --include-registry --registry
114
120
  - Do not publish sensitive tenant/customer data.
115
121
  - Temporary mitigation entries must remain bounded and governed (exit criteria, cleanup task, deadline).
116
122
  - Keep central registry append-only by PR review; deprecate low-value entries through normal curation.
123
+ - Recommended central repo gates:
124
+ - `node scripts/validate-registry.js`
125
+ - `node scripts/check-index-coverage.js --min-coverage 85`
126
+ - Recommended project-side release gates:
127
+ - `npm run gate:errorbook-registry-health` (advisory default)
128
+ - `SCE_REGISTRY_HEALTH_STRICT=1 npm run gate:errorbook-registry-health` (strict)
@@ -5,14 +5,14 @@
5
5
 
6
6
  const DEFAULT_CONFIG = {
7
7
  version: '1.0.0',
8
- mode: 'balanced',
8
+ mode: 'aggressive',
9
9
 
10
10
  checkpoints: {
11
11
  requirementsReview: false,
12
12
  designReview: false,
13
13
  tasksReview: false,
14
- phaseCompletion: true,
15
- finalReview: true,
14
+ phaseCompletion: false,
15
+ finalReview: false,
16
16
  errorThreshold: 5
17
17
  },
18
18
 
@@ -24,9 +24,9 @@ const DEFAULT_CONFIG = {
24
24
  },
25
25
 
26
26
  safety: {
27
- requireProductionConfirmation: true,
28
- requireExternalResourceConfirmation: true,
29
- requireDestructiveOperationConfirmation: true,
27
+ requireProductionConfirmation: false,
28
+ requireExternalResourceConfirmation: false,
29
+ requireDestructiveOperationConfirmation: false,
30
30
  allowedOperations: [],
31
31
  blockedOperations: []
32
32
  },
@@ -74,7 +74,7 @@ const MODE_PRESETS = {
74
74
  designReview: false,
75
75
  tasksReview: false,
76
76
  phaseCompletion: false,
77
- finalReview: true,
77
+ finalReview: false,
78
78
  errorThreshold: 10
79
79
  }
80
80
  }
@@ -104,7 +104,7 @@ function registerAutoCommands(program) {
104
104
  auto
105
105
  .command('run <spec-name>')
106
106
  .description('Run Spec autonomously')
107
- .option('-m, --mode <mode>', 'Execution mode (conservative|balanced|aggressive)', 'balanced')
107
+ .option('-m, --mode <mode>', 'Execution mode (conservative|balanced|aggressive)', 'aggressive')
108
108
  .action(async (specName, options) => {
109
109
  try {
110
110
  console.log(chalk.blue(`Starting autonomous execution: ${specName}`));
@@ -133,7 +133,7 @@ function registerAutoCommands(program) {
133
133
  .command('create <feature-description>')
134
134
  .description('Create and run Spec autonomously')
135
135
  .option('-n, --name <name>', 'Spec name')
136
- .option('-m, --mode <mode>', 'Execution mode', 'balanced')
136
+ .option('-m, --mode <mode>', 'Execution mode', 'aggressive')
137
137
  .action(async (description, options) => {
138
138
  try {
139
139
  const specName = options.name || generateSpecName(description);
@@ -47,6 +47,11 @@ const ONTOLOGY_TAG_ALIASES = Object.freeze({
47
47
  });
48
48
  const DEFAULT_PROMOTE_MIN_QUALITY = 75;
49
49
  const ERRORBOOK_RISK_LEVELS = Object.freeze(['low', 'medium', 'high']);
50
+ const DEBUG_EVIDENCE_TAGS = Object.freeze([
51
+ 'debug-evidence',
52
+ 'diagnostic-evidence',
53
+ 'debug-log'
54
+ ]);
50
55
  const HIGH_RISK_SIGNAL_TAGS = Object.freeze([
51
56
  'release-blocker',
52
57
  'security',
@@ -481,6 +486,44 @@ function validateRecordPayload(payload) {
481
486
  }
482
487
  }
483
488
 
489
+ function hasDebugEvidenceSignals(entry = {}) {
490
+ const tags = normalizeStringList(entry.tags).map((item) => item.toLowerCase());
491
+ if (tags.some((tag) => DEBUG_EVIDENCE_TAGS.includes(tag))) {
492
+ return true;
493
+ }
494
+
495
+ const verificationEvidence = normalizeStringList(entry.verification_evidence);
496
+ if (verificationEvidence.some((item) => /^debug:/i.test(item))) {
497
+ return true;
498
+ }
499
+
500
+ const sourceFiles = normalizeStringList(entry?.source?.files);
501
+ if (sourceFiles.some((item) => /(^|[\\/._-])(debug|trace|diagnostic|observability|telemetry|stack)/i.test(item))) {
502
+ return true;
503
+ }
504
+
505
+ const notes = normalizeText(entry.notes).toLowerCase();
506
+ if (notes && /(debug|trace|diagnostic|observability|telemetry|stack|日志|埋点|观测)/i.test(notes)) {
507
+ return true;
508
+ }
509
+
510
+ return false;
511
+ }
512
+
513
+ function enforceDebugEvidenceAfterRepeatedFailures(entry = {}, options = {}) {
514
+ const attemptCount = Number(options.attemptCount || 0);
515
+ if (!Number.isFinite(attemptCount) || attemptCount < 3) {
516
+ return;
517
+ }
518
+ if (hasDebugEvidenceSignals(entry)) {
519
+ return;
520
+ }
521
+ throw new Error(
522
+ 'two failed fix rounds detected (attempt #3+): debug evidence is required. '
523
+ + 'Provide --verification "debug: ...", add tag debug-evidence, or include debug trace/log file references.'
524
+ );
525
+ }
526
+
484
527
  function normalizeRecordPayload(options = {}, fromFilePayload = {}) {
485
528
  const temporaryMitigation = normalizeTemporaryMitigation(options, fromFilePayload);
486
529
  const payload = {
@@ -1287,6 +1330,9 @@ async function runErrorbookRecordCommand(options = {}, dependencies = {}) {
1287
1330
  throw new Error(`errorbook index references missing entry: ${existingSummary.id}`);
1288
1331
  }
1289
1332
  entry = mergeEntry(existingEntry, normalized);
1333
+ enforceDebugEvidenceAfterRepeatedFailures(entry, {
1334
+ attemptCount: Number(entry.occurrences || 0)
1335
+ });
1290
1336
  deduplicated = true;
1291
1337
  } else {
1292
1338
  const temporaryMitigation = normalizeExistingTemporaryMitigation(normalized.temporary_mitigation);
@@ -1499,6 +1545,196 @@ async function runErrorbookSyncRegistryCommand(options = {}, dependencies = {})
1499
1545
  return result;
1500
1546
  }
1501
1547
 
1548
+ async function runErrorbookRegistryHealthCommand(options = {}, dependencies = {}) {
1549
+ const projectPath = dependencies.projectPath || process.cwd();
1550
+ const fileSystem = dependencies.fileSystem || fs;
1551
+ const registryPaths = resolveErrorbookRegistryPaths(projectPath, {
1552
+ configPath: options.config,
1553
+ cachePath: options.cache
1554
+ });
1555
+
1556
+ const warnings = [];
1557
+ const errors = [];
1558
+ const overrideSource = normalizeText(options.source);
1559
+ const overrideIndex = normalizeText(options.index || options.registryIndex);
1560
+ const overrideSourceName = normalizeText(options.sourceName || options.registrySourceName) || 'override';
1561
+
1562
+ const configExists = await fileSystem.pathExists(registryPaths.configFile);
1563
+ if (configExists) {
1564
+ try {
1565
+ const rawConfig = await fileSystem.readJson(registryPaths.configFile);
1566
+ if (!rawConfig || typeof rawConfig !== 'object' || Array.isArray(rawConfig)) {
1567
+ errors.push(`registry config must be a JSON object: ${registryPaths.configFile}`);
1568
+ }
1569
+ } catch (error) {
1570
+ errors.push(`failed to parse registry config (${registryPaths.configFile}): ${error.message}`);
1571
+ }
1572
+ } else if (!overrideSource) {
1573
+ errors.push(`registry config file not found: ${registryPaths.configFile}`);
1574
+ }
1575
+
1576
+ const config = await readErrorbookRegistryConfig(registryPaths, fileSystem);
1577
+ let registryEnabled = normalizeBoolean(config.enabled, true);
1578
+ let sources = Array.isArray(config.sources) ? config.sources : [];
1579
+
1580
+ if (overrideSource) {
1581
+ registryEnabled = true;
1582
+ sources = [normalizeRegistrySource({
1583
+ name: overrideSourceName,
1584
+ source: overrideSource,
1585
+ index_url: overrideIndex
1586
+ })];
1587
+ } else if (overrideIndex && sources.length > 0) {
1588
+ sources = sources.map((source, sourceIndex) => (
1589
+ sourceIndex === 0 ? { ...source, index_url: overrideIndex } : source
1590
+ ));
1591
+ }
1592
+
1593
+ if (!registryEnabled) {
1594
+ warnings.push('registry config is disabled');
1595
+ }
1596
+ if (registryEnabled && sources.length === 0) {
1597
+ errors.push('registry enabled but no sources configured');
1598
+ }
1599
+
1600
+ const maxShards = Number.isFinite(Number(options.maxShards)) && Number(options.maxShards) > 0
1601
+ ? Number(options.maxShards)
1602
+ : 8;
1603
+ const shardSample = Number.isFinite(Number(options.shardSample)) && Number(options.shardSample) > 0
1604
+ ? Number(options.shardSample)
1605
+ : 2;
1606
+
1607
+ const sourceResults = [];
1608
+ for (const source of sources) {
1609
+ const sourceName = normalizeText(source.name) || 'registry';
1610
+ const sourceReport = {
1611
+ source_name: sourceName,
1612
+ source: normalizeText(source.source),
1613
+ index_url: normalizeText(source.index_url),
1614
+ source_ok: false,
1615
+ index_ok: null,
1616
+ shard_sources_checked: 0,
1617
+ source_entries: 0,
1618
+ shard_entries: 0,
1619
+ warnings: [],
1620
+ errors: []
1621
+ };
1622
+
1623
+ if (!sourceReport.source) {
1624
+ sourceReport.errors.push('source is empty');
1625
+ } else {
1626
+ try {
1627
+ const payload = await loadRegistryPayload(projectPath, sourceReport.source, fileSystem);
1628
+ const entries = extractRegistryEntries(payload, sourceName);
1629
+ sourceReport.source_ok = true;
1630
+ sourceReport.source_entries = entries.length;
1631
+ if (entries.length === 0) {
1632
+ sourceReport.warnings.push('source returned no valid entries');
1633
+ }
1634
+ } catch (error) {
1635
+ sourceReport.errors.push(`failed to load source (${sourceReport.source}): ${error.message}`);
1636
+ }
1637
+ }
1638
+
1639
+ if (!sourceReport.index_url) {
1640
+ sourceReport.warnings.push('index_url not configured; remote indexed lookup health is partially validated');
1641
+ } else {
1642
+ try {
1643
+ const indexPayload = await loadRegistryPayload(projectPath, sourceReport.index_url, fileSystem);
1644
+ const index = normalizeRegistryIndex(indexPayload, sourceName);
1645
+ if (!index) {
1646
+ sourceReport.errors.push(`invalid index payload: ${sourceReport.index_url}`);
1647
+ } else {
1648
+ sourceReport.index_ok = true;
1649
+ const tokenToBucket = index.token_to_bucket || {};
1650
+ const unresolved = [];
1651
+ for (const [token, bucketRaw] of Object.entries(tokenToBucket)) {
1652
+ const bucket = normalizeText(bucketRaw);
1653
+ if (!bucket) {
1654
+ continue;
1655
+ }
1656
+ const bucketSource = normalizeText(index.buckets[bucket] || index.buckets[token]);
1657
+ if (!bucketSource) {
1658
+ unresolved.push(`${token}->${bucket}`);
1659
+ }
1660
+ }
1661
+ if (unresolved.length > 0) {
1662
+ sourceReport.errors.push(`unresolved index bucket mappings: ${unresolved.slice(0, 10).join(', ')}`);
1663
+ }
1664
+
1665
+ const sampleTokens = Object.keys(tokenToBucket).slice(0, 64);
1666
+ const shardSources = collectRegistryShardSources(index, sampleTokens, maxShards);
1667
+ sourceReport.shard_sources_checked = shardSources.length;
1668
+ if (shardSources.length === 0) {
1669
+ sourceReport.warnings.push('index resolved zero shard sources');
1670
+ }
1671
+
1672
+ for (const shardSource of shardSources.slice(0, shardSample)) {
1673
+ try {
1674
+ const shardPayload = await loadRegistryPayload(projectPath, shardSource, fileSystem);
1675
+ const shardEntries = extractRegistryEntries(shardPayload, `${sourceName}-shard`);
1676
+ sourceReport.shard_entries += shardEntries.length;
1677
+ } catch (error) {
1678
+ sourceReport.errors.push(`failed to load shard (${shardSource}): ${error.message}`);
1679
+ }
1680
+ }
1681
+ }
1682
+ } catch (error) {
1683
+ sourceReport.index_ok = false;
1684
+ sourceReport.errors.push(`failed to load index (${sourceReport.index_url}): ${error.message}`);
1685
+ }
1686
+ }
1687
+
1688
+ for (const message of sourceReport.warnings) {
1689
+ warnings.push(`[${sourceName}] ${message}`);
1690
+ }
1691
+ for (const message of sourceReport.errors) {
1692
+ errors.push(`[${sourceName}] ${message}`);
1693
+ }
1694
+ sourceResults.push(sourceReport);
1695
+ }
1696
+
1697
+ const result = {
1698
+ mode: 'errorbook-health-registry',
1699
+ checked_at: nowIso(),
1700
+ passed: errors.length === 0,
1701
+ warning_count: warnings.length,
1702
+ error_count: errors.length,
1703
+ paths: {
1704
+ config_file: registryPaths.configFile,
1705
+ cache_file: registryPaths.cacheFile
1706
+ },
1707
+ config: {
1708
+ exists: configExists,
1709
+ enabled: registryEnabled,
1710
+ search_mode: config.search_mode || 'cache',
1711
+ source_count: sources.length
1712
+ },
1713
+ sources: sourceResults,
1714
+ warnings,
1715
+ errors
1716
+ };
1717
+
1718
+ if (options.json) {
1719
+ console.log(JSON.stringify(result, null, 2));
1720
+ } else if (!options.silent) {
1721
+ if (result.passed) {
1722
+ console.log(chalk.green('✓ Errorbook registry health check passed'));
1723
+ } else {
1724
+ console.log(chalk.red('✗ Errorbook registry health check failed'));
1725
+ }
1726
+ console.log(chalk.gray(` sources: ${result.config.source_count}`));
1727
+ console.log(chalk.gray(` warnings: ${result.warning_count}`));
1728
+ console.log(chalk.gray(` errors: ${result.error_count}`));
1729
+ }
1730
+
1731
+ if (options.failOnAlert && !result.passed) {
1732
+ throw new Error(`errorbook registry health failed: ${result.error_count} error(s)`);
1733
+ }
1734
+
1735
+ return result;
1736
+ }
1737
+
1502
1738
  async function runErrorbookListCommand(options = {}, dependencies = {}) {
1503
1739
  const projectPath = dependencies.projectPath || process.cwd();
1504
1740
  const fileSystem = dependencies.fileSystem || fs;
@@ -2148,6 +2384,26 @@ function registerErrorbookCommands(program) {
2148
2384
  }
2149
2385
  });
2150
2386
 
2387
+ errorbook
2388
+ .command('health-registry')
2389
+ .description('Validate external registry config/source/index health')
2390
+ .option('--config <path>', `Registry config path (default: ${DEFAULT_ERRORBOOK_REGISTRY_CONFIG})`)
2391
+ .option('--cache <path>', `Registry cache path (default: ${DEFAULT_ERRORBOOK_REGISTRY_CACHE})`)
2392
+ .option('--source <url-or-path>', 'Override registry source JSON (https://... or local file)')
2393
+ .option('--source-name <name>', 'Override source name label')
2394
+ .option('--index <url-or-path>', 'Override registry index source (https://... or local file)')
2395
+ .option('--max-shards <n>', 'Max index-resolved shards to validate', parseInt, 8)
2396
+ .option('--shard-sample <n>', 'Shard sample count to fetch and validate', parseInt, 2)
2397
+ .option('--fail-on-alert', 'Exit with error when health check finds errors')
2398
+ .option('--json', 'Emit machine-readable JSON')
2399
+ .action(async (options) => {
2400
+ try {
2401
+ await runErrorbookRegistryHealthCommand(options);
2402
+ } catch (error) {
2403
+ emitCommandError(error, options.json);
2404
+ }
2405
+ });
2406
+
2151
2407
  errorbook
2152
2408
  .command('promote <id>')
2153
2409
  .description('Promote entry after strict quality gate')
@@ -2212,6 +2468,7 @@ module.exports = {
2212
2468
  DEFAULT_ERRORBOOK_REGISTRY_CACHE,
2213
2469
  DEFAULT_ERRORBOOK_REGISTRY_EXPORT,
2214
2470
  HIGH_RISK_SIGNAL_TAGS,
2471
+ DEBUG_EVIDENCE_TAGS,
2215
2472
  DEFAULT_PROMOTE_MIN_QUALITY,
2216
2473
  resolveErrorbookPaths,
2217
2474
  resolveErrorbookRegistryPaths,
@@ -2223,6 +2480,7 @@ module.exports = {
2223
2480
  runErrorbookRecordCommand,
2224
2481
  runErrorbookExportCommand,
2225
2482
  runErrorbookSyncRegistryCommand,
2483
+ runErrorbookRegistryHealthCommand,
2226
2484
  runErrorbookListCommand,
2227
2485
  runErrorbookShowCommand,
2228
2486
  runErrorbookFindCommand,