kld-sdd 2.6.12 → 2.6.14
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 +29 -7
- package/kld-sdd-guide.html +428 -159
- package/lib/init.js +37 -7
- package/package.json +5 -2
- package/skywalk-sdd/index.cjs +2668 -415
- package/skywalk-sdd/metrics-v3.cjs +1153 -0
- package/skywalk-sdd/ontology/archive-package.cjs +120 -3
- package/skywalk-sdd/ontology/identity-index.cjs +9 -2
- package/skywalk-sdd/ontology/ontology-paths.cjs +73 -0
- package/skywalk-sdd/ontology/resolve-spec-root.cjs +20 -80
- package/skywalk-sdd/ontology/runtime.cjs +32 -22
- package/skywalk-sdd/ontology/structural-identity.cjs +11 -2
- package/skywalk-sdd/ontology/traceability-validator.cjs +16 -8
- package/skywalk-sdd/ontology/working-artifacts.cjs +4 -2
- package/skywalk-sdd/reporting/change-report-model.cjs +309 -0
- package/skywalk-sdd/reporting/change-report-renderer.cjs +425 -0
- package/skywalk-sdd/runtime-metadata.cjs +21 -0
- package/templates/hooks/claude/hooks/sdd-apply-test-gate.cjs +39 -9
- package/templates/hooks/claude/hooks/sdd-post-tool.cjs +87 -8
- package/templates/hooks/claude/hooks/sdd-prompt.cjs +3 -1
- package/templates/hooks/codebuddy/hooks/sdd-apply-test-gate.cjs +39 -9
- package/templates/hooks/codebuddy/hooks/sdd-post-tool.cjs +87 -9
- package/templates/hooks/codebuddy/hooks/sdd-prompt.cjs +3 -1
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +14 -13
- package/templates/skills/kld-sdd/opsx-apply/checklist.md +20 -20
- package/templates/skills/kld-sdd/opsx-apply/implementer-prompt.md +12 -12
- package/templates/skills/kld-sdd/opsx-apply/reference.md +32 -31
- package/templates/skills/kld-sdd/opsx-archive/SKILL.md +44 -6
- package/templates/skills/kld-sdd/opsx-archive/checklist.md +5 -2
- package/templates/skills/kld-sdd/opsx-check/SKILL.md +21 -13
- package/templates/skills/kld-sdd/opsx-check/checklist.md +8 -7
- package/templates/skills/kld-sdd/opsx-design/SKILL.md +1 -1
- package/templates/skills/kld-sdd/opsx-design/checklist.md +1 -1
- package/templates/skills/kld-sdd/opsx-kb-ingest/SKILL.md +14 -0
- package/templates/skills/kld-sdd/opsx-ontology-query/SKILL.md +31 -0
- package/templates/skills/kld-sdd/opsx-propose/SKILL.md +12 -4
- package/templates/skills/kld-sdd/opsx-propose/reference.md +29 -3
- package/templates/skills/kld-sdd/opsx-rules/reference.md +3 -1
- package/templates/skills/kld-sdd/opsx-spec/SKILL.md +2 -2
- package/templates/skills/kld-sdd/opsx-spec/checklist.md +1 -1
- package/templates/skills/kld-sdd/opsx-task/SKILL.md +10 -10
- package/templates/skills/kld-sdd/opsx-task/checklist.md +3 -3
- package/templates/skills/kld-sdd/opsx-task/reference.md +3 -3
- package/templates/skills/kld-sdd/opsx-test/SKILL.md +7 -5
- package/templates/skills/kld-sdd/{opsx-tdd-anti-patterns → tdd-anti-patterns}/SKILL.md +4 -4
- package/templates/skills/kld-sdd/{opsx-tdd-anti-patterns → tdd-anti-patterns}/reference.md +4 -4
- package/templates/skills/kld-sdd/{opsx-tdd-core → tdd-core}/SKILL.md +13 -13
- package/templates/skills/kld-sdd/{opsx-tdd-core → tdd-core}/checklist.md +5 -5
- package/templates/skills/kld-sdd/{opsx-tdd-core → tdd-core}/reference.md +12 -12
- package/templates/skills/kld-sdd/{opsx-tdd-metrics → tdd-metrics}/SKILL.md +3 -3
- package/templates/skills/kld-sdd/{opsx-tdd-metrics → tdd-metrics}/checklist.md +2 -2
- package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/SKILL.md +2 -2
- package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/parameterized-testing.md +1 -1
- package/templates/skills/kld-sdd/{opsx-tdd-review → tdd-review}/SKILL.md +5 -5
- package/templates/skills/kld-sdd/{opsx-tdd-review → tdd-review}/checklist.md +5 -5
- package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/SKILL.md +3 -3
- package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/non-tdd-modules.md +1 -1
- package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/refactor-checklist.md +1 -1
- package/templates/skills/kld-sdd/tdd-rules/rules/test-skeleton-telemetry.md +19 -0
- package/templates/skills/kld-sdd/opsx-tdd-rules/rules/test-skeleton-telemetry.md +0 -19
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/cause-effect-clarity.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/clean-test-data.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/existing-test-awareness.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/given-when-then.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/good-test-qualities.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/mock-boundary.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/naming-conventions.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/no-logic-in-tests.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/one-test-one-scenario.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/prefer-public-apis.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/general/test-behaviors-not-methods.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/java/argument-matching.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/java/controller-test-rules.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/java/domain-service-rules.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/java/java-test-template.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/java/json-serialization.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/java/logging-rules.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/post-generation/compilation-verification.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/post-generation/execution-verification.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/python/py-test-template.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-quality → tdd-quality}/rules/typescript/ts-test-template.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/controller-strategy.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/dag-generation-rules.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/des-step-annotation.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/exception-path-coverage.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/green-scope-declaration.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/green-yagni-fence.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/multi-validation-split.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/task-type-definitions.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/tdd-strategy-selection.md +0 -0
- /package/templates/skills/kld-sdd/{opsx-tdd-rules → tdd-rules}/rules/test-execution-gate.md +0 -0
|
@@ -4,9 +4,11 @@ const crypto = require('crypto');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { generateUuidV7 } = require('./id.cjs');
|
|
7
|
+
const { resolveRuntimeMetadata } = require('../runtime-metadata.cjs');
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
-
const
|
|
9
|
+
const RUNTIME_METADATA = resolveRuntimeMetadata();
|
|
10
|
+
const PRODUCER_VERSION = RUNTIME_METADATA.version;
|
|
11
|
+
const PRODUCER = RUNTIME_METADATA.producer;
|
|
10
12
|
const MANIFEST_SCHEMA_VERSION = 'kld-sdd-archive-manifest/v2';
|
|
11
13
|
const CANONICAL_FACTS_SCHEMA_VERSION = 'kld-sdd-canonical-facts/v2';
|
|
12
14
|
const CONVERSION_REPORT_SCHEMA_VERSION = 'kld-sdd-conversion-report/v1';
|
|
@@ -15,7 +17,7 @@ const MANIFEST_PATH = 'archive-manifest.json';
|
|
|
15
17
|
const ONTOLOGY_PATH = 'archive-ontology.json';
|
|
16
18
|
const CANONICAL_FACTS_PATH = 'canonical-facts.json';
|
|
17
19
|
const CONVERSION_REPORT_PATH = 'conversion-report.json';
|
|
18
|
-
const PACKAGE_SUFFIXES = new Set(['.md', '.json', '.jsonl', '.yaml', '.yml']);
|
|
20
|
+
const PACKAGE_SUFFIXES = new Set(['.md', '.html', '.json', '.jsonl', '.yaml', '.yml']);
|
|
19
21
|
|
|
20
22
|
function sha256(content) {
|
|
21
23
|
return crypto.createHash('sha256').update(content).digest('hex');
|
|
@@ -63,10 +65,16 @@ function ensureProjectIdentity(projectRoot) {
|
|
|
63
65
|
}
|
|
64
66
|
return existing;
|
|
65
67
|
}
|
|
68
|
+
// 兜底:project-identity.json 未通过 opsx-ontology-query 配置 KB 时创建
|
|
69
|
+
// 此时 project_id 为随机值,KB 入库时会因 project_id !== spaceKey 而失败
|
|
70
|
+
console.warn('⚠️ project-identity.json 不存在 — 未通过 opsx-ontology-query 配置 KB。');
|
|
71
|
+
console.warn('⚠️ 将生成临时 project_id,KB 入库时需确保与 Space spaceKey 一致。');
|
|
72
|
+
console.warn('⚠️ 建议在首次使用 opsx-ontology-query 配置 KB 时自动创建正确的 project-identity.json。');
|
|
66
73
|
const identity = {
|
|
67
74
|
schema_version: PROJECT_IDENTITY_SCHEMA_VERSION,
|
|
68
75
|
project_id: `urn:kld:sdd:project:${generateUuidV7()}`,
|
|
69
76
|
created_at: new Date().toISOString(),
|
|
77
|
+
_warning: 'UNCONFIGURED — 请通过 opsx-ontology-query 配置 KB 后更新 project_id 为 Space spaceKey',
|
|
70
78
|
};
|
|
71
79
|
writeJson(identityPath, identity);
|
|
72
80
|
return identity;
|
|
@@ -495,6 +503,77 @@ function writeArchiveZip(archiveDir, packagePath, manifest) {
|
|
|
495
503
|
return packagePath;
|
|
496
504
|
}
|
|
497
505
|
|
|
506
|
+
function readStoredZipEntries(packagePath) {
|
|
507
|
+
const bytes = fs.readFileSync(packagePath);
|
|
508
|
+
const entries = new Map();
|
|
509
|
+
let offset = 0;
|
|
510
|
+
while (offset + 4 <= bytes.length && bytes.readUInt32LE(offset) === 0x04034b50) {
|
|
511
|
+
if (offset + 30 > bytes.length) {
|
|
512
|
+
throw new Error('Archive Package ZIP local header 不完整');
|
|
513
|
+
}
|
|
514
|
+
const flags = bytes.readUInt16LE(offset + 6);
|
|
515
|
+
const method = bytes.readUInt16LE(offset + 8);
|
|
516
|
+
const checksum = bytes.readUInt32LE(offset + 14);
|
|
517
|
+
const compressedSize = bytes.readUInt32LE(offset + 18);
|
|
518
|
+
const uncompressedSize = bytes.readUInt32LE(offset + 22);
|
|
519
|
+
const nameLength = bytes.readUInt16LE(offset + 26);
|
|
520
|
+
const extraLength = bytes.readUInt16LE(offset + 28);
|
|
521
|
+
const nameStart = offset + 30;
|
|
522
|
+
const contentStart = nameStart + nameLength + extraLength;
|
|
523
|
+
const contentEnd = contentStart + compressedSize;
|
|
524
|
+
if ((flags & 0x0800) !== 0x0800 || method !== 0 || contentEnd > bytes.length) {
|
|
525
|
+
throw new Error('Archive Package ZIP 只允许完整的 UTF-8 stored entry');
|
|
526
|
+
}
|
|
527
|
+
const name = bytes.subarray(nameStart, nameStart + nameLength).toString('utf8');
|
|
528
|
+
const safeName = safePackagePath(name);
|
|
529
|
+
if (entries.has(safeName)) {
|
|
530
|
+
throw new Error(`Archive Package ZIP 包含重复路径: ${safeName}`);
|
|
531
|
+
}
|
|
532
|
+
const content = bytes.subarray(contentStart, contentEnd);
|
|
533
|
+
if (compressedSize !== uncompressedSize || crc32(content) !== checksum) {
|
|
534
|
+
throw new Error(`Archive Package ZIP entry 校验失败: ${safeName}`);
|
|
535
|
+
}
|
|
536
|
+
entries.set(safeName, content);
|
|
537
|
+
offset = contentEnd;
|
|
538
|
+
}
|
|
539
|
+
if (entries.size === 0 || offset + 4 > bytes.length || bytes.readUInt32LE(offset) !== 0x02014b50) {
|
|
540
|
+
throw new Error('Archive Package ZIP 缺少 central directory');
|
|
541
|
+
}
|
|
542
|
+
return entries;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function verifyArchivePackage(archiveDir, packagePath, manifest) {
|
|
546
|
+
const expectedPaths = [MANIFEST_PATH, ...(manifest.files || []).map((item) => safePackagePath(item.path))];
|
|
547
|
+
const expectedUnique = new Set(expectedPaths);
|
|
548
|
+
if (expectedUnique.size !== expectedPaths.length) {
|
|
549
|
+
throw new Error('Archive Package Manifest 包含重复路径');
|
|
550
|
+
}
|
|
551
|
+
const entries = readStoredZipEntries(packagePath);
|
|
552
|
+
const actualPaths = [...entries.keys()];
|
|
553
|
+
if (
|
|
554
|
+
actualPaths.length !== expectedPaths.length
|
|
555
|
+
|| expectedPaths.some((entryPath) => !entries.has(entryPath))
|
|
556
|
+
) {
|
|
557
|
+
throw new Error('Archive Package ZIP 与 Manifest 文件清单不一致');
|
|
558
|
+
}
|
|
559
|
+
const manifestBytes = fs.readFileSync(path.join(archiveDir, MANIFEST_PATH));
|
|
560
|
+
if (!entries.get(MANIFEST_PATH)?.equals(manifestBytes)) {
|
|
561
|
+
throw new Error('Archive Package ZIP 中的 Manifest 不是最终落盘版本');
|
|
562
|
+
}
|
|
563
|
+
for (const item of manifest.files || []) {
|
|
564
|
+
const content = entries.get(item.path);
|
|
565
|
+
const expectedHash = `sha256:${sha256(content)}`;
|
|
566
|
+
if (item.content_hash !== expectedHash) {
|
|
567
|
+
throw new Error(`Archive Package 内容哈希不一致: ${item.path}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
entry_count: entries.size,
|
|
572
|
+
package_hash: `sha256:${sha256(fs.readFileSync(packagePath))}`,
|
|
573
|
+
paths: actualPaths,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
498
577
|
function materializeArchivePackage(options = {}) {
|
|
499
578
|
const projectRoot = path.resolve(options.projectRoot || process.cwd());
|
|
500
579
|
const archiveDir = path.resolve(requiredText(options.archiveDir, 'archiveDir', 'Archive Package'));
|
|
@@ -588,6 +667,41 @@ function materializeArchivePackage(options = {}) {
|
|
|
588
667
|
};
|
|
589
668
|
}
|
|
590
669
|
|
|
670
|
+
function finalizeArchivePackageAfterReports(options = {}) {
|
|
671
|
+
const projectRoot = path.resolve(options.projectRoot || process.cwd());
|
|
672
|
+
const changeName = requiredText(options.changeName, 'changeName', 'Archive finalizer');
|
|
673
|
+
const runIdentity = requiredText(options.runIdentity, 'runIdentity', 'Archive finalizer');
|
|
674
|
+
const { withChangeLock } = require('./change-lock.cjs');
|
|
675
|
+
return withChangeLock(projectRoot, changeName, options.lockOptions || {}, (lock) => {
|
|
676
|
+
const prepared = typeof options.prepare === 'function'
|
|
677
|
+
? options.prepare(lock)
|
|
678
|
+
: {};
|
|
679
|
+
const produced = materializeArchivePackage({
|
|
680
|
+
...options,
|
|
681
|
+
...prepared,
|
|
682
|
+
projectRoot,
|
|
683
|
+
changeName,
|
|
684
|
+
});
|
|
685
|
+
if (!produced.package_path) {
|
|
686
|
+
throw new Error('Archive finalizer 必须生成 ZIP');
|
|
687
|
+
}
|
|
688
|
+
const verification = verifyArchivePackage(
|
|
689
|
+
path.resolve(prepared.archiveDir || options.archiveDir),
|
|
690
|
+
produced.package_path,
|
|
691
|
+
produced.manifest,
|
|
692
|
+
);
|
|
693
|
+
return {
|
|
694
|
+
...produced,
|
|
695
|
+
finalizer: {
|
|
696
|
+
run_identity: runIdentity,
|
|
697
|
+
lock_token: lock.token,
|
|
698
|
+
verified: true,
|
|
699
|
+
...verification,
|
|
700
|
+
},
|
|
701
|
+
};
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
591
705
|
module.exports = {
|
|
592
706
|
PRODUCER_VERSION,
|
|
593
707
|
PRODUCER,
|
|
@@ -605,5 +719,8 @@ module.exports = {
|
|
|
605
719
|
buildCanonicalFacts,
|
|
606
720
|
collectPackageFilePaths,
|
|
607
721
|
writeArchiveZip,
|
|
722
|
+
readStoredZipEntries,
|
|
723
|
+
verifyArchivePackage,
|
|
608
724
|
materializeArchivePackage,
|
|
725
|
+
finalizeArchivePackageAfterReports,
|
|
609
726
|
};
|
|
@@ -5,6 +5,10 @@ const path = require('path');
|
|
|
5
5
|
const { parseChangeArtifacts } = require('./artifact-parser.cjs');
|
|
6
6
|
const { normalizeFacts } = require('./normalizer.cjs');
|
|
7
7
|
const { DIAGNOSTIC_CODES } = require('./schema.cjs');
|
|
8
|
+
const {
|
|
9
|
+
ONTOLOGY_STATE_FILES,
|
|
10
|
+
resolveOntologyStateFile,
|
|
11
|
+
} = require('./ontology-paths.cjs');
|
|
8
12
|
|
|
9
13
|
function catalogDiagnostic(code, message, context = {}) {
|
|
10
14
|
return {
|
|
@@ -97,11 +101,14 @@ function validateArchiveCandidate(candidate, facts) {
|
|
|
97
101
|
return { eligible: false, snapshot, manifest, diagnostics };
|
|
98
102
|
}
|
|
99
103
|
|
|
100
|
-
const artifactIndexPath =
|
|
104
|
+
const artifactIndexPath = resolveOntologyStateFile(
|
|
105
|
+
candidate.changeDir,
|
|
106
|
+
ONTOLOGY_STATE_FILES.artifactIndex,
|
|
107
|
+
);
|
|
101
108
|
if (!fs.existsSync(artifactIndexPath)) {
|
|
102
109
|
diagnostics.push(catalogDiagnostic(
|
|
103
110
|
DIAGNOSTIC_CODES.ARCHIVE_NOT_CONFIRMED,
|
|
104
|
-
`Archive 缺少 change 目录下的 artifact-index.json: ${candidate.change}`,
|
|
111
|
+
`Archive 缺少 change 目录下的 ontology/artifact-index.json: ${candidate.change}`,
|
|
105
112
|
{ file: artifactIndexPath, suggestion: '通过 KLD-SDD reconcile/archive 在 change 目录生成工作态 JSON 后再归档' },
|
|
106
113
|
));
|
|
107
114
|
return { eligible: false, snapshot, manifest, diagnostics };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const ONTOLOGY_STATE_DIR = 'ontology';
|
|
7
|
+
|
|
8
|
+
const ONTOLOGY_STATE_FILES = {
|
|
9
|
+
working: 'working-ontology.json',
|
|
10
|
+
diagnostics: 'diagnostics.json',
|
|
11
|
+
fileIndex: 'file-index.json',
|
|
12
|
+
artifactIndex: 'artifact-index.json',
|
|
13
|
+
identities: 'ontology-identities.json',
|
|
14
|
+
continuityResolution: 'continuity-resolution.json',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function ontologyStateRelative(basename) {
|
|
18
|
+
return `${ONTOLOGY_STATE_DIR}/${basename}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function changeOntologyFilePaths(changeDir) {
|
|
22
|
+
const resolved = path.resolve(changeDir);
|
|
23
|
+
const ontologyDir = path.join(resolved, ONTOLOGY_STATE_DIR);
|
|
24
|
+
return {
|
|
25
|
+
changeDir: resolved,
|
|
26
|
+
ontologyDir,
|
|
27
|
+
working: path.join(ontologyDir, ONTOLOGY_STATE_FILES.working),
|
|
28
|
+
diagnostics: path.join(ontologyDir, ONTOLOGY_STATE_FILES.diagnostics),
|
|
29
|
+
fileIndex: path.join(ontologyDir, ONTOLOGY_STATE_FILES.fileIndex),
|
|
30
|
+
artifactIndex: path.join(ontologyDir, ONTOLOGY_STATE_FILES.artifactIndex),
|
|
31
|
+
continuityResolution: path.join(ontologyDir, ONTOLOGY_STATE_FILES.continuityResolution),
|
|
32
|
+
identities: path.join(ontologyDir, ONTOLOGY_STATE_FILES.identities),
|
|
33
|
+
artifactDir: path.join(resolved, 'artifacts'),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveOntologyStateFile(changeDir, basename) {
|
|
38
|
+
const resolved = path.resolve(changeDir);
|
|
39
|
+
const modern = path.join(resolved, ONTOLOGY_STATE_DIR, basename);
|
|
40
|
+
if (fs.existsSync(modern)) return modern;
|
|
41
|
+
const legacy = path.join(resolved, basename);
|
|
42
|
+
if (fs.existsSync(legacy)) return legacy;
|
|
43
|
+
return modern;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function resolveRevisionBundlePaths(changeDir) {
|
|
47
|
+
const preferred = changeOntologyFilePaths(changeDir);
|
|
48
|
+
return {
|
|
49
|
+
...preferred,
|
|
50
|
+
working: resolveOntologyStateFile(changeDir, ONTOLOGY_STATE_FILES.working),
|
|
51
|
+
diagnostics: resolveOntologyStateFile(changeDir, ONTOLOGY_STATE_FILES.diagnostics),
|
|
52
|
+
fileIndex: resolveOntologyStateFile(changeDir, ONTOLOGY_STATE_FILES.fileIndex),
|
|
53
|
+
artifactIndex: resolveOntologyStateFile(changeDir, ONTOLOGY_STATE_FILES.artifactIndex),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function removeLegacyOntologyStateFiles(changeDir) {
|
|
58
|
+
const resolved = path.resolve(changeDir);
|
|
59
|
+
for (const basename of Object.values(ONTOLOGY_STATE_FILES)) {
|
|
60
|
+
const legacy = path.join(resolved, basename);
|
|
61
|
+
if (fs.existsSync(legacy)) fs.rmSync(legacy, { force: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
ONTOLOGY_STATE_DIR,
|
|
67
|
+
ONTOLOGY_STATE_FILES,
|
|
68
|
+
ontologyStateRelative,
|
|
69
|
+
changeOntologyFilePaths,
|
|
70
|
+
resolveOntologyStateFile,
|
|
71
|
+
resolveRevisionBundlePaths,
|
|
72
|
+
removeLegacyOntologyStateFiles,
|
|
73
|
+
};
|
|
@@ -5,98 +5,38 @@ const path = require('path');
|
|
|
5
5
|
const sddConfig = require('./sdd-config.cjs');
|
|
6
6
|
const workspaceLayout = require('./workspace-layout.cjs');
|
|
7
7
|
|
|
8
|
-
function
|
|
9
|
-
const
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
);
|
|
8
|
+
function resolveHint(anchorRoot) {
|
|
9
|
+
const hintPath = path.join(anchorRoot, '.sdd-spec-root');
|
|
10
|
+
if (!fs.existsSync(hintPath)) return null;
|
|
11
|
+
const hint = fs.readFileSync(hintPath, 'utf8').trim();
|
|
12
|
+
if (!hint) return null;
|
|
13
|
+
const resolved = path.resolve(anchorRoot, hint);
|
|
14
|
+
return fs.existsSync(resolved) ? resolved : null;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* 解析顺序:
|
|
22
|
-
* 1. start 本身已是 spec 包根
|
|
23
|
-
* 2. KLD_SDD_SPEC_ROOT(绝对或相对 start)
|
|
24
|
-
* 3. 单仓 layout: mono → spec_path / 子目录 *-sdd-specs
|
|
25
|
-
* 4. 工作区 layout → specRepo
|
|
26
|
-
* 5. start 下嵌套包裹包
|
|
27
|
-
* 6. 代码仓 git config sdd.specPath(含仓内 nested 包裹)
|
|
28
|
-
* 7. 向上最多 3 层找 mono / 嵌套包(Git 根、工作区根常见)
|
|
29
|
-
* 8. 回退 start
|
|
30
|
-
*/
|
|
31
|
-
function resolveSpecProjectRoot(startDir = process.cwd(), options = {}) {
|
|
32
|
-
const start = path.resolve(startDir || process.cwd());
|
|
33
|
-
if (options.skipResolve || options.forCodeRepo) return start;
|
|
34
|
-
if (isSpecPackageRoot(start)) return start;
|
|
17
|
+
function resolveSpecProjectRoot(projectRoot = process.cwd()) {
|
|
18
|
+
const root = path.resolve(projectRoot);
|
|
19
|
+
const hinted = resolveHint(root);
|
|
20
|
+
if (hinted) return hinted;
|
|
35
21
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const candidate = path.isAbsolute(envRoot) ? envRoot : path.resolve(start, envRoot);
|
|
39
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
22
|
+
if (sddConfig.isMonoLayout(root)) {
|
|
23
|
+
return sddConfig.resolveMonoSpecRoot(root);
|
|
40
24
|
}
|
|
41
25
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (fs.existsSync(monoRoot)) return monoRoot;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const layout = workspaceLayout.detectWorkspaceLayout(start);
|
|
48
|
-
if (layout.specRepo && fs.existsSync(layout.specRepo.abs)) {
|
|
49
|
-
return layout.specRepo.abs;
|
|
50
|
-
}
|
|
26
|
+
const layout = workspaceLayout.detectWorkspaceLayout(root);
|
|
27
|
+
if (layout.specRepo) return layout.specRepo.abs;
|
|
51
28
|
|
|
52
|
-
const nested = workspaceLayout.findNestedSpecPackage(
|
|
29
|
+
const nested = workspaceLayout.findNestedSpecPackage(root);
|
|
53
30
|
if (nested) return nested.abs;
|
|
54
31
|
|
|
55
|
-
|
|
56
|
-
if (specPathResult.ok) {
|
|
57
|
-
const valid = sddConfig.validateSpecPath(specPathResult.path, { allowNestedPackage: true });
|
|
58
|
-
if (valid.ok) return valid.path;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
let dir = start;
|
|
62
|
-
for (let depth = 0; depth < 3; depth += 1) {
|
|
63
|
-
const parent = path.dirname(dir);
|
|
64
|
-
if (parent === dir) break;
|
|
65
|
-
if (sddConfig.isMonoLayout(parent)) {
|
|
66
|
-
const monoRoot = sddConfig.resolveMonoSpecRoot(parent);
|
|
67
|
-
if (fs.existsSync(monoRoot)) return monoRoot;
|
|
68
|
-
}
|
|
69
|
-
const parentNested = workspaceLayout.findNestedSpecPackage(parent);
|
|
70
|
-
if (parentNested) return parentNested.abs;
|
|
71
|
-
const parentLayout = workspaceLayout.detectWorkspaceLayout(parent);
|
|
72
|
-
if (parentLayout.specRepo && fs.existsSync(parentLayout.specRepo.abs)) {
|
|
73
|
-
return parentLayout.specRepo.abs;
|
|
74
|
-
}
|
|
75
|
-
dir = parent;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return start;
|
|
32
|
+
return root;
|
|
79
33
|
}
|
|
80
34
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
* '.' 或未显式绝对 spec 根时走 resolve;link-spec 等代码仓命令传 forCodeRepo:true。
|
|
84
|
-
*/
|
|
85
|
-
function resolveProjectArg(projectArg, startDir = process.cwd(), options = {}) {
|
|
86
|
-
const raw = projectArg == null || projectArg === '' ? '.' : String(projectArg);
|
|
87
|
-
const resolved = path.resolve(startDir, raw);
|
|
88
|
-
if (options.forCodeRepo) return resolved;
|
|
89
|
-
if (raw !== '.' && raw !== './' && path.isAbsolute(raw)) {
|
|
90
|
-
return isSpecPackageRoot(resolved) ? resolved : resolveSpecProjectRoot(resolved, options);
|
|
91
|
-
}
|
|
92
|
-
if (raw !== '.' && raw !== './') {
|
|
93
|
-
if (isSpecPackageRoot(resolved)) return resolved;
|
|
94
|
-
}
|
|
95
|
-
return resolveSpecProjectRoot(startDir, options);
|
|
35
|
+
function resolveProjectArg(projectArg = '.', cwd = process.cwd()) {
|
|
36
|
+
return resolveSpecProjectRoot(path.resolve(cwd, projectArg));
|
|
96
37
|
}
|
|
97
38
|
|
|
98
39
|
module.exports = {
|
|
99
|
-
isSpecPackageRoot,
|
|
100
|
-
resolveSpecProjectRoot,
|
|
101
40
|
resolveProjectArg,
|
|
41
|
+
resolveSpecProjectRoot,
|
|
102
42
|
};
|
|
@@ -20,6 +20,14 @@ const {
|
|
|
20
20
|
assertChangeLock,
|
|
21
21
|
releaseChangeLock,
|
|
22
22
|
} = require('./change-lock.cjs');
|
|
23
|
+
const {
|
|
24
|
+
ONTOLOGY_STATE_FILES,
|
|
25
|
+
ontologyStateRelative,
|
|
26
|
+
changeOntologyFilePaths,
|
|
27
|
+
resolveOntologyStateFile,
|
|
28
|
+
resolveRevisionBundlePaths,
|
|
29
|
+
removeLegacyOntologyStateFiles,
|
|
30
|
+
} = require('./ontology-paths.cjs');
|
|
23
31
|
|
|
24
32
|
function sha256(value) {
|
|
25
33
|
return crypto.createHash('sha256').update(value).digest('hex');
|
|
@@ -31,15 +39,7 @@ function resolveChangeDir(projectRoot, changeName) {
|
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
function changeOntologyPaths(projectRoot, changeName) {
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
changeDir,
|
|
37
|
-
working: path.join(changeDir, 'working-ontology.json'),
|
|
38
|
-
diagnostics: path.join(changeDir, 'diagnostics.json'),
|
|
39
|
-
fileIndex: path.join(changeDir, 'file-index.json'),
|
|
40
|
-
artifactIndex: path.join(changeDir, 'artifact-index.json'),
|
|
41
|
-
artifactDir: path.join(changeDir, 'artifacts'),
|
|
42
|
-
};
|
|
42
|
+
return changeOntologyFilePaths(resolveChangeDir(projectRoot, changeName));
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function statePaths(projectRoot, changeName) {
|
|
@@ -160,14 +160,16 @@ function commitRevision(projectRoot, changeName, result, lock) {
|
|
|
160
160
|
assertChangeLock(lock);
|
|
161
161
|
const basePaths = statePaths(projectRoot, changeName);
|
|
162
162
|
const paths = changeOntologyPaths(projectRoot, changeName);
|
|
163
|
+
const readablePaths = resolveRevisionBundlePaths(paths.changeDir);
|
|
163
164
|
fs.mkdirSync(basePaths.dir, { recursive: true });
|
|
164
165
|
fs.mkdirSync(paths.changeDir, { recursive: true });
|
|
166
|
+
fs.mkdirSync(paths.ontologyDir, { recursive: true });
|
|
165
167
|
|
|
166
|
-
if (fs.existsSync(
|
|
167
|
-
const existing = readJson(
|
|
168
|
+
if (fs.existsSync(readablePaths.working)) {
|
|
169
|
+
const existing = readJson(readablePaths.working, 'SEM_STATE_CORRUPT');
|
|
168
170
|
if (existing.revision === result.revision) {
|
|
169
|
-
readRevisionBundle(
|
|
170
|
-
const artifactIndex = readJson(
|
|
171
|
+
readRevisionBundle(readablePaths, result.revision);
|
|
172
|
+
const artifactIndex = readJson(readablePaths.artifactIndex, 'SEM_STATE_CORRUPT');
|
|
171
173
|
return {
|
|
172
174
|
changed: false,
|
|
173
175
|
paths: {
|
|
@@ -225,6 +227,7 @@ function commitRevision(projectRoot, changeName, result, lock) {
|
|
|
225
227
|
atomicWriteIfChanged(path.join(paths.changeDir, artifactFile.path), fs.readFileSync(stagedPath, 'utf8'));
|
|
226
228
|
}
|
|
227
229
|
pruneStaleArtifacts(paths.changeDir, artifactBundle.index);
|
|
230
|
+
removeLegacyOntologyStateFiles(paths.changeDir);
|
|
228
231
|
fs.rmSync(stagingDir, { recursive: true, force: true });
|
|
229
232
|
|
|
230
233
|
const pointer = {
|
|
@@ -232,7 +235,7 @@ function commitRevision(projectRoot, changeName, result, lock) {
|
|
|
232
235
|
change: safeChangeName(changeName),
|
|
233
236
|
revision: result.revision,
|
|
234
237
|
change_dir: path.relative(path.resolve(projectRoot), paths.changeDir).replace(/\\/g, '/'),
|
|
235
|
-
artifact_index:
|
|
238
|
+
artifact_index: ontologyStateRelative(ONTOLOGY_STATE_FILES.artifactIndex),
|
|
236
239
|
};
|
|
237
240
|
assertChangeLock(lock);
|
|
238
241
|
const changed = atomicWriteIfChanged(basePaths.current, pointer);
|
|
@@ -272,9 +275,9 @@ function scanChange(projectRoot, changeName, options = {}) {
|
|
|
272
275
|
identityHistory,
|
|
273
276
|
effectiveGraph,
|
|
274
277
|
projectRoot,
|
|
275
|
-
continuityResolutionPath:
|
|
278
|
+
continuityResolutionPath: resolveOntologyStateFile(
|
|
276
279
|
parsed.changeDir,
|
|
277
|
-
|
|
280
|
+
ONTOLOGY_STATE_FILES.continuityResolution,
|
|
278
281
|
),
|
|
279
282
|
});
|
|
280
283
|
const reviewStatus = options.markPending && validation.valid ? 'pending' : 'draft';
|
|
@@ -358,16 +361,17 @@ function reconcileChange(projectRoot, changeName, options = {}) {
|
|
|
358
361
|
function readWorkingState(projectRoot, changeName) {
|
|
359
362
|
const basePaths = statePaths(projectRoot, changeName);
|
|
360
363
|
const paths = changeOntologyPaths(projectRoot, changeName);
|
|
364
|
+
const readablePaths = resolveRevisionBundlePaths(paths.changeDir);
|
|
361
365
|
if (fs.existsSync(basePaths.current)) {
|
|
362
366
|
const pointer = readJson(basePaths.current, 'SEM_STATE_POINTER_CORRUPT');
|
|
363
367
|
if (!pointer.revision || pointer.change !== safeChangeName(changeName)) {
|
|
364
368
|
throw stateError('SEM_STATE_POINTER_CORRUPT', `current.json 不属于 Change ${changeName}`);
|
|
365
369
|
}
|
|
366
|
-
return readRevisionBundle(
|
|
370
|
+
return readRevisionBundle(readablePaths, pointer.revision).working;
|
|
367
371
|
}
|
|
368
|
-
if (!fs.existsSync(
|
|
369
|
-
const working = readJson(
|
|
370
|
-
readRevisionBundle(
|
|
372
|
+
if (!fs.existsSync(readablePaths.working)) return null;
|
|
373
|
+
const working = readJson(readablePaths.working, 'SEM_STATE_CORRUPT');
|
|
374
|
+
readRevisionBundle(readablePaths, working.revision);
|
|
371
375
|
return working;
|
|
372
376
|
}
|
|
373
377
|
|
|
@@ -396,9 +400,12 @@ function createArchiveSnapshot(projectRoot, changeName, archiveDir, inputState)
|
|
|
396
400
|
if (!state.facts_hash || state.facts_hash !== actualFacts.facts_hash) {
|
|
397
401
|
throw new Error(`归档正文与待确认本体事实不一致: ${changeName}`);
|
|
398
402
|
}
|
|
399
|
-
const artifactIndexPath =
|
|
403
|
+
const artifactIndexPath = resolveOntologyStateFile(
|
|
404
|
+
targetDir,
|
|
405
|
+
ONTOLOGY_STATE_FILES.artifactIndex,
|
|
406
|
+
);
|
|
400
407
|
if (!fs.existsSync(artifactIndexPath)) {
|
|
401
|
-
throw new Error(`归档目录缺少 artifact-index.json: ${targetDir}`);
|
|
408
|
+
throw new Error(`归档目录缺少 ontology/artifact-index.json: ${targetDir}`);
|
|
402
409
|
}
|
|
403
410
|
const artifactIndex = readJson(artifactIndexPath, 'SEM_STATE_CORRUPT');
|
|
404
411
|
if (artifactIndex.revision !== state.revision) {
|
|
@@ -468,4 +475,7 @@ module.exports = {
|
|
|
468
475
|
reconcileChange,
|
|
469
476
|
readWorkingState,
|
|
470
477
|
createArchiveSnapshot,
|
|
478
|
+
resolveOntologyStateFile,
|
|
479
|
+
resolveRevisionBundlePaths,
|
|
480
|
+
ONTOLOGY_STATE_FILES,
|
|
471
481
|
};
|
|
@@ -4,15 +4,20 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { SCHEMA_VERSION } = require('./schema.cjs');
|
|
6
6
|
const { generateUuidV7, isValidUuidV7 } = require('./id.cjs');
|
|
7
|
+
const {
|
|
8
|
+
ONTOLOGY_STATE_FILES,
|
|
9
|
+
ontologyStateRelative,
|
|
10
|
+
resolveOntologyStateFile,
|
|
11
|
+
} = require('./ontology-paths.cjs');
|
|
7
12
|
|
|
8
|
-
const REGISTRY_FILE =
|
|
13
|
+
const REGISTRY_FILE = ontologyStateRelative(ONTOLOGY_STATE_FILES.identities);
|
|
9
14
|
|
|
10
15
|
function registryPath(changeDir) {
|
|
11
16
|
return path.join(path.resolve(changeDir), REGISTRY_FILE);
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
function readRegistry(changeDir) {
|
|
15
|
-
const filePath =
|
|
20
|
+
const filePath = resolveOntologyStateFile(changeDir, ONTOLOGY_STATE_FILES.identities);
|
|
16
21
|
if (!fs.existsSync(filePath)) {
|
|
17
22
|
return { schema_version: SCHEMA_VERSION, identities: {} };
|
|
18
23
|
}
|
|
@@ -35,6 +40,10 @@ function writeRegistry(changeDir, registry) {
|
|
|
35
40
|
const tempPath = `${filePath}.${process.pid}.tmp`;
|
|
36
41
|
fs.writeFileSync(tempPath, content, 'utf8');
|
|
37
42
|
fs.renameSync(tempPath, filePath);
|
|
43
|
+
const legacy = path.join(path.resolve(changeDir), ONTOLOGY_STATE_FILES.identities);
|
|
44
|
+
if (legacy !== filePath && fs.existsSync(legacy)) {
|
|
45
|
+
fs.rmSync(legacy, { force: true });
|
|
46
|
+
}
|
|
38
47
|
return true;
|
|
39
48
|
}
|
|
40
49
|
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
3
5
|
const {
|
|
4
6
|
DIAGNOSTIC_CODES,
|
|
5
7
|
normalizeProfile,
|
|
6
8
|
relationDefinition,
|
|
7
9
|
} = require('./schema.cjs');
|
|
8
10
|
const { isValidEntityId, isValidUuid } = require('./id.cjs');
|
|
11
|
+
const {
|
|
12
|
+
ONTOLOGY_STATE_FILES,
|
|
13
|
+
ontologyStateRelative,
|
|
14
|
+
resolveOntologyStateFile,
|
|
15
|
+
} = require('./ontology-paths.cjs');
|
|
9
16
|
|
|
10
17
|
function diagnostic(code, severity, message, context = {}) {
|
|
11
18
|
return {
|
|
@@ -350,18 +357,19 @@ function validateContinuityAndExternalRefs(facts, diagnostics, options = {}) {
|
|
|
350
357
|
const kind = String(continuity.kind || '').toLowerCase();
|
|
351
358
|
const resolutionPath = options.continuityResolutionPath
|
|
352
359
|
|| (facts.change
|
|
353
|
-
?
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
360
|
+
? resolveOntologyStateFile(
|
|
361
|
+
path.join(
|
|
362
|
+
options.projectRoot || process.cwd(),
|
|
363
|
+
'openspec',
|
|
364
|
+
'changes',
|
|
365
|
+
facts.change,
|
|
366
|
+
),
|
|
367
|
+
ONTOLOGY_STATE_FILES.continuityResolution,
|
|
359
368
|
)
|
|
360
369
|
: '');
|
|
361
370
|
let resolution = null;
|
|
362
371
|
if (resolutionPath) {
|
|
363
372
|
try {
|
|
364
|
-
const fs = require('fs');
|
|
365
373
|
if (fs.existsSync(resolutionPath)) {
|
|
366
374
|
resolution = JSON.parse(fs.readFileSync(resolutionPath, 'utf8'));
|
|
367
375
|
}
|
|
@@ -392,7 +400,7 @@ function validateContinuityAndExternalRefs(facts, diagnostics, options = {}) {
|
|
|
392
400
|
'error',
|
|
393
401
|
'Continuity=iteration 但 continuity-resolution.json 缺失或仍为 pending',
|
|
394
402
|
{
|
|
395
|
-
file: resolutionPath ||
|
|
403
|
+
file: resolutionPath || ontologyStateRelative(ONTOLOGY_STATE_FILES.continuityResolution),
|
|
396
404
|
suggestion: '在 propose/spec 完成 A/B/C 决议后再 check',
|
|
397
405
|
},
|
|
398
406
|
));
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const { resolveRuntimeMetadata } = require('../runtime-metadata.cjs');
|
|
6
|
+
const { ontologyStateRelative, ONTOLOGY_STATE_FILES } = require('./ontology-paths.cjs');
|
|
5
7
|
|
|
6
8
|
const WORKING_ARTIFACT_SCHEMA_VERSION = 'kld-sdd-working-artifact-facts/v1';
|
|
7
9
|
const ARTIFACT_INDEX_SCHEMA_VERSION = 'kld-sdd-working-artifact-index/v1';
|
|
@@ -141,7 +143,7 @@ function compareFacts(left, right) {
|
|
|
141
143
|
|
|
142
144
|
function buildWorkingArtifactFacts(result, options = {}) {
|
|
143
145
|
const state = result.state || {};
|
|
144
|
-
const producerVersion = String(options.producerVersion ||
|
|
146
|
+
const producerVersion = String(options.producerVersion || resolveRuntimeMetadata().version);
|
|
145
147
|
const reviewStatus = state.review_status === 'pending' ? 'pending' : 'draft';
|
|
146
148
|
const rawEntities = Array.isArray(state.entities) ? state.entities : [];
|
|
147
149
|
const entityByAnchor = new Map(rawEntities.map((entity) => [
|
|
@@ -227,7 +229,7 @@ function buildWorkingArtifactFacts(result, options = {}) {
|
|
|
227
229
|
review_status: reviewStatus,
|
|
228
230
|
valid: Boolean(result.valid),
|
|
229
231
|
revision: result.revision,
|
|
230
|
-
working_ontology_path:
|
|
232
|
+
working_ontology_path: ontologyStateRelative(ONTOLOGY_STATE_FILES.working),
|
|
231
233
|
artifacts: artifactIndex,
|
|
232
234
|
},
|
|
233
235
|
files: artifactFiles,
|