kld-sdd 2.7.8 → 2.8.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/README.md +26 -0
- package/USABILITY.md +72 -0
- package/bin/kld-sdd-init.js +718 -34
- package/lib/command-bridge.js +26 -17
- package/lib/deploy-codebuddy-hooks.js +19 -26
- package/lib/deploy-strategy.js +293 -0
- package/lib/hook-gate-core.js +7 -8
- package/lib/hook-install.js +75 -0
- package/lib/init.js +507 -726
- package/lib/maintenance.js +221 -0
- package/lib/managed-hook-hashes.json +24 -0
- package/lib/settings-merge.js +102 -25
- package/lib/skills-bundle.js +5 -5
- package/lib/spec-choice.js +85 -0
- package/lib/tool-profiles.js +2 -2
- package/lib/uninstall.js +453 -0
- package/package.json +7 -4
- package/skywalk-sdd/context-client.cjs +38 -7
- package/skywalk-sdd/index.cjs +153 -27
- package/skywalk-sdd/kb-upload.cjs +224 -82
- package/skywalk-sdd/lib/check-review.cjs +41 -0
- package/skywalk-sdd/lib/shared.cjs +62 -41
- package/skywalk-sdd/lib/test-execution.cjs +130 -0
- package/skywalk-sdd/lib/usage-reporter.cjs +17 -8
- package/skywalk-sdd/ontology/archive-package.cjs +141 -0
- package/skywalk-sdd/ontology/artifact-parser.cjs +24 -3
- package/skywalk-sdd/ontology/cli.cjs +53 -0
- package/skywalk-sdd/ontology/id.cjs +1 -1
- package/skywalk-sdd/ontology/identity-index.cjs +5 -1
- package/skywalk-sdd/ontology/resolve-spec-root.cjs +49 -0
- package/skywalk-sdd/ontology/schema.cjs +6 -0
- package/skywalk-sdd/ontology/sdd-config.cjs +33 -5
- package/skywalk-sdd/ontology/spec-detect.cjs +421 -0
- package/skywalk-sdd/ontology/spec-ontology.cjs +507 -0
- package/skywalk-sdd/ontology/workspace-layout.cjs +65 -17
- package/templates/git-hooks/commit-msg +17 -85
- package/templates/git-hooks/commit-msg-runner.cjs +44 -0
- package/templates/git-hooks/commit-msg-sdd-trailer.cjs +60 -69
- package/templates/git-hooks/consistency-check-core.cjs +92 -261
- package/templates/git-hooks/delivery-check.cjs +231 -0
- package/templates/git-hooks/delivery-snapshot.cjs +181 -0
- package/templates/git-hooks/hooks.config +8 -6
- package/templates/git-hooks/pre-commit +19 -5
- package/templates/git-hooks/pre-commit-consistency-check.cjs +13 -31
- package/templates/git-hooks/pre-push +19 -5
- package/templates/git-hooks/pre-push-consistency-check.cjs +36 -83
- package/templates/hooks/claude/hooks/sdd-post-tool.cjs +2 -2
- package/templates/hooks/codebuddy/hooks/hook-gate-core.cjs +7 -8
- package/templates/hooks/codebuddy/hooks/sdd-post-tool.cjs +2 -2
- package/templates/openspec/proposal.md +1 -1
- package/templates/openspec/spec.md +8 -0
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +28 -24
- package/templates/skills/kld-sdd/opsx-apply/checklist.md +12 -3
- package/templates/skills/kld-sdd/opsx-apply/reference.md +21 -1
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +20 -18
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +18 -13
- package/templates/skills/kld-sdd/opsx-check/reference.md +3 -1
- package/templates/skills/kld-sdd/opsx-check/review-template.json +22 -0
- package/templates/skills/kld-sdd/opsx-check/reviewer.md +27 -6
- package/templates/skills/kld-sdd/opsx-consistency-check/SKILL.md +34 -112
- package/templates/skills/kld-sdd/opsx-consistency-check/scripts/scripts.cjs +15 -14
- package/templates/skills/kld-sdd/opsx-kb-config/SKILL.md +82 -61
- package/templates/skills/kld-sdd/opsx-kb-config/reference.md +8 -11
- package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +3 -2
- package/templates/skills/kld-sdd/opsx-kb-ingest/reference.md +2 -2
- package/templates/skills/kld-sdd/opsx-ontology-ingest/SKILL.md +77 -0
- package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +5 -4
- package/templates/skills/kld-sdd/opsx-ontology-query/reference.md +14 -1
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +1 -1
- package/templates/skills/kld-sdd/opsx-propose/interaction-policy.md +9 -0
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +5 -3
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +2 -2
- package/index.js +0 -13
package/skywalk-sdd/index.cjs
CHANGED
|
@@ -1313,28 +1313,32 @@ function acquireEventWriteLock(dataDir, changeName, options = {}) {
|
|
|
1313
1313
|
created_at: nowISO(),
|
|
1314
1314
|
change: safeChangeName(changeName),
|
|
1315
1315
|
};
|
|
1316
|
-
|
|
1316
|
+
const candidatePath = `${lockPath}.${token}.tmp`;
|
|
1317
1317
|
try {
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1318
|
+
fs.writeFileSync(candidatePath, JSON.stringify(payload), { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
1319
|
+
// Publish a complete payload atomically. A contender must never observe an
|
|
1320
|
+
// empty lock and remove it while its owner is still initializing it.
|
|
1321
|
+
fs.linkSync(candidatePath, lockPath);
|
|
1321
1322
|
return { ...payload, path: lockPath };
|
|
1322
1323
|
} catch (error) {
|
|
1323
|
-
if (descriptor !== undefined) fs.closeSync(descriptor);
|
|
1324
1324
|
if (error?.code !== 'EEXIST') throw error;
|
|
1325
1325
|
let existing = null;
|
|
1326
1326
|
try {
|
|
1327
1327
|
existing = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
1328
1328
|
} catch {}
|
|
1329
1329
|
const createdAt = Date.parse(existing?.created_at || '');
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1330
|
+
let age = 0;
|
|
1331
|
+
try { age = Date.now() - fs.statSync(lockPath).mtimeMs; } catch {}
|
|
1332
|
+
const stale = existing && Number.isFinite(createdAt)
|
|
1333
|
+
? !isProcessAlive(Number(existing.pid))
|
|
1334
|
+
: age > staleMs;
|
|
1333
1335
|
if (stale) {
|
|
1334
1336
|
try { fs.rmSync(lockPath, { force: true }); } catch {}
|
|
1335
1337
|
continue;
|
|
1336
1338
|
}
|
|
1337
1339
|
Atomics.wait(EVENT_LOCK_WAIT_BUFFER, 0, 0, 10);
|
|
1340
|
+
} finally {
|
|
1341
|
+
try { fs.unlinkSync(candidatePath); } catch {}
|
|
1338
1342
|
}
|
|
1339
1343
|
}
|
|
1340
1344
|
const error = new Error(`EVENT_WRITE_LOCK_TIMEOUT: 等待 change "${changeName}" 的事件写锁超时`);
|
|
@@ -1539,7 +1543,15 @@ function readAllEvents(dataDir) {
|
|
|
1539
1543
|
/**
|
|
1540
1544
|
* 计算单个 change 的四维指标
|
|
1541
1545
|
*/
|
|
1546
|
+
function normalizeLifecycleEvents(events) {
|
|
1547
|
+
// Older clients stored run notifications as stage_end with a different event ID.
|
|
1548
|
+
// Preserve the event and its wire kind without counting it as another stage.
|
|
1549
|
+
return events.map(event => event.type === 'stage_end' && event.event_kind === 'run_ended'
|
|
1550
|
+
? { ...event, type: 'run_end' } : event);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1542
1553
|
function computeChangeMetrics(changeName, events) {
|
|
1554
|
+
events = normalizeLifecycleEvents(events);
|
|
1543
1555
|
const changeEvents = events.filter(e => e.change === changeName && !e.orphan);
|
|
1544
1556
|
if (changeEvents.length === 0) return null;
|
|
1545
1557
|
|
|
@@ -1868,6 +1880,7 @@ function createReworkBucket(command, capability) {
|
|
|
1868
1880
|
}
|
|
1869
1881
|
|
|
1870
1882
|
function summarizeStageExecutions(events) {
|
|
1883
|
+
events = normalizeLifecycleEvents(events);
|
|
1871
1884
|
const stageEvents = events.filter(e => !e.orphan && (e.type === 'stage_start' || e.type === 'stage_end'));
|
|
1872
1885
|
const starts = stageEvents
|
|
1873
1886
|
.filter(e => e.type === 'stage_start')
|
|
@@ -3329,6 +3342,7 @@ function computeTelemetryHealthMetrics(events, options = {}) {
|
|
|
3329
3342
|
}
|
|
3330
3343
|
|
|
3331
3344
|
function computePdfMvpMetrics(events, options = {}) {
|
|
3345
|
+
events = normalizeLifecycleEvents(events);
|
|
3332
3346
|
if (options.level === 'capability' || options.capability) {
|
|
3333
3347
|
return computeCapabilityMetrics(options.capability, events, options);
|
|
3334
3348
|
}
|
|
@@ -4856,6 +4870,7 @@ function tddPairFinding(pair) {
|
|
|
4856
4870
|
}
|
|
4857
4871
|
|
|
4858
4872
|
function buildReport(projectRoot, events, options = {}) {
|
|
4873
|
+
events = normalizeLifecycleEvents(events);
|
|
4859
4874
|
const scopedReport = scopeEventsForChangeReport(events, options.change);
|
|
4860
4875
|
events = scopedReport.events;
|
|
4861
4876
|
const archiveEvent = latestByTimestamp(events.filter(e => {
|
|
@@ -5208,6 +5223,7 @@ function filterEventsByDate(events, dateFrom, dateTo) {
|
|
|
5208
5223
|
}
|
|
5209
5224
|
|
|
5210
5225
|
function computeDoctorReport(events, options = {}) {
|
|
5226
|
+
events = normalizeLifecycleEvents(events);
|
|
5211
5227
|
const scopedEvents = options.change
|
|
5212
5228
|
? events.filter(e => e.change === options.change)
|
|
5213
5229
|
: events;
|
|
@@ -5418,12 +5434,12 @@ function cmdStart(args) {
|
|
|
5418
5434
|
|
|
5419
5435
|
const ONTOLOGY_AUTHORING_STAGES = new Set(['propose', 'spec', 'design', 'task', 'check']);
|
|
5420
5436
|
|
|
5421
|
-
function syncWorkingOntologyForStage(projectRoot, changeName, command, result) {
|
|
5437
|
+
function syncWorkingOntologyForStage(projectRoot, changeName, command, result, verifiedReconciliation = null) {
|
|
5422
5438
|
if (!ONTOLOGY_AUTHORING_STAGES.has(command) || result === 'failure') return null;
|
|
5423
5439
|
if (!changeName || changeName === 'general') return null;
|
|
5424
5440
|
const changeDir = getChangeDir(projectRoot, changeName);
|
|
5425
5441
|
if (!fs.existsSync(changeDir)) return null;
|
|
5426
|
-
const reconciled = require('./ontology/runtime.cjs').reconcileChange(
|
|
5442
|
+
const reconciled = verifiedReconciliation || require('./ontology/runtime.cjs').reconcileChange(
|
|
5427
5443
|
projectRoot,
|
|
5428
5444
|
changeName,
|
|
5429
5445
|
{
|
|
@@ -5592,7 +5608,8 @@ function cmdEnd(args, options = {}) {
|
|
|
5592
5608
|
|
|
5593
5609
|
let semanticState = null;
|
|
5594
5610
|
try {
|
|
5595
|
-
semanticState = syncWorkingOntologyForStage(projectRoot, change, command, result
|
|
5611
|
+
semanticState = syncWorkingOntologyForStage(projectRoot, change, command, result,
|
|
5612
|
+
command === 'check' ? options.verifiedReconciliation : null);
|
|
5596
5613
|
if (semanticState) {
|
|
5597
5614
|
details = {
|
|
5598
5615
|
...details,
|
|
@@ -5779,7 +5796,7 @@ function cmdEnd(args, options = {}) {
|
|
|
5779
5796
|
appendEvent(dataDir, event.change, cleanOptionalFields({
|
|
5780
5797
|
schema_version: SCHEMA_VERSION,
|
|
5781
5798
|
event_id: generateEventId(),
|
|
5782
|
-
type: '
|
|
5799
|
+
type: 'run_end',
|
|
5783
5800
|
event_kind: 'run_ended',
|
|
5784
5801
|
source: event.source,
|
|
5785
5802
|
command: 'archive',
|
|
@@ -6434,8 +6451,10 @@ function normalizeStrictTestResult(args, details) {
|
|
|
6434
6451
|
failure_type: failureType,
|
|
6435
6452
|
expected_failure: expectedFailure,
|
|
6436
6453
|
evidence_tier: 'strict',
|
|
6454
|
+
...(input.execution ? { execution: input.execution } : {}),
|
|
6437
6455
|
valid_red: tddPhase === 'red'
|
|
6438
6456
|
? expectedFailure && exitCode !== 0 && ['assertion', 'contract'].includes(failureType)
|
|
6457
|
+
&& (!input.execution || (input.counts_known && Number(input.failed) > 0))
|
|
6439
6458
|
: false,
|
|
6440
6459
|
};
|
|
6441
6460
|
if (input.counts_known) {
|
|
@@ -6453,6 +6472,7 @@ function isValidCompletionTestResults(testResults) {
|
|
|
6453
6472
|
if (!testResults || typeof testResults !== 'object') return false;
|
|
6454
6473
|
if (!['green', 'refactor', 'regression'].includes(testResults.tdd_phase)) return false;
|
|
6455
6474
|
if (testResults.exit_code !== 0 || testResults.failure_type !== 'none') return false;
|
|
6475
|
+
if (testResults.execution && (!testResults.counts_known || !(testResults.passed > 0))) return false;
|
|
6456
6476
|
if (testResults.counts_known === true && Number(testResults.failed) !== 0) return false;
|
|
6457
6477
|
return true;
|
|
6458
6478
|
}
|
|
@@ -7348,18 +7368,6 @@ function cmdRecord(args) {
|
|
|
7348
7368
|
try {
|
|
7349
7369
|
details = parseJsonOption(args, 'details-json', 'details-file', projectRoot, {});
|
|
7350
7370
|
detailsFilePath = args['details-file'] || args.details_file || null;
|
|
7351
|
-
// 默认清理 details-file,避免项目根目录被临时 JSON 污染;--keep-details 保留调试用
|
|
7352
|
-
if (detailsFilePath && !args['keep-details'] && !args.keep_details) {
|
|
7353
|
-
try {
|
|
7354
|
-
const fileToRemove = normalizeDetailsFilePath(detailsFilePath, projectRoot);
|
|
7355
|
-
if (fs.existsSync(fileToRemove)) {
|
|
7356
|
-
fs.unlinkSync(fileToRemove);
|
|
7357
|
-
}
|
|
7358
|
-
} catch (cleanupErr) {
|
|
7359
|
-
// 清理失败不阻塞主流程
|
|
7360
|
-
try { console.error(`[telemetry] details-file 清理失败(不阻塞): ${cleanupErr.message}`); } catch {}
|
|
7361
|
-
}
|
|
7362
|
-
}
|
|
7363
7371
|
} catch (err) {
|
|
7364
7372
|
fail(`details JSON 解析失败: ${err.message}`);
|
|
7365
7373
|
}
|
|
@@ -7467,6 +7475,9 @@ function cmdRecord(args) {
|
|
|
7467
7475
|
};
|
|
7468
7476
|
}
|
|
7469
7477
|
if (strict && type === 'test_result') {
|
|
7478
|
+
if (details?.test_results?.execution) {
|
|
7479
|
+
require('./lib/test-execution.cjs').verifyCapture(details.test_results, projectRoot);
|
|
7480
|
+
}
|
|
7470
7481
|
const testResults = normalizeStrictTestResult(args, details);
|
|
7471
7482
|
strictRunId = testResults.run_id;
|
|
7472
7483
|
strictTaskId = testResults.task_id;
|
|
@@ -7560,6 +7571,7 @@ function cmdRecord(args) {
|
|
|
7560
7571
|
const taskId = strictTaskId || details?.task_id || null;
|
|
7561
7572
|
if (taskId) runCheckTaskSync(projectRoot, change, taskId);
|
|
7562
7573
|
} catch (taskErr) {
|
|
7574
|
+
if (args['require-projection']) throw taskErr;
|
|
7563
7575
|
try { console.error(`[telemetry] tasks.md 主任务状态自动同步失败(不阻塞): ${taskErr.message}`); } catch {}
|
|
7564
7576
|
}
|
|
7565
7577
|
};
|
|
@@ -7636,6 +7648,89 @@ function cmdRecord(args) {
|
|
|
7636
7648
|
} catch (error) {
|
|
7637
7649
|
fail(error.message || String(error));
|
|
7638
7650
|
}
|
|
7651
|
+
// Keep retry input until validation, persistence and task projection all succeed.
|
|
7652
|
+
if (detailsFilePath && !args['keep-details'] && !args.keep_details) {
|
|
7653
|
+
try { fs.unlinkSync(normalizeDetailsFilePath(detailsFilePath, projectRoot)); }
|
|
7654
|
+
catch (error) {
|
|
7655
|
+
if (error.code !== 'ENOENT') console.error(`[telemetry] details-file 清理失败(不阻塞): ${error.message}`);
|
|
7656
|
+
}
|
|
7657
|
+
}
|
|
7658
|
+
if (!args.quiet) console.log(JSON.stringify(output, null, 2));
|
|
7659
|
+
return output;
|
|
7660
|
+
}
|
|
7661
|
+
|
|
7662
|
+
function cmdTestRun(args) {
|
|
7663
|
+
const projectRoot = normalizeProjectRoot(args.project || process.cwd());
|
|
7664
|
+
requireValidChangeName(args.change);
|
|
7665
|
+
requireStrictString(args['session-id'], 'TEST_SESSION_REQUIRED', 'session-id');
|
|
7666
|
+
const completion = args['completion-file']
|
|
7667
|
+
? JSON.parse(fs.readFileSync(path.resolve(args['completion-file']), 'utf8')) : [];
|
|
7668
|
+
const declared = String(args['task-ids'] || args['task-id'] || '').split(',').map(x => x.trim());
|
|
7669
|
+
if (!Array.isArray(completion) || completion.some(item => !declared.includes(item.task_id)
|
|
7670
|
+
|| typeof item.tdd_required !== 'boolean'
|
|
7671
|
+
|| (!Array.isArray(item.files) && !item.no_file_change_reason))) {
|
|
7672
|
+
throw new Error('TEST_COMPLETION_INVALID: 完成信息须关联本轮明确任务,并包含实际文件及 tdd_required');
|
|
7673
|
+
}
|
|
7674
|
+
for (const item of completion) {
|
|
7675
|
+
normalizeRepositoryRelativeFiles(item.files || []);
|
|
7676
|
+
runCheckTaskSync(projectRoot, args.change, item.task_id, true);
|
|
7677
|
+
}
|
|
7678
|
+
const captured = require('./lib/test-execution.cjs').captureTest(args, projectRoot, args.change);
|
|
7679
|
+
const record = cmdRecord({ ...args, quiet: true, strict: true, type: 'test_result',
|
|
7680
|
+
command: 'test', project: projectRoot, change: args.change, 'run-id': captured.runId,
|
|
7681
|
+
source: args.source || 'test-run', summary: args.summary || `执行测试:${captured.testResults.command}`,
|
|
7682
|
+
result: captured.testResults.result, details: undefined,
|
|
7683
|
+
'details-file': captured.receipt, 'keep-details': true,
|
|
7684
|
+
});
|
|
7685
|
+
const completed = [];
|
|
7686
|
+
if (isValidCompletionTestResults(captured.testResults)) {
|
|
7687
|
+
for (const item of completion) {
|
|
7688
|
+
const updated = cmdRecord({ ...args, quiet: true, strict: true, type: 'task_update', command: 'apply',
|
|
7689
|
+
'require-projection': true,
|
|
7690
|
+
project: projectRoot, 'run-id': `${captured.runId}:${item.task_id}`, 'task-id': item.task_id,
|
|
7691
|
+
status: 'completed', result: 'success', source: args.source || 'test-run', summary: `${item.task_id} 通过本轮测试`,
|
|
7692
|
+
'details-json': JSON.stringify({ task_update: { ...item, test_event_id: record.event_id } }),
|
|
7693
|
+
});
|
|
7694
|
+
completed.push({ task_id: item.task_id, event_id: updated.event_id, test_event_id: record.event_id });
|
|
7695
|
+
}
|
|
7696
|
+
}
|
|
7697
|
+
const output = { ...record, test_results: captured.testResults, receipt: captured.receipt, completed_tasks: completed };
|
|
7698
|
+
console.log(JSON.stringify(output, null, 2));
|
|
7699
|
+
process.exitCode = captured.testResults.exit_code;
|
|
7700
|
+
return output;
|
|
7701
|
+
}
|
|
7702
|
+
|
|
7703
|
+
function cmdCheckRecord(args) {
|
|
7704
|
+
const projectRoot = normalizeProjectRoot(args.project || process.cwd());
|
|
7705
|
+
requireValidChangeName(args.change);
|
|
7706
|
+
const preflight = JSON.parse(fs.readFileSync(path.resolve(args['preflight-file']), 'utf8'));
|
|
7707
|
+
const review = JSON.parse(fs.readFileSync(path.resolve(args['review-file']), 'utf8'));
|
|
7708
|
+
const state = require('./ontology/runtime.cjs').reconcileChange(projectRoot, args.change, { profile: preflight.profile || 'auto', markPending: true });
|
|
7709
|
+
const { splitCheckDiagnostics } = require('./ontology/spec-ontology.cjs');
|
|
7710
|
+
const preflightBlocked = preflight.product_valid === false
|
|
7711
|
+
|| (preflight.product_valid == null && !splitCheckDiagnostics(preflight.diagnostics).product_valid);
|
|
7712
|
+
const stateBlocked = !splitCheckDiagnostics(state.diagnostics).product_valid;
|
|
7713
|
+
if (preflightBlocked || stateBlocked || preflight.revision !== state.revision
|
|
7714
|
+
|| preflight.review_inputs?.identity_history_hash !== state.state.identity_history_hash
|
|
7715
|
+
|| JSON.stringify(preflight.review_inputs?.files) !== JSON.stringify(state.state.files)) {
|
|
7716
|
+
throw new Error('CHECK_REVIEW_STALE: 文档或历史身份已变化,重新预检并评审受影响内容');
|
|
7717
|
+
}
|
|
7718
|
+
const events = readEvents(getDataDir(projectRoot), args.change);
|
|
7719
|
+
const beforeApply = !events.some(e => (e.type === 'stage_start' && ['apply', 'test', 'archive'].includes(e.command)) || ['test_result', 'task_update', 'build_result'].includes(e.type));
|
|
7720
|
+
const previous = events.filter(e => e.type === 'check_result').slice(-1)[0]?.details?.check_results;
|
|
7721
|
+
const assembled = require('./lib/check-review.cjs').assembleReview(review, scanTaskCompletion(projectRoot, args.change), {
|
|
7722
|
+
beforeApply, previousBeforeApply: previous?.fixed_before_apply, revision: preflight.revision,
|
|
7723
|
+
});
|
|
7724
|
+
const record = cmdRecord({ ...args, quiet: true, strict: true, type: 'check_result', command: 'check',
|
|
7725
|
+
project: projectRoot, result: assembled.result, 'run-id': args['run-id'] || generateEventId(),
|
|
7726
|
+
source: args.source || 'opsx-command', summary: args.summary || '按实际评审项生成 Check 结果',
|
|
7727
|
+
'session-id': args['session-id'] || review.reviewer?.parent_session_id || review.reviewer?.review_session_id,
|
|
7728
|
+
'details-json': JSON.stringify(assembled.details),
|
|
7729
|
+
});
|
|
7730
|
+
const ended = args['end-event-id'] ? cmdEnd({ ...args, 'event-id': args['end-event-id'], command: 'check',
|
|
7731
|
+
project: projectRoot, result: assembled.result, summary: args.summary || 'Check 已记录,复核文档状态',
|
|
7732
|
+
}, { silent: true, verifiedReconciliation: state }) : null;
|
|
7733
|
+
const output = { ...record, result: assembled.result, check_results: assembled.details.check_results, stage_end: ended };
|
|
7639
7734
|
console.log(JSON.stringify(output, null, 2));
|
|
7640
7735
|
return output;
|
|
7641
7736
|
}
|
|
@@ -8148,16 +8243,20 @@ function cmdSemanticIdentity(args) {
|
|
|
8148
8243
|
}
|
|
8149
8244
|
|
|
8150
8245
|
function semanticResultPayload(result) {
|
|
8246
|
+
const split = require('./ontology/spec-ontology.cjs').splitCheckDiagnostics(result.diagnostics);
|
|
8151
8247
|
return {
|
|
8152
8248
|
change: result.state.change,
|
|
8153
8249
|
profile: result.profile,
|
|
8154
8250
|
valid: result.valid,
|
|
8251
|
+
product_valid: split.product_valid,
|
|
8155
8252
|
review_status: result.state.review_status,
|
|
8156
8253
|
revision: result.revision,
|
|
8157
8254
|
changed: Boolean(result.changed),
|
|
8158
8255
|
counts: result.counts,
|
|
8159
8256
|
paths: result.paths,
|
|
8160
8257
|
diagnostics: result.diagnostics,
|
|
8258
|
+
tool_diagnostics: split.tool_diagnostics,
|
|
8259
|
+
product_diagnostics: split.product_diagnostics,
|
|
8161
8260
|
};
|
|
8162
8261
|
}
|
|
8163
8262
|
|
|
@@ -8199,13 +8298,32 @@ function cmdSemanticReconcile(args, options = {}) {
|
|
|
8199
8298
|
total: Number(finished - started) / 1e6,
|
|
8200
8299
|
};
|
|
8201
8300
|
}
|
|
8301
|
+
if (options.productGate && payload.tool_diagnostics.length > 0) {
|
|
8302
|
+
try {
|
|
8303
|
+
const repair = require('./ontology/spec-ontology.cjs').materializeChangeSpecOntology(
|
|
8304
|
+
context.projectRoot,
|
|
8305
|
+
context.changeName,
|
|
8306
|
+
);
|
|
8307
|
+
payload.tool_repair = {
|
|
8308
|
+
repaired: repair.validation.valid,
|
|
8309
|
+
output_path: repair.output_path,
|
|
8310
|
+
mapping_path: repair.mapping_path,
|
|
8311
|
+
};
|
|
8312
|
+
} catch (error) {
|
|
8313
|
+
payload.tool_repair = { repaired: false };
|
|
8314
|
+
}
|
|
8315
|
+
}
|
|
8202
8316
|
console.log(JSON.stringify(payload, null, 2));
|
|
8203
|
-
if (
|
|
8317
|
+
if (options.productGate) {
|
|
8318
|
+
if (!payload.product_valid) process.exitCode = 1;
|
|
8319
|
+
} else if (!result.valid) {
|
|
8320
|
+
process.exitCode = 1;
|
|
8321
|
+
}
|
|
8204
8322
|
return result;
|
|
8205
8323
|
}
|
|
8206
8324
|
|
|
8207
8325
|
function cmdSemanticCheck(args) {
|
|
8208
|
-
return cmdSemanticReconcile(args, { markPending: true, includeTasks: Boolean(args['include-tasks']) });
|
|
8326
|
+
return cmdSemanticReconcile(args, { markPending: true, includeTasks: Boolean(args['include-tasks']), productGate: true });
|
|
8209
8327
|
}
|
|
8210
8328
|
|
|
8211
8329
|
function cmdSemanticStatus(args) {
|
|
@@ -8424,6 +8542,12 @@ function main() {
|
|
|
8424
8542
|
case 'record':
|
|
8425
8543
|
cmdRecord(flags);
|
|
8426
8544
|
break;
|
|
8545
|
+
case 'test-run':
|
|
8546
|
+
cmdTestRun(flags);
|
|
8547
|
+
break;
|
|
8548
|
+
case 'check-record':
|
|
8549
|
+
cmdCheckRecord(flags);
|
|
8550
|
+
break;
|
|
8427
8551
|
case 'metrics':
|
|
8428
8552
|
cmdMetrics(flags);
|
|
8429
8553
|
break;
|
|
@@ -8504,6 +8628,8 @@ SDD Telemetry CLI - 流程度量采集工具
|
|
|
8504
8628
|
start 记录 SDD 阶段开始,返回 event_id
|
|
8505
8629
|
end 记录 SDD 阶段结束,关联 event_id
|
|
8506
8630
|
record 记录 task_update/check_result/test_result 等结构化事件
|
|
8631
|
+
test-run 执行 --command-json 参数数组,采集退出码、测试计数、原始输出与任务关联
|
|
8632
|
+
check-record 从 --review-file 与 --preflight-file 计算并记录 Check 结果
|
|
8507
8633
|
metrics 查询度量指标(四维分析)
|
|
8508
8634
|
report 生成只读度量报告(不写入事件)
|
|
8509
8635
|
doctor 诊断 Telemetry 数据质量
|
|
@@ -8511,7 +8637,7 @@ SDD Telemetry CLI - 流程度量采集工具
|
|
|
8511
8637
|
check-task 扫描变更目录 tasks.md 并勾选指定 task_id
|
|
8512
8638
|
archive-docs 将 Simple/Full spec 变更真实移动到 openspec/changes/archive/,并可结束 archive 阶段生成报告
|
|
8513
8639
|
kb-upload 知识库上传(文件夹自动打包 / zip 直传),支持随时入库
|
|
8514
|
-
semantic-identity 生成或复用实体/版本 ID
|
|
8640
|
+
semantic-identity 生成或复用实体/版本 ID(完整 UUID,界面可显示短标识);无需网络和中央 ID 服务
|
|
8515
8641
|
semantic-scan 只读解析并校验当前 Change
|
|
8516
8642
|
semantic-reconcile 全量对账文件与本地工作态本体实例
|
|
8517
8643
|
semantic-check 全量对账并在通过时标记 pending;--include-tasks 同时返回任务状态、评审输入清单和程序耗时
|