@fenglimg/fabric-cli 2.2.0-rc.3 → 2.2.0-rc.8

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.
Files changed (75) hide show
  1. package/README.md +8 -5
  2. package/dist/{chunk-5LQIHYFC.js → chunk-27HK6H5Y.js} +10 -5
  3. package/dist/{chunk-F6ITRM7T.js → chunk-2KBCTMID.js} +29 -6
  4. package/dist/{chunk-XC5RUHLK.js → chunk-3IOLS5EK.js} +23 -38
  5. package/dist/{chunk-XHHCRDIR.js → chunk-CMDW3PYK.js} +105 -220
  6. package/dist/chunk-FEOPLBGA.js +150 -0
  7. package/dist/{chunk-XCBVSGCS.js → chunk-FNHDQTPC.js} +1 -10
  8. package/dist/{chunk-2CY4BMTH.js → chunk-HORSMSZL.js} +9 -5
  9. package/dist/{doctor-J4O3X54I.js → chunk-JTHWLUD3.js} +103 -51
  10. package/dist/{chunk-BO4XIZWZ.js → chunk-NLNH64A3.js} +5 -18
  11. package/dist/{chunk-H3FE6VIK.js → chunk-PTGQAZEW.js} +13 -3
  12. package/dist/chunk-QFIVFZRH.js +13 -0
  13. package/dist/chunk-QPAW6IYT.js +387 -0
  14. package/dist/{chunk-COI5VDFU.js → chunk-WA3DYGSY.js} +1 -2
  15. package/dist/{plan-context-hint-CHVZGOZ5.js → chunk-YM4XATJF.js} +29 -4
  16. package/dist/{config-VJMXCLXW.js → config-A3LTECAY.js} +4 -3
  17. package/dist/context-7NUKXDB6.js +117 -0
  18. package/dist/doctor-REZDNH4A.js +24 -0
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +131 -21
  21. package/dist/info-7FKBTMVO.js +139 -0
  22. package/dist/install-v2-2COC3DO3.js +3277 -0
  23. package/dist/{metrics-RER6NLFC.js → metrics-HMFH4YHK.js} +1 -1
  24. package/dist/{onboard-coverage-JWQWDZW7.js → onboard-coverage-XSG77LL3.js} +48 -27
  25. package/dist/plan-context-hint-G75R4P4J.js +12 -0
  26. package/dist/{scope-explain-BWRWBCCP.js → scope-explain-HLJZ2M33.js} +3 -2
  27. package/dist/{status-PANEGKU2.js → status-4R3TM4FJ.js} +8 -5
  28. package/dist/store-HOCORVL3.js +563 -0
  29. package/dist/{sync-EA5HZMXM.js → sync-DT5UJMMR.js} +36 -13
  30. package/dist/{uninstall-F75MPKQC.js → uninstall-62F4LNKI.js} +62 -140
  31. package/dist/{whoami-66YKY5DZ.js → whoami-ITGEFWH4.js} +9 -7
  32. package/package.json +7 -5
  33. package/templates/hooks/cite-policy-evict.cjs +5 -5
  34. package/templates/hooks/configs/README.md +14 -27
  35. package/templates/hooks/configs/claude-code.json +1 -1
  36. package/templates/hooks/configs/codex-hooks.json +3 -3
  37. package/templates/hooks/fabric-hint.cjs +301 -161
  38. package/templates/hooks/knowledge-hint-broad.cjs +426 -207
  39. package/templates/hooks/knowledge-hint-narrow.cjs +56 -56
  40. package/templates/hooks/lib/banner-i18n.cjs +31 -0
  41. package/templates/hooks/lib/bindings-snapshot-reader.cjs +117 -7
  42. package/templates/hooks/lib/cite-line-parser.cjs +12 -20
  43. package/templates/hooks/lib/client-adapter.cjs +66 -7
  44. package/templates/hooks/lib/nudge-policy.cjs +117 -0
  45. package/templates/hooks/lib/state-store.cjs +60 -0
  46. package/templates/hooks/lib/summary-fallback.cjs +82 -19
  47. package/templates/hooks/post-tooluse-mutation.cjs +112 -11
  48. package/templates/skills/fabric/SKILL.md +94 -0
  49. package/templates/skills/fabric-archive/SKILL.md +29 -26
  50. package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
  51. package/templates/skills/fabric-archive/ref/i18n-policy.md +2 -3
  52. package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +2 -3
  53. package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +1 -1
  54. package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +1 -1
  55. package/templates/skills/fabric-archive/ref/phase-3-6-related-edges.md +18 -0
  56. package/templates/skills/fabric-archive/ref/phase-3-7-semantic-scope.md +47 -0
  57. package/templates/skills/fabric-audit/SKILL.md +13 -3
  58. package/templates/skills/fabric-connect/SKILL.md +3 -3
  59. package/templates/skills/fabric-import/SKILL.md +7 -7
  60. package/templates/skills/fabric-import/ref/i18n-policy.md +2 -3
  61. package/templates/skills/fabric-import/ref/state-recovery.md +1 -2
  62. package/templates/skills/fabric-review/SKILL.md +5 -5
  63. package/templates/skills/fabric-review/ref/cite-contract.md +1 -1
  64. package/templates/skills/fabric-review/ref/i18n-policy.md +2 -3
  65. package/templates/skills/fabric-review/ref/output-contract.md +1 -1
  66. package/templates/skills/fabric-review/ref/per-mode-flows.md +2 -2
  67. package/templates/skills/fabric-review/ref/worked-examples.md +1 -1
  68. package/templates/skills/fabric-store/SKILL.md +1 -1
  69. package/templates/skills/fabric-sync/SKILL.md +1 -1
  70. package/templates/skills/lib/shared-policy.md +2 -2
  71. package/dist/chunk-5ZUMLCD5.js +0 -248
  72. package/dist/install-BULNDUIM.js +0 -2816
  73. package/dist/store-66NK2FTQ.js +0 -443
  74. package/templates/hooks/configs/cursor-hooks.json +0 -30
  75. package/templates/hooks/lib/cite-contract-reminder.cjs +0 -179
@@ -0,0 +1,387 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ loadProjectConfig,
4
+ saveProjectConfig
5
+ } from "./chunk-QFIVFZRH.js";
6
+ import {
7
+ loadGlobalConfig,
8
+ resolveGlobalRoot,
9
+ saveGlobalConfig
10
+ } from "./chunk-FNHDQTPC.js";
11
+
12
+ // src/store/store-ops.ts
13
+ import { execFileSync } from "child_process";
14
+ import { randomUUID } from "crypto";
15
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from "fs";
16
+ import { join } from "path";
17
+ import {
18
+ addMountedStore,
19
+ addStoreProject,
20
+ bindRequiredStore,
21
+ deriveMountLabel,
22
+ detachMountedStore,
23
+ explainStore,
24
+ initStore,
25
+ STORE_GITIGNORE,
26
+ STORE_KNOWLEDGE_TYPE_DIRS,
27
+ STORE_LAYOUT,
28
+ STORE_PENDING_DIR,
29
+ STORES_ROOT_DIR,
30
+ storeHasProject,
31
+ storeIdentitySchema,
32
+ storeProjectsFileSchema,
33
+ storeMountSubPath,
34
+ storeRelativePath,
35
+ storeRelativePathForMount,
36
+ storeMountNameSchema,
37
+ writeRouteSchema
38
+ } from "@fenglimg/fabric-shared";
39
+ var NO_GLOBAL_CONFIG = "no global Fabric config found \u2014 run `fabric install --global <url>` first";
40
+ function requireConfig(globalRoot) {
41
+ const config = loadGlobalConfig(globalRoot);
42
+ if (config === null) {
43
+ throw new Error(NO_GLOBAL_CONFIG);
44
+ }
45
+ return config;
46
+ }
47
+ function storeList(globalRoot = resolveGlobalRoot()) {
48
+ return requireConfig(globalRoot).stores;
49
+ }
50
+ function mountedStoreDir(store, globalRoot) {
51
+ return join(globalRoot, storeRelativePathForMount(store));
52
+ }
53
+ function resolveStoreByAliasOrUuid(aliasOrUuid, globalRoot = resolveGlobalRoot()) {
54
+ const config = loadGlobalConfig(globalRoot);
55
+ if (config === null) {
56
+ return null;
57
+ }
58
+ return config.stores.find(
59
+ (s) => s.alias === aliasOrUuid || s.store_uuid === aliasOrUuid || s.mount_name === aliasOrUuid
60
+ ) ?? null;
61
+ }
62
+ var STORE_BY_ALIAS_DIR = "by-alias";
63
+ function syncStoreAliasLinks(globalRoot = resolveGlobalRoot()) {
64
+ const result = { created: [], removed: [], errors: [] };
65
+ const config = loadGlobalConfig(globalRoot);
66
+ if (config === null) {
67
+ return result;
68
+ }
69
+ const byAliasDir = join(globalRoot, STORES_ROOT_DIR, STORE_BY_ALIAS_DIR);
70
+ const desired = new Map(config.stores.map((s) => [s.alias, storeMountSubPath(s)]));
71
+ try {
72
+ mkdirSync(byAliasDir, { recursive: true });
73
+ } catch {
74
+ return result;
75
+ }
76
+ let existing = [];
77
+ try {
78
+ existing = readdirSync(byAliasDir);
79
+ } catch {
80
+ existing = [];
81
+ }
82
+ for (const name of existing) {
83
+ if (desired.has(name)) {
84
+ continue;
85
+ }
86
+ try {
87
+ rmSync(join(byAliasDir, name), { force: true, recursive: false });
88
+ result.removed.push(name);
89
+ } catch {
90
+ result.errors.push(name);
91
+ }
92
+ }
93
+ for (const [alias, mountName] of desired) {
94
+ const link = join(byAliasDir, alias);
95
+ const target = join("..", mountName);
96
+ try {
97
+ let current = null;
98
+ try {
99
+ if (lstatSync(link).isSymbolicLink()) {
100
+ current = readlinkSync(link);
101
+ }
102
+ } catch {
103
+ current = null;
104
+ }
105
+ if (current === target) {
106
+ continue;
107
+ }
108
+ rmSync(link, { force: true });
109
+ symlinkSync(target, link);
110
+ result.created.push(alias);
111
+ } catch {
112
+ result.errors.push(alias);
113
+ }
114
+ }
115
+ return result;
116
+ }
117
+ function detectAliasLinkDrift(globalRoot = resolveGlobalRoot()) {
118
+ const config = loadGlobalConfig(globalRoot);
119
+ if (config === null) {
120
+ return [];
121
+ }
122
+ const byAliasDir = join(globalRoot, STORES_ROOT_DIR, STORE_BY_ALIAS_DIR);
123
+ if (!existsSync(byAliasDir)) {
124
+ return [];
125
+ }
126
+ const drifted = [];
127
+ for (const store of config.stores) {
128
+ const link = join(byAliasDir, store.alias);
129
+ const target = join("..", storeMountSubPath(store));
130
+ try {
131
+ if (!lstatSync(link).isSymbolicLink() || readlinkSync(link) !== target) {
132
+ drifted.push(store.alias);
133
+ }
134
+ } catch {
135
+ drifted.push(store.alias);
136
+ }
137
+ }
138
+ return drifted;
139
+ }
140
+ function storeAdd(store, globalRoot = resolveGlobalRoot()) {
141
+ const next = addMountedStore(requireConfig(globalRoot), store);
142
+ saveGlobalConfig(next, globalRoot);
143
+ syncStoreAliasLinks(globalRoot);
144
+ return next;
145
+ }
146
+ async function storeCreate(alias, now, options = {}) {
147
+ const globalRoot = options.globalRoot ?? resolveGlobalRoot();
148
+ const config = requireConfig(globalRoot);
149
+ const uuid = options.uuid ?? randomUUID();
150
+ const mount_name = options.mountName !== void 0 ? storeMountNameSchema.parse(options.mountName) : deriveMountLabel({ remote: options.remote, alias, store_uuid: uuid });
151
+ const mountedBase = { store_uuid: uuid, alias, mount_name };
152
+ const storeDir = mountedStoreDir(mountedBase, globalRoot);
153
+ const identity = { store_uuid: uuid, created_at: now, canonical_alias: alias };
154
+ if (options.git === false) {
155
+ initStoreSync(storeDir, identity);
156
+ } else {
157
+ await initStore(storeDir, identity, { git: options.git });
158
+ }
159
+ if (options.remote !== void 0 && options.git !== false) {
160
+ gitRemoteAdd(storeDir, options.remote);
161
+ }
162
+ const mounted = options.remote === void 0 ? mountedBase : { ...mountedBase, remote: options.remote };
163
+ const next = addMountedStore(config, mounted);
164
+ saveGlobalConfig(next, globalRoot);
165
+ syncStoreAliasLinks(globalRoot);
166
+ return { config: next, store_uuid: uuid, storeDir };
167
+ }
168
+ function initStoreSync(absDir, identity) {
169
+ const parsed = storeIdentitySchema.parse(identity);
170
+ const identityFile = join(absDir, STORE_LAYOUT.identityFile);
171
+ if (existsSync(identityFile)) {
172
+ throw new Error(`store already initialized at ${absDir} (store.json exists)`);
173
+ }
174
+ for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
175
+ const typeDir = join(absDir, STORE_LAYOUT.knowledgeDir, type);
176
+ mkdirSync(typeDir, { recursive: true });
177
+ writeFileSync(join(typeDir, ".gitkeep"), "", "utf8");
178
+ }
179
+ mkdirSync(join(absDir, STORE_LAYOUT.knowledgeDir, STORE_PENDING_DIR), { recursive: true });
180
+ mkdirSync(join(absDir, STORE_LAYOUT.bindingsDir), { recursive: true });
181
+ mkdirSync(join(absDir, STORE_LAYOUT.stateDir), { recursive: true });
182
+ writeFileSync(identityFile, `${JSON.stringify(parsed, null, 2)}
183
+ `, "utf8");
184
+ writeFileSync(join(absDir, ".gitignore"), STORE_GITIGNORE, "utf8");
185
+ return parsed;
186
+ }
187
+ function gitRemoteAdd(storeDir, remote) {
188
+ try {
189
+ execFileSync("git", ["remote", "add", "origin", remote], {
190
+ cwd: storeDir,
191
+ stdio: ["ignore", "ignore", "pipe"]
192
+ });
193
+ } catch {
194
+ try {
195
+ execFileSync("git", ["remote", "set-url", "origin", remote], {
196
+ cwd: storeDir,
197
+ stdio: ["ignore", "ignore", "pipe"]
198
+ });
199
+ } catch {
200
+ }
201
+ }
202
+ }
203
+ function storeGitRemote(aliasOrUuid, globalRoot = resolveGlobalRoot()) {
204
+ const storeDir = resolveStoreDir(aliasOrUuid, globalRoot) ?? join(globalRoot, storeRelativePath(aliasOrUuid));
205
+ if (!existsSync(storeDir)) {
206
+ return void 0;
207
+ }
208
+ try {
209
+ const out = execFileSync("git", ["remote", "get-url", "origin"], {
210
+ cwd: storeDir,
211
+ stdio: ["ignore", "pipe", "pipe"],
212
+ encoding: "utf8"
213
+ });
214
+ const trimmed = out.trim();
215
+ return trimmed.length > 0 ? trimmed : void 0;
216
+ } catch {
217
+ return void 0;
218
+ }
219
+ }
220
+ function assertStoreMountable(uuid, globalRoot = resolveGlobalRoot(), mountName) {
221
+ const registered = resolveStoreByAliasOrUuid(uuid, globalRoot);
222
+ const candidates = mountName === void 0 && registered === null ? [join(globalRoot, storeRelativePath(uuid))] : [
223
+ join(
224
+ globalRoot,
225
+ storeRelativePathForMount({
226
+ store_uuid: uuid,
227
+ mount_name: mountName ?? registered?.mount_name,
228
+ personal: registered?.personal
229
+ })
230
+ ),
231
+ join(globalRoot, storeRelativePath(uuid))
232
+ ];
233
+ const storeDir = candidates.find((dir) => existsSync(join(dir, "store.json"))) ?? candidates[0];
234
+ if (!existsSync(join(storeDir, "store.json"))) {
235
+ throw new Error(
236
+ `cannot mount store ${uuid}: no store tree at ${storeDir} \u2014 clone it first (\`fabric install --global --url <remote>\`) or create it locally, then re-run \`fabric store add\`. Refusing to register a phantom store.`
237
+ );
238
+ }
239
+ }
240
+ function storeRemove(alias, globalRoot = resolveGlobalRoot()) {
241
+ const result = detachMountedStore(requireConfig(globalRoot), alias);
242
+ saveGlobalConfig(result.config, globalRoot);
243
+ syncStoreAliasLinks(globalRoot);
244
+ return result;
245
+ }
246
+ function storeExplain(alias, globalRoot = resolveGlobalRoot()) {
247
+ return explainStore(requireConfig(globalRoot), alias);
248
+ }
249
+ var NO_PROJECT_CONFIG = "no project Fabric config \u2014 run `fabric install` in this repo first";
250
+ function requireProjectConfig(projectRoot) {
251
+ const config = loadProjectConfig(projectRoot);
252
+ if (config === null) {
253
+ throw new Error(NO_PROJECT_CONFIG);
254
+ }
255
+ return config;
256
+ }
257
+ function resolveStoreDir(aliasOrUuid, globalRoot = resolveGlobalRoot()) {
258
+ const store = resolveStoreByAliasOrUuid(aliasOrUuid, globalRoot);
259
+ if (store === null) {
260
+ return null;
261
+ }
262
+ return mountedStoreDir(store, globalRoot);
263
+ }
264
+ function storeProjectList(aliasOrUuid, globalRoot = resolveGlobalRoot()) {
265
+ const storeDir = resolveStoreDir(aliasOrUuid, globalRoot);
266
+ if (storeDir === null) {
267
+ throw new Error(`no mounted store '${aliasOrUuid}' \u2014 run \`fabric store list\` to see mounts`);
268
+ }
269
+ return readStoreProjectsSync(storeDir);
270
+ }
271
+ function readStoreProjectsSync(storeDir) {
272
+ try {
273
+ const parsed = storeProjectsFileSchema.safeParse(
274
+ JSON.parse(readFileSync(join(storeDir, STORE_LAYOUT.projectsFile), "utf8"))
275
+ );
276
+ return parsed.success ? parsed.data.projects : [];
277
+ } catch {
278
+ return [];
279
+ }
280
+ }
281
+ async function storeProjectCreate(aliasOrUuid, id, now, options = {}) {
282
+ const globalRoot = options.globalRoot ?? resolveGlobalRoot();
283
+ const storeDir = resolveStoreDir(aliasOrUuid, globalRoot);
284
+ if (storeDir === null) {
285
+ throw new Error(`no mounted store '${aliasOrUuid}' \u2014 run \`fabric store list\` to see mounts`);
286
+ }
287
+ const project = options.name === void 0 ? { id, created_at: now } : { id, name: options.name, created_at: now };
288
+ await addStoreProject(storeDir, project);
289
+ return project;
290
+ }
291
+ async function storeBind(projectRoot, entry, options = {}) {
292
+ const config = requireProjectConfig(projectRoot);
293
+ let activeProject = config.active_project;
294
+ if (options.project !== void 0) {
295
+ const globalRoot = options.globalRoot ?? resolveGlobalRoot();
296
+ const storeDir = resolveStoreDir(entry.id, globalRoot);
297
+ if (storeDir === null) {
298
+ throw new Error(
299
+ `cannot bind project '${options.project}': store '${entry.id}' is not mounted \u2014 mount it first (\`fabric store add\` / \`fabric install --global --url <remote>\`)`
300
+ );
301
+ }
302
+ if (!await storeHasProject(storeDir, options.project)) {
303
+ throw new Error(
304
+ `cannot bind to project '${options.project}': not registered in store '${entry.id}' \u2014 create it first with \`fabric store project create ${entry.id} ${options.project}\``
305
+ );
306
+ }
307
+ activeProject = options.project;
308
+ }
309
+ const next = {
310
+ ...config,
311
+ required_stores: bindRequiredStore(config.required_stores ?? [], entry),
312
+ ...activeProject === void 0 ? {} : { active_project: activeProject }
313
+ };
314
+ saveProjectConfig(next, projectRoot);
315
+ return next;
316
+ }
317
+ function storeSwitchWrite(projectRoot, alias, options = {}) {
318
+ const config = requireProjectConfig(projectRoot);
319
+ const store = resolveStoreByAliasOrUuid(alias, options.globalRoot ?? resolveGlobalRoot());
320
+ if (store === null || store.personal === true || store.writable === false) {
321
+ throw new Error(`cannot set default write store '${alias}': mount a writable shared store first`);
322
+ }
323
+ const next = {
324
+ ...config,
325
+ active_write_store: alias,
326
+ default_write_store: alias
327
+ };
328
+ saveProjectConfig(next, projectRoot);
329
+ return next;
330
+ }
331
+ function storeSetWriteRoute(projectRoot, scope, alias, options = {}) {
332
+ const config = requireProjectConfig(projectRoot);
333
+ const route = writeRouteSchema.parse({ scope, store: alias });
334
+ const store = resolveStoreByAliasOrUuid(alias, options.globalRoot ?? resolveGlobalRoot());
335
+ if (store === null || store.personal === true || store.writable === false) {
336
+ throw new Error(`cannot route scope '${scope}' to '${alias}': mount a writable shared store first`);
337
+ }
338
+ const routes = [
339
+ ...(config.write_routes ?? []).filter((existing) => existing.scope !== route.scope),
340
+ route
341
+ ];
342
+ const next = { ...config, write_routes: routes };
343
+ saveProjectConfig(next, projectRoot);
344
+ return next;
345
+ }
346
+ function missingRequiredStores(projectRoot, globalRoot = resolveGlobalRoot()) {
347
+ const project = loadProjectConfig(projectRoot);
348
+ if (project === null || project.required_stores === void 0) {
349
+ return [];
350
+ }
351
+ const global = loadGlobalConfig(globalRoot);
352
+ const mounted = new Set(
353
+ (global?.stores ?? []).flatMap((s) => [s.alias, s.store_uuid])
354
+ );
355
+ return project.required_stores.filter((r) => !mounted.has(r.id));
356
+ }
357
+ function unboundAvailableStores(projectRoot, globalRoot = resolveGlobalRoot()) {
358
+ const global = loadGlobalConfig(globalRoot);
359
+ if (global === null) {
360
+ return [];
361
+ }
362
+ const project = loadProjectConfig(projectRoot);
363
+ const declared = new Set((project?.required_stores ?? []).map((r) => r.id));
364
+ return global.stores.filter(
365
+ (s) => s.personal !== true && !declared.has(s.alias) && !declared.has(s.store_uuid)
366
+ );
367
+ }
368
+
369
+ export {
370
+ storeList,
371
+ syncStoreAliasLinks,
372
+ detectAliasLinkDrift,
373
+ storeAdd,
374
+ storeCreate,
375
+ storeGitRemote,
376
+ assertStoreMountable,
377
+ storeRemove,
378
+ storeExplain,
379
+ resolveStoreDir,
380
+ storeProjectList,
381
+ storeProjectCreate,
382
+ storeBind,
383
+ storeSwitchWrite,
384
+ storeSetWriteRoute,
385
+ missingRequiredStores,
386
+ unboundAvailableStores
387
+ };
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/dev-mode.ts
4
- import { existsSync, readFileSync } from "fs";
5
- import { isAbsolute, join, resolve } from "path";
4
+ import { isAbsolute, resolve } from "path";
6
5
  function resolveDevMode(cliTarget, workspaceRoot = process.cwd()) {
7
6
  const envTarget = normalizeTarget(process.env.EXTERNAL_FIXTURE_PATH, workspaceRoot);
8
7
  const directTarget = normalizeTarget(cliTarget, workspaceRoot);
@@ -1,11 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  resolveDevMode
4
- } from "./chunk-COI5VDFU.js";
4
+ } from "./chunk-WA3DYGSY.js";
5
5
 
6
6
  // src/commands/plan-context-hint.ts
7
7
  import { defineCommand } from "citty";
8
- import { planContext } from "@fenglimg/fabric-server";
8
+ import {
9
+ buildAlwaysActiveBodies,
10
+ buildKnowledgeCensus,
11
+ planContext
12
+ } from "@fenglimg/fabric-server";
9
13
  var ALL_PATHS_SENTINEL = "**";
10
14
  var planContextHintCommand = defineCommand({
11
15
  meta: {
@@ -74,6 +78,9 @@ async function runPlanContextHint(opts) {
74
78
  maturity: item.description.maturity ?? "",
75
79
  summary: item.description.summary,
76
80
  relevance_scope: item.description.relevance_scope ?? "broad",
81
+ // W2-2 (KT-DEC-0027): forward the must_read_if trigger hook for the
82
+ // SessionStart REFERENCE rendering. Omitted when absent/empty.
83
+ ...typeof item.description.must_read_if === "string" && item.description.must_read_if.length > 0 ? { must_read_if: item.description.must_read_if } : {},
77
84
  // Only set when this entry was pulled in via a graph edge — its presence
78
85
  // is the honest signal, never synthesized for ordinarily-ranked entries.
79
86
  ...typeof relatedTo === "string" ? { related_to: relatedTo } : {}
@@ -85,6 +92,15 @@ async function runPlanContextHint(opts) {
85
92
  if (e.relevance_scope === "narrow") narrow_count += 1;
86
93
  else broad_only_count += 1;
87
94
  }
95
+ const alwaysBodies = await buildAlwaysActiveBodies(resolution.target).catch(() => []);
96
+ const census = await buildKnowledgeCensus(resolution.target).catch(
97
+ () => ({
98
+ by_type: {},
99
+ by_layer: { team: 0, personal: 0, project: 0 },
100
+ dropped_other_project: 0,
101
+ total: 0
102
+ })
103
+ );
88
104
  const output = {
89
105
  version: 2,
90
106
  revision_hash: result.revision_hash,
@@ -94,7 +110,15 @@ async function runPlanContextHint(opts) {
94
110
  // semantics unchanged from rc.18 (total candidate count).
95
111
  broad_count: candidates.length,
96
112
  narrow_count,
97
- broad_only_count
113
+ broad_only_count,
114
+ always_bodies: alwaysBodies.map((b) => ({
115
+ id: b.stable_id,
116
+ type: b.type,
117
+ layer: b.layer,
118
+ summary: b.summary,
119
+ body: b.body
120
+ })),
121
+ census
98
122
  };
99
123
  if (result.auto_healed === true) {
100
124
  output.auto_healed = true;
@@ -110,8 +134,9 @@ function parsePathsArg(raw) {
110
134
  }
111
135
  return raw.split(",").map((part) => part.trim()).filter((part) => part.length > 0);
112
136
  }
137
+
113
138
  export {
114
- plan_context_hint_default as default,
115
139
  planContextHintCommand,
140
+ plan_context_hint_default,
116
141
  runPlanContextHint
117
142
  };
@@ -3,9 +3,10 @@ import {
3
3
  configCmd,
4
4
  config_default,
5
5
  installMcpClients
6
- } from "./chunk-F6ITRM7T.js";
7
- import "./chunk-XC5RUHLK.js";
8
- import "./chunk-2CY4BMTH.js";
6
+ } from "./chunk-2KBCTMID.js";
7
+ import "./chunk-3IOLS5EK.js";
8
+ import "./chunk-FNHDQTPC.js";
9
+ import "./chunk-HORSMSZL.js";
9
10
  export {
10
11
  configCmd,
11
12
  config_default as default,
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ runPlanContextHint
4
+ } from "./chunk-YM4XATJF.js";
5
+ import "./chunk-WA3DYGSY.js";
6
+
7
+ // src/commands/context.ts
8
+ import { existsSync } from "fs";
9
+ import { createRequire } from "module";
10
+ import { dirname, join, parse, resolve } from "path";
11
+ import { fileURLToPath } from "url";
12
+ import { defineCommand } from "citty";
13
+ function findTemplatePath(relativePath) {
14
+ const startDir = dirname(fileURLToPath(import.meta.url));
15
+ let current = resolve(startDir);
16
+ for (; ; ) {
17
+ const candidate = join(current, "templates", relativePath);
18
+ if (existsSync(candidate)) return candidate;
19
+ const parent = dirname(current);
20
+ if (parent === current || parse(current).root === current) {
21
+ throw new Error(`Template not found: templates/${relativePath} (searched up from ${startDir})`);
22
+ }
23
+ current = parent;
24
+ }
25
+ }
26
+ function loadHookRenderer() {
27
+ const require2 = createRequire(import.meta.url);
28
+ return require2(findTemplatePath("hooks/knowledge-hint-broad.cjs"));
29
+ }
30
+ function renderExplain(sinks) {
31
+ const payload = sinks.resolvedPayload;
32
+ const lines = ["", "\u2014 explain (provenance; not injected) \u2014"];
33
+ const bodies = Array.isArray(payload?.always_bodies) ? payload.always_bodies : [];
34
+ if (bodies.length > 0) {
35
+ lines.push("always-active (body injected):");
36
+ for (const b of bodies) {
37
+ lines.push(` [${b.type}] ${b.id} (${b.layer}) \xB7 ${b.summary}`);
38
+ }
39
+ }
40
+ const entries = Array.isArray(payload?.entries) ? payload.entries : [];
41
+ if (entries.length > 0) {
42
+ lines.push("reference / candidates:");
43
+ for (const e of entries) {
44
+ const provenance = typeof e.related_to === "string" ? ` \u2190related-to:${e.related_to}` : "";
45
+ lines.push(
46
+ ` [${e.type}] ${e.id} (${e.maturity || "?"}, ${e.relevance_scope})${provenance} \xB7 ${e.summary}`
47
+ );
48
+ if (typeof e.must_read_if === "string" && e.must_read_if.length > 0) {
49
+ lines.push(` must_read_if: ${e.must_read_if}`);
50
+ }
51
+ }
52
+ }
53
+ const census = payload?.census;
54
+ if (census) {
55
+ const layer = census.by_layer ?? { team: 0, personal: 0, project: 0 };
56
+ lines.push(
57
+ `census: total ${census.total} \xB7 [team]${layer.team ?? 0} [project]${layer.project ?? 0} [personal]${layer.personal ?? 0}`
58
+ );
59
+ }
60
+ return lines.join("\n");
61
+ }
62
+ async function runContext(opts) {
63
+ const cwd = opts.target ? resolve(opts.target) : process.cwd();
64
+ const payload = opts.payload !== void 0 ? opts.payload : await runPlanContextHint({ all: true, target: opts.target });
65
+ const renderer = loadHookRenderer();
66
+ const sinks = renderer.buildSessionStartSinks(cwd, payload, {});
67
+ let base;
68
+ if (opts.render === "ai") {
69
+ base = sinks.ai ?? "";
70
+ } else if (opts.render === "human") {
71
+ base = sinks.human ?? "";
72
+ } else {
73
+ base = [sinks.human, sinks.ai].filter((s) => typeof s === "string" && s.length > 0).join("\n\n");
74
+ }
75
+ if (base.length === 0 && !sinks.hasRenderedContent) return "";
76
+ return opts.explain === true ? base + "\n" + renderExplain(sinks) : base;
77
+ }
78
+ var contextCommand = defineCommand({
79
+ meta: {
80
+ name: "context",
81
+ description: "Show what Fabric injects at SessionStart (the knowledge spine). --explain for per-entry provenance."
82
+ },
83
+ args: {
84
+ render: {
85
+ type: "string",
86
+ description: "Which sink to show: 'human' (systemMessage) or 'ai' (additionalContext). Default: both."
87
+ },
88
+ explain: {
89
+ type: "boolean",
90
+ description: "Append a per-entry provenance section (id \xB7 type \xB7 maturity \xB7 scope \xB7 why-surfaced).",
91
+ default: false
92
+ },
93
+ target: {
94
+ type: "string",
95
+ description: "Override the project root (defaults to cwd / dev-mode resolution)."
96
+ }
97
+ },
98
+ async run({ args }) {
99
+ try {
100
+ const render = args.render === "human" || args.render === "ai" ? args.render : void 0;
101
+ const out = await runContext({ render, explain: args.explain === true, target: args.target });
102
+ if (out.length > 0) process.stdout.write(`${out}
103
+ `);
104
+ } catch (error) {
105
+ const message = error instanceof Error ? error.message : String(error);
106
+ process.stderr.write(`context failed: ${message}
107
+ `);
108
+ process.exitCode = 1;
109
+ }
110
+ }
111
+ });
112
+ var context_default = contextCommand;
113
+ export {
114
+ contextCommand,
115
+ context_default as default,
116
+ runContext
117
+ };
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ doctorCommand,
4
+ doctor_default,
5
+ parseSinceDuration,
6
+ renderDoctorFilteredHelp,
7
+ renderTldrHeader
8
+ } from "./chunk-JTHWLUD3.js";
9
+ import "./chunk-FEOPLBGA.js";
10
+ import "./chunk-WA3DYGSY.js";
11
+ import "./chunk-NLNH64A3.js";
12
+ import "./chunk-PTGQAZEW.js";
13
+ import "./chunk-EOT63RDH.js";
14
+ import "./chunk-QPAW6IYT.js";
15
+ import "./chunk-QFIVFZRH.js";
16
+ import "./chunk-FNHDQTPC.js";
17
+ import "./chunk-HORSMSZL.js";
18
+ export {
19
+ doctor_default as default,
20
+ doctorCommand,
21
+ parseSinceDuration,
22
+ renderDoctorFilteredHelp,
23
+ renderTldrHeader
24
+ };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import * as citty from 'citty';
1
+ import { CommandDef, ArgsDef } from 'citty';
2
2
 
3
- declare const main: citty.CommandDef<citty.ArgsDef>;
3
+ declare const main: CommandDef<ArgsDef>;
4
4
  declare function run(): Promise<void>;
5
5
 
6
6
  export { main, run };