scene-capability-engine 3.6.45 → 3.6.47

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 (72) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +1 -0
  3. package/README.zh.md +1 -0
  4. package/docs/agent-runtime/symbol-evidence.schema.json +1 -1
  5. package/docs/command-reference.md +8 -0
  6. package/docs/interactive-customization/dialogue-governance-policy-baseline.json +4 -1
  7. package/docs/interactive-customization/embedded-assistant-authorization-dialogue-rules.md +5 -0
  8. package/docs/releases/README.md +2 -0
  9. package/docs/releases/v3.6.46.md +23 -0
  10. package/docs/releases/v3.6.47.md +23 -0
  11. package/docs/sce-business-mode-map.md +2 -1
  12. package/docs/sce-capability-matrix-e2e-example.md +2 -1
  13. package/docs/security-governance-default-baseline.md +2 -0
  14. package/docs/starter-kit/README.md +3 -0
  15. package/docs/zh/releases/README.md +2 -0
  16. package/docs/zh/releases/v3.6.46.md +23 -0
  17. package/docs/zh/releases/v3.6.47.md +23 -0
  18. package/lib/workspace/takeover-baseline.js +293 -1
  19. package/package.json +6 -2
  20. package/scripts/auto-strategy-router.js +231 -0
  21. package/scripts/capability-mapping-report.js +339 -0
  22. package/scripts/check-branding-consistency.js +140 -0
  23. package/scripts/check-sce-tracking.js +54 -0
  24. package/scripts/check-skip-allowlist.js +94 -0
  25. package/scripts/clarification-first-audit.js +322 -0
  26. package/scripts/errorbook-registry-health-gate.js +172 -0
  27. package/scripts/errorbook-release-gate.js +132 -0
  28. package/scripts/failure-attribution-repair.js +317 -0
  29. package/scripts/git-managed-gate.js +464 -0
  30. package/scripts/interactive-approval-event-projection.js +400 -0
  31. package/scripts/interactive-approval-workflow.js +829 -0
  32. package/scripts/interactive-authorization-tier-evaluate.js +413 -0
  33. package/scripts/interactive-change-plan-gate.js +225 -0
  34. package/scripts/interactive-context-bridge.js +617 -0
  35. package/scripts/interactive-customization-loop.js +1690 -0
  36. package/scripts/interactive-dialogue-governance.js +873 -0
  37. package/scripts/interactive-feedback-log.js +253 -0
  38. package/scripts/interactive-flow-smoke.js +238 -0
  39. package/scripts/interactive-flow.js +1059 -0
  40. package/scripts/interactive-governance-report.js +1112 -0
  41. package/scripts/interactive-intent-build.js +707 -0
  42. package/scripts/interactive-loop-smoke.js +215 -0
  43. package/scripts/interactive-moqui-adapter.js +304 -0
  44. package/scripts/interactive-plan-build.js +426 -0
  45. package/scripts/interactive-runtime-policy-evaluate.js +495 -0
  46. package/scripts/interactive-work-order-build.js +552 -0
  47. package/scripts/matrix-regression-gate.js +167 -0
  48. package/scripts/moqui-core-regression-suite.js +397 -0
  49. package/scripts/moqui-lexicon-audit.js +651 -0
  50. package/scripts/moqui-matrix-remediation-phased-runner.js +865 -0
  51. package/scripts/moqui-matrix-remediation-queue.js +852 -0
  52. package/scripts/moqui-metadata-extract.js +1340 -0
  53. package/scripts/moqui-rebuild-gate.js +167 -0
  54. package/scripts/moqui-release-summary.js +729 -0
  55. package/scripts/moqui-standard-rebuild.js +1370 -0
  56. package/scripts/moqui-template-baseline-report.js +682 -0
  57. package/scripts/npm-package-runtime-asset-check.js +221 -0
  58. package/scripts/problem-closure-gate.js +441 -0
  59. package/scripts/release-asset-integrity-check.js +216 -0
  60. package/scripts/release-asset-nonempty-normalize.js +166 -0
  61. package/scripts/release-drift-evaluate.js +223 -0
  62. package/scripts/release-drift-signals.js +255 -0
  63. package/scripts/release-governance-snapshot-export.js +132 -0
  64. package/scripts/release-ops-weekly-summary.js +934 -0
  65. package/scripts/release-risk-remediation-bundle.js +315 -0
  66. package/scripts/release-weekly-ops-gate.js +423 -0
  67. package/scripts/state-migration-reconciliation-gate.js +110 -0
  68. package/scripts/state-storage-tiering-audit.js +337 -0
  69. package/scripts/steering-content-audit.js +393 -0
  70. package/scripts/symbol-evidence-locate.js +370 -0
  71. package/template/.sce/README.md +1 -0
  72. package/template/.sce/steering/CORE_PRINCIPLES.md +25 -0
@@ -0,0 +1,322 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const REQUIRED_CHECKS = [
8
+ {
9
+ path: 'scripts/interactive-dialogue-governance.js',
10
+ requiredSnippets: [
11
+ 'contextHasBusinessScope',
12
+ 'goalMentionsBusinessScope',
13
+ 'business scene/module/page/entity context is missing; clarify scope before fallback or execution',
14
+ 'Which module/page/entity is affected first?'
15
+ ]
16
+ },
17
+ {
18
+ path: 'scripts/symbol-evidence-locate.js',
19
+ requiredSnippets: [
20
+ 'clarify_business_scope',
21
+ 'Clarify target module/page/entity and business constraints before deciding whether scoped writes are safe.'
22
+ ]
23
+ },
24
+ {
25
+ path: 'lib/workspace/takeover-baseline.js',
26
+ requiredSnippets: [
27
+ 'CLARIFICATION_FIRST_CORE_PRINCIPLE_HEADING',
28
+ '_reconcileCorePrinciplesBaseline',
29
+ '这条规则适用于所有接入 SCE 的项目、模式和交互面,不允许按项目例外绕过。'
30
+ ]
31
+ },
32
+ {
33
+ path: 'docs/interactive-customization/dialogue-governance-policy-baseline.json',
34
+ requiredSnippets: [
35
+ 'clarify and narrow scope before using any fallback restriction; do not replace understanding with blanket disable',
36
+ 'Which entity or business rule is affected, and what constraint must stay intact?'
37
+ ]
38
+ },
39
+ {
40
+ path: '.sce/steering/CORE_PRINCIPLES.md',
41
+ requiredSnippets: [
42
+ '业务场景未知时必须先澄清,禁止直接彻底禁用',
43
+ '这条规则适用于所有接入 SCE 的项目、模式和交互面,不允许按项目例外绕过。'
44
+ ]
45
+ },
46
+ {
47
+ path: 'template/.sce/steering/CORE_PRINCIPLES.md',
48
+ requiredSnippets: [
49
+ '业务场景未知时先澄清,不得直接彻底禁用',
50
+ '这条规则适用于所有使用 SCE 的项目,不设项目级例外。'
51
+ ]
52
+ },
53
+ {
54
+ path: 'README.md',
55
+ requiredSnippets: [
56
+ 'When business scene/module/page/entity context is missing, SCE must route to clarification first; unknown business scope must not be turned into blanket disable.'
57
+ ]
58
+ },
59
+ {
60
+ path: 'README.zh.md',
61
+ requiredSnippets: [
62
+ '缺少业务场景/模块/页面/实体上下文时,SCE 必须先进入澄清,而不是把未知业务范围直接变成一刀切禁用'
63
+ ]
64
+ },
65
+ {
66
+ path: 'docs/security-governance-default-baseline.md',
67
+ requiredSnippets: [
68
+ 'Missing business scene/module/page/entity context must route to clarification first; unknown scope is never a valid reason for blanket disable.',
69
+ 'This clarification-first rule applies to every SCE-integrated project and surface with no project-specific exception.'
70
+ ]
71
+ },
72
+ {
73
+ path: 'docs/starter-kit/README.md',
74
+ requiredSnippets: [
75
+ 'This baseline applies to every onboarded project with no project-specific exception.',
76
+ 'missing business scope is handled through clarification, not blanket disable fallback.'
77
+ ]
78
+ },
79
+ {
80
+ path: 'docs/command-reference.md',
81
+ requiredSnippets: [
82
+ 'Missing business scene/module/page/entity context defaults to `clarify`; unknown scope must not be converted into blanket disable fallback.'
83
+ ]
84
+ }
85
+ ];
86
+
87
+ const PROHIBITED_SNIPPETS = [
88
+ {
89
+ value: 'block_high_risk_write',
90
+ allowedPaths: [
91
+ 'scripts/clarification-first-audit.js',
92
+ 'tests/unit/scripts/clarification-first-audit.test.js'
93
+ ]
94
+ },
95
+ {
96
+ value: 'Fallback to answer-only mode and block high-risk writes.',
97
+ allowedPaths: [
98
+ 'scripts/clarification-first-audit.js',
99
+ 'tests/unit/scripts/clarification-first-audit.test.js'
100
+ ]
101
+ }
102
+ ];
103
+
104
+ const SEARCH_DIRECTORIES = ['lib', 'scripts', 'docs', '.sce', 'template', 'tests'];
105
+ const SEARCH_EXTENSIONS = new Set(['.js', '.md', '.json', '.txt', '.yaml', '.yml']);
106
+
107
+ function parseArgs(argv = process.argv.slice(2)) {
108
+ const options = {
109
+ projectPath: process.cwd(),
110
+ json: false,
111
+ failOnViolation: false,
112
+ out: null
113
+ };
114
+
115
+ for (let index = 0; index < argv.length; index += 1) {
116
+ const token = argv[index];
117
+ const next = argv[index + 1];
118
+ if (token === '--project-path' && next) {
119
+ options.projectPath = path.resolve(next);
120
+ index += 1;
121
+ continue;
122
+ }
123
+ if (token === '--json') {
124
+ options.json = true;
125
+ continue;
126
+ }
127
+ if (token === '--fail-on-violation') {
128
+ options.failOnViolation = true;
129
+ continue;
130
+ }
131
+ if (token === '--out' && next) {
132
+ options.out = path.resolve(next);
133
+ index += 1;
134
+ continue;
135
+ }
136
+ if (token === '--help' || token === '-h') {
137
+ printHelpAndExit(0);
138
+ }
139
+ }
140
+
141
+ return options;
142
+ }
143
+
144
+ function printHelpAndExit(code) {
145
+ const lines = [
146
+ 'Usage: node scripts/clarification-first-audit.js [options]',
147
+ '',
148
+ 'Options:',
149
+ ' --project-path <path> Project root to audit (default: current directory)',
150
+ ' --json Print JSON payload',
151
+ ' --fail-on-violation Exit code 2 when any violation is found',
152
+ ' --out <path> Write JSON payload to file',
153
+ ' -h, --help Show this help'
154
+ ];
155
+ console.log(lines.join('\n'));
156
+ process.exit(code);
157
+ }
158
+
159
+ function normalizeSlashes(value) {
160
+ return `${value || ''}`.replace(/\\/g, '/');
161
+ }
162
+
163
+ function pushViolation(violations, severity, rule, file, message) {
164
+ violations.push({
165
+ severity,
166
+ rule,
167
+ file,
168
+ message
169
+ });
170
+ }
171
+
172
+ function readText(filePath) {
173
+ return fs.readFileSync(filePath, 'utf8');
174
+ }
175
+
176
+ function collectFilesRecursive(rootDir, relativeRoot = '') {
177
+ if (!fs.existsSync(rootDir)) {
178
+ return [];
179
+ }
180
+ const results = [];
181
+ const entries = fs.readdirSync(rootDir, { withFileTypes: true });
182
+ for (const entry of entries) {
183
+ const absolutePath = path.join(rootDir, entry.name);
184
+ const relativePath = normalizeSlashes(path.join(relativeRoot, entry.name));
185
+ if (entry.isDirectory()) {
186
+ if (entry.name === 'node_modules' || entry.name === '.git') {
187
+ continue;
188
+ }
189
+ results.push(...collectFilesRecursive(absolutePath, relativePath));
190
+ continue;
191
+ }
192
+ if (!entry.isFile()) {
193
+ continue;
194
+ }
195
+ if (!SEARCH_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
196
+ continue;
197
+ }
198
+ results.push({
199
+ absolutePath,
200
+ relativePath
201
+ });
202
+ }
203
+ return results;
204
+ }
205
+
206
+ function auditClarificationFirst(options = {}) {
207
+ const projectPath = path.resolve(options.projectPath || process.cwd());
208
+ const violations = [];
209
+ const checkedFiles = [];
210
+
211
+ for (const check of REQUIRED_CHECKS) {
212
+ const relativePath = normalizeSlashes(check.path);
213
+ const absolutePath = path.join(projectPath, relativePath);
214
+ checkedFiles.push(relativePath);
215
+ if (!fs.existsSync(absolutePath)) {
216
+ pushViolation(
217
+ violations,
218
+ 'error',
219
+ 'missing_required_file',
220
+ relativePath,
221
+ `Required clarification-first baseline file is missing: ${relativePath}`
222
+ );
223
+ continue;
224
+ }
225
+
226
+ const content = readText(absolutePath);
227
+ for (const snippet of check.requiredSnippets) {
228
+ if (!content.includes(snippet)) {
229
+ pushViolation(
230
+ violations,
231
+ 'error',
232
+ 'missing_required_snippet',
233
+ relativePath,
234
+ `Missing required clarification-first snippet: ${snippet}`
235
+ );
236
+ }
237
+ }
238
+ }
239
+
240
+ const searchableFiles = SEARCH_DIRECTORIES.flatMap((dirName) => {
241
+ const absoluteDir = path.join(projectPath, dirName);
242
+ return collectFilesRecursive(absoluteDir, dirName);
243
+ });
244
+
245
+ for (const file of searchableFiles) {
246
+ const content = readText(file.absolutePath);
247
+ for (const rule of PROHIBITED_SNIPPETS) {
248
+ if (!content.includes(rule.value)) {
249
+ continue;
250
+ }
251
+ const allowedPaths = Array.isArray(rule.allowedPaths) ? rule.allowedPaths.map(normalizeSlashes) : [];
252
+ if (allowedPaths.includes(file.relativePath)) {
253
+ continue;
254
+ }
255
+ pushViolation(
256
+ violations,
257
+ 'error',
258
+ 'prohibited_legacy_disable_phrase',
259
+ file.relativePath,
260
+ `Prohibited legacy fallback phrase found: ${rule.value}`
261
+ );
262
+ }
263
+ }
264
+
265
+ const errorCount = violations.filter((item) => item.severity === 'error').length;
266
+ return {
267
+ mode: 'clarification-first-audit',
268
+ project_path: projectPath,
269
+ checked_files: checkedFiles,
270
+ searched_file_count: searchableFiles.length,
271
+ violation_count: violations.length,
272
+ error_count: errorCount,
273
+ passed: violations.length === 0,
274
+ violations
275
+ };
276
+ }
277
+
278
+ function writeReportIfNeeded(report, outPath) {
279
+ if (!outPath) {
280
+ return;
281
+ }
282
+ const resolved = path.resolve(outPath);
283
+ fs.mkdirSync(path.dirname(resolved), { recursive: true });
284
+ fs.writeFileSync(resolved, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
285
+ }
286
+
287
+ function main() {
288
+ const options = parseArgs(process.argv.slice(2));
289
+ const report = auditClarificationFirst(options);
290
+ writeReportIfNeeded(report, options.out);
291
+
292
+ if (options.json) {
293
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
294
+ } else if (report.passed) {
295
+ console.log('[clarification-first-audit] passed');
296
+ } else {
297
+ console.error(`[clarification-first-audit] failed with ${report.violation_count} violation(s)`);
298
+ for (const violation of report.violations) {
299
+ console.error(`[clarification-first-audit] ${violation.rule} ${violation.file}: ${violation.message}`);
300
+ }
301
+ }
302
+
303
+ if (options.failOnViolation && !report.passed) {
304
+ process.exitCode = 2;
305
+ }
306
+ }
307
+
308
+ if (require.main === module) {
309
+ try {
310
+ main();
311
+ } catch (error) {
312
+ console.error(`[clarification-first-audit] ${error.message}`);
313
+ process.exit(1);
314
+ }
315
+ }
316
+
317
+ module.exports = {
318
+ REQUIRED_CHECKS,
319
+ PROHIBITED_SNIPPETS,
320
+ parseArgs,
321
+ auditClarificationFirst
322
+ };
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const { runErrorbookRegistryHealthCommand } = require('../lib/commands/errorbook');
6
+
7
+ function parseBoolean(value, fallback = false) {
8
+ if (typeof value === 'boolean') {
9
+ return value;
10
+ }
11
+ const normalized = `${value || ''}`.trim().toLowerCase();
12
+ if (!normalized) {
13
+ return fallback;
14
+ }
15
+ if (['1', 'true', 'yes', 'on', 'y'].includes(normalized)) {
16
+ return true;
17
+ }
18
+ if (['0', 'false', 'no', 'off', 'n'].includes(normalized)) {
19
+ return false;
20
+ }
21
+ return fallback;
22
+ }
23
+
24
+ function parseArgs(argv = [], env = process.env) {
25
+ const options = {
26
+ strict: parseBoolean(env.SCE_REGISTRY_HEALTH_STRICT, false),
27
+ json: false,
28
+ projectPath: process.cwd(),
29
+ maxShards: 8,
30
+ shardSample: 2
31
+ };
32
+
33
+ for (let index = 0; index < argv.length; index += 1) {
34
+ const token = argv[index];
35
+ const next = argv[index + 1];
36
+
37
+ if (token === '--strict') {
38
+ options.strict = true;
39
+ } else if (token === '--no-strict') {
40
+ options.strict = false;
41
+ } else if (token === '--json') {
42
+ options.json = true;
43
+ } else if (token === '--project-path' && next) {
44
+ options.projectPath = path.resolve(next);
45
+ index += 1;
46
+ } else if (token === '--config' && next) {
47
+ options.config = next;
48
+ index += 1;
49
+ } else if (token === '--cache' && next) {
50
+ options.cache = next;
51
+ index += 1;
52
+ } else if (token === '--source' && next) {
53
+ options.source = next;
54
+ index += 1;
55
+ } else if (token === '--source-name' && next) {
56
+ options.sourceName = next;
57
+ index += 1;
58
+ } else if (token === '--index' && next) {
59
+ options.index = next;
60
+ index += 1;
61
+ } else if (token === '--max-shards' && next) {
62
+ options.maxShards = Number.parseInt(next, 10);
63
+ index += 1;
64
+ } else if (token === '--shard-sample' && next) {
65
+ options.shardSample = Number.parseInt(next, 10);
66
+ index += 1;
67
+ } else if (token === '--help' || token === '-h') {
68
+ options.help = true;
69
+ }
70
+ }
71
+
72
+ return options;
73
+ }
74
+
75
+ function printHelp() {
76
+ const lines = [
77
+ 'Usage: node scripts/errorbook-registry-health-gate.js [options]',
78
+ '',
79
+ 'Options:',
80
+ ' --strict Exit with code 2 when health check reports errors',
81
+ ' --no-strict Force advisory mode even if env is strict',
82
+ ' --project-path <path> Override project path (default: cwd)',
83
+ ' --config <path> Registry config path override',
84
+ ' --cache <path> Registry cache path override',
85
+ ' --source <url-or-path> Override registry source JSON',
86
+ ' --source-name <name> Override registry source label',
87
+ ' --index <url-or-path> Override registry index JSON',
88
+ ' --max-shards <n> Max index-resolved shards to validate (default: 8)',
89
+ ' --shard-sample <n> Number of shard files to fetch for validation (default: 2)',
90
+ ' --json Print JSON payload',
91
+ ' -h, --help Show this help',
92
+ '',
93
+ 'Environment:',
94
+ ' SCE_REGISTRY_HEALTH_STRICT=1 enables strict mode by default'
95
+ ];
96
+ process.stdout.write(`${lines.join('\n')}\n`);
97
+ }
98
+
99
+ async function runErrorbookRegistryHealthGateScript(options = {}) {
100
+ const health = await runErrorbookRegistryHealthCommand({
101
+ config: options.config,
102
+ cache: options.cache,
103
+ source: options.source,
104
+ sourceName: options.sourceName,
105
+ index: options.index,
106
+ maxShards: options.maxShards,
107
+ shardSample: options.shardSample,
108
+ silent: true
109
+ }, {
110
+ projectPath: options.projectPath
111
+ });
112
+
113
+ if (options.json) {
114
+ process.stdout.write(`${JSON.stringify({
115
+ ...health,
116
+ mode: 'errorbook-registry-health-gate',
117
+ strict: options.strict === true
118
+ }, null, 2)}\n`);
119
+ } else if (health.passed) {
120
+ process.stdout.write('[errorbook-registry-health-gate] passed\n');
121
+ process.stdout.write(
122
+ `[errorbook-registry-health-gate] sources=${health.config.source_count} warnings=${health.warning_count} errors=0\n`
123
+ );
124
+ } else {
125
+ process.stdout.write('[errorbook-registry-health-gate] alert\n');
126
+ process.stdout.write(
127
+ `[errorbook-registry-health-gate] sources=${health.config.source_count} warnings=${health.warning_count} errors=${health.error_count}\n`
128
+ );
129
+ health.errors.slice(0, 20).forEach((message) => {
130
+ process.stdout.write(`[errorbook-registry-health-gate] error=${message}\n`);
131
+ });
132
+ }
133
+
134
+ const exitCode = options.strict && !health.passed ? 2 : 0;
135
+ return {
136
+ ...health,
137
+ mode: 'errorbook-registry-health-gate',
138
+ strict: options.strict === true,
139
+ exit_code: exitCode
140
+ };
141
+ }
142
+
143
+ if (require.main === module) {
144
+ const options = parseArgs(process.argv.slice(2));
145
+ if (options.help) {
146
+ printHelp();
147
+ process.exit(0);
148
+ }
149
+
150
+ runErrorbookRegistryHealthGateScript(options)
151
+ .then((result) => {
152
+ process.exitCode = result.exit_code;
153
+ })
154
+ .catch((error) => {
155
+ const payload = {
156
+ mode: 'errorbook-registry-health-gate',
157
+ passed: false,
158
+ error: error.message
159
+ };
160
+ if (options.json) {
161
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
162
+ } else {
163
+ process.stderr.write(`[errorbook-registry-health-gate] error=${error.message}\n`);
164
+ }
165
+ process.exitCode = 1;
166
+ });
167
+ }
168
+
169
+ module.exports = {
170
+ parseArgs,
171
+ runErrorbookRegistryHealthGateScript
172
+ };
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const { evaluateErrorbookReleaseGate } = require('../lib/commands/errorbook');
6
+
7
+ function parseArgs(argv = []) {
8
+ const options = {
9
+ minRisk: 'high',
10
+ minQuality: 70,
11
+ includeVerified: false,
12
+ failOnBlock: false,
13
+ json: false,
14
+ projectPath: process.cwd()
15
+ };
16
+
17
+ for (let index = 0; index < argv.length; index += 1) {
18
+ const token = argv[index];
19
+ const next = argv[index + 1];
20
+
21
+ if (token === '--min-risk' && next) {
22
+ options.minRisk = `${next}`.trim().toLowerCase();
23
+ index += 1;
24
+ } else if (token === '--min-quality' && next) {
25
+ const parsed = Number.parseInt(`${next}`, 10);
26
+ if (Number.isFinite(parsed)) {
27
+ options.minQuality = Math.max(0, Math.min(100, parsed));
28
+ }
29
+ index += 1;
30
+ } else if (token === '--include-verified') {
31
+ options.includeVerified = true;
32
+ } else if (token === '--fail-on-block') {
33
+ options.failOnBlock = true;
34
+ } else if (token === '--json') {
35
+ options.json = true;
36
+ } else if (token === '--project-path' && next) {
37
+ options.projectPath = path.resolve(next);
38
+ index += 1;
39
+ } else if (token === '--help' || token === '-h') {
40
+ options.help = true;
41
+ }
42
+ }
43
+
44
+ return options;
45
+ }
46
+
47
+ function printHelp() {
48
+ const lines = [
49
+ 'Usage: node scripts/errorbook-release-gate.js [options]',
50
+ '',
51
+ 'Options:',
52
+ ' --min-risk <level> Risk threshold to block release (low|medium|high, default: high)',
53
+ ' --min-quality <0-100> Minimum curation quality for unresolved entries (default: 70)',
54
+ ' --include-verified Also inspect verified (non-promoted) entries for risk threshold',
55
+ ' (temporary mitigation policy is always enforced for active entries)',
56
+ ' --fail-on-block Exit with code 2 when gate is blocked',
57
+ ' --project-path <path> Override project path (default: cwd)',
58
+ ' --json Print JSON payload',
59
+ ' -h, --help Show this help'
60
+ ];
61
+ process.stdout.write(`${lines.join('\n')}\n`);
62
+ }
63
+
64
+ async function runErrorbookReleaseGateScript(options = {}) {
65
+ const payload = await evaluateErrorbookReleaseGate({
66
+ minRisk: options.minRisk,
67
+ minQuality: options.minQuality,
68
+ includeVerified: options.includeVerified
69
+ }, {
70
+ projectPath: options.projectPath
71
+ });
72
+
73
+ if (options.json) {
74
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
75
+ } else if (payload.passed) {
76
+ process.stdout.write('[errorbook-release-gate] passed\n');
77
+ process.stdout.write(`[errorbook-release-gate] inspected=${payload.inspected_count} blocked=0\n`);
78
+ } else {
79
+ process.stdout.write('[errorbook-release-gate] blocked\n');
80
+ process.stdout.write(
81
+ `[errorbook-release-gate] inspected=${payload.inspected_count} blocked=${payload.blocked_count} min-risk=${payload.gate.min_risk} min-quality=${payload.gate.min_quality}\n`
82
+ );
83
+ process.stdout.write(
84
+ `[errorbook-release-gate] risk-blocked=${payload.risk_blocked_count || 0} curation-blocked=${payload.curation_blocked_count || 0} mitigation-blocked=${payload.mitigation_blocked_count || 0}\n`
85
+ );
86
+ payload.blocked_entries.slice(0, 20).forEach((item) => {
87
+ const policy = Array.isArray(item.policy_violations) && item.policy_violations.length > 0
88
+ ? ` policy=${item.policy_violations.join('|')}`
89
+ : '';
90
+ process.stdout.write(
91
+ `[errorbook-release-gate] entry=${item.id} risk=${item.risk} status=${item.status} quality=${item.quality_score}${policy}\n`
92
+ );
93
+ });
94
+ }
95
+
96
+ const exitCode = options.failOnBlock && !payload.passed ? 2 : 0;
97
+ return {
98
+ ...payload,
99
+ exit_code: exitCode
100
+ };
101
+ }
102
+
103
+ if (require.main === module) {
104
+ const options = parseArgs(process.argv.slice(2));
105
+ if (options.help) {
106
+ printHelp();
107
+ process.exit(0);
108
+ }
109
+
110
+ runErrorbookReleaseGateScript(options)
111
+ .then((result) => {
112
+ process.exitCode = result.exit_code;
113
+ })
114
+ .catch((error) => {
115
+ const payload = {
116
+ mode: 'errorbook-release-gate',
117
+ passed: false,
118
+ error: error.message
119
+ };
120
+ if (options.json) {
121
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
122
+ } else {
123
+ process.stderr.write(`[errorbook-release-gate] error=${error.message}\n`);
124
+ }
125
+ process.exitCode = 1;
126
+ });
127
+ }
128
+
129
+ module.exports = {
130
+ parseArgs,
131
+ runErrorbookReleaseGateScript
132
+ };