kibi-cli 0.2.3 → 0.2.5
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.js +3 -28
- package/dist/commands/aggregated-checks.d.ts +4 -1
- package/dist/commands/aggregated-checks.d.ts.map +1 -1
- package/dist/commands/aggregated-checks.js +13 -3
- package/dist/commands/branch.d.ts.map +1 -1
- package/dist/commands/branch.js +3 -41
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +54 -44
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +0 -27
- package/dist/commands/gc.d.ts.map +1 -1
- package/dist/commands/gc.js +2 -51
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +23 -36
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +0 -27
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +7 -288
- package/dist/commands/sync/cache.d.ts +13 -0
- package/dist/commands/sync/cache.d.ts.map +1 -0
- package/dist/commands/sync/cache.js +76 -0
- package/dist/commands/sync/discovery.d.ts +8 -0
- package/dist/commands/sync/discovery.d.ts.map +1 -0
- package/dist/commands/sync/discovery.js +50 -0
- package/dist/commands/sync/extraction.d.ts +11 -0
- package/dist/commands/sync/extraction.d.ts.map +1 -0
- package/dist/commands/sync/extraction.js +69 -0
- package/dist/commands/sync/manifest.d.ts +5 -0
- package/dist/commands/sync/manifest.d.ts.map +1 -0
- package/dist/commands/sync/manifest.js +118 -0
- package/dist/commands/sync/persistence.d.ts +16 -0
- package/dist/commands/sync/persistence.d.ts.map +1 -0
- package/dist/commands/sync/persistence.js +188 -0
- package/dist/commands/sync/staging.d.ts +4 -0
- package/dist/commands/sync/staging.d.ts.map +1 -0
- package/dist/commands/sync/staging.js +86 -0
- package/dist/commands/sync.d.ts +2 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +69 -501
- package/dist/extractors/manifest.d.ts +0 -1
- package/dist/extractors/manifest.d.ts.map +1 -1
- package/dist/extractors/manifest.js +39 -48
- package/dist/extractors/markdown.d.ts +0 -1
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +16 -49
- package/dist/extractors/relationships.d.ts +39 -0
- package/dist/extractors/relationships.d.ts.map +1 -0
- package/dist/extractors/relationships.js +137 -0
- package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
- package/dist/extractors/symbols-coordinator.js +0 -27
- package/dist/extractors/symbols-ts.d.ts.map +1 -1
- package/dist/extractors/symbols-ts.js +0 -27
- package/dist/kb/target-resolver.d.ts +80 -0
- package/dist/kb/target-resolver.d.ts.map +1 -0
- package/dist/kb/target-resolver.js +313 -0
- package/dist/prolog/codec.d.ts +63 -0
- package/dist/prolog/codec.d.ts.map +1 -0
- package/dist/prolog/codec.js +434 -0
- package/dist/prolog.d.ts.map +1 -1
- package/dist/prolog.js +0 -27
- package/dist/public/extractors/symbols-coordinator.d.ts.map +1 -1
- package/dist/public/extractors/symbols-coordinator.js +0 -27
- package/dist/public/prolog/index.d.ts.map +1 -1
- package/dist/public/prolog/index.js +0 -27
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +0 -27
- package/dist/public/schemas/relationship.d.ts.map +1 -1
- package/dist/public/schemas/relationship.js +0 -27
- package/dist/query/service.d.ts +35 -0
- package/dist/query/service.d.ts.map +1 -0
- package/dist/query/service.js +149 -0
- package/dist/relationships/shards.d.ts +68 -0
- package/dist/relationships/shards.d.ts.map +1 -0
- package/dist/relationships/shards.js +263 -0
- package/dist/traceability/git-staged.d.ts +4 -1
- package/dist/traceability/git-staged.d.ts.map +1 -1
- package/dist/traceability/git-staged.js +24 -11
- package/dist/types/changeset.d.ts.map +1 -1
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/types/relationships.d.ts.map +1 -1
- package/dist/utils/branch-resolver.d.ts +4 -0
- package/dist/utils/branch-resolver.d.ts.map +1 -1
- package/dist/utils/branch-resolver.js +4 -0
- package/dist/utils/config.d.ts +10 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +27 -1
- package/dist/utils/rule-registry.d.ts +47 -0
- package/dist/utils/rule-registry.d.ts.map +1 -0
- package/dist/utils/rule-registry.js +139 -0
- package/package.json +5 -1
- package/schema/config.json +156 -0
- package/src/public/extractors/symbols-coordinator.ts +0 -27
- package/src/public/prolog/index.ts +0 -27
- package/src/public/schemas/entity.ts +0 -27
- package/src/public/schemas/relationship.ts +0 -27
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
19
|
+
import * as path from "node:path";
|
|
20
|
+
import { dump as dumpYAML, load as parseYAML } from "js-yaml";
|
|
21
|
+
import { enrichSymbolCoordinates, } from "../../extractors/symbols-coordinator.js";
|
|
22
|
+
const SYMBOLS_MANIFEST_COMMENT_BLOCK = `# symbols.yaml
|
|
23
|
+
# AUTHORED fields (edit freely):
|
|
24
|
+
# id, title, sourceFile, links, status, tags, owner, priority
|
|
25
|
+
# GENERATED fields (never edit manually — overwritten by kibi sync and kb.symbols.refresh):
|
|
26
|
+
# sourceLine, sourceColumn, sourceEndLine, sourceEndColumn, coordinatesGeneratedAt
|
|
27
|
+
# Run \`kibi sync\` or call the \`kb.symbols.refresh\` MCP tool to refresh coordinates.
|
|
28
|
+
`;
|
|
29
|
+
const SYMBOL_COORD_EXTENSIONS = new Set([
|
|
30
|
+
".ts",
|
|
31
|
+
".tsx",
|
|
32
|
+
".js",
|
|
33
|
+
".jsx",
|
|
34
|
+
".mts",
|
|
35
|
+
".cts",
|
|
36
|
+
".mjs",
|
|
37
|
+
".cjs",
|
|
38
|
+
]);
|
|
39
|
+
const GENERATED_COORD_FIELDS = [
|
|
40
|
+
"sourceLine",
|
|
41
|
+
"sourceColumn",
|
|
42
|
+
"sourceEndLine",
|
|
43
|
+
"sourceEndColumn",
|
|
44
|
+
"coordinatesGeneratedAt",
|
|
45
|
+
];
|
|
46
|
+
export async function refreshManifestCoordinates(manifestPath, workspaceRoot) {
|
|
47
|
+
const rawContent = readFileSync(manifestPath, "utf8");
|
|
48
|
+
const parsed = parseYAML(rawContent);
|
|
49
|
+
if (!isRecord(parsed)) {
|
|
50
|
+
console.warn(`Warning: symbols manifest ${manifestPath} is not a YAML object; skipping coordinate refresh`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const rawSymbols = parsed.symbols;
|
|
54
|
+
if (!Array.isArray(rawSymbols)) {
|
|
55
|
+
console.warn(`Warning: symbols manifest ${manifestPath} has no symbols array; skipping coordinate refresh`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const before = rawSymbols.map((entry) => isRecord(entry)
|
|
59
|
+
? { ...entry }
|
|
60
|
+
: {});
|
|
61
|
+
const enriched = await enrichSymbolCoordinates(before, workspaceRoot);
|
|
62
|
+
parsed.symbols = enriched;
|
|
63
|
+
let refreshed = 0;
|
|
64
|
+
let failed = 0;
|
|
65
|
+
let unchanged = 0;
|
|
66
|
+
for (let i = 0; i < before.length; i++) {
|
|
67
|
+
const previous = before[i] ?? {};
|
|
68
|
+
const current = enriched[i] ?? previous;
|
|
69
|
+
const changed = GENERATED_COORD_FIELDS.some((field) => previous[field] !== current[field]);
|
|
70
|
+
if (changed) {
|
|
71
|
+
refreshed++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const eligible = isEligibleForCoordinateRefresh(typeof current.sourceFile === "string"
|
|
75
|
+
? current.sourceFile
|
|
76
|
+
: typeof previous.sourceFile === "string"
|
|
77
|
+
? previous.sourceFile
|
|
78
|
+
: undefined, workspaceRoot);
|
|
79
|
+
if (eligible && !hasAllGeneratedCoordinates(current)) {
|
|
80
|
+
failed++;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
unchanged++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const dumped = dumpYAML(parsed, {
|
|
87
|
+
lineWidth: -1,
|
|
88
|
+
noRefs: true,
|
|
89
|
+
sortKeys: false,
|
|
90
|
+
});
|
|
91
|
+
const nextContent = `${SYMBOLS_MANIFEST_COMMENT_BLOCK}${dumped}`;
|
|
92
|
+
if (rawContent !== nextContent) {
|
|
93
|
+
writeFileSync(manifestPath, nextContent, "utf8");
|
|
94
|
+
}
|
|
95
|
+
console.log(`✓ Refreshed symbol coordinates in ${path.relative(workspaceRoot, manifestPath)} (refreshed=${refreshed}, unchanged=${unchanged}, failed=${failed})`);
|
|
96
|
+
}
|
|
97
|
+
export function hasAllGeneratedCoordinates(entry) {
|
|
98
|
+
return (typeof entry.sourceLine === "number" &&
|
|
99
|
+
typeof entry.sourceColumn === "number" &&
|
|
100
|
+
typeof entry.sourceEndLine === "number" &&
|
|
101
|
+
typeof entry.sourceEndColumn === "number" &&
|
|
102
|
+
typeof entry.coordinatesGeneratedAt === "string" &&
|
|
103
|
+
entry.coordinatesGeneratedAt.length > 0);
|
|
104
|
+
}
|
|
105
|
+
export function isEligibleForCoordinateRefresh(sourceFile, workspaceRoot) {
|
|
106
|
+
if (!sourceFile)
|
|
107
|
+
return false;
|
|
108
|
+
const absolute = path.isAbsolute(sourceFile)
|
|
109
|
+
? sourceFile
|
|
110
|
+
: path.resolve(workspaceRoot, sourceFile);
|
|
111
|
+
if (!existsSync(absolute))
|
|
112
|
+
return false;
|
|
113
|
+
const ext = path.extname(absolute).toLowerCase();
|
|
114
|
+
return SYMBOL_COORD_EXTENSIONS.has(ext);
|
|
115
|
+
}
|
|
116
|
+
function isRecord(value) {
|
|
117
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
118
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ExtractedRelationship, ExtractionResult } from "../../extractors/markdown.js";
|
|
2
|
+
import type { PrologProcess } from "../../prolog.js";
|
|
3
|
+
export interface PersistenceResult {
|
|
4
|
+
entityCount: number;
|
|
5
|
+
relationshipCount: number;
|
|
6
|
+
kbModified: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function persistEntities(prolog: PrologProcess, results: ExtractionResult[], entityIds: Set<string>): Promise<{
|
|
9
|
+
entityCount: number;
|
|
10
|
+
kbModified: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function persistRelationships(prolog: PrologProcess, results: ExtractionResult[], shardRelationships: ExtractedRelationship[]): Promise<{
|
|
13
|
+
relationshipCount: number;
|
|
14
|
+
kbModified: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
//# sourceMappingURL=persistence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/persistence.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GACrB,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CA0DvD;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,kBAAkB,EAAE,qBAAqB,EAAE,GAC1C,OAAO,CAAC;IAAE,iBAAiB,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAAC,CAiI7D"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import { toPrologAtom } from "../../prolog/codec.js";
|
|
20
|
+
export async function persistEntities(prolog, results, entityIds) {
|
|
21
|
+
let entityCount = 0;
|
|
22
|
+
let kbModified = false;
|
|
23
|
+
// Query existing entity IDs to include unchanged entities
|
|
24
|
+
const existingIdsResult = await prolog.query("findall(Id, kb_entity(Id, _, _), ExistingIds)");
|
|
25
|
+
if (existingIdsResult.success && existingIdsResult.bindings?.ExistingIds) {
|
|
26
|
+
const raw = existingIdsResult.bindings.ExistingIds;
|
|
27
|
+
const cleaned = raw.trim().replace(/^\[/, "").replace(/\]$/, "");
|
|
28
|
+
if (cleaned) {
|
|
29
|
+
for (const atom of cleaned.split(",")) {
|
|
30
|
+
const id = atom.trim().replace(/^'|'$/g, "");
|
|
31
|
+
if (id)
|
|
32
|
+
entityIds.add(id);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const { entity } of results) {
|
|
37
|
+
entityIds.add(entity.id);
|
|
38
|
+
}
|
|
39
|
+
for (const { entity } of results) {
|
|
40
|
+
try {
|
|
41
|
+
const props = [
|
|
42
|
+
`id='${entity.id}'`,
|
|
43
|
+
`title="${entity.title.replace(/"/g, '\\"')}"`,
|
|
44
|
+
`status=${toPrologAtom(entity.status)}`,
|
|
45
|
+
`created_at="${entity.created_at}"`,
|
|
46
|
+
`updated_at="${entity.updated_at}"`,
|
|
47
|
+
`source="${entity.source.replace(/"/g, '\\"')}"`,
|
|
48
|
+
];
|
|
49
|
+
if (entity.tags && entity.tags.length > 0) {
|
|
50
|
+
const tagsList = entity.tags.map(toPrologAtom).join(",");
|
|
51
|
+
props.push(`tags=[${tagsList}]`);
|
|
52
|
+
}
|
|
53
|
+
if (entity.owner)
|
|
54
|
+
props.push(`owner=${toPrologAtom(entity.owner)}`);
|
|
55
|
+
if (entity.priority)
|
|
56
|
+
props.push(`priority=${toPrologAtom(entity.priority)}`);
|
|
57
|
+
if (entity.severity)
|
|
58
|
+
props.push(`severity=${toPrologAtom(entity.severity)}`);
|
|
59
|
+
if (entity.text_ref)
|
|
60
|
+
props.push(`text_ref="${entity.text_ref}"`);
|
|
61
|
+
const propsList = `[${props.join(", ")}]`;
|
|
62
|
+
const goal = `kb_assert_entity(${entity.type}, ${propsList})`;
|
|
63
|
+
const result = await prolog.query(goal);
|
|
64
|
+
if (result.success) {
|
|
65
|
+
entityCount++;
|
|
66
|
+
kbModified = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
71
|
+
console.warn(`Warning: Failed to upsert entity ${entity.id}: ${message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { entityCount, kbModified };
|
|
75
|
+
}
|
|
76
|
+
export async function persistRelationships(prolog, results, shardRelationships) {
|
|
77
|
+
let relCount = 0;
|
|
78
|
+
let kbModified = false;
|
|
79
|
+
// Build ID lookup for relationship resolution
|
|
80
|
+
const idLookup = new Map();
|
|
81
|
+
for (const { entity } of results) {
|
|
82
|
+
const filename = path.basename(entity.source, ".md");
|
|
83
|
+
idLookup.set(filename, entity.id);
|
|
84
|
+
idLookup.set(entity.id, entity.id);
|
|
85
|
+
}
|
|
86
|
+
// Collect all relationships from results and shards
|
|
87
|
+
const failedRelationships = [];
|
|
88
|
+
for (const { relationships } of results) {
|
|
89
|
+
for (const rel of relationships) {
|
|
90
|
+
try {
|
|
91
|
+
const fromId = idLookup.get(rel.from) || rel.from;
|
|
92
|
+
const toId = idLookup.get(rel.to) || rel.to;
|
|
93
|
+
const goal = `kb_assert_relationship(${toPrologAtom(rel.type)}, ${toPrologAtom(fromId)}, ${toPrologAtom(toId)}, [])`;
|
|
94
|
+
const result = await prolog.query(goal);
|
|
95
|
+
if (result.success) {
|
|
96
|
+
relCount++;
|
|
97
|
+
kbModified = true;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
failedRelationships.push({
|
|
101
|
+
rel,
|
|
102
|
+
fromId,
|
|
103
|
+
toId,
|
|
104
|
+
error: result.error || "Unknown error",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
110
|
+
const fromId = idLookup.get(rel.from) || rel.from;
|
|
111
|
+
const toId = idLookup.get(rel.to) || rel.to;
|
|
112
|
+
failedRelationships.push({ rel, fromId, toId, error: message });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Assert relationships from shards
|
|
117
|
+
for (const rel of shardRelationships) {
|
|
118
|
+
try {
|
|
119
|
+
const goal = `kb_assert_relationship(${toPrologAtom(rel.type)}, ${toPrologAtom(rel.from)}, ${toPrologAtom(rel.to)}, [])`;
|
|
120
|
+
const result = await prolog.query(goal);
|
|
121
|
+
if (result.success) {
|
|
122
|
+
relCount++;
|
|
123
|
+
kbModified = true;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
failedRelationships.push({
|
|
127
|
+
rel,
|
|
128
|
+
fromId: rel.from,
|
|
129
|
+
toId: rel.to,
|
|
130
|
+
error: result.error || "Unknown error",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
136
|
+
failedRelationships.push({
|
|
137
|
+
rel,
|
|
138
|
+
fromId: rel.from,
|
|
139
|
+
toId: rel.to,
|
|
140
|
+
error: message,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Retry failed relationships
|
|
145
|
+
const retryCount = 3;
|
|
146
|
+
for (let pass = 0; pass < retryCount && failedRelationships.length > 0; pass++) {
|
|
147
|
+
const remainingFailed = [];
|
|
148
|
+
for (const { rel, fromId, toId } of failedRelationships) {
|
|
149
|
+
try {
|
|
150
|
+
const goal = `kb_assert_relationship(${toPrologAtom(rel.type)}, ${toPrologAtom(fromId)}, ${toPrologAtom(toId)}, [])`;
|
|
151
|
+
const result = await prolog.query(goal);
|
|
152
|
+
if (result.success) {
|
|
153
|
+
relCount++;
|
|
154
|
+
kbModified = true;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
remainingFailed.push({
|
|
158
|
+
rel,
|
|
159
|
+
fromId,
|
|
160
|
+
toId,
|
|
161
|
+
error: result.error || "Unknown error",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
167
|
+
remainingFailed.push({ rel, fromId, toId, error: message });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
failedRelationships.length = 0;
|
|
171
|
+
failedRelationships.push(...remainingFailed);
|
|
172
|
+
}
|
|
173
|
+
// Log remaining failures
|
|
174
|
+
if (failedRelationships.length > 0) {
|
|
175
|
+
console.warn(`\nWarning: ${failedRelationships.length} relationship(s) failed to sync:`);
|
|
176
|
+
const seen = new Set();
|
|
177
|
+
for (const { rel, fromId, toId, error } of failedRelationships) {
|
|
178
|
+
const key = `${rel.type}:${fromId}->${toId}`;
|
|
179
|
+
if (!seen.has(key)) {
|
|
180
|
+
seen.add(key);
|
|
181
|
+
console.warn(` - ${rel.type}: ${fromId} -> ${toId}`);
|
|
182
|
+
console.warn(` Error: ${error}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
console.warn("\nTip: Ensure target entities exist before creating relationships.");
|
|
186
|
+
}
|
|
187
|
+
return { relationshipCount: relCount, kbModified };
|
|
188
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function prepareStagingEnvironment(stagingPath: string, livePath: string, rebuild: boolean): Promise<void>;
|
|
2
|
+
export declare function atomicPublish(stagingPath: string, livePath: string): void;
|
|
3
|
+
export declare function cleanupStaging(stagingPath: string): void;
|
|
4
|
+
//# sourceMappingURL=staging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/staging.ts"],"names":[],"mappings":"AAwBA,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,CAYf;AAuCD,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAczE;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAIxD"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, mkdirSync, renameSync, rmSync } from "node:fs";
|
|
19
|
+
import { copyFileSync } from "node:fs";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
import fg from "fast-glob";
|
|
22
|
+
import { copyCleanSnapshot } from "../../utils/branch-resolver.js";
|
|
23
|
+
export async function prepareStagingEnvironment(stagingPath, livePath, rebuild) {
|
|
24
|
+
// Cleanup any existing staging directory
|
|
25
|
+
cleanupStaging(stagingPath);
|
|
26
|
+
mkdirSync(stagingPath, { recursive: true });
|
|
27
|
+
if (!rebuild && existsSync(livePath)) {
|
|
28
|
+
// Use existing live path if available
|
|
29
|
+
copyCleanSnapshot(livePath, stagingPath);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Start fresh with schema only
|
|
33
|
+
await copySchemaToStaging(stagingPath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function copySchemaToStaging(stagingPath) {
|
|
37
|
+
const possibleSchemaPaths = [
|
|
38
|
+
path.resolve(process.cwd(), "node_modules", "kibi-cli", "schema"),
|
|
39
|
+
path.resolve(process.cwd(), "..", "..", "schema"),
|
|
40
|
+
path.resolve(import.meta.dirname || __dirname, "..", "..", "schema"),
|
|
41
|
+
path.resolve(process.cwd(), "packages", "cli", "schema"),
|
|
42
|
+
];
|
|
43
|
+
let schemaSourceDir = null;
|
|
44
|
+
for (const p of possibleSchemaPaths) {
|
|
45
|
+
if (existsSync(p)) {
|
|
46
|
+
schemaSourceDir = p;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!schemaSourceDir) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const schemaFiles = await fg("*.pl", {
|
|
54
|
+
cwd: schemaSourceDir,
|
|
55
|
+
absolute: false,
|
|
56
|
+
});
|
|
57
|
+
const schemaDestDir = path.join(stagingPath, "schema");
|
|
58
|
+
if (!existsSync(schemaDestDir)) {
|
|
59
|
+
mkdirSync(schemaDestDir, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
for (const file of schemaFiles) {
|
|
62
|
+
const sourcePath = path.join(schemaSourceDir, file);
|
|
63
|
+
const destPath = path.join(schemaDestDir, file);
|
|
64
|
+
copyFileSync(sourcePath, destPath);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function atomicPublish(stagingPath, livePath) {
|
|
68
|
+
const liveParent = path.dirname(livePath);
|
|
69
|
+
if (!existsSync(liveParent)) {
|
|
70
|
+
mkdirSync(liveParent, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
if (existsSync(livePath)) {
|
|
73
|
+
const tempPath = `${livePath}.old.${Date.now()}`;
|
|
74
|
+
renameSync(livePath, tempPath);
|
|
75
|
+
renameSync(stagingPath, livePath);
|
|
76
|
+
rmSync(tempPath, { recursive: true, force: true });
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
renameSync(stagingPath, livePath);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function cleanupStaging(stagingPath) {
|
|
83
|
+
if (existsSync(stagingPath)) {
|
|
84
|
+
rmSync(stagingPath, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
}
|
package/dist/commands/sync.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { SyncSummary } from "../diagnostics.js";
|
|
2
2
|
export declare class SyncError extends Error {
|
|
3
3
|
constructor(message: string);
|
|
4
4
|
}
|
|
@@ -6,4 +6,5 @@ export declare function syncCommand(options?: {
|
|
|
6
6
|
validateOnly?: boolean;
|
|
7
7
|
rebuild?: boolean;
|
|
8
8
|
}): Promise<SyncSummary>;
|
|
9
|
+
export { normalizeMarkdownPath } from "./sync/discovery.js";
|
|
9
10
|
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAyCjE,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAsB,WAAW,CAC/B,OAAO,GAAE;IACP,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACd,GACL,OAAO,CAAC,WAAW,CAAC,CA4VtB;AAGD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
|