kibi-cli 0.1.6 → 0.2.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.js +4 -2
- package/dist/commands/aggregated-checks.d.ts.map +1 -1
- package/dist/commands/aggregated-checks.js +16 -41
- package/dist/commands/branch.d.ts +4 -1
- package/dist/commands/branch.d.ts.map +1 -1
- package/dist/commands/branch.js +62 -12
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +42 -23
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +57 -0
- package/dist/commands/gc.d.ts.map +1 -1
- package/dist/commands/gc.js +24 -1
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +46 -28
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +23 -2
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +19 -10
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +348 -203
- package/dist/diagnostics.d.ts +61 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +114 -0
- package/dist/extractors/markdown.d.ts +1 -0
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +47 -0
- package/dist/public/branch-resolver.d.ts +2 -0
- package/dist/public/branch-resolver.d.ts.map +1 -0
- package/dist/public/branch-resolver.js +1 -0
- package/dist/traceability/git-staged.d.ts.map +1 -1
- package/dist/traceability/git-staged.js +13 -3
- package/dist/traceability/markdown-validate.d.ts +7 -0
- package/dist/traceability/markdown-validate.d.ts.map +1 -0
- package/dist/traceability/markdown-validate.js +35 -0
- package/dist/utils/branch-resolver.d.ts +79 -0
- package/dist/utils/branch-resolver.d.ts.map +1 -0
- package/dist/utils/branch-resolver.js +311 -0
- package/dist/utils/config.d.ts +47 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +105 -0
- package/package.json +15 -9
- package/src/public/branch-resolver.ts +1 -0
package/dist/cli.js
CHANGED
|
@@ -67,6 +67,7 @@ program
|
|
|
67
67
|
.command("sync")
|
|
68
68
|
.description("Sync entities from documents")
|
|
69
69
|
.option("--validate-only", "Perform validation without mutations")
|
|
70
|
+
.option("--rebuild", "Rebuild branch snapshot from scratch (discards current KB)")
|
|
70
71
|
.action(async (options) => {
|
|
71
72
|
await syncCommand(options);
|
|
72
73
|
});
|
|
@@ -114,9 +115,10 @@ program
|
|
|
114
115
|
.command("branch")
|
|
115
116
|
.description("Manage branch KBs")
|
|
116
117
|
.argument("<action>", "Action: ensure")
|
|
117
|
-
.
|
|
118
|
+
.option("--from <branch>", "Source branch to copy KB from")
|
|
119
|
+
.action(async (action, options) => {
|
|
118
120
|
if (action === "ensure") {
|
|
119
|
-
await branchEnsureCommand();
|
|
121
|
+
await branchEnsureCommand(options);
|
|
120
122
|
}
|
|
121
123
|
});
|
|
122
124
|
program.parse(process.argv);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregated-checks.d.ts","sourceRoot":"","sources":["../../src/commands/aggregated-checks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAmB,MAAM,cAAc,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"aggregated-checks.d.ts","sourceRoot":"","sources":["../../src/commands/aggregated-checks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAmB,MAAM,cAAc,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAU5C;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,EACrB,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,GACjC,OAAO,CAAC,SAAS,EAAE,CAAC,CAqDtB"}
|
|
@@ -7,27 +7,6 @@ import { resolveKbPlPath } from "../prolog.js";
|
|
|
7
7
|
*/
|
|
8
8
|
export async function runAggregatedChecks(prolog, rulesAllowlist) {
|
|
9
9
|
const violations = [];
|
|
10
|
-
// Build the check goal based on allowlist
|
|
11
|
-
const checkGoals = [];
|
|
12
|
-
const supportedRules = [
|
|
13
|
-
"must-priority-coverage",
|
|
14
|
-
"symbol-coverage",
|
|
15
|
-
"no-dangling-refs",
|
|
16
|
-
"no-cycles",
|
|
17
|
-
"required-fields",
|
|
18
|
-
"deprecated-adr-no-successor",
|
|
19
|
-
"domain-contradictions",
|
|
20
|
-
];
|
|
21
|
-
for (const rule of supportedRules) {
|
|
22
|
-
if (!rulesAllowlist || rulesAllowlist.has(rule)) {
|
|
23
|
-
// Convert kebab-case to snake_case for Prolog
|
|
24
|
-
const prologRule = rule.replace(/-/g, "_");
|
|
25
|
-
checkGoals.push(`findall(V, checks:check_${prologRule}(V), ${prologRule}_violations)`);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
if (checkGoals.length === 0) {
|
|
29
|
-
return violations;
|
|
30
|
-
}
|
|
31
10
|
const checksPlPath = path.join(path.dirname(resolveKbPlPath()), "checks.pl");
|
|
32
11
|
const checksPlPathEscaped = checksPlPath.replace(/'/g, "''");
|
|
33
12
|
const query = `(use_module('${checksPlPathEscaped}'), call(checks:check_all_json(JsonString)))`;
|
|
@@ -37,37 +16,33 @@ export async function runAggregatedChecks(prolog, rulesAllowlist) {
|
|
|
37
16
|
console.warn("Aggregated checks query failed, falling back to individual checks");
|
|
38
17
|
return [];
|
|
39
18
|
}
|
|
40
|
-
// Parse the JSON from the binding
|
|
41
19
|
let violationsDict;
|
|
42
20
|
try {
|
|
43
21
|
const jsonString = result.bindings.JsonString;
|
|
44
|
-
if (jsonString) {
|
|
45
|
-
violationsDict = JSON.parse(jsonString);
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
22
|
+
if (!jsonString) {
|
|
48
23
|
throw new Error("No JSON string in binding");
|
|
49
24
|
}
|
|
25
|
+
let parsed = JSON.parse(jsonString);
|
|
26
|
+
if (typeof parsed === "string") {
|
|
27
|
+
parsed = JSON.parse(parsed);
|
|
28
|
+
}
|
|
29
|
+
violationsDict = parsed;
|
|
50
30
|
}
|
|
51
31
|
catch (parseError) {
|
|
52
32
|
console.warn("Failed to parse violations JSON:", parseError);
|
|
53
33
|
return [];
|
|
54
34
|
}
|
|
55
|
-
|
|
56
|
-
for (const [ruleKey, ruleViolations] of Object.entries(violationsDict)) {
|
|
57
|
-
const rule = ruleKey.replace(/_/g, "-");
|
|
35
|
+
for (const ruleViolations of Object.values(violationsDict)) {
|
|
58
36
|
for (const v of ruleViolations) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
source: match[5] || undefined,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
37
|
+
const isAllowed = !rulesAllowlist || rulesAllowlist.has(v.rule);
|
|
38
|
+
if (isAllowed) {
|
|
39
|
+
violations.push({
|
|
40
|
+
rule: v.rule,
|
|
41
|
+
entityId: v.entityId,
|
|
42
|
+
description: v.description,
|
|
43
|
+
suggestion: v.suggestion || undefined,
|
|
44
|
+
source: v.source || undefined,
|
|
45
|
+
});
|
|
71
46
|
}
|
|
72
47
|
}
|
|
73
48
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface BranchEnsureOptions {
|
|
2
|
+
from?: string;
|
|
3
|
+
}
|
|
4
|
+
export declare function branchEnsureCommand(options?: BranchEnsureOptions): Promise<void>;
|
|
2
5
|
export default branchEnsureCommand;
|
|
3
6
|
//# sourceMappingURL=branch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/commands/branch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/commands/branch.ts"],"names":[],"mappings":"AAwDA,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA+DD,wBAAsB,mBAAmB,CACvC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED,eAAe,mBAAmB,CAAC"}
|
package/dist/commands/branch.js
CHANGED
|
@@ -42,25 +42,75 @@
|
|
|
42
42
|
fi
|
|
43
43
|
done
|
|
44
44
|
*/
|
|
45
|
-
import { execSync } from "node:child_process";
|
|
46
45
|
import * as fs from "node:fs";
|
|
47
46
|
import * as path from "node:path";
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
import { copyCleanSnapshot, getBranchDiagnostic, isValidBranchName, resolveActiveBranch, resolveDefaultBranch, } from "../utils/branch-resolver.js";
|
|
48
|
+
import { loadConfig } from "../utils/config.js";
|
|
49
|
+
function resolveExplicitFromBranch(fromBranch) {
|
|
50
|
+
if (!isValidBranchName(fromBranch)) {
|
|
51
|
+
console.warn(`Warning: invalid branch name provided via --from: '${fromBranch}'`);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const fromPath = path.join(process.cwd(), ".kb/branches", fromBranch);
|
|
55
|
+
if (fs.existsSync(fromPath)) {
|
|
56
|
+
return fromBranch;
|
|
57
|
+
}
|
|
58
|
+
console.warn(`Warning: --from branch '${fromBranch}' KB does not exist`);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function resolveDefaultSourceBranch() {
|
|
62
|
+
const config = loadConfig(process.cwd());
|
|
63
|
+
const defaultResult = resolveDefaultBranch(process.cwd(), config);
|
|
64
|
+
if ("branch" in defaultResult) {
|
|
65
|
+
const defaultBranch = defaultResult.branch;
|
|
66
|
+
const defaultPath = path.join(process.cwd(), ".kb/branches", defaultBranch);
|
|
67
|
+
if (fs.existsSync(defaultPath)) {
|
|
68
|
+
return defaultBranch;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.warn(`Warning: could not resolve default branch: ${defaultResult.error}`);
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
function determineSourceBranch(explicitFromBranch) {
|
|
77
|
+
if (explicitFromBranch) {
|
|
78
|
+
const fromResult = resolveExplicitFromBranch(explicitFromBranch);
|
|
79
|
+
if (fromResult) {
|
|
80
|
+
return fromResult;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return resolveDefaultSourceBranch();
|
|
84
|
+
}
|
|
85
|
+
function createBranchKbFromSource(sourceBranch, targetBranch) {
|
|
86
|
+
const sourcePath = path.join(process.cwd(), ".kb/branches", sourceBranch);
|
|
87
|
+
const targetPath = path.join(process.cwd(), ".kb/branches", targetBranch);
|
|
88
|
+
copyCleanSnapshot(sourcePath, targetPath);
|
|
89
|
+
console.log(`Created branch KB: ${targetBranch} (from ${sourceBranch})`);
|
|
90
|
+
}
|
|
91
|
+
function createEmptyBranchKb(branch) {
|
|
52
92
|
const kbPath = path.join(process.cwd(), ".kb/branches", branch);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
93
|
+
fs.mkdirSync(kbPath, { recursive: true });
|
|
94
|
+
console.log(`Created branch KB: ${branch} (empty schema)`);
|
|
95
|
+
}
|
|
96
|
+
export async function branchEnsureCommand(options) {
|
|
97
|
+
const branchResult = resolveActiveBranch(process.cwd());
|
|
98
|
+
if ("error" in branchResult) {
|
|
99
|
+
console.error(getBranchDiagnostic(undefined, branchResult.error));
|
|
100
|
+
throw new Error(`Failed to resolve active branch: ${branchResult.error}`);
|
|
101
|
+
}
|
|
102
|
+
const currentBranch = branchResult.branch;
|
|
103
|
+
const kbPath = path.join(process.cwd(), ".kb/branches", currentBranch);
|
|
104
|
+
if (fs.existsSync(kbPath)) {
|
|
105
|
+
console.log(`Branch KB already exists: ${currentBranch}`);
|
|
56
106
|
return;
|
|
57
107
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
108
|
+
const sourceBranch = determineSourceBranch(options?.from);
|
|
109
|
+
if (sourceBranch) {
|
|
110
|
+
createBranchKbFromSource(sourceBranch, currentBranch);
|
|
61
111
|
}
|
|
62
112
|
else {
|
|
63
|
-
|
|
113
|
+
createEmptyBranchKb(currentBranch);
|
|
64
114
|
}
|
|
65
115
|
}
|
|
66
116
|
export default branchEnsureCommand;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AA+DA,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA2OvE"}
|
package/dist/commands/check.js
CHANGED
|
@@ -45,11 +45,12 @@
|
|
|
45
45
|
import * as path from "node:path";
|
|
46
46
|
import { PrologProcess } from "../prolog.js";
|
|
47
47
|
import { getStagedFiles } from "../traceability/git-staged.js";
|
|
48
|
+
import { validateStagedMarkdown } from "../traceability/markdown-validate.js";
|
|
48
49
|
import { extractSymbolsFromStagedFile } from "../traceability/symbol-extract.js";
|
|
49
|
-
import { cleanupTempKb, consultOverlay, createOverlayFacts, createTempKb } from "../traceability/temp-kb.js";
|
|
50
|
-
import { formatViolations as formatStagedViolations, validateStagedSymbols } from "../traceability/validate.js";
|
|
51
|
-
import { getCurrentBranch } from "./init-helpers.js";
|
|
50
|
+
import { cleanupTempKb, consultOverlay, createOverlayFacts, createTempKb, } from "../traceability/temp-kb.js";
|
|
51
|
+
import { formatViolations as formatStagedViolations, validateStagedSymbols, } from "../traceability/validate.js";
|
|
52
52
|
import { runAggregatedChecks } from "./aggregated-checks.js";
|
|
53
|
+
import { getCurrentBranch } from "./init-helpers.js";
|
|
53
54
|
export async function checkCommand(options) {
|
|
54
55
|
try {
|
|
55
56
|
// Resolve KB path with priority:
|
|
@@ -86,9 +87,28 @@ export async function checkCommand(options) {
|
|
|
86
87
|
console.log("No staged files found.");
|
|
87
88
|
process.exit(0);
|
|
88
89
|
}
|
|
89
|
-
|
|
90
|
+
const codeFiles = stagedFiles.filter((f) => !f.path.endsWith(".md"));
|
|
91
|
+
const markdownFiles = stagedFiles.filter((f) => f.path.endsWith(".md"));
|
|
92
|
+
const markdownErrors = [];
|
|
93
|
+
for (const f of markdownFiles) {
|
|
94
|
+
const result = validateStagedMarkdown(f.path, f.content || "");
|
|
95
|
+
for (const err of result.errors) {
|
|
96
|
+
markdownErrors.push(err.toString());
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (markdownErrors.length > 0) {
|
|
100
|
+
console.log("Found embedded entity violations in staged markdown files:");
|
|
101
|
+
for (const err of markdownErrors) {
|
|
102
|
+
console.log(err);
|
|
103
|
+
console.log();
|
|
104
|
+
}
|
|
105
|
+
if (options.dryRun) {
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
90
110
|
const allSymbols = [];
|
|
91
|
-
for (const f of
|
|
111
|
+
for (const f of codeFiles) {
|
|
92
112
|
try {
|
|
93
113
|
const symbols = extractSymbolsFromStagedFile(f);
|
|
94
114
|
if (symbols && symbols.length) {
|
|
@@ -99,8 +119,12 @@ export async function checkCommand(options) {
|
|
|
99
119
|
console.error(`Error extracting symbols from staged file ${f.path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
100
120
|
}
|
|
101
121
|
}
|
|
122
|
+
if (allSymbols.length === 0 && markdownFiles.length === 0) {
|
|
123
|
+
console.log("No exported symbols or markdown entities found in staged files.");
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
102
126
|
if (allSymbols.length === 0) {
|
|
103
|
-
console.log("No
|
|
127
|
+
console.log("✓ No violations found in staged files.");
|
|
104
128
|
process.exit(0);
|
|
105
129
|
}
|
|
106
130
|
// Create temp KB
|
|
@@ -111,7 +135,10 @@ export async function checkCommand(options) {
|
|
|
111
135
|
await fs.writeFile(tempCtx.overlayPath, overlayFacts, "utf8");
|
|
112
136
|
await consultOverlay(tempCtx);
|
|
113
137
|
// Validate staged symbols using the temp KB prolog session
|
|
114
|
-
const violationsRaw = await validateStagedSymbols({
|
|
138
|
+
const violationsRaw = await validateStagedSymbols({
|
|
139
|
+
minLinks,
|
|
140
|
+
prolog: tempCtx.prolog,
|
|
141
|
+
});
|
|
115
142
|
const violationsFormatted = formatStagedViolations(violationsRaw);
|
|
116
143
|
if (violationsRaw && violationsRaw.length > 0) {
|
|
117
144
|
console.log(violationsFormatted);
|
|
@@ -167,16 +194,16 @@ export async function checkCommand(options) {
|
|
|
167
194
|
// This is significantly faster in Bun/Docker environments where one-shot mode
|
|
168
195
|
// spawns a new Prolog process for each query
|
|
169
196
|
const supportedRules = [
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
197
|
+
"must-priority-coverage",
|
|
198
|
+
"symbol-coverage",
|
|
199
|
+
"no-dangling-refs",
|
|
200
|
+
"no-cycles",
|
|
201
|
+
"required-fields",
|
|
202
|
+
"deprecated-adr-no-successor",
|
|
203
|
+
"domain-contradictions",
|
|
177
204
|
];
|
|
178
205
|
const canUseAggregated = !rulesAllowlist ||
|
|
179
|
-
Array.from(rulesAllowlist).every(r => supportedRules.includes(r));
|
|
206
|
+
Array.from(rulesAllowlist).every((r) => supportedRules.includes(r));
|
|
180
207
|
if (canUseAggregated) {
|
|
181
208
|
// Fast path: single Prolog call returning all violations
|
|
182
209
|
const aggregatedViolations = await runAggregatedChecks(prolog, rulesAllowlist);
|
|
@@ -193,14 +220,6 @@ export async function checkCommand(options) {
|
|
|
193
220
|
await runCheck("deprecated-adr-no-successor", checkDeprecatedAdrs);
|
|
194
221
|
await runCheck("domain-contradictions", checkDomainContradictions);
|
|
195
222
|
}
|
|
196
|
-
await runCheck("must-priority-coverage", checkMustPriorityCoverage);
|
|
197
|
-
await runCheck("symbol-coverage", checkSymbolCoverage);
|
|
198
|
-
await runCheck("no-dangling-refs", checkNoDanglingRefs);
|
|
199
|
-
await runCheck("no-cycles", checkNoCycles);
|
|
200
|
-
const allEntityIds = await getAllEntityIds(prolog);
|
|
201
|
-
await runCheck("required-fields", checkRequiredFields, allEntityIds);
|
|
202
|
-
await runCheck("deprecated-adr-no-successor", checkDeprecatedAdrs);
|
|
203
|
-
await runCheck("domain-contradictions", checkDomainContradictions);
|
|
204
223
|
await prolog.query("kb_detach");
|
|
205
224
|
await prolog.terminate();
|
|
206
225
|
if (violations.length === 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAsDA,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAsDA,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CA0DnD"}
|
package/dist/commands/doctor.js
CHANGED
|
@@ -71,6 +71,10 @@ export async function doctorCommand() {
|
|
|
71
71
|
name: "pre-commit hook",
|
|
72
72
|
check: checkPreCommitHook,
|
|
73
73
|
},
|
|
74
|
+
{
|
|
75
|
+
name: "post-rewrite hook",
|
|
76
|
+
check: checkPostRewriteHook,
|
|
77
|
+
},
|
|
74
78
|
];
|
|
75
79
|
console.log("Kibi Environment Diagnostics\n");
|
|
76
80
|
let allPassed = true;
|
|
@@ -287,3 +291,56 @@ function checkPreCommitHook() {
|
|
|
287
291
|
};
|
|
288
292
|
}
|
|
289
293
|
}
|
|
294
|
+
function checkPostRewriteHook() {
|
|
295
|
+
const postCheckoutPath = path.join(process.cwd(), ".git/hooks/post-checkout");
|
|
296
|
+
const postMergePath = path.join(process.cwd(), ".git/hooks/post-merge");
|
|
297
|
+
const postRewritePath = path.join(process.cwd(), ".git/hooks/post-rewrite");
|
|
298
|
+
const postCheckoutExists = existsSync(postCheckoutPath);
|
|
299
|
+
const postMergeExists = existsSync(postMergePath);
|
|
300
|
+
if (!postCheckoutExists && !postMergeExists) {
|
|
301
|
+
return {
|
|
302
|
+
passed: true,
|
|
303
|
+
message: "Not installed (optional)",
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const postRewriteExists = existsSync(postRewritePath);
|
|
307
|
+
if (!postRewriteExists) {
|
|
308
|
+
return {
|
|
309
|
+
passed: false,
|
|
310
|
+
message: "Not installed",
|
|
311
|
+
remediation: "Run: kibi init --hooks",
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
const postRewriteStats = statSync(postRewritePath);
|
|
316
|
+
const postRewriteExecutable = (postRewriteStats.mode & 0o111) !== 0;
|
|
317
|
+
// Read hook content to verify it invokes kibi
|
|
318
|
+
const content = readFileSync(postRewritePath, "utf-8");
|
|
319
|
+
const usesKibi = content.includes("kibi sync");
|
|
320
|
+
if (!usesKibi) {
|
|
321
|
+
return {
|
|
322
|
+
passed: false,
|
|
323
|
+
message: "post-rewrite hook installed but does not invoke kibi",
|
|
324
|
+
remediation: "Run: kibi init --hooks to install recommended hooks",
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
if (postRewriteExecutable) {
|
|
328
|
+
return {
|
|
329
|
+
passed: true,
|
|
330
|
+
message: "Installed and executable",
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
passed: false,
|
|
335
|
+
message: "Installed but not executable",
|
|
336
|
+
remediation: "Run: chmod +x .git/hooks/post-rewrite",
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
return {
|
|
341
|
+
passed: false,
|
|
342
|
+
message: "Unable to check hook permissions or read content",
|
|
343
|
+
remediation: "Run: kibi init --hooks",
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gc.d.ts","sourceRoot":"","sources":["../../src/commands/gc.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gc.d.ts","sourceRoot":"","sources":["../../src/commands/gc.ts"],"names":[],"mappings":"AAmDA,wBAAsB,SAAS,CAAC,OAAO,EAAE;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,iBAsGA;AAED,eAAe,SAAS,CAAC"}
|
package/dist/commands/gc.js
CHANGED
|
@@ -45,6 +45,8 @@
|
|
|
45
45
|
import { execSync } from "node:child_process";
|
|
46
46
|
import * as fs from "node:fs";
|
|
47
47
|
import * as path from "node:path";
|
|
48
|
+
import { resolveDefaultBranch } from "../utils/branch-resolver.js";
|
|
49
|
+
import { loadConfig } from "../utils/config.js";
|
|
48
50
|
export async function gcCommand(options) {
|
|
49
51
|
// If force is true, perform deletion. Otherwise default to dry run.
|
|
50
52
|
const dryRun = options?.force ? false : (options?.dryRun ?? true);
|
|
@@ -85,7 +87,28 @@ export async function gcCommand(options) {
|
|
|
85
87
|
.readdirSync(kbRoot, { withFileTypes: true })
|
|
86
88
|
.filter((d) => d.isDirectory())
|
|
87
89
|
.map((d) => d.name);
|
|
88
|
-
|
|
90
|
+
// Resolve configured/default branch to protect
|
|
91
|
+
const config = loadConfig(process.cwd());
|
|
92
|
+
// Prefer explicit configured defaultBranch if set
|
|
93
|
+
const configured = config?.defaultBranch;
|
|
94
|
+
let defaultBranch;
|
|
95
|
+
if (configured && typeof configured === "string" && configured.trim()) {
|
|
96
|
+
defaultBranch = configured.trim();
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
const resolved = resolveDefaultBranch(process.cwd(), config);
|
|
100
|
+
defaultBranch =
|
|
101
|
+
"branch" in resolved && typeof resolved.branch === "string"
|
|
102
|
+
? resolved.branch
|
|
103
|
+
: "main";
|
|
104
|
+
}
|
|
105
|
+
// Protect resolved branch and its 'master'->'main' normalization
|
|
106
|
+
const protectedBranches = new Set([defaultBranch]);
|
|
107
|
+
if (defaultBranch === "main")
|
|
108
|
+
protectedBranches.add("master");
|
|
109
|
+
if (defaultBranch === "master")
|
|
110
|
+
protectedBranches.add("main");
|
|
111
|
+
const staleBranches = kbBranches.filter((kb) => !protectedBranches.has(kb) && !gitBranches.has(kb));
|
|
89
112
|
// Perform deletion when dryRun is false (force requested)
|
|
90
113
|
const performDelete = !dryRun;
|
|
91
114
|
let deletedCount = 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init-helpers.d.ts","sourceRoot":"","sources":["../../src/commands/init-helpers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init-helpers.d.ts","sourceRoot":"","sources":["../../src/commands/init-helpers.ts"],"names":[],"mappings":"AA8GA,wBAAsB,gBAAgB,CACpC,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,IAAI,CAQN;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAMpD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAajD;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAuBnE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAiBpD"}
|
|
@@ -45,44 +45,60 @@
|
|
|
45
45
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
46
46
|
import * as path from "node:path";
|
|
47
47
|
import fg from "fast-glob";
|
|
48
|
+
import { getBranchDiagnostic, resolveActiveBranch, } from "../utils/branch-resolver.js";
|
|
49
|
+
import { DEFAULT_CONFIG } from "../utils/config.js";
|
|
48
50
|
const POST_CHECKOUT_HOOK = `#!/bin/sh
|
|
49
|
-
kibi
|
|
51
|
+
# post-checkout hook for kibi
|
|
52
|
+
# Parameters: old_ref new_ref branch_flag
|
|
53
|
+
# branch_flag is 1 for branch checkout, 0 for file checkout
|
|
54
|
+
|
|
55
|
+
old_ref=$1
|
|
56
|
+
new_ref=$2
|
|
57
|
+
branch_flag=$3
|
|
58
|
+
|
|
59
|
+
if [ "$branch_flag" = "1" ]; then
|
|
60
|
+
# Try to resolve the branch we just left (strip decorations like ^ and ~)
|
|
61
|
+
old_branch=$(git name-rev --name-only "$old_ref" 2>/dev/null | sed 's/\^.*//')
|
|
62
|
+
|
|
63
|
+
# Basic validation: non-empty and does not contain ~ or ^
|
|
64
|
+
if [ -n "$old_branch" ] && echo "$old_branch" | grep -qv '[~^]'; then
|
|
65
|
+
kibi branch ensure --from "$old_branch" && kibi sync
|
|
66
|
+
else
|
|
67
|
+
kibi branch ensure && kibi sync
|
|
68
|
+
fi
|
|
69
|
+
fi
|
|
50
70
|
`;
|
|
51
71
|
const POST_MERGE_HOOK = `#!/bin/sh
|
|
72
|
+
# post-merge hook for kibi
|
|
73
|
+
# Parameter: squash_flag (not used)
|
|
74
|
+
|
|
52
75
|
kibi sync
|
|
53
76
|
`;
|
|
77
|
+
const POST_REWRITE_HOOK = `#!/bin/sh
|
|
78
|
+
# post-rewrite hook for kibi
|
|
79
|
+
# Triggered after git rebase, git commit --amend, etc.
|
|
80
|
+
# Parameter: rewrite_type (rebase or amend)
|
|
81
|
+
|
|
82
|
+
rewrite_type=$1
|
|
83
|
+
|
|
84
|
+
if [ "$rewrite_type" = "rebase" ]; then
|
|
85
|
+
kibi sync
|
|
86
|
+
fi
|
|
87
|
+
`;
|
|
54
88
|
const PRE_COMMIT_HOOK = `#!/bin/sh
|
|
89
|
+
# pre-commit hook for kibi
|
|
90
|
+
# Blocks commits if kibi check finds violations
|
|
91
|
+
|
|
55
92
|
set -e
|
|
56
93
|
kibi check --staged
|
|
57
94
|
`;
|
|
58
|
-
const DEFAULT_CONFIG = {
|
|
59
|
-
paths: {
|
|
60
|
-
requirements: "requirements",
|
|
61
|
-
scenarios: "scenarios",
|
|
62
|
-
tests: "tests",
|
|
63
|
-
adr: "adr",
|
|
64
|
-
flags: "flags",
|
|
65
|
-
events: "events",
|
|
66
|
-
facts: "facts",
|
|
67
|
-
symbols: "symbols.yaml",
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
95
|
export async function getCurrentBranch(cwd = process.cwd()) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
cwd,
|
|
76
|
-
encoding: "utf8",
|
|
77
|
-
}).trim();
|
|
78
|
-
if (branch && branch !== "master") {
|
|
79
|
-
currentBranch = branch;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
currentBranch = "develop";
|
|
96
|
+
const result = resolveActiveBranch(cwd);
|
|
97
|
+
if ("error" in result) {
|
|
98
|
+
console.error(getBranchDiagnostic(undefined, result.error));
|
|
99
|
+
throw new Error(`Failed to resolve active branch: ${result.error}`);
|
|
84
100
|
}
|
|
85
|
-
return
|
|
101
|
+
return result.branch;
|
|
86
102
|
}
|
|
87
103
|
export function createKbDirectoryStructure(kbDir, currentBranch) {
|
|
88
104
|
mkdirSync(kbDir, { recursive: true });
|
|
@@ -144,9 +160,11 @@ export function installGitHooks(gitDir) {
|
|
|
144
160
|
mkdirSync(hooksDir, { recursive: true });
|
|
145
161
|
const postCheckoutPath = path.join(hooksDir, "post-checkout");
|
|
146
162
|
const postMergePath = path.join(hooksDir, "post-merge");
|
|
163
|
+
const postRewritePath = path.join(hooksDir, "post-rewrite");
|
|
147
164
|
const preCommitPath = path.join(hooksDir, "pre-commit");
|
|
148
165
|
installHook(postCheckoutPath, POST_CHECKOUT_HOOK.replace("#!/bin/sh\n", ""));
|
|
149
166
|
installHook(postMergePath, POST_MERGE_HOOK.replace("#!/bin/sh\n", ""));
|
|
167
|
+
installHook(postRewritePath, POST_REWRITE_HOOK.replace("#!/bin/sh\n", ""));
|
|
150
168
|
installHook(preCommitPath, PRE_COMMIT_HOOK.replace("#!/bin/sh\n", ""));
|
|
151
|
-
console.log("✓ Installed git hooks (pre-commit, post-checkout, post-merge)");
|
|
169
|
+
console.log("✓ Installed git hooks (pre-commit, post-checkout, post-merge, post-rewrite)");
|
|
152
170
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA4DA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA4DA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DrE"}
|
package/dist/commands/init.js
CHANGED
|
@@ -45,13 +45,34 @@
|
|
|
45
45
|
import { existsSync } from "node:fs";
|
|
46
46
|
import * as path from "node:path";
|
|
47
47
|
import { fileURLToPath } from "node:url";
|
|
48
|
-
import {
|
|
48
|
+
import { resolveActiveBranch } from "../utils/branch-resolver.js";
|
|
49
|
+
import { copySchemaFiles, createConfigFile, createKbDirectoryStructure, installGitHooks, updateGitIgnore, } from "./init-helpers.js";
|
|
49
50
|
const __filename = fileURLToPath(import.meta.url);
|
|
50
51
|
const __dirname = path.dirname(__filename);
|
|
51
52
|
export async function initCommand(options) {
|
|
52
53
|
const kbDir = path.join(process.cwd(), ".kb");
|
|
53
54
|
const kbExists = existsSync(kbDir);
|
|
54
|
-
|
|
55
|
+
// Resolve branch: allow non-git repos to use default "main" for init
|
|
56
|
+
let currentBranch;
|
|
57
|
+
const result = resolveActiveBranch();
|
|
58
|
+
if ("error" in result) {
|
|
59
|
+
const isNonGitError = result.code === "NOT_A_GIT_REPO" || result.code === "GIT_NOT_AVAILABLE";
|
|
60
|
+
if (isNonGitError) {
|
|
61
|
+
// For init command, use "main" as default branch when not in a git repo.
|
|
62
|
+
// This allows initialization before git init, which is useful for first-time setup.
|
|
63
|
+
console.warn("Warning: Not in a git repository");
|
|
64
|
+
console.warn("Using 'main' as default branch. Run 'kibi sync' in a git repo for proper branch-aware behavior.");
|
|
65
|
+
currentBranch = "main";
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.error("Error: Failed to resolve the active git branch.");
|
|
69
|
+
console.error(result.error);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
currentBranch = result.branch;
|
|
75
|
+
}
|
|
55
76
|
try {
|
|
56
77
|
if (!kbExists) {
|
|
57
78
|
createKbDirectoryStructure(kbDir, currentBranch);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/commands/query.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/commands/query.ts"],"names":[],"mappings":"AAqDA,UAAU,YAAY;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAmLf"}
|
package/dist/commands/query.js
CHANGED
|
@@ -46,24 +46,33 @@ import * as path from "node:path";
|
|
|
46
46
|
import Table from "cli-table3";
|
|
47
47
|
import { PrologProcess } from "../prolog.js";
|
|
48
48
|
import relationshipSchema from "../public/schemas/relationship.js";
|
|
49
|
+
import { resolveActiveBranch } from "../utils/branch-resolver.js";
|
|
49
50
|
const REL_TYPES = relationshipSchema.properties.type.enum;
|
|
50
51
|
export async function queryCommand(type, options) {
|
|
51
52
|
try {
|
|
52
53
|
const prolog = new PrologProcess({ timeout: 120000 });
|
|
53
54
|
await prolog.start();
|
|
54
55
|
await prolog.query("set_prolog_flag(answer_write_options, [max_depth(0), spacing(next_argument)])");
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
// Resolve branch: allow non-git repos to use default "main" for query
|
|
57
|
+
let currentBranch;
|
|
58
|
+
const branchResult = resolveActiveBranch();
|
|
59
|
+
if ("error" in branchResult) {
|
|
60
|
+
const isNonGitError = branchResult.code === "NOT_A_GIT_REPO" ||
|
|
61
|
+
branchResult.code === "GIT_NOT_AVAILABLE";
|
|
62
|
+
if (isNonGitError) {
|
|
63
|
+
// For query command, use "main" as default branch when git is not available
|
|
64
|
+
// or the current directory is not a git repository. This allows querying
|
|
65
|
+
// after init in a non-git directory.
|
|
63
66
|
currentBranch = "main";
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.error(`Error: Failed to resolve active branch:\n${branchResult.error}`);
|
|
70
|
+
await prolog.terminate();
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
64
73
|
}
|
|
65
|
-
|
|
66
|
-
currentBranch =
|
|
74
|
+
else {
|
|
75
|
+
currentBranch = branchResult.branch;
|
|
67
76
|
}
|
|
68
77
|
const kbPath = path.join(process.cwd(), `.kb/branches/${currentBranch}`);
|
|
69
78
|
const attachResult = await prolog.query(`kb_attach('${kbPath}')`);
|