@fenglimg/fabric-cli 2.1.0-rc.2 → 2.2.0-rc.3
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/{chunk-PWLW3B57.js → chunk-2CY4BMTH.js} +5 -1
- package/dist/chunk-5LQIHYFC.js +64 -0
- package/dist/chunk-5ZUMLCD5.js +248 -0
- package/dist/{chunk-WWNXR34K.js → chunk-BO4XIZWZ.js} +8 -1
- package/dist/chunk-EOT63RDH.js +36 -0
- package/dist/{chunk-BATF4PEJ.js → chunk-F6ITRM7T.js} +4 -4
- package/dist/{chunk-WU6GAPKH.js → chunk-H3FE6VIK.js} +3 -5
- package/dist/{chunk-MF3OTILQ.js → chunk-XC5RUHLK.js} +29 -8
- package/dist/chunk-XCBVSGCS.js +25 -0
- package/dist/{chunk-F46ORPOA.js → chunk-XHHCRDIR.js} +149 -7
- package/dist/{config-XJIPZNUP.js → config-VJMXCLXW.js} +3 -3
- package/dist/{doctor-QVNPHLJK.js → doctor-J4O3X54I.js} +154 -30
- package/dist/index.js +57 -16
- package/dist/{install-2HDO5FTQ.js → install-BULNDUIM.js} +241 -108
- package/dist/{metrics-ACEQFPDU.js → metrics-RER6NLFC.js} +22 -9
- package/dist/{onboard-coverage-MFCAEBDO.js → onboard-coverage-JWQWDZW7.js} +1 -1
- package/dist/{plan-context-hint-FC6P3WFE.js → plan-context-hint-CHVZGOZ5.js} +21 -8
- package/dist/{scope-explain-2F2R5URO.js → scope-explain-BWRWBCCP.js} +19 -5
- package/dist/{status-GLQWLWH6.js → status-PANEGKU2.js} +17 -6
- package/dist/store-66NK2FTQ.js +443 -0
- package/dist/sync-EA5HZMXM.js +395 -0
- package/dist/{uninstall-TAXSUSKH.js → uninstall-F75MPKQC.js} +61 -4
- package/dist/whoami-66YKY5DZ.js +47 -0
- package/package.json +3 -3
- package/templates/hooks/cite-policy-evict.cjs +412 -160
- package/templates/hooks/configs/claude-code.json +17 -2
- package/templates/hooks/configs/codex-hooks.json +14 -2
- package/templates/hooks/configs/cursor-hooks.json +14 -2
- package/templates/hooks/fabric-hint.cjs +247 -19
- package/templates/hooks/knowledge-hint-broad.cjs +176 -10
- package/templates/hooks/knowledge-hint-narrow.cjs +64 -5
- package/templates/hooks/lib/injection-log.cjs +91 -0
- package/templates/hooks/lib/state-store.cjs +30 -11
- package/templates/hooks/post-tooluse-mutation.cjs +285 -0
- package/templates/hooks/session-end-marker.cjs +140 -0
- package/templates/skills/fabric-archive/SKILL.md +7 -1
- package/templates/skills/fabric-audit/SKILL.md +53 -0
- package/templates/skills/fabric-connect/SKILL.md +48 -0
- package/templates/skills/fabric-review/SKILL.md +2 -0
- package/templates/skills/fabric-review/ref/cite-contract.md +56 -0
- package/templates/skills/fabric-store/SKILL.md +44 -0
- package/dist/chunk-HFQVXY6P.js +0 -86
- package/dist/chunk-L4Q55UC4.js +0 -52
- package/dist/chunk-LFIKMVY7.js +0 -27
- package/dist/chunk-RYAFBNES.js +0 -33
- package/dist/chunk-T5RPGCCM.js +0 -40
- package/dist/store-XTSE5TY6.js +0 -105
- package/dist/sync-BJCWDPNC.js +0 -245
- package/dist/whoami-B6AEMSEV.js +0 -31
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getProjectTranslator
|
|
4
|
+
} from "./chunk-2CY4BMTH.js";
|
|
2
5
|
|
|
3
6
|
// src/commands/metrics.ts
|
|
4
7
|
import { resolve } from "path";
|
|
5
8
|
import { defineCommand } from "citty";
|
|
6
9
|
import { readMetrics } from "@fenglimg/fabric-server";
|
|
7
|
-
function parseSinceArg(raw) {
|
|
10
|
+
function parseSinceArg(raw, t) {
|
|
8
11
|
if (raw === void 0 || raw.length === 0) return 0;
|
|
9
12
|
const match = /^(\d+)([smhd]?)$/u.exec(raw);
|
|
10
13
|
if (match === null) {
|
|
11
|
-
throw new Error(
|
|
14
|
+
throw new Error(t("cli.metrics.invalid-since", { raw }));
|
|
12
15
|
}
|
|
13
16
|
const n = Number.parseInt(match[1], 10);
|
|
14
17
|
const unit = match[2] ?? "s";
|
|
@@ -36,6 +39,8 @@ function aggregate(rows, sinceMs, now) {
|
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
return {
|
|
42
|
+
// Stable token (NOT localized) so the --json contract is locale-independent;
|
|
43
|
+
// renderText localizes "all-time" at presentation time only.
|
|
39
44
|
windowDescription: sinceMs > 0 ? formatDuration(sinceMs) : "all-time",
|
|
40
45
|
rowCount: filtered.length,
|
|
41
46
|
totals,
|
|
@@ -50,17 +55,24 @@ function formatDuration(ms) {
|
|
|
50
55
|
if (ms >= 6e4) return `${Math.round(ms / 6e4)}m`;
|
|
51
56
|
return `${Math.round(ms / 1e3)}s`;
|
|
52
57
|
}
|
|
53
|
-
function renderText(agg) {
|
|
58
|
+
function renderText(agg, t) {
|
|
54
59
|
const lines = [];
|
|
55
|
-
|
|
60
|
+
const windowDisplay = agg.windowDescription === "all-time" ? t("cli.metrics.window-all-time") : agg.windowDescription;
|
|
61
|
+
lines.push(t("cli.metrics.window", { window: windowDisplay }));
|
|
56
62
|
if (agg.rangeStart && agg.rangeEnd) {
|
|
57
|
-
lines.push(
|
|
63
|
+
lines.push(
|
|
64
|
+
t("cli.metrics.rows-range", {
|
|
65
|
+
count: String(agg.rowCount),
|
|
66
|
+
start: agg.rangeStart,
|
|
67
|
+
end: agg.rangeEnd
|
|
68
|
+
})
|
|
69
|
+
);
|
|
58
70
|
} else {
|
|
59
|
-
lines.push(
|
|
71
|
+
lines.push(t("cli.metrics.rows", { count: String(agg.rowCount) }));
|
|
60
72
|
}
|
|
61
73
|
lines.push("");
|
|
62
74
|
if (Object.keys(agg.totals).length === 0) {
|
|
63
|
-
lines.push("
|
|
75
|
+
lines.push(t("cli.metrics.no-activity"));
|
|
64
76
|
return lines.join("\n");
|
|
65
77
|
}
|
|
66
78
|
lines.push(" counter total");
|
|
@@ -103,14 +115,15 @@ var metricsCommand = defineCommand({
|
|
|
103
115
|
},
|
|
104
116
|
async run({ args }) {
|
|
105
117
|
const projectRoot = resolve(args.target ?? process.cwd());
|
|
106
|
-
const
|
|
118
|
+
const t = getProjectTranslator(projectRoot);
|
|
119
|
+
const sinceMs = parseSinceArg(args.since, t);
|
|
107
120
|
const rows = await readMetrics(projectRoot);
|
|
108
121
|
const aggregated = aggregate(rows, sinceMs, /* @__PURE__ */ new Date());
|
|
109
122
|
if (args.json === true) {
|
|
110
123
|
process.stdout.write(`${JSON.stringify(aggregated)}
|
|
111
124
|
`);
|
|
112
125
|
} else {
|
|
113
|
-
process.stdout.write(`${renderText(aggregated)}
|
|
126
|
+
process.stdout.write(`${renderText(aggregated, t)}
|
|
114
127
|
`);
|
|
115
128
|
}
|
|
116
129
|
}
|
|
@@ -56,16 +56,29 @@ async function runPlanContextHint(opts) {
|
|
|
56
56
|
const targetPaths = all ? [ALL_PATHS_SENTINEL] : explicitPaths.length > 0 ? explicitPaths : [ALL_PATHS_SENTINEL];
|
|
57
57
|
const resolution = resolveDevMode(opts.target, process.cwd());
|
|
58
58
|
const result = await planContext(resolution.target, {
|
|
59
|
-
paths: targetPaths
|
|
59
|
+
paths: targetPaths,
|
|
60
|
+
// lifecycle-refactor W3-T2 (§7 图谱消费 / §5): default-enable graph二阶召回 for
|
|
61
|
+
// the hint path. planContext appends the one-hop `related` neighbours that
|
|
62
|
+
// ranked outside top_k of the surfaced set and reports them in
|
|
63
|
+
// `related_appended` (appended id → source id). Honest no-op when the
|
|
64
|
+
// surfaced set declares no in-corpus related edge.
|
|
65
|
+
include_related: true
|
|
60
66
|
});
|
|
61
67
|
const candidates = result.candidates;
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
const relatedAppended = result.related_appended ?? {};
|
|
69
|
+
const entries = candidates.map((item) => {
|
|
70
|
+
const relatedTo = relatedAppended[item.stable_id];
|
|
71
|
+
return {
|
|
72
|
+
id: item.stable_id,
|
|
73
|
+
type: item.description.knowledge_type ?? "",
|
|
74
|
+
maturity: item.description.maturity ?? "",
|
|
75
|
+
summary: item.description.summary,
|
|
76
|
+
relevance_scope: item.description.relevance_scope ?? "broad",
|
|
77
|
+
// Only set when this entry was pulled in via a graph edge — its presence
|
|
78
|
+
// is the honest signal, never synthesized for ordinarily-ranked entries.
|
|
79
|
+
...typeof relatedTo === "string" ? { related_to: relatedTo } : {}
|
|
80
|
+
};
|
|
81
|
+
});
|
|
69
82
|
let narrow_count = 0;
|
|
70
83
|
let broad_only_count = 0;
|
|
71
84
|
for (const e of entries) {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
scopeExplain
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import
|
|
6
|
-
|
|
4
|
+
} from "./chunk-EOT63RDH.js";
|
|
5
|
+
import {
|
|
6
|
+
getProjectTranslator
|
|
7
|
+
} from "./chunk-2CY4BMTH.js";
|
|
7
8
|
|
|
8
9
|
// src/commands/scope-explain.ts
|
|
9
10
|
import { defineCommand } from "citty";
|
|
11
|
+
import { FabricError } from "@fenglimg/fabric-shared/errors";
|
|
10
12
|
var scope_explain_default = defineCommand({
|
|
11
13
|
meta: {
|
|
12
14
|
name: "scope-explain",
|
|
@@ -20,9 +22,21 @@ var scope_explain_default = defineCommand({
|
|
|
20
22
|
}
|
|
21
23
|
},
|
|
22
24
|
run({ args }) {
|
|
23
|
-
const
|
|
25
|
+
const projectRoot = process.cwd();
|
|
26
|
+
let result;
|
|
27
|
+
try {
|
|
28
|
+
result = scopeExplain(projectRoot, args.scope);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error instanceof FabricError) {
|
|
31
|
+
console.error(`${error.message}
|
|
32
|
+
\u2192 ${error.actionHint}`);
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
24
38
|
if (result === null) {
|
|
25
|
-
console.log("no
|
|
39
|
+
console.log(getProjectTranslator(projectRoot)("cli.cmd.no-global-config"));
|
|
26
40
|
return;
|
|
27
41
|
}
|
|
28
42
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
projectStatus
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import "./chunk-
|
|
3
|
+
projectStatus,
|
|
4
|
+
warnUnknownFlags
|
|
5
|
+
} from "./chunk-5LQIHYFC.js";
|
|
6
|
+
import "./chunk-5ZUMLCD5.js";
|
|
7
|
+
import "./chunk-XCBVSGCS.js";
|
|
7
8
|
|
|
8
9
|
// src/commands/status.ts
|
|
9
10
|
import { defineCommand } from "citty";
|
|
10
11
|
var status_default = defineCommand({
|
|
11
12
|
meta: { name: "status", description: "Show this project's Fabric store status" },
|
|
12
|
-
|
|
13
|
+
args: {
|
|
14
|
+
// F27: `--json` machine-readable output (was silently ignored pre-F27).
|
|
15
|
+
json: { type: "boolean", description: "Emit machine-readable JSON instead of text" }
|
|
16
|
+
},
|
|
17
|
+
run({ args }) {
|
|
18
|
+
warnUnknownFlags(["json"]);
|
|
13
19
|
const status = projectStatus(process.cwd());
|
|
20
|
+
if (args.json === true) {
|
|
21
|
+
console.log(JSON.stringify(status, null, 2));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
14
24
|
console.log(`uid: ${status.uid ?? "(no global config)"}`);
|
|
15
|
-
|
|
25
|
+
const projectIdLabel = status.project_id ?? (status.is_fabric_project ? "(unset)" : "(not a Fabric project)");
|
|
26
|
+
console.log(`project_id: ${projectIdLabel}`);
|
|
16
27
|
console.log(`mounted stores: ${status.mounted.length > 0 ? status.mounted.join(", ") : "(none)"}`);
|
|
17
28
|
console.log(`required: ${status.required.length > 0 ? status.required.join(", ") : "(none)"}`);
|
|
18
29
|
console.log(`active write: ${status.active_write_store ?? "(none \u2014 personal scope only)"}`);
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
regenerateBindingsSnapshot
|
|
4
|
+
} from "./chunk-H3FE6VIK.js";
|
|
5
|
+
import "./chunk-EOT63RDH.js";
|
|
6
|
+
import {
|
|
7
|
+
getProjectTranslator
|
|
8
|
+
} from "./chunk-2CY4BMTH.js";
|
|
9
|
+
import {
|
|
10
|
+
assertStoreMountable,
|
|
11
|
+
storeAdd,
|
|
12
|
+
storeBind,
|
|
13
|
+
storeCreate,
|
|
14
|
+
storeExplain,
|
|
15
|
+
storeGitRemote,
|
|
16
|
+
storeList,
|
|
17
|
+
storeRemove,
|
|
18
|
+
storeSwitchWrite
|
|
19
|
+
} from "./chunk-5ZUMLCD5.js";
|
|
20
|
+
import {
|
|
21
|
+
loadGlobalConfig
|
|
22
|
+
} from "./chunk-XCBVSGCS.js";
|
|
23
|
+
|
|
24
|
+
// src/commands/store.ts
|
|
25
|
+
import { defineCommand } from "citty";
|
|
26
|
+
|
|
27
|
+
// src/store/store-migrate.ts
|
|
28
|
+
import { execFileSync } from "child_process";
|
|
29
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
|
|
30
|
+
import { basename, join } from "path";
|
|
31
|
+
import {
|
|
32
|
+
STORE_KNOWLEDGE_TYPE_DIRS,
|
|
33
|
+
STORE_LAYOUT,
|
|
34
|
+
STORE_PENDING_DIR,
|
|
35
|
+
buildStoreResolveInput,
|
|
36
|
+
createStoreResolver,
|
|
37
|
+
formatKnowledgeId,
|
|
38
|
+
parseKnowledgeId,
|
|
39
|
+
resolveGlobalRoot,
|
|
40
|
+
storeRelativePath
|
|
41
|
+
} from "@fenglimg/fabric-shared";
|
|
42
|
+
function resolveTargetStore(layer, projectRoot, globalRoot) {
|
|
43
|
+
const input = buildStoreResolveInput(projectRoot, globalRoot);
|
|
44
|
+
if (input === null) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const scope = layer === "personal" ? "personal" : "team";
|
|
48
|
+
const { target } = createStoreResolver().resolveWriteTarget(input, scope);
|
|
49
|
+
if (target === null) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const alias = loadGlobalConfig(globalRoot)?.stores.find((s) => s.store_uuid === target.store_uuid)?.alias ?? target.store_uuid;
|
|
53
|
+
return {
|
|
54
|
+
uuid: target.store_uuid,
|
|
55
|
+
alias,
|
|
56
|
+
dir: join(globalRoot, storeRelativePath(target.store_uuid))
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function listMd(dir) {
|
|
60
|
+
if (!existsSync(dir)) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
return readdirSync(dir).filter((name) => name.endsWith(".md")).sort();
|
|
64
|
+
}
|
|
65
|
+
function readId(content) {
|
|
66
|
+
const match = content.match(/^id:\s*(\S+)\s*$/mu);
|
|
67
|
+
return match ? match[1] : null;
|
|
68
|
+
}
|
|
69
|
+
function slugSuffix(fileName, oldId) {
|
|
70
|
+
const stem = fileName.replace(/\.md$/u, "");
|
|
71
|
+
if (oldId !== null && stem.startsWith(`${oldId}--`)) {
|
|
72
|
+
return stem.slice(oldId.length);
|
|
73
|
+
}
|
|
74
|
+
return "";
|
|
75
|
+
}
|
|
76
|
+
function buildStoreIdIndex(storeDir) {
|
|
77
|
+
const existing = /* @__PURE__ */ new Set();
|
|
78
|
+
const maxCounter = /* @__PURE__ */ new Map();
|
|
79
|
+
for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
|
|
80
|
+
const dir = join(storeDir, STORE_LAYOUT.knowledgeDir, type);
|
|
81
|
+
for (const file of listMd(dir)) {
|
|
82
|
+
const content = readFileSync(join(dir, file), "utf8");
|
|
83
|
+
const id = readId(content) ?? file.replace(/\.md$/u, "").split("--")[0];
|
|
84
|
+
const parsed = parseKnowledgeId(id);
|
|
85
|
+
if (parsed === null) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
existing.add(id);
|
|
89
|
+
const key = id.slice(0, id.lastIndexOf("-"));
|
|
90
|
+
maxCounter.set(key, Math.max(maxCounter.get(key) ?? 0, parsed.counter));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { existing, maxCounter };
|
|
94
|
+
}
|
|
95
|
+
function nextId(index, layer, type) {
|
|
96
|
+
const probe = formatKnowledgeId(layer, type, 1);
|
|
97
|
+
const key = probe.slice(0, probe.lastIndexOf("-"));
|
|
98
|
+
let counter = (index.maxCounter.get(key) ?? 0) + 1;
|
|
99
|
+
let id = formatKnowledgeId(layer, type, counter);
|
|
100
|
+
while (index.existing.has(id)) {
|
|
101
|
+
counter += 1;
|
|
102
|
+
id = formatKnowledgeId(layer, type, counter);
|
|
103
|
+
}
|
|
104
|
+
index.existing.add(id);
|
|
105
|
+
index.maxCounter.set(key, counter);
|
|
106
|
+
return id;
|
|
107
|
+
}
|
|
108
|
+
function typeDirToKnowledgeType(typeDir) {
|
|
109
|
+
return STORE_KNOWLEDGE_TYPE_DIRS.includes(typeDir) ? typeDir : null;
|
|
110
|
+
}
|
|
111
|
+
function migrateProjectKnowledge(projectRoot, options = {}) {
|
|
112
|
+
const dryRun = options.dryRun ?? false;
|
|
113
|
+
const globalRoot = options.globalRoot ?? resolveGlobalRoot();
|
|
114
|
+
const runGit = options.git ?? true;
|
|
115
|
+
const items = [];
|
|
116
|
+
const skips = [];
|
|
117
|
+
const remap = {};
|
|
118
|
+
const targets = {};
|
|
119
|
+
const sourceRoots = {
|
|
120
|
+
team: join(projectRoot, ".fabric", "knowledge"),
|
|
121
|
+
personal: join(globalRoot, "knowledge")
|
|
122
|
+
};
|
|
123
|
+
const layerState = {};
|
|
124
|
+
for (const layer of ["team", "personal"]) {
|
|
125
|
+
if (!existsSync(sourceRoots[layer])) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const target = resolveTargetStore(layer, projectRoot, globalRoot);
|
|
129
|
+
if (target === null) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
targets[layer] = { uuid: target.uuid, dir: target.dir };
|
|
133
|
+
layerState[layer] = { target, index: buildStoreIdIndex(target.dir) };
|
|
134
|
+
}
|
|
135
|
+
for (const layer of ["team", "personal"]) {
|
|
136
|
+
const root = sourceRoots[layer];
|
|
137
|
+
if (!existsSync(root)) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const state = layerState[layer];
|
|
141
|
+
for (const typeDir of STORE_KNOWLEDGE_TYPE_DIRS) {
|
|
142
|
+
const dir = join(root, typeDir);
|
|
143
|
+
for (const file of listMd(dir)) {
|
|
144
|
+
const source = join(dir, file);
|
|
145
|
+
if (state === void 0) {
|
|
146
|
+
skips.push({
|
|
147
|
+
source,
|
|
148
|
+
reason: `no ${layer} write-target store \u2014 run \`fabric install --global\` then \`fabric store bind <alias>\`${layer === "team" ? " + `fabric store switch-write <alias>`" : ""}`
|
|
149
|
+
});
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const content = readFileSync(source, "utf8");
|
|
153
|
+
const oldId = readId(content);
|
|
154
|
+
const knowledgeType = typeDirToKnowledgeType(typeDir);
|
|
155
|
+
let newId = null;
|
|
156
|
+
if (oldId !== null && state.index.existing.has(oldId) && knowledgeType !== null) {
|
|
157
|
+
const parsed = parseKnowledgeId(oldId);
|
|
158
|
+
const idLayer = parsed?.layer ?? layer;
|
|
159
|
+
newId = nextId(state.index, idLayer, knowledgeType);
|
|
160
|
+
remap[oldId] = newId;
|
|
161
|
+
} else if (oldId !== null) {
|
|
162
|
+
state.index.existing.add(oldId);
|
|
163
|
+
const parsed = parseKnowledgeId(oldId);
|
|
164
|
+
if (parsed !== null) {
|
|
165
|
+
const key = oldId.slice(0, oldId.lastIndexOf("-"));
|
|
166
|
+
state.index.maxCounter.set(
|
|
167
|
+
key,
|
|
168
|
+
Math.max(state.index.maxCounter.get(key) ?? 0, parsed.counter)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const effectiveId = newId ?? oldId;
|
|
173
|
+
const targetName = newId !== null && effectiveId !== null ? `${effectiveId}${slugSuffix(file, oldId)}.md` : file;
|
|
174
|
+
const targetFile = join(state.target.dir, STORE_LAYOUT.knowledgeDir, typeDir, targetName);
|
|
175
|
+
items.push({
|
|
176
|
+
source,
|
|
177
|
+
layer,
|
|
178
|
+
type: typeDir,
|
|
179
|
+
oldId,
|
|
180
|
+
newId,
|
|
181
|
+
target: targetFile,
|
|
182
|
+
storeUuid: state.target.uuid,
|
|
183
|
+
alias: state.target.alias
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const pendingRoot = join(root, STORE_PENDING_DIR);
|
|
188
|
+
for (const sub of [".", "decisions", "guidelines", "pitfalls", "models", "processes"]) {
|
|
189
|
+
const dir = sub === "." ? pendingRoot : join(pendingRoot, sub);
|
|
190
|
+
for (const file of listMd(dir)) {
|
|
191
|
+
const source = join(dir, file);
|
|
192
|
+
if (state === void 0) {
|
|
193
|
+
skips.push({ source, reason: `no ${layer} write-target store` });
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const rel = sub === "." ? file : join(sub, file);
|
|
197
|
+
const targetFile = join(
|
|
198
|
+
state.target.dir,
|
|
199
|
+
STORE_LAYOUT.knowledgeDir,
|
|
200
|
+
STORE_PENDING_DIR,
|
|
201
|
+
rel
|
|
202
|
+
);
|
|
203
|
+
if (existsSync(targetFile)) {
|
|
204
|
+
skips.push({ source, reason: `pending already present in store: ${basename(targetFile)}` });
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
items.push({
|
|
208
|
+
source,
|
|
209
|
+
layer,
|
|
210
|
+
type: STORE_PENDING_DIR,
|
|
211
|
+
oldId: null,
|
|
212
|
+
newId: null,
|
|
213
|
+
target: targetFile,
|
|
214
|
+
storeUuid: state.target.uuid,
|
|
215
|
+
alias: state.target.alias
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (dryRun || items.length === 0) {
|
|
221
|
+
return { dryRun, committed: false, items, skips, remap, targets };
|
|
222
|
+
}
|
|
223
|
+
for (const item of items) {
|
|
224
|
+
let content = readFileSync(item.source, "utf8");
|
|
225
|
+
if (item.newId !== null && item.oldId !== null) {
|
|
226
|
+
content = content.replace(/^id:\s*\S+\s*$/mu, `id: ${item.newId}`);
|
|
227
|
+
}
|
|
228
|
+
content = rewriteRelated(content, remap);
|
|
229
|
+
mkdirSync(join(item.target, ".."), { recursive: true });
|
|
230
|
+
writeFileSync(item.target, content, "utf8");
|
|
231
|
+
}
|
|
232
|
+
for (const item of items) {
|
|
233
|
+
rmSync(item.source, { force: true });
|
|
234
|
+
}
|
|
235
|
+
let committed = false;
|
|
236
|
+
if (runGit) {
|
|
237
|
+
for (const [layer, info] of Object.entries(targets)) {
|
|
238
|
+
const moved = items.filter((i) => i.layer === layer).length;
|
|
239
|
+
if (moved === 0) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
committed = gitCommitStore(info.dir, moved) || committed;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return { dryRun, committed, items, skips, remap, targets };
|
|
246
|
+
}
|
|
247
|
+
function rewriteRelated(content, remap) {
|
|
248
|
+
if (Object.keys(remap).length === 0) {
|
|
249
|
+
return content;
|
|
250
|
+
}
|
|
251
|
+
return content.replace(/^related:\s*\[(.*)\]\s*$/mu, (line, inner) => {
|
|
252
|
+
const rewritten = inner.split(",").map((token) => {
|
|
253
|
+
const trimmed = token.trim();
|
|
254
|
+
return remap[trimmed] ?? trimmed;
|
|
255
|
+
}).join(", ");
|
|
256
|
+
return `related: [${rewritten}]`;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function gitCommitStore(storeDir, count) {
|
|
260
|
+
if (!existsSync(join(storeDir, ".git"))) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
execFileSync("git", ["add", "-A"], { cwd: storeDir, stdio: ["ignore", "ignore", "pipe"] });
|
|
265
|
+
execFileSync(
|
|
266
|
+
"git",
|
|
267
|
+
["commit", "-m", `chore(migrate): import ${count} entries from project dual-root`],
|
|
268
|
+
{ cwd: storeDir, stdio: ["ignore", "ignore", "pipe"] }
|
|
269
|
+
);
|
|
270
|
+
return true;
|
|
271
|
+
} catch {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/commands/store.ts
|
|
277
|
+
var listCommand = defineCommand({
|
|
278
|
+
meta: { name: "list", description: "List mounted knowledge stores" },
|
|
279
|
+
run() {
|
|
280
|
+
const t = getProjectTranslator();
|
|
281
|
+
const stores = storeList();
|
|
282
|
+
if (stores.length === 0) {
|
|
283
|
+
console.log(t("cli.store.none-mounted"));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const localOnly = t("cli.shared.local-only");
|
|
287
|
+
for (const store of stores) {
|
|
288
|
+
const realRemote = storeGitRemote(store.store_uuid);
|
|
289
|
+
console.log(`${store.alias} ${store.store_uuid} ${realRemote ?? localOnly}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
var addCommand = defineCommand({
|
|
294
|
+
meta: { name: "add", description: "Mount a knowledge store into the global registry" },
|
|
295
|
+
args: {
|
|
296
|
+
uuid: { type: "string", required: true, description: "Intrinsic store UUID" },
|
|
297
|
+
alias: { type: "string", required: true, description: "Local alias for this store" },
|
|
298
|
+
remote: { type: "string", description: "Git remote locator (omit for local-only)" }
|
|
299
|
+
},
|
|
300
|
+
run({ args }) {
|
|
301
|
+
assertStoreMountable(args.uuid);
|
|
302
|
+
const store = args.remote === void 0 ? { store_uuid: args.uuid, alias: args.alias } : { store_uuid: args.uuid, alias: args.alias, remote: args.remote };
|
|
303
|
+
const next = storeAdd(store);
|
|
304
|
+
const t = getProjectTranslator();
|
|
305
|
+
console.log(
|
|
306
|
+
t("cli.store.mounted", {
|
|
307
|
+
alias: args.alias,
|
|
308
|
+
count: String(next.stores.length)
|
|
309
|
+
})
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
var createCommand = defineCommand({
|
|
314
|
+
meta: { name: "create", description: "Create a brand-new local knowledge store and mount it" },
|
|
315
|
+
args: {
|
|
316
|
+
alias: { type: "string", required: true, description: "Local alias for the new store" },
|
|
317
|
+
remote: { type: "string", description: "Git remote to associate (push target; optional)" }
|
|
318
|
+
},
|
|
319
|
+
run({ args }) {
|
|
320
|
+
const result = storeCreate(args.alias, (/* @__PURE__ */ new Date()).toISOString(), {
|
|
321
|
+
...args.remote === void 0 ? {} : { remote: args.remote }
|
|
322
|
+
});
|
|
323
|
+
const t = getProjectTranslator();
|
|
324
|
+
console.log(
|
|
325
|
+
t("cli.store.created", { alias: args.alias, uuid: result.store_uuid, dir: result.storeDir }) + (args.remote === void 0 ? `
|
|
326
|
+
${t("cli.store.created-local-hint")}` : "")
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
var removeCommand = defineCommand({
|
|
331
|
+
meta: { name: "remove", description: "Detach a store from the registry (does NOT delete it)" },
|
|
332
|
+
args: {
|
|
333
|
+
alias: { type: "positional", required: true, description: "Alias to detach" }
|
|
334
|
+
},
|
|
335
|
+
run({ args }) {
|
|
336
|
+
const { detached } = storeRemove(args.alias);
|
|
337
|
+
const t = getProjectTranslator();
|
|
338
|
+
console.log(
|
|
339
|
+
detached === null ? t("cli.store.no-alias", { alias: args.alias }) : t("cli.store.detached", { alias: args.alias })
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
var explainCommand = defineCommand({
|
|
344
|
+
meta: { name: "explain", description: "Explain how a store alias resolves" },
|
|
345
|
+
args: {
|
|
346
|
+
alias: { type: "positional", required: true, description: "Alias to explain" }
|
|
347
|
+
},
|
|
348
|
+
run({ args }) {
|
|
349
|
+
const explanation = storeExplain(args.alias);
|
|
350
|
+
console.log(
|
|
351
|
+
explanation === null ? getProjectTranslator()("cli.store.no-alias", { alias: args.alias }) : JSON.stringify(explanation, null, 2)
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
var bindCommand = defineCommand({
|
|
356
|
+
meta: { name: "bind", description: "Declare a required store on this project's config" },
|
|
357
|
+
args: {
|
|
358
|
+
id: { type: "positional", required: true, description: "Store alias/UUID to require" },
|
|
359
|
+
remote: { type: "string", description: "Suggested remote for clone onboarding" }
|
|
360
|
+
},
|
|
361
|
+
run({ args }) {
|
|
362
|
+
const entry = args.remote === void 0 ? { id: args.id } : { id: args.id, suggested_remote: args.remote };
|
|
363
|
+
const projectRoot = process.cwd();
|
|
364
|
+
const next = storeBind(projectRoot, entry);
|
|
365
|
+
console.log(
|
|
366
|
+
getProjectTranslator(projectRoot)("cli.store.bound", {
|
|
367
|
+
id: args.id,
|
|
368
|
+
count: String(next.required_stores?.length ?? 0)
|
|
369
|
+
})
|
|
370
|
+
);
|
|
371
|
+
regenerateBindingsSnapshot(projectRoot, { now: (/* @__PURE__ */ new Date()).toISOString() });
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
var switchWriteCommand = defineCommand({
|
|
375
|
+
meta: { name: "switch-write", description: "Set the active write store for non-personal scopes" },
|
|
376
|
+
args: {
|
|
377
|
+
alias: { type: "positional", required: true, description: "Alias of the store to write to" }
|
|
378
|
+
},
|
|
379
|
+
run({ args }) {
|
|
380
|
+
const projectRoot = process.cwd();
|
|
381
|
+
storeSwitchWrite(projectRoot, args.alias);
|
|
382
|
+
console.log(getProjectTranslator(projectRoot)("cli.store.switch-write", { alias: args.alias }));
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
var migrateCommand = defineCommand({
|
|
386
|
+
meta: {
|
|
387
|
+
name: "migrate",
|
|
388
|
+
description: "Move project-local (dual-root) knowledge into the resolved write-target stores"
|
|
389
|
+
},
|
|
390
|
+
args: {
|
|
391
|
+
"dry-run": {
|
|
392
|
+
type: "boolean",
|
|
393
|
+
description: "Preview the move without writing anything"
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
run({ args }) {
|
|
397
|
+
const projectRoot = process.cwd();
|
|
398
|
+
const t = getProjectTranslator(projectRoot);
|
|
399
|
+
const dryRun = args["dry-run"] === true;
|
|
400
|
+
const report = migrateProjectKnowledge(projectRoot, { dryRun });
|
|
401
|
+
if (report.items.length === 0 && report.skips.length === 0) {
|
|
402
|
+
console.log(t("cli.store.migrate.none"));
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
console.log(
|
|
406
|
+
dryRun ? t("cli.store.migrate.dry-run-header") : t("cli.store.migrate.applied-header", { count: String(report.items.length) })
|
|
407
|
+
);
|
|
408
|
+
for (const item of report.items) {
|
|
409
|
+
const id = item.newId ?? item.oldId ?? "(draft)";
|
|
410
|
+
console.log(` ${item.layer}/${item.type} ${id} \u2192 ${item.alias}`);
|
|
411
|
+
if (item.newId !== null && item.oldId !== null) {
|
|
412
|
+
console.log(
|
|
413
|
+
t("cli.store.migrate.remap-note", { oldId: item.oldId, newId: item.newId })
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (report.skips.length > 0) {
|
|
418
|
+
console.log(t("cli.store.migrate.skips-header", { count: String(report.skips.length) }));
|
|
419
|
+
for (const skip of report.skips) {
|
|
420
|
+
console.log(` ${skip.source}: ${skip.reason}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (report.committed) {
|
|
424
|
+
console.log(t("cli.store.migrate.committed"));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
var store_default = defineCommand({
|
|
429
|
+
meta: { name: "store", description: "Manage mounted Fabric knowledge stores" },
|
|
430
|
+
subCommands: {
|
|
431
|
+
list: listCommand,
|
|
432
|
+
create: createCommand,
|
|
433
|
+
add: addCommand,
|
|
434
|
+
remove: removeCommand,
|
|
435
|
+
explain: explainCommand,
|
|
436
|
+
bind: bindCommand,
|
|
437
|
+
"switch-write": switchWriteCommand,
|
|
438
|
+
migrate: migrateCommand
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
export {
|
|
442
|
+
store_default as default
|
|
443
|
+
};
|