scene-capability-engine 3.3.17 → 3.3.21
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 +48 -0
- package/README.md +19 -10
- package/README.zh.md +21 -11
- package/bin/scene-capability-engine.js +4 -0
- package/docs/command-reference.md +94 -12
- package/docs/release-checklist.md +6 -0
- package/docs/zh/release-checklist.md +6 -0
- package/lib/commands/errorbook.js +1244 -0
- package/lib/commands/spec-bootstrap.js +126 -51
- package/lib/commands/spec-gate.js +92 -25
- package/lib/commands/spec-pipeline.js +86 -7
- package/lib/commands/studio.js +265 -30
- package/lib/runtime/multi-spec-scene-session.js +147 -0
- package/lib/runtime/scene-session-binding.js +109 -0
- package/lib/runtime/session-store.js +475 -3
- package/package.json +4 -2
- package/template/.sce/steering/CORE_PRINCIPLES.md +26 -1
|
@@ -11,9 +11,13 @@ const { ContextCollector } = require('../spec/bootstrap/context-collector');
|
|
|
11
11
|
const { QuestionnaireEngine } = require('../spec/bootstrap/questionnaire-engine');
|
|
12
12
|
const { DraftGenerator } = require('../spec/bootstrap/draft-generator');
|
|
13
13
|
const { TraceEmitter } = require('../spec/bootstrap/trace-emitter');
|
|
14
|
+
const { SessionStore } = require('../runtime/session-store');
|
|
15
|
+
const { resolveSpecSceneBinding } = require('../runtime/scene-session-binding');
|
|
16
|
+
const { bindMultiSpecSceneSession } = require('../runtime/multi-spec-scene-session');
|
|
14
17
|
|
|
15
18
|
async function runSpecBootstrap(options = {}, dependencies = {}) {
|
|
16
19
|
const projectPath = dependencies.projectPath || process.cwd();
|
|
20
|
+
const sessionStore = dependencies.sessionStore || new SessionStore(projectPath);
|
|
17
21
|
|
|
18
22
|
const specTargets = parseSpecTargets({
|
|
19
23
|
spec: options.spec || options.name,
|
|
@@ -22,13 +26,24 @@ async function runSpecBootstrap(options = {}, dependencies = {}) {
|
|
|
22
26
|
|
|
23
27
|
if (specTargets.length > 1) {
|
|
24
28
|
const executeOrchestration = dependencies.runOrchestration || runOrchestration;
|
|
25
|
-
return
|
|
29
|
+
return bindMultiSpecSceneSession({
|
|
26
30
|
specTargets,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
runOrchestration: executeOrchestration,
|
|
31
|
+
sceneId: options.scene,
|
|
32
|
+
commandName: 'spec-bootstrap',
|
|
30
33
|
commandLabel: 'Multi-spec bootstrap',
|
|
31
|
-
|
|
34
|
+
commandOptions: options,
|
|
35
|
+
runViaOrchestrate: () => runMultiSpecViaOrchestrate({
|
|
36
|
+
specTargets,
|
|
37
|
+
projectPath,
|
|
38
|
+
commandOptions: options,
|
|
39
|
+
runOrchestration: executeOrchestration,
|
|
40
|
+
commandLabel: 'Multi-spec bootstrap',
|
|
41
|
+
nextActionLabel: 'Multi-spec bootstrap defaulted to orchestrate mode.'
|
|
42
|
+
})
|
|
43
|
+
}, {
|
|
44
|
+
projectPath,
|
|
45
|
+
fileSystem: dependencies.fileSystem || fs,
|
|
46
|
+
sessionStore
|
|
32
47
|
});
|
|
33
48
|
}
|
|
34
49
|
|
|
@@ -57,61 +72,120 @@ async function runSpecBootstrap(options = {}, dependencies = {}) {
|
|
|
57
72
|
throw new Error('Spec name is required');
|
|
58
73
|
}
|
|
59
74
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
const sceneBinding = await resolveSpecSceneBinding({
|
|
76
|
+
sceneId: options.scene,
|
|
77
|
+
allowNoScene: false
|
|
78
|
+
}, {
|
|
79
|
+
projectPath,
|
|
80
|
+
fileSystem: dependencies.fileSystem || fs,
|
|
81
|
+
sessionStore
|
|
66
82
|
});
|
|
67
83
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
await fs.ensureDir(specPath);
|
|
77
|
-
await fs.writeFile(files.requirements, draft.requirements, 'utf8');
|
|
78
|
-
await fs.writeFile(files.design, draft.design, 'utf8');
|
|
79
|
-
await fs.writeFile(files.tasks, draft.tasks, 'utf8');
|
|
84
|
+
let specSession = null;
|
|
85
|
+
if (sceneBinding && !options.dryRun) {
|
|
86
|
+
const linked = await sessionStore.startSpecSession({
|
|
87
|
+
sceneId: sceneBinding.scene_id,
|
|
88
|
+
specId: specName,
|
|
89
|
+
objective: `Spec bootstrap: ${specName}`
|
|
90
|
+
});
|
|
91
|
+
specSession = linked.spec_session;
|
|
80
92
|
}
|
|
81
93
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
specPath: path.relative(projectPath, specPath),
|
|
86
|
-
dryRun: !!options.dryRun,
|
|
87
|
-
files: {
|
|
88
|
-
requirements: path.relative(projectPath, files.requirements),
|
|
89
|
-
design: path.relative(projectPath, files.design),
|
|
90
|
-
tasks: path.relative(projectPath, files.tasks)
|
|
91
|
-
},
|
|
92
|
-
trace: {
|
|
93
|
-
template: options.template || 'default',
|
|
94
|
+
try {
|
|
95
|
+
const draft = draftGenerator.generate({
|
|
96
|
+
specName,
|
|
94
97
|
profile: options.profile || 'general',
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
template: options.template || 'default',
|
|
99
|
+
context,
|
|
100
|
+
answers
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const specPath = path.join(projectPath, '.sce', 'specs', specName);
|
|
104
|
+
const files = {
|
|
105
|
+
requirements: path.join(specPath, 'requirements.md'),
|
|
106
|
+
design: path.join(specPath, 'design.md'),
|
|
107
|
+
tasks: path.join(specPath, 'tasks.md')
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (!options.dryRun) {
|
|
111
|
+
await fs.ensureDir(specPath);
|
|
112
|
+
await fs.writeFile(files.requirements, draft.requirements, 'utf8');
|
|
113
|
+
await fs.writeFile(files.design, draft.design, 'utf8');
|
|
114
|
+
await fs.writeFile(files.tasks, draft.tasks, 'utf8');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const result = {
|
|
118
|
+
success: true,
|
|
119
|
+
specName,
|
|
120
|
+
specPath: path.relative(projectPath, specPath),
|
|
121
|
+
dryRun: !!options.dryRun,
|
|
122
|
+
files: {
|
|
123
|
+
requirements: path.relative(projectPath, files.requirements),
|
|
124
|
+
design: path.relative(projectPath, files.design),
|
|
125
|
+
tasks: path.relative(projectPath, files.tasks)
|
|
126
|
+
},
|
|
127
|
+
trace: {
|
|
128
|
+
template: options.template || 'default',
|
|
129
|
+
profile: options.profile || 'general',
|
|
130
|
+
parameters: {
|
|
131
|
+
nonInteractive: !!options.nonInteractive,
|
|
132
|
+
dryRun: !!options.dryRun,
|
|
133
|
+
json: !!options.json
|
|
134
|
+
},
|
|
135
|
+
context: {
|
|
136
|
+
totalSpecs: context.totalSpecs,
|
|
137
|
+
preferredLanguage: context.preferredLanguage
|
|
138
|
+
},
|
|
139
|
+
mapping: draft.metadata.mapping
|
|
99
140
|
},
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
141
|
+
preview: {
|
|
142
|
+
requirements: draft.requirements,
|
|
143
|
+
design: draft.design,
|
|
144
|
+
tasks: draft.tasks
|
|
103
145
|
},
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
146
|
+
scene_session: sceneBinding
|
|
147
|
+
? {
|
|
148
|
+
bound: true,
|
|
149
|
+
scene_id: sceneBinding.scene_id,
|
|
150
|
+
scene_cycle: sceneBinding.scene_cycle,
|
|
151
|
+
scene_session_id: sceneBinding.scene_session_id,
|
|
152
|
+
spec_session_id: specSession ? specSession.session_id : null,
|
|
153
|
+
binding_source: sceneBinding.source
|
|
154
|
+
}
|
|
155
|
+
: {
|
|
156
|
+
bound: false
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if (specSession) {
|
|
161
|
+
await sessionStore.completeSpecSession({
|
|
162
|
+
specSessionRef: specSession.session_id,
|
|
163
|
+
status: 'completed',
|
|
164
|
+
summary: `Spec bootstrap completed: ${specName}`,
|
|
165
|
+
payload: {
|
|
166
|
+
command: 'spec-bootstrap',
|
|
167
|
+
spec: specName
|
|
168
|
+
}
|
|
169
|
+
});
|
|
110
170
|
}
|
|
111
|
-
};
|
|
112
171
|
|
|
113
|
-
|
|
114
|
-
|
|
172
|
+
traceEmitter.emit(result, { json: options.json });
|
|
173
|
+
return result;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (specSession) {
|
|
176
|
+
await sessionStore.completeSpecSession({
|
|
177
|
+
specSessionRef: specSession.session_id,
|
|
178
|
+
status: 'failed',
|
|
179
|
+
summary: `Spec bootstrap failed: ${specName}`,
|
|
180
|
+
payload: {
|
|
181
|
+
command: 'spec-bootstrap',
|
|
182
|
+
spec: specName,
|
|
183
|
+
error: error.message
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
115
189
|
}
|
|
116
190
|
|
|
117
191
|
function registerSpecBootstrapCommand(program) {
|
|
@@ -123,6 +197,7 @@ function registerSpecBootstrapCommand(program) {
|
|
|
123
197
|
.option('--specs <names>', 'Comma-separated Spec identifiers (multi-spec defaults to orchestrate mode)')
|
|
124
198
|
.option('--template <template-id>', 'Template hint for draft generation')
|
|
125
199
|
.option('--profile <profile-id>', 'Profile for default generation strategy')
|
|
200
|
+
.option('--scene <scene-id>', 'Bind this spec bootstrap as a child session of an active scene')
|
|
126
201
|
.option('--non-interactive', 'Disable prompts and use arguments/defaults only')
|
|
127
202
|
.option('--dry-run', 'Preview generation result without writing files')
|
|
128
203
|
.option('--json', 'Output machine-readable JSON')
|
|
@@ -12,9 +12,13 @@ const { RuleRegistry } = require('../spec-gate/rules/rule-registry');
|
|
|
12
12
|
const { createDefaultRules } = require('../spec-gate/rules/default-rules');
|
|
13
13
|
const { GateEngine } = require('../spec-gate/engine/gate-engine');
|
|
14
14
|
const { ResultEmitter } = require('../spec-gate/result-emitter');
|
|
15
|
+
const { SessionStore } = require('../runtime/session-store');
|
|
16
|
+
const { resolveSpecSceneBinding } = require('../runtime/scene-session-binding');
|
|
17
|
+
const { bindMultiSpecSceneSession } = require('../runtime/multi-spec-scene-session');
|
|
15
18
|
|
|
16
19
|
async function runSpecGate(options = {}, dependencies = {}) {
|
|
17
20
|
const projectPath = dependencies.projectPath || process.cwd();
|
|
21
|
+
const sessionStore = dependencies.sessionStore || new SessionStore(projectPath);
|
|
18
22
|
const specTargets = parseSpecTargets(options);
|
|
19
23
|
|
|
20
24
|
if (specTargets.length === 0) {
|
|
@@ -23,13 +27,24 @@ async function runSpecGate(options = {}, dependencies = {}) {
|
|
|
23
27
|
|
|
24
28
|
if (specTargets.length > 1) {
|
|
25
29
|
const executeOrchestration = dependencies.runOrchestration || runOrchestration;
|
|
26
|
-
return
|
|
30
|
+
return bindMultiSpecSceneSession({
|
|
27
31
|
specTargets,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
runOrchestration: executeOrchestration,
|
|
32
|
+
sceneId: options.scene,
|
|
33
|
+
commandName: 'spec-gate',
|
|
31
34
|
commandLabel: 'Multi-spec gate',
|
|
32
|
-
|
|
35
|
+
commandOptions: options,
|
|
36
|
+
runViaOrchestrate: () => runMultiSpecViaOrchestrate({
|
|
37
|
+
specTargets,
|
|
38
|
+
projectPath,
|
|
39
|
+
commandOptions: options,
|
|
40
|
+
runOrchestration: executeOrchestration,
|
|
41
|
+
commandLabel: 'Multi-spec gate',
|
|
42
|
+
nextActionLabel: 'Multi-spec gate execution defaulted to orchestrate mode.'
|
|
43
|
+
})
|
|
44
|
+
}, {
|
|
45
|
+
projectPath,
|
|
46
|
+
fileSystem: dependencies.fileSystem || fs,
|
|
47
|
+
sessionStore
|
|
33
48
|
});
|
|
34
49
|
}
|
|
35
50
|
|
|
@@ -40,30 +55,81 @@ async function runSpecGate(options = {}, dependencies = {}) {
|
|
|
40
55
|
throw new Error(`Spec not found: ${specId}`);
|
|
41
56
|
}
|
|
42
57
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
58
|
+
const sceneBinding = await resolveSpecSceneBinding({
|
|
59
|
+
sceneId: options.scene,
|
|
60
|
+
allowNoScene: false
|
|
61
|
+
}, {
|
|
62
|
+
projectPath,
|
|
63
|
+
fileSystem: dependencies.fileSystem || fs,
|
|
64
|
+
sessionStore
|
|
47
65
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
policy
|
|
66
|
+
const linked = await sessionStore.startSpecSession({
|
|
67
|
+
sceneId: sceneBinding.scene_id,
|
|
68
|
+
specId,
|
|
69
|
+
objective: `Spec gate: ${specId}`
|
|
53
70
|
});
|
|
71
|
+
const specSession = linked.spec_session;
|
|
54
72
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
});
|
|
73
|
+
const policyLoader = dependencies.policyLoader || new PolicyLoader(projectPath);
|
|
74
|
+
try {
|
|
75
|
+
const policy = dependencies.policy || await policyLoader.load({
|
|
76
|
+
policy: options.policy,
|
|
77
|
+
strict: options.strict
|
|
78
|
+
});
|
|
62
79
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
const registry = dependencies.registry || new RuleRegistry(createDefaultRules(projectPath));
|
|
81
|
+
const engine = dependencies.engine || new GateEngine({
|
|
82
|
+
registry,
|
|
83
|
+
policy
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const result = await engine.evaluate({ specId });
|
|
87
|
+
const emitter = dependencies.emitter || new ResultEmitter(projectPath);
|
|
88
|
+
const emitted = await emitter.emit(result, {
|
|
89
|
+
json: options.json,
|
|
90
|
+
out: options.out,
|
|
91
|
+
silent: options.silent
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const decisionStatus = result.decision === 'no-go' ? 'failed' : 'completed';
|
|
95
|
+
await sessionStore.completeSpecSession({
|
|
96
|
+
specSessionRef: specSession.session_id,
|
|
97
|
+
status: decisionStatus,
|
|
98
|
+
summary: `Spec gate ${result.decision}: ${specId}`,
|
|
99
|
+
payload: {
|
|
100
|
+
command: 'spec-gate',
|
|
101
|
+
spec: specId,
|
|
102
|
+
decision: result.decision,
|
|
103
|
+
score: result.score,
|
|
104
|
+
report_path: emitted.outputPath || null
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
...result,
|
|
110
|
+
report_path: emitted.outputPath,
|
|
111
|
+
scene_session: {
|
|
112
|
+
bound: true,
|
|
113
|
+
scene_id: sceneBinding.scene_id,
|
|
114
|
+
scene_cycle: sceneBinding.scene_cycle,
|
|
115
|
+
scene_session_id: sceneBinding.scene_session_id,
|
|
116
|
+
spec_session_id: specSession.session_id,
|
|
117
|
+
binding_source: sceneBinding.source
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
} catch (error) {
|
|
121
|
+
await sessionStore.completeSpecSession({
|
|
122
|
+
specSessionRef: specSession.session_id,
|
|
123
|
+
status: 'failed',
|
|
124
|
+
summary: `Spec gate failed: ${specId}`,
|
|
125
|
+
payload: {
|
|
126
|
+
command: 'spec-gate',
|
|
127
|
+
spec: specId,
|
|
128
|
+
error: error.message
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
67
133
|
}
|
|
68
134
|
|
|
69
135
|
async function generateSpecGatePolicyTemplate(options = {}, dependencies = {}) {
|
|
@@ -99,6 +165,7 @@ function registerSpecGateCommand(program) {
|
|
|
99
165
|
.description('Execute gate checks for one or more Specs')
|
|
100
166
|
.option('--spec <name>', 'Single Spec identifier')
|
|
101
167
|
.option('--specs <names>', 'Comma-separated Spec identifiers (multi-spec defaults to orchestrate mode)')
|
|
168
|
+
.option('--scene <scene-id>', 'Bind this spec gate run as a child session of an active scene')
|
|
102
169
|
.option('--policy <path>', 'Policy JSON path')
|
|
103
170
|
.option('--strict', 'Enable strict mode override')
|
|
104
171
|
.option('--json', 'Output machine-readable JSON')
|
|
@@ -10,9 +10,13 @@ const {
|
|
|
10
10
|
const { PipelineStateStore } = require('../spec/pipeline/state-store');
|
|
11
11
|
const { StageRunner } = require('../spec/pipeline/stage-runner');
|
|
12
12
|
const { createDefaultStageAdapters } = require('../spec/pipeline/stage-adapters');
|
|
13
|
+
const { SessionStore } = require('../runtime/session-store');
|
|
14
|
+
const { resolveSpecSceneBinding } = require('../runtime/scene-session-binding');
|
|
15
|
+
const { bindMultiSpecSceneSession } = require('../runtime/multi-spec-scene-session');
|
|
13
16
|
|
|
14
17
|
async function runSpecPipeline(options = {}, dependencies = {}) {
|
|
15
18
|
const projectPath = dependencies.projectPath || process.cwd();
|
|
19
|
+
const sessionStore = dependencies.sessionStore || new SessionStore(projectPath);
|
|
16
20
|
const specTargets = parseSpecTargets(options);
|
|
17
21
|
if (specTargets.length === 0) {
|
|
18
22
|
throw new Error('Either --spec or --specs is required');
|
|
@@ -20,13 +24,24 @@ async function runSpecPipeline(options = {}, dependencies = {}) {
|
|
|
20
24
|
|
|
21
25
|
if (specTargets.length > 1) {
|
|
22
26
|
const executeOrchestration = dependencies.runOrchestration || runOrchestration;
|
|
23
|
-
return
|
|
27
|
+
return bindMultiSpecSceneSession({
|
|
24
28
|
specTargets,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
runOrchestration: executeOrchestration,
|
|
29
|
+
sceneId: options.scene,
|
|
30
|
+
commandName: 'spec-pipeline',
|
|
28
31
|
commandLabel: 'Multi-spec pipeline',
|
|
29
|
-
|
|
32
|
+
commandOptions: options,
|
|
33
|
+
runViaOrchestrate: () => runMultiSpecViaOrchestrate({
|
|
34
|
+
specTargets,
|
|
35
|
+
projectPath,
|
|
36
|
+
commandOptions: options,
|
|
37
|
+
runOrchestration: executeOrchestration,
|
|
38
|
+
commandLabel: 'Multi-spec pipeline',
|
|
39
|
+
nextActionLabel: 'Multi-spec execution defaulted to orchestrate mode.'
|
|
40
|
+
})
|
|
41
|
+
}, {
|
|
42
|
+
projectPath,
|
|
43
|
+
fileSystem: dependencies.fileSystem || fs,
|
|
44
|
+
sessionStore
|
|
30
45
|
});
|
|
31
46
|
}
|
|
32
47
|
|
|
@@ -37,6 +52,25 @@ async function runSpecPipeline(options = {}, dependencies = {}) {
|
|
|
37
52
|
throw new Error(`Spec not found: ${specId}`);
|
|
38
53
|
}
|
|
39
54
|
|
|
55
|
+
const sceneBinding = await resolveSpecSceneBinding({
|
|
56
|
+
sceneId: options.scene,
|
|
57
|
+
allowNoScene: false
|
|
58
|
+
}, {
|
|
59
|
+
projectPath,
|
|
60
|
+
fileSystem: dependencies.fileSystem || fs,
|
|
61
|
+
sessionStore
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
let specSession = null;
|
|
65
|
+
if (sceneBinding && !options.dryRun) {
|
|
66
|
+
const linked = await sessionStore.startSpecSession({
|
|
67
|
+
sceneId: sceneBinding.scene_id,
|
|
68
|
+
specId,
|
|
69
|
+
objective: `Spec pipeline: ${specId}`
|
|
70
|
+
});
|
|
71
|
+
specSession = linked.spec_session;
|
|
72
|
+
}
|
|
73
|
+
|
|
40
74
|
const stateStore = dependencies.stateStore || new PipelineStateStore(projectPath);
|
|
41
75
|
const adapters = dependencies.adapters || createDefaultStageAdapters(projectPath);
|
|
42
76
|
const stageRunner = dependencies.stageRunner || new StageRunner({
|
|
@@ -70,7 +104,24 @@ async function runSpecPipeline(options = {}, dependencies = {}) {
|
|
|
70
104
|
state
|
|
71
105
|
};
|
|
72
106
|
|
|
73
|
-
|
|
107
|
+
let execution;
|
|
108
|
+
try {
|
|
109
|
+
execution = await stageRunner.run(runContext);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (specSession) {
|
|
112
|
+
await sessionStore.completeSpecSession({
|
|
113
|
+
specSessionRef: specSession.session_id,
|
|
114
|
+
status: 'failed',
|
|
115
|
+
summary: `Spec pipeline failed: ${specId}`,
|
|
116
|
+
payload: {
|
|
117
|
+
command: 'spec-pipeline',
|
|
118
|
+
spec: specId,
|
|
119
|
+
error: error.message
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
74
125
|
await stateStore.markFinished(state, execution.status);
|
|
75
126
|
|
|
76
127
|
const result = {
|
|
@@ -80,9 +131,36 @@ async function runSpecPipeline(options = {}, dependencies = {}) {
|
|
|
80
131
|
stage_results: execution.stageResults,
|
|
81
132
|
failure: execution.failure,
|
|
82
133
|
next_actions: buildNextActions(execution),
|
|
83
|
-
state_file: path.relative(projectPath, stateStore.getRunPath(specId, state.run_id))
|
|
134
|
+
state_file: path.relative(projectPath, stateStore.getRunPath(specId, state.run_id)),
|
|
135
|
+
scene_session: sceneBinding
|
|
136
|
+
? {
|
|
137
|
+
bound: true,
|
|
138
|
+
scene_id: sceneBinding.scene_id,
|
|
139
|
+
scene_cycle: sceneBinding.scene_cycle,
|
|
140
|
+
scene_session_id: sceneBinding.scene_session_id,
|
|
141
|
+
spec_session_id: specSession ? specSession.session_id : null,
|
|
142
|
+
binding_source: sceneBinding.source
|
|
143
|
+
}
|
|
144
|
+
: {
|
|
145
|
+
bound: false
|
|
146
|
+
}
|
|
84
147
|
};
|
|
85
148
|
|
|
149
|
+
if (specSession) {
|
|
150
|
+
await sessionStore.completeSpecSession({
|
|
151
|
+
specSessionRef: specSession.session_id,
|
|
152
|
+
status: execution.status === 'completed' ? 'completed' : 'failed',
|
|
153
|
+
summary: `Spec pipeline ${execution.status}: ${specId}`,
|
|
154
|
+
payload: {
|
|
155
|
+
command: 'spec-pipeline',
|
|
156
|
+
spec: specId,
|
|
157
|
+
run_id: state.run_id,
|
|
158
|
+
pipeline_status: execution.status,
|
|
159
|
+
failure: execution.failure || null
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
86
164
|
if (options.out) {
|
|
87
165
|
const outPath = path.isAbsolute(options.out)
|
|
88
166
|
? options.out
|
|
@@ -111,6 +189,7 @@ function registerSpecPipelineCommand(program) {
|
|
|
111
189
|
.description('Execute pipeline stages for one or more Specs')
|
|
112
190
|
.option('--spec <name>', 'Single Spec identifier')
|
|
113
191
|
.option('--specs <names>', 'Comma-separated Spec identifiers (multi-spec defaults to orchestrate mode)')
|
|
192
|
+
.option('--scene <scene-id>', 'Bind this spec pipeline run as a child session of an active scene')
|
|
114
193
|
.option('--from-stage <stage>', 'Start stage (requirements/design/tasks/gate)')
|
|
115
194
|
.option('--to-stage <stage>', 'End stage (requirements/design/tasks/gate)')
|
|
116
195
|
.option('--resume', 'Resume from latest unfinished stage state')
|