kibi-cli 0.17.0 → 0.19.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/cli-json-command.d.ts +1 -2
- package/dist/cli-json-command.d.ts.map +1 -1
- package/dist/cli-json-command.js +88 -44
- package/dist/cli-operation-loader.d.ts +3 -0
- package/dist/cli-operation-loader.d.ts.map +1 -0
- package/dist/cli-operation-loader.js +58 -0
- package/dist/cli-operation-metadata.d.ts +82 -0
- package/dist/cli-operation-metadata.d.ts.map +1 -0
- package/dist/cli-operation-metadata.js +106 -0
- package/dist/cli-protocol.d.ts +1 -1
- package/dist/cli-protocol.d.ts.map +1 -1
- package/dist/cli-protocol.js +4 -3
- package/dist/cli-register-foundation.d.ts.map +1 -1
- package/dist/cli-register-foundation.js +14 -17
- package/dist/cli-register-json.d.ts +3 -0
- package/dist/cli-register-json.d.ts.map +1 -0
- package/dist/cli-register-json.js +35 -0
- package/dist/cli-register-maintenance.d.ts.map +1 -1
- package/dist/cli-register-maintenance.js +41 -8
- package/dist/cli-register-reporting.d.ts.map +1 -1
- package/dist/cli-register-reporting.js +18 -14
- package/dist/cli-register-skills.d.ts.map +1 -1
- package/dist/cli-register-skills.js +8 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -2
- package/dist/commands/aggregated-checks.d.ts.map +1 -1
- package/dist/commands/aggregated-checks.js +1 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +30 -12
- package/dist/commands/coverage.d.ts +5 -0
- package/dist/commands/coverage.d.ts.map +1 -1
- package/dist/commands/coverage.js +6 -1
- package/dist/commands/discovery-shared.d.ts +2 -1
- package/dist/commands/discovery-shared.d.ts.map +1 -1
- package/dist/commands/discovery-shared.js +35 -13
- package/dist/commands/discovery-table.d.ts.map +1 -1
- package/dist/commands/discovery-table.js +96 -5
- package/dist/commands/engine.d.ts +8 -0
- package/dist/commands/engine.d.ts.map +1 -0
- package/dist/commands/engine.js +100 -0
- package/dist/commands/gaps.js +1 -1
- package/dist/commands/graph.js +1 -1
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +3 -1
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/search.js +3 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/sync/cache.d.ts +12 -0
- package/dist/commands/sync/cache.d.ts.map +1 -1
- package/dist/commands/sync/cache.js +38 -1
- package/dist/commands/sync/persistence.d.ts +10 -1
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +276 -43
- package/dist/commands/sync/staging.d.ts +10 -1
- package/dist/commands/sync/staging.d.ts.map +1 -1
- package/dist/commands/sync/staging.js +84 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +521 -27
- package/dist/commands/usage-metrics.d.ts +66 -2
- package/dist/commands/usage-metrics.d.ts.map +1 -1
- package/dist/commands/usage-metrics.js +34 -24
- package/dist/commands/usage-remediation.d.ts +9 -0
- package/dist/commands/usage-remediation.d.ts.map +1 -0
- package/dist/commands/usage-remediation.js +52 -0
- package/dist/engine-daemon.d.ts +3 -0
- package/dist/engine-daemon.d.ts.map +1 -0
- package/dist/engine-daemon.js +34 -0
- package/dist/engine.d.ts +63 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +1256 -0
- package/dist/extractors/markdown.d.ts +8 -0
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +69 -2
- package/dist/extractors/relationships.d.ts +2 -0
- package/dist/extractors/relationships.d.ts.map +1 -1
- package/dist/extractors/relationships.js +15 -17
- package/dist/operations/modeling/logic-modeling.d.ts.map +1 -1
- package/dist/operations/modeling/logic-modeling.js +14 -5
- package/dist/operations/modeling/model-requirement.d.ts.map +1 -1
- package/dist/operations/modeling/model-requirement.js +27 -2
- package/dist/operations/modeling/predicate-applyplan.d.ts +1 -1
- package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-applyplan.js +18 -6
- package/dist/operations/modeling/predicate-inference.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-inference.js +5 -17
- package/dist/operations/modeling/predicate-loader.js +1 -1
- package/dist/operations/modeling/predicate-types.d.ts +9 -1
- package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
- package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
- package/dist/operations/modeling/suggest-predicates.js +35 -11
- package/dist/operations/mutation/audit.js +1 -1
- package/dist/operations/mutation/contradictions.d.ts +1 -1
- package/dist/operations/mutation/contradictions.d.ts.map +1 -1
- package/dist/operations/mutation/contradictions.js +23 -18
- package/dist/operations/mutation/serialization.d.ts.map +1 -1
- package/dist/operations/mutation/serialization.js +6 -3
- package/dist/operations/mutation/upsert.d.ts +3 -1
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +51 -12
- package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
- package/dist/operations/mutation/validate-upsert.js +18 -2
- package/dist/operations/mutation/validation.d.ts.map +1 -1
- package/dist/operations/mutation/validation.js +8 -0
- package/dist/operations/semantic-advisor/analysis-receipt.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/analysis-receipt.js +7 -1
- package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/analyze-prose.js +76 -61
- package/dist/operations/semantic-advisor/clauses.d.ts +1 -0
- package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/clauses.js +7 -7
- package/dist/operations/semantic-advisor/ingestion-boundary.d.ts +18 -0
- package/dist/operations/semantic-advisor/ingestion-boundary.d.ts.map +1 -0
- package/dist/operations/semantic-advisor/ingestion-boundary.js +167 -0
- package/dist/operations/semantic-advisor/shared.d.ts +8 -0
- package/dist/operations/semantic-advisor/shared.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/shared.js +37 -3
- package/dist/operations/semantic-advisor/types.d.ts +5 -0
- package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
- package/dist/prolog/codec.d.ts.map +1 -1
- package/dist/prolog/codec.js +9 -2
- package/dist/prolog.d.ts +14 -0
- package/dist/prolog.d.ts.map +1 -1
- package/dist/prolog.js +279 -69
- package/dist/public/diagnostic-usage.d.ts +17 -0
- package/dist/public/diagnostic-usage.d.ts.map +1 -0
- package/dist/public/diagnostic-usage.js +152 -0
- package/dist/public/distribution-parity.d.ts +90 -0
- package/dist/public/distribution-parity.d.ts.map +1 -0
- package/dist/public/distribution-parity.js +367 -0
- package/dist/public/impact/full-kb-quality.d.ts +2 -0
- package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
- package/dist/public/impact/full-kb-quality.js +53 -1
- package/dist/public/impact-diagnostics.d.ts +2 -0
- package/dist/public/impact-diagnostics.d.ts.map +1 -1
- package/dist/public/impact-diagnostics.js +1 -0
- package/dist/public/operations/catalog.d.ts +151 -6
- package/dist/public/operations/catalog.d.ts.map +1 -1
- package/dist/public/operations/check-executor.d.ts.map +1 -1
- package/dist/public/operations/check-executor.js +4 -0
- package/dist/public/operations/discovery-entities.d.ts.map +1 -1
- package/dist/public/operations/discovery-entities.js +3 -2
- package/dist/public/operations/discovery-executors.d.ts +6 -0
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +58 -5
- package/dist/public/operations/index.d.ts +4 -0
- package/dist/public/operations/index.d.ts.map +1 -1
- package/dist/public/operations/index.js +4 -0
- package/dist/public/operations/legacy-migration-plan.d.ts +113 -0
- package/dist/public/operations/legacy-migration-plan.d.ts.map +1 -0
- package/dist/public/operations/legacy-migration-plan.js +409 -0
- package/dist/public/operations/node-ports.d.ts.map +1 -1
- package/dist/public/operations/node-ports.js +90 -0
- package/dist/public/operations/prolog-json.d.ts.map +1 -1
- package/dist/public/operations/prolog-json.js +14 -4
- package/dist/public/operations/repair-plan.d.ts +184 -0
- package/dist/public/operations/repair-plan.d.ts.map +1 -0
- package/dist/public/operations/repair-plan.js +397 -0
- package/dist/public/operations/runtime-types.d.ts +38 -0
- package/dist/public/operations/runtime-types.d.ts.map +1 -1
- package/dist/public/operations/specs/check.d.ts +2 -2
- package/dist/public/operations/specs/check.js +2 -2
- package/dist/public/operations/specs/discovery.d.ts +1 -1
- package/dist/public/operations/specs/discovery.js +1 -1
- package/dist/public/operations/specs/modeling.d.ts +19 -1
- package/dist/public/operations/specs/modeling.d.ts.map +1 -1
- package/dist/public/operations/specs/modeling.js +16 -1
- package/dist/public/operations/specs/mutation-schemas.d.ts +96 -1
- package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +40 -1
- package/dist/public/operations/specs/reporting.d.ts +42 -1
- package/dist/public/operations/specs/reporting.d.ts.map +1 -1
- package/dist/public/operations/specs/reporting.js +70 -6
- package/dist/public/operations/specs/semantic.js +1 -1
- package/dist/public/operations/specs/upsert.d.ts +96 -1
- package/dist/public/operations/specs/upsert.d.ts.map +1 -1
- package/dist/public/operations/workspace-snapshot.d.ts +10 -0
- package/dist/public/operations/workspace-snapshot.d.ts.map +1 -0
- package/dist/public/operations/workspace-snapshot.js +20 -0
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +43 -1
- package/dist/public/skills/init-kibi/SKILL.md +2 -2
- package/dist/public/skills/kibi-freshness/SKILL.md +2 -2
- package/dist/public/skills/kibi-traceability/SKILL.md +8 -2
- package/dist/public/skills/kibi-usage/SKILL.md +12 -6
- package/dist/public/skills/kibi-usage/resources/fact-lanes.md +1 -1
- package/dist/public/skills/kibi-usage/resources/logic-ir.md +1 -1
- package/dist/public/skills/kibi-usage/resources/operation-access.md +1 -1
- package/dist/public/skills/kibi-usage/resources/workflows.md +19 -2
- package/dist/public/telemetry-acceptance.d.ts +89 -0
- package/dist/public/telemetry-acceptance.d.ts.map +1 -0
- package/dist/public/telemetry-acceptance.js +597 -0
- package/dist/public/telemetry-remediation.d.ts +33 -0
- package/dist/public/telemetry-remediation.d.ts.map +1 -0
- package/dist/public/telemetry-remediation.js +257 -0
- package/dist/public/verification-receipt.d.ts +75 -0
- package/dist/public/verification-receipt.d.ts.map +1 -0
- package/dist/public/verification-receipt.js +129 -0
- package/dist/runtime/cli-runtime.d.ts.map +1 -1
- package/dist/runtime/cli-runtime.js +33 -37
- package/dist/schemas/entity.schema.json +96 -2
- package/dist/traceability/temp-kb.d.ts.map +1 -1
- package/dist/traceability/temp-kb.js +36 -3
- package/dist/types/entities.d.ts +1 -0
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/utils/rule-registry.d.ts +2 -0
- package/dist/utils/rule-registry.d.ts.map +1 -1
- package/package.json +36 -4
- package/src/public/diagnostic-usage.ts +206 -0
- package/src/public/distribution-parity.ts +580 -0
- package/src/public/impact/full-kb-quality.ts +74 -1
- package/src/public/impact-diagnostics.ts +16 -0
- package/src/public/operations/check-executor.ts +4 -0
- package/src/public/operations/discovery-entities.ts +3 -2
- package/src/public/operations/discovery-executors.ts +68 -5
- package/src/public/operations/index.ts +4 -0
- package/src/public/operations/legacy-migration-plan.ts +688 -0
- package/src/public/operations/node-ports.ts +109 -0
- package/src/public/operations/prolog-json.ts +15 -3
- package/src/public/operations/repair-plan.ts +562 -0
- package/src/public/operations/runtime-types.ts +47 -0
- package/src/public/operations/specs/check.ts +2 -2
- package/src/public/operations/specs/discovery.ts +1 -1
- package/src/public/operations/specs/modeling.ts +19 -1
- package/src/public/operations/specs/mutation-schemas.ts +47 -1
- package/src/public/operations/specs/reporting.ts +91 -6
- package/src/public/operations/specs/semantic.ts +1 -1
- package/src/public/operations/workspace-snapshot.ts +28 -0
- package/src/public/schemas/entity.ts +44 -1
- package/src/public/skills/init-kibi/SKILL.md +2 -2
- package/src/public/skills/kibi-freshness/SKILL.md +2 -2
- package/src/public/skills/kibi-traceability/SKILL.md +8 -2
- package/src/public/skills/kibi-usage/SKILL.md +12 -6
- package/src/public/skills/kibi-usage/resources/fact-lanes.md +1 -1
- package/src/public/skills/kibi-usage/resources/logic-ir.md +1 -1
- package/src/public/skills/kibi-usage/resources/operation-access.md +1 -1
- package/src/public/skills/kibi-usage/resources/workflows.md +19 -2
- package/src/public/telemetry-acceptance.ts +859 -0
- package/src/public/telemetry-remediation.ts +462 -0
- package/src/public/verification-receipt.ts +170 -0
- package/src/schemas/entity.schema.json +96 -2
package/dist/commands/sync.js
CHANGED
|
@@ -16,26 +16,103 @@
|
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
import { execSync } from "node:child_process";
|
|
19
|
-
import { existsSync } from "node:fs";
|
|
19
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
20
20
|
import * as path from "node:path";
|
|
21
21
|
import { branchErrorToDiagnostic, createDocsNotIndexedDiagnostic, createInvalidAuthoringDiagnostic, createKbMissingDiagnostic, formatSyncSummary, } from "../diagnostics.js";
|
|
22
|
+
import { EngineClient, engineSocketPath, ensureJournaledBranchStoreAsync, fsyncJournaledBranchStore, } from "../engine.js";
|
|
22
23
|
import { isCliDebugEnabled } from "../env.js";
|
|
23
|
-
import {
|
|
24
|
+
import { extractRelationshipShard, validateRelationships, } from "../extractors/relationships.js";
|
|
25
|
+
import { analyzeSemanticAdvisorInput } from "../operations/semantic-advisor/analyze-prose.js";
|
|
26
|
+
import { validateSemanticInventoryBoundary } from "../operations/semantic-advisor/ingestion-boundary.js";
|
|
24
27
|
import { PrologProcess } from "../prolog.js";
|
|
25
28
|
import { resolveActiveBranch, } from "../utils/branch-resolver.js";
|
|
26
29
|
import { loadSyncConfig } from "../utils/config.js";
|
|
27
|
-
import { SYNC_CACHE_TTL_MS, SYNC_CACHE_VERSION, hashFile, readSyncCache, toCacheKey, writeSyncCache, } from "./sync/cache.js";
|
|
30
|
+
import { SYNC_CACHE_TTL_MS, SYNC_CACHE_VERSION, hashFile, hashNormalized, readSyncCache, toCacheKey, writeSyncCache, } from "./sync/cache.js";
|
|
28
31
|
import { discoverSourceFiles, } from "./sync/discovery.js";
|
|
29
32
|
import { processExtractions } from "./sync/extraction.js";
|
|
30
33
|
import { refreshManifestCoordinates } from "./sync/manifest.js";
|
|
31
|
-
import { persistEntities, persistRelationships } from "./sync/persistence.js";
|
|
32
|
-
import { atomicPublish, cleanupStaging, createUniqueStagingPath, prepareStagingEnvironment, } from "./sync/staging.js";
|
|
34
|
+
import { persistEntities, persistRelationships, retractEntitiesById, retractEntitiesForSources, retractEntityRelationshipsById, retractRelationships, } from "./sync/persistence.js";
|
|
35
|
+
import { atomicPublish, atomicPublishGeneration, cleanupStaging, createUniqueStagingPath, prepareStagingEnvironment, } from "./sync/staging.js";
|
|
33
36
|
export class SyncError extends Error {
|
|
34
37
|
constructor(message) {
|
|
35
38
|
super(message);
|
|
36
39
|
this.name = "SyncError";
|
|
37
40
|
}
|
|
38
41
|
}
|
|
42
|
+
function relationshipKey(relationship) {
|
|
43
|
+
return `${relationship.type}\0${relationship.from}\0${relationship.to}`;
|
|
44
|
+
}
|
|
45
|
+
function relationshipFromKey(key) {
|
|
46
|
+
const [type, from, to] = key.split("\0");
|
|
47
|
+
if (type === undefined || from === undefined || to === undefined) {
|
|
48
|
+
throw new SyncError(`Invalid cached relationship key: ${key}`);
|
|
49
|
+
}
|
|
50
|
+
return { type, from, to };
|
|
51
|
+
}
|
|
52
|
+
function normalizedEntityHash(result) {
|
|
53
|
+
const entity = { ...result.entity };
|
|
54
|
+
// Manifest defaults are generated at extraction time. They must not turn a
|
|
55
|
+
// one-record edit into 10,000 false-positive symbol updates.
|
|
56
|
+
entity.created_at = undefined;
|
|
57
|
+
entity.updated_at = undefined;
|
|
58
|
+
return hashNormalized({
|
|
59
|
+
entity,
|
|
60
|
+
sourceFile: result.sourceFile,
|
|
61
|
+
relationships: result.relationships
|
|
62
|
+
.map(relationshipKey)
|
|
63
|
+
.sort((left, right) => left.localeCompare(right)),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function checkpointNoopSync(workspaceRoot, branch) {
|
|
67
|
+
const engine = new EngineClient({
|
|
68
|
+
workspaceRoot,
|
|
69
|
+
branch,
|
|
70
|
+
timeout: 120_000,
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
await engine.start();
|
|
74
|
+
const checkpoint = await engine.checkpoint();
|
|
75
|
+
if (!checkpoint.success) {
|
|
76
|
+
throw new SyncError(`Failed to publish the no-op sync checkpoint: ${checkpoint.error ?? "Unknown error"}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
await engine.terminate();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function compilerCacheHasEntityDelta(cache) {
|
|
84
|
+
return (cache.entityHashes !== undefined && cache.sourceEntityIds !== undefined);
|
|
85
|
+
}
|
|
86
|
+
function compilerCacheIsFresh(cache, sourceFiles, nowMs) {
|
|
87
|
+
const sourceKeys = sourceFiles.map(toCacheKey);
|
|
88
|
+
if (Object.keys(cache.hashes).some((key) => !sourceKeys.includes(key))) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return sourceKeys.every((key) => {
|
|
92
|
+
const lastSeen = cache.seenAt[key];
|
|
93
|
+
const lastSeenMs = lastSeen ? Date.parse(lastSeen) : Number.NaN;
|
|
94
|
+
return (typeof cache.hashes[key] === "string" &&
|
|
95
|
+
lastSeen !== undefined &&
|
|
96
|
+
!Number.isNaN(lastSeenMs) &&
|
|
97
|
+
nowMs - lastSeenMs <= SYNC_CACHE_TTL_MS);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function compilerCacheFilesMatch(cache, sourceFiles, relationshipFiles) {
|
|
101
|
+
for (const file of sourceFiles) {
|
|
102
|
+
if (cache.hashes[toCacheKey(file)] !== hashFile(file))
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const relationshipKeys = new Set(relationshipFiles.map(toCacheKey));
|
|
106
|
+
if (Object.keys(cache.relationshipHashes ?? {}).some((key) => !relationshipKeys.has(key))) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
for (const file of relationshipFiles) {
|
|
110
|
+
if (cache.relationshipHashes?.[toCacheKey(file)] !== hashFile(file)) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
39
116
|
// implements REQ-003, REQ-007
|
|
40
117
|
export async function syncCommand(options = {}, runtime = {}) {
|
|
41
118
|
const validateOnly = options.validateOnly ?? false;
|
|
@@ -70,6 +147,24 @@ export async function syncCommand(options = {}, runtime = {}) {
|
|
|
70
147
|
throw new SyncError(`Failed to resolve active branch: ${branchResult.error}`);
|
|
71
148
|
}
|
|
72
149
|
currentBranch = branchResult.branch;
|
|
150
|
+
// Cut over legacy branches before creating a staging copy. If another
|
|
151
|
+
// CLI/MCP operation has an engine attached, stop that single writer so the
|
|
152
|
+
// atomic publish below cannot race its RDF lock.
|
|
153
|
+
const livePathForEngine = path.join(process.cwd(), `.kb/branches/${currentBranch}`);
|
|
154
|
+
if (!validateOnly) {
|
|
155
|
+
await ensureJournaledBranchStoreAsync(livePathForEngine);
|
|
156
|
+
const existingEngine = new EngineClient({
|
|
157
|
+
workspaceRoot: process.cwd(),
|
|
158
|
+
branch: currentBranch,
|
|
159
|
+
timeout: 2_000,
|
|
160
|
+
});
|
|
161
|
+
const needsExclusiveGenerationPublish = rebuild || runtime.createProlog !== undefined;
|
|
162
|
+
if (needsExclusiveGenerationPublish &&
|
|
163
|
+
existsSync(engineSocketPath(process.cwd(), currentBranch))) {
|
|
164
|
+
await existingEngine.stop(false).catch(() => undefined);
|
|
165
|
+
await existingEngine.terminate();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
73
168
|
if (isCliDebugEnabled()) {
|
|
74
169
|
// eslint-disable-next-line no-console
|
|
75
170
|
console.log("[kibi-debug] currentBranch:", currentBranch);
|
|
@@ -88,16 +183,120 @@ export async function syncCommand(options = {}, runtime = {}) {
|
|
|
88
183
|
const syncCache = readSyncCache(cachePath);
|
|
89
184
|
const nowIso = new Date().toISOString();
|
|
90
185
|
const nowMs = Date.now();
|
|
186
|
+
const currentSourceKeys = new Set(sourceFiles.map(toCacheKey));
|
|
187
|
+
const relationshipShardFiles = existsSync(relationshipsDir)
|
|
188
|
+
? readdirSync(relationshipsDir)
|
|
189
|
+
.filter((file) => file.endsWith(".yaml") || file.endsWith(".yml"))
|
|
190
|
+
.map((file) => path.join(relationshipsDir, file))
|
|
191
|
+
.sort()
|
|
192
|
+
: [];
|
|
193
|
+
// No-op is a first-class compiler result. Hashing the configured inputs is
|
|
194
|
+
// sufficient when normalized entity/shard inventories are already fresh;
|
|
195
|
+
// avoid rebuilding maps and rediscovering RDF state for the common path.
|
|
196
|
+
if (!validateOnly &&
|
|
197
|
+
!rebuild &&
|
|
198
|
+
!options.refreshSymbolCoordinates &&
|
|
199
|
+
runtime.createProlog === undefined &&
|
|
200
|
+
compilerCacheHasEntityDelta(syncCache) &&
|
|
201
|
+
compilerCacheIsFresh(syncCache, sourceFiles, nowMs) &&
|
|
202
|
+
compilerCacheFilesMatch(syncCache, sourceFiles, relationshipShardFiles)) {
|
|
203
|
+
await checkpointNoopSync(process.cwd(), currentBranch);
|
|
204
|
+
console.log("✓ Imported 0 entities, 0 relationships (no changes)");
|
|
205
|
+
return withOptionalCommit({
|
|
206
|
+
branch: currentBranch,
|
|
207
|
+
timestamp: new Date().toISOString(),
|
|
208
|
+
entityCounts,
|
|
209
|
+
relationshipCount: 0,
|
|
210
|
+
success: true,
|
|
211
|
+
published: false,
|
|
212
|
+
failures: diagnostics,
|
|
213
|
+
durationMs: Date.now() - startTime,
|
|
214
|
+
exitCode: 0,
|
|
215
|
+
}, getCurrentCommit());
|
|
216
|
+
}
|
|
217
|
+
const changedSourceHashes = new Map();
|
|
218
|
+
for (const file of sourceFiles) {
|
|
219
|
+
const key = toCacheKey(file);
|
|
220
|
+
const hash = hashFile(file);
|
|
221
|
+
if (syncCache.hashes[key] !== hash)
|
|
222
|
+
changedSourceHashes.set(key, hash);
|
|
223
|
+
}
|
|
91
224
|
const nextHashes = {};
|
|
92
225
|
const nextSeenAt = {};
|
|
93
|
-
const
|
|
94
|
-
|
|
226
|
+
const nextSemanticHashes = {
|
|
227
|
+
...syncCache.semanticHashes,
|
|
228
|
+
};
|
|
229
|
+
const nextSemanticContracts = {
|
|
230
|
+
...syncCache.semanticContracts,
|
|
231
|
+
};
|
|
232
|
+
const nextRelationshipHashes = {};
|
|
233
|
+
const nextShardRelationships = {};
|
|
234
|
+
const addedShardRelationships = [];
|
|
235
|
+
const removedShardRelationships = [];
|
|
236
|
+
const initialSemanticBaseline = Object.keys(syncCache.hashes).length === 0;
|
|
95
237
|
const changedMarkdownFiles = [];
|
|
96
238
|
const changedManifestFiles = [];
|
|
239
|
+
const forceEntitySourceKeys = new Set();
|
|
240
|
+
// A removed source has no file left to extract, but its entities must be
|
|
241
|
+
// retracted from the branch. Keep this separate from failed extraction:
|
|
242
|
+
// malformed current files remain authoritative until a later successful
|
|
243
|
+
// sync rather than deleting their last known-good entities.
|
|
244
|
+
const deletedSourceFiles = Object.keys(syncCache.hashes)
|
|
245
|
+
.filter((key) => !currentSourceKeys.has(key))
|
|
246
|
+
.map((key) => path.resolve(process.cwd(), key));
|
|
247
|
+
const currentRelationshipKeys = new Set();
|
|
248
|
+
for (const shardPath of relationshipShardFiles) {
|
|
249
|
+
const key = toCacheKey(shardPath);
|
|
250
|
+
currentRelationshipKeys.add(key);
|
|
251
|
+
let hash;
|
|
252
|
+
try {
|
|
253
|
+
hash = hashFile(shardPath);
|
|
254
|
+
nextRelationshipHashes[key] = hash;
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
258
|
+
console.warn(`Warning: Failed to hash relationship shard ${shardPath}: ${message}`);
|
|
259
|
+
}
|
|
260
|
+
const previousKeys = syncCache.shardRelationships?.[key] ?? [];
|
|
261
|
+
const shardChanged = syncCache.shardRelationships === undefined ||
|
|
262
|
+
hash === undefined ||
|
|
263
|
+
syncCache.relationshipHashes?.[key] !== hash;
|
|
264
|
+
const currentKeys = shardChanged
|
|
265
|
+
? extractRelationshipShard(shardPath)
|
|
266
|
+
.relationships.map(relationshipKey)
|
|
267
|
+
.sort((left, right) => left.localeCompare(right))
|
|
268
|
+
: previousKeys;
|
|
269
|
+
nextShardRelationships[key] = currentKeys;
|
|
270
|
+
if (shardChanged) {
|
|
271
|
+
const previous = new Set(previousKeys);
|
|
272
|
+
const current = new Set(currentKeys);
|
|
273
|
+
for (const edge of previous) {
|
|
274
|
+
if (!current.has(edge)) {
|
|
275
|
+
removedShardRelationships.push(relationshipFromKey(edge));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
for (const edge of current) {
|
|
279
|
+
if (!previous.has(edge)) {
|
|
280
|
+
addedShardRelationships.push(relationshipFromKey(edge));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
for (const [key, previousKeys] of Object.entries(syncCache.shardRelationships ?? {})) {
|
|
286
|
+
if (currentRelationshipKeys.has(key))
|
|
287
|
+
continue;
|
|
288
|
+
for (const edge of previousKeys) {
|
|
289
|
+
removedShardRelationships.push(relationshipFromKey(edge));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const relationshipChanged = addedShardRelationships.length > 0 ||
|
|
293
|
+
removedShardRelationships.length > 0;
|
|
97
294
|
for (const file of sourceFiles) {
|
|
98
295
|
try {
|
|
99
296
|
const key = toCacheKey(file);
|
|
100
|
-
const hash =
|
|
297
|
+
const hash = changedSourceHashes.get(key) ?? syncCache.hashes[key];
|
|
298
|
+
if (hash === undefined)
|
|
299
|
+
continue;
|
|
101
300
|
const lastSeen = syncCache.seenAt[key];
|
|
102
301
|
const lastSeenMs = lastSeen ? Date.parse(lastSeen) : Number.NaN;
|
|
103
302
|
const expired = !lastSeen ||
|
|
@@ -105,8 +304,10 @@ export async function syncCommand(options = {}, runtime = {}) {
|
|
|
105
304
|
nowMs - lastSeenMs > SYNC_CACHE_TTL_MS;
|
|
106
305
|
nextHashes[key] = hash;
|
|
107
306
|
nextSeenAt[key] = nowIso;
|
|
108
|
-
const isChanged = expired ||
|
|
307
|
+
const isChanged = expired || changedSourceHashes.has(key) || validateOnly || rebuild;
|
|
109
308
|
if (isChanged) {
|
|
309
|
+
if (expired)
|
|
310
|
+
forceEntitySourceKeys.add(key);
|
|
110
311
|
if (markdownFiles.includes(file)) {
|
|
111
312
|
changedMarkdownFiles.push(file);
|
|
112
313
|
}
|
|
@@ -120,9 +321,134 @@ export async function syncCommand(options = {}, runtime = {}) {
|
|
|
120
321
|
console.warn(`Warning: Failed to hash ${file}: ${message}`);
|
|
121
322
|
}
|
|
122
323
|
}
|
|
324
|
+
// A v1 cache has only whole-file hashes. Perform one compiler-metadata
|
|
325
|
+
// backfill so subsequent edits can be reduced to normalized entity deltas.
|
|
326
|
+
if (syncCache.entityHashes === undefined ||
|
|
327
|
+
syncCache.sourceEntityIds === undefined) {
|
|
328
|
+
for (const file of markdownFiles) {
|
|
329
|
+
if (!changedMarkdownFiles.includes(file))
|
|
330
|
+
changedMarkdownFiles.push(file);
|
|
331
|
+
}
|
|
332
|
+
for (const file of manifestFiles) {
|
|
333
|
+
if (!changedManifestFiles.includes(file))
|
|
334
|
+
changedManifestFiles.push(file);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// Coordinate refresh must precede extraction so the manifest overlay that
|
|
338
|
+
// is persisted into RDF observes the newly generated coordinates. Force
|
|
339
|
+
// each refreshed manifest through extraction even when its authored YAML
|
|
340
|
+
// hash is unchanged; the generated coordinate artifact is an input too.
|
|
341
|
+
if (!validateOnly && options.refreshSymbolCoordinates) {
|
|
342
|
+
for (const file of manifestFiles) {
|
|
343
|
+
try {
|
|
344
|
+
await refreshManifestCoordinates(file, process.cwd(), {
|
|
345
|
+
refreshSymbolCoordinates: options.refreshSymbolCoordinates,
|
|
346
|
+
});
|
|
347
|
+
if (!changedManifestFiles.includes(file)) {
|
|
348
|
+
changedManifestFiles.push(file);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
353
|
+
console.warn(`Warning: Failed to refresh symbol coordinates in ${file}: ${message}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
123
357
|
const performedFullReindex = changedMarkdownFiles.length === markdownFiles.length &&
|
|
124
358
|
changedManifestFiles.length === manifestFiles.length;
|
|
125
|
-
const
|
|
359
|
+
const extraction = await processExtractions(changedMarkdownFiles, changedManifestFiles, validateOnly);
|
|
360
|
+
const { failedCacheKeys, errors } = extraction;
|
|
361
|
+
const extractedResults = extraction.results;
|
|
362
|
+
const nextEntityHashes = {
|
|
363
|
+
...(syncCache.entityHashes ?? {}),
|
|
364
|
+
};
|
|
365
|
+
const nextSourceEntityIds = {
|
|
366
|
+
...(syncCache.sourceEntityIds ?? {}),
|
|
367
|
+
};
|
|
368
|
+
const removedEntityIds = new Set();
|
|
369
|
+
const changedEntityIds = new Set();
|
|
370
|
+
const supportsEntityDelta = syncCache.entityHashes !== undefined &&
|
|
371
|
+
syncCache.sourceEntityIds !== undefined;
|
|
372
|
+
const useEntityDelta = supportsEntityDelta &&
|
|
373
|
+
!validateOnly &&
|
|
374
|
+
!rebuild &&
|
|
375
|
+
runtime.createProlog === undefined;
|
|
376
|
+
const materializeAllRelationships = !useEntityDelta || changedMarkdownFiles.length > 0;
|
|
377
|
+
const allRelationships = materializeAllRelationships
|
|
378
|
+
? Object.values(nextShardRelationships).flat().map(relationshipFromKey)
|
|
379
|
+
: [...addedShardRelationships, ...removedShardRelationships];
|
|
380
|
+
for (const deletedSource of deletedSourceFiles) {
|
|
381
|
+
const sourceKey = toCacheKey(deletedSource);
|
|
382
|
+
for (const id of nextSourceEntityIds[sourceKey] ?? []) {
|
|
383
|
+
removedEntityIds.add(id);
|
|
384
|
+
delete nextEntityHashes[id];
|
|
385
|
+
}
|
|
386
|
+
delete nextSourceEntityIds[sourceKey];
|
|
387
|
+
}
|
|
388
|
+
const resultsBySource = new Map();
|
|
389
|
+
for (const result of extractedResults) {
|
|
390
|
+
const sourceKey = toCacheKey(result.entity.source);
|
|
391
|
+
const sourceResults = resultsBySource.get(sourceKey) ?? [];
|
|
392
|
+
sourceResults.push(result);
|
|
393
|
+
resultsBySource.set(sourceKey, sourceResults);
|
|
394
|
+
}
|
|
395
|
+
for (const changedSource of [
|
|
396
|
+
...changedMarkdownFiles,
|
|
397
|
+
...changedManifestFiles,
|
|
398
|
+
]) {
|
|
399
|
+
const sourceKey = toCacheKey(changedSource);
|
|
400
|
+
if (failedCacheKeys.has(sourceKey))
|
|
401
|
+
continue;
|
|
402
|
+
const sourceResults = resultsBySource.get(sourceKey) ?? [];
|
|
403
|
+
const previousIds = new Set(nextSourceEntityIds[sourceKey] ?? []);
|
|
404
|
+
const currentIds = sourceResults.map(({ entity }) => entity.id).sort();
|
|
405
|
+
const currentIdSet = new Set(currentIds);
|
|
406
|
+
for (const previousId of previousIds) {
|
|
407
|
+
if (!currentIdSet.has(previousId)) {
|
|
408
|
+
removedEntityIds.add(previousId);
|
|
409
|
+
delete nextEntityHashes[previousId];
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
for (const result of sourceResults) {
|
|
413
|
+
const hash = normalizedEntityHash(result);
|
|
414
|
+
if (forceEntitySourceKeys.has(sourceKey) ||
|
|
415
|
+
nextEntityHashes[result.entity.id] !== hash) {
|
|
416
|
+
changedEntityIds.add(result.entity.id);
|
|
417
|
+
}
|
|
418
|
+
nextEntityHashes[result.entity.id] = hash;
|
|
419
|
+
}
|
|
420
|
+
nextSourceEntityIds[sourceKey] = currentIds;
|
|
421
|
+
}
|
|
422
|
+
const results = useEntityDelta
|
|
423
|
+
? extractedResults.filter(({ entity }) => changedEntityIds.has(entity.id))
|
|
424
|
+
: extractedResults;
|
|
425
|
+
for (const result of extractedResults) {
|
|
426
|
+
if (result.entity.type !== "req")
|
|
427
|
+
continue;
|
|
428
|
+
const key = toCacheKey(result.entity.source);
|
|
429
|
+
const payload = {
|
|
430
|
+
type: result.entity.type,
|
|
431
|
+
id: result.entity.id,
|
|
432
|
+
properties: result.entity,
|
|
433
|
+
relationships: result.relationships,
|
|
434
|
+
};
|
|
435
|
+
const semantic = analyzeSemanticAdvisorInput({ payload });
|
|
436
|
+
const boundary = validateSemanticInventoryBoundary(payload, result.relationships, semantic.receipt);
|
|
437
|
+
const previousSemanticHash = syncCache.semanticHashes[key];
|
|
438
|
+
const advertisedContract = result.entity.semantic_inventory_version !== undefined ||
|
|
439
|
+
result.entity.semantic_source_hash !== undefined ||
|
|
440
|
+
result.entity.semantic_inventory !== undefined;
|
|
441
|
+
const enforceBoundary = (!initialSemanticBaseline && syncCache.hashes[key] === undefined) ||
|
|
442
|
+
syncCache.semanticContracts[key] === true ||
|
|
443
|
+
(previousSemanticHash !== undefined &&
|
|
444
|
+
previousSemanticHash !== boundary.sourceHash) ||
|
|
445
|
+
advertisedContract;
|
|
446
|
+
if (enforceBoundary && boundary.errors.length > 0) {
|
|
447
|
+
throw new SyncError(`${key}: proposition-complete ingestion failed: ${boundary.errors.join("; ")}. Run kb_semantic_advisor with the complete requirement prose and preserve its inventory contract.`);
|
|
448
|
+
}
|
|
449
|
+
nextSemanticHashes[key] = boundary.sourceHash;
|
|
450
|
+
nextSemanticContracts[key] = advertisedContract;
|
|
451
|
+
}
|
|
126
452
|
// Collect INVALID_AUTHORING diagnostics
|
|
127
453
|
for (const err of errors) {
|
|
128
454
|
const error = new Error(err.message);
|
|
@@ -139,21 +465,6 @@ export async function syncCommand(options = {}, runtime = {}) {
|
|
|
139
465
|
diagnostics.push(createInvalidAuthoringDiagnostic(err.file, embeddedTypes));
|
|
140
466
|
}
|
|
141
467
|
}
|
|
142
|
-
if (!validateOnly) {
|
|
143
|
-
if (options.refreshSymbolCoordinates) {
|
|
144
|
-
for (const file of manifestFiles) {
|
|
145
|
-
try {
|
|
146
|
-
await refreshManifestCoordinates(file, process.cwd(), {
|
|
147
|
-
refreshSymbolCoordinates: options.refreshSymbolCoordinates,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
catch (error) {
|
|
151
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
152
|
-
console.warn(`Warning: Failed to refresh symbol coordinates in ${file}: ${message}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
468
|
if (validateOnly && errors.length > 0 && results.length === 0) {
|
|
158
469
|
for (const err of errors) {
|
|
159
470
|
console.error(`${err.file}: ${err.message}`);
|
|
@@ -171,21 +482,42 @@ export async function syncCommand(options = {}, runtime = {}) {
|
|
|
171
482
|
exitCode: 1,
|
|
172
483
|
}, getCurrentCommit());
|
|
173
484
|
}
|
|
174
|
-
if (results.length === 0 &&
|
|
485
|
+
if (results.length === 0 &&
|
|
486
|
+
removedEntityIds.size === 0 &&
|
|
487
|
+
!relationshipChanged &&
|
|
488
|
+
(useEntityDelta || changedMarkdownFiles.length === 0) &&
|
|
489
|
+
(useEntityDelta || changedManifestFiles.length === 0) &&
|
|
490
|
+
deletedSourceFiles.length === 0 &&
|
|
491
|
+
!rebuild) {
|
|
175
492
|
const evictedHashes = {};
|
|
176
493
|
const evictedSeenAt = {};
|
|
494
|
+
const evictedSemanticHashes = {};
|
|
495
|
+
const evictedSemanticContracts = {};
|
|
177
496
|
for (const [key, hash] of Object.entries(nextHashes)) {
|
|
178
497
|
if (failedCacheKeys.has(key)) {
|
|
179
498
|
continue;
|
|
180
499
|
}
|
|
181
500
|
evictedHashes[key] = hash;
|
|
182
501
|
evictedSeenAt[key] = nextSeenAt[key] ?? nowIso;
|
|
502
|
+
if (nextSemanticHashes[key] !== undefined) {
|
|
503
|
+
evictedSemanticHashes[key] = nextSemanticHashes[key];
|
|
504
|
+
evictedSemanticContracts[key] = nextSemanticContracts[key] === true;
|
|
505
|
+
}
|
|
183
506
|
}
|
|
184
507
|
writeSyncCache(cachePath, {
|
|
185
508
|
version: SYNC_CACHE_VERSION,
|
|
186
509
|
hashes: evictedHashes,
|
|
510
|
+
relationshipHashes: nextRelationshipHashes,
|
|
511
|
+
entityHashes: nextEntityHashes,
|
|
512
|
+
sourceEntityIds: nextSourceEntityIds,
|
|
513
|
+
shardRelationships: nextShardRelationships,
|
|
187
514
|
seenAt: evictedSeenAt,
|
|
515
|
+
semanticHashes: evictedSemanticHashes,
|
|
516
|
+
semanticContracts: evictedSemanticContracts,
|
|
188
517
|
});
|
|
518
|
+
if (runtime.createProlog === undefined) {
|
|
519
|
+
await checkpointNoopSync(process.cwd(), currentBranch);
|
|
520
|
+
}
|
|
189
521
|
console.log("✓ Imported 0 entities, 0 relationships (no changes)");
|
|
190
522
|
return withOptionalCommit({
|
|
191
523
|
branch: currentBranch,
|
|
@@ -204,6 +536,135 @@ export async function syncCommand(options = {}, runtime = {}) {
|
|
|
204
536
|
if (!kbExists && !rebuild) {
|
|
205
537
|
diagnostics.push(createKbMissingDiagnostic(currentBranch, livePath));
|
|
206
538
|
}
|
|
539
|
+
// implements REQ-core-journaled-engine-delta-sync
|
|
540
|
+
// Normal syncs are compiled directly into the long-lived single-writer
|
|
541
|
+
// engine. Rebuilds retain generation replacement semantics, and injected
|
|
542
|
+
// Prolog runtimes keep the staging path used by the contract fixtures.
|
|
543
|
+
// This is the cutover that avoids copying and rewriting a complete branch
|
|
544
|
+
// for a one-symbol or relationship-only change.
|
|
545
|
+
if (!validateOnly && !rebuild && runtime.createProlog === undefined) {
|
|
546
|
+
const engine = new EngineClient({
|
|
547
|
+
workspaceRoot: process.cwd(),
|
|
548
|
+
branch: currentBranch,
|
|
549
|
+
timeout: 120_000,
|
|
550
|
+
});
|
|
551
|
+
const engineProlog = engine;
|
|
552
|
+
try {
|
|
553
|
+
await engine.start();
|
|
554
|
+
const successfulChangedSources = [
|
|
555
|
+
...changedMarkdownFiles,
|
|
556
|
+
...changedManifestFiles,
|
|
557
|
+
].filter((file) => !failedCacheKeys.has(toCacheKey(file)));
|
|
558
|
+
const removedCount = useEntityDelta
|
|
559
|
+
? await retractEntitiesById(engineProlog, [...removedEntityIds])
|
|
560
|
+
: await retractEntitiesForSources(engineProlog, [
|
|
561
|
+
...successfulChangedSources,
|
|
562
|
+
...deletedSourceFiles,
|
|
563
|
+
]);
|
|
564
|
+
if (useEntityDelta) {
|
|
565
|
+
await retractEntityRelationshipsById(engineProlog, [
|
|
566
|
+
...changedEntityIds,
|
|
567
|
+
]);
|
|
568
|
+
const exactRemovedEdges = removedShardRelationships.filter((relationship) => !removedEntityIds.has(relationship.from) &&
|
|
569
|
+
!removedEntityIds.has(relationship.to) &&
|
|
570
|
+
!changedEntityIds.has(relationship.from));
|
|
571
|
+
await retractRelationships(engineProlog, exactRemovedEdges);
|
|
572
|
+
}
|
|
573
|
+
else if (relationshipChanged) {
|
|
574
|
+
const cleared = await engine.query("kb_retract_all_relationships");
|
|
575
|
+
if (!cleared.success) {
|
|
576
|
+
throw new SyncError(`Failed to clear changed relationship shards: ${cleared.error || "Unknown error"}`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
const entityIds = new Set();
|
|
580
|
+
for (const { entity } of results) {
|
|
581
|
+
entityCounts[entity.type] = (entityCounts[entity.type] || 0) + 1;
|
|
582
|
+
}
|
|
583
|
+
const { entityCount, kbModified: entitiesModified } = await persistEntities(engineProlog, results, entityIds, {
|
|
584
|
+
loadExistingEntityIds: !useEntityDelta,
|
|
585
|
+
});
|
|
586
|
+
const validationErrors = useEntityDelta
|
|
587
|
+
? []
|
|
588
|
+
: validateRelationships(allRelationships, entityIds);
|
|
589
|
+
if (validationErrors.length > 0) {
|
|
590
|
+
console.warn(`Warning: ${validationErrors.length} dangling relationship(s) found`);
|
|
591
|
+
}
|
|
592
|
+
const danglingKeys = new Set(validationErrors.map(({ relationship }) => `${relationship.type}|${relationship.from}|${relationship.to}`));
|
|
593
|
+
const shardDeltaByKey = new Map();
|
|
594
|
+
const shardCandidates = useEntityDelta
|
|
595
|
+
? [
|
|
596
|
+
...addedShardRelationships,
|
|
597
|
+
...allRelationships.filter((relationship) => changedEntityIds.has(relationship.from)),
|
|
598
|
+
]
|
|
599
|
+
: allRelationships;
|
|
600
|
+
for (const relationship of shardCandidates) {
|
|
601
|
+
if (!danglingKeys.has(`${relationship.type}|${relationship.from}|${relationship.to}`)) {
|
|
602
|
+
shardDeltaByKey.set(relationshipKey(relationship), relationship);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const { relationshipCount, kbModified: relationshipsModified } = await persistRelationships(engineProlog, results, [
|
|
606
|
+
...shardDeltaByKey.values(),
|
|
607
|
+
]);
|
|
608
|
+
const kbModified = removedCount > 0 ||
|
|
609
|
+
relationshipChanged ||
|
|
610
|
+
entitiesModified ||
|
|
611
|
+
relationshipsModified;
|
|
612
|
+
if (kbModified)
|
|
613
|
+
engine.invalidateCache();
|
|
614
|
+
const saveResult = await engine.save();
|
|
615
|
+
if (!saveResult.success) {
|
|
616
|
+
throw new SyncError(`Failed to save journaled KB: ${saveResult.error || "Unknown error"}`);
|
|
617
|
+
}
|
|
618
|
+
const evictedHashes = {};
|
|
619
|
+
const evictedSeenAt = {};
|
|
620
|
+
const evictedSemanticHashes = {};
|
|
621
|
+
const evictedSemanticContracts = {};
|
|
622
|
+
for (const [key, hash] of Object.entries(nextHashes)) {
|
|
623
|
+
if (failedCacheKeys.has(key))
|
|
624
|
+
continue;
|
|
625
|
+
evictedHashes[key] = hash;
|
|
626
|
+
evictedSeenAt[key] = nextSeenAt[key] ?? nowIso;
|
|
627
|
+
if (nextSemanticHashes[key] !== undefined) {
|
|
628
|
+
evictedSemanticHashes[key] = nextSemanticHashes[key];
|
|
629
|
+
evictedSemanticContracts[key] = nextSemanticContracts[key] === true;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
writeSyncCache(path.join(livePath, "sync-cache.json"), {
|
|
633
|
+
version: SYNC_CACHE_VERSION,
|
|
634
|
+
hashes: evictedHashes,
|
|
635
|
+
relationshipHashes: nextRelationshipHashes,
|
|
636
|
+
entityHashes: nextEntityHashes,
|
|
637
|
+
sourceEntityIds: nextSourceEntityIds,
|
|
638
|
+
shardRelationships: nextShardRelationships,
|
|
639
|
+
seenAt: evictedSeenAt,
|
|
640
|
+
semanticHashes: evictedSemanticHashes,
|
|
641
|
+
semanticContracts: evictedSemanticContracts,
|
|
642
|
+
});
|
|
643
|
+
published = true;
|
|
644
|
+
if (performedFullReindex &&
|
|
645
|
+
markdownFiles.length > 0 &&
|
|
646
|
+
entityCount < markdownFiles.length) {
|
|
647
|
+
diagnostics.push(createDocsNotIndexedDiagnostic(markdownFiles.length, entityCount));
|
|
648
|
+
}
|
|
649
|
+
console.log(`✓ Imported ${entityCount} entities, ${relationshipCount} relationships (removed ${removedCount} entities)`);
|
|
650
|
+
const commit = getCurrentCommit();
|
|
651
|
+
const summary = withOptionalCommit({
|
|
652
|
+
branch: currentBranch,
|
|
653
|
+
timestamp: new Date().toISOString(),
|
|
654
|
+
entityCounts,
|
|
655
|
+
relationshipCount,
|
|
656
|
+
success: true,
|
|
657
|
+
published,
|
|
658
|
+
failures: diagnostics,
|
|
659
|
+
durationMs: Date.now() - startTime,
|
|
660
|
+
}, commit);
|
|
661
|
+
console.log(formatSyncSummary(summary));
|
|
662
|
+
return { ...summary, exitCode: 0 };
|
|
663
|
+
}
|
|
664
|
+
finally {
|
|
665
|
+
await engine.terminate();
|
|
666
|
+
}
|
|
667
|
+
}
|
|
207
668
|
stagingPath = createUniqueStagingPath(currentBranch, process.cwd());
|
|
208
669
|
const runtimeContext = {
|
|
209
670
|
currentBranch,
|
|
@@ -227,6 +688,20 @@ export async function syncCommand(options = {}, runtime = {}) {
|
|
|
227
688
|
for (const { entity } of results) {
|
|
228
689
|
entityCounts[entity.type] = (entityCounts[entity.type] || 0) + 1;
|
|
229
690
|
}
|
|
691
|
+
// A copied generation retains entities from old source contents. Remove
|
|
692
|
+
// only successfully extracted/deleted sources before applying the new
|
|
693
|
+
// entities; failed extraction deliberately leaves the previous data.
|
|
694
|
+
await retractEntitiesForSources(prolog, [
|
|
695
|
+
...changedMarkdownFiles,
|
|
696
|
+
...changedManifestFiles,
|
|
697
|
+
...deletedSourceFiles,
|
|
698
|
+
].filter((file) => !failedCacheKeys.has(toCacheKey(file))));
|
|
699
|
+
if (relationshipChanged) {
|
|
700
|
+
const clearRelationships = await prolog.query("kb_retract_all_relationships");
|
|
701
|
+
if (!clearRelationships.success) {
|
|
702
|
+
throw new SyncError(`Failed to clear changed relationship shards: ${clearRelationships.error || "Unknown error"}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
230
705
|
const { entityCount, kbModified: entitiesModified } = await persistEntities(prolog, results, entityIds);
|
|
231
706
|
const validationErrors = validateRelationships(allRelationships, entityIds);
|
|
232
707
|
if (validationErrors.length > 0) {
|
|
@@ -284,22 +759,41 @@ export async function syncCommand(options = {}, runtime = {}) {
|
|
|
284
759
|
}
|
|
285
760
|
await prolog.query("kb_detach");
|
|
286
761
|
await prolog.terminate();
|
|
287
|
-
|
|
762
|
+
const journaledLive = existsSync(path.join(livePath, "storage.json"));
|
|
763
|
+
if (rebuild && journaledLive) {
|
|
764
|
+
atomicPublishGeneration(stagingPath, livePath);
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
atomicPublish(stagingPath, livePath);
|
|
768
|
+
}
|
|
769
|
+
fsyncJournaledBranchStore(livePath);
|
|
288
770
|
cleanupStaging(stagingPath);
|
|
289
771
|
const evictedHashes = {};
|
|
290
772
|
const evictedSeenAt = {};
|
|
773
|
+
const evictedSemanticHashes = {};
|
|
774
|
+
const evictedSemanticContracts = {};
|
|
291
775
|
for (const [key, hash] of Object.entries(nextHashes)) {
|
|
292
776
|
if (failedCacheKeys.has(key)) {
|
|
293
777
|
continue;
|
|
294
778
|
}
|
|
295
779
|
evictedHashes[key] = hash;
|
|
296
780
|
evictedSeenAt[key] = nextSeenAt[key] ?? nowIso;
|
|
781
|
+
if (nextSemanticHashes[key] !== undefined) {
|
|
782
|
+
evictedSemanticHashes[key] = nextSemanticHashes[key];
|
|
783
|
+
evictedSemanticContracts[key] = nextSemanticContracts[key] === true;
|
|
784
|
+
}
|
|
297
785
|
}
|
|
298
786
|
const liveCachePath = path.join(livePath, "sync-cache.json");
|
|
299
787
|
writeSyncCache(liveCachePath, {
|
|
300
788
|
version: SYNC_CACHE_VERSION,
|
|
301
789
|
hashes: evictedHashes,
|
|
790
|
+
relationshipHashes: nextRelationshipHashes,
|
|
791
|
+
entityHashes: nextEntityHashes,
|
|
792
|
+
sourceEntityIds: nextSourceEntityIds,
|
|
793
|
+
shardRelationships: nextShardRelationships,
|
|
302
794
|
seenAt: evictedSeenAt,
|
|
795
|
+
semanticHashes: evictedSemanticHashes,
|
|
796
|
+
semanticContracts: evictedSemanticContracts,
|
|
303
797
|
});
|
|
304
798
|
published = true;
|
|
305
799
|
if (performedFullReindex &&
|