@fenglimg/fabric-cli 2.0.0-rc.15 → 2.0.0-rc.21

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.
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  FABRIC_HOOK_COMMAND_PATHS,
4
- FABRIC_SECTION_REGEX,
5
4
  HOOK_CONFIG_ARRAY_PATHS,
6
5
  HOOK_CONFIG_TARGETS,
6
+ HOOK_LIB_DESTINATIONS,
7
7
  HOOK_SCRIPT_DESTINATIONS,
8
- SECTION_TARGETS,
9
- SKILL_DESTINATIONS
10
- } from "./chunk-UTF4YBDN.js";
8
+ SKILL_DESTINATIONS,
9
+ fabricAgentsSnapshotPath
10
+ } from "./chunk-4HC5ZK7H.js";
11
11
  import {
12
12
  detectClientSupports,
13
13
  resolveClients
14
- } from "./chunk-SKSYUHKK.js";
14
+ } from "./chunk-MF3OTILQ.js";
15
15
  import {
16
16
  hasActionHint,
17
17
  paint,
@@ -23,7 +23,7 @@ import {
23
23
  import {
24
24
  createDebugLogger,
25
25
  resolveDevMode
26
- } from "./chunk-OBQU6NHO.js";
26
+ } from "./chunk-ZSESMG6L.js";
27
27
 
28
28
  // src/commands/uninstall.ts
29
29
  import { existsSync as existsSync2, statSync } from "fs";
@@ -39,6 +39,7 @@ import { existsSync } from "fs";
39
39
  import { readdir, readFile, rm, rmdir } from "fs/promises";
40
40
  import { dirname, join } from "path";
41
41
  import { atomicWriteJson, atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
42
+ import { BOOTSTRAP_REGEX } from "@fenglimg/fabric-shared/templates/bootstrap-canonical";
42
43
  async function uninstallFabricArchiveSkill(projectRoot) {
43
44
  return removeSkill("skill", SKILL_DESTINATIONS.fabricArchive, projectRoot);
44
45
  }
@@ -82,6 +83,34 @@ async function removeHookScripts(step, rels, projectRoot) {
82
83
  }
83
84
  return results;
84
85
  }
86
+ async function removeHookLibs(projectRoot) {
87
+ const results = [];
88
+ for (const dirRel of HOOK_LIB_DESTINATIONS) {
89
+ const dirAbs = join(projectRoot, dirRel);
90
+ if (!existsSync(dirAbs)) {
91
+ results.push({ step: "hook-lib", path: dirAbs, status: "skipped", message: "absent" });
92
+ continue;
93
+ }
94
+ let entries;
95
+ try {
96
+ entries = await readdir(dirAbs);
97
+ } catch (error) {
98
+ results.push({
99
+ step: "hook-lib",
100
+ path: dirAbs,
101
+ status: "error",
102
+ message: error instanceof Error ? error.message : String(error)
103
+ });
104
+ continue;
105
+ }
106
+ for (const entry of entries) {
107
+ if (!entry.endsWith(".cjs")) continue;
108
+ results.push(await rmIfExists("hook-lib", join(dirAbs, entry)));
109
+ }
110
+ results.push(await rmDirIfEmpty("hook-lib-dir", dirAbs));
111
+ }
112
+ return results;
113
+ }
85
114
  async function unmergeClaudeCodeHookConfig(projectRoot) {
86
115
  return unmergeHookConfig({
87
116
  step: "claude-hook-config",
@@ -112,69 +141,131 @@ async function unmergeCursorHookConfig(projectRoot) {
112
141
  extractCommands: extractFlatCommands
113
142
  });
114
143
  }
115
- async function stripFabricKnowledgeBaseSection(projectRoot) {
144
+ async function stripFabricBootstrapBlocks(projectRoot) {
116
145
  const results = [];
117
- for (const rel of SECTION_TARGETS) {
118
- const target = join(projectRoot, rel);
119
- if (!existsSync(target)) {
120
- results.push({ step: "section", path: target, status: "skipped", message: "absent" });
121
- continue;
122
- }
123
- let existing;
124
- try {
125
- existing = await readFile(target, "utf8");
126
- } catch (error) {
127
- results.push({
128
- step: "section",
129
- path: target,
130
- status: "error",
131
- message: error instanceof Error ? error.message : String(error)
132
- });
133
- continue;
134
- }
135
- const match = existing.match(FABRIC_SECTION_REGEX);
136
- if (match === null) {
137
- results.push({
138
- step: "section",
139
- path: target,
140
- status: "skipped",
141
- message: "no-fabric-section"
142
- });
143
- continue;
144
- }
145
- const before = existing.slice(0, match.index ?? 0);
146
- const after = existing.slice((match.index ?? 0) + match[0].length);
147
- const filtered = `${before}${after.replace(/^\r?\n/, "")}`;
148
- if (filtered === existing) {
149
- results.push({
150
- step: "section",
151
- path: target,
152
- status: "skipped",
153
- message: "no-fabric-section"
154
- });
155
- continue;
156
- }
146
+ results.push(await stripClaudeBootstrapImports(projectRoot));
147
+ results.push(await stripManagedBlock(projectRoot, "AGENTS.md", { deleteWhenEmpty: false }));
148
+ results.push(
149
+ await stripManagedBlock(projectRoot, join(".cursor", "rules", "fabric-bootstrap.mdc"), {
150
+ deleteWhenEmpty: true
151
+ })
152
+ );
153
+ return results;
154
+ }
155
+ async function stripClaudeBootstrapImports(projectRoot) {
156
+ const step = "bootstrap-claude";
157
+ const target = join(projectRoot, "CLAUDE.md");
158
+ if (!existsSync(target)) {
159
+ return { step, path: target, status: "skipped", message: "absent" };
160
+ }
161
+ let existing;
162
+ try {
163
+ existing = await readFile(target, "utf8");
164
+ } catch (error) {
165
+ return {
166
+ step,
167
+ path: target,
168
+ status: "error",
169
+ message: error instanceof Error ? error.message : String(error)
170
+ };
171
+ }
172
+ const managedLines = /* @__PURE__ */ new Set(["@.fabric/AGENTS.md", "@.fabric/project-rules.md"]);
173
+ const lines = existing.split(/\r?\n/);
174
+ const filtered = lines.filter((l) => !managedLines.has(l.replace(/\s+$/, "")));
175
+ if (filtered.length === lines.length) {
176
+ return { step, path: target, status: "skipped", message: "no-fabric-section" };
177
+ }
178
+ while (filtered.length > 1 && filtered[filtered.length - 1] === "" && filtered[filtered.length - 2] === "") {
179
+ filtered.pop();
180
+ }
181
+ const next = filtered.join("\n");
182
+ if (next === existing) {
183
+ return { step, path: target, status: "skipped", message: "no-fabric-section" };
184
+ }
185
+ try {
186
+ await atomicWriteText(target, next);
187
+ return { step, path: target, status: "removed" };
188
+ } catch (error) {
189
+ return {
190
+ step,
191
+ path: target,
192
+ status: "error",
193
+ message: error instanceof Error ? error.message : String(error)
194
+ };
195
+ }
196
+ }
197
+ async function stripManagedBlock(projectRoot, relPath, options) {
198
+ const step = relPath.endsWith(".mdc") ? "bootstrap-cursor" : "bootstrap-codex";
199
+ const target = join(projectRoot, relPath);
200
+ if (!existsSync(target)) {
201
+ return { step, path: target, status: "skipped", message: "absent" };
202
+ }
203
+ let existing;
204
+ try {
205
+ existing = await readFile(target, "utf8");
206
+ } catch (error) {
207
+ return {
208
+ step,
209
+ path: target,
210
+ status: "error",
211
+ message: error instanceof Error ? error.message : String(error)
212
+ };
213
+ }
214
+ const match = existing.match(BOOTSTRAP_REGEX);
215
+ if (match === null) {
216
+ return { step, path: target, status: "skipped", message: "no-fabric-section" };
217
+ }
218
+ const before = existing.slice(0, match.index ?? 0);
219
+ const after = existing.slice((match.index ?? 0) + match[0].length);
220
+ const filtered = `${before}${after.replace(/^\r?\n/, "")}`;
221
+ if (options.deleteWhenEmpty && isFrontMatterOnly(filtered)) {
157
222
  try {
158
- await atomicWriteText(target, filtered);
159
- results.push({ step: "section", path: target, status: "removed" });
223
+ await rm(target, { force: true });
224
+ return { step, path: target, status: "removed", message: "front-matter-only" };
160
225
  } catch (error) {
161
- results.push({
162
- step: "section",
226
+ return {
227
+ step,
163
228
  path: target,
164
229
  status: "error",
165
230
  message: error instanceof Error ? error.message : String(error)
166
- });
231
+ };
167
232
  }
168
233
  }
169
- return results;
234
+ try {
235
+ await atomicWriteText(target, filtered);
236
+ return { step, path: target, status: "removed" };
237
+ } catch (error) {
238
+ return {
239
+ step,
240
+ path: target,
241
+ status: "error",
242
+ message: error instanceof Error ? error.message : String(error)
243
+ };
244
+ }
245
+ }
246
+ function isFrontMatterOnly(content) {
247
+ const trimmed = content.replace(/^\s+/, "");
248
+ const match = trimmed.match(/^---\n[\s\S]*?\n---\s*$/);
249
+ if (match === null) return trimmed.length === 0;
250
+ return true;
251
+ }
252
+ async function deleteFabricAgentsSnapshot(projectRoot) {
253
+ const target = fabricAgentsSnapshotPath(projectRoot);
254
+ return rmIfExists("bootstrap-snapshot", target);
170
255
  }
171
256
  async function uninstallBootstrapStage(projectRoot, _opts = {}) {
172
257
  const results = [];
173
258
  await runAndCollect(
174
259
  results,
175
- "section",
260
+ "bootstrap-blocks",
261
+ projectRoot,
262
+ () => stripFabricBootstrapBlocks(projectRoot)
263
+ );
264
+ await runAndCollectOne(
265
+ results,
266
+ "bootstrap-snapshot",
176
267
  projectRoot,
177
- () => stripFabricKnowledgeBaseSection(projectRoot)
268
+ () => deleteFabricAgentsSnapshot(projectRoot)
178
269
  );
179
270
  await runAndCollectOne(
180
271
  results,
@@ -194,6 +285,7 @@ async function uninstallBootstrapStage(projectRoot, _opts = {}) {
194
285
  projectRoot,
195
286
  () => unmergeClaudeCodeHookConfig(projectRoot)
196
287
  );
288
+ await runAndCollect(results, "hook-lib", projectRoot, () => removeHookLibs(projectRoot));
197
289
  await runAndCollect(
198
290
  results,
199
291
  "hook-narrow-script",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-cli",
3
- "version": "2.0.0-rc.15",
3
+ "version": "2.0.0-rc.21",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "fab": "dist/index.js",
@@ -20,8 +20,8 @@
20
20
  "tree-sitter-javascript": "^0.25.0",
21
21
  "tree-sitter-typescript": "^0.23.2",
22
22
  "web-tree-sitter": "^0.26.8",
23
- "@fenglimg/fabric-server": "2.0.0-rc.15",
24
- "@fenglimg/fabric-shared": "2.0.0-rc.15"
23
+ "@fenglimg/fabric-server": "2.0.0-rc.21",
24
+ "@fenglimg/fabric-shared": "2.0.0-rc.21"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.15.0",