scene-capability-engine 3.3.26 → 3.4.6

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.
@@ -4,7 +4,19 @@ const path = require('path');
4
4
  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
+ const PROBLEM_CONTRACT_RELATIVE_PATH = path.join('custom', 'problem-contract.json');
7
8
  const DOMAIN_CHAIN_API_VERSION = 'sce.problem-domain-chain/v0.1';
9
+ const DOMAIN_RESEARCH_DIMENSIONS = Object.freeze([
10
+ 'scene_boundary',
11
+ 'entity',
12
+ 'relation',
13
+ 'business_rule',
14
+ 'decision_policy',
15
+ 'execution_flow',
16
+ 'failure_signal',
17
+ 'debug_evidence_plan',
18
+ 'verification_gate'
19
+ ]);
8
20
 
9
21
  function normalizeText(value) {
10
22
  if (typeof value !== 'string') {
@@ -19,7 +31,30 @@ function resolveSpecPaths(projectPath, specId) {
19
31
  specPath,
20
32
  domainMapPath: path.join(specPath, DOMAIN_MAP_RELATIVE_PATH),
21
33
  sceneSpecPath: path.join(specPath, SCENE_SPEC_RELATIVE_PATH),
22
- domainChainPath: path.join(specPath, DOMAIN_CHAIN_RELATIVE_PATH)
34
+ domainChainPath: path.join(specPath, DOMAIN_CHAIN_RELATIVE_PATH),
35
+ problemContractPath: path.join(specPath, PROBLEM_CONTRACT_RELATIVE_PATH)
36
+ };
37
+ }
38
+
39
+ function buildProblemContract(specId, options = {}) {
40
+ const sceneId = normalizeText(options.sceneId) || `scene.${specId.replace(/[^a-zA-Z0-9]+/g, '-').toLowerCase()}`;
41
+ const problemStatement = normalizeText(options.problemStatement) || 'TBD: describe the primary business problem';
42
+ const verificationPlan = normalizeText(options.verificationPlan) || 'TBD: define validation and rollback criteria';
43
+ return {
44
+ schema_version: '1.0',
45
+ spec_id: specId,
46
+ scene_id: sceneId,
47
+ issue_statement: problemStatement,
48
+ expected_outcome: verificationPlan,
49
+ reproduction_steps: [
50
+ 'Reproduce the issue along the failing scene path.',
51
+ 'Capture gate/test/runtime evidence for failed behavior.'
52
+ ],
53
+ impact_scope: `scene=${sceneId}`,
54
+ forbidden_workarounds: [
55
+ 'Do not bypass mandatory gates or tests.',
56
+ 'Do not silence runtime errors without root-cause remediation.'
57
+ ]
23
58
  };
24
59
  }
25
60
 
@@ -79,6 +114,20 @@ mindmap
79
114
  4. Produce candidate fixes and risk tradeoffs.
80
115
  5. Define verification path and measurable acceptance.
81
116
 
117
+ ## Closed-Loop Research Coverage Matrix
118
+
119
+ | Dimension | Coverage Goal | Status |
120
+ | --- | --- | --- |
121
+ | Scene Boundary | Entry, scope, excluded boundaries are explicit | [ ] |
122
+ | Entity | Key entities are listed and scoped | [ ] |
123
+ | Relation | Entity relations and direction are explicit | [ ] |
124
+ | Business Rule | Enforceable rules are mapped | [ ] |
125
+ | Decision Policy | Decision branches and conditions are explicit | [ ] |
126
+ | Execution Flow | End-to-end action chain is explicit | [ ] |
127
+ | Failure Signal | Wrong-direction signals are listed | [ ] |
128
+ | Debug Evidence Plan | Debug-log/diagnostic evidence path is defined | [ ] |
129
+ | Verification Gate | Acceptance and gate criteria are explicit | [ ] |
130
+
82
131
  ## Correction Loop
83
132
 
84
133
  - Expected Wrong-Direction Signals:
@@ -140,6 +189,12 @@ function buildSceneSpec(specId, options = {}) {
140
189
  4. Expected outputs and side effects.
141
190
  5. Failure path and rollback criteria.
142
191
 
192
+ ## Closed-Loop Research Contract
193
+
194
+ - This Scene Spec is invalid if the domain mind map coverage matrix is missing.
195
+ - Decision and execution statements must map to ontology fields in \`problem-domain-chain.json\`.
196
+ - If two remediation rounds fail, debug evidence and diagnostic logs are mandatory before another patch round.
197
+
143
198
  ## Acceptance & Gate
144
199
 
145
200
  - Functional acceptance: define testable behaviors.
@@ -208,6 +263,22 @@ function buildProblemDomainChain(specId, options = {}) {
208
263
  expected_result: 'TBD: side effect and data change'
209
264
  }
210
265
  ],
266
+ research_coverage: {
267
+ mode: 'scene-closed-loop',
268
+ required_dimensions: [...DOMAIN_RESEARCH_DIMENSIONS],
269
+ checklist: {
270
+ scene_boundary: true,
271
+ entity: true,
272
+ relation: true,
273
+ business_rule: true,
274
+ decision_policy: true,
275
+ execution_flow: true,
276
+ failure_signal: true,
277
+ debug_evidence_plan: true,
278
+ verification_gate: true
279
+ },
280
+ status: 'draft'
281
+ },
211
282
  correction_loop: {
212
283
  triggers: [
213
284
  'gate failure',
@@ -227,6 +298,14 @@ function buildProblemDomainChain(specId, options = {}) {
227
298
  'tests',
228
299
  'release preflight'
229
300
  ]
301
+ },
302
+ problem_contract: buildProblemContract(specId, options),
303
+ ontology_evidence: {
304
+ entity: ['Entity mapping evidence: model/schema reference'],
305
+ relation: ['Relation mapping evidence: join/foreign-key or service linkage'],
306
+ business_rule: ['Rule evidence: validation/policy check reference'],
307
+ decision_policy: ['Decision evidence: branch condition and policy source'],
308
+ execution_flow: ['Execution evidence: service/screen/runtime trace path']
230
309
  }
231
310
  };
232
311
  }
@@ -236,6 +315,7 @@ function validateProblemDomainMapContent(content = '') {
236
315
  hasRootProblem: /##\s+Root Problem/i.test(content),
237
316
  hasMindMapBlock: /```mermaid[\s\S]*mindmap/i.test(content),
238
317
  hasLayeredExplorationChain: /##\s+Layered Exploration Chain/i.test(content),
318
+ hasCoverageMatrix: /##\s+Closed-Loop Research Coverage Matrix/i.test(content),
239
319
  hasCorrectionLoop: /##\s+Correction Loop/i.test(content)
240
320
  };
241
321
  const passed = Object.values(checks).every(Boolean);
@@ -247,6 +327,7 @@ function validateSceneSpecContent(content = '') {
247
327
  hasSceneDefinition: /##\s+Scene Definition/i.test(content),
248
328
  hasOntologyCoverage: /##\s+Ontology Coverage/i.test(content),
249
329
  hasDecisionExecutionPath: /##\s+Decision & Execution Path/i.test(content),
330
+ hasClosedLoopResearchContract: /##\s+Closed-Loop Research Contract/i.test(content),
250
331
  hasAcceptanceGate: /##\s+Acceptance & Gate/i.test(content)
251
332
  };
252
333
  const passed = Object.values(checks).every(Boolean);
@@ -261,7 +342,41 @@ function hasNonEmptyStringArray(value) {
261
342
  return Array.isArray(value) && value.some((item) => isNonEmptyString(item));
262
343
  }
263
344
 
345
+ function isObject(value) {
346
+ return value && typeof value === 'object' && !Array.isArray(value);
347
+ }
348
+
349
+ function isMeaningfulText(value) {
350
+ if (!isNonEmptyString(value)) {
351
+ return false;
352
+ }
353
+ const normalized = value.trim();
354
+ return !/^(tbd|todo|n\/a|na|待补|待定|待完善|placeholder)\b/i.test(normalized);
355
+ }
356
+
357
+ function hasMeaningfulStringArray(value) {
358
+ return Array.isArray(value) && value.some((item) => isMeaningfulText(item));
359
+ }
360
+
361
+ function hasResearchCoverageChecklist(value) {
362
+ if (!isObject(value)) {
363
+ return false;
364
+ }
365
+ return DOMAIN_RESEARCH_DIMENSIONS.every((dimension) => typeof value[dimension] === 'boolean');
366
+ }
367
+
368
+ function hasResearchCoverageDimensions(value) {
369
+ if (!Array.isArray(value)) {
370
+ return false;
371
+ }
372
+ const normalized = new Set(value.map((item) => `${item || ''}`.trim()).filter(Boolean));
373
+ return DOMAIN_RESEARCH_DIMENSIONS.every((dimension) => normalized.has(dimension));
374
+ }
375
+
264
376
  function validateProblemDomainChainPayload(payload = {}, specId = '') {
377
+ const researchCoverage = payload && isObject(payload.research_coverage)
378
+ ? payload.research_coverage
379
+ : null;
265
380
  const checks = {
266
381
  apiVersion: isNonEmptyString(payload.api_version),
267
382
  sceneId: isNonEmptyString(payload.scene_id),
@@ -275,6 +390,10 @@ function validateProblemDomainChainPayload(payload = {}, specId = '') {
275
390
  hasHypotheses: Array.isArray(payload.hypotheses) && payload.hypotheses.length > 0,
276
391
  hasRisks: Array.isArray(payload.risks) && payload.risks.length > 0,
277
392
  hasDecisionPath: Array.isArray(payload.decision_execution_path) && payload.decision_execution_path.length >= 3,
393
+ hasResearchCoverageObject: Boolean(researchCoverage),
394
+ hasResearchCoverageMode: isNonEmptyString(researchCoverage && researchCoverage.mode),
395
+ hasResearchCoverageDimensions: hasResearchCoverageDimensions(researchCoverage && researchCoverage.required_dimensions),
396
+ hasResearchCoverageChecklist: hasResearchCoverageChecklist(researchCoverage && researchCoverage.checklist),
278
397
  hasCorrectionTriggers: hasNonEmptyStringArray(payload?.correction_loop?.triggers),
279
398
  hasCorrectionActions: hasNonEmptyStringArray(payload?.correction_loop?.actions),
280
399
  hasVerificationGates: hasNonEmptyStringArray(payload?.verification?.gates)
@@ -285,6 +404,104 @@ function validateProblemDomainChainPayload(payload = {}, specId = '') {
285
404
  };
286
405
  }
287
406
 
407
+ function buildDomainCoverageItems(chainPayload = {}, validation = null) {
408
+ const ontology = isObject(chainPayload && chainPayload.ontology) ? chainPayload.ontology : {};
409
+ const correctionLoop = isObject(chainPayload && chainPayload.correction_loop) ? chainPayload.correction_loop : {};
410
+ const verification = isObject(chainPayload && chainPayload.verification) ? chainPayload.verification : {};
411
+ const researchCoverage = isObject(chainPayload && chainPayload.research_coverage)
412
+ ? chainPayload.research_coverage
413
+ : {};
414
+
415
+ const structuralItems = [];
416
+ if (validation && isObject(validation.details)) {
417
+ structuralItems.push({
418
+ id: 'map_structure',
419
+ label: 'problem-domain-map structure',
420
+ 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)),
421
+ evidence: DOMAIN_MAP_RELATIVE_PATH
422
+ });
423
+ structuralItems.push({
424
+ id: 'scene_structure',
425
+ label: 'scene-spec structure',
426
+ 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)),
427
+ evidence: SCENE_SPEC_RELATIVE_PATH
428
+ });
429
+ structuralItems.push({
430
+ id: 'chain_structure',
431
+ label: 'problem-domain-chain structure',
432
+ 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)),
433
+ evidence: DOMAIN_CHAIN_RELATIVE_PATH
434
+ });
435
+ }
436
+
437
+ const domainItems = [
438
+ {
439
+ id: 'scene_boundary',
440
+ label: 'Scene boundary',
441
+ covered: isMeaningfulText(chainPayload?.problem?.statement) && isMeaningfulText(chainPayload?.problem?.scope),
442
+ evidence: 'problem.statement + problem.scope'
443
+ },
444
+ {
445
+ id: 'entity',
446
+ label: 'Ontology entity coverage',
447
+ covered: hasMeaningfulStringArray(ontology.entity),
448
+ evidence: 'ontology.entity[]'
449
+ },
450
+ {
451
+ id: 'relation',
452
+ label: 'Ontology relation coverage',
453
+ covered: hasMeaningfulStringArray(ontology.relation),
454
+ evidence: 'ontology.relation[]'
455
+ },
456
+ {
457
+ id: 'business_rule',
458
+ label: 'Business rule coverage',
459
+ covered: hasMeaningfulStringArray(ontology.business_rule),
460
+ evidence: 'ontology.business_rule[]'
461
+ },
462
+ {
463
+ id: 'decision_policy',
464
+ label: 'Decision policy coverage',
465
+ covered: hasMeaningfulStringArray(ontology.decision_policy),
466
+ evidence: 'ontology.decision_policy[]'
467
+ },
468
+ {
469
+ id: 'execution_flow',
470
+ label: 'Execution flow coverage',
471
+ covered: hasMeaningfulStringArray(ontology.execution_flow),
472
+ evidence: 'ontology.execution_flow[]'
473
+ },
474
+ {
475
+ id: 'failure_signal',
476
+ label: 'Failure-signal coverage',
477
+ covered: hasMeaningfulStringArray(correctionLoop.triggers),
478
+ evidence: 'correction_loop.triggers[]'
479
+ },
480
+ {
481
+ id: 'debug_evidence_plan',
482
+ label: 'Debug evidence plan',
483
+ covered: Array.isArray(correctionLoop.actions)
484
+ && correctionLoop.actions.some((item) => /debug|diagnostic|日志|evidence/i.test(`${item || ''}`)),
485
+ evidence: 'correction_loop.actions[]'
486
+ },
487
+ {
488
+ id: 'verification_gate',
489
+ label: 'Verification gate coverage',
490
+ covered: hasMeaningfulStringArray(verification.gates),
491
+ evidence: 'verification.gates[]'
492
+ },
493
+ {
494
+ id: 'research_contract',
495
+ label: 'Research contract checklist',
496
+ covered: hasResearchCoverageChecklist(researchCoverage.checklist)
497
+ && hasResearchCoverageDimensions(researchCoverage.required_dimensions),
498
+ evidence: 'research_coverage.required_dimensions + checklist'
499
+ }
500
+ ];
501
+
502
+ return [...structuralItems, ...domainItems];
503
+ }
504
+
288
505
  async function ensureSpecDomainArtifacts(projectPath, specId, options = {}) {
289
506
  const fileSystem = options.fileSystem || fs;
290
507
  const dryRun = options.dryRun === true;
@@ -294,11 +511,13 @@ async function ensureSpecDomainArtifacts(projectPath, specId, options = {}) {
294
511
  const domainMapContent = buildProblemDomainMindMap(specId, options);
295
512
  const sceneSpecContent = buildSceneSpec(specId, options);
296
513
  const domainChainPayload = buildProblemDomainChain(specId, options);
514
+ const problemContractPayload = buildProblemContract(specId, options);
297
515
 
298
516
  const created = {
299
517
  domain_map: false,
300
518
  scene_spec: false,
301
- domain_chain: false
519
+ domain_chain: false,
520
+ problem_contract: false
302
521
  };
303
522
 
304
523
  if (!dryRun) {
@@ -321,23 +540,32 @@ async function ensureSpecDomainArtifacts(projectPath, specId, options = {}) {
321
540
  await fileSystem.writeJson(paths.domainChainPath, domainChainPayload, { spaces: 2 });
322
541
  created.domain_chain = true;
323
542
  }
543
+
544
+ const hasProblemContract = await fileSystem.pathExists(paths.problemContractPath);
545
+ if (force || !hasProblemContract) {
546
+ await fileSystem.writeJson(paths.problemContractPath, problemContractPayload, { spaces: 2 });
547
+ created.problem_contract = true;
548
+ }
324
549
  } else {
325
550
  created.domain_map = true;
326
551
  created.scene_spec = true;
327
552
  created.domain_chain = true;
553
+ created.problem_contract = true;
328
554
  }
329
555
 
330
556
  return {
331
557
  paths: {
332
558
  domain_map: paths.domainMapPath,
333
559
  scene_spec: paths.sceneSpecPath,
334
- domain_chain: paths.domainChainPath
560
+ domain_chain: paths.domainChainPath,
561
+ problem_contract: paths.problemContractPath
335
562
  },
336
563
  created,
337
564
  preview: {
338
565
  domain_map: domainMapContent,
339
566
  scene_spec: sceneSpecContent,
340
- domain_chain: domainChainPayload
567
+ domain_chain: domainChainPayload,
568
+ problem_contract: problemContractPayload
341
569
  }
342
570
  };
343
571
  }
@@ -426,14 +654,47 @@ async function validateSpecDomainArtifacts(projectPath, specId, fileSystem = fs)
426
654
  };
427
655
  }
428
656
 
657
+ async function analyzeSpecDomainCoverage(projectPath, specId, fileSystem = fs) {
658
+ const validation = await validateSpecDomainArtifacts(projectPath, specId, fileSystem);
659
+ const paths = resolveSpecPaths(projectPath, specId);
660
+ let chainPayload = null;
661
+
662
+ if (await fileSystem.pathExists(paths.domainChainPath)) {
663
+ try {
664
+ chainPayload = await fileSystem.readJson(paths.domainChainPath);
665
+ } catch (_error) {
666
+ chainPayload = null;
667
+ }
668
+ }
669
+
670
+ const items = buildDomainCoverageItems(chainPayload || {}, validation);
671
+ const coveredCount = items.filter((item) => item.covered).length;
672
+ const totalCount = items.length;
673
+ const uncovered = items.filter((item) => !item.covered).map((item) => item.id);
674
+
675
+ return {
676
+ passed: uncovered.length === 0,
677
+ coverage_ratio: totalCount > 0 ? coveredCount / totalCount : 0,
678
+ covered_count: coveredCount,
679
+ total_count: totalCount,
680
+ uncovered,
681
+ items,
682
+ validation
683
+ };
684
+ }
685
+
429
686
  module.exports = {
430
687
  DOMAIN_MAP_RELATIVE_PATH,
431
688
  SCENE_SPEC_RELATIVE_PATH,
432
689
  DOMAIN_CHAIN_RELATIVE_PATH,
690
+ PROBLEM_CONTRACT_RELATIVE_PATH,
433
691
  DOMAIN_CHAIN_API_VERSION,
692
+ DOMAIN_RESEARCH_DIMENSIONS,
434
693
  buildProblemDomainMindMap,
435
694
  buildSceneSpec,
436
695
  buildProblemDomainChain,
696
+ buildProblemContract,
437
697
  ensureSpecDomainArtifacts,
438
- validateSpecDomainArtifacts
698
+ validateSpecDomainArtifacts,
699
+ analyzeSpecDomainCoverage
439
700
  };