scene-capability-engine 3.6.55 → 3.6.57

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 (32) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +7 -2
  3. package/README.zh.md +7 -2
  4. package/bin/scene-capability-engine.js +10 -0
  5. package/docs/app-intent-apply-contract.md +263 -0
  6. package/docs/autonomous-control-guide.md +12 -0
  7. package/docs/command-reference.md +15 -0
  8. package/docs/examples/app-intent-phase1/.sce/app/collections/planning-workbench.json +34 -0
  9. package/docs/examples/app-intent-phase1/.sce/app/collections/sales-workbench.json +40 -0
  10. package/docs/examples/app-intent-phase1/.sce/app/collections/warehouse-kiosk.json +35 -0
  11. package/docs/examples/app-intent-phase1/.sce/app/scene-profiles/planning-desktop.json +23 -0
  12. package/docs/examples/app-intent-phase1/.sce/app/scene-profiles/sales-desktop.json +24 -0
  13. package/docs/examples/app-intent-phase1/.sce/app/scene-profiles/warehouse-tablet.json +23 -0
  14. package/docs/examples/app-intent-phase1/README.md +29 -0
  15. package/docs/magicball-app-collection-phase-1.md +37 -0
  16. package/docs/magicball-cli-invocation-examples.md +1 -0
  17. package/docs/magicball-integration-doc-index.md +13 -6
  18. package/docs/magicball-sce-adaptation-guide.md +2 -0
  19. package/docs/releases/README.md +2 -0
  20. package/docs/releases/v3.6.56.md +19 -0
  21. package/docs/releases/v3.6.57.md +19 -0
  22. package/docs/spec-workflow.md +42 -0
  23. package/docs/zh/releases/README.md +2 -0
  24. package/docs/zh/releases/v3.6.56.md +19 -0
  25. package/docs/zh/releases/v3.6.57.md +19 -0
  26. package/lib/commands/spec-gate.js +57 -6
  27. package/lib/commands/spec-pipeline.js +13 -4
  28. package/lib/commands/spec-strategy.js +73 -0
  29. package/lib/problem/project-problem-projection.js +43 -0
  30. package/lib/spec/complexity-strategy.js +636 -0
  31. package/package.json +1 -1
  32. package/template/.sce/README.md +2 -2
@@ -0,0 +1,636 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const { analyzeGoalSemantics } = require('../auto/semantic-decomposer');
4
+ const {
5
+ DOMAIN_MAP_RELATIVE_PATH,
6
+ SCENE_SPEC_RELATIVE_PATH,
7
+ DOMAIN_CHAIN_RELATIVE_PATH,
8
+ PROBLEM_CONTRACT_RELATIVE_PATH,
9
+ analyzeSpecDomainCoverage
10
+ } = require('./domain-modeling');
11
+
12
+ const UNCERTAINTY_PATTERNS = [
13
+ /\bunknown\b/gi,
14
+ /\bunclear\b/gi,
15
+ /\bunsure\b/gi,
16
+ /\bnot sure\b/gi,
17
+ /\btbd\b/gi,
18
+ /\btodo\b/gi,
19
+ /\bplaceholder\b/gi,
20
+ /待确认/g,
21
+ /待定/g,
22
+ /不明确/g,
23
+ /不清晰/g,
24
+ /未知/g
25
+ ];
26
+
27
+ const CONTRACT_PATTERNS = [
28
+ /\bapi\b/gi,
29
+ /\binterface\b/gi,
30
+ /\bcontract\b/gi,
31
+ /\bendpoint\b/gi,
32
+ /\bschema\b/gi,
33
+ /\bfrontend\b/gi,
34
+ /\bbackend\b/gi,
35
+ /接口/g,
36
+ /前端/g,
37
+ /后端/g,
38
+ /契约/g
39
+ ];
40
+
41
+ const POLICY_PATTERNS = [
42
+ /\bpolicy\b/gi,
43
+ /\brule\b/gi,
44
+ /\bapproval\b/gi,
45
+ /\bpermission\b/gi,
46
+ /\bcompliance\b/gi,
47
+ /\bgovernance\b/gi,
48
+ /规则/g,
49
+ /策略/g,
50
+ /审批/g,
51
+ /权限/g,
52
+ /治理/g
53
+ ];
54
+
55
+ const DEPENDENCY_PATTERNS = [
56
+ /\bdependency\b/gi,
57
+ /\bintegration\b/gi,
58
+ /\bworkflow\b/gi,
59
+ /\borchestrate\b/gi,
60
+ /\bscheduler\b/gi,
61
+ /\bsubsystem\b/gi,
62
+ /\bservice\b/gi,
63
+ /\bmodule\b/gi,
64
+ /依赖/g,
65
+ /集成/g,
66
+ /编排/g,
67
+ /调度/g,
68
+ /子系统/g,
69
+ /服务/g,
70
+ /模块/g
71
+ ];
72
+
73
+ const OWNERSHIP_PATTERNS = [
74
+ /\bowner\b/gi,
75
+ /\bownership\b/gi,
76
+ /\bresponsibility\b/gi,
77
+ /\bteam\b/gi,
78
+ /\brole\b/gi,
79
+ /负责人/g,
80
+ /归属/g,
81
+ /责任/g,
82
+ /团队/g,
83
+ /角色/g
84
+ ];
85
+
86
+ const VERIFICATION_PATTERNS = [
87
+ /\btest\b/gi,
88
+ /\bverify\b/gi,
89
+ /\bvalidation\b/gi,
90
+ /\bacceptance\b/gi,
91
+ /\bgate\b/gi,
92
+ /\bevidence\b/gi,
93
+ /测试/g,
94
+ /验证/g,
95
+ /验收/g,
96
+ /门禁/g,
97
+ /证据/g
98
+ ];
99
+
100
+ const ROLE_PATTERNS = [
101
+ /\buser\b/gi,
102
+ /\boperator\b/gi,
103
+ /\bmaintainer\b/gi,
104
+ /\badmin\b/gi,
105
+ /\bplanner\b/gi,
106
+ /\bsales\b/gi,
107
+ /\bwarehouse\b/gi,
108
+ /用户/g,
109
+ /运维/g,
110
+ /维护/g,
111
+ /管理员/g,
112
+ /计划/g,
113
+ /销售/g,
114
+ /仓储/g
115
+ ];
116
+
117
+ function normalizeText(value) {
118
+ if (typeof value !== 'string') {
119
+ return '';
120
+ }
121
+ return value.trim();
122
+ }
123
+
124
+ function clampScore(value) {
125
+ const numeric = Number(value);
126
+ if (!Number.isFinite(numeric)) {
127
+ return 1;
128
+ }
129
+ return Math.max(1, Math.min(5, Math.round(numeric)));
130
+ }
131
+
132
+ function scoreLevel(score) {
133
+ if (score >= 4) {
134
+ return 'high';
135
+ }
136
+ if (score >= 3) {
137
+ return 'medium';
138
+ }
139
+ return 'low';
140
+ }
141
+
142
+ function countPatternHits(text, patterns = []) {
143
+ const source = normalizeText(text);
144
+ if (!source) {
145
+ return 0;
146
+ }
147
+ let total = 0;
148
+ for (const pattern of patterns) {
149
+ const matches = source.match(pattern);
150
+ total += Array.isArray(matches) ? matches.length : 0;
151
+ }
152
+ return total;
153
+ }
154
+
155
+ function countChecklistItems(content = '') {
156
+ const matches = content.match(/^- \[[ xX]\]/gm);
157
+ return Array.isArray(matches) ? matches.length : 0;
158
+ }
159
+
160
+ function average(values = []) {
161
+ const filtered = values.filter((value) => Number.isFinite(Number(value))).map((value) => Number(value));
162
+ if (filtered.length === 0) {
163
+ return 0;
164
+ }
165
+ return filtered.reduce((sum, value) => sum + value, 0) / filtered.length;
166
+ }
167
+
168
+ function createDimension(key, score, reason, evidence = []) {
169
+ const normalizedScore = clampScore(score);
170
+ return {
171
+ key,
172
+ score: normalizedScore,
173
+ level: scoreLevel(normalizedScore),
174
+ reason,
175
+ evidence: Array.isArray(evidence) ? evidence.filter(Boolean) : []
176
+ };
177
+ }
178
+
179
+ function listHighPressureSignals(dimensions = []) {
180
+ return dimensions
181
+ .filter((item) => item.score >= 4)
182
+ .map((item) => item.reason);
183
+ }
184
+
185
+ function selectTopology(decision, dimensions = []) {
186
+ const byKey = Object.fromEntries(dimensions.map((item) => [item.key, item]));
187
+ if (decision === 'single-spec') {
188
+ return {
189
+ type: 'single-spec',
190
+ tracks: ['implementation']
191
+ };
192
+ }
193
+
194
+ if (decision === 'research-program') {
195
+ const tracks = ['domain-clarification'];
196
+ if ((byKey.contract_clarity && byKey.contract_clarity.score >= 4)) {
197
+ tracks.push('contract-clarification');
198
+ }
199
+ if ((byKey.policy_clarity && byKey.policy_clarity.score >= 4)) {
200
+ tracks.push('policy-clarification');
201
+ }
202
+ if ((byKey.ownership_clarity && byKey.ownership_clarity.score >= 4)) {
203
+ tracks.push('ownership-clarification');
204
+ }
205
+ tracks.push('implementation-decomposition');
206
+ tracks.push('verification-strategy');
207
+ return {
208
+ type: 'research-first-master-sub',
209
+ tracks: Array.from(new Set(tracks))
210
+ };
211
+ }
212
+
213
+ const tracks = ['implementation-split'];
214
+ if ((byKey.contract_clarity && byKey.contract_clarity.score >= 3)) {
215
+ tracks.push('integration-contract');
216
+ }
217
+ if ((byKey.verification_readiness && byKey.verification_readiness.score >= 3)) {
218
+ tracks.push('verification');
219
+ }
220
+ if ((byKey.dependency_entanglement && byKey.dependency_entanglement.score >= 4)) {
221
+ tracks.push('orchestration');
222
+ }
223
+ return {
224
+ type: 'implementation-master-sub',
225
+ tracks: Array.from(new Set(tracks))
226
+ };
227
+ }
228
+
229
+ function recommendProgramSpecCount(decision, dimensions = []) {
230
+ if (decision === 'single-spec') {
231
+ return 1;
232
+ }
233
+ const highCount = dimensions.filter((item) => item.score >= 4).length;
234
+ return decision === 'research-program'
235
+ ? Math.max(4, Math.min(6, 3 + highCount))
236
+ : Math.max(3, Math.min(5, 2 + highCount));
237
+ }
238
+
239
+ function buildNextActions(decision, sourceType, sourceId = null) {
240
+ if (decision === 'single-spec') {
241
+ return [
242
+ sourceType === 'spec'
243
+ ? `continue refining Spec ${sourceId} through requirements/design/tasks`
244
+ : 'bootstrap one Spec and keep domain-first artifacts aligned',
245
+ 'run spec domain coverage before implementation tasks',
246
+ 'avoid premature multi-Spec split unless new evidence appears'
247
+ ];
248
+ }
249
+
250
+ if (decision === 'research-program') {
251
+ return [
252
+ 'create a master program spec before implementation splitting',
253
+ 'split clarification specs for domain, contract, and policy unknowns first',
254
+ 'wait for stable executable tasks before routing child specs into implementation execution'
255
+ ];
256
+ }
257
+
258
+ return [
259
+ 'create a coordinated multi-Spec portfolio with explicit dependencies',
260
+ 'use orchestrate or close-loop-program instead of forcing one oversized Spec',
261
+ 'keep verification and integration contract tracks explicit across child specs'
262
+ ];
263
+ }
264
+
265
+ function readJsonSafely(filePath, fileSystem) {
266
+ return fileSystem.readJson(filePath).catch(() => null);
267
+ }
268
+
269
+ async function buildSpecEvidence(projectPath, specId, fileSystem = fs) {
270
+ const specRoot = path.join(projectPath, '.sce', 'specs', specId);
271
+ const paths = {
272
+ requirements: path.join(specRoot, 'requirements.md'),
273
+ design: path.join(specRoot, 'design.md'),
274
+ tasks: path.join(specRoot, 'tasks.md'),
275
+ domainMap: path.join(specRoot, DOMAIN_MAP_RELATIVE_PATH),
276
+ sceneSpec: path.join(specRoot, SCENE_SPEC_RELATIVE_PATH),
277
+ domainChain: path.join(specRoot, DOMAIN_CHAIN_RELATIVE_PATH),
278
+ problemContract: path.join(specRoot, PROBLEM_CONTRACT_RELATIVE_PATH)
279
+ };
280
+
281
+ const contents = {};
282
+ for (const [key, filePath] of Object.entries(paths)) {
283
+ if (await fileSystem.pathExists(filePath)) {
284
+ contents[key] = await fileSystem.readFile(filePath, 'utf8');
285
+ } else {
286
+ contents[key] = '';
287
+ }
288
+ }
289
+
290
+ const domainChainPayload = await fileSystem.pathExists(paths.domainChain)
291
+ ? await readJsonSafely(paths.domainChain, fileSystem)
292
+ : null;
293
+ const problemContractPayload = await fileSystem.pathExists(paths.problemContract)
294
+ ? await readJsonSafely(paths.problemContract, fileSystem)
295
+ : null;
296
+ const coverage = await analyzeSpecDomainCoverage(projectPath, specId, fileSystem).catch(() => null);
297
+
298
+ return {
299
+ specRoot,
300
+ paths,
301
+ contents,
302
+ domainChainPayload,
303
+ problemContractPayload,
304
+ coverage
305
+ };
306
+ }
307
+
308
+ function computeGoalDimensions(goal) {
309
+ const semantic = analyzeGoalSemantics(goal);
310
+ const uncertaintyHits = countPatternHits(goal, UNCERTAINTY_PATTERNS);
311
+ const contractHits = countPatternHits(goal, CONTRACT_PATTERNS);
312
+ const policyHits = countPatternHits(goal, POLICY_PATTERNS);
313
+ const dependencyHits = countPatternHits(goal, DEPENDENCY_PATTERNS);
314
+ const ownershipHits = countPatternHits(goal, OWNERSHIP_PATTERNS);
315
+ const verificationHits = countPatternHits(goal, VERIFICATION_PATTERNS);
316
+ const roleHits = countPatternHits(goal, ROLE_PATTERNS);
317
+ const clauseCount = semantic.clauses.length || 1;
318
+ const activeCategories = Object.values(semantic.categoryScores).filter((value) => value > 0).length;
319
+
320
+ return [
321
+ createDimension(
322
+ 'scene_span',
323
+ 1 + Math.min(4, roleHits + (clauseCount >= 4 ? 1 : 0)),
324
+ roleHits >= 2 || clauseCount >= 4
325
+ ? 'problem spans multiple scene/role concerns instead of one narrow execution path'
326
+ : 'problem appears to stay within one narrow scene path',
327
+ [`clauses=${clauseCount}`, `role_hits=${roleHits}`]
328
+ ),
329
+ createDimension(
330
+ 'domain_span',
331
+ 1 + Math.min(4, activeCategories + (clauseCount >= 3 ? 1 : 0)),
332
+ activeCategories >= 3 || clauseCount >= 3
333
+ ? 'goal already implies multiple implementation or domain tracks'
334
+ : 'goal scope is still narrow enough for one bounded context',
335
+ [`active_categories=${activeCategories}`, `clauses=${clauseCount}`]
336
+ ),
337
+ createDimension(
338
+ 'contract_clarity',
339
+ 1 + Math.min(4, (contractHits > 0 ? 1 : 0) + uncertaintyHits),
340
+ contractHits > 0 && uncertaintyHits > 0
341
+ ? 'API/interface contract concerns exist but remain unclear'
342
+ : contractHits > 0
343
+ ? 'contract constraints exist and should be made explicit'
344
+ : 'goal does not currently show strong contract ambiguity',
345
+ [`contract_hits=${contractHits}`, `uncertainty_hits=${uncertaintyHits}`]
346
+ ),
347
+ createDimension(
348
+ 'policy_clarity',
349
+ 1 + Math.min(4, (policyHits > 0 ? 1 : 0) + (uncertaintyHits > 1 ? 1 : 0)),
350
+ policyHits > 0 && uncertaintyHits > 0
351
+ ? 'business rules or policy constraints are mentioned but not yet stable'
352
+ : policyHits > 0
353
+ ? 'policy or rule constraints are present and should be clarified before execution'
354
+ : 'policy scope does not dominate this goal',
355
+ [`policy_hits=${policyHits}`, `uncertainty_hits=${uncertaintyHits}`]
356
+ ),
357
+ createDimension(
358
+ 'dependency_entanglement',
359
+ 1 + Math.min(4, dependencyHits + (clauseCount >= 3 ? 1 : 0)),
360
+ dependencyHits >= 2 || clauseCount >= 3
361
+ ? 'goal depends on multiple subsystems or coordinated tracks'
362
+ : 'dependency surface looks limited',
363
+ [`dependency_hits=${dependencyHits}`, `clauses=${clauseCount}`]
364
+ ),
365
+ createDimension(
366
+ 'ownership_clarity',
367
+ 1 + Math.min(4, (ownershipHits > 0 ? 1 : 0) + (roleHits >= 2 ? 1 : 0) + (uncertaintyHits > 0 ? 1 : 0)),
368
+ ownershipHits > 0 && uncertaintyHits > 0
369
+ ? 'responsibility boundaries are present but not yet clear'
370
+ : roleHits >= 2
371
+ ? 'multiple roles imply cross-owner coordination'
372
+ : 'ownership boundary appears relatively clear',
373
+ [`ownership_hits=${ownershipHits}`, `role_hits=${roleHits}`]
374
+ ),
375
+ createDimension(
376
+ 'verification_readiness',
377
+ verificationHits > 0 ? 2 : (clauseCount >= 3 ? 4 : 3),
378
+ verificationHits > 0
379
+ ? 'goal already mentions verification or acceptance signals'
380
+ : 'verification path is not yet explicit for this goal',
381
+ [`verification_hits=${verificationHits}`]
382
+ ),
383
+ createDimension(
384
+ 'decomposition_stability',
385
+ 1 + Math.min(4, (clauseCount >= 3 ? 1 : 0) + activeCategories + uncertaintyHits),
386
+ uncertaintyHits > 0 || clauseCount >= 4
387
+ ? 'direct task breakdown is likely unstable until the goal is disentangled'
388
+ : 'direct task breakdown should be relatively stable',
389
+ [`clauses=${clauseCount}`, `uncertainty_hits=${uncertaintyHits}`, `active_categories=${activeCategories}`]
390
+ )
391
+ ];
392
+ }
393
+
394
+ function computeSpecDimensions(specId, evidence) {
395
+ const corpus = [
396
+ evidence.contents.requirements,
397
+ evidence.contents.design,
398
+ evidence.contents.tasks,
399
+ evidence.contents.sceneSpec,
400
+ evidence.contents.domainMap
401
+ ].filter(Boolean).join('\n');
402
+ const uncertaintyHits = countPatternHits(corpus, UNCERTAINTY_PATTERNS);
403
+ const contractHits = countPatternHits(corpus, CONTRACT_PATTERNS);
404
+ const policyHits = countPatternHits(corpus, POLICY_PATTERNS);
405
+ const dependencyHits = countPatternHits(corpus, DEPENDENCY_PATTERNS);
406
+ const ownershipHits = countPatternHits(corpus, OWNERSHIP_PATTERNS);
407
+ const verificationHits = countPatternHits(corpus, VERIFICATION_PATTERNS);
408
+ const roleHits = countPatternHits(corpus, ROLE_PATTERNS);
409
+ const taskCount = countChecklistItems(evidence.contents.tasks || '');
410
+ const coverage = evidence.coverage;
411
+ const coverageRatio = coverage ? Number(coverage.coverage_ratio || 0) : 0;
412
+ const uncovered = coverage && Array.isArray(coverage.uncovered) ? coverage.uncovered : [];
413
+ const ontology = evidence.domainChainPayload && typeof evidence.domainChainPayload === 'object'
414
+ ? evidence.domainChainPayload.ontology || {}
415
+ : {};
416
+ const ontologyBreadth = ['entity', 'relation', 'business_rule', 'decision_policy', 'execution_flow']
417
+ .reduce((sum, key) => {
418
+ const value = Array.isArray(ontology[key]) ? ontology[key].length : 0;
419
+ return sum + value;
420
+ }, 0);
421
+
422
+ return [
423
+ createDimension(
424
+ 'scene_span',
425
+ 1 + Math.min(4, (roleHits >= 3 ? 2 : roleHits >= 1 ? 1 : 0) + (dependencyHits >= 3 ? 1 : 0)),
426
+ roleHits >= 2
427
+ ? `Spec ${specId} appears to cover multiple user/owner perspectives`
428
+ : `Spec ${specId} still looks centered on one primary scene`,
429
+ [`role_hits=${roleHits}`]
430
+ ),
431
+ createDimension(
432
+ 'domain_span',
433
+ 1 + Math.min(4, Math.ceil(ontologyBreadth / 3)),
434
+ ontologyBreadth >= 8
435
+ ? `Spec ${specId} already carries a broad ontology/problem surface`
436
+ : `Spec ${specId} keeps a relatively bounded ontology surface`,
437
+ [`ontology_breadth=${ontologyBreadth}`]
438
+ ),
439
+ createDimension(
440
+ 'contract_clarity',
441
+ 1 + Math.min(4, (contractHits > 0 ? 1 : 0) + (uncertaintyHits > 0 ? 1 : 0) + (uncovered.includes('relation') ? 1 : 0)),
442
+ contractHits > 0 && (uncertaintyHits > 0 || uncovered.includes('relation'))
443
+ ? `Spec ${specId} still has unresolved contract/interface ambiguity`
444
+ : `Spec ${specId} does not currently show severe contract ambiguity`,
445
+ [`contract_hits=${contractHits}`, `uncertainty_hits=${uncertaintyHits}`]
446
+ ),
447
+ createDimension(
448
+ 'policy_clarity',
449
+ 1 + Math.min(4, (policyHits > 0 ? 1 : 0) + (uncovered.includes('business_rule') ? 1 : 0) + (uncovered.includes('decision_policy') ? 1 : 0)),
450
+ policyHits > 0 && (uncovered.includes('business_rule') || uncovered.includes('decision_policy'))
451
+ ? `Spec ${specId} still has unresolved business-rule or decision-policy gaps`
452
+ : `Spec ${specId} has manageable policy scope`,
453
+ [`policy_hits=${policyHits}`, `uncovered=${uncovered.filter((item) => item === 'business_rule' || item === 'decision_policy').join('|') || 'none'}`]
454
+ ),
455
+ createDimension(
456
+ 'dependency_entanglement',
457
+ 1 + Math.min(4, dependencyHits + (ontologyBreadth >= 8 ? 1 : 0)),
458
+ dependencyHits >= 2 || ontologyBreadth >= 8
459
+ ? `Spec ${specId} shows significant dependency coordination pressure`
460
+ : `Spec ${specId} dependency surface is still manageable`,
461
+ [`dependency_hits=${dependencyHits}`, `ontology_breadth=${ontologyBreadth}`]
462
+ ),
463
+ createDimension(
464
+ 'ownership_clarity',
465
+ 1 + Math.min(4, (ownershipHits > 0 ? 1 : 0) + (roleHits >= 2 ? 1 : 0) + (uncertaintyHits > 1 ? 1 : 0)),
466
+ roleHits >= 2 || (ownershipHits > 0 && uncertaintyHits > 0)
467
+ ? `Spec ${specId} likely needs clearer ownership boundaries`
468
+ : `Spec ${specId} ownership boundary appears acceptable`,
469
+ [`ownership_hits=${ownershipHits}`, `role_hits=${roleHits}`]
470
+ ),
471
+ createDimension(
472
+ 'verification_readiness',
473
+ clampScore(5 - (coverageRatio * 4) + (verificationHits > 0 ? -1 : 0)),
474
+ coverageRatio < 0.6
475
+ ? `Spec ${specId} lacks enough domain/verification evidence for stable execution`
476
+ : `Spec ${specId} has a workable verification baseline`,
477
+ [`coverage_ratio=${coverageRatio.toFixed(2)}`, `verification_hits=${verificationHits}`]
478
+ ),
479
+ createDimension(
480
+ 'decomposition_stability',
481
+ taskCount === 0
482
+ ? 5
483
+ : clampScore(1 + (uncertaintyHits > 1 ? 2 : uncertaintyHits > 0 ? 1 : 0) + (taskCount < 3 ? 1 : 0) + (coverageRatio < 0.6 ? 1 : 0)),
484
+ taskCount === 0
485
+ ? `Spec ${specId} has no executable task breakdown yet`
486
+ : uncertaintyHits > 0 || coverageRatio < 0.6
487
+ ? `Spec ${specId} task breakdown is likely unstable without more clarification`
488
+ : `Spec ${specId} task breakdown is reasonably stable`,
489
+ [`task_count=${taskCount}`, `uncertainty_hits=${uncertaintyHits}`, `coverage_ratio=${coverageRatio.toFixed(2)}`]
490
+ )
491
+ ];
492
+ }
493
+
494
+ function decideStrategy(dimensions = []) {
495
+ const byKey = Object.fromEntries(dimensions.map((item) => [item.key, item]));
496
+ const clarityPressure = average([
497
+ byKey.contract_clarity && byKey.contract_clarity.score,
498
+ byKey.policy_clarity && byKey.policy_clarity.score,
499
+ byKey.ownership_clarity && byKey.ownership_clarity.score,
500
+ byKey.verification_readiness && byKey.verification_readiness.score,
501
+ byKey.decomposition_stability && byKey.decomposition_stability.score
502
+ ]);
503
+ const breadthPressure = average([
504
+ byKey.scene_span && byKey.scene_span.score,
505
+ byKey.domain_span && byKey.domain_span.score,
506
+ byKey.dependency_entanglement && byKey.dependency_entanglement.score
507
+ ]);
508
+ const highest = [...dimensions].sort((left, right) => right.score - left.score)[0] || null;
509
+ const criticalClarificationCount = [
510
+ byKey.contract_clarity && byKey.contract_clarity.score,
511
+ byKey.policy_clarity && byKey.policy_clarity.score,
512
+ byKey.ownership_clarity && byKey.ownership_clarity.score,
513
+ byKey.verification_readiness && byKey.verification_readiness.score,
514
+ byKey.decomposition_stability && byKey.decomposition_stability.score
515
+ ].filter((value) => Number(value) >= 4).length;
516
+
517
+ if (
518
+ (clarityPressure >= 3.6 && breadthPressure >= 2.5)
519
+ || (clarityPressure >= 3.6 && criticalClarificationCount >= 3)
520
+ ) {
521
+ return {
522
+ decision: 'research-program',
523
+ reason: 'problem breadth is high and core clarification dimensions are still unstable',
524
+ clarity_pressure: clarityPressure,
525
+ breadth_pressure: breadthPressure,
526
+ highest_dimension: highest ? highest.key : null
527
+ };
528
+ }
529
+
530
+ if (
531
+ breadthPressure >= 3
532
+ || average(dimensions.map((item) => item.score)) >= 3.1
533
+ || (highest && highest.score >= 5)
534
+ ) {
535
+ return {
536
+ decision: 'multi-spec-program',
537
+ reason: 'problem is broad enough that coordinated multi-Spec execution is safer than one oversized Spec',
538
+ clarity_pressure: clarityPressure,
539
+ breadth_pressure: breadthPressure,
540
+ highest_dimension: highest ? highest.key : null
541
+ };
542
+ }
543
+
544
+ return {
545
+ decision: 'single-spec',
546
+ reason: 'problem still fits one Spec with manageable complexity and clarification pressure',
547
+ clarity_pressure: clarityPressure,
548
+ breadth_pressure: breadthPressure,
549
+ highest_dimension: highest ? highest.key : null
550
+ };
551
+ }
552
+
553
+ function buildPayload(source, dimensions, decisionInfo) {
554
+ const topology = selectTopology(decisionInfo.decision, dimensions);
555
+ return {
556
+ mode: 'spec-strategy-assess',
557
+ generated_at: new Date().toISOString(),
558
+ source,
559
+ summary: {
560
+ assessment_source: source.type,
561
+ decision: decisionInfo.decision,
562
+ single_spec_fit: decisionInfo.decision === 'single-spec',
563
+ highest_pressure_dimension: decisionInfo.highest_dimension,
564
+ reason_count: listHighPressureSignals(dimensions).length,
565
+ recommended_program_specs: recommendProgramSpecCount(decisionInfo.decision, dimensions)
566
+ },
567
+ decision: decisionInfo.decision,
568
+ decision_reason: decisionInfo.reason,
569
+ pressures: {
570
+ clarity: Number(decisionInfo.clarity_pressure.toFixed(2)),
571
+ breadth: Number(decisionInfo.breadth_pressure.toFixed(2))
572
+ },
573
+ dimensions,
574
+ signals: listHighPressureSignals(dimensions),
575
+ recommended_topology: topology,
576
+ next_actions: buildNextActions(decisionInfo.decision, source.type, source.id || null)
577
+ };
578
+ }
579
+
580
+ async function assessComplexityStrategy(options = {}, dependencies = {}) {
581
+ const goal = normalizeText(options.goal);
582
+ const specId = normalizeText(options.spec);
583
+ if (!goal && !specId) {
584
+ throw new Error('One selector is required: --goal or --spec');
585
+ }
586
+ if (goal && specId) {
587
+ throw new Error('Use either --goal or --spec, not both');
588
+ }
589
+
590
+ if (goal) {
591
+ const dimensions = computeGoalDimensions(goal);
592
+ const decisionInfo = decideStrategy(dimensions);
593
+ return buildPayload({
594
+ type: 'goal',
595
+ goal
596
+ }, dimensions, decisionInfo);
597
+ }
598
+
599
+ const projectPath = dependencies.projectPath || process.cwd();
600
+ const fileSystem = dependencies.fileSystem || fs;
601
+ const specRoot = path.join(projectPath, '.sce', 'specs', specId);
602
+ if (!await fileSystem.pathExists(specRoot)) {
603
+ throw new Error(`Spec not found: ${specId}`);
604
+ }
605
+ const evidence = await buildSpecEvidence(projectPath, specId, fileSystem);
606
+ const dimensions = computeSpecDimensions(specId, evidence);
607
+ const decisionInfo = decideStrategy(dimensions);
608
+ const payload = buildPayload({
609
+ type: 'spec',
610
+ id: specId,
611
+ spec_root: specRoot,
612
+ evidence_files: [
613
+ path.join(specRoot, 'requirements.md'),
614
+ path.join(specRoot, 'design.md'),
615
+ path.join(specRoot, 'tasks.md'),
616
+ path.join(specRoot, DOMAIN_MAP_RELATIVE_PATH),
617
+ path.join(specRoot, SCENE_SPEC_RELATIVE_PATH),
618
+ path.join(specRoot, DOMAIN_CHAIN_RELATIVE_PATH),
619
+ path.join(specRoot, PROBLEM_CONTRACT_RELATIVE_PATH)
620
+ ]
621
+ }, dimensions, decisionInfo);
622
+ if (evidence.coverage) {
623
+ payload.spec_coverage = {
624
+ passed: evidence.coverage.passed,
625
+ coverage_ratio: evidence.coverage.coverage_ratio,
626
+ covered_count: evidence.coverage.covered_count,
627
+ total_count: evidence.coverage.total_count,
628
+ uncovered: evidence.coverage.uncovered
629
+ };
630
+ }
631
+ return payload;
632
+ }
633
+
634
+ module.exports = {
635
+ assessComplexityStrategy
636
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scene-capability-engine",
3
- "version": "3.6.55",
3
+ "version": "3.6.57",
4
4
  "description": "SCE (Scene Capability Engine) - A CLI tool and npm package for spec-driven development with AI coding assistants.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -243,6 +243,6 @@ A Spec is a complete feature definition with three parts:
243
243
  ---
244
244
 
245
245
  **Project Type**: Spec-driven development
246
- **sce Version**: 3.6.55
247
- **Last Updated**: 2026-03-16
246
+ **sce Version**: 3.6.57
247
+ **Last Updated**: 2026-03-17
248
248
  **Purpose**: Guide AI tools to work effectively with this project