@hongmaple0820/scale-engine 0.17.0 → 0.18.0

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/dist/api/cli.js CHANGED
@@ -30,7 +30,7 @@ import { EvidenceStore } from '../workflow/EvidenceStore.js';
30
30
  import { OutOfScopeStore } from '../workflow/OutOfScopeStore.js';
31
31
  import { ReviewStore } from '../workflow/ReviewStore.js';
32
32
  import { WorkflowEngine } from '../workflow/WorkflowEngine.js';
33
- import { resolveVerificationTargets } from '../workflow/VerificationProfile.js';
33
+ import { resolveVerificationTargets, } from '../workflow/VerificationProfile.js';
34
34
  import { writeGovernanceTemplates } from '../workflow/GovernanceTemplates.js';
35
35
  import { computeGovernanceDrift } from '../workflow/GovernanceLock.js';
36
36
  import { baselineEngineeringStandards, doctorEngineeringStandards, scanEngineeringStandards, settleEngineeringStandards, } from '../workflow/EngineeringStandards.js';
@@ -47,11 +47,13 @@ import { evaluateToolEvidenceGate } from '../tools/ToolEvidenceGate.js';
47
47
  import { ToolEvidenceStore } from '../tools/ToolEvidenceStore.js';
48
48
  import { ToolOrchestrator } from '../tools/ToolOrchestrator.js';
49
49
  import { loadToolPolicy, toolPolicyTemplate } from '../tools/ToolPolicy.js';
50
+ import { doctorHtmlArtifacts, renderHtmlArtifact, resolveHtmlArtifactForOpen, settleHtmlArtifacts, } from '../output/HTMLArtifactLayer.js';
50
51
  import { cleanupWorkspaceLifecycle, inspectWorkspaceLifecycle, } from '../workflow/WorkspaceLifecycle.js';
51
52
  import { resolveWorkspaceTopology, workspaceTopologyPath, workspaceTopologyTemplate, } from '../workflow/WorkspaceTopology.js';
52
53
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
53
54
  import { join, resolve } from 'node:path';
54
55
  import { execFileSync } from 'node:child_process';
56
+ import { pathToFileURL } from 'node:url';
55
57
  import { SCALE_ENGINE_VERSION } from '../version.js';
56
58
  // ============================================================================
57
59
  // Engine bootstrap (单例 + lazy init)
@@ -96,6 +98,26 @@ function gatesForPreflightProfile(profile) {
96
98
  return ['G3', 'G0', 'G4', 'G5'];
97
99
  return ['G3', 'G0', 'G4', 'G5', 'G6', 'G7'];
98
100
  }
101
+ function shouldSkipPreflightCommandTargets(resolved, args) {
102
+ if (!resolved.matrix)
103
+ return false;
104
+ const requestedService = String(args.service ?? '').trim();
105
+ if (requestedService && requestedService !== 'all')
106
+ return false;
107
+ const hasCommandOverrides = [
108
+ args['build-cmd'],
109
+ args['lint-cmd'],
110
+ args['test-cmd'],
111
+ args['coverage-cmd'],
112
+ ].some(value => typeof value === 'string' && value.trim().length > 0);
113
+ if (hasCommandOverrides)
114
+ return false;
115
+ const profile = resolved.matrix.profiles?.[resolved.profileName];
116
+ const hasProfileCommands = Object.values(profile?.commands ?? {})
117
+ .some(value => typeof value === 'string' && value.trim().length > 0);
118
+ const hasServices = (resolved.matrix.services ?? []).length > 0;
119
+ return !hasServices && !hasProfileCommands;
120
+ }
99
121
  function evaluateEngineeringStandardsGate(options) {
100
122
  const mode = normalizeEngineeringStandardsGateMode(options.policy.engineeringStandardsGate);
101
123
  if (mode === 'off') {
@@ -1378,6 +1400,10 @@ const preflight = defineCommand({
1378
1400
  profile: args.profile,
1379
1401
  service: args.service,
1380
1402
  });
1403
+ const commandTargetsSkipped = shouldSkipPreflightCommandTargets(resolved, args);
1404
+ if (commandTargetsSkipped) {
1405
+ resolved.warnings.push('No verification services or profile commands configured; command gates skipped for this governance-only project.');
1406
+ }
1381
1407
  const engineeringStandards = evaluateEngineeringStandardsGate({
1382
1408
  policy: resolved.policy,
1383
1409
  });
@@ -1397,7 +1423,7 @@ const preflight = defineCommand({
1397
1423
  console.log(' Engineering standards: skipped');
1398
1424
  }
1399
1425
  }
1400
- for (const target of resolved.targets) {
1426
+ for (const target of commandTargetsSkipped ? [] : resolved.targets) {
1401
1427
  if (!args.json) {
1402
1428
  const label = target.service ? `${target.service.name} (${target.service.path})` : 'root';
1403
1429
  console.log(`\n Target: ${label}`);
@@ -1427,8 +1453,7 @@ const preflight = defineCommand({
1427
1453
  }
1428
1454
  }
1429
1455
  }
1430
- const passed = targetResults.length > 0 &&
1431
- targetResults.every(target => target.passed) &&
1456
+ const passed = (targetResults.length === 0 || targetResults.every(target => target.passed)) &&
1432
1457
  !engineeringStandards.blocked;
1433
1458
  const result = {
1434
1459
  phase: 'PREFLIGHT',
@@ -1439,6 +1464,7 @@ const preflight = defineCommand({
1439
1464
  policy: resolved.policy,
1440
1465
  engineeringStandards,
1441
1466
  targets: targetResults,
1467
+ commandTargetsSkipped,
1442
1468
  passed,
1443
1469
  };
1444
1470
  if (args.json) {
@@ -1689,7 +1715,7 @@ const init = defineCommand({
1689
1715
  const detection = qsResult.success ? undefined : detectPlatform(args.dir);
1690
1716
  console.log(JSON.stringify({
1691
1717
  ok: qsResult.success,
1692
- mode: 'quick',
1718
+ mode: qsResult.success && !qsResult.platform ? 'governance-only' : 'quick',
1693
1719
  platform: qsResult.platform,
1694
1720
  created: qsResult.created,
1695
1721
  skipped: qsResult.skipped,
@@ -1701,8 +1727,11 @@ const init = defineCommand({
1701
1727
  }, null, 2));
1702
1728
  return;
1703
1729
  }
1704
- if (qsResult.success && qsResult.platform) {
1705
- console.log(`\n✅ SCALE Engine Quick Start completed for ${qsResult.platform}`);
1730
+ if (qsResult.success) {
1731
+ if (!qsResult.platform)
1732
+ console.log(`\nSCALE governance templates initialized`);
1733
+ else
1734
+ console.log(`\n✅ SCALE Engine Quick Start completed for ${qsResult.platform}`);
1706
1735
  console.log(`\n📁 Created (${qsResult.created.length}):`);
1707
1736
  for (const f of qsResult.created)
1708
1737
  console.log(` + ${f}`);
@@ -2056,6 +2085,182 @@ const standards = defineCommand({
2056
2085
  subCommands: { scan: standardsScan, doctor: standardsDoctor, settle: standardsSettle, baseline: standardsBaseline },
2057
2086
  });
2058
2087
  // ============================================================================
2088
+ // artifact command - Derived HTML artifacts for human review
2089
+ // ============================================================================
2090
+ const artifactRender = defineCommand({
2091
+ meta: { name: 'render', description: 'Render a task Markdown source set into a governed HTML artifact' },
2092
+ args: {
2093
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2094
+ 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2095
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2096
+ type: { type: 'string', default: 'release-report', description: 'HTML artifact type' },
2097
+ source: { type: 'string', description: 'Comma or newline separated source Markdown files relative to the task directory' },
2098
+ theme: { type: 'string', default: 'auto', description: 'Theme mode: dark/light/auto' },
2099
+ lang: { type: 'string', default: 'zh', description: 'HTML language: zh/en' },
2100
+ title: { type: 'string', description: 'HTML document title override' },
2101
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2102
+ },
2103
+ run({ args }) {
2104
+ const result = renderHtmlArtifact({
2105
+ projectDir: args.dir,
2106
+ taskId: args['task-id'],
2107
+ artifactDir: args['artifact-dir'],
2108
+ type: String(args.type ?? 'release-report'),
2109
+ sourcePaths: splitChangedFiles(typeof args.source === 'string' ? args.source : undefined),
2110
+ theme: normalizeThemeArg(args.theme),
2111
+ lang: normalizeLangArg(args.lang),
2112
+ title: typeof args.title === 'string' ? args.title : undefined,
2113
+ });
2114
+ if (args.json) {
2115
+ console.log(JSON.stringify(result, null, 2));
2116
+ return;
2117
+ }
2118
+ console.log('SCALE HTML Artifact Render');
2119
+ console.log(` Type: ${result.type}`);
2120
+ console.log(` HTML: ${result.outputPath}`);
2121
+ console.log(` Index: ${result.indexPath}`);
2122
+ console.log(` Manifest: ${result.manifestPath}`);
2123
+ if (result.missingSources.length > 0) {
2124
+ console.log(` Missing sources: ${result.missingSources.join(', ')}`);
2125
+ }
2126
+ },
2127
+ });
2128
+ const artifactDoctor = defineCommand({
2129
+ meta: { name: 'doctor', description: 'Check HTML artifacts for traceability, stale sources, remote assets, and secret-like content' },
2130
+ args: {
2131
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2132
+ 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2133
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2134
+ type: { type: 'string', description: 'Optional HTML artifact type to check' },
2135
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2136
+ },
2137
+ run({ args }) {
2138
+ const report = doctorHtmlArtifacts({
2139
+ projectDir: args.dir,
2140
+ taskId: args['task-id'],
2141
+ artifactDir: args['artifact-dir'],
2142
+ type: typeof args.type === 'string' ? args.type : undefined,
2143
+ });
2144
+ if (args.json) {
2145
+ console.log(JSON.stringify(report, null, 2));
2146
+ if (!report.ok)
2147
+ process.exitCode = 1;
2148
+ return;
2149
+ }
2150
+ console.log(`SCALE HTML Artifact Doctor: ${report.ok ? 'OK' : 'FAILED'}`);
2151
+ console.log(` Manifest: ${report.manifestPath}`);
2152
+ console.log(` Artifacts: ${report.artifacts.length}`);
2153
+ if (report.findings.length === 0) {
2154
+ console.log(' No HTML artifact findings.');
2155
+ }
2156
+ else {
2157
+ for (const finding of report.findings) {
2158
+ const path = finding.path ? ` ${finding.path}` : '';
2159
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
2160
+ if (finding.fix)
2161
+ console.log(` fix: ${finding.fix}`);
2162
+ }
2163
+ }
2164
+ if (!report.ok)
2165
+ process.exitCode = 1;
2166
+ },
2167
+ });
2168
+ const artifactSettle = defineCommand({
2169
+ meta: { name: 'settle', description: 'Record HTML artifact settlement evidence for a task' },
2170
+ args: {
2171
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2172
+ 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2173
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2174
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2175
+ },
2176
+ run({ args }) {
2177
+ const report = settleHtmlArtifacts({
2178
+ projectDir: args.dir,
2179
+ taskId: args['task-id'],
2180
+ artifactDir: args['artifact-dir'],
2181
+ });
2182
+ if (args.json) {
2183
+ console.log(JSON.stringify(report, null, 2));
2184
+ if (!report.ok)
2185
+ process.exitCode = 1;
2186
+ return;
2187
+ }
2188
+ console.log(`SCALE HTML Artifact Settlement: ${report.ok ? 'OK' : 'FAILED'}`);
2189
+ console.log(` HTML impact: ${report.htmlImpactPath}`);
2190
+ for (const finding of report.doctor.findings) {
2191
+ const path = finding.path ? ` ${finding.path}` : '';
2192
+ console.log(` [${finding.severity.toUpperCase()}] ${finding.code}${path}: ${finding.message}`);
2193
+ }
2194
+ if (!report.ok)
2195
+ process.exitCode = 1;
2196
+ },
2197
+ });
2198
+ const artifactOpen = defineCommand({
2199
+ meta: { name: 'open', description: 'Open or print the local file URL for a rendered HTML artifact' },
2200
+ args: {
2201
+ dir: { type: 'string', default: '.', description: 'Project directory' },
2202
+ 'task-id': { type: 'string', description: 'Task id under docs/worklog/tasks' },
2203
+ 'artifact-dir': { type: 'string', description: 'Task artifact directory override' },
2204
+ type: { type: 'string', description: 'Optional HTML artifact type to open' },
2205
+ 'print-only': { type: 'boolean', default: false, description: 'Only print the file URL without launching a browser' },
2206
+ json: { type: 'boolean', default: false, description: 'Print JSON output' },
2207
+ },
2208
+ run({ args }) {
2209
+ const path = resolveHtmlArtifactForOpen({
2210
+ projectDir: args.dir,
2211
+ taskId: args['task-id'],
2212
+ artifactDir: args['artifact-dir'],
2213
+ type: typeof args.type === 'string' ? args.type : undefined,
2214
+ });
2215
+ const url = pathToFileURL(path).toString();
2216
+ const exists = existsSync(path);
2217
+ if (!args['print-only'] && exists)
2218
+ launchLocalFile(path);
2219
+ const output = { ok: exists, path, url, launched: Boolean(!args['print-only'] && exists) };
2220
+ if (args.json) {
2221
+ console.log(JSON.stringify(output, null, 2));
2222
+ if (!exists)
2223
+ process.exitCode = 1;
2224
+ return;
2225
+ }
2226
+ if (!exists) {
2227
+ console.log(`HTML artifact not found: ${path}`);
2228
+ process.exitCode = 1;
2229
+ return;
2230
+ }
2231
+ console.log(url);
2232
+ },
2233
+ });
2234
+ const artifact = defineCommand({
2235
+ meta: { name: 'artifact', description: 'Derived HTML artifact rendering and safety checks' },
2236
+ subCommands: { render: artifactRender, doctor: artifactDoctor, settle: artifactSettle, open: artifactOpen },
2237
+ });
2238
+ function normalizeThemeArg(value) {
2239
+ const normalized = String(value ?? 'auto').trim().toLowerCase();
2240
+ if (normalized === 'dark' || normalized === 'light' || normalized === 'auto')
2241
+ return normalized;
2242
+ return 'auto';
2243
+ }
2244
+ function normalizeLangArg(value) {
2245
+ return String(value ?? 'zh').trim().toLowerCase() === 'en' ? 'en' : 'zh';
2246
+ }
2247
+ function launchLocalFile(path) {
2248
+ try {
2249
+ if (process.platform === 'win32') {
2250
+ execFileSync('cmd', ['/c', 'start', '', path], { stdio: 'ignore' });
2251
+ }
2252
+ else if (process.platform === 'darwin') {
2253
+ execFileSync('open', [path], { stdio: 'ignore' });
2254
+ }
2255
+ else {
2256
+ execFileSync('xdg-open', [path], { stdio: 'ignore' });
2257
+ }
2258
+ }
2259
+ catch {
2260
+ // Opening is convenience-only; artifact doctor/render remains the source of truth.
2261
+ }
2262
+ }
2263
+ // ============================================================================
2059
2264
  // evolve command
2060
2265
  // ============================================================================
2061
2266
  const evolve = defineCommand({
@@ -2453,6 +2658,7 @@ const skillCheckCommand = defineCommand({
2453
2658
  artifactsDir: args.dir ?? state?.artifactsDir,
2454
2659
  level,
2455
2660
  requiredArtifacts: state?.requiredSkillArtifacts,
2661
+ requiredSkills: state?.requiredSkills,
2456
2662
  mode: state?.skillRoutingMode ?? policy.policy.mode,
2457
2663
  enforceLevels: policy.policy.enforceLevels,
2458
2664
  });
@@ -3005,6 +3211,7 @@ const main = defineCommand({
3005
3211
  stats,
3006
3212
  preflight,
3007
3213
  governance,
3214
+ artifact,
3008
3215
  assets,
3009
3216
  standards,
3010
3217
  metrics,