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.
- package/CHANGELOG.md +21 -0
- package/README.md +7 -2
- package/README.zh.md +7 -2
- package/bin/scene-capability-engine.js +10 -0
- package/docs/app-intent-apply-contract.md +263 -0
- package/docs/autonomous-control-guide.md +12 -0
- package/docs/command-reference.md +15 -0
- package/docs/examples/app-intent-phase1/.sce/app/collections/planning-workbench.json +34 -0
- package/docs/examples/app-intent-phase1/.sce/app/collections/sales-workbench.json +40 -0
- package/docs/examples/app-intent-phase1/.sce/app/collections/warehouse-kiosk.json +35 -0
- package/docs/examples/app-intent-phase1/.sce/app/scene-profiles/planning-desktop.json +23 -0
- package/docs/examples/app-intent-phase1/.sce/app/scene-profiles/sales-desktop.json +24 -0
- package/docs/examples/app-intent-phase1/.sce/app/scene-profiles/warehouse-tablet.json +23 -0
- package/docs/examples/app-intent-phase1/README.md +29 -0
- package/docs/magicball-app-collection-phase-1.md +37 -0
- package/docs/magicball-cli-invocation-examples.md +1 -0
- package/docs/magicball-integration-doc-index.md +13 -6
- package/docs/magicball-sce-adaptation-guide.md +2 -0
- package/docs/releases/README.md +2 -0
- package/docs/releases/v3.6.56.md +19 -0
- package/docs/releases/v3.6.57.md +19 -0
- package/docs/spec-workflow.md +42 -0
- package/docs/zh/releases/README.md +2 -0
- package/docs/zh/releases/v3.6.56.md +19 -0
- package/docs/zh/releases/v3.6.57.md +19 -0
- package/lib/commands/spec-gate.js +57 -6
- package/lib/commands/spec-pipeline.js +13 -4
- package/lib/commands/spec-strategy.js +73 -0
- package/lib/problem/project-problem-projection.js +43 -0
- package/lib/spec/complexity-strategy.js +636 -0
- package/package.json +1 -1
- package/template/.sce/README.md +2 -2
|
@@ -84,6 +84,22 @@ Recommended split:
|
|
|
84
84
|
|
|
85
85
|
This boundary matters because SQLite is suitable for local query-heavy facts, but it should not become the only source of truth for portable shared intent.
|
|
86
86
|
|
|
87
|
+
## Copy-ready examples
|
|
88
|
+
|
|
89
|
+
Sample assets are available here:
|
|
90
|
+
|
|
91
|
+
- `docs/examples/app-intent-phase1/.sce/app/collections/*.json`
|
|
92
|
+
- `docs/examples/app-intent-phase1/.sce/app/scene-profiles/*.json`
|
|
93
|
+
- `docs/examples/app-intent-phase1/README.md`
|
|
94
|
+
|
|
95
|
+
These examples are intentionally realistic but still generic:
|
|
96
|
+
|
|
97
|
+
- sales desktop workspace
|
|
98
|
+
- planning desktop workspace
|
|
99
|
+
- warehouse tablet workspace
|
|
100
|
+
|
|
101
|
+
Use them as a starting point, then replace app keys and business metadata with project-specific values.
|
|
102
|
+
|
|
87
103
|
## Recommended phase-1 command shape
|
|
88
104
|
|
|
89
105
|
Phase-1 overall command surface:
|
|
@@ -108,6 +124,10 @@ Execution rule:
|
|
|
108
124
|
- local override updates now also have an explicit CLI governance surface instead of requiring manual file edits
|
|
109
125
|
- execution should reuse the existing app runtime path
|
|
110
126
|
|
|
127
|
+
JSON response contract:
|
|
128
|
+
|
|
129
|
+
- `docs/app-intent-apply-contract.md`
|
|
130
|
+
|
|
111
131
|
## Frontend implication for MagicBall
|
|
112
132
|
|
|
113
133
|
MagicBall should keep using current per-app runtime controls until this phase ships.
|
|
@@ -128,6 +148,23 @@ Once phase-1 lands, the preferred top-level interaction should become:
|
|
|
128
148
|
3. explicitly apply the plan
|
|
129
149
|
4. continue using per-app runtime controls for detail views
|
|
130
150
|
|
|
151
|
+
## Phase 2 direction
|
|
152
|
+
|
|
153
|
+
Phase-2 should be planned as lightweight user-intent sync, not as device-state sync.
|
|
154
|
+
|
|
155
|
+
Recommended boundary:
|
|
156
|
+
|
|
157
|
+
- sync shared `app_collection` / `scene_profile` selections or bindings
|
|
158
|
+
- do not sync `device_installation` facts as the cross-device source of truth
|
|
159
|
+
- keep `device_override` local unless a project explicitly introduces a higher-level policy mechanism
|
|
160
|
+
|
|
161
|
+
Recommended deliverables for phase-2 planning:
|
|
162
|
+
|
|
163
|
+
1. user binding model for scene/profile selection
|
|
164
|
+
2. device-aware resolution order for `user intent + local override + capability tags`
|
|
165
|
+
3. pull/push sync semantics with conflict visibility rather than silent overwrite
|
|
166
|
+
4. explicit non-goal: do not force all devices into the same installed app set
|
|
167
|
+
|
|
131
168
|
## Practical conclusion
|
|
132
169
|
|
|
133
170
|
SCE should support this direction, but it should do so by extending the current app/runtime model rather than replacing it.
|
|
@@ -44,6 +44,7 @@ Optional local override input:
|
|
|
44
44
|
- `.sce/state/device/device-override.json`
|
|
45
45
|
- use this for per-device add/remove exceptions instead of mutating shared collection/workspace definitions
|
|
46
46
|
- update it explicitly via `sce device override upsert --input <json> --json`
|
|
47
|
+
- copy-ready collection/workspace examples live under `docs/examples/app-intent-phase1/.sce/app/...`
|
|
47
48
|
|
|
48
49
|
Example local override patch:
|
|
49
50
|
```json
|
|
@@ -15,6 +15,7 @@ It is a navigation layer, not a new source of truth.
|
|
|
15
15
|
These are the current first-line integration documents:
|
|
16
16
|
- `docs/magicball-sce-adaptation-guide.md`
|
|
17
17
|
- `docs/magicball-app-collection-phase-1.md`
|
|
18
|
+
- `docs/app-intent-apply-contract.md`
|
|
18
19
|
- `docs/magicball-ui-surface-checklist.md`
|
|
19
20
|
- `docs/magicball-mode-home-and-ontology-empty-state-playbook.md`
|
|
20
21
|
- `docs/magicball-frontend-state-and-command-mapping.md`
|
|
@@ -37,28 +38,32 @@ These are the current first-line integration documents:
|
|
|
37
38
|
- current device / collection / workspace / override baseline
|
|
38
39
|
- shipped phase-1 install orchestration contract
|
|
39
40
|
|
|
40
|
-
4. `docs/
|
|
41
|
+
4. `docs/app-intent-apply-contract.md`
|
|
42
|
+
- stable JSON response contract for `collection apply` and `scene workspace apply`
|
|
43
|
+
- execution/blocking/preflight semantics
|
|
44
|
+
|
|
45
|
+
5. `docs/magicball-mode-home-and-ontology-empty-state-playbook.md`
|
|
41
46
|
- two active frontend-sensitive defaults
|
|
42
47
|
- serialized mode-home loading
|
|
43
48
|
- ontology empty-state and starter-seed behavior
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
6. `docs/magicball-frontend-state-and-command-mapping.md`
|
|
46
51
|
- page state ownership
|
|
47
52
|
- command-to-action mapping
|
|
48
53
|
- error and retry boundaries
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
7. `docs/magicball-write-auth-adaptation-guide.md`
|
|
51
56
|
- write authorization and lease handling
|
|
52
57
|
|
|
53
|
-
|
|
58
|
+
8. `docs/magicball-task-feedback-timeline-guide.md`
|
|
54
59
|
- task feedback cards
|
|
55
60
|
- timeline view integration
|
|
56
61
|
|
|
57
|
-
|
|
62
|
+
9. `docs/magicball-cli-invocation-examples.md`
|
|
58
63
|
- copy-ready CLI examples
|
|
59
64
|
- wrapper and smoke verification patterns
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
10. `docs/magicball-integration-issue-tracker.md`
|
|
62
67
|
- current cross-project truth
|
|
63
68
|
- only open issues, active decisions, and verified resolutions
|
|
64
69
|
|
|
@@ -68,6 +73,7 @@ These are the current first-line integration documents:
|
|
|
68
73
|
Use:
|
|
69
74
|
- `docs/magicball-sce-adaptation-guide.md`
|
|
70
75
|
- `docs/magicball-app-collection-phase-1.md`
|
|
76
|
+
- `docs/app-intent-apply-contract.md`
|
|
71
77
|
- `docs/magicball-mode-home-and-ontology-empty-state-playbook.md`
|
|
72
78
|
- `docs/magicball-frontend-state-and-command-mapping.md`
|
|
73
79
|
|
|
@@ -102,6 +108,7 @@ Use:
|
|
|
102
108
|
| --- | --- | --- |
|
|
103
109
|
| `magicball-sce-adaptation-guide.md` | main overview | low |
|
|
104
110
|
| `magicball-app-collection-phase-1.md` | next-phase install model | medium |
|
|
111
|
+
| `app-intent-apply-contract.md` | apply JSON contract | medium |
|
|
105
112
|
| `magicball-adaptation-task-checklist-v1.md` | execution checklist | medium |
|
|
106
113
|
| `magicball-mode-home-and-ontology-empty-state-playbook.md` | frontend behavior policy | medium |
|
|
107
114
|
| `magicball-frontend-state-and-command-mapping.md` | frontend implementation mapping | medium |
|
|
@@ -13,6 +13,7 @@ Implementation detail should live in the specialized documents listed below.
|
|
|
13
13
|
Use these documents together:
|
|
14
14
|
- `docs/magicball-integration-doc-index.md`
|
|
15
15
|
- `docs/magicball-app-collection-phase-1.md`
|
|
16
|
+
- `docs/app-intent-apply-contract.md`
|
|
16
17
|
- `docs/magicball-ui-surface-checklist.md`
|
|
17
18
|
- `docs/magicball-mode-home-and-ontology-empty-state-playbook.md`
|
|
18
19
|
- `docs/magicball-frontend-state-and-command-mapping.md`
|
|
@@ -58,6 +59,7 @@ Planned phase-1 position:
|
|
|
58
59
|
|
|
59
60
|
Reference:
|
|
60
61
|
- `docs/magicball-app-collection-phase-1.md`
|
|
62
|
+
- `docs/app-intent-apply-contract.md`
|
|
61
63
|
|
|
62
64
|
## Core Integration Positioning
|
|
63
65
|
|
package/docs/releases/README.md
CHANGED
|
@@ -9,6 +9,8 @@ This directory stores release-facing documents:
|
|
|
9
9
|
## Archived Versions
|
|
10
10
|
|
|
11
11
|
- [Release checklist](../release-checklist.md)
|
|
12
|
+
- [v3.6.57 release notes](./v3.6.57.md)
|
|
13
|
+
- [v3.6.56 release notes](./v3.6.56.md)
|
|
12
14
|
- [v3.6.55 release notes](./v3.6.55.md)
|
|
13
15
|
- [v3.6.54 release notes](./v3.6.54.md)
|
|
14
16
|
- [v3.6.48 release notes](./v3.6.48.md)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# v3.6.56 Release Notes
|
|
2
|
+
|
|
3
|
+
Release date: 2026-03-17
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- Added read-only `sce spec strategy assess` to decide whether a problem should stay `single-spec` or escalate to `multi-spec-program` / `research-program`.
|
|
8
|
+
- Added a formal strategy Spec for complex-problem program escalation so SCE can stop forcing highly entangled work through one oversized Spec.
|
|
9
|
+
- Added copy-ready phase-1 app intent examples plus a stable `apply` JSON contract document so scene-oriented install management can be adopted directly instead of inferred from source code.
|
|
10
|
+
|
|
11
|
+
## Validation
|
|
12
|
+
|
|
13
|
+
- `npx jest tests/unit/commands/spec-strategy.test.js --runInBand`
|
|
14
|
+
- `node bin/sce.js --skip-steering-check --no-version-check spec strategy assess --goal "fix login button alignment in application home page" --json`
|
|
15
|
+
- `node scripts/release-doc-version-audit.js --fail-on-error`
|
|
16
|
+
|
|
17
|
+
## Release Notes
|
|
18
|
+
|
|
19
|
+
- This patch turns SCE's existing multi-Spec and close-loop-program runtime into a better governed path for complex work. The key addition is not another executor, but an explicit strategy assessment layer that can say when one Spec is still enough and when the problem should first be treated as a coordinated program or a research-first clarification portfolio.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# v3.6.57 Release Notes
|
|
2
|
+
|
|
3
|
+
Release date: 2026-03-17
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- Added a read-only `strategy_assessment` block to single-Spec `sce spec gate run` and `sce spec pipeline run` output.
|
|
8
|
+
- Human-readable gate and pipeline flows now explicitly warn when a problem no longer fits one Spec and should escalate to `multi-spec-program` or `research-program`.
|
|
9
|
+
- Kept the integration conservative: SCE now surfaces the strategy mismatch early, but it does not auto-reroute execution or silently create more Specs.
|
|
10
|
+
- Fixed shared project problem projection sync to be idempotent when payload content is unchanged, preventing release/push governance from dirtying the worktree with timestamp-only rewrites.
|
|
11
|
+
|
|
12
|
+
## Validation
|
|
13
|
+
|
|
14
|
+
- `npx jest tests/unit/commands/spec-gate.test.js tests/unit/commands/spec-pipeline.test.js --runInBand`
|
|
15
|
+
- `node scripts/release-doc-version-audit.js --fail-on-error`
|
|
16
|
+
|
|
17
|
+
## Release Notes
|
|
18
|
+
|
|
19
|
+
- This patch closes the last obvious gap in the complex-problem strategy line added in `v3.6.56`. SCE could already assess whether a goal or Spec was too broad for one Spec; now the same judgment is visible inside the normal single-Spec execution path, so teams get an explicit warning before continuing blind decomposition in the wrong container. It also fixes a co-work publication edge case where project-shared problem projection refresh could dirty the repository even when nothing meaningful changed.
|
package/docs/spec-workflow.md
CHANGED
|
@@ -115,6 +115,48 @@ sce spec domain validate --spec <spec-id> --fail-on-gap --json
|
|
|
115
115
|
|
|
116
116
|
This is the default way to avoid wrong-direction implementation and to force evidence-based correction loops.
|
|
117
117
|
|
|
118
|
+
## When One Spec Is Not Enough
|
|
119
|
+
|
|
120
|
+
Not every problem should be forced into a single Spec.
|
|
121
|
+
|
|
122
|
+
Use this rule of thumb:
|
|
123
|
+
|
|
124
|
+
- simple problem: stay with one Spec
|
|
125
|
+
- medium complexity: split into a few implementation Specs and use orchestrate mode
|
|
126
|
+
- very complex problem: first treat it as a program, not as a single implementation Spec
|
|
127
|
+
|
|
128
|
+
Typical escalation signals:
|
|
129
|
+
|
|
130
|
+
- one Spec keeps expanding but still cannot produce stable executable tasks
|
|
131
|
+
- the problem spans multiple scenes, roles, or bounded contexts
|
|
132
|
+
- API/data contracts are still unclear enough that task breakdown would be guesswork
|
|
133
|
+
- business rules and policy boundaries are not yet settled
|
|
134
|
+
- a single problem-domain chain already shows multiple semi-independent clusters
|
|
135
|
+
|
|
136
|
+
Current SCE execution paths you can already use:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# read-only strategy assessment
|
|
140
|
+
sce spec strategy assess --goal "broad complex goal" --json
|
|
141
|
+
|
|
142
|
+
# multi-spec implementation program
|
|
143
|
+
sce spec bootstrap --specs "spec-a,spec-b" --max-parallel 3
|
|
144
|
+
|
|
145
|
+
# broad goal -> autonomous master/sub program decomposition
|
|
146
|
+
sce auto close-loop-program "broad complex goal" --program-goals 4 --json
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`sce spec gate run --spec <spec-id>` and `sce spec pipeline run --spec <spec-id>` now both carry a read-only strategy advisory for single-Spec runs. If the current Spec is assessed as `multi-spec-program` or `research-program`, SCE surfaces that explicitly instead of silently pushing more decomposition into the same Spec.
|
|
150
|
+
|
|
151
|
+
The strategic gap currently being formalized is:
|
|
152
|
+
|
|
153
|
+
- how SCE should assess this threshold explicitly
|
|
154
|
+
- when it should recommend `single-spec` vs `multi-spec-program` vs `research-program`
|
|
155
|
+
|
|
156
|
+
Reference Spec:
|
|
157
|
+
|
|
158
|
+
- `.sce/specs/129-00-complex-problem-program-escalation/`
|
|
159
|
+
|
|
118
160
|
---
|
|
119
161
|
|
|
120
162
|
## Stage 1: Requirements
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# v3.6.56 发布说明
|
|
2
|
+
|
|
3
|
+
发布日期:2026-03-17
|
|
4
|
+
|
|
5
|
+
## 重点变化
|
|
6
|
+
|
|
7
|
+
- 新增只读命令 `sce spec strategy assess`,用于判断问题应继续使用 `single-spec`,还是升级为 `multi-spec-program` / `research-program`。
|
|
8
|
+
- 新增复杂问题升级策略 Spec,使 SCE 不再默认把高度纠缠的问题硬塞进一个超大 Spec。
|
|
9
|
+
- 补充 phase-1 应用意图样例与稳定的 `apply` JSON 契约文档,让面向场景的安装管理能力可以直接落地,而不是只能靠读源码理解。
|
|
10
|
+
|
|
11
|
+
## 验证
|
|
12
|
+
|
|
13
|
+
- `npx jest tests/unit/commands/spec-strategy.test.js --runInBand`
|
|
14
|
+
- `node bin/sce.js --skip-steering-check --no-version-check spec strategy assess --goal "fix login button alignment in application home page" --json`
|
|
15
|
+
- `node scripts/release-doc-version-audit.js --fail-on-error`
|
|
16
|
+
|
|
17
|
+
## 发布说明
|
|
18
|
+
|
|
19
|
+
- 这个补丁版的重点不是再造一个执行器,而是给 SCE 已有的多 Spec / close-loop-program 执行能力补上策略层。现在 SCE 可以更明确地判断:一个问题是否还适合留在单 Spec,还是应该先升级为协调型 program,或者先进入 research-first 的澄清拆分路径。
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# v3.6.57 发布说明
|
|
2
|
+
|
|
3
|
+
发布日期:2026-03-17
|
|
4
|
+
|
|
5
|
+
## 重点变化
|
|
6
|
+
|
|
7
|
+
- 为单 spec 的 `sce spec gate run` 和 `sce spec pipeline run` 输出增加只读 `strategy_assessment` 字段。
|
|
8
|
+
- 当问题已经不适合继续塞进一个 spec,而应升级为 `multi-spec-program` 或 `research-program` 时,gate 和 pipeline 的人类可读输出会明确给出提示。
|
|
9
|
+
- 这次集成保持保守:SCE 会尽早暴露策略不匹配,但不会擅自改道执行,也不会静默创建更多 spec。
|
|
10
|
+
- 修复共享问题投影同步的幂等性;当内容未变化时,不再只因 `generated_at` 刷新就把仓库重新写脏,避免 push / 发版治理被时间戳漂移误伤。
|
|
11
|
+
|
|
12
|
+
## 验证
|
|
13
|
+
|
|
14
|
+
- `npx jest tests/unit/commands/spec-gate.test.js tests/unit/commands/spec-pipeline.test.js --runInBand`
|
|
15
|
+
- `node scripts/release-doc-version-audit.js --fail-on-error`
|
|
16
|
+
|
|
17
|
+
## 发布说明
|
|
18
|
+
|
|
19
|
+
- 这个补丁版把 `v3.6.56` 刚加上的复杂问题策略评估真正接入到了日常单 spec 主路径。现在 SCE 不只是“能评估”,而是会在正常的 gate / pipeline 过程中显式提醒:当前问题已经不该继续在一个 spec 里盲拆,而应升级为更合适的 program 路径。同时也修掉了 co-work 发布链路上的一个实际阻断点:共享问题投影在内容不变时不应因为时间戳刷新而制造脏工作区。
|
|
@@ -15,9 +15,11 @@ const { ResultEmitter } = require('../spec-gate/result-emitter');
|
|
|
15
15
|
const { SessionStore } = require('../runtime/session-store');
|
|
16
16
|
const { resolveSpecSceneBinding } = require('../runtime/scene-session-binding');
|
|
17
17
|
const { bindMultiSpecSceneSession } = require('../runtime/multi-spec-scene-session');
|
|
18
|
+
const { assessComplexityStrategy } = require('../spec/complexity-strategy');
|
|
18
19
|
|
|
19
20
|
async function runSpecGate(options = {}, dependencies = {}) {
|
|
20
21
|
const projectPath = dependencies.projectPath || process.cwd();
|
|
22
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
21
23
|
const sessionStore = dependencies.sessionStore || new SessionStore(projectPath);
|
|
22
24
|
const specTargets = parseSpecTargets(options);
|
|
23
25
|
|
|
@@ -43,7 +45,7 @@ async function runSpecGate(options = {}, dependencies = {}) {
|
|
|
43
45
|
})
|
|
44
46
|
}, {
|
|
45
47
|
projectPath,
|
|
46
|
-
fileSystem
|
|
48
|
+
fileSystem,
|
|
47
49
|
sessionStore
|
|
48
50
|
});
|
|
49
51
|
}
|
|
@@ -51,7 +53,7 @@ async function runSpecGate(options = {}, dependencies = {}) {
|
|
|
51
53
|
const specId = specTargets[0];
|
|
52
54
|
|
|
53
55
|
const specPath = path.join(projectPath, '.sce', 'specs', specId);
|
|
54
|
-
if (!await
|
|
56
|
+
if (!await fileSystem.pathExists(specPath)) {
|
|
55
57
|
throw new Error(`Spec not found: ${specId}`);
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -60,7 +62,7 @@ async function runSpecGate(options = {}, dependencies = {}) {
|
|
|
60
62
|
allowNoScene: false
|
|
61
63
|
}, {
|
|
62
64
|
projectPath,
|
|
63
|
-
fileSystem
|
|
65
|
+
fileSystem,
|
|
64
66
|
sessionStore
|
|
65
67
|
});
|
|
66
68
|
const linked = await sessionStore.startSpecSession({
|
|
@@ -83,13 +85,22 @@ async function runSpecGate(options = {}, dependencies = {}) {
|
|
|
83
85
|
policy
|
|
84
86
|
});
|
|
85
87
|
|
|
86
|
-
const
|
|
88
|
+
const gateResult = await engine.evaluate({ specId });
|
|
89
|
+
const result = {
|
|
90
|
+
...gateResult,
|
|
91
|
+
strategy_assessment: await buildStrategyAssessment(specId, {
|
|
92
|
+
projectPath,
|
|
93
|
+
fileSystem,
|
|
94
|
+
strategyAssessor: dependencies.strategyAssessor
|
|
95
|
+
})
|
|
96
|
+
};
|
|
87
97
|
const emitter = dependencies.emitter || new ResultEmitter(projectPath);
|
|
88
98
|
const emitted = await emitter.emit(result, {
|
|
89
99
|
json: options.json,
|
|
90
100
|
out: options.out,
|
|
91
101
|
silent: options.silent
|
|
92
102
|
});
|
|
103
|
+
emitStrategyAdvisory(result.strategy_assessment, options);
|
|
93
104
|
|
|
94
105
|
const decisionStatus = result.decision === 'no-go' ? 'failed' : 'completed';
|
|
95
106
|
await sessionStore.completeSpecSession({
|
|
@@ -101,7 +112,8 @@ async function runSpecGate(options = {}, dependencies = {}) {
|
|
|
101
112
|
spec: specId,
|
|
102
113
|
decision: result.decision,
|
|
103
114
|
score: result.score,
|
|
104
|
-
report_path: emitted.outputPath || null
|
|
115
|
+
report_path: emitted.outputPath || null,
|
|
116
|
+
strategy_decision: result.strategy_assessment ? result.strategy_assessment.decision : null
|
|
105
117
|
}
|
|
106
118
|
});
|
|
107
119
|
|
|
@@ -132,6 +144,43 @@ async function runSpecGate(options = {}, dependencies = {}) {
|
|
|
132
144
|
}
|
|
133
145
|
}
|
|
134
146
|
|
|
147
|
+
async function buildStrategyAssessment(specId, dependencies = {}) {
|
|
148
|
+
const assess = dependencies.strategyAssessor || assessComplexityStrategy;
|
|
149
|
+
try {
|
|
150
|
+
return await assess({
|
|
151
|
+
spec: specId
|
|
152
|
+
}, {
|
|
153
|
+
projectPath: dependencies.projectPath,
|
|
154
|
+
fileSystem: dependencies.fileSystem
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
return {
|
|
158
|
+
decision: 'assessment-unavailable',
|
|
159
|
+
decision_reason: error.message,
|
|
160
|
+
advisory_only: true
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function emitStrategyAdvisory(strategyAssessment, options = {}) {
|
|
166
|
+
if (!strategyAssessment || options.json || options.silent) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!['multi-spec-program', 'research-program'].includes(strategyAssessment.decision)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log();
|
|
175
|
+
console.log(chalk.yellow('⚠ Strategy Advisory'));
|
|
176
|
+
console.log(` ${strategyAssessment.decision}: ${strategyAssessment.decision_reason}`);
|
|
177
|
+
if (Array.isArray(strategyAssessment.next_actions)) {
|
|
178
|
+
strategyAssessment.next_actions.forEach(action => {
|
|
179
|
+
console.log(` - ${action}`);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
135
184
|
async function generateSpecGatePolicyTemplate(options = {}, dependencies = {}) {
|
|
136
185
|
const projectPath = dependencies.projectPath || process.cwd();
|
|
137
186
|
const loader = dependencies.policyLoader || new PolicyLoader(projectPath);
|
|
@@ -220,5 +269,7 @@ module.exports = {
|
|
|
220
269
|
registerSpecGateCommand,
|
|
221
270
|
runSpecGate,
|
|
222
271
|
generateSpecGatePolicyTemplate,
|
|
223
|
-
_parseSpecTargets
|
|
272
|
+
_parseSpecTargets,
|
|
273
|
+
buildStrategyAssessment,
|
|
274
|
+
emitStrategyAdvisory
|
|
224
275
|
};
|
|
@@ -13,9 +13,11 @@ const { createDefaultStageAdapters } = require('../spec/pipeline/stage-adapters'
|
|
|
13
13
|
const { SessionStore } = require('../runtime/session-store');
|
|
14
14
|
const { resolveSpecSceneBinding } = require('../runtime/scene-session-binding');
|
|
15
15
|
const { bindMultiSpecSceneSession } = require('../runtime/multi-spec-scene-session');
|
|
16
|
+
const { buildStrategyAssessment, emitStrategyAdvisory } = require('./spec-gate');
|
|
16
17
|
|
|
17
18
|
async function runSpecPipeline(options = {}, dependencies = {}) {
|
|
18
19
|
const projectPath = dependencies.projectPath || process.cwd();
|
|
20
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
19
21
|
const sessionStore = dependencies.sessionStore || new SessionStore(projectPath);
|
|
20
22
|
const specTargets = parseSpecTargets(options);
|
|
21
23
|
if (specTargets.length === 0) {
|
|
@@ -40,7 +42,7 @@ async function runSpecPipeline(options = {}, dependencies = {}) {
|
|
|
40
42
|
})
|
|
41
43
|
}, {
|
|
42
44
|
projectPath,
|
|
43
|
-
fileSystem
|
|
45
|
+
fileSystem,
|
|
44
46
|
sessionStore
|
|
45
47
|
});
|
|
46
48
|
}
|
|
@@ -48,7 +50,7 @@ async function runSpecPipeline(options = {}, dependencies = {}) {
|
|
|
48
50
|
const specId = specTargets[0];
|
|
49
51
|
|
|
50
52
|
const specPath = path.join(projectPath, '.sce', 'specs', specId);
|
|
51
|
-
if (!await
|
|
53
|
+
if (!await fileSystem.pathExists(specPath)) {
|
|
52
54
|
throw new Error(`Spec not found: ${specId}`);
|
|
53
55
|
}
|
|
54
56
|
|
|
@@ -57,7 +59,7 @@ async function runSpecPipeline(options = {}, dependencies = {}) {
|
|
|
57
59
|
allowNoScene: false
|
|
58
60
|
}, {
|
|
59
61
|
projectPath,
|
|
60
|
-
fileSystem
|
|
62
|
+
fileSystem,
|
|
61
63
|
sessionStore
|
|
62
64
|
});
|
|
63
65
|
|
|
@@ -130,6 +132,11 @@ async function runSpecPipeline(options = {}, dependencies = {}) {
|
|
|
130
132
|
status: execution.status,
|
|
131
133
|
stage_results: execution.stageResults,
|
|
132
134
|
failure: execution.failure,
|
|
135
|
+
strategy_assessment: await buildStrategyAssessment(specId, {
|
|
136
|
+
projectPath,
|
|
137
|
+
fileSystem,
|
|
138
|
+
strategyAssessor: dependencies.strategyAssessor
|
|
139
|
+
}),
|
|
133
140
|
next_actions: buildNextActions(execution),
|
|
134
141
|
state_file: path.relative(projectPath, stateStore.getRunPath(specId, state.run_id)),
|
|
135
142
|
scene_session: sceneBinding
|
|
@@ -156,7 +163,8 @@ async function runSpecPipeline(options = {}, dependencies = {}) {
|
|
|
156
163
|
spec: specId,
|
|
157
164
|
run_id: state.run_id,
|
|
158
165
|
pipeline_status: execution.status,
|
|
159
|
-
failure: execution.failure || null
|
|
166
|
+
failure: execution.failure || null,
|
|
167
|
+
strategy_decision: result.strategy_assessment ? result.strategy_assessment.decision : null
|
|
160
168
|
}
|
|
161
169
|
});
|
|
162
170
|
}
|
|
@@ -174,6 +182,7 @@ async function runSpecPipeline(options = {}, dependencies = {}) {
|
|
|
174
182
|
console.log(JSON.stringify(result, null, 2));
|
|
175
183
|
} else {
|
|
176
184
|
printResult(result);
|
|
185
|
+
emitStrategyAdvisory(result.strategy_assessment, options);
|
|
177
186
|
}
|
|
178
187
|
|
|
179
188
|
return result;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const fs = require('fs-extra');
|
|
3
|
+
const { assessComplexityStrategy } = require('../spec/complexity-strategy');
|
|
4
|
+
|
|
5
|
+
function normalizeText(value) {
|
|
6
|
+
if (typeof value !== 'string') {
|
|
7
|
+
return '';
|
|
8
|
+
}
|
|
9
|
+
return value.trim();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function printPayload(payload, options = {}) {
|
|
13
|
+
if (options.json) {
|
|
14
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log(chalk.blue('Spec Strategy Assess'));
|
|
19
|
+
console.log(` Source: ${payload.source.type}${payload.source.id ? ` (${payload.source.id})` : ''}`);
|
|
20
|
+
console.log(` Decision: ${payload.decision}`);
|
|
21
|
+
console.log(` Reason: ${payload.decision_reason}`);
|
|
22
|
+
console.log(` Recommended Program Specs: ${payload.summary.recommended_program_specs}`);
|
|
23
|
+
if (Array.isArray(payload.signals) && payload.signals.length > 0) {
|
|
24
|
+
console.log(chalk.gray(' Signals:'));
|
|
25
|
+
payload.signals.forEach((signal) => {
|
|
26
|
+
console.log(chalk.gray(` - ${signal}`));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function runSpecStrategyAssessCommand(options = {}, dependencies = {}) {
|
|
32
|
+
const projectPath = dependencies.projectPath || process.cwd();
|
|
33
|
+
const fileSystem = dependencies.fileSystem || fs;
|
|
34
|
+
const payload = await assessComplexityStrategy({
|
|
35
|
+
goal: normalizeText(options.goal),
|
|
36
|
+
spec: normalizeText(options.spec)
|
|
37
|
+
}, {
|
|
38
|
+
projectPath,
|
|
39
|
+
fileSystem
|
|
40
|
+
});
|
|
41
|
+
printPayload(payload, options);
|
|
42
|
+
return payload;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function registerSpecStrategyCommand(program) {
|
|
46
|
+
const strategy = program
|
|
47
|
+
.command('spec-strategy')
|
|
48
|
+
.description('Assess whether a problem should stay single-spec or escalate to a coordinated program');
|
|
49
|
+
|
|
50
|
+
strategy
|
|
51
|
+
.command('assess')
|
|
52
|
+
.description('Assess complexity and recommend single-spec vs multi-spec-program vs research-program')
|
|
53
|
+
.option('--goal <text>', 'Broad goal or problem statement to assess')
|
|
54
|
+
.option('--spec <spec-id>', 'Existing spec to assess from current artifacts')
|
|
55
|
+
.option('--json', 'Output machine-readable JSON')
|
|
56
|
+
.action(async (options) => {
|
|
57
|
+
try {
|
|
58
|
+
await runSpecStrategyAssessCommand(options);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (options.json) {
|
|
61
|
+
console.log(JSON.stringify({ success: false, error: error.message }, null, 2));
|
|
62
|
+
} else {
|
|
63
|
+
console.error(chalk.red('❌ spec-strategy assess failed:'), error.message);
|
|
64
|
+
}
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
runSpecStrategyAssessCommand,
|
|
72
|
+
registerSpecStrategyCommand
|
|
73
|
+
};
|
|
@@ -82,6 +82,31 @@ function toRelativePosix(projectPath, absolutePath) {
|
|
|
82
82
|
return path.relative(projectPath, absolutePath).replace(/\\/g, '/');
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
function sortKeysDeep(value) {
|
|
86
|
+
if (Array.isArray(value)) {
|
|
87
|
+
return value.map((item) => sortKeysDeep(item));
|
|
88
|
+
}
|
|
89
|
+
if (!value || typeof value !== 'object') {
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
const sorted = {};
|
|
93
|
+
Object.keys(value).sort().forEach((key) => {
|
|
94
|
+
sorted[key] = sortKeysDeep(value[key]);
|
|
95
|
+
});
|
|
96
|
+
return sorted;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function toComparableProjection(payload) {
|
|
100
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const clone = {
|
|
104
|
+
...payload
|
|
105
|
+
};
|
|
106
|
+
delete clone.generated_at;
|
|
107
|
+
return sortKeysDeep(clone);
|
|
108
|
+
}
|
|
109
|
+
|
|
85
110
|
async function buildProjectSharedProblemProjection(projectPath = process.cwd(), options = {}, dependencies = {}) {
|
|
86
111
|
const fileSystem = dependencies.fileSystem || fs;
|
|
87
112
|
const studioIntakePolicy = dependencies.studioIntakePolicy || await loadStudioIntakePolicy(projectPath, fileSystem);
|
|
@@ -212,6 +237,24 @@ async function syncProjectSharedProblemProjection(projectPath = process.cwd(), o
|
|
|
212
237
|
...dependencies,
|
|
213
238
|
problemClosurePolicyBundle: closurePolicy
|
|
214
239
|
});
|
|
240
|
+
const existing = await readJsonSafe(absolutePath, fileSystem);
|
|
241
|
+
const existingComparable = toComparableProjection(existing);
|
|
242
|
+
const nextComparable = toComparableProjection(payload);
|
|
243
|
+
|
|
244
|
+
if (
|
|
245
|
+
existingComparable
|
|
246
|
+
&& nextComparable
|
|
247
|
+
&& JSON.stringify(existingComparable) === JSON.stringify(nextComparable)
|
|
248
|
+
) {
|
|
249
|
+
return {
|
|
250
|
+
mode: 'project-problem-projection-sync',
|
|
251
|
+
enabled: true,
|
|
252
|
+
file: absolutePath,
|
|
253
|
+
scope: projectionConfig.scope,
|
|
254
|
+
total_entries: Number(payload.summary.total_entries || payload.entries.length || 0),
|
|
255
|
+
refreshed: false
|
|
256
|
+
};
|
|
257
|
+
}
|
|
215
258
|
|
|
216
259
|
await fileSystem.ensureDir(path.dirname(absolutePath));
|
|
217
260
|
await fileSystem.writeJson(absolutePath, payload, { spaces: 2 });
|