scene-capability-engine 3.5.0 → 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 +15 -0
- package/README.md +5 -2
- package/README.zh.md +5 -2
- package/docs/command-reference.md +18 -2
- package/lib/commands/studio.js +69 -4
- package/lib/spec/related-specs.js +10 -2
- package/lib/spec/scene-binding-overrides.js +115 -0
- package/lib/studio/spec-intake-governor.js +328 -5
- package/lib/workspace/takeover-baseline.js +25 -0
- package/package.json +1 -1
- package/template/.sce/config/studio-intake-policy.json +86 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.5.1] - 2026-03-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Studio historical scene backfill command:
|
|
14
|
+
- `sce studio backfill-spec-scenes`
|
|
15
|
+
- supports active-only/default batch backfill and writes `.sce/spec-governance/spec-scene-overrides.json`
|
|
16
|
+
- optionally refreshes governance artifacts after apply
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Studio intake/governance policy is now stricter by default:
|
|
20
|
+
- `allow_manual_spec_override=false` blocks `--manual-spec` bypass unless explicitly enabled
|
|
21
|
+
- `governance.require_auto_on_plan=true` blocks `--no-spec-governance` bypass unless explicitly enabled
|
|
22
|
+
- Spec governance and related-spec lookup now read scene override mappings from `.sce/spec-governance/spec-scene-overrides.json`.
|
|
23
|
+
- Takeover/adoption baseline now provisions the stricter intake policy and backfill defaults.
|
|
24
|
+
|
|
10
25
|
## [3.5.0] - 2026-03-02
|
|
11
26
|
|
|
12
27
|
### Added
|
package/README.md
CHANGED
|
@@ -108,6 +108,8 @@ Hard rule defaults:
|
|
|
108
108
|
- After two failed rounds on the same problem fingerprint, debug evidence is required in subsequent attempts.
|
|
109
109
|
- `studio verify/release` run `problem-closure-gate` by default when a spec is bound.
|
|
110
110
|
- `studio plan` auto-runs goal intake (`bind existing spec` or `create spec`) and writes scene portfolio governance snapshots by default.
|
|
111
|
+
- `studio plan --manual-spec` and `--no-spec-governance` are blocked by default policy; use policy overrides only when absolutely necessary.
|
|
112
|
+
- Historical specs can be scene-governed incrementally via `sce studio backfill-spec-scenes --apply` (writes `.sce/spec-governance/spec-scene-overrides.json`).
|
|
111
113
|
|
|
112
114
|
---
|
|
113
115
|
|
|
@@ -130,6 +132,7 @@ SCE is tool-agnostic and works with Codex, Claude Code, Cursor, Windsurf, VS Cod
|
|
|
130
132
|
|
|
131
133
|
## Important Version Changes
|
|
132
134
|
|
|
135
|
+
- `3.5.1`: Enforced stricter Studio intake defaults (`--manual-spec` and `--no-spec-governance` blocked unless policy override), added historical spec scene backfill command (`sce studio backfill-spec-scenes`) and persisted override mapping (`.sce/spec-governance/spec-scene-overrides.json`) for portfolio/related-spec alignment.
|
|
133
136
|
- `3.5.0`: Added Studio automatic goal intake + scene spec portfolio governance (`sce studio intake`, `sce studio portfolio`), including default intake policy baseline and governance artifacts for bounded scene spec growth.
|
|
134
137
|
- `3.4.6`: Added default `problem-closure-gate` + `problem-contract` baseline and strengthened mandatory problem evaluation dimensions (`problem_contract`/`ontology_alignment`/`convergence`) for verify/release convergence control.
|
|
135
138
|
- `3.4.5`: `git-managed-gate` now treats worktree checks as advisory in default relaxed CI mode (`CI/GITHUB_ACTIONS`, non-strict), preventing false release blocking.
|
|
@@ -177,5 +180,5 @@ MIT. See [LICENSE](LICENSE).
|
|
|
177
180
|
|
|
178
181
|
---
|
|
179
182
|
|
|
180
|
-
**Version**: 3.5.
|
|
181
|
-
**Last Updated**: 2026-03-
|
|
183
|
+
**Version**: 3.5.1
|
|
184
|
+
**Last Updated**: 2026-03-03
|
package/README.zh.md
CHANGED
|
@@ -108,6 +108,8 @@ SCE 默认按“问题域闭环”推进诊断与修复:
|
|
|
108
108
|
- 同一问题指纹失败两轮后,后续尝试必须补充 debug 证据。
|
|
109
109
|
- 当 spec 绑定时,`studio verify/release` 默认执行 `problem-closure-gate`。
|
|
110
110
|
- `studio plan` 默认执行目标 intake(自动绑定已有 spec 或新建 spec),并自动写入 scene 维度的 spec 治理快照。
|
|
111
|
+
- 默认策略会阻断 `studio plan --manual-spec` 与 `--no-spec-governance`(仅在确有必要且策略显式放开时可绕过)。
|
|
112
|
+
- 历史 spec 可通过 `sce studio backfill-spec-scenes --apply` 分批回填到 scene 治理映射(写入 `.sce/spec-governance/spec-scene-overrides.json`)。
|
|
111
113
|
|
|
112
114
|
---
|
|
113
115
|
|
|
@@ -130,6 +132,7 @@ SCE 对工具无锁定,可接入 Codex、Claude Code、Cursor、Windsurf、VS
|
|
|
130
132
|
|
|
131
133
|
## 重要版本变更
|
|
132
134
|
|
|
135
|
+
- `3.5.1`:默认强化 Studio intake 治理(`--manual-spec`、`--no-spec-governance` 在未显式放开策略时会被阻断),新增历史 spec 场景回填命令 `sce studio backfill-spec-scenes`,并写入 `.sce/spec-governance/spec-scene-overrides.json` 以统一 portfolio 与 related-spec 的场景映射。
|
|
133
136
|
- `3.5.0`:新增 Studio 目标自动 intake + 场景 spec 组合治理(`sce studio intake`、`sce studio portfolio`),并默认启用 intake 策略基线与治理快照产物,控制场景内 spec 无序增长。
|
|
134
137
|
- `3.4.6`:新增默认 `problem-closure-gate` + `problem-contract` 基线,并强化问题评估强制维度(`problem_contract`/`ontology_alignment`/`convergence`),提升 verify/release 收敛控制。
|
|
135
138
|
- `3.4.5`:`git-managed-gate` 在默认 CI 放宽模式下(`CI/GITHUB_ACTIONS` 且非 strict)对工作区变更改为告警,不再误阻断发布。
|
|
@@ -177,5 +180,5 @@ MIT,见 [LICENSE](LICENSE)。
|
|
|
177
180
|
|
|
178
181
|
---
|
|
179
182
|
|
|
180
|
-
**版本**:3.5.
|
|
181
|
-
**最后更新**:2026-03-
|
|
183
|
+
**版本**:3.5.1
|
|
184
|
+
**最后更新**:2026-03-03
|
|
@@ -523,8 +523,6 @@ Curated quality policy (`宁缺毋滥,优胜略汰`) defaults:
|
|
|
523
523
|
sce studio plan --scene scene.customer-order-inventory --from-chat session-20260226 --goal "customer+order+inventory demo" --json
|
|
524
524
|
# Recommended: bind spec explicitly so Studio can ingest problem-domain-chain deterministically
|
|
525
525
|
sce studio plan --scene scene.customer-order-inventory --spec 01-00-customer-order-inventory --from-chat session-20260226 --goal "customer+order+inventory demo" --json
|
|
526
|
-
# Disable auto intake for emergency/manual mode
|
|
527
|
-
sce studio plan --scene scene.customer-order-inventory --from-chat session-20260226 --goal "customer+order+inventory demo" --manual-spec --json
|
|
528
526
|
|
|
529
527
|
# Analyze intake decision only (no write by default)
|
|
530
528
|
sce studio intake --scene scene.customer-order-inventory --from-chat session-20260226 --goal "optimize checkout retry flow" --json
|
|
@@ -560,6 +558,11 @@ sce studio rollback --job <job-id> --reason "manual-check-failed" --json
|
|
|
560
558
|
sce studio portfolio --json
|
|
561
559
|
sce studio portfolio --scene scene.customer-order-inventory --strict --json
|
|
562
560
|
|
|
561
|
+
# Backfill historical spec scene bindings into override map (active-only by default)
|
|
562
|
+
sce studio backfill-spec-scenes --apply --json
|
|
563
|
+
# Backfill a bounded batch from unassigned scene and refresh governance snapshot
|
|
564
|
+
sce studio backfill-spec-scenes --scene scene.unassigned --limit 20 --apply --json
|
|
565
|
+
|
|
563
566
|
# Enforce authorization for a protected action
|
|
564
567
|
SCE_STUDIO_REQUIRE_AUTH=1 SCE_STUDIO_AUTH_PASSWORD=top-secret sce studio apply --job <job-id> --auth-password top-secret --json
|
|
565
568
|
```
|
|
@@ -570,12 +573,17 @@ Stage guardrails are enforced by default:
|
|
|
570
573
|
- detect goal intent (`change_request` vs `analysis_only`)
|
|
571
574
|
- resolve spec via explicit binding / scene latest / related specs / auto-create
|
|
572
575
|
- auto-create spec artifacts when no suitable spec is found and policy requires tracking
|
|
576
|
+
- `plan --manual-spec` is blocked by default (`allow_manual_spec_override=false`)
|
|
577
|
+
- `plan --no-spec-governance` is blocked by default (`governance.require_auto_on_plan=true`)
|
|
573
578
|
- `plan --spec <id>` (recommended) ingests `.sce/specs/<spec>/custom/problem-domain-chain.json` into studio job context
|
|
574
579
|
- when `--spec` is omitted, `plan` auto-resolves the latest matching spec chain by `scene_id` when available
|
|
575
580
|
- `plan` auto-searches related historical specs by `scene + goal` and writes top candidates into job metadata (`source.related_specs`)
|
|
576
581
|
- `plan` auto-runs scene spec governance snapshot and writes:
|
|
577
582
|
- `.sce/spec-governance/scene-portfolio.latest.json`
|
|
578
583
|
- `.sce/spec-governance/scene-index.json`
|
|
584
|
+
- historical spec scene backfill can be managed via:
|
|
585
|
+
- `sce studio backfill-spec-scenes --apply`
|
|
586
|
+
- writes `.sce/spec-governance/spec-scene-overrides.json`
|
|
579
587
|
- successful `release` auto-archives current scene session and auto-opens the next scene cycle session
|
|
580
588
|
- `generate` requires `plan`
|
|
581
589
|
- `generate` consumes the plan-stage domain-chain context and writes chain-aware metadata/report (`.sce/reports/studio/generate-<job-id>.json`)
|
|
@@ -657,13 +665,21 @@ Studio intake policy file (default, recommended to commit): `.sce/config/studio-
|
|
|
657
665
|
"enabled": true,
|
|
658
666
|
"auto_create_spec": true,
|
|
659
667
|
"force_spec_for_studio_plan": true,
|
|
668
|
+
"allow_manual_spec_override": false,
|
|
660
669
|
"prefer_existing_scene_spec": true,
|
|
661
670
|
"related_spec_min_score": 45,
|
|
662
671
|
"governance": {
|
|
663
672
|
"auto_run_on_plan": true,
|
|
673
|
+
"require_auto_on_plan": true,
|
|
664
674
|
"max_active_specs_per_scene": 3,
|
|
665
675
|
"stale_days": 14,
|
|
666
676
|
"duplicate_similarity_threshold": 0.66
|
|
677
|
+
},
|
|
678
|
+
"backfill": {
|
|
679
|
+
"enabled": true,
|
|
680
|
+
"active_only_default": true,
|
|
681
|
+
"default_scene_id": "scene.sce-core",
|
|
682
|
+
"override_file": ".sce/spec-governance/spec-scene-overrides.json"
|
|
667
683
|
}
|
|
668
684
|
}
|
|
669
685
|
```
|
package/lib/commands/studio.js
CHANGED
|
@@ -12,8 +12,10 @@ const { findRelatedSpecs } = require('../spec/related-specs');
|
|
|
12
12
|
const { captureTimelineCheckpoint } = require('../runtime/project-timeline');
|
|
13
13
|
const { runProblemEvaluation } = require('../problem/problem-evaluator');
|
|
14
14
|
const {
|
|
15
|
+
loadStudioIntakePolicy,
|
|
15
16
|
runStudioAutoIntake,
|
|
16
|
-
runStudioSpecGovernance
|
|
17
|
+
runStudioSpecGovernance,
|
|
18
|
+
runStudioSceneBackfill
|
|
17
19
|
} = require('../studio/spec-intake-governor');
|
|
18
20
|
|
|
19
21
|
const STUDIO_JOB_API_VERSION = 'sce.studio.job/v0.1';
|
|
@@ -1358,6 +1360,20 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
|
|
|
1358
1360
|
throw new Error('--scene is required');
|
|
1359
1361
|
}
|
|
1360
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
|
+
|
|
1361
1377
|
let domainChainBinding = await resolveDomainChainBinding({
|
|
1362
1378
|
sceneId,
|
|
1363
1379
|
specId,
|
|
@@ -2383,6 +2399,19 @@ function printStudioPortfolioPayload(payload, options = {}) {
|
|
|
2383
2399
|
console.log(` Overflow scenes: ${summary.overflow_scenes || 0}`);
|
|
2384
2400
|
}
|
|
2385
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
|
+
|
|
2386
2415
|
async function runStudioPortfolioCommand(options = {}, dependencies = {}) {
|
|
2387
2416
|
const projectPath = dependencies.projectPath || process.cwd();
|
|
2388
2417
|
const fileSystem = dependencies.fileSystem || fs;
|
|
@@ -2404,6 +2433,29 @@ async function runStudioPortfolioCommand(options = {}, dependencies = {}) {
|
|
|
2404
2433
|
return payload;
|
|
2405
2434
|
}
|
|
2406
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
|
+
|
|
2407
2459
|
async function runStudioCommand(handler, options, stageName = '') {
|
|
2408
2460
|
try {
|
|
2409
2461
|
const stage = normalizeString(stageName) || 'unknown';
|
|
@@ -2447,9 +2499,9 @@ function registerStudioCommands(program) {
|
|
|
2447
2499
|
.requiredOption('--from-chat <session>', 'Chat session identifier or transcript reference')
|
|
2448
2500
|
.option('--spec <spec-id>', 'Optional spec binding for domain-chain context ingestion')
|
|
2449
2501
|
.option('--goal <goal>', 'Optional goal summary')
|
|
2450
|
-
.option('--manual-spec', '
|
|
2502
|
+
.option('--manual-spec', 'Legacy bypass flag (disabled by default policy)')
|
|
2451
2503
|
.option('--target <target>', 'Target integration profile', 'default')
|
|
2452
|
-
.option('--no-spec-governance', '
|
|
2504
|
+
.option('--no-spec-governance', 'Legacy bypass flag (disabled by default policy)')
|
|
2453
2505
|
.option('--job <job-id>', 'Reuse an explicit studio job id')
|
|
2454
2506
|
.option('--json', 'Print machine-readable JSON output')
|
|
2455
2507
|
.action(async (options) => runStudioCommand(runStudioPlanCommand, options, 'plan'));
|
|
@@ -2462,7 +2514,7 @@ function registerStudioCommands(program) {
|
|
|
2462
2514
|
.option('--spec <spec-id>', 'Optional explicit spec id')
|
|
2463
2515
|
.option('--goal <goal>', 'Goal text used for intent classification')
|
|
2464
2516
|
.option('--apply', 'Create spec when decision is create_spec')
|
|
2465
|
-
.option('--manual-spec', '
|
|
2517
|
+
.option('--manual-spec', 'Legacy bypass flag (disabled by default policy)')
|
|
2466
2518
|
.option('--json', 'Print machine-readable JSON output')
|
|
2467
2519
|
.action(async (options) => runStudioCommand(runStudioIntakeCommand, options, 'intake'));
|
|
2468
2520
|
|
|
@@ -2475,6 +2527,18 @@ function registerStudioCommands(program) {
|
|
|
2475
2527
|
.option('--json', 'Print machine-readable JSON output')
|
|
2476
2528
|
.action(async (options) => runStudioCommand(runStudioPortfolioCommand, options, 'portfolio'));
|
|
2477
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
|
+
|
|
2478
2542
|
studio
|
|
2479
2543
|
.command('generate')
|
|
2480
2544
|
.description('Generate patch bundle metadata for a planned studio job (scene inherited from plan)')
|
|
@@ -2567,6 +2631,7 @@ module.exports = {
|
|
|
2567
2631
|
runStudioRollbackCommand,
|
|
2568
2632
|
runStudioEventsCommand,
|
|
2569
2633
|
runStudioPortfolioCommand,
|
|
2634
|
+
runStudioBackfillSpecScenesCommand,
|
|
2570
2635
|
runStudioResumeCommand,
|
|
2571
2636
|
registerStudioCommands
|
|
2572
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
|
+
};
|
|
@@ -2,17 +2,63 @@ const path = require('path');
|
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const { DraftGenerator } = require('../spec/bootstrap/draft-generator');
|
|
4
4
|
const { ensureSpecDomainArtifacts } = require('../spec/domain-modeling');
|
|
5
|
+
const {
|
|
6
|
+
DEFAULT_SPEC_SCENE_OVERRIDE_PATH,
|
|
7
|
+
loadSceneBindingOverrides,
|
|
8
|
+
normalizeSceneBindingOverrides,
|
|
9
|
+
resolveSceneIdFromOverrides
|
|
10
|
+
} = require('../spec/scene-binding-overrides');
|
|
5
11
|
|
|
6
12
|
const DEFAULT_STUDIO_INTAKE_POLICY_PATH = '.sce/config/studio-intake-policy.json';
|
|
7
13
|
const DEFAULT_STUDIO_GOVERNANCE_DIR = '.sce/spec-governance';
|
|
8
14
|
const DEFAULT_STUDIO_PORTFOLIO_REPORT = `${DEFAULT_STUDIO_GOVERNANCE_DIR}/scene-portfolio.latest.json`;
|
|
9
15
|
const DEFAULT_STUDIO_SCENE_INDEX = `${DEFAULT_STUDIO_GOVERNANCE_DIR}/scene-index.json`;
|
|
16
|
+
const DEFAULT_STUDIO_SCENE_OVERRIDE_PATH = DEFAULT_SPEC_SCENE_OVERRIDE_PATH;
|
|
17
|
+
|
|
18
|
+
const DEFAULT_STUDIO_SCENE_BACKFILL_RULES = Object.freeze([
|
|
19
|
+
{
|
|
20
|
+
id: 'moqui-core',
|
|
21
|
+
scene_id: 'scene.moqui-core',
|
|
22
|
+
keywords: ['moqui']
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'orchestration',
|
|
26
|
+
scene_id: 'scene.sce-orchestration',
|
|
27
|
+
keywords: ['orchestrate', 'runtime', 'controller', 'batch', 'parallel']
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'template-registry',
|
|
31
|
+
scene_id: 'scene.sce-template-registry',
|
|
32
|
+
keywords: ['template', 'scene-package', 'registry', 'catalog', 'scene-template']
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'spec-governance',
|
|
36
|
+
scene_id: 'scene.sce-spec-governance',
|
|
37
|
+
keywords: ['spec', 'gate', 'ontology', 'governance', 'policy']
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'quality',
|
|
41
|
+
scene_id: 'scene.sce-quality',
|
|
42
|
+
keywords: ['test', 'quality', 'stability', 'jest', 'coverage']
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'docs',
|
|
46
|
+
scene_id: 'scene.sce-docs',
|
|
47
|
+
keywords: ['document', 'documentation', 'onboarding', 'guide']
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'platform',
|
|
51
|
+
scene_id: 'scene.sce-platform',
|
|
52
|
+
keywords: ['adopt', 'upgrade', 'workspace', 'repo', 'environment', 'devops', 'release', 'github', 'npm']
|
|
53
|
+
}
|
|
54
|
+
]);
|
|
10
55
|
|
|
11
56
|
const DEFAULT_STUDIO_INTAKE_POLICY = Object.freeze({
|
|
12
57
|
schema_version: '1.0',
|
|
13
58
|
enabled: true,
|
|
14
59
|
auto_create_spec: true,
|
|
15
60
|
force_spec_for_studio_plan: true,
|
|
61
|
+
allow_manual_spec_override: false,
|
|
16
62
|
prefer_existing_scene_spec: true,
|
|
17
63
|
related_spec_min_score: 45,
|
|
18
64
|
allow_new_spec_when_goal_diverges: true,
|
|
@@ -33,9 +79,17 @@ const DEFAULT_STUDIO_INTAKE_POLICY = Object.freeze({
|
|
|
33
79
|
},
|
|
34
80
|
governance: {
|
|
35
81
|
auto_run_on_plan: true,
|
|
82
|
+
require_auto_on_plan: true,
|
|
36
83
|
max_active_specs_per_scene: 3,
|
|
37
84
|
stale_days: 14,
|
|
38
85
|
duplicate_similarity_threshold: 0.66
|
|
86
|
+
},
|
|
87
|
+
backfill: {
|
|
88
|
+
enabled: true,
|
|
89
|
+
active_only_default: true,
|
|
90
|
+
default_scene_id: 'scene.sce-core',
|
|
91
|
+
override_file: DEFAULT_STUDIO_SCENE_OVERRIDE_PATH,
|
|
92
|
+
rules: DEFAULT_STUDIO_SCENE_BACKFILL_RULES
|
|
39
93
|
}
|
|
40
94
|
});
|
|
41
95
|
|
|
@@ -87,6 +141,30 @@ function normalizeTextList(value = []) {
|
|
|
87
141
|
.filter(Boolean);
|
|
88
142
|
}
|
|
89
143
|
|
|
144
|
+
function normalizeBackfillRules(value = []) {
|
|
145
|
+
if (!Array.isArray(value)) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
const rules = [];
|
|
149
|
+
for (const item of value) {
|
|
150
|
+
if (!item || typeof item !== 'object') {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const id = normalizeText(item.id);
|
|
154
|
+
const sceneId = normalizeText(item.scene_id || item.sceneId);
|
|
155
|
+
const keywords = normalizeTextList(item.keywords || item.match_any_keywords || item.matchAnyKeywords);
|
|
156
|
+
if (!id || !sceneId || keywords.length === 0) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
rules.push({
|
|
160
|
+
id,
|
|
161
|
+
scene_id: sceneId,
|
|
162
|
+
keywords
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return rules;
|
|
166
|
+
}
|
|
167
|
+
|
|
90
168
|
function toRelativePosix(projectPath, absolutePath) {
|
|
91
169
|
return path.relative(projectPath, absolutePath).replace(/\\/g, '/');
|
|
92
170
|
}
|
|
@@ -169,6 +247,8 @@ function normalizeStudioIntakePolicy(raw = {}) {
|
|
|
169
247
|
const payload = raw && typeof raw === 'object' ? raw : {};
|
|
170
248
|
const specId = payload.spec_id && typeof payload.spec_id === 'object' ? payload.spec_id : {};
|
|
171
249
|
const governance = payload.governance && typeof payload.governance === 'object' ? payload.governance : {};
|
|
250
|
+
const backfill = payload.backfill && typeof payload.backfill === 'object' ? payload.backfill : {};
|
|
251
|
+
const normalizedBackfillRules = normalizeBackfillRules(backfill.rules);
|
|
172
252
|
|
|
173
253
|
return {
|
|
174
254
|
schema_version: normalizeText(payload.schema_version) || DEFAULT_STUDIO_INTAKE_POLICY.schema_version,
|
|
@@ -178,6 +258,10 @@ function normalizeStudioIntakePolicy(raw = {}) {
|
|
|
178
258
|
payload.force_spec_for_studio_plan,
|
|
179
259
|
DEFAULT_STUDIO_INTAKE_POLICY.force_spec_for_studio_plan
|
|
180
260
|
),
|
|
261
|
+
allow_manual_spec_override: normalizeBoolean(
|
|
262
|
+
payload.allow_manual_spec_override,
|
|
263
|
+
DEFAULT_STUDIO_INTAKE_POLICY.allow_manual_spec_override
|
|
264
|
+
),
|
|
181
265
|
prefer_existing_scene_spec: normalizeBoolean(
|
|
182
266
|
payload.prefer_existing_scene_spec,
|
|
183
267
|
DEFAULT_STUDIO_INTAKE_POLICY.prefer_existing_scene_spec
|
|
@@ -224,6 +308,10 @@ function normalizeStudioIntakePolicy(raw = {}) {
|
|
|
224
308
|
governance.auto_run_on_plan,
|
|
225
309
|
DEFAULT_STUDIO_INTAKE_POLICY.governance.auto_run_on_plan
|
|
226
310
|
),
|
|
311
|
+
require_auto_on_plan: normalizeBoolean(
|
|
312
|
+
governance.require_auto_on_plan,
|
|
313
|
+
DEFAULT_STUDIO_INTAKE_POLICY.governance.require_auto_on_plan
|
|
314
|
+
),
|
|
227
315
|
max_active_specs_per_scene: normalizeInteger(
|
|
228
316
|
governance.max_active_specs_per_scene,
|
|
229
317
|
DEFAULT_STUDIO_INTAKE_POLICY.governance.max_active_specs_per_scene,
|
|
@@ -243,6 +331,23 @@ function normalizeStudioIntakePolicy(raw = {}) {
|
|
|
243
331
|
DEFAULT_STUDIO_INTAKE_POLICY.governance.duplicate_similarity_threshold
|
|
244
332
|
))
|
|
245
333
|
)
|
|
334
|
+
},
|
|
335
|
+
backfill: {
|
|
336
|
+
enabled: normalizeBoolean(
|
|
337
|
+
backfill.enabled,
|
|
338
|
+
DEFAULT_STUDIO_INTAKE_POLICY.backfill.enabled
|
|
339
|
+
),
|
|
340
|
+
active_only_default: normalizeBoolean(
|
|
341
|
+
backfill.active_only_default,
|
|
342
|
+
DEFAULT_STUDIO_INTAKE_POLICY.backfill.active_only_default
|
|
343
|
+
),
|
|
344
|
+
default_scene_id: normalizeText(backfill.default_scene_id)
|
|
345
|
+
|| DEFAULT_STUDIO_INTAKE_POLICY.backfill.default_scene_id,
|
|
346
|
+
override_file: normalizeText(backfill.override_file)
|
|
347
|
+
|| DEFAULT_STUDIO_INTAKE_POLICY.backfill.override_file,
|
|
348
|
+
rules: normalizedBackfillRules.length > 0
|
|
349
|
+
? normalizedBackfillRules
|
|
350
|
+
: normalizeBackfillRules(DEFAULT_STUDIO_INTAKE_POLICY.backfill.rules)
|
|
246
351
|
}
|
|
247
352
|
};
|
|
248
353
|
}
|
|
@@ -611,6 +716,12 @@ async function runStudioAutoIntake(options = {}, dependencies = {}) {
|
|
|
611
716
|
created_spec: null
|
|
612
717
|
};
|
|
613
718
|
|
|
719
|
+
if (skip && policy.allow_manual_spec_override !== true) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
'manual spec override is disabled by studio intake policy (allow_manual_spec_override=false)'
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
614
725
|
if (skip) {
|
|
615
726
|
payload.enabled = false;
|
|
616
727
|
payload.decision = {
|
|
@@ -705,6 +816,10 @@ async function scanSpecPortfolio(projectPath = process.cwd(), options = {}, depe
|
|
|
705
816
|
return [];
|
|
706
817
|
}
|
|
707
818
|
const staleDays = normalizeInteger(options.stale_days, 14, 1, 3650);
|
|
819
|
+
const overrideContext = await loadSceneBindingOverrides(projectPath, {
|
|
820
|
+
overridePath: options.override_file || DEFAULT_STUDIO_SCENE_OVERRIDE_PATH
|
|
821
|
+
}, fileSystem);
|
|
822
|
+
const sceneOverrides = normalizeSceneBindingOverrides(overrideContext.overrides || {});
|
|
708
823
|
const entries = await fileSystem.readdir(specsRoot);
|
|
709
824
|
const records = [];
|
|
710
825
|
|
|
@@ -733,9 +848,12 @@ async function scanSpecPortfolio(projectPath = process.cwd(), options = {}, depe
|
|
|
733
848
|
readFileSafe(tasksPath, fileSystem)
|
|
734
849
|
]);
|
|
735
850
|
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
851
|
+
const sceneFromChain = normalizeText(chain && chain.scene_id ? chain.scene_id : '');
|
|
852
|
+
const sceneFromOverride = resolveSceneIdFromOverrides(entry, sceneOverrides);
|
|
853
|
+
const sceneId = sceneFromChain || sceneFromOverride || 'scene.unassigned';
|
|
854
|
+
const sceneSource = sceneFromChain
|
|
855
|
+
? 'domain-chain'
|
|
856
|
+
: (sceneFromOverride ? 'override' : 'unassigned');
|
|
739
857
|
const problemStatement = normalizeText(
|
|
740
858
|
(chain && chain.problem && chain.problem.statement)
|
|
741
859
|
|| (contract && contract.issue_statement)
|
|
@@ -767,6 +885,7 @@ async function scanSpecPortfolio(projectPath = process.cwd(), options = {}, depe
|
|
|
767
885
|
tasks_progress: taskProgress.ratio,
|
|
768
886
|
lifecycle_state: lifecycle.state,
|
|
769
887
|
age_days: lifecycle.age_days,
|
|
888
|
+
scene_source: sceneSource,
|
|
770
889
|
tokens
|
|
771
890
|
});
|
|
772
891
|
}
|
|
@@ -889,6 +1008,202 @@ function buildSceneGovernanceReport(records = [], policy = DEFAULT_STUDIO_INTAKE
|
|
|
889
1008
|
};
|
|
890
1009
|
}
|
|
891
1010
|
|
|
1011
|
+
function classifyBackfillRule(record = {}, backfillPolicy = {}) {
|
|
1012
|
+
const rules = Array.isArray(backfillPolicy.rules) ? backfillPolicy.rules : [];
|
|
1013
|
+
const defaultSceneId = normalizeText(backfillPolicy.default_scene_id) || 'scene.sce-core';
|
|
1014
|
+
const searchText = [
|
|
1015
|
+
normalizeText(record.spec_id).toLowerCase(),
|
|
1016
|
+
normalizeText(record.problem_statement).toLowerCase()
|
|
1017
|
+
].join(' ');
|
|
1018
|
+
const searchTokens = new Set(tokenizeText(searchText));
|
|
1019
|
+
let bestMatch = null;
|
|
1020
|
+
|
|
1021
|
+
for (const rule of rules) {
|
|
1022
|
+
const ruleId = normalizeText(rule.id);
|
|
1023
|
+
const sceneId = normalizeText(rule.scene_id);
|
|
1024
|
+
const keywords = normalizeTextList(rule.keywords).map((item) => item.toLowerCase());
|
|
1025
|
+
if (!ruleId || !sceneId || keywords.length === 0) {
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
const matchedKeywords = [];
|
|
1030
|
+
for (const keyword of keywords) {
|
|
1031
|
+
if (!keyword) {
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
if (searchText.includes(keyword) || searchTokens.has(keyword)) {
|
|
1035
|
+
matchedKeywords.push(keyword);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
if (matchedKeywords.length === 0) {
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const score = matchedKeywords.length;
|
|
1043
|
+
if (!bestMatch || score > bestMatch.score) {
|
|
1044
|
+
bestMatch = {
|
|
1045
|
+
rule_id: ruleId,
|
|
1046
|
+
scene_id: sceneId,
|
|
1047
|
+
matched_keywords: matchedKeywords,
|
|
1048
|
+
score
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (!bestMatch) {
|
|
1054
|
+
return {
|
|
1055
|
+
scene_id: defaultSceneId,
|
|
1056
|
+
rule_id: 'default',
|
|
1057
|
+
matched_keywords: [],
|
|
1058
|
+
confidence: 'low',
|
|
1059
|
+
source: 'default'
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
return {
|
|
1064
|
+
scene_id: bestMatch.scene_id,
|
|
1065
|
+
rule_id: bestMatch.rule_id,
|
|
1066
|
+
matched_keywords: bestMatch.matched_keywords,
|
|
1067
|
+
confidence: bestMatch.score >= 2 ? 'high' : 'medium',
|
|
1068
|
+
source: 'rule'
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function clampBackfillLimit(value, fallback = 0, max = 1000) {
|
|
1073
|
+
if (value === undefined || value === null || value === '') {
|
|
1074
|
+
return fallback;
|
|
1075
|
+
}
|
|
1076
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
1077
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
1078
|
+
return fallback;
|
|
1079
|
+
}
|
|
1080
|
+
return Math.min(parsed, max);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
async function runStudioSceneBackfill(options = {}, dependencies = {}) {
|
|
1084
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
1085
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
1086
|
+
const loaded = options.policy && typeof options.policy === 'object'
|
|
1087
|
+
? { policy: normalizeStudioIntakePolicy(options.policy), policy_path: '(inline)', loaded_from: 'inline' }
|
|
1088
|
+
: await loadStudioIntakePolicy(projectPath, fileSystem);
|
|
1089
|
+
const policy = loaded.policy;
|
|
1090
|
+
const backfillPolicy = policy.backfill || DEFAULT_STUDIO_INTAKE_POLICY.backfill;
|
|
1091
|
+
const apply = options.apply === true;
|
|
1092
|
+
const refreshGovernance = options.refresh_governance !== false && options.refreshGovernance !== false;
|
|
1093
|
+
const sourceScene = normalizeText(options.source_scene || options.sourceScene || options.scene) || 'scene.unassigned';
|
|
1094
|
+
const includeAll = options.all === true || options.active_only === false || options.activeOnly === false;
|
|
1095
|
+
const activeOnly = includeAll ? false : (options.active_only === true || options.activeOnly === true || backfillPolicy.active_only_default !== false);
|
|
1096
|
+
const limit = clampBackfillLimit(options.limit, 0, 2000);
|
|
1097
|
+
const overrideFile = normalizeText(backfillPolicy.override_file) || DEFAULT_STUDIO_SCENE_OVERRIDE_PATH;
|
|
1098
|
+
|
|
1099
|
+
const records = await scanSpecPortfolio(projectPath, {
|
|
1100
|
+
stale_days: policy.governance && policy.governance.stale_days,
|
|
1101
|
+
override_file: overrideFile
|
|
1102
|
+
}, {
|
|
1103
|
+
fileSystem
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
let candidates = records.filter((item) => normalizeText(item.scene_id) === sourceScene);
|
|
1107
|
+
if (activeOnly) {
|
|
1108
|
+
candidates = candidates.filter((item) => item.lifecycle_state === 'active');
|
|
1109
|
+
}
|
|
1110
|
+
if (limit > 0) {
|
|
1111
|
+
candidates = candidates.slice(0, limit);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
const assignmentPlan = candidates.map((record) => {
|
|
1115
|
+
const decision = classifyBackfillRule(record, backfillPolicy);
|
|
1116
|
+
return {
|
|
1117
|
+
spec_id: record.spec_id,
|
|
1118
|
+
from_scene_id: sourceScene,
|
|
1119
|
+
to_scene_id: decision.scene_id,
|
|
1120
|
+
lifecycle_state: record.lifecycle_state,
|
|
1121
|
+
rule_id: decision.rule_id,
|
|
1122
|
+
source: decision.source,
|
|
1123
|
+
confidence: decision.confidence,
|
|
1124
|
+
matched_keywords: decision.matched_keywords
|
|
1125
|
+
};
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
const overrideContext = await loadSceneBindingOverrides(projectPath, {
|
|
1129
|
+
overridePath: overrideFile
|
|
1130
|
+
}, fileSystem);
|
|
1131
|
+
const existingOverrides = normalizeSceneBindingOverrides(overrideContext.overrides || {});
|
|
1132
|
+
const nextOverrides = normalizeSceneBindingOverrides(existingOverrides);
|
|
1133
|
+
const now = new Date().toISOString();
|
|
1134
|
+
let changedCount = 0;
|
|
1135
|
+
|
|
1136
|
+
for (const item of assignmentPlan) {
|
|
1137
|
+
const existing = existingOverrides.mappings[item.spec_id];
|
|
1138
|
+
const currentScene = normalizeText(existing && existing.scene_id);
|
|
1139
|
+
if (currentScene === item.to_scene_id) {
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
nextOverrides.mappings[item.spec_id] = {
|
|
1143
|
+
scene_id: item.to_scene_id,
|
|
1144
|
+
source: 'scene-backfill',
|
|
1145
|
+
rule_id: item.rule_id,
|
|
1146
|
+
updated_at: now
|
|
1147
|
+
};
|
|
1148
|
+
changedCount += 1;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
const totalsByTargetScene = {};
|
|
1152
|
+
for (const item of assignmentPlan) {
|
|
1153
|
+
totalsByTargetScene[item.to_scene_id] = (totalsByTargetScene[item.to_scene_id] || 0) + 1;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
const payload = {
|
|
1157
|
+
mode: 'studio-scene-backfill',
|
|
1158
|
+
success: true,
|
|
1159
|
+
generated_at: now,
|
|
1160
|
+
policy_path: loaded.policy_path,
|
|
1161
|
+
policy_loaded_from: loaded.loaded_from,
|
|
1162
|
+
source_scene: sourceScene,
|
|
1163
|
+
active_only: activeOnly,
|
|
1164
|
+
apply,
|
|
1165
|
+
refresh_governance: refreshGovernance,
|
|
1166
|
+
override_file: overrideFile,
|
|
1167
|
+
summary: {
|
|
1168
|
+
candidate_count: assignmentPlan.length,
|
|
1169
|
+
changed_count: changedCount,
|
|
1170
|
+
target_scene_count: Object.keys(totalsByTargetScene).length
|
|
1171
|
+
},
|
|
1172
|
+
targets: totalsByTargetScene,
|
|
1173
|
+
assignments: assignmentPlan
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
if (apply) {
|
|
1177
|
+
const overrideAbsolutePath = path.join(projectPath, overrideFile);
|
|
1178
|
+
await fileSystem.ensureDir(path.dirname(overrideAbsolutePath));
|
|
1179
|
+
const serialized = {
|
|
1180
|
+
schema_version: '1.0',
|
|
1181
|
+
generated_at: nextOverrides.generated_at || now,
|
|
1182
|
+
updated_at: now,
|
|
1183
|
+
source: 'studio-scene-backfill',
|
|
1184
|
+
mappings: nextOverrides.mappings
|
|
1185
|
+
};
|
|
1186
|
+
await fileSystem.writeJson(overrideAbsolutePath, serialized, { spaces: 2 });
|
|
1187
|
+
payload.override_written = overrideFile;
|
|
1188
|
+
if (refreshGovernance) {
|
|
1189
|
+
const refreshed = await runStudioSpecGovernance({
|
|
1190
|
+
apply: true
|
|
1191
|
+
}, {
|
|
1192
|
+
projectPath,
|
|
1193
|
+
fileSystem
|
|
1194
|
+
});
|
|
1195
|
+
payload.governance = {
|
|
1196
|
+
status: refreshed.summary ? refreshed.summary.status : null,
|
|
1197
|
+
alert_count: refreshed.summary ? Number(refreshed.summary.alert_count || 0) : 0,
|
|
1198
|
+
report_file: refreshed.report_file || null,
|
|
1199
|
+
scene_index_file: refreshed.scene_index_file || null
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
return payload;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
892
1207
|
async function runStudioSpecGovernance(options = {}, dependencies = {}) {
|
|
893
1208
|
const projectPath = dependencies.projectPath || process.cwd();
|
|
894
1209
|
const fileSystem = dependencies.fileSystem || fs;
|
|
@@ -897,11 +1212,14 @@ async function runStudioSpecGovernance(options = {}, dependencies = {}) {
|
|
|
897
1212
|
: await loadStudioIntakePolicy(projectPath, fileSystem);
|
|
898
1213
|
const policy = loaded.policy;
|
|
899
1214
|
const governance = policy.governance || DEFAULT_STUDIO_INTAKE_POLICY.governance;
|
|
1215
|
+
const backfill = policy.backfill || DEFAULT_STUDIO_INTAKE_POLICY.backfill;
|
|
900
1216
|
const apply = options.apply !== false;
|
|
901
1217
|
const sceneFilter = normalizeText(options.scene_id || options.sceneId || options.scene);
|
|
1218
|
+
const overrideFile = normalizeText(backfill.override_file) || DEFAULT_STUDIO_SCENE_OVERRIDE_PATH;
|
|
902
1219
|
|
|
903
1220
|
const records = await scanSpecPortfolio(projectPath, {
|
|
904
|
-
stale_days: governance.stale_days
|
|
1221
|
+
stale_days: governance.stale_days,
|
|
1222
|
+
override_file: overrideFile
|
|
905
1223
|
}, {
|
|
906
1224
|
fileSystem
|
|
907
1225
|
});
|
|
@@ -920,7 +1238,10 @@ async function runStudioSpecGovernance(options = {}, dependencies = {}) {
|
|
|
920
1238
|
policy_path: loaded.policy_path,
|
|
921
1239
|
policy_loaded_from: loaded.loaded_from,
|
|
922
1240
|
policy: {
|
|
923
|
-
governance
|
|
1241
|
+
governance,
|
|
1242
|
+
backfill: {
|
|
1243
|
+
override_file: overrideFile
|
|
1244
|
+
}
|
|
924
1245
|
},
|
|
925
1246
|
summary: {
|
|
926
1247
|
scene_count: summary.scene_count,
|
|
@@ -974,6 +1295,7 @@ async function runStudioSpecGovernance(options = {}, dependencies = {}) {
|
|
|
974
1295
|
module.exports = {
|
|
975
1296
|
DEFAULT_STUDIO_INTAKE_POLICY_PATH,
|
|
976
1297
|
DEFAULT_STUDIO_INTAKE_POLICY,
|
|
1298
|
+
DEFAULT_STUDIO_SCENE_OVERRIDE_PATH,
|
|
977
1299
|
DEFAULT_STUDIO_PORTFOLIO_REPORT,
|
|
978
1300
|
DEFAULT_STUDIO_SCENE_INDEX,
|
|
979
1301
|
normalizeStudioIntakePolicy,
|
|
@@ -986,6 +1308,7 @@ module.exports = {
|
|
|
986
1308
|
parseTasksProgress,
|
|
987
1309
|
scanSpecPortfolio,
|
|
988
1310
|
buildSceneGovernanceReport,
|
|
1311
|
+
runStudioSceneBackfill,
|
|
989
1312
|
runStudioSpecGovernance,
|
|
990
1313
|
tokenizeText,
|
|
991
1314
|
computeJaccard
|
|
@@ -92,6 +92,7 @@ const STUDIO_INTAKE_POLICY_DEFAULTS = Object.freeze({
|
|
|
92
92
|
enabled: true,
|
|
93
93
|
auto_create_spec: true,
|
|
94
94
|
force_spec_for_studio_plan: true,
|
|
95
|
+
allow_manual_spec_override: false,
|
|
95
96
|
prefer_existing_scene_spec: true,
|
|
96
97
|
related_spec_min_score: 45,
|
|
97
98
|
allow_new_spec_when_goal_diverges: true,
|
|
@@ -112,9 +113,25 @@ const STUDIO_INTAKE_POLICY_DEFAULTS = Object.freeze({
|
|
|
112
113
|
},
|
|
113
114
|
governance: {
|
|
114
115
|
auto_run_on_plan: true,
|
|
116
|
+
require_auto_on_plan: true,
|
|
115
117
|
max_active_specs_per_scene: 3,
|
|
116
118
|
stale_days: 14,
|
|
117
119
|
duplicate_similarity_threshold: 0.66
|
|
120
|
+
},
|
|
121
|
+
backfill: {
|
|
122
|
+
enabled: true,
|
|
123
|
+
active_only_default: true,
|
|
124
|
+
default_scene_id: 'scene.sce-core',
|
|
125
|
+
override_file: '.sce/spec-governance/spec-scene-overrides.json',
|
|
126
|
+
rules: [
|
|
127
|
+
{ id: 'moqui-core', scene_id: 'scene.moqui-core', keywords: ['moqui'] },
|
|
128
|
+
{ id: 'orchestration', scene_id: 'scene.sce-orchestration', keywords: ['orchestrate', 'runtime', 'controller', 'batch', 'parallel'] },
|
|
129
|
+
{ id: 'template-registry', scene_id: 'scene.sce-template-registry', keywords: ['template', 'scene-package', 'registry', 'catalog', 'scene-template'] },
|
|
130
|
+
{ id: 'spec-governance', scene_id: 'scene.sce-spec-governance', keywords: ['spec', 'gate', 'ontology', 'governance', 'policy'] },
|
|
131
|
+
{ id: 'quality', scene_id: 'scene.sce-quality', keywords: ['test', 'quality', 'stability', 'jest', 'coverage'] },
|
|
132
|
+
{ id: 'docs', scene_id: 'scene.sce-docs', keywords: ['document', 'documentation', 'onboarding', 'guide'] },
|
|
133
|
+
{ id: 'platform', scene_id: 'scene.sce-platform', keywords: ['adopt', 'upgrade', 'workspace', 'repo', 'environment', 'devops', 'release', 'github', 'npm'] }
|
|
134
|
+
]
|
|
118
135
|
}
|
|
119
136
|
});
|
|
120
137
|
|
|
@@ -172,6 +189,7 @@ const TAKEOVER_DEFAULTS = Object.freeze({
|
|
|
172
189
|
enabled: true,
|
|
173
190
|
auto_create_spec: true,
|
|
174
191
|
force_spec_for_studio_plan: true,
|
|
192
|
+
allow_manual_spec_override: false,
|
|
175
193
|
prefer_existing_scene_spec: true,
|
|
176
194
|
related_spec_min_score: 45,
|
|
177
195
|
allow_new_spec_when_goal_diverges: true,
|
|
@@ -179,9 +197,16 @@ const TAKEOVER_DEFAULTS = Object.freeze({
|
|
|
179
197
|
goal_missing_strategy: 'create_for_tracking',
|
|
180
198
|
governance: {
|
|
181
199
|
auto_run_on_plan: true,
|
|
200
|
+
require_auto_on_plan: true,
|
|
182
201
|
max_active_specs_per_scene: 3,
|
|
183
202
|
stale_days: 14,
|
|
184
203
|
duplicate_similarity_threshold: 0.66
|
|
204
|
+
},
|
|
205
|
+
backfill: {
|
|
206
|
+
enabled: true,
|
|
207
|
+
active_only_default: true,
|
|
208
|
+
default_scene_id: 'scene.sce-core',
|
|
209
|
+
override_file: '.sce/spec-governance/spec-scene-overrides.json'
|
|
185
210
|
}
|
|
186
211
|
},
|
|
187
212
|
debug_policy: {
|
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"enabled": true,
|
|
4
4
|
"auto_create_spec": true,
|
|
5
5
|
"force_spec_for_studio_plan": true,
|
|
6
|
+
"allow_manual_spec_override": false,
|
|
6
7
|
"prefer_existing_scene_spec": true,
|
|
7
8
|
"related_spec_min_score": 45,
|
|
8
9
|
"allow_new_spec_when_goal_diverges": true,
|
|
@@ -61,8 +62,93 @@
|
|
|
61
62
|
},
|
|
62
63
|
"governance": {
|
|
63
64
|
"auto_run_on_plan": true,
|
|
65
|
+
"require_auto_on_plan": true,
|
|
64
66
|
"max_active_specs_per_scene": 3,
|
|
65
67
|
"stale_days": 14,
|
|
66
68
|
"duplicate_similarity_threshold": 0.66
|
|
69
|
+
},
|
|
70
|
+
"backfill": {
|
|
71
|
+
"enabled": true,
|
|
72
|
+
"active_only_default": true,
|
|
73
|
+
"default_scene_id": "scene.sce-core",
|
|
74
|
+
"override_file": ".sce/spec-governance/spec-scene-overrides.json",
|
|
75
|
+
"rules": [
|
|
76
|
+
{
|
|
77
|
+
"id": "moqui-core",
|
|
78
|
+
"scene_id": "scene.moqui-core",
|
|
79
|
+
"keywords": [
|
|
80
|
+
"moqui"
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "orchestration",
|
|
85
|
+
"scene_id": "scene.sce-orchestration",
|
|
86
|
+
"keywords": [
|
|
87
|
+
"orchestrate",
|
|
88
|
+
"runtime",
|
|
89
|
+
"controller",
|
|
90
|
+
"batch",
|
|
91
|
+
"parallel"
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "template-registry",
|
|
96
|
+
"scene_id": "scene.sce-template-registry",
|
|
97
|
+
"keywords": [
|
|
98
|
+
"template",
|
|
99
|
+
"scene-package",
|
|
100
|
+
"registry",
|
|
101
|
+
"catalog",
|
|
102
|
+
"scene-template"
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "spec-governance",
|
|
107
|
+
"scene_id": "scene.sce-spec-governance",
|
|
108
|
+
"keywords": [
|
|
109
|
+
"spec",
|
|
110
|
+
"gate",
|
|
111
|
+
"ontology",
|
|
112
|
+
"governance",
|
|
113
|
+
"policy"
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "quality",
|
|
118
|
+
"scene_id": "scene.sce-quality",
|
|
119
|
+
"keywords": [
|
|
120
|
+
"test",
|
|
121
|
+
"quality",
|
|
122
|
+
"stability",
|
|
123
|
+
"jest",
|
|
124
|
+
"coverage"
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"id": "docs",
|
|
129
|
+
"scene_id": "scene.sce-docs",
|
|
130
|
+
"keywords": [
|
|
131
|
+
"document",
|
|
132
|
+
"documentation",
|
|
133
|
+
"onboarding",
|
|
134
|
+
"guide"
|
|
135
|
+
]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"id": "platform",
|
|
139
|
+
"scene_id": "scene.sce-platform",
|
|
140
|
+
"keywords": [
|
|
141
|
+
"adopt",
|
|
142
|
+
"upgrade",
|
|
143
|
+
"workspace",
|
|
144
|
+
"repo",
|
|
145
|
+
"environment",
|
|
146
|
+
"devops",
|
|
147
|
+
"release",
|
|
148
|
+
"github",
|
|
149
|
+
"npm"
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
]
|
|
67
153
|
}
|
|
68
154
|
}
|