scene-capability-engine 3.6.44 → 3.6.46

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/bin/scene-capability-engine.js +36 -2
  3. package/docs/command-reference.md +5 -0
  4. package/docs/releases/README.md +2 -0
  5. package/docs/releases/v3.6.45.md +18 -0
  6. package/docs/releases/v3.6.46.md +23 -0
  7. package/docs/zh/releases/README.md +2 -0
  8. package/docs/zh/releases/v3.6.45.md +18 -0
  9. package/docs/zh/releases/v3.6.46.md +23 -0
  10. package/lib/workspace/collab-governance-audit.js +575 -0
  11. package/package.json +4 -2
  12. package/scripts/auto-strategy-router.js +231 -0
  13. package/scripts/capability-mapping-report.js +339 -0
  14. package/scripts/check-branding-consistency.js +140 -0
  15. package/scripts/check-sce-tracking.js +54 -0
  16. package/scripts/check-skip-allowlist.js +94 -0
  17. package/scripts/errorbook-registry-health-gate.js +172 -0
  18. package/scripts/errorbook-release-gate.js +132 -0
  19. package/scripts/failure-attribution-repair.js +317 -0
  20. package/scripts/git-managed-gate.js +464 -0
  21. package/scripts/interactive-approval-event-projection.js +400 -0
  22. package/scripts/interactive-approval-workflow.js +829 -0
  23. package/scripts/interactive-authorization-tier-evaluate.js +413 -0
  24. package/scripts/interactive-change-plan-gate.js +225 -0
  25. package/scripts/interactive-context-bridge.js +617 -0
  26. package/scripts/interactive-customization-loop.js +1690 -0
  27. package/scripts/interactive-dialogue-governance.js +842 -0
  28. package/scripts/interactive-feedback-log.js +253 -0
  29. package/scripts/interactive-flow-smoke.js +238 -0
  30. package/scripts/interactive-flow.js +1059 -0
  31. package/scripts/interactive-governance-report.js +1112 -0
  32. package/scripts/interactive-intent-build.js +707 -0
  33. package/scripts/interactive-loop-smoke.js +215 -0
  34. package/scripts/interactive-moqui-adapter.js +304 -0
  35. package/scripts/interactive-plan-build.js +426 -0
  36. package/scripts/interactive-runtime-policy-evaluate.js +495 -0
  37. package/scripts/interactive-work-order-build.js +552 -0
  38. package/scripts/matrix-regression-gate.js +167 -0
  39. package/scripts/moqui-core-regression-suite.js +397 -0
  40. package/scripts/moqui-lexicon-audit.js +651 -0
  41. package/scripts/moqui-matrix-remediation-phased-runner.js +865 -0
  42. package/scripts/moqui-matrix-remediation-queue.js +852 -0
  43. package/scripts/moqui-metadata-extract.js +1340 -0
  44. package/scripts/moqui-rebuild-gate.js +167 -0
  45. package/scripts/moqui-release-summary.js +729 -0
  46. package/scripts/moqui-standard-rebuild.js +1370 -0
  47. package/scripts/moqui-template-baseline-report.js +682 -0
  48. package/scripts/npm-package-runtime-asset-check.js +221 -0
  49. package/scripts/problem-closure-gate.js +441 -0
  50. package/scripts/release-asset-integrity-check.js +216 -0
  51. package/scripts/release-asset-nonempty-normalize.js +166 -0
  52. package/scripts/release-drift-evaluate.js +223 -0
  53. package/scripts/release-drift-signals.js +255 -0
  54. package/scripts/release-governance-snapshot-export.js +132 -0
  55. package/scripts/release-ops-weekly-summary.js +934 -0
  56. package/scripts/release-risk-remediation-bundle.js +315 -0
  57. package/scripts/release-weekly-ops-gate.js +423 -0
  58. package/scripts/state-migration-reconciliation-gate.js +110 -0
  59. package/scripts/state-storage-tiering-audit.js +337 -0
  60. package/scripts/steering-content-audit.js +393 -0
  61. package/scripts/symbol-evidence-locate.js +366 -0
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { execSync, spawnSync } = require('child_process');
7
+
8
+ function parseArgs(argv = []) {
9
+ const options = {
10
+ projectPath: process.cwd(),
11
+ failOnViolation: false,
12
+ json: false,
13
+ help: false
14
+ };
15
+
16
+ for (let index = 0; index < argv.length; index += 1) {
17
+ const token = argv[index];
18
+ const next = argv[index + 1];
19
+
20
+ if (token === '--project-path' && next) {
21
+ options.projectPath = path.resolve(next);
22
+ index += 1;
23
+ } else if (token === '--fail-on-violation') {
24
+ options.failOnViolation = true;
25
+ } else if (token === '--json') {
26
+ options.json = true;
27
+ } else if (token === '--help' || token === '-h') {
28
+ options.help = true;
29
+ }
30
+ }
31
+
32
+ return options;
33
+ }
34
+
35
+ function printHelp() {
36
+ const lines = [
37
+ 'Usage: node scripts/npm-package-runtime-asset-check.js [options]',
38
+ '',
39
+ 'Options:',
40
+ ' --project-path <path> Project path (default: cwd)',
41
+ ' --fail-on-violation Exit non-zero when runtime assets are missing',
42
+ ' --json Print JSON payload',
43
+ ' -h, --help Show help'
44
+ ];
45
+ process.stdout.write(`${lines.join('\n')}\n`);
46
+ }
47
+
48
+ function resolveNpmCommand() {
49
+ return process.platform === 'win32' ? 'npm.cmd' : 'npm';
50
+ }
51
+
52
+ function collectRuntimeScriptFiles(projectPath = process.cwd(), dependencies = {}) {
53
+ const fileSystem = dependencies.fileSystem || fs;
54
+ const scriptsDir = path.join(projectPath, 'scripts');
55
+ const results = [];
56
+
57
+ function walk(currentDir) {
58
+ let entries = [];
59
+ try {
60
+ entries = fileSystem.readdirSync(currentDir, { withFileTypes: true });
61
+ } catch (_error) {
62
+ return;
63
+ }
64
+
65
+ for (const entry of entries) {
66
+ const absolutePath = path.join(currentDir, entry.name);
67
+ if (entry.isDirectory()) {
68
+ walk(absolutePath);
69
+ continue;
70
+ }
71
+ if (!entry.isFile()) {
72
+ continue;
73
+ }
74
+ const relativePath = path.relative(projectPath, absolutePath).replace(/\\/g, '/');
75
+ if (!relativePath.startsWith('scripts/') || !relativePath.endsWith('.js')) {
76
+ continue;
77
+ }
78
+ results.push(relativePath);
79
+ }
80
+ }
81
+
82
+ walk(scriptsDir);
83
+ return results.sort();
84
+ }
85
+
86
+ function runPackDryRun(projectPath = process.cwd(), dependencies = {}) {
87
+ if (typeof dependencies.packRunner === 'function') {
88
+ return dependencies.packRunner(projectPath);
89
+ }
90
+
91
+ if (process.platform === 'win32') {
92
+ try {
93
+ const stdout = execSync('npm pack --json --dry-run', {
94
+ cwd: projectPath,
95
+ encoding: 'utf8',
96
+ windowsHide: true,
97
+ maxBuffer: 20 * 1024 * 1024
98
+ });
99
+ return {
100
+ status: 0,
101
+ stdout: `${stdout || ''}`,
102
+ stderr: '',
103
+ error: ''
104
+ };
105
+ } catch (error) {
106
+ return {
107
+ status: Number.isInteger(error.status) ? error.status : 1,
108
+ stdout: `${error.stdout || ''}`,
109
+ stderr: `${error.stderr || ''}`,
110
+ error: `${error.message || ''}`.trim()
111
+ };
112
+ }
113
+ }
114
+
115
+ const result = spawnSync(resolveNpmCommand(), ['pack', '--json', '--dry-run'], {
116
+ cwd: projectPath,
117
+ encoding: 'utf8',
118
+ windowsHide: true,
119
+ maxBuffer: 20 * 1024 * 1024
120
+ });
121
+
122
+ return {
123
+ status: Number.isInteger(result.status) ? result.status : 1,
124
+ stdout: `${result.stdout || ''}`,
125
+ stderr: `${result.stderr || ''}`,
126
+ error: result.error ? `${result.error.message || ''}`.trim() : ''
127
+ };
128
+ }
129
+
130
+ function extractPackedFilePaths(packStdout) {
131
+ const parsed = JSON.parse(`${packStdout || ''}`);
132
+ const payload = Array.isArray(parsed) ? parsed[0] : parsed;
133
+ const files = Array.isArray(payload && payload.files) ? payload.files : [];
134
+ return files
135
+ .map((entry) => entry && typeof entry.path === 'string' ? entry.path.replace(/\\/g, '/') : '')
136
+ .filter(Boolean)
137
+ .sort();
138
+ }
139
+
140
+ function evaluatePackageRuntimeAssets(projectPath = process.cwd(), options = {}, dependencies = {}) {
141
+ const expectedScripts = Array.isArray(options.expectedScripts)
142
+ ? options.expectedScripts.slice().sort()
143
+ : collectRuntimeScriptFiles(projectPath, dependencies);
144
+ const packResult = runPackDryRun(projectPath, dependencies);
145
+
146
+ if (packResult.status !== 0) {
147
+ const packError = `${packResult.stderr || ''}`.trim()
148
+ || `${packResult.error || ''}`.trim()
149
+ || 'unknown error';
150
+ const violations = [`npm pack --dry-run failed: ${packError}`];
151
+ return {
152
+ exit_code: options.failOnViolation ? 1 : 0,
153
+ passed: false,
154
+ blocked: options.failOnViolation === true,
155
+ violations,
156
+ payload: {
157
+ mode: 'npm-package-runtime-asset-check',
158
+ project_path: projectPath,
159
+ expected_script_count: expectedScripts.length,
160
+ expected_scripts: expectedScripts,
161
+ packed_script_count: 0,
162
+ packed_scripts: [],
163
+ missing_runtime_scripts: expectedScripts,
164
+ pack_error: packError,
165
+ passed: false
166
+ }
167
+ };
168
+ }
169
+
170
+ const packedFiles = extractPackedFilePaths(packResult.stdout);
171
+ const packedScripts = packedFiles.filter((item) => item.startsWith('scripts/'));
172
+ const packedScriptSet = new Set(packedScripts);
173
+ const missingRuntimeScripts = expectedScripts.filter((item) => !packedScriptSet.has(item));
174
+ const violations = missingRuntimeScripts.map((item) => `missing runtime script from npm package: ${item}`);
175
+ const passed = violations.length === 0;
176
+ const blocked = options.failOnViolation === true && !passed;
177
+
178
+ return {
179
+ exit_code: blocked ? 1 : 0,
180
+ passed,
181
+ blocked,
182
+ violations,
183
+ payload: {
184
+ mode: 'npm-package-runtime-asset-check',
185
+ project_path: projectPath,
186
+ expected_script_count: expectedScripts.length,
187
+ expected_scripts: expectedScripts,
188
+ packed_script_count: packedScripts.length,
189
+ packed_scripts: packedScripts,
190
+ missing_runtime_scripts: missingRuntimeScripts,
191
+ passed
192
+ }
193
+ };
194
+ }
195
+
196
+ if (require.main === module) {
197
+ const options = parseArgs(process.argv.slice(2));
198
+ if (options.help) {
199
+ printHelp();
200
+ process.exit(0);
201
+ }
202
+
203
+ const result = evaluatePackageRuntimeAssets(options.projectPath, options);
204
+ if (options.json) {
205
+ process.stdout.write(`${JSON.stringify(result.payload, null, 2)}\n`);
206
+ } else if (result.passed) {
207
+ process.stdout.write('[npm-package-runtime-assets] passed\n');
208
+ } else {
209
+ process.stdout.write('[npm-package-runtime-assets] blocked\n');
210
+ result.violations.forEach((item) => process.stdout.write(`[npm-package-runtime-assets] violation=${item}\n`));
211
+ }
212
+ process.exit(result.exit_code);
213
+ }
214
+
215
+ module.exports = {
216
+ collectRuntimeScriptFiles,
217
+ evaluatePackageRuntimeAssets,
218
+ extractPackedFilePaths,
219
+ parseArgs,
220
+ runPackDryRun
221
+ };
@@ -0,0 +1,441 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const fs = require('fs-extra');
6
+ const {
7
+ validateSpecDomainArtifacts,
8
+ analyzeSpecDomainCoverage
9
+ } = require('../lib/spec/domain-modeling');
10
+
11
+ const DEFAULT_POLICY_PATH = '.sce/config/problem-closure-policy.json';
12
+ const DEFAULT_GOVERNANCE_REPORT = '.sce/reports/interactive-governance-report.json';
13
+ const DEFAULT_CONTRACT_RELATIVE_PATH = path.join('custom', 'problem-contract.json');
14
+ const DEFAULT_POLICY = Object.freeze({
15
+ schema_version: '1.0',
16
+ enabled: true,
17
+ governance_report_path: DEFAULT_GOVERNANCE_REPORT,
18
+ verify: {
19
+ require_problem_contract: true,
20
+ require_domain_validation: true,
21
+ require_domain_coverage: true
22
+ },
23
+ release: {
24
+ require_problem_contract: true,
25
+ require_domain_validation: true,
26
+ require_domain_coverage: true,
27
+ require_verify_report: true,
28
+ require_governance_report: false,
29
+ block_on_high_governance_alerts: true
30
+ }
31
+ });
32
+
33
+ function normalizeText(value) {
34
+ if (typeof value !== 'string') {
35
+ return '';
36
+ }
37
+ return value.trim();
38
+ }
39
+
40
+ function normalizeTextList(value = [], limit = 20) {
41
+ if (!Array.isArray(value)) {
42
+ return [];
43
+ }
44
+ return value
45
+ .map((item) => {
46
+ if (typeof item === 'string') {
47
+ return normalizeText(item);
48
+ }
49
+ if (item && typeof item === 'object') {
50
+ return normalizeText(item.step || item.description || item.id || item.name || '');
51
+ }
52
+ return '';
53
+ })
54
+ .filter(Boolean)
55
+ .slice(0, limit);
56
+ }
57
+
58
+ function parseArgs(argv = []) {
59
+ const options = {
60
+ projectPath: process.cwd(),
61
+ stage: 'release',
62
+ spec: '',
63
+ policy: DEFAULT_POLICY_PATH,
64
+ verifyReport: '',
65
+ governanceReport: DEFAULT_GOVERNANCE_REPORT,
66
+ failOnBlock: false,
67
+ json: false,
68
+ help: false
69
+ };
70
+
71
+ for (let index = 0; index < argv.length; index += 1) {
72
+ const token = argv[index];
73
+ const next = argv[index + 1];
74
+ if (token === '--project-path' && next) {
75
+ options.projectPath = path.resolve(next);
76
+ index += 1;
77
+ } else if (token === '--stage' && next) {
78
+ options.stage = normalizeText(next).toLowerCase();
79
+ index += 1;
80
+ } else if ((token === '--spec' || token === '--spec-id') && next) {
81
+ options.spec = normalizeText(next);
82
+ index += 1;
83
+ } else if (token === '--policy' && next) {
84
+ options.policy = normalizeText(next);
85
+ index += 1;
86
+ } else if (token === '--verify-report' && next) {
87
+ options.verifyReport = normalizeText(next);
88
+ index += 1;
89
+ } else if (token === '--governance-report' && next) {
90
+ options.governanceReport = normalizeText(next);
91
+ index += 1;
92
+ } else if (token === '--fail-on-block') {
93
+ options.failOnBlock = true;
94
+ } else if (token === '--json') {
95
+ options.json = true;
96
+ } else if (token === '--help' || token === '-h') {
97
+ options.help = true;
98
+ }
99
+ }
100
+ return options;
101
+ }
102
+
103
+ function printHelp() {
104
+ const lines = [
105
+ 'Usage: node scripts/problem-closure-gate.js [options]',
106
+ '',
107
+ 'Options:',
108
+ ' --stage <verify|release> Gate stage (default: release)',
109
+ ' --spec <spec-id> Spec id under .sce/specs (required for strict checks)',
110
+ ` --policy <path> Policy path (default: ${DEFAULT_POLICY_PATH})`,
111
+ ' --verify-report <path> Verify report path (required for release stage convergence)',
112
+ ` --governance-report <path> Governance report path (default: ${DEFAULT_GOVERNANCE_REPORT})`,
113
+ ' --project-path <path> Project path (default: cwd)',
114
+ ' --fail-on-block Exit with code 2 when blocked',
115
+ ' --json Print JSON payload',
116
+ ' -h, --help Show help'
117
+ ];
118
+ process.stdout.write(`${lines.join('\n')}\n`);
119
+ }
120
+
121
+ function loadPolicy(raw = {}) {
122
+ const payload = raw && typeof raw === 'object' ? raw : {};
123
+ const verify = payload.verify && typeof payload.verify === 'object' ? payload.verify : {};
124
+ const release = payload.release && typeof payload.release === 'object' ? payload.release : {};
125
+ return {
126
+ schema_version: normalizeText(payload.schema_version) || DEFAULT_POLICY.schema_version,
127
+ enabled: payload.enabled !== false,
128
+ governance_report_path: normalizeText(payload.governance_report_path) || DEFAULT_POLICY.governance_report_path,
129
+ verify: {
130
+ require_problem_contract: verify.require_problem_contract !== false,
131
+ require_domain_validation: verify.require_domain_validation !== false,
132
+ require_domain_coverage: verify.require_domain_coverage !== false
133
+ },
134
+ release: {
135
+ require_problem_contract: release.require_problem_contract !== false,
136
+ require_domain_validation: release.require_domain_validation !== false,
137
+ require_domain_coverage: release.require_domain_coverage !== false,
138
+ require_verify_report: release.require_verify_report !== false,
139
+ require_governance_report: release.require_governance_report === true,
140
+ block_on_high_governance_alerts: release.block_on_high_governance_alerts !== false
141
+ }
142
+ };
143
+ }
144
+
145
+ function evaluateProblemContract(contract = {}) {
146
+ const source = contract && typeof contract === 'object' ? contract : {};
147
+ const checks = {
148
+ issue_statement: normalizeText(source.issue_statement || source.issue || source.problem_statement).length > 0,
149
+ expected_outcome: normalizeText(source.expected_outcome || source.expected || source.success_criteria).length > 0,
150
+ reproduction_steps: normalizeTextList(source.reproduction_steps || source.repro_steps || source.steps).length > 0,
151
+ impact_scope: normalizeText(source.impact_scope || source.scope).length > 0,
152
+ forbidden_workarounds: normalizeTextList(
153
+ source.forbidden_workarounds || source.prohibited_workarounds || source.disallowed_workarounds
154
+ ).length > 0
155
+ };
156
+ const missing = Object.keys(checks).filter((key) => !checks[key]);
157
+ return {
158
+ passed: missing.length === 0,
159
+ checks,
160
+ missing
161
+ };
162
+ }
163
+
164
+ async function readVerifySignals(projectPath, reportPath, fileSystem = fs) {
165
+ const normalized = normalizeText(reportPath);
166
+ if (!normalized) {
167
+ return {
168
+ available: false,
169
+ report_path: null,
170
+ passed: false,
171
+ failed_step_count: 0
172
+ };
173
+ }
174
+ const absolutePath = path.isAbsolute(normalized)
175
+ ? normalized
176
+ : path.join(projectPath, normalized);
177
+ if (!await fileSystem.pathExists(absolutePath)) {
178
+ return {
179
+ available: false,
180
+ report_path: normalized,
181
+ passed: false,
182
+ failed_step_count: 0
183
+ };
184
+ }
185
+ const payload = await fileSystem.readJson(absolutePath).catch(() => null);
186
+ const steps = Array.isArray(payload && payload.steps) ? payload.steps : [];
187
+ const failedStepCount = steps.filter((step) => normalizeText(step && step.status).toLowerCase() === 'failed').length;
188
+ return {
189
+ available: true,
190
+ report_path: path.relative(projectPath, absolutePath).replace(/\\/g, '/'),
191
+ passed: payload && payload.passed === true && failedStepCount === 0,
192
+ failed_step_count: failedStepCount
193
+ };
194
+ }
195
+
196
+ async function readGovernanceSignals(projectPath, reportPath, fileSystem = fs) {
197
+ const normalized = normalizeText(reportPath) || DEFAULT_GOVERNANCE_REPORT;
198
+ const absolutePath = path.isAbsolute(normalized)
199
+ ? normalized
200
+ : path.join(projectPath, normalized);
201
+ if (!await fileSystem.pathExists(absolutePath)) {
202
+ return {
203
+ available: false,
204
+ report_path: normalized,
205
+ high_breach_count: 0,
206
+ medium_breach_count: 0
207
+ };
208
+ }
209
+ const payload = await fileSystem.readJson(absolutePath).catch(() => null);
210
+ const alerts = Array.isArray(payload && payload.alerts) ? payload.alerts : [];
211
+ let high = 0;
212
+ let medium = 0;
213
+ for (const alert of alerts) {
214
+ const status = normalizeText(alert && alert.status).toLowerCase();
215
+ const severity = normalizeText(alert && alert.severity).toLowerCase();
216
+ if (status !== 'breach') {
217
+ continue;
218
+ }
219
+ if (severity === 'high') {
220
+ high += 1;
221
+ } else if (severity === 'medium') {
222
+ medium += 1;
223
+ }
224
+ }
225
+ return {
226
+ available: true,
227
+ report_path: path.relative(projectPath, absolutePath).replace(/\\/g, '/'),
228
+ high_breach_count: high,
229
+ medium_breach_count: medium
230
+ };
231
+ }
232
+
233
+ async function evaluateProblemClosureGate(options = {}, dependencies = {}) {
234
+ const projectPath = options.projectPath || dependencies.projectPath || process.cwd();
235
+ const fileSystem = dependencies.fileSystem || fs;
236
+ const stage = normalizeText(options.stage || 'release').toLowerCase();
237
+ const specId = normalizeText(options.spec || options.specId);
238
+ const policyPath = normalizeText(options.policy || options.policyPath || DEFAULT_POLICY_PATH);
239
+ const policyAbsolutePath = path.isAbsolute(policyPath)
240
+ ? policyPath
241
+ : path.join(projectPath, policyPath);
242
+ const policyPayload = await fileSystem.readJson(policyAbsolutePath).catch(() => ({}));
243
+ const policy = loadPolicy(policyPayload);
244
+ const stagePolicy = stage === 'release' ? policy.release : policy.verify;
245
+ const verifyReport = normalizeText(options.verifyReport || options.verify_report);
246
+ const governanceReport = normalizeText(
247
+ options.governanceReport || options.governance_report || policy.governance_report_path || DEFAULT_GOVERNANCE_REPORT
248
+ );
249
+ if (!['verify', 'release'].includes(stage)) {
250
+ throw new Error(`--stage must be verify|release, received: ${stage || '(empty)'}`);
251
+ }
252
+
253
+ const warnings = [];
254
+ const violations = [];
255
+ const checks = {
256
+ spec_available: false,
257
+ problem_contract: {
258
+ available: false,
259
+ passed: false,
260
+ missing: []
261
+ },
262
+ domain_artifacts: {
263
+ passed: false,
264
+ warnings: []
265
+ },
266
+ domain_coverage: {
267
+ passed: false,
268
+ uncovered: []
269
+ },
270
+ convergence: {
271
+ verify_report: {
272
+ available: false,
273
+ passed: false,
274
+ failed_step_count: 0
275
+ },
276
+ governance: {
277
+ available: false,
278
+ high_breach_count: 0,
279
+ medium_breach_count: 0
280
+ }
281
+ }
282
+ };
283
+
284
+ if (policy.enabled !== true) {
285
+ warnings.push('problem closure gate disabled by policy');
286
+ return {
287
+ mode: 'problem-closure-gate',
288
+ stage,
289
+ spec_id: specId || null,
290
+ skipped: true,
291
+ passed: true,
292
+ policy,
293
+ warnings,
294
+ violations: [],
295
+ checks
296
+ };
297
+ }
298
+
299
+ if (!specId) {
300
+ warnings.push('spec id is not provided; problem closure gate skipped');
301
+ return {
302
+ mode: 'problem-closure-gate',
303
+ stage,
304
+ spec_id: null,
305
+ skipped: true,
306
+ passed: true,
307
+ policy,
308
+ warnings,
309
+ violations: [],
310
+ checks
311
+ };
312
+ }
313
+
314
+ const specRoot = path.join(projectPath, '.sce', 'specs', specId);
315
+ const specExists = await fileSystem.pathExists(specRoot);
316
+ checks.spec_available = specExists;
317
+ if (!specExists) {
318
+ violations.push(`spec not found: ${specId}`);
319
+ } else {
320
+ const contractPath = path.join(specRoot, DEFAULT_CONTRACT_RELATIVE_PATH);
321
+ const contractExists = await fileSystem.pathExists(contractPath);
322
+ checks.problem_contract.available = contractExists;
323
+ if (!contractExists && stagePolicy.require_problem_contract) {
324
+ violations.push(`missing required artifact: .sce/specs/${specId}/${DEFAULT_CONTRACT_RELATIVE_PATH.replace(/\\/g, '/')}`);
325
+ } else if (contractExists) {
326
+ const contractPayload = await fileSystem.readJson(contractPath).catch(() => null);
327
+ const contractEval = evaluateProblemContract(contractPayload || {});
328
+ checks.problem_contract.passed = contractEval.passed;
329
+ checks.problem_contract.missing = contractEval.missing;
330
+ if (!contractEval.passed && stagePolicy.require_problem_contract) {
331
+ violations.push(`problem contract incomplete: ${contractEval.missing.join(', ')}`);
332
+ }
333
+ }
334
+
335
+ const domainValidation = await validateSpecDomainArtifacts(projectPath, specId, fileSystem);
336
+ checks.domain_artifacts.passed = domainValidation.passed;
337
+ checks.domain_artifacts.warnings = Array.isArray(domainValidation.warnings) ? domainValidation.warnings : [];
338
+ if (!domainValidation.passed && stagePolicy.require_domain_validation) {
339
+ violations.push(`spec-domain validation failed: ${checks.domain_artifacts.warnings.join('; ')}`);
340
+ }
341
+
342
+ const domainCoverage = await analyzeSpecDomainCoverage(projectPath, specId, fileSystem);
343
+ checks.domain_coverage.passed = domainCoverage.passed;
344
+ checks.domain_coverage.uncovered = Array.isArray(domainCoverage.uncovered) ? domainCoverage.uncovered : [];
345
+ if (!domainCoverage.passed && stagePolicy.require_domain_coverage) {
346
+ violations.push(`spec-domain coverage gaps: ${checks.domain_coverage.uncovered.join(', ')}`);
347
+ }
348
+ }
349
+
350
+ if (stage === 'release') {
351
+ const verifySignals = await readVerifySignals(projectPath, verifyReport, fileSystem);
352
+ checks.convergence.verify_report = verifySignals;
353
+ if (!verifySignals.available && stagePolicy.require_verify_report) {
354
+ violations.push('verify report is required for release convergence gate');
355
+ } else if (verifySignals.available && !verifySignals.passed && stagePolicy.require_verify_report) {
356
+ violations.push('verify report indicates failed checks or failed steps');
357
+ }
358
+
359
+ const governanceSignals = await readGovernanceSignals(projectPath, governanceReport, fileSystem);
360
+ checks.convergence.governance = governanceSignals;
361
+ if (!governanceSignals.available && stagePolicy.require_governance_report) {
362
+ violations.push('governance report is required for release convergence gate');
363
+ }
364
+ if (
365
+ governanceSignals.available
366
+ && governanceSignals.high_breach_count > 0
367
+ && stagePolicy.block_on_high_governance_alerts
368
+ ) {
369
+ violations.push(`interactive governance has high-severity breaches: ${governanceSignals.high_breach_count}`);
370
+ }
371
+ }
372
+
373
+ return {
374
+ mode: 'problem-closure-gate',
375
+ stage,
376
+ spec_id: specId,
377
+ skipped: false,
378
+ policy,
379
+ passed: violations.length === 0,
380
+ blocked: violations.length > 0,
381
+ warnings,
382
+ violations,
383
+ checks
384
+ };
385
+ }
386
+
387
+ async function runProblemClosureGateScript(options = {}, dependencies = {}) {
388
+ const payload = await evaluateProblemClosureGate(options, dependencies);
389
+ if (options.json) {
390
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
391
+ } else if (payload.passed) {
392
+ process.stdout.write('[problem-closure-gate] passed\n');
393
+ if (payload.skipped) {
394
+ process.stdout.write('[problem-closure-gate] skipped: no bound spec\n');
395
+ }
396
+ } else {
397
+ process.stdout.write('[problem-closure-gate] blocked\n');
398
+ payload.violations.forEach((item) => {
399
+ process.stdout.write(`[problem-closure-gate] violation=${item}\n`);
400
+ });
401
+ }
402
+ return {
403
+ ...payload,
404
+ exit_code: options.failOnBlock && payload.passed !== true ? 2 : 0
405
+ };
406
+ }
407
+
408
+ if (require.main === module) {
409
+ const options = parseArgs(process.argv.slice(2));
410
+ if (options.help) {
411
+ printHelp();
412
+ process.exit(0);
413
+ }
414
+ runProblemClosureGateScript(options)
415
+ .then((result) => {
416
+ process.exitCode = result.exit_code;
417
+ })
418
+ .catch((error) => {
419
+ if (options.json) {
420
+ process.stdout.write(`${JSON.stringify({
421
+ mode: 'problem-closure-gate',
422
+ passed: false,
423
+ error: error.message
424
+ }, null, 2)}\n`);
425
+ } else {
426
+ process.stderr.write(`[problem-closure-gate] error=${error.message}\n`);
427
+ }
428
+ process.exitCode = 1;
429
+ });
430
+ }
431
+
432
+ module.exports = {
433
+ DEFAULT_POLICY_PATH,
434
+ DEFAULT_POLICY,
435
+ DEFAULT_GOVERNANCE_REPORT,
436
+ parseArgs,
437
+ loadPolicy,
438
+ evaluateProblemContract,
439
+ evaluateProblemClosureGate,
440
+ runProblemClosureGateScript
441
+ };