scene-capability-engine 3.3.26 → 3.4.5

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.
@@ -5,6 +5,17 @@ const DOMAIN_MAP_RELATIVE_PATH = path.join('custom', 'problem-domain-map.md');
5
5
  const SCENE_SPEC_RELATIVE_PATH = path.join('custom', 'scene-spec.md');
6
6
  const DOMAIN_CHAIN_RELATIVE_PATH = path.join('custom', 'problem-domain-chain.json');
7
7
  const DOMAIN_CHAIN_API_VERSION = 'sce.problem-domain-chain/v0.1';
8
+ const DOMAIN_RESEARCH_DIMENSIONS = Object.freeze([
9
+ 'scene_boundary',
10
+ 'entity',
11
+ 'relation',
12
+ 'business_rule',
13
+ 'decision_policy',
14
+ 'execution_flow',
15
+ 'failure_signal',
16
+ 'debug_evidence_plan',
17
+ 'verification_gate'
18
+ ]);
8
19
 
9
20
  function normalizeText(value) {
10
21
  if (typeof value !== 'string') {
@@ -79,6 +90,20 @@ mindmap
79
90
  4. Produce candidate fixes and risk tradeoffs.
80
91
  5. Define verification path and measurable acceptance.
81
92
 
93
+ ## Closed-Loop Research Coverage Matrix
94
+
95
+ | Dimension | Coverage Goal | Status |
96
+ | --- | --- | --- |
97
+ | Scene Boundary | Entry, scope, excluded boundaries are explicit | [ ] |
98
+ | Entity | Key entities are listed and scoped | [ ] |
99
+ | Relation | Entity relations and direction are explicit | [ ] |
100
+ | Business Rule | Enforceable rules are mapped | [ ] |
101
+ | Decision Policy | Decision branches and conditions are explicit | [ ] |
102
+ | Execution Flow | End-to-end action chain is explicit | [ ] |
103
+ | Failure Signal | Wrong-direction signals are listed | [ ] |
104
+ | Debug Evidence Plan | Debug-log/diagnostic evidence path is defined | [ ] |
105
+ | Verification Gate | Acceptance and gate criteria are explicit | [ ] |
106
+
82
107
  ## Correction Loop
83
108
 
84
109
  - Expected Wrong-Direction Signals:
@@ -140,6 +165,12 @@ function buildSceneSpec(specId, options = {}) {
140
165
  4. Expected outputs and side effects.
141
166
  5. Failure path and rollback criteria.
142
167
 
168
+ ## Closed-Loop Research Contract
169
+
170
+ - This Scene Spec is invalid if the domain mind map coverage matrix is missing.
171
+ - Decision and execution statements must map to ontology fields in \`problem-domain-chain.json\`.
172
+ - If two remediation rounds fail, debug evidence and diagnostic logs are mandatory before another patch round.
173
+
143
174
  ## Acceptance & Gate
144
175
 
145
176
  - Functional acceptance: define testable behaviors.
@@ -208,6 +239,22 @@ function buildProblemDomainChain(specId, options = {}) {
208
239
  expected_result: 'TBD: side effect and data change'
209
240
  }
210
241
  ],
242
+ research_coverage: {
243
+ mode: 'scene-closed-loop',
244
+ required_dimensions: [...DOMAIN_RESEARCH_DIMENSIONS],
245
+ checklist: {
246
+ scene_boundary: true,
247
+ entity: true,
248
+ relation: true,
249
+ business_rule: true,
250
+ decision_policy: true,
251
+ execution_flow: true,
252
+ failure_signal: true,
253
+ debug_evidence_plan: true,
254
+ verification_gate: true
255
+ },
256
+ status: 'draft'
257
+ },
211
258
  correction_loop: {
212
259
  triggers: [
213
260
  'gate failure',
@@ -236,6 +283,7 @@ function validateProblemDomainMapContent(content = '') {
236
283
  hasRootProblem: /##\s+Root Problem/i.test(content),
237
284
  hasMindMapBlock: /```mermaid[\s\S]*mindmap/i.test(content),
238
285
  hasLayeredExplorationChain: /##\s+Layered Exploration Chain/i.test(content),
286
+ hasCoverageMatrix: /##\s+Closed-Loop Research Coverage Matrix/i.test(content),
239
287
  hasCorrectionLoop: /##\s+Correction Loop/i.test(content)
240
288
  };
241
289
  const passed = Object.values(checks).every(Boolean);
@@ -247,6 +295,7 @@ function validateSceneSpecContent(content = '') {
247
295
  hasSceneDefinition: /##\s+Scene Definition/i.test(content),
248
296
  hasOntologyCoverage: /##\s+Ontology Coverage/i.test(content),
249
297
  hasDecisionExecutionPath: /##\s+Decision & Execution Path/i.test(content),
298
+ hasClosedLoopResearchContract: /##\s+Closed-Loop Research Contract/i.test(content),
250
299
  hasAcceptanceGate: /##\s+Acceptance & Gate/i.test(content)
251
300
  };
252
301
  const passed = Object.values(checks).every(Boolean);
@@ -261,7 +310,41 @@ function hasNonEmptyStringArray(value) {
261
310
  return Array.isArray(value) && value.some((item) => isNonEmptyString(item));
262
311
  }
263
312
 
313
+ function isObject(value) {
314
+ return value && typeof value === 'object' && !Array.isArray(value);
315
+ }
316
+
317
+ function isMeaningfulText(value) {
318
+ if (!isNonEmptyString(value)) {
319
+ return false;
320
+ }
321
+ const normalized = value.trim();
322
+ return !/^(tbd|todo|n\/a|na|待补|待定|待完善|placeholder)\b/i.test(normalized);
323
+ }
324
+
325
+ function hasMeaningfulStringArray(value) {
326
+ return Array.isArray(value) && value.some((item) => isMeaningfulText(item));
327
+ }
328
+
329
+ function hasResearchCoverageChecklist(value) {
330
+ if (!isObject(value)) {
331
+ return false;
332
+ }
333
+ return DOMAIN_RESEARCH_DIMENSIONS.every((dimension) => typeof value[dimension] === 'boolean');
334
+ }
335
+
336
+ function hasResearchCoverageDimensions(value) {
337
+ if (!Array.isArray(value)) {
338
+ return false;
339
+ }
340
+ const normalized = new Set(value.map((item) => `${item || ''}`.trim()).filter(Boolean));
341
+ return DOMAIN_RESEARCH_DIMENSIONS.every((dimension) => normalized.has(dimension));
342
+ }
343
+
264
344
  function validateProblemDomainChainPayload(payload = {}, specId = '') {
345
+ const researchCoverage = payload && isObject(payload.research_coverage)
346
+ ? payload.research_coverage
347
+ : null;
265
348
  const checks = {
266
349
  apiVersion: isNonEmptyString(payload.api_version),
267
350
  sceneId: isNonEmptyString(payload.scene_id),
@@ -275,6 +358,10 @@ function validateProblemDomainChainPayload(payload = {}, specId = '') {
275
358
  hasHypotheses: Array.isArray(payload.hypotheses) && payload.hypotheses.length > 0,
276
359
  hasRisks: Array.isArray(payload.risks) && payload.risks.length > 0,
277
360
  hasDecisionPath: Array.isArray(payload.decision_execution_path) && payload.decision_execution_path.length >= 3,
361
+ hasResearchCoverageObject: Boolean(researchCoverage),
362
+ hasResearchCoverageMode: isNonEmptyString(researchCoverage && researchCoverage.mode),
363
+ hasResearchCoverageDimensions: hasResearchCoverageDimensions(researchCoverage && researchCoverage.required_dimensions),
364
+ hasResearchCoverageChecklist: hasResearchCoverageChecklist(researchCoverage && researchCoverage.checklist),
278
365
  hasCorrectionTriggers: hasNonEmptyStringArray(payload?.correction_loop?.triggers),
279
366
  hasCorrectionActions: hasNonEmptyStringArray(payload?.correction_loop?.actions),
280
367
  hasVerificationGates: hasNonEmptyStringArray(payload?.verification?.gates)
@@ -285,6 +372,104 @@ function validateProblemDomainChainPayload(payload = {}, specId = '') {
285
372
  };
286
373
  }
287
374
 
375
+ function buildDomainCoverageItems(chainPayload = {}, validation = null) {
376
+ const ontology = isObject(chainPayload && chainPayload.ontology) ? chainPayload.ontology : {};
377
+ const correctionLoop = isObject(chainPayload && chainPayload.correction_loop) ? chainPayload.correction_loop : {};
378
+ const verification = isObject(chainPayload && chainPayload.verification) ? chainPayload.verification : {};
379
+ const researchCoverage = isObject(chainPayload && chainPayload.research_coverage)
380
+ ? chainPayload.research_coverage
381
+ : {};
382
+
383
+ const structuralItems = [];
384
+ if (validation && isObject(validation.details)) {
385
+ structuralItems.push({
386
+ id: 'map_structure',
387
+ label: 'problem-domain-map structure',
388
+ covered: Boolean(validation.details.domain_map && validation.details.domain_map.exists && validation.details.domain_map.checks && Object.values(validation.details.domain_map.checks).every(Boolean)),
389
+ evidence: DOMAIN_MAP_RELATIVE_PATH
390
+ });
391
+ structuralItems.push({
392
+ id: 'scene_structure',
393
+ label: 'scene-spec structure',
394
+ covered: Boolean(validation.details.scene_spec && validation.details.scene_spec.exists && validation.details.scene_spec.checks && Object.values(validation.details.scene_spec.checks).every(Boolean)),
395
+ evidence: SCENE_SPEC_RELATIVE_PATH
396
+ });
397
+ structuralItems.push({
398
+ id: 'chain_structure',
399
+ label: 'problem-domain-chain structure',
400
+ covered: Boolean(validation.details.domain_chain && validation.details.domain_chain.exists && validation.details.domain_chain.checks && Object.values(validation.details.domain_chain.checks).every(Boolean)),
401
+ evidence: DOMAIN_CHAIN_RELATIVE_PATH
402
+ });
403
+ }
404
+
405
+ const domainItems = [
406
+ {
407
+ id: 'scene_boundary',
408
+ label: 'Scene boundary',
409
+ covered: isMeaningfulText(chainPayload?.problem?.statement) && isMeaningfulText(chainPayload?.problem?.scope),
410
+ evidence: 'problem.statement + problem.scope'
411
+ },
412
+ {
413
+ id: 'entity',
414
+ label: 'Ontology entity coverage',
415
+ covered: hasMeaningfulStringArray(ontology.entity),
416
+ evidence: 'ontology.entity[]'
417
+ },
418
+ {
419
+ id: 'relation',
420
+ label: 'Ontology relation coverage',
421
+ covered: hasMeaningfulStringArray(ontology.relation),
422
+ evidence: 'ontology.relation[]'
423
+ },
424
+ {
425
+ id: 'business_rule',
426
+ label: 'Business rule coverage',
427
+ covered: hasMeaningfulStringArray(ontology.business_rule),
428
+ evidence: 'ontology.business_rule[]'
429
+ },
430
+ {
431
+ id: 'decision_policy',
432
+ label: 'Decision policy coverage',
433
+ covered: hasMeaningfulStringArray(ontology.decision_policy),
434
+ evidence: 'ontology.decision_policy[]'
435
+ },
436
+ {
437
+ id: 'execution_flow',
438
+ label: 'Execution flow coverage',
439
+ covered: hasMeaningfulStringArray(ontology.execution_flow),
440
+ evidence: 'ontology.execution_flow[]'
441
+ },
442
+ {
443
+ id: 'failure_signal',
444
+ label: 'Failure-signal coverage',
445
+ covered: hasMeaningfulStringArray(correctionLoop.triggers),
446
+ evidence: 'correction_loop.triggers[]'
447
+ },
448
+ {
449
+ id: 'debug_evidence_plan',
450
+ label: 'Debug evidence plan',
451
+ covered: Array.isArray(correctionLoop.actions)
452
+ && correctionLoop.actions.some((item) => /debug|diagnostic|日志|evidence/i.test(`${item || ''}`)),
453
+ evidence: 'correction_loop.actions[]'
454
+ },
455
+ {
456
+ id: 'verification_gate',
457
+ label: 'Verification gate coverage',
458
+ covered: hasMeaningfulStringArray(verification.gates),
459
+ evidence: 'verification.gates[]'
460
+ },
461
+ {
462
+ id: 'research_contract',
463
+ label: 'Research contract checklist',
464
+ covered: hasResearchCoverageChecklist(researchCoverage.checklist)
465
+ && hasResearchCoverageDimensions(researchCoverage.required_dimensions),
466
+ evidence: 'research_coverage.required_dimensions + checklist'
467
+ }
468
+ ];
469
+
470
+ return [...structuralItems, ...domainItems];
471
+ }
472
+
288
473
  async function ensureSpecDomainArtifacts(projectPath, specId, options = {}) {
289
474
  const fileSystem = options.fileSystem || fs;
290
475
  const dryRun = options.dryRun === true;
@@ -426,14 +611,45 @@ async function validateSpecDomainArtifacts(projectPath, specId, fileSystem = fs)
426
611
  };
427
612
  }
428
613
 
614
+ async function analyzeSpecDomainCoverage(projectPath, specId, fileSystem = fs) {
615
+ const validation = await validateSpecDomainArtifacts(projectPath, specId, fileSystem);
616
+ const paths = resolveSpecPaths(projectPath, specId);
617
+ let chainPayload = null;
618
+
619
+ if (await fileSystem.pathExists(paths.domainChainPath)) {
620
+ try {
621
+ chainPayload = await fileSystem.readJson(paths.domainChainPath);
622
+ } catch (_error) {
623
+ chainPayload = null;
624
+ }
625
+ }
626
+
627
+ const items = buildDomainCoverageItems(chainPayload || {}, validation);
628
+ const coveredCount = items.filter((item) => item.covered).length;
629
+ const totalCount = items.length;
630
+ const uncovered = items.filter((item) => !item.covered).map((item) => item.id);
631
+
632
+ return {
633
+ passed: uncovered.length === 0,
634
+ coverage_ratio: totalCount > 0 ? coveredCount / totalCount : 0,
635
+ covered_count: coveredCount,
636
+ total_count: totalCount,
637
+ uncovered,
638
+ items,
639
+ validation
640
+ };
641
+ }
642
+
429
643
  module.exports = {
430
644
  DOMAIN_MAP_RELATIVE_PATH,
431
645
  SCENE_SPEC_RELATIVE_PATH,
432
646
  DOMAIN_CHAIN_RELATIVE_PATH,
433
647
  DOMAIN_CHAIN_API_VERSION,
648
+ DOMAIN_RESEARCH_DIMENSIONS,
434
649
  buildProblemDomainMindMap,
435
650
  buildSceneSpec,
436
651
  buildProblemDomainChain,
437
652
  ensureSpecDomainArtifacts,
438
- validateSpecDomainArtifacts
653
+ validateSpecDomainArtifacts,
654
+ analyzeSpecDomainCoverage
439
655
  };