scene-capability-engine 3.3.23 → 3.3.25

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.
@@ -4,6 +4,11 @@ const { spawnSync } = require('child_process');
4
4
  const fs = require('fs-extra');
5
5
  const chalk = require('chalk');
6
6
  const { SessionStore } = require('../runtime/session-store');
7
+ const {
8
+ DOMAIN_CHAIN_RELATIVE_PATH,
9
+ ensureSpecDomainArtifacts
10
+ } = require('../spec/domain-modeling');
11
+ const { findRelatedSpecs } = require('../spec/related-specs');
7
12
 
8
13
  const STUDIO_JOB_API_VERSION = 'sce.studio.job/v0.1';
9
14
  const STAGE_ORDER = ['plan', 'generate', 'apply', 'verify', 'release'];
@@ -151,7 +156,7 @@ async function autoRecordGateFailure(failure = {}, context = {}, dependencies =
151
156
  const title = `[studio:${stage}] gate failure: ${stepId}`;
152
157
  const tags = ['studio', 'gate-failure', 'release-blocker', `stage-${stage}`];
153
158
  const fingerprint = createGateFailureFingerprint(failure, context);
154
- const specRef = normalizeString(context.scene_id) || jobId;
159
+ const specRef = normalizeString(context.spec_id) || normalizeString(context.scene_id) || jobId;
155
160
 
156
161
  const result = await runErrorbookRecordCommand({
157
162
  title,
@@ -678,6 +683,211 @@ function resolveNextAction(job) {
678
683
  return 'complete';
679
684
  }
680
685
 
686
+ function toRelativePosix(projectPath, absolutePath) {
687
+ return path.relative(projectPath, absolutePath).replace(/\\/g, '/');
688
+ }
689
+
690
+ async function readSpecDomainChain(projectPath, specId, fileSystem = fs) {
691
+ const specRoot = path.join(projectPath, '.sce', 'specs', specId);
692
+ const chainPath = path.join(specRoot, DOMAIN_CHAIN_RELATIVE_PATH);
693
+ if (!await fileSystem.pathExists(chainPath)) {
694
+ return null;
695
+ }
696
+ try {
697
+ const payload = await fileSystem.readJson(chainPath);
698
+ const stat = await fileSystem.stat(chainPath);
699
+ return {
700
+ spec_id: specId,
701
+ chain_path: toRelativePosix(projectPath, chainPath),
702
+ payload,
703
+ updated_at: stat && stat.mtime ? stat.mtime.toISOString() : null,
704
+ mtime_ms: Number(stat && stat.mtimeMs) || 0
705
+ };
706
+ } catch (_error) {
707
+ return null;
708
+ }
709
+ }
710
+
711
+ function normalizeChainList(value, limit = 5) {
712
+ if (!Array.isArray(value)) {
713
+ return [];
714
+ }
715
+ return value
716
+ .filter((item) => item && (typeof item === 'string' || typeof item === 'object'))
717
+ .slice(0, limit)
718
+ .map((item) => {
719
+ if (typeof item === 'string') {
720
+ return normalizeString(item);
721
+ }
722
+ return item;
723
+ });
724
+ }
725
+
726
+ function summarizeDomainChain(payload = {}) {
727
+ const ontology = payload && typeof payload.ontology === 'object' ? payload.ontology : {};
728
+ const decisionPath = Array.isArray(payload.decision_execution_path) ? payload.decision_execution_path : [];
729
+ const correctionLoop = payload && typeof payload.correction_loop === 'object' ? payload.correction_loop : {};
730
+ const verification = payload && typeof payload.verification === 'object' ? payload.verification : {};
731
+ const hypotheses = Array.isArray(payload.hypotheses) ? payload.hypotheses : [];
732
+ const risks = Array.isArray(payload.risks) ? payload.risks : [];
733
+
734
+ return {
735
+ scene_id: normalizeString(payload.scene_id) || null,
736
+ spec_id: normalizeString(payload.spec_id) || null,
737
+ problem_statement: normalizeString(payload?.problem?.statement) || null,
738
+ ontology_counts: {
739
+ entity: Array.isArray(ontology.entity) ? ontology.entity.length : 0,
740
+ relation: Array.isArray(ontology.relation) ? ontology.relation.length : 0,
741
+ business_rule: Array.isArray(ontology.business_rule) ? ontology.business_rule.length : 0,
742
+ decision_policy: Array.isArray(ontology.decision_policy) ? ontology.decision_policy.length : 0,
743
+ execution_flow: Array.isArray(ontology.execution_flow) ? ontology.execution_flow.length : 0
744
+ },
745
+ hypothesis_count: hypotheses.length,
746
+ risk_count: risks.length,
747
+ decision_path_steps: decisionPath.length,
748
+ correction_loop: {
749
+ triggers: normalizeChainList(correctionLoop.triggers, 5),
750
+ actions: normalizeChainList(correctionLoop.actions, 5)
751
+ },
752
+ verification_gates: normalizeChainList(verification.gates, 6)
753
+ };
754
+ }
755
+
756
+ function buildDomainChainRuntimeContext(payload = {}) {
757
+ return {
758
+ scene_id: normalizeString(payload.scene_id) || null,
759
+ spec_id: normalizeString(payload.spec_id) || null,
760
+ problem: {
761
+ statement: normalizeString(payload?.problem?.statement) || null,
762
+ scope: normalizeString(payload?.problem?.scope) || null,
763
+ symptom: normalizeString(payload?.problem?.symptom) || null
764
+ },
765
+ ontology: {
766
+ entity: normalizeChainList(payload?.ontology?.entity, 20),
767
+ relation: normalizeChainList(payload?.ontology?.relation, 20),
768
+ business_rule: normalizeChainList(payload?.ontology?.business_rule, 20),
769
+ decision_policy: normalizeChainList(payload?.ontology?.decision_policy, 20),
770
+ execution_flow: normalizeChainList(payload?.ontology?.execution_flow, 20)
771
+ },
772
+ hypotheses: normalizeChainList(payload.hypotheses, 10),
773
+ risks: normalizeChainList(payload.risks, 10),
774
+ decision_execution_path: normalizeChainList(payload.decision_execution_path, 12),
775
+ correction_loop: {
776
+ triggers: normalizeChainList(payload?.correction_loop?.triggers, 10),
777
+ actions: normalizeChainList(payload?.correction_loop?.actions, 10)
778
+ },
779
+ verification: {
780
+ plan: normalizeString(payload?.verification?.plan) || null,
781
+ gates: normalizeChainList(payload?.verification?.gates, 10)
782
+ }
783
+ };
784
+ }
785
+
786
+ async function resolveSceneDomainChainCandidates(projectPath, sceneId, fileSystem = fs) {
787
+ const specsRoot = path.join(projectPath, '.sce', 'specs');
788
+ if (!await fileSystem.pathExists(specsRoot)) {
789
+ return [];
790
+ }
791
+ const entries = await fileSystem.readdir(specsRoot);
792
+ const candidates = [];
793
+ for (const entry of entries) {
794
+ const specRoot = path.join(specsRoot, entry);
795
+ let stat = null;
796
+ try {
797
+ stat = await fileSystem.stat(specRoot);
798
+ } catch (_error) {
799
+ continue;
800
+ }
801
+ if (!stat || !stat.isDirectory()) {
802
+ continue;
803
+ }
804
+ const chain = await readSpecDomainChain(projectPath, entry, fileSystem);
805
+ if (!chain) {
806
+ continue;
807
+ }
808
+ if (normalizeString(chain?.payload?.scene_id) !== sceneId) {
809
+ continue;
810
+ }
811
+ candidates.push(chain);
812
+ }
813
+ candidates.sort((left, right) => (right.mtime_ms || 0) - (left.mtime_ms || 0));
814
+ return candidates;
815
+ }
816
+
817
+ async function resolveDomainChainBinding(options = {}, dependencies = {}) {
818
+ const projectPath = dependencies.projectPath || process.cwd();
819
+ const fileSystem = dependencies.fileSystem || fs;
820
+ const sceneId = normalizeString(options.sceneId);
821
+ const explicitSpec = normalizeString(options.specId);
822
+ const goal = normalizeString(options.goal);
823
+
824
+ if (explicitSpec) {
825
+ const specRoot = path.join(projectPath, '.sce', 'specs', explicitSpec);
826
+ if (!await fileSystem.pathExists(specRoot)) {
827
+ throw new Error(`--spec not found under .sce/specs: ${explicitSpec}`);
828
+ }
829
+ await ensureSpecDomainArtifacts(projectPath, explicitSpec, {
830
+ fileSystem,
831
+ sceneId,
832
+ problemStatement: goal || `Studio scene cycle for ${sceneId}`
833
+ });
834
+ const chain = await readSpecDomainChain(projectPath, explicitSpec, fileSystem);
835
+ if (!chain) {
836
+ return {
837
+ resolved: false,
838
+ source: 'explicit-spec',
839
+ spec_id: explicitSpec,
840
+ reason: 'domain_chain_missing'
841
+ };
842
+ }
843
+ return {
844
+ resolved: true,
845
+ source: 'explicit-spec',
846
+ spec_id: explicitSpec,
847
+ chain_path: chain.chain_path,
848
+ updated_at: chain.updated_at,
849
+ summary: summarizeDomainChain(chain.payload),
850
+ context: buildDomainChainRuntimeContext(chain.payload)
851
+ };
852
+ }
853
+
854
+ if (!sceneId) {
855
+ return {
856
+ resolved: false,
857
+ source: 'none',
858
+ spec_id: null,
859
+ reason: 'scene_id_missing'
860
+ };
861
+ }
862
+
863
+ const candidates = await resolveSceneDomainChainCandidates(projectPath, sceneId, fileSystem);
864
+ if (candidates.length === 0) {
865
+ return {
866
+ resolved: false,
867
+ source: 'none',
868
+ spec_id: null,
869
+ reason: 'no_scene_bound_domain_chain'
870
+ };
871
+ }
872
+
873
+ const selected = candidates[0];
874
+ return {
875
+ resolved: true,
876
+ source: candidates.length === 1 ? 'scene-auto-single' : 'scene-auto-latest',
877
+ spec_id: selected.spec_id,
878
+ chain_path: selected.chain_path,
879
+ updated_at: selected.updated_at,
880
+ candidate_count: candidates.length,
881
+ candidates: candidates.slice(0, 5).map((item) => ({
882
+ spec_id: item.spec_id,
883
+ chain_path: item.chain_path,
884
+ updated_at: item.updated_at
885
+ })),
886
+ summary: summarizeDomainChain(selected.payload),
887
+ context: buildDomainChainRuntimeContext(selected.payload)
888
+ };
889
+ }
890
+
681
891
  function printStudioPayload(payload, options = {}) {
682
892
  if (options.json) {
683
893
  console.log(JSON.stringify(payload, null, 2));
@@ -731,11 +941,37 @@ function buildCommandPayload(mode, job) {
731
941
  };
732
942
  }
733
943
 
944
+ function buildJobDomainChainMetadata(job = {}) {
945
+ const domainChain = job && job.source && job.source.domain_chain
946
+ ? job.source.domain_chain
947
+ : null;
948
+ const summary = domainChain && domainChain.summary ? domainChain.summary : null;
949
+ const context = domainChain && domainChain.context ? domainChain.context : null;
950
+ return {
951
+ resolved: domainChain && domainChain.resolved === true,
952
+ source: domainChain && domainChain.source ? domainChain.source : 'none',
953
+ spec_id: normalizeString(job?.source?.spec_id) || normalizeString(job?.scene?.spec_id) || null,
954
+ chain_path: domainChain && domainChain.chain_path ? domainChain.chain_path : null,
955
+ reason: domainChain && domainChain.reason ? domainChain.reason : null,
956
+ decision_path_steps: summary ? Number(summary.decision_path_steps || 0) : 0,
957
+ risk_count: summary ? Number(summary.risk_count || 0) : 0,
958
+ correction_triggers: summary && summary.correction_loop
959
+ ? normalizeChainList(summary.correction_loop.triggers, 10)
960
+ : [],
961
+ verification_gates: summary
962
+ ? normalizeChainList(summary.verification_gates, 10)
963
+ : [],
964
+ summary: summary || null,
965
+ context: context || null
966
+ };
967
+ }
968
+
734
969
  async function runStudioPlanCommand(options = {}, dependencies = {}) {
735
970
  const projectPath = dependencies.projectPath || process.cwd();
736
971
  const fileSystem = dependencies.fileSystem || fs;
737
972
  const fromChat = normalizeString(options.fromChat);
738
973
  const sceneId = normalizeString(options.scene);
974
+ const specId = normalizeString(options.spec);
739
975
 
740
976
  if (!fromChat) {
741
977
  throw new Error('--from-chat is required');
@@ -743,6 +979,33 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
743
979
  if (!sceneId) {
744
980
  throw new Error('--scene is required');
745
981
  }
982
+ const domainChainBinding = await resolveDomainChainBinding({
983
+ sceneId,
984
+ specId,
985
+ goal: normalizeString(options.goal)
986
+ }, {
987
+ projectPath,
988
+ fileSystem
989
+ });
990
+ const relatedSpecLookup = await findRelatedSpecs({
991
+ query: normalizeString(options.goal),
992
+ sceneId,
993
+ limit: 8,
994
+ excludeSpecId: domainChainBinding.spec_id || specId || null
995
+ }, {
996
+ projectPath,
997
+ fileSystem
998
+ });
999
+ const relatedSpecItems = Array.isArray(relatedSpecLookup.related_specs)
1000
+ ? relatedSpecLookup.related_specs.map((item) => ({
1001
+ spec_id: item.spec_id,
1002
+ scene_id: item.scene_id || null,
1003
+ score: Number(item.score || 0),
1004
+ reasons: Array.isArray(item.reasons) ? item.reasons : [],
1005
+ matched_tokens: Array.isArray(item.matched_tokens) ? item.matched_tokens : [],
1006
+ updated_at: item.updated_at || null
1007
+ }))
1008
+ : [];
746
1009
 
747
1010
  const paths = resolveStudioPaths(projectPath);
748
1011
  await ensureStudioDirectories(paths, fileSystem);
@@ -762,8 +1025,17 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
762
1025
  metadata: {
763
1026
  from_chat: fromChat,
764
1027
  scene_id: sceneId,
1028
+ spec_id: domainChainBinding.spec_id || specId || null,
765
1029
  scene_session_id: sceneSessionBinding.session.session_id,
766
- scene_cycle: sceneSessionBinding.scene_cycle
1030
+ scene_cycle: sceneSessionBinding.scene_cycle,
1031
+ domain_chain_resolved: domainChainBinding.resolved === true,
1032
+ domain_chain_source: domainChainBinding.source || 'none',
1033
+ domain_chain_spec_id: domainChainBinding.spec_id || null,
1034
+ domain_chain_path: domainChainBinding.chain_path || null,
1035
+ domain_chain_summary: domainChainBinding.summary || null,
1036
+ domain_chain_reason: domainChainBinding.reason || null,
1037
+ related_specs_total: Number(relatedSpecLookup.total_candidates || 0),
1038
+ related_specs_top: relatedSpecItems
767
1039
  }
768
1040
  };
769
1041
 
@@ -775,10 +1047,33 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
775
1047
  status: 'planned',
776
1048
  source: {
777
1049
  from_chat: fromChat,
778
- goal: normalizeString(options.goal) || null
1050
+ goal: normalizeString(options.goal) || null,
1051
+ spec_id: domainChainBinding.spec_id || specId || null,
1052
+ domain_chain: {
1053
+ resolved: domainChainBinding.resolved === true,
1054
+ source: domainChainBinding.source || 'none',
1055
+ reason: domainChainBinding.reason || null,
1056
+ spec_id: domainChainBinding.spec_id || null,
1057
+ chain_path: domainChainBinding.chain_path || null,
1058
+ candidate_count: Number.isFinite(Number(domainChainBinding.candidate_count))
1059
+ ? Number(domainChainBinding.candidate_count)
1060
+ : 0,
1061
+ candidates: Array.isArray(domainChainBinding.candidates) ? domainChainBinding.candidates : [],
1062
+ summary: domainChainBinding.summary || null,
1063
+ context: domainChainBinding.context || null,
1064
+ updated_at: domainChainBinding.updated_at || null
1065
+ },
1066
+ related_specs: {
1067
+ query: relatedSpecLookup.query || '',
1068
+ scene_id: relatedSpecLookup.scene_id || null,
1069
+ total_candidates: Number(relatedSpecLookup.total_candidates || 0),
1070
+ items: relatedSpecItems
1071
+ }
779
1072
  },
780
1073
  scene: {
781
- id: sceneId
1074
+ id: sceneId,
1075
+ spec_id: domainChainBinding.spec_id || specId || null,
1076
+ related_spec_ids: relatedSpecItems.map((item) => item.spec_id)
782
1077
  },
783
1078
  session: {
784
1079
  policy: 'mandatory.scene-primary',
@@ -800,9 +1095,16 @@ async function runStudioPlanCommand(options = {}, dependencies = {}) {
800
1095
  await appendStudioEvent(paths, job, 'stage.plan.completed', {
801
1096
  from_chat: fromChat,
802
1097
  scene_id: sceneId,
1098
+ spec_id: domainChainBinding.spec_id || specId || null,
803
1099
  scene_session_id: sceneSessionBinding.session.session_id,
804
1100
  scene_cycle: sceneSessionBinding.scene_cycle,
805
- target: job.target
1101
+ target: job.target,
1102
+ domain_chain_resolved: domainChainBinding.resolved === true,
1103
+ domain_chain_source: domainChainBinding.source || 'none',
1104
+ domain_chain_spec_id: domainChainBinding.spec_id || null,
1105
+ domain_chain_path: domainChainBinding.chain_path || null,
1106
+ related_specs_total: Number(relatedSpecLookup.total_candidates || 0),
1107
+ related_spec_ids: relatedSpecItems.map((item) => item.spec_id)
806
1108
  }, fileSystem);
807
1109
  await writeLatestJob(paths, jobId, fileSystem);
808
1110
 
@@ -836,26 +1138,45 @@ async function runStudioGenerateCommand(options = {}, dependencies = {}) {
836
1138
  }
837
1139
  const sceneId = sceneArg || jobSceneId;
838
1140
  const patchBundleId = normalizeString(options.patchBundle) || `patch-${sceneId}-${Date.now()}`;
1141
+ const domainChainMetadata = buildJobDomainChainMetadata(job);
1142
+ const generateReportPath = `${STUDIO_REPORTS_DIR}/generate-${job.job_id}.json`;
1143
+ const generateReport = {
1144
+ mode: 'studio-generate',
1145
+ api_version: STUDIO_JOB_API_VERSION,
1146
+ job_id: job.job_id,
1147
+ scene_id: sceneId,
1148
+ target: normalizeString(options.target) || job.target || 'default',
1149
+ patch_bundle_id: patchBundleId,
1150
+ generated_at: nowIso(),
1151
+ domain_chain: domainChainMetadata
1152
+ };
1153
+ await writeStudioReport(projectPath, generateReportPath, generateReport, fileSystem);
839
1154
 
840
1155
  job.scene = job.scene || {};
841
1156
  job.scene.id = sceneId;
1157
+ job.scene.spec_id = normalizeString(job?.source?.spec_id) || normalizeString(job?.scene?.spec_id) || null;
842
1158
  job.target = normalizeString(options.target) || job.target || 'default';
843
1159
  job.status = 'generated';
844
1160
  job.artifacts = job.artifacts || {};
845
1161
  job.artifacts.patch_bundle_id = patchBundleId;
1162
+ job.artifacts.generate_report = generateReportPath;
846
1163
  job.updated_at = nowIso();
847
1164
 
848
1165
  ensureStageCompleted(job, 'generate', {
849
1166
  scene_id: sceneId,
850
1167
  target: job.target,
851
- patch_bundle_id: patchBundleId
1168
+ patch_bundle_id: patchBundleId,
1169
+ domain_chain: domainChainMetadata,
1170
+ report: generateReportPath
852
1171
  });
853
1172
 
854
1173
  await saveJob(paths, job, fileSystem);
855
1174
  await appendStudioEvent(paths, job, 'stage.generate.completed', {
856
1175
  scene_id: sceneId,
857
1176
  target: job.target,
858
- patch_bundle_id: patchBundleId
1177
+ patch_bundle_id: patchBundleId,
1178
+ domain_chain: domainChainMetadata,
1179
+ report: generateReportPath
859
1180
  }, fileSystem);
860
1181
  await writeLatestJob(paths, jobId, fileSystem);
861
1182
 
@@ -931,6 +1252,7 @@ async function runStudioVerifyCommand(options = {}, dependencies = {}) {
931
1252
 
932
1253
  const verifyReportPath = `${STUDIO_REPORTS_DIR}/verify-${job.job_id}.json`;
933
1254
  const verifyStartedAt = nowIso();
1255
+ const domainChainMetadata = buildJobDomainChainMetadata(job);
934
1256
  const autoErrorbookRecords = [];
935
1257
  const gateSteps = await buildVerifyGateSteps({ profile }, {
936
1258
  projectPath,
@@ -946,7 +1268,8 @@ async function runStudioVerifyCommand(options = {}, dependencies = {}) {
946
1268
  stage: 'verify',
947
1269
  profile,
948
1270
  job_id: job.job_id,
949
- scene_id: job?.scene?.id
1271
+ scene_id: job?.scene?.id,
1272
+ spec_id: domainChainMetadata.spec_id
950
1273
  }, {
951
1274
  projectPath,
952
1275
  fileSystem,
@@ -971,6 +1294,7 @@ async function runStudioVerifyCommand(options = {}, dependencies = {}) {
971
1294
  completed_at: verifyCompletedAt,
972
1295
  passed: gateResult.passed,
973
1296
  steps: gateResult.steps,
1297
+ domain_chain: domainChainMetadata,
974
1298
  auto_errorbook_records: autoErrorbookRecords
975
1299
  };
976
1300
 
@@ -989,6 +1313,7 @@ async function runStudioVerifyCommand(options = {}, dependencies = {}) {
989
1313
  profile,
990
1314
  passed: false,
991
1315
  report: verifyReportPath,
1316
+ domain_chain: domainChainMetadata,
992
1317
  auto_errorbook_records: autoErrorbookRecords
993
1318
  }
994
1319
  };
@@ -996,6 +1321,7 @@ async function runStudioVerifyCommand(options = {}, dependencies = {}) {
996
1321
  await appendStudioEvent(paths, job, 'stage.verify.failed', {
997
1322
  profile,
998
1323
  report: verifyReportPath,
1324
+ domain_chain: domainChainMetadata,
999
1325
  auto_errorbook_records: autoErrorbookRecords
1000
1326
  }, fileSystem);
1001
1327
  await writeLatestJob(paths, jobId, fileSystem);
@@ -1007,6 +1333,7 @@ async function runStudioVerifyCommand(options = {}, dependencies = {}) {
1007
1333
  profile,
1008
1334
  passed: true,
1009
1335
  report: verifyReportPath,
1336
+ domain_chain: domainChainMetadata,
1010
1337
  auto_errorbook_records: autoErrorbookRecords
1011
1338
  });
1012
1339
 
@@ -1015,6 +1342,7 @@ async function runStudioVerifyCommand(options = {}, dependencies = {}) {
1015
1342
  profile,
1016
1343
  passed: true,
1017
1344
  report: verifyReportPath,
1345
+ domain_chain: domainChainMetadata,
1018
1346
  auto_errorbook_records: autoErrorbookRecords
1019
1347
  }, fileSystem);
1020
1348
  await writeLatestJob(paths, jobId, fileSystem);
@@ -1055,6 +1383,7 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
1055
1383
  const profile = normalizeString(options.profile) || 'standard';
1056
1384
  const releaseReportPath = `${STUDIO_REPORTS_DIR}/release-${job.job_id}.json`;
1057
1385
  const releaseStartedAt = nowIso();
1386
+ const domainChainMetadata = buildJobDomainChainMetadata(job);
1058
1387
  const autoErrorbookRecords = [];
1059
1388
  const gateSteps = await buildReleaseGateSteps({ profile }, {
1060
1389
  projectPath,
@@ -1070,7 +1399,8 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
1070
1399
  stage: 'release',
1071
1400
  profile,
1072
1401
  job_id: job.job_id,
1073
- scene_id: job?.scene?.id
1402
+ scene_id: job?.scene?.id,
1403
+ spec_id: domainChainMetadata.spec_id
1074
1404
  }, {
1075
1405
  projectPath,
1076
1406
  fileSystem,
@@ -1097,6 +1427,7 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
1097
1427
  completed_at: releaseCompletedAt,
1098
1428
  passed: gateResult.passed,
1099
1429
  steps: gateResult.steps,
1430
+ domain_chain: domainChainMetadata,
1100
1431
  auto_errorbook_records: autoErrorbookRecords
1101
1432
  };
1102
1433
 
@@ -1118,6 +1449,7 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
1118
1449
  passed: false,
1119
1450
  report: releaseReportPath,
1120
1451
  auth_required: authResult.required,
1452
+ domain_chain: domainChainMetadata,
1121
1453
  auto_errorbook_records: autoErrorbookRecords
1122
1454
  }
1123
1455
  };
@@ -1127,6 +1459,7 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
1127
1459
  release_ref: releaseRef,
1128
1460
  report: releaseReportPath,
1129
1461
  auth_required: authResult.required,
1462
+ domain_chain: domainChainMetadata,
1130
1463
  auto_errorbook_records: autoErrorbookRecords
1131
1464
  }, fileSystem);
1132
1465
  await writeLatestJob(paths, jobId, fileSystem);
@@ -1139,6 +1472,7 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
1139
1472
  release_ref: releaseRef,
1140
1473
  report: releaseReportPath,
1141
1474
  auth_required: authResult.required,
1475
+ domain_chain: domainChainMetadata,
1142
1476
  auto_errorbook_records: autoErrorbookRecords
1143
1477
  });
1144
1478
 
@@ -1169,6 +1503,7 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
1169
1503
  release_ref: releaseRef,
1170
1504
  report: releaseReportPath,
1171
1505
  auth_required: authResult.required,
1506
+ domain_chain: domainChainMetadata,
1172
1507
  auto_errorbook_records: autoErrorbookRecords
1173
1508
  }, fileSystem);
1174
1509
  await writeLatestJob(paths, jobId, fileSystem);
@@ -1320,6 +1655,7 @@ function registerStudioCommands(program) {
1320
1655
  .description('Create/refresh a studio plan job from chat context')
1321
1656
  .requiredOption('--scene <scene-id>', 'Scene identifier (mandatory primary session anchor)')
1322
1657
  .requiredOption('--from-chat <session>', 'Chat session identifier or transcript reference')
1658
+ .option('--spec <spec-id>', 'Optional spec binding for domain-chain context ingestion')
1323
1659
  .option('--goal <goal>', 'Optional goal summary')
1324
1660
  .option('--target <target>', 'Target integration profile', 'default')
1325
1661
  .option('--job <job-id>', 'Reuse an explicit studio job id')