scene-capability-engine 3.4.6 → 3.5.1
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 +35 -0
- package/README.md +8 -2
- package/README.zh.md +8 -2
- package/docs/command-reference.md +53 -1
- package/lib/adoption/adoption-strategy.js +1 -0
- package/lib/adoption/detection-engine.js +1 -0
- package/lib/adoption/file-classifier.js +2 -1
- package/lib/adoption/smart-orchestrator.js +1 -0
- package/lib/commands/studio.js +334 -18
- package/lib/spec/related-specs.js +10 -2
- package/lib/spec/scene-binding-overrides.js +115 -0
- package/lib/studio/spec-intake-governor.js +1315 -0
- package/lib/workspace/takeover-baseline.js +81 -0
- package/package.json +1 -1
- package/template/.sce/config/studio-intake-policy.json +154 -0
- package/template/.sce/config/takeover-baseline.json +88 -1
package/lib/commands/studio.js
CHANGED
|
@@ -11,6 +11,12 @@ const {
|
|
|
11
11
|
const { findRelatedSpecs } = require('../spec/related-specs');
|
|
12
12
|
const { captureTimelineCheckpoint } = require('../runtime/project-timeline');
|
|
13
13
|
const { runProblemEvaluation } = require('../problem/problem-evaluator');
|
|
14
|
+
const {
|
|
15
|
+
loadStudioIntakePolicy,
|
|
16
|
+
runStudioAutoIntake,
|
|
17
|
+
runStudioSpecGovernance,
|
|
18
|
+
runStudioSceneBackfill
|
|
19
|
+
} = require('../studio/spec-intake-governor');
|
|
14
20
|
|
|
15
21
|
const STUDIO_JOB_API_VERSION = 'sce.studio.job/v0.1';
|
|
16
22
|
const STAGE_ORDER = ['plan', 'generate', 'apply', 'verify', 'release'];
|
|
@@ -1343,6 +1349,9 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1343
1349
|
const fromChat = normalizeString(options.fromChat);
|
|
1344
1350
|
const sceneId = normalizeString(options.scene);
|
|
1345
1351
|
const specId = normalizeString(options.spec);
|
|
1352
|
+
const goal = normalizeString(options.goal);
|
|
1353
|
+
const manualSpecMode = options.manualSpec === true;
|
|
1354
|
+
const skipSpecGovernance = options.specGovernance === false;
|
|
1346
1355
|
|
|
1347
1356
|
if (!fromChat) {
|
|
1348
1357
|
throw new Error('--from-chat is required');
|
|
@@ -1350,16 +1359,32 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1350
1359
|
if (!sceneId) {
|
|
1351
1360
|
throw new Error('--scene is required');
|
|
1352
1361
|
}
|
|
1353
|
-
|
|
1362
|
+
|
|
1363
|
+
const intakePolicyBundle = await loadStudioIntakePolicy(projectPath, fileSystem);
|
|
1364
|
+
const intakePolicy = intakePolicyBundle.policy || {};
|
|
1365
|
+
const governancePolicy = intakePolicy.governance || {};
|
|
1366
|
+
if (manualSpecMode && intakePolicy.allow_manual_spec_override !== true) {
|
|
1367
|
+
throw new Error(
|
|
1368
|
+
'--manual-spec is disabled by studio intake policy (allow_manual_spec_override=false)'
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
if (skipSpecGovernance && governancePolicy.require_auto_on_plan !== false) {
|
|
1372
|
+
throw new Error(
|
|
1373
|
+
'--no-spec-governance is disabled by studio intake policy (governance.require_auto_on_plan=true)'
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
let domainChainBinding = await resolveDomainChainBinding({
|
|
1354
1378
|
sceneId,
|
|
1355
1379
|
specId,
|
|
1356
|
-
goal
|
|
1380
|
+
goal
|
|
1357
1381
|
}, {
|
|
1358
1382
|
projectPath,
|
|
1359
1383
|
fileSystem
|
|
1360
1384
|
});
|
|
1361
|
-
|
|
1362
|
-
|
|
1385
|
+
|
|
1386
|
+
let relatedSpecLookup = await findRelatedSpecs({
|
|
1387
|
+
query: goal,
|
|
1363
1388
|
sceneId,
|
|
1364
1389
|
limit: 8,
|
|
1365
1390
|
excludeSpecId: domainChainBinding.spec_id || specId || null
|
|
@@ -1367,6 +1392,45 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1367
1392
|
projectPath,
|
|
1368
1393
|
fileSystem
|
|
1369
1394
|
});
|
|
1395
|
+
|
|
1396
|
+
const intake = await runStudioAutoIntake({
|
|
1397
|
+
scene_id: sceneId,
|
|
1398
|
+
from_chat: fromChat,
|
|
1399
|
+
goal,
|
|
1400
|
+
explicit_spec_id: specId,
|
|
1401
|
+
domain_chain_binding: domainChainBinding,
|
|
1402
|
+
related_specs: relatedSpecLookup,
|
|
1403
|
+
apply: !manualSpecMode,
|
|
1404
|
+
skip: manualSpecMode
|
|
1405
|
+
}, {
|
|
1406
|
+
projectPath,
|
|
1407
|
+
fileSystem
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
const intakeSpecId = normalizeString(intake && intake.selected_spec_id);
|
|
1411
|
+
const effectiveSpecId = intakeSpecId || normalizeString(domainChainBinding.spec_id) || specId || null;
|
|
1412
|
+
|
|
1413
|
+
if (effectiveSpecId && effectiveSpecId !== normalizeString(domainChainBinding.spec_id)) {
|
|
1414
|
+
domainChainBinding = await resolveDomainChainBinding({
|
|
1415
|
+
sceneId,
|
|
1416
|
+
specId: effectiveSpecId,
|
|
1417
|
+
goal
|
|
1418
|
+
}, {
|
|
1419
|
+
projectPath,
|
|
1420
|
+
fileSystem
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
relatedSpecLookup = await findRelatedSpecs({
|
|
1425
|
+
query: goal,
|
|
1426
|
+
sceneId,
|
|
1427
|
+
limit: 8,
|
|
1428
|
+
excludeSpecId: effectiveSpecId || null
|
|
1429
|
+
}, {
|
|
1430
|
+
projectPath,
|
|
1431
|
+
fileSystem
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1370
1434
|
const relatedSpecItems = Array.isArray(relatedSpecLookup.related_specs)
|
|
1371
1435
|
? relatedSpecLookup.related_specs.map((item) => ({
|
|
1372
1436
|
spec_id: item.spec_id,
|
|
@@ -1387,7 +1451,7 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1387
1451
|
domainChainBinding.problem_contract || {},
|
|
1388
1452
|
{
|
|
1389
1453
|
scene_id: sceneId,
|
|
1390
|
-
goal
|
|
1454
|
+
goal,
|
|
1391
1455
|
problem_statement: normalizeString(domainChainBinding?.summary?.problem_statement),
|
|
1392
1456
|
verification_plan: normalizeString(domainChainBinding?.summary?.verification_plan)
|
|
1393
1457
|
}
|
|
@@ -1396,11 +1460,11 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1396
1460
|
job_id: jobId,
|
|
1397
1461
|
scene: {
|
|
1398
1462
|
id: sceneId,
|
|
1399
|
-
spec_id:
|
|
1463
|
+
spec_id: effectiveSpecId
|
|
1400
1464
|
},
|
|
1401
1465
|
source: {
|
|
1402
|
-
goal:
|
|
1403
|
-
spec_id:
|
|
1466
|
+
goal: goal || null,
|
|
1467
|
+
spec_id: effectiveSpecId,
|
|
1404
1468
|
problem_contract: problemContract,
|
|
1405
1469
|
problem_contract_path: domainChainBinding.problem_contract_path || null,
|
|
1406
1470
|
domain_chain: {
|
|
@@ -1415,8 +1479,8 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1415
1479
|
};
|
|
1416
1480
|
const planProblemEvaluation = await enforceProblemEvaluationForStage(planShadowJob, 'plan', {
|
|
1417
1481
|
scene_id: sceneId,
|
|
1418
|
-
spec_id:
|
|
1419
|
-
goal:
|
|
1482
|
+
spec_id: effectiveSpecId,
|
|
1483
|
+
goal: goal || null,
|
|
1420
1484
|
problem_contract: problemContract,
|
|
1421
1485
|
domain_chain: {
|
|
1422
1486
|
resolved: domainChainBinding.resolved === true,
|
|
@@ -1444,16 +1508,35 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1444
1508
|
const sessionStore = dependencies.sessionStore || new SessionStore(projectPath);
|
|
1445
1509
|
const sceneSessionBinding = await sessionStore.beginSceneSession({
|
|
1446
1510
|
sceneId,
|
|
1447
|
-
objective:
|
|
1511
|
+
objective: goal || `Studio scene cycle for ${sceneId}`,
|
|
1448
1512
|
tool: normalizeString(options.tool) || 'generic'
|
|
1449
1513
|
});
|
|
1514
|
+
|
|
1515
|
+
let governanceSnapshot = null;
|
|
1516
|
+
let governanceWarning = '';
|
|
1517
|
+
const autoRunGovernance = !(skipSpecGovernance)
|
|
1518
|
+
&& (!intake || !intake.policy || !intake.policy.governance || intake.policy.governance.auto_run_on_plan !== false);
|
|
1519
|
+
if (autoRunGovernance) {
|
|
1520
|
+
try {
|
|
1521
|
+
governanceSnapshot = await runStudioSpecGovernance({
|
|
1522
|
+
apply: true,
|
|
1523
|
+
scene: sceneId
|
|
1524
|
+
}, {
|
|
1525
|
+
projectPath,
|
|
1526
|
+
fileSystem
|
|
1527
|
+
});
|
|
1528
|
+
} catch (error) {
|
|
1529
|
+
governanceWarning = normalizeString(error && error.message);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1450
1533
|
stages.plan = {
|
|
1451
1534
|
status: 'completed',
|
|
1452
1535
|
completed_at: now,
|
|
1453
1536
|
metadata: {
|
|
1454
1537
|
from_chat: fromChat,
|
|
1455
1538
|
scene_id: sceneId,
|
|
1456
|
-
spec_id:
|
|
1539
|
+
spec_id: effectiveSpecId,
|
|
1457
1540
|
scene_session_id: sceneSessionBinding.session.session_id,
|
|
1458
1541
|
scene_cycle: sceneSessionBinding.scene_cycle,
|
|
1459
1542
|
domain_chain_resolved: domainChainBinding.resolved === true,
|
|
@@ -1463,7 +1546,18 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1463
1546
|
domain_chain_summary: domainChainBinding.summary || null,
|
|
1464
1547
|
domain_chain_reason: domainChainBinding.reason || null,
|
|
1465
1548
|
problem_contract: problemContract,
|
|
1549
|
+
intake: intake ? {
|
|
1550
|
+
enabled: intake.enabled === true,
|
|
1551
|
+
intent_type: intake.intent ? intake.intent.intent_type : null,
|
|
1552
|
+
decision_action: intake.decision ? intake.decision.action : null,
|
|
1553
|
+
decision_reason: intake.decision ? intake.decision.reason : null,
|
|
1554
|
+
selected_spec_id: intake.selected_spec_id || effectiveSpecId || null,
|
|
1555
|
+
created_spec_id: intake.created_spec && intake.created_spec.created ? intake.created_spec.spec_id : null,
|
|
1556
|
+
policy_path: intake.policy_path || null
|
|
1557
|
+
} : null,
|
|
1466
1558
|
problem_evaluation: summarizeProblemEvaluation(planProblemEvaluation),
|
|
1559
|
+
spec_governance: governanceSnapshot ? governanceSnapshot.summary : null,
|
|
1560
|
+
spec_governance_warning: governanceWarning || null,
|
|
1467
1561
|
related_specs_total: Number(relatedSpecLookup.total_candidates || 0),
|
|
1468
1562
|
related_specs_top: relatedSpecItems
|
|
1469
1563
|
}
|
|
@@ -1477,15 +1571,24 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1477
1571
|
status: 'planned',
|
|
1478
1572
|
source: {
|
|
1479
1573
|
from_chat: fromChat,
|
|
1480
|
-
goal:
|
|
1481
|
-
spec_id:
|
|
1574
|
+
goal: goal || null,
|
|
1575
|
+
spec_id: effectiveSpecId,
|
|
1482
1576
|
problem_contract: problemContract,
|
|
1483
1577
|
problem_contract_path: domainChainBinding.problem_contract_path || null,
|
|
1578
|
+
intake: intake ? {
|
|
1579
|
+
enabled: intake.enabled === true,
|
|
1580
|
+
policy_path: intake.policy_path || null,
|
|
1581
|
+
policy_loaded_from: intake.policy_loaded_from || null,
|
|
1582
|
+
intent: intake.intent || null,
|
|
1583
|
+
decision: intake.decision || null,
|
|
1584
|
+
selected_spec_id: intake.selected_spec_id || effectiveSpecId || null,
|
|
1585
|
+
created_spec: intake.created_spec || null
|
|
1586
|
+
} : null,
|
|
1484
1587
|
domain_chain: {
|
|
1485
1588
|
resolved: domainChainBinding.resolved === true,
|
|
1486
1589
|
source: domainChainBinding.source || 'none',
|
|
1487
1590
|
reason: domainChainBinding.reason || null,
|
|
1488
|
-
spec_id: domainChainBinding.spec_id || null,
|
|
1591
|
+
spec_id: effectiveSpecId || domainChainBinding.spec_id || null,
|
|
1489
1592
|
chain_path: domainChainBinding.chain_path || null,
|
|
1490
1593
|
candidate_count: Number.isFinite(Number(domainChainBinding.candidate_count))
|
|
1491
1594
|
? Number(domainChainBinding.candidate_count)
|
|
@@ -1500,11 +1603,20 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1500
1603
|
scene_id: relatedSpecLookup.scene_id || null,
|
|
1501
1604
|
total_candidates: Number(relatedSpecLookup.total_candidates || 0),
|
|
1502
1605
|
items: relatedSpecItems
|
|
1503
|
-
}
|
|
1606
|
+
},
|
|
1607
|
+
spec_governance: governanceSnapshot
|
|
1608
|
+
? {
|
|
1609
|
+
status: governanceSnapshot.summary ? governanceSnapshot.summary.status : null,
|
|
1610
|
+
alert_count: governanceSnapshot.summary ? Number(governanceSnapshot.summary.alert_count || 0) : 0,
|
|
1611
|
+
report_file: governanceSnapshot.report_file || null,
|
|
1612
|
+
scene_index_file: governanceSnapshot.scene_index_file || null
|
|
1613
|
+
}
|
|
1614
|
+
: null,
|
|
1615
|
+
spec_governance_warning: governanceWarning || null
|
|
1504
1616
|
},
|
|
1505
1617
|
scene: {
|
|
1506
1618
|
id: sceneId,
|
|
1507
|
-
spec_id:
|
|
1619
|
+
spec_id: effectiveSpecId,
|
|
1508
1620
|
related_spec_ids: relatedSpecItems.map((item) => item.spec_id)
|
|
1509
1621
|
},
|
|
1510
1622
|
session: {
|
|
@@ -1520,6 +1632,12 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1520
1632
|
patch_bundle_id: null,
|
|
1521
1633
|
verify_report: null,
|
|
1522
1634
|
release_ref: null,
|
|
1635
|
+
spec_portfolio_report: governanceSnapshot && governanceSnapshot.report_file
|
|
1636
|
+
? governanceSnapshot.report_file
|
|
1637
|
+
: null,
|
|
1638
|
+
spec_scene_index: governanceSnapshot && governanceSnapshot.scene_index_file
|
|
1639
|
+
? governanceSnapshot.scene_index_file
|
|
1640
|
+
: null,
|
|
1523
1641
|
problem_eval_reports: {
|
|
1524
1642
|
plan: normalizeString(planProblemEvaluation.report_file) || null
|
|
1525
1643
|
}
|
|
@@ -1530,7 +1648,7 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1530
1648
|
await appendStudioEvent(paths, job, 'stage.plan.completed', {
|
|
1531
1649
|
from_chat: fromChat,
|
|
1532
1650
|
scene_id: sceneId,
|
|
1533
|
-
spec_id:
|
|
1651
|
+
spec_id: effectiveSpecId,
|
|
1534
1652
|
scene_session_id: sceneSessionBinding.session.session_id,
|
|
1535
1653
|
scene_cycle: sceneSessionBinding.scene_cycle,
|
|
1536
1654
|
target: job.target,
|
|
@@ -1539,13 +1657,27 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1539
1657
|
domain_chain_spec_id: domainChainBinding.spec_id || null,
|
|
1540
1658
|
domain_chain_path: domainChainBinding.chain_path || null,
|
|
1541
1659
|
problem_contract: problemContract,
|
|
1660
|
+
intake_action: intake && intake.decision ? intake.decision.action : null,
|
|
1661
|
+
intake_reason: intake && intake.decision ? intake.decision.reason : null,
|
|
1662
|
+
intake_selected_spec_id: intake ? intake.selected_spec_id || effectiveSpecId || null : effectiveSpecId,
|
|
1663
|
+
intake_created_spec_id: intake && intake.created_spec && intake.created_spec.created
|
|
1664
|
+
? intake.created_spec.spec_id
|
|
1665
|
+
: null,
|
|
1542
1666
|
problem_evaluation: summarizeProblemEvaluation(planProblemEvaluation),
|
|
1667
|
+
spec_governance: governanceSnapshot ? governanceSnapshot.summary : null,
|
|
1668
|
+
spec_governance_warning: governanceWarning || null,
|
|
1543
1669
|
related_specs_total: Number(relatedSpecLookup.total_candidates || 0),
|
|
1544
1670
|
related_spec_ids: relatedSpecItems.map((item) => item.spec_id)
|
|
1545
1671
|
}, fileSystem);
|
|
1546
1672
|
await writeLatestJob(paths, jobId, fileSystem);
|
|
1547
1673
|
|
|
1548
1674
|
const payload = buildCommandPayload('studio-plan', job);
|
|
1675
|
+
payload.scene = {
|
|
1676
|
+
id: sceneId,
|
|
1677
|
+
spec_id: effectiveSpecId
|
|
1678
|
+
};
|
|
1679
|
+
payload.intake = job.source && job.source.intake ? job.source.intake : null;
|
|
1680
|
+
payload.spec_governance = governanceSnapshot ? governanceSnapshot.summary : null;
|
|
1549
1681
|
printStudioPayload(payload, options);
|
|
1550
1682
|
return payload;
|
|
1551
1683
|
}
|
|
@@ -2178,6 +2310,152 @@ async function runStudioEventsCommand(options = {}, dependencies = {}) {
|
|
|
2178
2310
|
return payload;
|
|
2179
2311
|
}
|
|
2180
2312
|
|
|
2313
|
+
function printStudioIntakePayload(payload, options = {}) {
|
|
2314
|
+
if (options.json) {
|
|
2315
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
console.log(chalk.blue('Studio intake'));
|
|
2320
|
+
console.log(` Scene: ${payload.scene_id || 'n/a'}`);
|
|
2321
|
+
console.log(` Goal: ${payload.goal || '(empty)'}`);
|
|
2322
|
+
console.log(` Intent: ${payload.intent && payload.intent.intent_type ? payload.intent.intent_type : 'unknown'}`);
|
|
2323
|
+
console.log(` Decision: ${payload.decision && payload.decision.action ? payload.decision.action : 'none'}`);
|
|
2324
|
+
console.log(` Spec: ${payload.selected_spec_id || 'n/a'}`);
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
async function runStudioIntakeCommand(options = {}, dependencies = {}) {
|
|
2328
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
2329
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
2330
|
+
const sceneId = normalizeString(options.scene);
|
|
2331
|
+
const fromChat = normalizeString(options.fromChat);
|
|
2332
|
+
const goal = normalizeString(options.goal);
|
|
2333
|
+
const specId = normalizeString(options.spec);
|
|
2334
|
+
|
|
2335
|
+
if (!sceneId) {
|
|
2336
|
+
throw new Error('--scene is required');
|
|
2337
|
+
}
|
|
2338
|
+
if (!fromChat) {
|
|
2339
|
+
throw new Error('--from-chat is required');
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
const domainChainBinding = await resolveDomainChainBinding({
|
|
2343
|
+
sceneId,
|
|
2344
|
+
specId,
|
|
2345
|
+
goal
|
|
2346
|
+
}, {
|
|
2347
|
+
projectPath,
|
|
2348
|
+
fileSystem
|
|
2349
|
+
});
|
|
2350
|
+
|
|
2351
|
+
const relatedSpecLookup = await findRelatedSpecs({
|
|
2352
|
+
query: goal,
|
|
2353
|
+
sceneId,
|
|
2354
|
+
limit: 8,
|
|
2355
|
+
excludeSpecId: domainChainBinding.spec_id || specId || null
|
|
2356
|
+
}, {
|
|
2357
|
+
projectPath,
|
|
2358
|
+
fileSystem
|
|
2359
|
+
});
|
|
2360
|
+
|
|
2361
|
+
const intake = await runStudioAutoIntake({
|
|
2362
|
+
scene_id: sceneId,
|
|
2363
|
+
from_chat: fromChat,
|
|
2364
|
+
goal,
|
|
2365
|
+
explicit_spec_id: specId,
|
|
2366
|
+
domain_chain_binding: domainChainBinding,
|
|
2367
|
+
related_specs: relatedSpecLookup,
|
|
2368
|
+
apply: options.apply === true,
|
|
2369
|
+
skip: options.manualSpec === true
|
|
2370
|
+
}, {
|
|
2371
|
+
projectPath,
|
|
2372
|
+
fileSystem
|
|
2373
|
+
});
|
|
2374
|
+
|
|
2375
|
+
const payload = {
|
|
2376
|
+
...intake,
|
|
2377
|
+
domain_chain_source: domainChainBinding.source || 'none',
|
|
2378
|
+
domain_chain_spec_id: domainChainBinding.spec_id || null,
|
|
2379
|
+
related_specs_total: Number(relatedSpecLookup.total_candidates || 0)
|
|
2380
|
+
};
|
|
2381
|
+
printStudioIntakePayload(payload, options);
|
|
2382
|
+
return payload;
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
function printStudioPortfolioPayload(payload, options = {}) {
|
|
2386
|
+
if (options.json) {
|
|
2387
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
const summary = payload.summary || {};
|
|
2391
|
+
console.log(chalk.blue('Studio portfolio governance'));
|
|
2392
|
+
console.log(` Status: ${summary.status || 'unknown'}`);
|
|
2393
|
+
console.log(` Scenes: ${summary.scene_count || 0}`);
|
|
2394
|
+
console.log(` Specs: ${summary.total_specs || 0}`);
|
|
2395
|
+
console.log(` Active: ${summary.active_specs || 0}`);
|
|
2396
|
+
console.log(` Completed: ${summary.completed_specs || 0}`);
|
|
2397
|
+
console.log(` Stale: ${summary.stale_specs || 0}`);
|
|
2398
|
+
console.log(` Duplicate pairs: ${summary.duplicate_pairs || 0}`);
|
|
2399
|
+
console.log(` Overflow scenes: ${summary.overflow_scenes || 0}`);
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
function printStudioBackfillPayload(payload, options = {}) {
|
|
2403
|
+
if (options.json) {
|
|
2404
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
console.log(chalk.blue('Studio scene backfill'));
|
|
2408
|
+
console.log(` Source scene: ${payload.source_scene || 'scene.unassigned'}`);
|
|
2409
|
+
console.log(` Candidates: ${payload.summary ? payload.summary.candidate_count : 0}`);
|
|
2410
|
+
console.log(` Changed: ${payload.summary ? payload.summary.changed_count : 0}`);
|
|
2411
|
+
console.log(` Apply: ${payload.apply ? 'yes' : 'no'}`);
|
|
2412
|
+
console.log(` Override file: ${payload.override_file || 'n/a'}`);
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
async function runStudioPortfolioCommand(options = {}, dependencies = {}) {
|
|
2416
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
2417
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
2418
|
+
const payload = await runStudioSpecGovernance({
|
|
2419
|
+
scene: normalizeString(options.scene),
|
|
2420
|
+
apply: options.apply !== false
|
|
2421
|
+
}, {
|
|
2422
|
+
projectPath,
|
|
2423
|
+
fileSystem
|
|
2424
|
+
});
|
|
2425
|
+
|
|
2426
|
+
if (options.strict && payload.summary && Number(payload.summary.alert_count || 0) > 0) {
|
|
2427
|
+
throw new Error(
|
|
2428
|
+
`studio portfolio governance has alerts: ${payload.summary.alert_count} (duplicate/stale/overflow)`
|
|
2429
|
+
);
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
printStudioPortfolioPayload(payload, options);
|
|
2433
|
+
return payload;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
async function runStudioBackfillSpecScenesCommand(options = {}, dependencies = {}) {
|
|
2437
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
2438
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
2439
|
+
const backfillOptions = {
|
|
2440
|
+
scene: normalizeString(options.scene),
|
|
2441
|
+
all: options.all === true,
|
|
2442
|
+
limit: options.limit,
|
|
2443
|
+
apply: options.apply === true,
|
|
2444
|
+
refresh_governance: options.refreshGovernance !== false
|
|
2445
|
+
};
|
|
2446
|
+
if (options.activeOnly === true) {
|
|
2447
|
+
backfillOptions.active_only = true;
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
const payload = await runStudioSceneBackfill(backfillOptions, {
|
|
2451
|
+
projectPath,
|
|
2452
|
+
fileSystem
|
|
2453
|
+
});
|
|
2454
|
+
|
|
2455
|
+
printStudioBackfillPayload(payload, options);
|
|
2456
|
+
return payload;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2181
2459
|
async function runStudioCommand(handler, options, stageName = '') {
|
|
2182
2460
|
try {
|
|
2183
2461
|
const stage = normalizeString(stageName) || 'unknown';
|
|
@@ -2221,11 +2499,46 @@ function registerStudioCommands(program) {
|
|
|
2221
2499
|
.requiredOption('--from-chat <session>', 'Chat session identifier or transcript reference')
|
|
2222
2500
|
.option('--spec <spec-id>', 'Optional spec binding for domain-chain context ingestion')
|
|
2223
2501
|
.option('--goal <goal>', 'Optional goal summary')
|
|
2502
|
+
.option('--manual-spec', 'Legacy bypass flag (disabled by default policy)')
|
|
2224
2503
|
.option('--target <target>', 'Target integration profile', 'default')
|
|
2504
|
+
.option('--no-spec-governance', 'Legacy bypass flag (disabled by default policy)')
|
|
2225
2505
|
.option('--job <job-id>', 'Reuse an explicit studio job id')
|
|
2226
2506
|
.option('--json', 'Print machine-readable JSON output')
|
|
2227
2507
|
.action(async (options) => runStudioCommand(runStudioPlanCommand, options, 'plan'));
|
|
2228
2508
|
|
|
2509
|
+
studio
|
|
2510
|
+
.command('intake')
|
|
2511
|
+
.description('Analyze chat goal and auto-resolve spec binding/create decision')
|
|
2512
|
+
.requiredOption('--scene <scene-id>', 'Scene identifier')
|
|
2513
|
+
.requiredOption('--from-chat <session>', 'Chat session identifier or transcript reference')
|
|
2514
|
+
.option('--spec <spec-id>', 'Optional explicit spec id')
|
|
2515
|
+
.option('--goal <goal>', 'Goal text used for intent classification')
|
|
2516
|
+
.option('--apply', 'Create spec when decision is create_spec')
|
|
2517
|
+
.option('--manual-spec', 'Legacy bypass flag (disabled by default policy)')
|
|
2518
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
2519
|
+
.action(async (options) => runStudioCommand(runStudioIntakeCommand, options, 'intake'));
|
|
2520
|
+
|
|
2521
|
+
studio
|
|
2522
|
+
.command('portfolio')
|
|
2523
|
+
.description('Build scene-organized spec governance portfolio')
|
|
2524
|
+
.option('--scene <scene-id>', 'Optional scene filter')
|
|
2525
|
+
.option('--no-apply', 'Do not write portfolio/index artifacts to .sce/spec-governance/')
|
|
2526
|
+
.option('--strict', 'Fail when governance alerts are detected')
|
|
2527
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
2528
|
+
.action(async (options) => runStudioCommand(runStudioPortfolioCommand, options, 'portfolio'));
|
|
2529
|
+
|
|
2530
|
+
studio
|
|
2531
|
+
.command('backfill-spec-scenes')
|
|
2532
|
+
.description('Backfill scene bindings for historical specs (writes override mapping when --apply)')
|
|
2533
|
+
.option('--scene <scene-id>', 'Source scene filter (default: scene.unassigned)')
|
|
2534
|
+
.option('--all', 'Include completed/stale specs (default uses active-only policy)')
|
|
2535
|
+
.option('--active-only', 'Force active-only filtering')
|
|
2536
|
+
.option('--limit <n>', 'Maximum number of specs to process')
|
|
2537
|
+
.option('--apply', 'Write mapping to .sce/spec-governance/spec-scene-overrides.json')
|
|
2538
|
+
.option('--no-refresh-governance', 'Skip portfolio refresh after apply')
|
|
2539
|
+
.option('--json', 'Print machine-readable JSON output')
|
|
2540
|
+
.action(async (options) => runStudioCommand(runStudioBackfillSpecScenesCommand, options, 'backfill-spec-scenes'));
|
|
2541
|
+
|
|
2229
2542
|
studio
|
|
2230
2543
|
.command('generate')
|
|
2231
2544
|
.description('Generate patch bundle metadata for a planned studio job (scene inherited from plan)')
|
|
@@ -2310,12 +2623,15 @@ module.exports = {
|
|
|
2310
2623
|
resolveNextAction,
|
|
2311
2624
|
buildProgress,
|
|
2312
2625
|
runStudioPlanCommand,
|
|
2626
|
+
runStudioIntakeCommand,
|
|
2313
2627
|
runStudioGenerateCommand,
|
|
2314
2628
|
runStudioApplyCommand,
|
|
2315
2629
|
runStudioVerifyCommand,
|
|
2316
2630
|
runStudioReleaseCommand,
|
|
2317
2631
|
runStudioRollbackCommand,
|
|
2318
2632
|
runStudioEventsCommand,
|
|
2633
|
+
runStudioPortfolioCommand,
|
|
2634
|
+
runStudioBackfillSpecScenesCommand,
|
|
2319
2635
|
runStudioResumeCommand,
|
|
2320
2636
|
registerStudioCommands
|
|
2321
2637
|
};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const { DOMAIN_CHAIN_RELATIVE_PATH } = require('./domain-modeling');
|
|
4
|
+
const {
|
|
5
|
+
loadSceneBindingOverrides,
|
|
6
|
+
resolveSceneIdFromOverrides
|
|
7
|
+
} = require('./scene-binding-overrides');
|
|
4
8
|
|
|
5
9
|
function normalizeText(value) {
|
|
6
10
|
if (typeof value !== 'string') {
|
|
@@ -70,6 +74,8 @@ async function resolveSpecSearchEntries(projectPath, fileSystem = fs) {
|
|
|
70
74
|
return [];
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
const overrideContext = await loadSceneBindingOverrides(projectPath, {}, fileSystem);
|
|
78
|
+
const overrides = overrideContext.overrides;
|
|
73
79
|
const names = await fileSystem.readdir(specsRoot);
|
|
74
80
|
const entries = [];
|
|
75
81
|
|
|
@@ -106,7 +112,10 @@ async function resolveSpecSearchEntries(projectPath, fileSystem = fs) {
|
|
|
106
112
|
]);
|
|
107
113
|
|
|
108
114
|
const sceneId = normalizeText(
|
|
109
|
-
(domainChain && domainChain.scene_id)
|
|
115
|
+
(domainChain && domainChain.scene_id)
|
|
116
|
+
|| extractSceneIdFromSceneSpec(sceneSpecContent)
|
|
117
|
+
|| resolveSceneIdFromOverrides(specId, overrides)
|
|
118
|
+
|| ''
|
|
110
119
|
) || null;
|
|
111
120
|
const problemStatement = normalizeText(
|
|
112
121
|
(domainChain && domainChain.problem && domainChain.problem.statement) || ''
|
|
@@ -257,4 +266,3 @@ module.exports = {
|
|
|
257
266
|
calculateSpecRelevance,
|
|
258
267
|
findRelatedSpecs
|
|
259
268
|
};
|
|
260
|
-
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
|
|
4
|
+
const DEFAULT_SPEC_SCENE_OVERRIDE_PATH = '.sce/spec-governance/spec-scene-overrides.json';
|
|
5
|
+
|
|
6
|
+
function normalizeText(value) {
|
|
7
|
+
if (typeof value !== 'string') {
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
return value.trim();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeOverrideEntry(specId, payload = {}) {
|
|
14
|
+
const normalizedSpecId = normalizeText(specId);
|
|
15
|
+
if (!normalizedSpecId) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (typeof payload === 'string') {
|
|
20
|
+
const sceneId = normalizeText(payload);
|
|
21
|
+
if (!sceneId) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
spec_id: normalizedSpecId,
|
|
26
|
+
scene_id: sceneId,
|
|
27
|
+
source: 'override',
|
|
28
|
+
rule_id: null,
|
|
29
|
+
updated_at: null
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const sceneId = normalizeText(payload && payload.scene_id);
|
|
34
|
+
if (!sceneId) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
spec_id: normalizedSpecId,
|
|
39
|
+
scene_id: sceneId,
|
|
40
|
+
source: normalizeText(payload.source) || 'override',
|
|
41
|
+
rule_id: normalizeText(payload.rule_id) || null,
|
|
42
|
+
updated_at: normalizeText(payload.updated_at) || null
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeSceneBindingOverrides(raw = {}) {
|
|
47
|
+
const payload = raw && typeof raw === 'object' ? raw : {};
|
|
48
|
+
const mappingsRaw = payload.mappings && typeof payload.mappings === 'object'
|
|
49
|
+
? payload.mappings
|
|
50
|
+
: {};
|
|
51
|
+
const mappings = {};
|
|
52
|
+
for (const [specId, entry] of Object.entries(mappingsRaw)) {
|
|
53
|
+
const normalized = normalizeOverrideEntry(specId, entry);
|
|
54
|
+
if (!normalized) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
mappings[normalized.spec_id] = {
|
|
58
|
+
scene_id: normalized.scene_id,
|
|
59
|
+
source: normalized.source,
|
|
60
|
+
rule_id: normalized.rule_id,
|
|
61
|
+
updated_at: normalized.updated_at
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
schema_version: normalizeText(payload.schema_version) || '1.0',
|
|
66
|
+
generated_at: normalizeText(payload.generated_at) || null,
|
|
67
|
+
updated_at: normalizeText(payload.updated_at) || null,
|
|
68
|
+
mappings
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function loadSceneBindingOverrides(projectPath = process.cwd(), options = {}, fileSystem = fs) {
|
|
73
|
+
const overridePath = normalizeText(options.override_path || options.overridePath)
|
|
74
|
+
|| DEFAULT_SPEC_SCENE_OVERRIDE_PATH;
|
|
75
|
+
const absolutePath = path.join(projectPath, overridePath);
|
|
76
|
+
let payload = {};
|
|
77
|
+
let loadedFrom = 'default';
|
|
78
|
+
if (await fileSystem.pathExists(absolutePath)) {
|
|
79
|
+
try {
|
|
80
|
+
payload = await fileSystem.readJson(absolutePath);
|
|
81
|
+
loadedFrom = 'file';
|
|
82
|
+
} catch (_error) {
|
|
83
|
+
payload = {};
|
|
84
|
+
loadedFrom = 'default';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
override_path: overridePath,
|
|
89
|
+
absolute_path: absolutePath,
|
|
90
|
+
loaded_from: loadedFrom,
|
|
91
|
+
overrides: normalizeSceneBindingOverrides(payload)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveSceneIdFromOverrides(specId, overrides = {}) {
|
|
96
|
+
const normalizedSpecId = normalizeText(specId);
|
|
97
|
+
if (!normalizedSpecId) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const mappings = overrides && typeof overrides === 'object' && overrides.mappings
|
|
101
|
+
? overrides.mappings
|
|
102
|
+
: {};
|
|
103
|
+
const entry = mappings[normalizedSpecId];
|
|
104
|
+
if (!entry || typeof entry !== 'object') {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
return normalizeText(entry.scene_id) || null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = {
|
|
111
|
+
DEFAULT_SPEC_SCENE_OVERRIDE_PATH,
|
|
112
|
+
normalizeSceneBindingOverrides,
|
|
113
|
+
loadSceneBindingOverrides,
|
|
114
|
+
resolveSceneIdFromOverrides
|
|
115
|
+
};
|