@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.
- package/README.md +8 -5
- package/dist/{chunk-5LQIHYFC.js → chunk-27HK6H5Y.js} +10 -5
- package/dist/{chunk-F6ITRM7T.js → chunk-2KBCTMID.js} +29 -6
- package/dist/{chunk-XC5RUHLK.js → chunk-3IOLS5EK.js} +23 -38
- package/dist/{chunk-XHHCRDIR.js → chunk-CMDW3PYK.js} +105 -220
- package/dist/chunk-FEOPLBGA.js +150 -0
- package/dist/{chunk-XCBVSGCS.js → chunk-FNHDQTPC.js} +1 -10
- package/dist/{chunk-2CY4BMTH.js → chunk-HORSMSZL.js} +9 -5
- package/dist/{doctor-J4O3X54I.js → chunk-JTHWLUD3.js} +103 -51
- package/dist/{chunk-BO4XIZWZ.js → chunk-NLNH64A3.js} +5 -18
- package/dist/{chunk-H3FE6VIK.js → chunk-PTGQAZEW.js} +13 -3
- package/dist/chunk-QFIVFZRH.js +13 -0
- package/dist/chunk-QPAW6IYT.js +387 -0
- package/dist/{chunk-COI5VDFU.js → chunk-WA3DYGSY.js} +1 -2
- package/dist/{plan-context-hint-CHVZGOZ5.js → chunk-YM4XATJF.js} +29 -4
- package/dist/{config-VJMXCLXW.js → config-A3LTECAY.js} +4 -3
- package/dist/context-7NUKXDB6.js +117 -0
- package/dist/doctor-REZDNH4A.js +24 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +131 -21
- package/dist/info-7FKBTMVO.js +139 -0
- package/dist/install-v2-2COC3DO3.js +3277 -0
- package/dist/{metrics-RER6NLFC.js → metrics-HMFH4YHK.js} +1 -1
- package/dist/{onboard-coverage-JWQWDZW7.js → onboard-coverage-XSG77LL3.js} +48 -27
- package/dist/plan-context-hint-G75R4P4J.js +12 -0
- package/dist/{scope-explain-BWRWBCCP.js → scope-explain-HLJZ2M33.js} +3 -2
- package/dist/{status-PANEGKU2.js → status-4R3TM4FJ.js} +8 -5
- package/dist/store-HOCORVL3.js +563 -0
- package/dist/{sync-EA5HZMXM.js → sync-DT5UJMMR.js} +36 -13
- package/dist/{uninstall-F75MPKQC.js → uninstall-62F4LNKI.js} +62 -140
- package/dist/{whoami-66YKY5DZ.js → whoami-ITGEFWH4.js} +9 -7
- package/package.json +7 -5
- package/templates/hooks/cite-policy-evict.cjs +5 -5
- package/templates/hooks/configs/README.md +14 -27
- package/templates/hooks/configs/claude-code.json +1 -1
- package/templates/hooks/configs/codex-hooks.json +3 -3
- package/templates/hooks/fabric-hint.cjs +301 -161
- package/templates/hooks/knowledge-hint-broad.cjs +426 -207
- package/templates/hooks/knowledge-hint-narrow.cjs +56 -56
- package/templates/hooks/lib/banner-i18n.cjs +31 -0
- package/templates/hooks/lib/bindings-snapshot-reader.cjs +117 -7
- package/templates/hooks/lib/cite-line-parser.cjs +12 -20
- package/templates/hooks/lib/client-adapter.cjs +66 -7
- package/templates/hooks/lib/nudge-policy.cjs +117 -0
- package/templates/hooks/lib/state-store.cjs +60 -0
- package/templates/hooks/lib/summary-fallback.cjs +82 -19
- package/templates/hooks/post-tooluse-mutation.cjs +112 -11
- package/templates/skills/fabric/SKILL.md +94 -0
- package/templates/skills/fabric-archive/SKILL.md +29 -26
- package/templates/skills/fabric-archive/ref/dry-run-scope.md +1 -1
- package/templates/skills/fabric-archive/ref/i18n-policy.md +2 -3
- package/templates/skills/fabric-archive/ref/phase-1-5-onboard.md +2 -3
- package/templates/skills/fabric-archive/ref/phase-1-cross-session.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-2-5-viability.md +1 -1
- package/templates/skills/fabric-archive/ref/phase-3-6-related-edges.md +18 -0
- package/templates/skills/fabric-archive/ref/phase-3-7-semantic-scope.md +47 -0
- package/templates/skills/fabric-audit/SKILL.md +13 -3
- package/templates/skills/fabric-connect/SKILL.md +3 -3
- package/templates/skills/fabric-import/SKILL.md +7 -7
- package/templates/skills/fabric-import/ref/i18n-policy.md +2 -3
- package/templates/skills/fabric-import/ref/state-recovery.md +1 -2
- package/templates/skills/fabric-review/SKILL.md +5 -5
- package/templates/skills/fabric-review/ref/cite-contract.md +1 -1
- package/templates/skills/fabric-review/ref/i18n-policy.md +2 -3
- package/templates/skills/fabric-review/ref/output-contract.md +1 -1
- package/templates/skills/fabric-review/ref/per-mode-flows.md +2 -2
- package/templates/skills/fabric-review/ref/worked-examples.md +1 -1
- package/templates/skills/fabric-store/SKILL.md +1 -1
- package/templates/skills/fabric-sync/SKILL.md +1 -1
- package/templates/skills/lib/shared-policy.md +2 -2
- package/dist/chunk-5ZUMLCD5.js +0 -248
- package/dist/install-BULNDUIM.js +0 -2816
- package/dist/store-66NK2FTQ.js +0 -443
- package/templates/hooks/configs/cursor-hooks.json +0 -30
- package/templates/hooks/lib/cite-contract-reminder.cjs +0 -179
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
deepMerge
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3IOLS5EK.js";
|
|
5
5
|
|
|
6
6
|
// src/install/write-bootstrap-snapshot.ts
|
|
7
7
|
import { existsSync, readFileSync } from "fs";
|
|
8
8
|
import { mkdir } from "fs/promises";
|
|
9
9
|
import { dirname, join } from "path";
|
|
10
10
|
import { atomicWriteText } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
11
|
-
import {
|
|
11
|
+
import { resolveBootstrapCanonical } from "@fenglimg/fabric-shared/templates/bootstrap-canonical";
|
|
12
12
|
var FABRIC_AGENTS_RELPATH = join(".fabric", "AGENTS.md");
|
|
13
13
|
var PROJECT_RULES_RELPATH = join(".fabric", "project-rules.md");
|
|
14
14
|
function fabricAgentsSnapshotPath(targetRoot) {
|
|
@@ -25,31 +25,32 @@ function readProjectRulesIfPresent(targetRoot) {
|
|
|
25
25
|
async function writeFabricAgentsSnapshot(targetRoot) {
|
|
26
26
|
const step = "bootstrap-snapshot";
|
|
27
27
|
const target = fabricAgentsSnapshotPath(targetRoot);
|
|
28
|
+
const canonical = resolveBootstrapCanonical();
|
|
28
29
|
if (existsSync(target)) {
|
|
29
30
|
try {
|
|
30
31
|
const existing = readFileSync(target, "utf8");
|
|
31
|
-
if (existing ===
|
|
32
|
+
if (existing === canonical) {
|
|
32
33
|
return { step, path: target, status: "skipped", message: "up-to-date" };
|
|
33
34
|
}
|
|
34
35
|
} catch {
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
await mkdir(dirname(target), { recursive: true });
|
|
38
|
-
await atomicWriteText(target,
|
|
39
|
+
await atomicWriteText(target, canonical);
|
|
39
40
|
return { step, path: target, status: "written" };
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
// src/install/skills-and-hooks.ts
|
|
43
|
-
import { chmodSync, existsSync as existsSync2, readdirSync, readFileSync as readFileSync2
|
|
44
|
+
import { chmodSync, existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
44
45
|
import { mkdir as mkdir2, readFile, rm } from "fs/promises";
|
|
45
46
|
import { dirname as dirname2, join as join2, parse, resolve } from "path";
|
|
46
47
|
import { fileURLToPath } from "url";
|
|
48
|
+
import { resolveGlobalLocale } from "@fenglimg/fabric-shared";
|
|
47
49
|
import { atomicWriteJson, atomicWriteText as atomicWriteText2 } from "@fenglimg/fabric-shared/node/atomic-write";
|
|
48
50
|
import {
|
|
49
51
|
BOOTSTRAP_MARKER_BEGIN,
|
|
50
52
|
BOOTSTRAP_MARKER_END,
|
|
51
|
-
BOOTSTRAP_REGEX
|
|
52
|
-
LEGACY_KB_REGEX
|
|
53
|
+
BOOTSTRAP_REGEX
|
|
53
54
|
} from "@fenglimg/fabric-shared/templates/bootstrap-canonical";
|
|
54
55
|
var SKILL_TEMPLATE_REL = "skills/fabric-archive/SKILL.md";
|
|
55
56
|
var SKILL_REVIEW_TEMPLATE_REL = "skills/fabric-review/SKILL.md";
|
|
@@ -67,7 +68,6 @@ var HOOK_POST_TOOLUSE_SCRIPT_TEMPLATE_REL = "hooks/post-tooluse-mutation.cjs";
|
|
|
67
68
|
var HOOK_LIB_TEMPLATE_DIR_REL = "hooks/lib";
|
|
68
69
|
var CLAUDE_HOOK_CONFIG_TEMPLATE_REL = "hooks/configs/claude-code.json";
|
|
69
70
|
var CODEX_HOOK_CONFIG_TEMPLATE_REL = "hooks/configs/codex-hooks.json";
|
|
70
|
-
var CURSOR_HOOK_CONFIG_TEMPLATE_REL = "hooks/configs/cursor-hooks.json";
|
|
71
71
|
var SKILL_DESTINATIONS = {
|
|
72
72
|
fabricArchive: [
|
|
73
73
|
".claude/skills/fabric-archive/SKILL.md",
|
|
@@ -105,6 +105,53 @@ var SKILL_DESTINATIONS = {
|
|
|
105
105
|
".codex/skills/fabric-connect/SKILL.md"
|
|
106
106
|
]
|
|
107
107
|
};
|
|
108
|
+
var FABRIC_SKILL_INSTALL_SPECS = {
|
|
109
|
+
fabricArchive: {
|
|
110
|
+
slug: "fabric-archive",
|
|
111
|
+
templateRel: SKILL_TEMPLATE_REL,
|
|
112
|
+
destinations: SKILL_DESTINATIONS.fabricArchive,
|
|
113
|
+
step: "skill",
|
|
114
|
+
includeRefFiles: true
|
|
115
|
+
},
|
|
116
|
+
fabricReview: {
|
|
117
|
+
slug: "fabric-review",
|
|
118
|
+
templateRel: SKILL_REVIEW_TEMPLATE_REL,
|
|
119
|
+
destinations: SKILL_DESTINATIONS.fabricReview,
|
|
120
|
+
step: "skill-review",
|
|
121
|
+
includeRefFiles: true
|
|
122
|
+
},
|
|
123
|
+
fabricImport: {
|
|
124
|
+
slug: "fabric-import",
|
|
125
|
+
templateRel: SKILL_IMPORT_TEMPLATE_REL,
|
|
126
|
+
destinations: SKILL_DESTINATIONS.fabricImport,
|
|
127
|
+
step: "skill-import",
|
|
128
|
+
includeRefFiles: true
|
|
129
|
+
},
|
|
130
|
+
fabricSync: {
|
|
131
|
+
slug: "fabric-sync",
|
|
132
|
+
templateRel: SKILL_SYNC_TEMPLATE_REL,
|
|
133
|
+
destinations: SKILL_DESTINATIONS.fabricSync,
|
|
134
|
+
step: "skill-sync"
|
|
135
|
+
},
|
|
136
|
+
fabricStore: {
|
|
137
|
+
slug: "fabric-store",
|
|
138
|
+
templateRel: SKILL_STORE_TEMPLATE_REL,
|
|
139
|
+
destinations: SKILL_DESTINATIONS.fabricStore,
|
|
140
|
+
step: "skill-store"
|
|
141
|
+
},
|
|
142
|
+
fabricAudit: {
|
|
143
|
+
slug: "fabric-audit",
|
|
144
|
+
templateRel: SKILL_AUDIT_TEMPLATE_REL,
|
|
145
|
+
destinations: SKILL_DESTINATIONS.fabricAudit,
|
|
146
|
+
step: "skill-audit"
|
|
147
|
+
},
|
|
148
|
+
fabricConnect: {
|
|
149
|
+
slug: "fabric-connect",
|
|
150
|
+
templateRel: SKILL_CONNECT_TEMPLATE_REL,
|
|
151
|
+
destinations: SKILL_DESTINATIONS.fabricConnect,
|
|
152
|
+
step: "skill-connect"
|
|
153
|
+
}
|
|
154
|
+
};
|
|
108
155
|
var DEPRECATED_SKILL_DIRS = [
|
|
109
156
|
".claude/skills/fabric-init",
|
|
110
157
|
".codex/skills/fabric-init"
|
|
@@ -112,54 +159,46 @@ var DEPRECATED_SKILL_DIRS = [
|
|
|
112
159
|
var HOOK_SCRIPT_DESTINATIONS = {
|
|
113
160
|
fabricHint: [
|
|
114
161
|
".claude/hooks/fabric-hint.cjs",
|
|
115
|
-
".codex/hooks/fabric-hint.cjs"
|
|
116
|
-
".cursor/hooks/fabric-hint.cjs"
|
|
162
|
+
".codex/hooks/fabric-hint.cjs"
|
|
117
163
|
],
|
|
118
164
|
knowledgeHintBroad: [
|
|
119
165
|
".claude/hooks/knowledge-hint-broad.cjs",
|
|
120
|
-
".codex/hooks/knowledge-hint-broad.cjs"
|
|
121
|
-
".cursor/hooks/knowledge-hint-broad.cjs"
|
|
166
|
+
".codex/hooks/knowledge-hint-broad.cjs"
|
|
122
167
|
],
|
|
123
168
|
knowledgeHintNarrow: [
|
|
124
169
|
".claude/hooks/knowledge-hint-narrow.cjs",
|
|
125
|
-
".codex/hooks/knowledge-hint-narrow.cjs"
|
|
126
|
-
".cursor/hooks/knowledge-hint-narrow.cjs"
|
|
170
|
+
".codex/hooks/knowledge-hint-narrow.cjs"
|
|
127
171
|
],
|
|
128
172
|
// v2.0.0-rc.34 TASK-06: Claude Code — UserPromptSubmit cite-policy long-
|
|
129
173
|
// session evict sidecar.
|
|
130
|
-
// v2.0.0-rc.37 NEW-21: extended to Codex
|
|
131
|
-
//
|
|
174
|
+
// v2.0.0-rc.37 NEW-21: extended to Codex SessionStart slot.
|
|
175
|
+
// Codex doesn't have an equivalent per-prompt event, so cite-policy-
|
|
132
176
|
// evict.cjs runs in "SessionStart mode" (one-shot stderr emit per session
|
|
133
177
|
// boot, no turn-counter). Cadence is lower than Claude Code's per-prompt
|
|
134
|
-
// window but strictly better than 0 (rc.32 baseline measured Codex
|
|
178
|
+
// window but strictly better than 0 (rc.32 baseline measured Codex
|
|
135
179
|
// at 3.1% cite coverage when no cite-reminder surface existed).
|
|
136
180
|
citePolicyEvict: [
|
|
137
181
|
".claude/hooks/cite-policy-evict.cjs",
|
|
138
|
-
".codex/hooks/cite-policy-evict.cjs"
|
|
139
|
-
".cursor/hooks/cite-policy-evict.cjs"
|
|
182
|
+
".codex/hooks/cite-policy-evict.cjs"
|
|
140
183
|
],
|
|
141
|
-
// lifecycle-refactor W2-T2: SessionEnd marker hook —
|
|
184
|
+
// lifecycle-refactor W2-T2: SessionEnd marker hook — both clients.
|
|
142
185
|
sessionEndMarker: [
|
|
143
186
|
".claude/hooks/session-end-marker.cjs",
|
|
144
|
-
".codex/hooks/session-end-marker.cjs"
|
|
145
|
-
".cursor/hooks/session-end-marker.cjs"
|
|
187
|
+
".codex/hooks/session-end-marker.cjs"
|
|
146
188
|
],
|
|
147
|
-
// lifecycle-refactor W2-T3: PostToolUse mutation marker hook —
|
|
189
|
+
// lifecycle-refactor W2-T3: PostToolUse mutation marker hook — both.
|
|
148
190
|
postTooluseMutation: [
|
|
149
191
|
".claude/hooks/post-tooluse-mutation.cjs",
|
|
150
|
-
".codex/hooks/post-tooluse-mutation.cjs"
|
|
151
|
-
".cursor/hooks/post-tooluse-mutation.cjs"
|
|
192
|
+
".codex/hooks/post-tooluse-mutation.cjs"
|
|
152
193
|
]
|
|
153
194
|
};
|
|
154
195
|
var HOOK_LIB_DESTINATIONS = [
|
|
155
196
|
".claude/hooks/lib",
|
|
156
|
-
".codex/hooks/lib"
|
|
157
|
-
".cursor/hooks/lib"
|
|
197
|
+
".codex/hooks/lib"
|
|
158
198
|
];
|
|
159
199
|
var HOOK_CONFIG_TARGETS = {
|
|
160
200
|
claudeCode: ".claude/settings.json",
|
|
161
|
-
codex: ".codex/hooks.json"
|
|
162
|
-
cursor: ".cursor/hooks.json"
|
|
201
|
+
codex: ".codex/hooks.json"
|
|
163
202
|
};
|
|
164
203
|
var HOOK_CONFIG_ARRAY_PATHS = {
|
|
165
204
|
// F2: "hooks.UserPromptSubmit" MUST be listed — the Claude Code template
|
|
@@ -177,8 +216,7 @@ var HOOK_CONFIG_ARRAY_PATHS = {
|
|
|
177
216
|
"hooks.PostToolUse",
|
|
178
217
|
"hooks.SessionEnd"
|
|
179
218
|
],
|
|
180
|
-
codex: ["events.Stop", "events.SessionStart", "events.PreToolUse", "events.PostToolUse", "events.SessionEnd"]
|
|
181
|
-
cursor: ["hooks.stop", "hooks.sessionStart", "hooks.preToolUse", "hooks.postToolUse", "hooks.sessionEnd"]
|
|
219
|
+
codex: ["events.Stop", "events.SessionStart", "events.PreToolUse", "events.PostToolUse", "events.SessionEnd"]
|
|
182
220
|
};
|
|
183
221
|
var FABRIC_HOOK_COMMAND_PATHS = {
|
|
184
222
|
claudeCode: {
|
|
@@ -199,33 +237,8 @@ var FABRIC_HOOK_COMMAND_PATHS = {
|
|
|
199
237
|
citePolicyEvict: '"$(git rev-parse --show-toplevel)/.codex/hooks/cite-policy-evict.cjs"',
|
|
200
238
|
sessionEndMarker: '"$(git rev-parse --show-toplevel)/.codex/hooks/session-end-marker.cjs"',
|
|
201
239
|
postTooluseMutation: '"$(git rev-parse --show-toplevel)/.codex/hooks/post-tooluse-mutation.cjs"'
|
|
202
|
-
},
|
|
203
|
-
cursor: {
|
|
204
|
-
fabricHint: ".cursor/hooks/fabric-hint.cjs",
|
|
205
|
-
knowledgeHintBroad: ".cursor/hooks/knowledge-hint-broad.cjs",
|
|
206
|
-
knowledgeHintNarrow: ".cursor/hooks/knowledge-hint-narrow.cjs",
|
|
207
|
-
citePolicyEvict: ".cursor/hooks/cite-policy-evict.cjs",
|
|
208
|
-
sessionEndMarker: ".cursor/hooks/session-end-marker.cjs",
|
|
209
|
-
postTooluseMutation: ".cursor/hooks/post-tooluse-mutation.cjs"
|
|
210
240
|
}
|
|
211
241
|
};
|
|
212
|
-
function readFabricLanguagePreference(projectRoot) {
|
|
213
|
-
const configPath = join2(projectRoot, ".fabric", "fabric-config.json");
|
|
214
|
-
if (!existsSync2(configPath)) {
|
|
215
|
-
return "match-existing";
|
|
216
|
-
}
|
|
217
|
-
try {
|
|
218
|
-
const raw = readFileSync2(configPath, "utf8");
|
|
219
|
-
const parsed = JSON.parse(raw);
|
|
220
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
221
|
-
return "match-existing";
|
|
222
|
-
}
|
|
223
|
-
const value = parsed["fabric_language"];
|
|
224
|
-
return typeof value === "string" && value.length > 0 ? value : "match-existing";
|
|
225
|
-
} catch {
|
|
226
|
-
return "match-existing";
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
242
|
var SKILL_TOKEN_ERROR_TOKENS = 1e4;
|
|
230
243
|
var STALE_INSTALL_RATIO = 1.5;
|
|
231
244
|
function estimateSkillTokens(text) {
|
|
@@ -254,113 +267,44 @@ function inspectStaleInstall(target, source) {
|
|
|
254
267
|
}
|
|
255
268
|
return null;
|
|
256
269
|
}
|
|
257
|
-
async function
|
|
258
|
-
const source = await readTemplate(
|
|
259
|
-
validateSkillCanonicalSize(source,
|
|
260
|
-
const targets =
|
|
270
|
+
async function installFabricSkill(projectRoot, spec) {
|
|
271
|
+
const source = await readTemplate(spec.templateRel);
|
|
272
|
+
validateSkillCanonicalSize(source, spec.slug);
|
|
273
|
+
const targets = spec.destinations.map((rel) => join2(projectRoot, rel));
|
|
261
274
|
const results = [];
|
|
262
275
|
for (const target of targets) {
|
|
263
276
|
const staleMsg = inspectStaleInstall(target, source);
|
|
264
|
-
const result = await copyTextIdempotent(
|
|
277
|
+
const result = await copyTextIdempotent(spec.step, source, target);
|
|
265
278
|
if (staleMsg && result.status === "written") {
|
|
266
279
|
result.message = result.message ? `${staleMsg}; ${result.message}` : staleMsg;
|
|
267
280
|
}
|
|
268
281
|
results.push(result);
|
|
269
282
|
}
|
|
270
|
-
|
|
283
|
+
if (spec.includeRefFiles) {
|
|
284
|
+
results.push(...await installSkillRefFiles(projectRoot, spec.slug));
|
|
285
|
+
}
|
|
271
286
|
return results;
|
|
272
287
|
}
|
|
288
|
+
async function installFabricArchiveSkill(projectRoot, _options = {}) {
|
|
289
|
+
return installFabricSkill(projectRoot, FABRIC_SKILL_INSTALL_SPECS.fabricArchive);
|
|
290
|
+
}
|
|
273
291
|
async function installFabricReviewSkill(projectRoot, _options = {}) {
|
|
274
|
-
|
|
275
|
-
validateSkillCanonicalSize(source, "fabric-review");
|
|
276
|
-
const targets = SKILL_DESTINATIONS.fabricReview.map((rel) => join2(projectRoot, rel));
|
|
277
|
-
const results = [];
|
|
278
|
-
for (const target of targets) {
|
|
279
|
-
const staleMsg = inspectStaleInstall(target, source);
|
|
280
|
-
const result = await copyTextIdempotent("skill-review", source, target);
|
|
281
|
-
if (staleMsg && result.status === "written") {
|
|
282
|
-
result.message = result.message ? `${staleMsg}; ${result.message}` : staleMsg;
|
|
283
|
-
}
|
|
284
|
-
results.push(result);
|
|
285
|
-
}
|
|
286
|
-
results.push(...await installSkillRefFiles(projectRoot, "fabric-review"));
|
|
287
|
-
return results;
|
|
292
|
+
return installFabricSkill(projectRoot, FABRIC_SKILL_INSTALL_SPECS.fabricReview);
|
|
288
293
|
}
|
|
289
294
|
async function installFabricImportSkill(projectRoot, _options = {}) {
|
|
290
|
-
|
|
291
|
-
validateSkillCanonicalSize(source, "fabric-import");
|
|
292
|
-
const targets = SKILL_DESTINATIONS.fabricImport.map((rel) => join2(projectRoot, rel));
|
|
293
|
-
const results = [];
|
|
294
|
-
for (const target of targets) {
|
|
295
|
-
const staleMsg = inspectStaleInstall(target, source);
|
|
296
|
-
const result = await copyTextIdempotent("skill-import", source, target);
|
|
297
|
-
if (staleMsg && result.status === "written") {
|
|
298
|
-
result.message = result.message ? `${staleMsg}; ${result.message}` : staleMsg;
|
|
299
|
-
}
|
|
300
|
-
results.push(result);
|
|
301
|
-
}
|
|
302
|
-
results.push(...await installSkillRefFiles(projectRoot, "fabric-import"));
|
|
303
|
-
return results;
|
|
295
|
+
return installFabricSkill(projectRoot, FABRIC_SKILL_INSTALL_SPECS.fabricImport);
|
|
304
296
|
}
|
|
305
297
|
async function installFabricSyncSkill(projectRoot, _options = {}) {
|
|
306
|
-
|
|
307
|
-
validateSkillCanonicalSize(source, "fabric-sync");
|
|
308
|
-
const targets = SKILL_DESTINATIONS.fabricSync.map((rel) => join2(projectRoot, rel));
|
|
309
|
-
const results = [];
|
|
310
|
-
for (const target of targets) {
|
|
311
|
-
const staleMsg = inspectStaleInstall(target, source);
|
|
312
|
-
const result = await copyTextIdempotent("skill-sync", source, target);
|
|
313
|
-
if (staleMsg && result.status === "written") {
|
|
314
|
-
result.message = result.message ? `${staleMsg}; ${result.message}` : staleMsg;
|
|
315
|
-
}
|
|
316
|
-
results.push(result);
|
|
317
|
-
}
|
|
318
|
-
return results;
|
|
298
|
+
return installFabricSkill(projectRoot, FABRIC_SKILL_INSTALL_SPECS.fabricSync);
|
|
319
299
|
}
|
|
320
300
|
async function installFabricStoreSkill(projectRoot, _options = {}) {
|
|
321
|
-
|
|
322
|
-
validateSkillCanonicalSize(source, "fabric-store");
|
|
323
|
-
const targets = SKILL_DESTINATIONS.fabricStore.map((rel) => join2(projectRoot, rel));
|
|
324
|
-
const results = [];
|
|
325
|
-
for (const target of targets) {
|
|
326
|
-
const staleMsg = inspectStaleInstall(target, source);
|
|
327
|
-
const result = await copyTextIdempotent("skill-store", source, target);
|
|
328
|
-
if (staleMsg && result.status === "written") {
|
|
329
|
-
result.message = result.message ? `${staleMsg}; ${result.message}` : staleMsg;
|
|
330
|
-
}
|
|
331
|
-
results.push(result);
|
|
332
|
-
}
|
|
333
|
-
return results;
|
|
301
|
+
return installFabricSkill(projectRoot, FABRIC_SKILL_INSTALL_SPECS.fabricStore);
|
|
334
302
|
}
|
|
335
303
|
async function installFabricAuditSkill(projectRoot, _options = {}) {
|
|
336
|
-
|
|
337
|
-
validateSkillCanonicalSize(source, "fabric-audit");
|
|
338
|
-
const targets = SKILL_DESTINATIONS.fabricAudit.map((rel) => join2(projectRoot, rel));
|
|
339
|
-
const results = [];
|
|
340
|
-
for (const target of targets) {
|
|
341
|
-
const staleMsg = inspectStaleInstall(target, source);
|
|
342
|
-
const result = await copyTextIdempotent("skill-audit", source, target);
|
|
343
|
-
if (staleMsg && result.status === "written") {
|
|
344
|
-
result.message = result.message ? `${staleMsg}; ${result.message}` : staleMsg;
|
|
345
|
-
}
|
|
346
|
-
results.push(result);
|
|
347
|
-
}
|
|
348
|
-
return results;
|
|
304
|
+
return installFabricSkill(projectRoot, FABRIC_SKILL_INSTALL_SPECS.fabricAudit);
|
|
349
305
|
}
|
|
350
306
|
async function installFabricConnectSkill(projectRoot, _options = {}) {
|
|
351
|
-
|
|
352
|
-
validateSkillCanonicalSize(source, "fabric-connect");
|
|
353
|
-
const targets = SKILL_DESTINATIONS.fabricConnect.map((rel) => join2(projectRoot, rel));
|
|
354
|
-
const results = [];
|
|
355
|
-
for (const target of targets) {
|
|
356
|
-
const staleMsg = inspectStaleInstall(target, source);
|
|
357
|
-
const result = await copyTextIdempotent("skill-connect", source, target);
|
|
358
|
-
if (staleMsg && result.status === "written") {
|
|
359
|
-
result.message = result.message ? `${staleMsg}; ${result.message}` : staleMsg;
|
|
360
|
-
}
|
|
361
|
-
results.push(result);
|
|
362
|
-
}
|
|
363
|
-
return results;
|
|
307
|
+
return installFabricSkill(projectRoot, FABRIC_SKILL_INSTALL_SPECS.fabricConnect);
|
|
364
308
|
}
|
|
365
309
|
async function cleanupDeprecatedSkills(projectRoot) {
|
|
366
310
|
const results = [];
|
|
@@ -647,16 +591,6 @@ async function mergeCodexHookConfig(projectRoot, _options = {}) {
|
|
|
647
591
|
[...HOOK_CONFIG_ARRAY_PATHS.codex]
|
|
648
592
|
);
|
|
649
593
|
}
|
|
650
|
-
async function mergeCursorHookConfig(projectRoot, _options = {}) {
|
|
651
|
-
const fragment = await readJsonTemplate(CURSOR_HOOK_CONFIG_TEMPLATE_REL);
|
|
652
|
-
const targetPath = join2(projectRoot, HOOK_CONFIG_TARGETS.cursor);
|
|
653
|
-
return mergeJsonIdempotent(
|
|
654
|
-
"cursor-hook-config",
|
|
655
|
-
targetPath,
|
|
656
|
-
fragment,
|
|
657
|
-
[...HOOK_CONFIG_ARRAY_PATHS.cursor]
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
594
|
function buildManagedBlockBody(targetRoot) {
|
|
661
595
|
const snapshotPath = fabricAgentsSnapshotPath(targetRoot);
|
|
662
596
|
const snapshot = readFileSync2(snapshotPath, "utf8");
|
|
@@ -673,13 +607,6 @@ function wrapInBootstrapMarkers(body) {
|
|
|
673
607
|
${body}
|
|
674
608
|
${BOOTSTRAP_MARKER_END}`;
|
|
675
609
|
}
|
|
676
|
-
function stripLegacyKnowledgeBaseSection(existing) {
|
|
677
|
-
const match = existing.match(LEGACY_KB_REGEX);
|
|
678
|
-
if (match === null) return existing;
|
|
679
|
-
const before = existing.slice(0, match.index ?? 0);
|
|
680
|
-
const after = existing.slice((match.index ?? 0) + match[0].length);
|
|
681
|
-
return `${before}${after.replace(/^\r?\n/, "")}`;
|
|
682
|
-
}
|
|
683
610
|
var CLAUDE_BOOTSTRAP_HEADER = "# Project Knowledge";
|
|
684
611
|
var CLAUDE_AGENTS_IMPORT_LINE = "@.fabric/AGENTS.md";
|
|
685
612
|
var CLAUDE_PROJECT_RULES_IMPORT_LINE = "@.fabric/project-rules.md";
|
|
@@ -702,7 +629,7 @@ async function writeClaudeBootstrapThinShell(targetRoot, _options = {}) {
|
|
|
702
629
|
};
|
|
703
630
|
}
|
|
704
631
|
}
|
|
705
|
-
let next =
|
|
632
|
+
let next = existing;
|
|
706
633
|
if (!projectRulesPresent) {
|
|
707
634
|
next = removeImportLine(next, CLAUDE_PROJECT_RULES_IMPORT_LINE);
|
|
708
635
|
}
|
|
@@ -777,23 +704,22 @@ async function writeCodexBootstrapManagedBlock(targetRoot, _options = {}) {
|
|
|
777
704
|
}
|
|
778
705
|
const body = buildManagedBlockBody(targetRoot);
|
|
779
706
|
const managedBlock = wrapInBootstrapMarkers(body);
|
|
780
|
-
const stripped = stripLegacyKnowledgeBaseSection(existing);
|
|
781
707
|
let next;
|
|
782
|
-
const match =
|
|
708
|
+
const match = existing.match(BOOTSTRAP_REGEX);
|
|
783
709
|
if (match !== null) {
|
|
784
|
-
const before =
|
|
785
|
-
const after =
|
|
710
|
+
const before = existing.slice(0, match.index ?? 0);
|
|
711
|
+
const after = existing.slice((match.index ?? 0) + match[0].length);
|
|
786
712
|
const cleaned = `${before}${after.replace(/^\r?\n/, "")}`;
|
|
787
713
|
const trailingNewline = cleaned.length === 0 || cleaned.endsWith("\n") ? "" : "\n";
|
|
788
714
|
next = `${cleaned}${trailingNewline}${cleaned.length === 0 ? "" : "\n"}${managedBlock}
|
|
789
715
|
`;
|
|
790
716
|
} else {
|
|
791
|
-
if (
|
|
717
|
+
if (existing.length === 0) {
|
|
792
718
|
next = `${managedBlock}
|
|
793
719
|
`;
|
|
794
720
|
} else {
|
|
795
|
-
const trailingNewline =
|
|
796
|
-
next = `${
|
|
721
|
+
const trailingNewline = existing.endsWith("\n") ? "" : "\n";
|
|
722
|
+
next = `${existing}${trailingNewline}
|
|
797
723
|
${managedBlock}
|
|
798
724
|
`;
|
|
799
725
|
}
|
|
@@ -814,55 +740,6 @@ ${managedBlock}
|
|
|
814
740
|
};
|
|
815
741
|
}
|
|
816
742
|
}
|
|
817
|
-
var CURSOR_RULE_FRONT_MATTER = "---\nalwaysApply: true\ndescription: Fabric Protocol bootstrap rules\n---\n\n";
|
|
818
|
-
var CURSOR_LEGACY_FLAT_FILE_REL = join2(".cursor", "rules");
|
|
819
|
-
var CURSOR_BOOTSTRAP_MDC_REL = join2(".cursor", "rules", "fabric-bootstrap.mdc");
|
|
820
|
-
async function writeCursorBootstrapManagedBlock(targetRoot, _options = {}) {
|
|
821
|
-
const step = "bootstrap-cursor";
|
|
822
|
-
const target = join2(targetRoot, CURSOR_BOOTSTRAP_MDC_REL);
|
|
823
|
-
const legacyFlatFile = join2(targetRoot, CURSOR_LEGACY_FLAT_FILE_REL);
|
|
824
|
-
try {
|
|
825
|
-
if (existsSync2(legacyFlatFile)) {
|
|
826
|
-
const stat = statSync(legacyFlatFile);
|
|
827
|
-
if (stat.isFile()) {
|
|
828
|
-
await rm(legacyFlatFile, { force: true });
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
} catch {
|
|
832
|
-
}
|
|
833
|
-
const body = buildManagedBlockBody(targetRoot);
|
|
834
|
-
const managedBlock = wrapInBootstrapMarkers(body);
|
|
835
|
-
const expected = `${CURSOR_RULE_FRONT_MATTER}${managedBlock}
|
|
836
|
-
`;
|
|
837
|
-
let existing = "";
|
|
838
|
-
if (existsSync2(target)) {
|
|
839
|
-
try {
|
|
840
|
-
existing = await readFile(target, "utf8");
|
|
841
|
-
} catch (error) {
|
|
842
|
-
return {
|
|
843
|
-
step,
|
|
844
|
-
path: target,
|
|
845
|
-
status: "error",
|
|
846
|
-
message: error instanceof Error ? error.message : String(error)
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
if (existing === expected) {
|
|
851
|
-
return { step, path: target, status: "skipped", message: "up-to-date" };
|
|
852
|
-
}
|
|
853
|
-
try {
|
|
854
|
-
await mkdir2(dirname2(target), { recursive: true });
|
|
855
|
-
await atomicWriteText2(target, expected);
|
|
856
|
-
return { step, path: target, status: "written" };
|
|
857
|
-
} catch (error) {
|
|
858
|
-
return {
|
|
859
|
-
step,
|
|
860
|
-
path: target,
|
|
861
|
-
status: "error",
|
|
862
|
-
message: error instanceof Error ? error.message : String(error)
|
|
863
|
-
};
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
743
|
async function copyTextIdempotent(step, source, target) {
|
|
867
744
|
if (existsSync2(target)) {
|
|
868
745
|
try {
|
|
@@ -881,6 +758,17 @@ var FABRIC_HOOK_SCRIPT_BASENAMES = /* @__PURE__ */ new Set([
|
|
|
881
758
|
"fabric-hint.cjs",
|
|
882
759
|
"knowledge-hint-broad.cjs",
|
|
883
760
|
"knowledge-hint-narrow.cjs",
|
|
761
|
+
// dual-sink W5-1: the strip set must enumerate the COMPLETE fabric-owned hook
|
|
762
|
+
// surface — same set as FABRIC_HOOK_COMMAND_PATHS. Otherwise a matcher change
|
|
763
|
+
// in the template (e.g. adding `apply_patch` to the Codex PreToolUse/PostToolUse
|
|
764
|
+
// matchers) silently fails to propagate on upgrade: stripStaleHookEntries
|
|
765
|
+
// leaves the un-listed entry in place, and the subsequent append-with-dedupe
|
|
766
|
+
// matches it by `command` and SKIPS the new-matcher fragment, preserving the
|
|
767
|
+
// stale matcher. Listing these three makes the canonical template entry the
|
|
768
|
+
// sole survivor on every re-install, so matcher edits actually sync.
|
|
769
|
+
"cite-policy-evict.cjs",
|
|
770
|
+
"post-tooluse-mutation.cjs",
|
|
771
|
+
"session-end-marker.cjs",
|
|
884
772
|
// rc.5 TASK-010 rename — old hook scripts that pre-upgrade workspaces
|
|
885
773
|
// may still have registered. Sweeping them prevents the double-fire
|
|
886
774
|
// documented in audit §2.6.
|
|
@@ -1019,7 +907,6 @@ export {
|
|
|
1019
907
|
HOOK_CONFIG_TARGETS,
|
|
1020
908
|
HOOK_CONFIG_ARRAY_PATHS,
|
|
1021
909
|
FABRIC_HOOK_COMMAND_PATHS,
|
|
1022
|
-
readFabricLanguagePreference,
|
|
1023
910
|
installFabricArchiveSkill,
|
|
1024
911
|
installFabricReviewSkill,
|
|
1025
912
|
installFabricImportSkill,
|
|
@@ -1038,8 +925,6 @@ export {
|
|
|
1038
925
|
installHookLibs,
|
|
1039
926
|
mergeClaudeCodeHookConfig,
|
|
1040
927
|
mergeCodexHookConfig,
|
|
1041
|
-
mergeCursorHookConfig,
|
|
1042
928
|
writeClaudeBootstrapThinShell,
|
|
1043
|
-
writeCodexBootstrapManagedBlock
|
|
1044
|
-
writeCursorBootstrapManagedBlock
|
|
929
|
+
writeCodexBootstrapManagedBlock
|
|
1045
930
|
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
regenerateBindingsSnapshot
|
|
4
|
+
} from "./chunk-PTGQAZEW.js";
|
|
5
|
+
import {
|
|
6
|
+
storeBind,
|
|
7
|
+
storeProjectCreate,
|
|
8
|
+
storeProjectList,
|
|
9
|
+
storeSetWriteRoute,
|
|
10
|
+
storeSwitchWrite
|
|
11
|
+
} from "./chunk-QPAW6IYT.js";
|
|
12
|
+
import {
|
|
13
|
+
loadProjectConfig,
|
|
14
|
+
saveProjectConfig
|
|
15
|
+
} from "./chunk-QFIVFZRH.js";
|
|
16
|
+
|
|
17
|
+
// src/install/migrate-root-config.ts
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
19
|
+
import { dirname, join } from "path";
|
|
20
|
+
var ROOT_AUTHORITATIVE_KEYS = /* @__PURE__ */ new Set([
|
|
21
|
+
"embed_enabled",
|
|
22
|
+
"embed_model",
|
|
23
|
+
"embed_weight",
|
|
24
|
+
"plan_context_top_k",
|
|
25
|
+
"mcpPayloadLimits",
|
|
26
|
+
"retrieval_budget_profile",
|
|
27
|
+
"recall_body_budget_bytes",
|
|
28
|
+
"selection_token_ttl_ms",
|
|
29
|
+
"orphan_demote_proven_days",
|
|
30
|
+
"orphan_demote_verified_days",
|
|
31
|
+
"orphan_demote_draft_days",
|
|
32
|
+
"clientPaths"
|
|
33
|
+
]);
|
|
34
|
+
function readJsonObject(path) {
|
|
35
|
+
if (!existsSync(path)) return null;
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
38
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return parsed;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function migrateRootConfig(projectRoot) {
|
|
47
|
+
const rootPath = join(projectRoot, "fabric.config.json");
|
|
48
|
+
const fabricPath = join(projectRoot, ".fabric", "fabric-config.json");
|
|
49
|
+
const result = {
|
|
50
|
+
migrated: false,
|
|
51
|
+
mergedKeys: [],
|
|
52
|
+
rootPath,
|
|
53
|
+
fabricPath
|
|
54
|
+
};
|
|
55
|
+
const rootConfig = readJsonObject(rootPath);
|
|
56
|
+
if (rootConfig === null) {
|
|
57
|
+
if (existsSync(rootPath)) {
|
|
58
|
+
rmSync(rootPath, { force: true });
|
|
59
|
+
result.migrated = true;
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
const fabricConfig = readJsonObject(fabricPath) ?? {};
|
|
64
|
+
const merged = { ...fabricConfig };
|
|
65
|
+
const mergedKeys = [];
|
|
66
|
+
for (const [key, value] of Object.entries(rootConfig)) {
|
|
67
|
+
const fabricHasKey = Object.prototype.hasOwnProperty.call(merged, key);
|
|
68
|
+
if (ROOT_AUTHORITATIVE_KEYS.has(key) || !fabricHasKey) {
|
|
69
|
+
if (!fabricHasKey || merged[key] !== value) {
|
|
70
|
+
mergedKeys.push(key);
|
|
71
|
+
}
|
|
72
|
+
merged[key] = value;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
mkdirSync(dirname(fabricPath), { recursive: true });
|
|
76
|
+
writeFileSync(fabricPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
77
|
+
rmSync(rootPath, { force: true });
|
|
78
|
+
result.migrated = true;
|
|
79
|
+
result.mergedKeys = mergedKeys;
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/install/store-project-onboarding.ts
|
|
84
|
+
import { execFileSync } from "child_process";
|
|
85
|
+
import { randomUUID } from "crypto";
|
|
86
|
+
import { basename } from "path";
|
|
87
|
+
function ensureProjectId(projectRoot, uuid = randomUUID()) {
|
|
88
|
+
const config = loadProjectConfig(projectRoot) ?? {};
|
|
89
|
+
if (typeof config.project_id === "string" && config.project_id.length > 0) {
|
|
90
|
+
return config.project_id;
|
|
91
|
+
}
|
|
92
|
+
saveProjectConfig({ ...config, project_id: uuid }, projectRoot);
|
|
93
|
+
return uuid;
|
|
94
|
+
}
|
|
95
|
+
function suggestStoreProjectId(projectRoot) {
|
|
96
|
+
return normalizeStoreProjectId(readGitRemoteName(projectRoot) ?? basename(projectRoot));
|
|
97
|
+
}
|
|
98
|
+
function normalizeStoreProjectId(value) {
|
|
99
|
+
const normalized = value.trim().replace(/\.git$/iu, "").toLowerCase().replace(/[^a-z0-9_-]+/gu, "-").replace(/-+/gu, "-").replace(/^[-_]+|[-_]+$/gu, "");
|
|
100
|
+
return normalized.length > 0 ? normalized : "project";
|
|
101
|
+
}
|
|
102
|
+
async function ensureStoreProjectBinding(projectRoot, storeAlias, options) {
|
|
103
|
+
const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
104
|
+
const project_id = ensureProjectId(projectRoot, options.uuid);
|
|
105
|
+
const currentConfig = loadProjectConfig(projectRoot);
|
|
106
|
+
const requested = options.requestedProjectId ?? currentConfig?.active_project ?? suggestStoreProjectId(projectRoot);
|
|
107
|
+
const active_project = normalizeStoreProjectId(requested);
|
|
108
|
+
const projects = await storeProjectList(storeAlias, options.globalRoot);
|
|
109
|
+
const project_created = !projects.some((project) => project.id === active_project);
|
|
110
|
+
if (project_created) {
|
|
111
|
+
await storeProjectCreate(storeAlias, active_project, now, {
|
|
112
|
+
name: active_project,
|
|
113
|
+
globalRoot: options.globalRoot
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const entry = options.suggestedRemote === void 0 ? { id: storeAlias } : { id: storeAlias, suggested_remote: options.suggestedRemote };
|
|
117
|
+
await storeBind(projectRoot, entry, { project: active_project, globalRoot: options.globalRoot });
|
|
118
|
+
storeSwitchWrite(projectRoot, storeAlias, { globalRoot: options.globalRoot });
|
|
119
|
+
storeSetWriteRoute(projectRoot, `project:${active_project}`, storeAlias, {
|
|
120
|
+
globalRoot: options.globalRoot
|
|
121
|
+
});
|
|
122
|
+
regenerateBindingsSnapshot(projectRoot, {
|
|
123
|
+
now,
|
|
124
|
+
globalRoot: options.globalRoot
|
|
125
|
+
});
|
|
126
|
+
return { project_id, active_project, project_created };
|
|
127
|
+
}
|
|
128
|
+
function readGitRemoteName(projectRoot) {
|
|
129
|
+
try {
|
|
130
|
+
const remote = execFileSync("git", ["config", "--get", "remote.origin.url"], {
|
|
131
|
+
cwd: projectRoot,
|
|
132
|
+
encoding: "utf8",
|
|
133
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
134
|
+
}).trim();
|
|
135
|
+
if (remote.length === 0) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const lastSegment = remote.split(/[/:\\]/u).filter(Boolean).at(-1);
|
|
139
|
+
return lastSegment === void 0 ? null : lastSegment.replace(/\.git$/iu, "");
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
migrateRootConfig,
|
|
147
|
+
suggestStoreProjectId,
|
|
148
|
+
normalizeStoreProjectId,
|
|
149
|
+
ensureStoreProjectBinding
|
|
150
|
+
};
|
|
@@ -8,18 +8,9 @@ import {
|
|
|
8
8
|
saveGlobalConfig
|
|
9
9
|
} from "@fenglimg/fabric-shared";
|
|
10
10
|
|
|
11
|
-
// src/store/project-config-io.ts
|
|
12
|
-
import {
|
|
13
|
-
projectConfigPath,
|
|
14
|
-
loadProjectConfig,
|
|
15
|
-
saveProjectConfig
|
|
16
|
-
} from "@fenglimg/fabric-shared";
|
|
17
|
-
|
|
18
11
|
export {
|
|
19
12
|
resolveGlobalRoot,
|
|
20
13
|
globalConfigPath,
|
|
21
14
|
loadGlobalConfig,
|
|
22
|
-
saveGlobalConfig
|
|
23
|
-
loadProjectConfig,
|
|
24
|
-
saveProjectConfig
|
|
15
|
+
saveGlobalConfig
|
|
25
16
|
};
|