kld-sdd 2.5.1 → 2.6.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 +118 -8
- package/kld-sdd-guide.html +1 -1
- package/lib/init.js +24 -5
- package/lib/tool-profiles.js +1 -1
- package/package.json +4 -2
- package/skywalk-sdd/context-client.cjs +160 -0
- package/skywalk-sdd/index.cjs +445 -36
- package/skywalk-sdd/ontology/archive-package.cjs +489 -0
- package/skywalk-sdd/ontology/artifact-observer.cjs +91 -0
- package/skywalk-sdd/ontology/artifact-parser.cjs +621 -0
- package/skywalk-sdd/ontology/change-lock.cjs +126 -0
- package/skywalk-sdd/ontology/cli.cjs +146 -0
- package/skywalk-sdd/ontology/effective-graph.cjs +158 -0
- package/skywalk-sdd/ontology/id.cjs +126 -0
- package/skywalk-sdd/ontology/identity-index.cjs +287 -0
- package/skywalk-sdd/ontology/normalizer.cjs +107 -0
- package/skywalk-sdd/ontology/runtime.cjs +466 -0
- package/skywalk-sdd/ontology/schema.cjs +139 -0
- package/skywalk-sdd/ontology/structural-identity.cjs +77 -0
- package/skywalk-sdd/ontology/traceability-validator.cjs +610 -0
- package/skywalk-sdd/ontology/working-artifacts.cjs +243 -0
- package/templates/openspec/design.md +18 -0
- package/templates/openspec/proposal.md +19 -6
- package/templates/openspec/spec.md +62 -8
- package/templates/openspec/tasks.md +28 -6
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +19 -1
- package/templates/skills/kld-sdd/opsx-archive/checklist.md +5 -1
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +18 -0
- package/templates/skills/kld-sdd/opsx-check/checklist.md +2 -0
- package/templates/skills/kld-sdd/opsx-design/SKILL.md +11 -0
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +12 -0
- package/templates/skills/kld-sdd/opsx-propose/checklist.md +2 -0
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +34 -0
- package/templates/skills/kld-sdd/opsx-spec/checklist.md +5 -0
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +11 -0
package/skywalk-sdd/index.cjs
CHANGED
|
@@ -520,31 +520,119 @@ function syncArchivedSpecs(projectRoot, archiveDir, changeName) {
|
|
|
520
520
|
return copiedSpecs;
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
+
function prepareArchivedSpecSync(projectRoot, archiveDir, changeName, token) {
|
|
524
|
+
const operations = discoverFullSpecFiles(archiveDir, changeName).map((spec) => {
|
|
525
|
+
const target = path.join(projectRoot, 'openspec', 'specs', spec.capability, 'spec.md');
|
|
526
|
+
ensureDir(path.dirname(target));
|
|
527
|
+
const temp = `${target}.${token}.archive-tmp`;
|
|
528
|
+
const backup = `${target}.${token}.archive-bak`;
|
|
529
|
+
fs.copyFileSync(spec.source, temp);
|
|
530
|
+
if (fs.existsSync(target)) fs.copyFileSync(target, backup);
|
|
531
|
+
return {
|
|
532
|
+
capability: spec.capability,
|
|
533
|
+
source: spec.source,
|
|
534
|
+
target,
|
|
535
|
+
temp,
|
|
536
|
+
backup,
|
|
537
|
+
had_original: fs.existsSync(target),
|
|
538
|
+
committed: false,
|
|
539
|
+
};
|
|
540
|
+
});
|
|
541
|
+
return {
|
|
542
|
+
operations,
|
|
543
|
+
copiedSpecs: operations.map((operation) => ({
|
|
544
|
+
capability: operation.capability,
|
|
545
|
+
source: toProjectRelative(projectRoot, operation.source),
|
|
546
|
+
target: toProjectRelative(projectRoot, operation.target),
|
|
547
|
+
})),
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function commitArchivedSpecSync(transaction, failAt = '') {
|
|
552
|
+
for (const operation of transaction.operations) {
|
|
553
|
+
if (fs.existsSync(operation.target)) fs.rmSync(operation.target, { force: true });
|
|
554
|
+
fs.renameSync(operation.temp, operation.target);
|
|
555
|
+
operation.committed = true;
|
|
556
|
+
if (failAt === 'during-spec-sync') throw new Error('during-spec-sync');
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function rollbackArchivedSpecSync(transaction) {
|
|
561
|
+
if (!transaction || !transaction.operations) return;
|
|
562
|
+
for (const operation of [...transaction.operations].reverse()) {
|
|
563
|
+
if (fs.existsSync(operation.backup)) {
|
|
564
|
+
fs.rmSync(operation.target, { force: true });
|
|
565
|
+
fs.renameSync(operation.backup, operation.target);
|
|
566
|
+
} else if (operation.committed) {
|
|
567
|
+
fs.rmSync(operation.target, { force: true });
|
|
568
|
+
}
|
|
569
|
+
fs.rmSync(operation.temp, { force: true });
|
|
570
|
+
fs.rmSync(operation.backup, { force: true });
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function cleanupArchivedSpecSync(transaction) {
|
|
575
|
+
if (!transaction || !transaction.operations) return;
|
|
576
|
+
for (const operation of transaction.operations) {
|
|
577
|
+
fs.rmSync(operation.temp, { force: true });
|
|
578
|
+
fs.rmSync(operation.backup, { force: true });
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function assertConfirmedArchive(projectRoot, changeName, archiveDir, options = {}) {
|
|
583
|
+
const { parseChangeArtifacts } = require('./ontology/artifact-parser.cjs');
|
|
584
|
+
const { normalizeFacts } = require('./ontology/normalizer.cjs');
|
|
585
|
+
const { validateArchiveCandidate } = require('./ontology/identity-index.cjs');
|
|
586
|
+
const facts = normalizeFacts(parseChangeArtifacts(projectRoot, changeName, {
|
|
587
|
+
changeDir: archiveDir,
|
|
588
|
+
persistStructuralIdentities: false,
|
|
589
|
+
}));
|
|
590
|
+
const validation = validateArchiveCandidate({
|
|
591
|
+
change: path.basename(archiveDir),
|
|
592
|
+
changeDir: archiveDir,
|
|
593
|
+
expectedArchiveDir: options.expectedArchiveDir,
|
|
594
|
+
}, facts);
|
|
595
|
+
if (!validation.eligible) {
|
|
596
|
+
const codes = validation.diagnostics.map((item) => item.code).join(', ');
|
|
597
|
+
throw new Error(`Archive 未通过 confirmed 事实校验: ${codes}`);
|
|
598
|
+
}
|
|
599
|
+
return { facts, snapshot: validation.snapshot, manifest: validation.manifest };
|
|
600
|
+
}
|
|
601
|
+
|
|
523
602
|
function ensureArchiveManifest(projectRoot, changeName, archiveDir, options = {}) {
|
|
524
|
-
const
|
|
525
|
-
|
|
603
|
+
const copiedSpecs = Array.isArray(options.copiedSpecs)
|
|
604
|
+
? options.copiedSpecs
|
|
605
|
+
: syncArchivedSpecs(projectRoot, archiveDir, changeName);
|
|
606
|
+
const committedArchiveDir = options.archivePath || archiveDir;
|
|
526
607
|
// U4: 归档时拷 events jsonl 进 archive/evidence/events/,报告可从归档目录重建(源数据与产物不再分离)
|
|
527
608
|
const eventsSrcDir = path.join(getDataDir(projectRoot), 'events', safeChangeName(changeName));
|
|
528
609
|
let evidenceEventsPath = null;
|
|
529
610
|
if (fs.existsSync(eventsSrcDir)) {
|
|
530
611
|
const eventsDstDir = path.join(archiveDir, 'evidence', 'events');
|
|
531
612
|
copyDirSync(eventsSrcDir, eventsDstDir);
|
|
532
|
-
evidenceEventsPath = toProjectRelative(
|
|
613
|
+
evidenceEventsPath = toProjectRelative(
|
|
614
|
+
projectRoot,
|
|
615
|
+
path.join(committedArchiveDir, 'evidence', 'events'),
|
|
616
|
+
);
|
|
533
617
|
}
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
618
|
+
const { materializeArchivePackage } = require('./ontology/archive-package.cjs');
|
|
619
|
+
const produced = materializeArchivePackage({
|
|
620
|
+
projectRoot,
|
|
621
|
+
changeName,
|
|
622
|
+
archiveDir,
|
|
623
|
+
archivePath: committedArchiveDir,
|
|
624
|
+
packagePath: Object.prototype.hasOwnProperty.call(options, 'packagePath')
|
|
625
|
+
? options.packagePath
|
|
626
|
+
: `${committedArchiveDir}.zip`,
|
|
627
|
+
archivedAt: options.archivedAt,
|
|
537
628
|
reason: options.reason || '变更已完成实施',
|
|
538
629
|
method: options.method || 'skywalk-full-spec-archive',
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
};
|
|
544
|
-
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
630
|
+
sourcePath: `openspec/changes/${changeName}`,
|
|
631
|
+
copiedSpecs,
|
|
632
|
+
evidenceEventsPath,
|
|
633
|
+
});
|
|
545
634
|
return {
|
|
546
|
-
|
|
547
|
-
manifest_path: manifestPath,
|
|
635
|
+
...produced,
|
|
548
636
|
copied_specs: copiedSpecs,
|
|
549
637
|
};
|
|
550
638
|
}
|
|
@@ -574,6 +662,8 @@ function ensureArchiveSuccessArtifacts(projectRoot, changeName, details = {}, op
|
|
|
574
662
|
};
|
|
575
663
|
}
|
|
576
664
|
|
|
665
|
+
assertConfirmedArchive(normalizedRoot, changeName, archiveDir);
|
|
666
|
+
|
|
577
667
|
// 显式 reportOutput 优先;否则默认落归档后 archive 目录的 reports/ 子目录
|
|
578
668
|
const reportPath = options.reportOutput
|
|
579
669
|
? (path.isAbsolute(options.reportOutput) ? options.reportOutput : path.resolve(normalizedRoot, options.reportOutput))
|
|
@@ -587,18 +677,27 @@ function ensureArchiveSuccessArtifacts(projectRoot, changeName, details = {}, op
|
|
|
587
677
|
const manifestInfo = ensureArchiveManifest(normalizedRoot, changeName, archiveDir, {
|
|
588
678
|
reason: archiveReason,
|
|
589
679
|
method: archiveMethod || 'skywalk-full-spec-archive',
|
|
590
|
-
archivedAt: details.archive_result?.
|
|
680
|
+
archivedAt: details.archive_result?.created_at || details.archive_result?.archived_at,
|
|
591
681
|
});
|
|
592
682
|
|
|
593
683
|
const archiveResult = {
|
|
594
684
|
reason: archiveReason,
|
|
595
685
|
method: manifestInfo.manifest.method,
|
|
686
|
+
schema_version: manifestInfo.manifest.schema_version,
|
|
687
|
+
producer: manifestInfo.manifest.producer,
|
|
688
|
+
project_id: manifestInfo.manifest.project_id,
|
|
689
|
+
archive_id: manifestInfo.manifest.archive_id,
|
|
690
|
+
change_id: manifestInfo.manifest.change_id,
|
|
691
|
+
created_at: manifestInfo.manifest.created_at,
|
|
596
692
|
archive_path: manifestInfo.manifest.archive_path,
|
|
597
693
|
report_path: toProjectRelative(normalizedRoot, reportPath),
|
|
598
694
|
// report_html_path 为预期路径:由 cmdEnd 落盘阶段写入(try/catch 容错,失败时不阻塞 md 主产物)。
|
|
599
695
|
// 此处无条件派生,html 实际落盘失败时该路径可能不存在;语义为"预期路径",消费者不应假定文件已存在。
|
|
600
696
|
report_html_path: toProjectRelative(normalizedRoot, reportPath.replace(/\.md$/i, '.html')),
|
|
601
697
|
manifest_path: toProjectRelative(normalizedRoot, manifestInfo.manifest_path),
|
|
698
|
+
canonical_facts_path: toProjectRelative(normalizedRoot, manifestInfo.canonical_facts_path),
|
|
699
|
+
conversion_report_path: toProjectRelative(normalizedRoot, manifestInfo.conversion_report_path),
|
|
700
|
+
package_path: toProjectRelative(normalizedRoot, manifestInfo.package_path),
|
|
602
701
|
task_completion: taskCompletion,
|
|
603
702
|
copied_specs: manifestInfo.copied_specs,
|
|
604
703
|
};
|
|
@@ -632,30 +731,124 @@ function archiveChangeDocs(projectRoot, changeName, options = {}) {
|
|
|
632
731
|
ensureDir(archiveRoot);
|
|
633
732
|
const archiveDate = options.date || today();
|
|
634
733
|
const archiveDir = nextAvailableDir(archiveRoot, `${archiveDate}-${changeName}`);
|
|
734
|
+
const { acquireChangeLock, releaseChangeLock } = require('./ontology/change-lock.cjs');
|
|
735
|
+
const ontologyRuntime = require('./ontology/runtime.cjs');
|
|
736
|
+
const lock = acquireChangeLock(normalizedRoot, changeName, options.lockOptions);
|
|
737
|
+
const token = lock.token.replace(/-/g, '');
|
|
738
|
+
const stagingDir = path.join(archiveRoot, `.archive-staging-${token}`);
|
|
739
|
+
const stagingPackagePath = `${stagingDir}.zip`;
|
|
740
|
+
const finalPackagePath = `${archiveDir}.zip`;
|
|
741
|
+
const sourceBackup = path.join(path.dirname(sourceDir), `.archive-source-${token}`);
|
|
742
|
+
let specTransaction = null;
|
|
743
|
+
let sourceMoved = false;
|
|
744
|
+
let archiveCommitted = false;
|
|
745
|
+
let packageCommitted = false;
|
|
746
|
+
try {
|
|
747
|
+
const semanticResult = ontologyRuntime.reconcileChange(normalizedRoot, changeName, {
|
|
748
|
+
profile: options.profile || 'auto',
|
|
749
|
+
markPending: true,
|
|
750
|
+
lock,
|
|
751
|
+
});
|
|
752
|
+
if (!semanticResult.valid) {
|
|
753
|
+
const codes = [...new Set(semanticResult.diagnostics
|
|
754
|
+
.filter(item => item.severity === 'error')
|
|
755
|
+
.map(item => item.code))];
|
|
756
|
+
throw new Error(`本体语义校验失败: ${codes.join(', ')}`);
|
|
757
|
+
}
|
|
635
758
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
759
|
+
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
760
|
+
fs.rmSync(stagingPackagePath, { force: true });
|
|
761
|
+
copyDirSync(sourceDir, stagingDir);
|
|
762
|
+
if (options.failAt === 'after-copy') throw new Error('after-copy');
|
|
763
|
+
|
|
764
|
+
ontologyRuntime.createArchiveSnapshot(
|
|
765
|
+
normalizedRoot,
|
|
766
|
+
changeName,
|
|
767
|
+
stagingDir,
|
|
768
|
+
semanticResult.state,
|
|
769
|
+
);
|
|
770
|
+
if (options.failAt === 'after-snapshot') throw new Error('after-snapshot');
|
|
771
|
+
|
|
772
|
+
const tcForReason = scanTaskCompletionForArchiveDir(normalizedRoot, changeName, stagingDir);
|
|
773
|
+
let archiveReason = options.reason || '变更已完成实施';
|
|
774
|
+
if (tcForReason?.has_incomplete) {
|
|
775
|
+
archiveReason = `部分完成(${tcForReason.incomplete} 项验收未勾选)`;
|
|
776
|
+
}
|
|
777
|
+
specTransaction = prepareArchivedSpecSync(
|
|
778
|
+
normalizedRoot,
|
|
779
|
+
stagingDir,
|
|
780
|
+
changeName,
|
|
781
|
+
token,
|
|
782
|
+
);
|
|
783
|
+
const manifestInfo = ensureArchiveManifest(normalizedRoot, changeName, stagingDir, {
|
|
784
|
+
reason: archiveReason,
|
|
785
|
+
method: 'skywalk-full-spec-archive',
|
|
786
|
+
archivePath: archiveDir,
|
|
787
|
+
packagePath: stagingPackagePath,
|
|
788
|
+
copiedSpecs: specTransaction.copiedSpecs,
|
|
789
|
+
});
|
|
790
|
+
const confirmed = assertConfirmedArchive(normalizedRoot, changeName, stagingDir, {
|
|
791
|
+
expectedArchiveDir: archiveDir,
|
|
792
|
+
});
|
|
793
|
+
if (confirmed.snapshot.source_revision !== semanticResult.revision) {
|
|
794
|
+
throw new Error('confirmed snapshot 的 source_revision 与工作态 revision 不一致');
|
|
795
|
+
}
|
|
796
|
+
if (options.failAt === 'after-manifest') throw new Error('after-manifest');
|
|
797
|
+
if (typeof options._testBeforeFinalize === 'function') {
|
|
798
|
+
options._testBeforeFinalize({ sourceDir, stagingDir, archiveDir });
|
|
799
|
+
}
|
|
648
800
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
801
|
+
const activeFacts = require('./ontology/normalizer.cjs').normalizeFacts(
|
|
802
|
+
require('./ontology/artifact-parser.cjs').parseChangeArtifacts(
|
|
803
|
+
normalizedRoot,
|
|
804
|
+
changeName,
|
|
805
|
+
{ changeDir: sourceDir, persistStructuralIdentities: false },
|
|
806
|
+
),
|
|
807
|
+
);
|
|
808
|
+
if (activeFacts.facts_hash !== semanticResult.state.facts_hash) {
|
|
809
|
+
throw new Error('活动 Change 在归档事务期间发生变化');
|
|
810
|
+
}
|
|
652
811
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
812
|
+
if (!options.keepActive) {
|
|
813
|
+
fs.renameSync(sourceDir, sourceBackup);
|
|
814
|
+
sourceMoved = true;
|
|
815
|
+
}
|
|
816
|
+
if (options.failAt === 'after-source-move') throw new Error('after-source-move');
|
|
817
|
+
fs.renameSync(stagingDir, archiveDir);
|
|
818
|
+
archiveCommitted = true;
|
|
819
|
+
commitArchivedSpecSync(specTransaction, options.failAt);
|
|
820
|
+
fs.rmSync(finalPackagePath, { force: true });
|
|
821
|
+
fs.renameSync(stagingPackagePath, finalPackagePath);
|
|
822
|
+
packageCommitted = true;
|
|
823
|
+
if (sourceMoved) fs.rmSync(sourceBackup, { recursive: true, force: true });
|
|
824
|
+
cleanupArchivedSpecSync(specTransaction);
|
|
825
|
+
|
|
826
|
+
return {
|
|
827
|
+
...manifestInfo.manifest,
|
|
828
|
+
project_root: normalizedRoot,
|
|
829
|
+
archive_path: archiveDir,
|
|
830
|
+
manifest_path: path.join(archiveDir, 'archive-manifest.json'),
|
|
831
|
+
canonical_facts_path: path.join(archiveDir, 'canonical-facts.json'),
|
|
832
|
+
conversion_report_path: path.join(archiveDir, 'conversion-report.json'),
|
|
833
|
+
package_path: finalPackagePath,
|
|
834
|
+
active_change_exists: fs.existsSync(sourceDir),
|
|
835
|
+
};
|
|
836
|
+
} catch (error) {
|
|
837
|
+
rollbackArchivedSpecSync(specTransaction);
|
|
838
|
+
if (archiveCommitted) fs.rmSync(archiveDir, { recursive: true, force: true });
|
|
839
|
+
if (packageCommitted) fs.rmSync(finalPackagePath, { force: true });
|
|
840
|
+
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
841
|
+
fs.rmSync(stagingPackagePath, { force: true });
|
|
842
|
+
if (sourceMoved && fs.existsSync(sourceBackup) && !fs.existsSync(sourceDir)) {
|
|
843
|
+
fs.renameSync(sourceBackup, sourceDir);
|
|
844
|
+
}
|
|
845
|
+
const wrapped = new Error(`归档事务失败,活动 Change 保持不变: ${error.message}`);
|
|
846
|
+
wrapped.code = error.code || 'SEM_ARCHIVE_TRANSACTION_FAILED';
|
|
847
|
+
wrapped.cause = error;
|
|
848
|
+
throw wrapped;
|
|
849
|
+
} finally {
|
|
850
|
+
releaseChangeLock(lock);
|
|
851
|
+
}
|
|
659
852
|
}
|
|
660
853
|
|
|
661
854
|
/** 追加一行 JSONL 到事件文件(写入失败时抛出异常) */
|
|
@@ -3340,6 +3533,37 @@ function cmdStart(args) {
|
|
|
3340
3533
|
console.log(JSON.stringify(output, null, 2));
|
|
3341
3534
|
}
|
|
3342
3535
|
|
|
3536
|
+
const ONTOLOGY_AUTHORING_STAGES = new Set(['propose', 'spec', 'design', 'task', 'check']);
|
|
3537
|
+
|
|
3538
|
+
function syncWorkingOntologyForStage(projectRoot, changeName, command, result) {
|
|
3539
|
+
if (!ONTOLOGY_AUTHORING_STAGES.has(command) || result === 'failure') return null;
|
|
3540
|
+
if (!changeName || changeName === 'general') return null;
|
|
3541
|
+
const changeDir = getChangeDir(projectRoot, changeName);
|
|
3542
|
+
if (!fs.existsSync(changeDir)) return null;
|
|
3543
|
+
const reconciled = require('./ontology/runtime.cjs').reconcileChange(
|
|
3544
|
+
projectRoot,
|
|
3545
|
+
changeName,
|
|
3546
|
+
{
|
|
3547
|
+
profile: 'auto',
|
|
3548
|
+
markPending: command === 'check',
|
|
3549
|
+
},
|
|
3550
|
+
);
|
|
3551
|
+
return {
|
|
3552
|
+
schema_version: reconciled.state.artifact_schema_version,
|
|
3553
|
+
index_schema_version: reconciled.state.artifact_index_schema_version,
|
|
3554
|
+
revision: reconciled.revision,
|
|
3555
|
+
review_status: reconciled.state.review_status,
|
|
3556
|
+
valid: reconciled.valid,
|
|
3557
|
+
changed: Boolean(reconciled.changed),
|
|
3558
|
+
working_ontology_path: toProjectRelative(projectRoot, reconciled.paths.working),
|
|
3559
|
+
artifact_index_path: toProjectRelative(projectRoot, reconciled.paths.artifactIndex),
|
|
3560
|
+
artifact_json_paths: reconciled.paths.artifactFacts.map((filePath) => (
|
|
3561
|
+
toProjectRelative(projectRoot, filePath)
|
|
3562
|
+
)),
|
|
3563
|
+
diagnostics: reconciled.diagnostics,
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3343
3567
|
/**
|
|
3344
3568
|
* log end: 记录阶段结束
|
|
3345
3569
|
*/
|
|
@@ -3422,6 +3646,30 @@ function cmdEnd(args, options = {}) {
|
|
|
3422
3646
|
fail(`details JSON 解析失败: ${err.message}`);
|
|
3423
3647
|
}
|
|
3424
3648
|
|
|
3649
|
+
let semanticState = null;
|
|
3650
|
+
try {
|
|
3651
|
+
semanticState = syncWorkingOntologyForStage(projectRoot, change, command, result);
|
|
3652
|
+
if (semanticState) {
|
|
3653
|
+
details = {
|
|
3654
|
+
...details,
|
|
3655
|
+
semantic_state: semanticState,
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
} catch (error) {
|
|
3659
|
+
if (ONTOLOGY_AUTHORING_STAGES.has(command) && result !== 'failure') {
|
|
3660
|
+
result = 'partial';
|
|
3661
|
+
summary = `${summary}(工作态本体 JSON 同步失败:${error.message})`.trim();
|
|
3662
|
+
semanticState = {
|
|
3663
|
+
status: 'failed',
|
|
3664
|
+
error: error.message,
|
|
3665
|
+
};
|
|
3666
|
+
details = {
|
|
3667
|
+
...details,
|
|
3668
|
+
semantic_state: semanticState,
|
|
3669
|
+
};
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3425
3673
|
const durationMs = startEvent
|
|
3426
3674
|
? new Date(timestamp).getTime() - new Date(startEvent.timestamp).getTime()
|
|
3427
3675
|
: null;
|
|
@@ -3565,6 +3813,7 @@ function cmdEnd(args, options = {}) {
|
|
|
3565
3813
|
duration_ms: durationMs,
|
|
3566
3814
|
recorded_at: timestamp,
|
|
3567
3815
|
report_output: resolvedReportOutput || undefined,
|
|
3816
|
+
semantic_state: semanticState || undefined,
|
|
3568
3817
|
message: `SDD ${event.command} 阶段结束(${result},耗时 ${durationMs ? (durationMs / 1000).toFixed(1) + 's' : '未知'})`,
|
|
3569
3818
|
};
|
|
3570
3819
|
if (!options.silent) {
|
|
@@ -4133,6 +4382,106 @@ function cmdTasksStatus(args) {
|
|
|
4133
4382
|
}
|
|
4134
4383
|
}
|
|
4135
4384
|
|
|
4385
|
+
function semanticCommandContext(args) {
|
|
4386
|
+
const projectRoot = normalizeProjectRoot(args.project || args['project-root'] || process.cwd());
|
|
4387
|
+
const changeName = args.change || args['change-name'];
|
|
4388
|
+
if (!changeName) {
|
|
4389
|
+
throw new Error('缺少 --change 参数');
|
|
4390
|
+
}
|
|
4391
|
+
return {
|
|
4392
|
+
projectRoot,
|
|
4393
|
+
changeName,
|
|
4394
|
+
profile: args.profile || 'auto',
|
|
4395
|
+
};
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
function cmdSemanticIdentity(args) {
|
|
4399
|
+
const identity = require('./ontology/id.cjs').allocateIdentity(args);
|
|
4400
|
+
console.log(JSON.stringify(identity, null, 2));
|
|
4401
|
+
return identity;
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4404
|
+
function semanticResultPayload(result) {
|
|
4405
|
+
return {
|
|
4406
|
+
change: result.state.change,
|
|
4407
|
+
profile: result.profile,
|
|
4408
|
+
valid: result.valid,
|
|
4409
|
+
review_status: result.state.review_status,
|
|
4410
|
+
revision: result.revision,
|
|
4411
|
+
changed: Boolean(result.changed),
|
|
4412
|
+
counts: result.counts,
|
|
4413
|
+
paths: result.paths,
|
|
4414
|
+
diagnostics: result.diagnostics,
|
|
4415
|
+
};
|
|
4416
|
+
}
|
|
4417
|
+
|
|
4418
|
+
function cmdSemanticScan(args) {
|
|
4419
|
+
const context = semanticCommandContext(args);
|
|
4420
|
+
const result = require('./ontology/runtime.cjs').scanChange(
|
|
4421
|
+
context.projectRoot,
|
|
4422
|
+
context.changeName,
|
|
4423
|
+
{ profile: context.profile },
|
|
4424
|
+
);
|
|
4425
|
+
console.log(JSON.stringify(semanticResultPayload({ ...result, changed: false, paths: undefined }), null, 2));
|
|
4426
|
+
if (!result.valid) process.exitCode = 1;
|
|
4427
|
+
return result;
|
|
4428
|
+
}
|
|
4429
|
+
|
|
4430
|
+
function cmdSemanticReconcile(args, options = {}) {
|
|
4431
|
+
const context = semanticCommandContext(args);
|
|
4432
|
+
const result = require('./ontology/runtime.cjs').reconcileChange(
|
|
4433
|
+
context.projectRoot,
|
|
4434
|
+
context.changeName,
|
|
4435
|
+
{ profile: context.profile, markPending: Boolean(options.markPending) },
|
|
4436
|
+
);
|
|
4437
|
+
console.log(JSON.stringify(semanticResultPayload(result), null, 2));
|
|
4438
|
+
if (!result.valid) process.exitCode = 1;
|
|
4439
|
+
return result;
|
|
4440
|
+
}
|
|
4441
|
+
|
|
4442
|
+
function cmdSemanticCheck(args) {
|
|
4443
|
+
return cmdSemanticReconcile(args, { markPending: true });
|
|
4444
|
+
}
|
|
4445
|
+
|
|
4446
|
+
function cmdSemanticStatus(args) {
|
|
4447
|
+
const context = semanticCommandContext(args);
|
|
4448
|
+
const state = require('./ontology/runtime.cjs').readWorkingState(context.projectRoot, context.changeName);
|
|
4449
|
+
if (!state) throw new Error(`不存在工作态本体实例: ${context.changeName}`);
|
|
4450
|
+
console.log(JSON.stringify(state, null, 2));
|
|
4451
|
+
return state;
|
|
4452
|
+
}
|
|
4453
|
+
|
|
4454
|
+
function cmdSemanticObserve(args) {
|
|
4455
|
+
const context = semanticCommandContext(args);
|
|
4456
|
+
const observer = require('./ontology/artifact-observer.cjs').observeChangeArtifacts(
|
|
4457
|
+
context.projectRoot,
|
|
4458
|
+
context.changeName,
|
|
4459
|
+
{
|
|
4460
|
+
profile: context.profile,
|
|
4461
|
+
pollIntervalMs: Number(args.interval || 1500),
|
|
4462
|
+
onReconciled(result) {
|
|
4463
|
+
console.log(JSON.stringify(semanticResultPayload(result)));
|
|
4464
|
+
},
|
|
4465
|
+
onError(error) {
|
|
4466
|
+
console.error(`[ontology-observer] ${error.message}`);
|
|
4467
|
+
},
|
|
4468
|
+
},
|
|
4469
|
+
);
|
|
4470
|
+
console.log(JSON.stringify({
|
|
4471
|
+
change: context.changeName,
|
|
4472
|
+
mode: observer.mode,
|
|
4473
|
+
change_dir: observer.changeDir,
|
|
4474
|
+
boundary: 'observe-and-reconcile-only',
|
|
4475
|
+
}));
|
|
4476
|
+
const close = () => {
|
|
4477
|
+
observer.close();
|
|
4478
|
+
process.exit(0);
|
|
4479
|
+
};
|
|
4480
|
+
process.once('SIGINT', close);
|
|
4481
|
+
process.once('SIGTERM', close);
|
|
4482
|
+
return observer;
|
|
4483
|
+
}
|
|
4484
|
+
|
|
4136
4485
|
/**
|
|
4137
4486
|
* log archive-docs: 真实归档 Simple/Full spec,结束 archive 阶段并生成最终报告
|
|
4138
4487
|
*/
|
|
@@ -4149,6 +4498,7 @@ function cmdArchiveDocs(args) {
|
|
|
4149
4498
|
reason: args.reason || '',
|
|
4150
4499
|
date: args.date,
|
|
4151
4500
|
keepActive: Boolean(args['keep-active']),
|
|
4501
|
+
profile: args.profile || 'auto',
|
|
4152
4502
|
});
|
|
4153
4503
|
} else {
|
|
4154
4504
|
const repaired = ensureArchiveSuccessArtifacts(projectRoot, changeName, {
|
|
@@ -4195,6 +4545,28 @@ function cmdArchiveDocs(args) {
|
|
|
4195
4545
|
}, { silent: true });
|
|
4196
4546
|
}
|
|
4197
4547
|
|
|
4548
|
+
const archiveDir = path.resolve(result.archive_path);
|
|
4549
|
+
const finalPackage = ensureArchiveManifest(projectRoot, changeName, archiveDir, {
|
|
4550
|
+
reason: result.reason || args.reason || '',
|
|
4551
|
+
method: result.method || 'skywalk-full-spec-archive',
|
|
4552
|
+
archivePath: archiveDir,
|
|
4553
|
+
archivedAt: result.created_at || result.archived_at,
|
|
4554
|
+
copiedSpecs: result.copied_specs,
|
|
4555
|
+
});
|
|
4556
|
+
result = {
|
|
4557
|
+
...result,
|
|
4558
|
+
schema_version: finalPackage.manifest.schema_version,
|
|
4559
|
+
producer: finalPackage.manifest.producer,
|
|
4560
|
+
project_id: finalPackage.manifest.project_id,
|
|
4561
|
+
archive_id: finalPackage.manifest.archive_id,
|
|
4562
|
+
change_id: finalPackage.manifest.change_id,
|
|
4563
|
+
created_at: finalPackage.manifest.created_at,
|
|
4564
|
+
manifest_path: finalPackage.manifest_path,
|
|
4565
|
+
canonical_facts_path: finalPackage.canonical_facts_path,
|
|
4566
|
+
conversion_report_path: finalPackage.conversion_report_path,
|
|
4567
|
+
package_path: finalPackage.package_path,
|
|
4568
|
+
};
|
|
4569
|
+
|
|
4198
4570
|
console.log(JSON.stringify({
|
|
4199
4571
|
...result,
|
|
4200
4572
|
report_output: stageEnd ? stageEnd.report_output : reportOutput,
|
|
@@ -4257,6 +4629,24 @@ function main() {
|
|
|
4257
4629
|
case 'archive-docs':
|
|
4258
4630
|
cmdArchiveDocs(flags);
|
|
4259
4631
|
break;
|
|
4632
|
+
case 'semantic-scan':
|
|
4633
|
+
cmdSemanticScan(flags);
|
|
4634
|
+
break;
|
|
4635
|
+
case 'semantic-identity':
|
|
4636
|
+
cmdSemanticIdentity(flags);
|
|
4637
|
+
break;
|
|
4638
|
+
case 'semantic-reconcile':
|
|
4639
|
+
cmdSemanticReconcile(flags);
|
|
4640
|
+
break;
|
|
4641
|
+
case 'semantic-check':
|
|
4642
|
+
cmdSemanticCheck(flags);
|
|
4643
|
+
break;
|
|
4644
|
+
case 'semantic-status':
|
|
4645
|
+
cmdSemanticStatus(flags);
|
|
4646
|
+
break;
|
|
4647
|
+
case 'semantic-observe':
|
|
4648
|
+
cmdSemanticObserve(flags);
|
|
4649
|
+
break;
|
|
4260
4650
|
case 'check-task':
|
|
4261
4651
|
cmdCheckTask(flags);
|
|
4262
4652
|
break;
|
|
@@ -4278,6 +4668,13 @@ SDD Telemetry CLI - 流程度量采集工具
|
|
|
4278
4668
|
node skywalk-sdd/log.cjs tasks-status --project=<path> --change=<name> [--require-complete]
|
|
4279
4669
|
node skywalk-sdd/log.cjs check-task --project=<path> --change=<name> --task-id=<id>
|
|
4280
4670
|
node skywalk-sdd/log.cjs archive-docs --project=<path> --change=<name> [--reason=<text>] [--event-id=<id>] [--report-output=<file>]
|
|
4671
|
+
node skywalk-sdd/log.cjs semantic-identity --delta-state=added
|
|
4672
|
+
node skywalk-sdd/log.cjs semantic-identity --delta-state=modified --entity-id=<uuid> --predecessor-version=<uuid>
|
|
4673
|
+
node skywalk-sdd/log.cjs semantic-identity --delta-state=unchanged --entity-id=<uuid> --version-id=<uuid>
|
|
4674
|
+
node skywalk-sdd/log.cjs semantic-reconcile --project=<path> --change=<name> [--profile=auto|simple|full|strict]
|
|
4675
|
+
node skywalk-sdd/log.cjs semantic-check --project=<path> --change=<name> [--profile=...]
|
|
4676
|
+
node skywalk-sdd/log.cjs semantic-status --project=<path> --change=<name>
|
|
4677
|
+
node skywalk-sdd/log.cjs semantic-observe --project=<path> --change=<name> [--interval=1500]
|
|
4281
4678
|
|
|
4282
4679
|
子命令:
|
|
4283
4680
|
start 记录 SDD 阶段开始,返回 event_id
|
|
@@ -4289,6 +4686,12 @@ SDD Telemetry CLI - 流程度量采集工具
|
|
|
4289
4686
|
tasks-status 扫描 Full/Simple 模式 tasks.md 勾选状态
|
|
4290
4687
|
check-task 扫描变更目录 tasks.md 并勾选指定 task_id
|
|
4291
4688
|
archive-docs 将 Simple/Full spec 变更真实移动到 openspec/changes/archive/,并可结束 archive 阶段生成报告
|
|
4689
|
+
semantic-identity 生成或复用实体/版本 UUID;无需网络和中央 ID 服务
|
|
4690
|
+
semantic-scan 只读解析并校验当前 Change
|
|
4691
|
+
semantic-reconcile 全量对账文件与本地工作态本体实例
|
|
4692
|
+
semantic-check 全量对账并在通过时标记 pending
|
|
4693
|
+
semantic-status 读取当前工作态本体实例
|
|
4694
|
+
semantic-observe 观察文件变化并同步;不提供 Hook 式控制,Check/Archive 仍会全量对账
|
|
4292
4695
|
|
|
4293
4696
|
示例:
|
|
4294
4697
|
node skywalk-sdd/log.cjs start --command=propose --project=/my/project --change=user-auth --agent=cursor
|
|
@@ -4327,6 +4730,12 @@ module.exports = {
|
|
|
4327
4730
|
cmdDoctor,
|
|
4328
4731
|
cmdTasksStatus,
|
|
4329
4732
|
cmdArchiveDocs,
|
|
4733
|
+
cmdSemanticIdentity,
|
|
4734
|
+
cmdSemanticScan,
|
|
4735
|
+
cmdSemanticReconcile,
|
|
4736
|
+
cmdSemanticCheck,
|
|
4737
|
+
cmdSemanticStatus,
|
|
4738
|
+
cmdSemanticObserve,
|
|
4330
4739
|
computeChangeMetrics,
|
|
4331
4740
|
computeOverviewMetrics,
|
|
4332
4741
|
computeCapabilityMetrics,
|