@fenglimg/fabric-cli 2.0.0-rc.1 → 2.0.0-rc.10
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 +6 -6
- package/dist/chunk-6ICJICVU.js +10 -0
- package/dist/chunk-AW3G7ZH5.js +576 -0
- package/dist/chunk-HQLEHH4O.js +321 -0
- package/dist/{chunk-UHNP7T7W.js → chunk-MT3R57VG.js} +346 -86
- package/dist/{chunk-5LOYBXWD.js → chunk-OBQU6NHO.js} +2 -52
- package/dist/chunk-WPTA74BY.js +184 -0
- package/dist/chunk-WWNXR34K.js +49 -0
- package/dist/doctor-RILCO5OG.js +282 -0
- package/dist/hooks-NX32PPEN.js +13 -0
- package/dist/index.js +8 -5
- package/dist/{init-DRHUYHYA.js → init-SAVH4SKE.js} +188 -491
- package/dist/plan-context-hint-QMUPAXIB.js +98 -0
- package/dist/{scan-HU2EGITF.js → scan-ELSNCSKS.js} +4 -2
- package/dist/{serve-3LXXSBFR.js → serve-NGLXHDYC.js} +8 -4
- package/dist/uninstall-DBAR2JBS.js +1082 -0
- package/package.json +3 -3
- package/templates/bootstrap/CLAUDE.md +1 -1
- package/templates/bootstrap/codex-AGENTS-header.md +1 -1
- package/templates/bootstrap/cursor-fabric-bootstrap.mdc +1 -1
- package/templates/hooks/configs/README.md +73 -0
- package/templates/hooks/configs/claude-code.json +37 -0
- package/templates/hooks/configs/codex-hooks.json +20 -0
- package/templates/hooks/configs/cursor-hooks.json +20 -0
- package/templates/hooks/fabric-hint.cjs +1337 -0
- package/templates/hooks/knowledge-hint-broad.cjs +612 -0
- package/templates/hooks/knowledge-hint-narrow.cjs +826 -0
- package/templates/hooks/lib/session-digest-writer.cjs +172 -0
- package/templates/skills/fabric-archive/SKILL.md +486 -0
- package/templates/skills/fabric-import/SKILL.md +560 -0
- package/templates/skills/fabric-review/SKILL.md +382 -0
- package/dist/doctor-DUHWLAYD.js +0 -98
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
createDebugLogger,
|
|
4
3
|
paint,
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
symbol
|
|
5
|
+
} from "./chunk-WWNXR34K.js";
|
|
6
|
+
import {
|
|
7
|
+
createDebugLogger,
|
|
8
|
+
readFabricConfig,
|
|
9
|
+
resolveDevMode
|
|
10
|
+
} from "./chunk-OBQU6NHO.js";
|
|
11
|
+
import {
|
|
7
12
|
t
|
|
8
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-6ICJICVU.js";
|
|
9
14
|
|
|
10
15
|
// src/commands/scan.ts
|
|
11
16
|
import { createHash } from "crypto";
|
|
@@ -16,7 +21,7 @@ import { defineCommand } from "citty";
|
|
|
16
21
|
import {
|
|
17
22
|
KnowledgeIdAllocator,
|
|
18
23
|
appendEventLedgerEvent,
|
|
19
|
-
|
|
24
|
+
writeKnowledgeMeta
|
|
20
25
|
} from "@fenglimg/fabric-server";
|
|
21
26
|
import {
|
|
22
27
|
formatKnowledgeId
|
|
@@ -82,13 +87,16 @@ async function runInitScan(targetInput, options = {}) {
|
|
|
82
87
|
const forensic = await readForensic(forensicPath);
|
|
83
88
|
const nowIso = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
84
89
|
const tags = deriveTagsFromForensic(forensic);
|
|
90
|
+
const fabricConfig = readFabricConfig(target);
|
|
91
|
+
const knowledgeLanguage = fabricConfig.knowledge_language ?? "match-existing";
|
|
92
|
+
const resolvedLanguage = resolveKnowledgeLanguage(knowledgeLanguage, target);
|
|
85
93
|
const candidates = [
|
|
86
|
-
buildTechStackEntry(forensic, nowIso, tags),
|
|
87
|
-
buildModuleStructureEntry(forensic, nowIso, tags),
|
|
88
|
-
buildBuildConfigEntry(forensic, nowIso, tags),
|
|
89
|
-
buildCodeStyleEntry(forensic, nowIso, tags),
|
|
94
|
+
buildTechStackEntry(forensic, nowIso, tags, resolvedLanguage),
|
|
95
|
+
buildModuleStructureEntry(forensic, nowIso, tags, resolvedLanguage),
|
|
96
|
+
buildBuildConfigEntry(forensic, nowIso, tags, resolvedLanguage),
|
|
97
|
+
buildCodeStyleEntry(forensic, nowIso, tags, resolvedLanguage),
|
|
90
98
|
buildCIConfigEntry(forensic, nowIso, tags),
|
|
91
|
-
buildReadmeFirstParaEntry(target, forensic, nowIso, tags),
|
|
99
|
+
buildReadmeFirstParaEntry(target, forensic, nowIso, tags, resolvedLanguage),
|
|
92
100
|
buildProjectBriefEntry(target, forensic, nowIso, tags)
|
|
93
101
|
];
|
|
94
102
|
const entries = candidates.filter((e) => e !== null);
|
|
@@ -119,7 +127,7 @@ async function runInitScan(targetInput, options = {}) {
|
|
|
119
127
|
await ensureParentDirectory(sidecarPath);
|
|
120
128
|
await atomicWriteJson(sidecarPath, sidecar);
|
|
121
129
|
await registerKnowledgeNodesInMeta(target, placedEntries);
|
|
122
|
-
await
|
|
130
|
+
await writeKnowledgeMeta(target, { source: "doctor_fix" });
|
|
123
131
|
const durationMs = Date.now() - startTs;
|
|
124
132
|
await appendEventLedgerEvent(target, {
|
|
125
133
|
event_type: "init_scan_completed",
|
|
@@ -178,124 +186,338 @@ var scanCommand = defineCommand({
|
|
|
178
186
|
}
|
|
179
187
|
});
|
|
180
188
|
var scan_default = scanCommand;
|
|
181
|
-
|
|
189
|
+
var BASELINE_TEMPLATES = {
|
|
190
|
+
en: {
|
|
191
|
+
"tech-stack": {
|
|
192
|
+
title: ({ frameworkSummary }) => `Tech stack: ${frameworkSummary}`,
|
|
193
|
+
build: ({ projectName, frameworkSummary, topExtensionsLine, evidenceLines }) => ({
|
|
194
|
+
mission: `Track the primary tech stack and runtime surface used by ${projectName}.`,
|
|
195
|
+
context: [
|
|
196
|
+
`Framework: ${frameworkSummary}`,
|
|
197
|
+
`Top file extensions: ${topExtensionsLine}`,
|
|
198
|
+
`Evidence:`,
|
|
199
|
+
...evidenceLines.map((line) => `- ${line}`)
|
|
200
|
+
].join("\n")
|
|
201
|
+
})
|
|
202
|
+
},
|
|
203
|
+
"module-structure": {
|
|
204
|
+
title: "Module structure",
|
|
205
|
+
build: ({ projectName, totalFiles, maxDepth, dirsBlock, entriesBlock }) => ({
|
|
206
|
+
mission: `Map the high-level module layout and primary entry points of ${projectName}.`,
|
|
207
|
+
context: [
|
|
208
|
+
`Total files: ${totalFiles}`,
|
|
209
|
+
`Max directory depth: ${maxDepth}`,
|
|
210
|
+
"",
|
|
211
|
+
"Key directories:",
|
|
212
|
+
dirsBlock,
|
|
213
|
+
"",
|
|
214
|
+
"Entry points:",
|
|
215
|
+
entriesBlock
|
|
216
|
+
].join("\n")
|
|
217
|
+
})
|
|
218
|
+
},
|
|
219
|
+
"build-config": {
|
|
220
|
+
title: "Build configuration",
|
|
221
|
+
build: ({ projectName, framework, configBlock }) => ({
|
|
222
|
+
mission: `Document the deterministic build/bootstrap configuration anchoring ${projectName}.`,
|
|
223
|
+
businessLogic: [
|
|
224
|
+
`1. Detect framework: \`${framework}\`.`,
|
|
225
|
+
`2. Read configuration files in declared order.`,
|
|
226
|
+
`3. Honor compiler/bundler boundaries before generating new code.`,
|
|
227
|
+
`4. Treat config drift as a fact-check signal \u2014 re-run \`fab scan\` after edits.`
|
|
228
|
+
].join("\n"),
|
|
229
|
+
context: [
|
|
230
|
+
`Framework: ${framework}`,
|
|
231
|
+
"",
|
|
232
|
+
"Configuration files:",
|
|
233
|
+
configBlock
|
|
234
|
+
].join("\n")
|
|
235
|
+
})
|
|
236
|
+
},
|
|
237
|
+
"code-style": {
|
|
238
|
+
title: "Code style guidelines",
|
|
239
|
+
build: ({ projectName, rulesBlock, patternsBlock }) => ({
|
|
240
|
+
mission: `Codify the recurring authoring conventions observed in ${projectName}.`,
|
|
241
|
+
mandatoryInjection: [
|
|
242
|
+
"When generating or modifying source files in this repo, AI agents MUST:",
|
|
243
|
+
rulesBlock
|
|
244
|
+
].join("\n"),
|
|
245
|
+
context: [
|
|
246
|
+
"Detected patterns:",
|
|
247
|
+
patternsBlock
|
|
248
|
+
].join("\n")
|
|
249
|
+
})
|
|
250
|
+
},
|
|
251
|
+
"readme-first-paragraph": {
|
|
252
|
+
title: "README first paragraph",
|
|
253
|
+
build: ({ lineCount, quality, excerpt }) => ({
|
|
254
|
+
mission: `Preserve the README headline and first paragraph as the canonical project elevator pitch.`,
|
|
255
|
+
context: [
|
|
256
|
+
`Source: README.md (${lineCount} lines, quality=${quality})`,
|
|
257
|
+
"",
|
|
258
|
+
"Excerpt:",
|
|
259
|
+
"> " + excerpt.split("\n").join("\n> ")
|
|
260
|
+
].join("\n")
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
"zh-CN": {
|
|
265
|
+
"tech-stack": {
|
|
266
|
+
title: ({ frameworkSummary }) => `Tech stack: ${frameworkSummary}`,
|
|
267
|
+
build: ({ projectName, frameworkSummary, topExtensionsLine, evidenceLines }) => ({
|
|
268
|
+
mission: `\u8BB0\u5F55 ${projectName} \u6240\u4F7F\u7528\u7684\u4E3B\u8981 tech stack \u4E0E\u8FD0\u884C\u65F6\u9762\u3002`,
|
|
269
|
+
context: [
|
|
270
|
+
`Framework\uFF1A${frameworkSummary}`,
|
|
271
|
+
`\u4E3B\u8981\u6587\u4EF6\u540E\u7F00\uFF1A${topExtensionsLine}`,
|
|
272
|
+
`\u8BC1\u636E\uFF1A`,
|
|
273
|
+
...evidenceLines.map((line) => `- ${line}`)
|
|
274
|
+
].join("\n")
|
|
275
|
+
})
|
|
276
|
+
},
|
|
277
|
+
"module-structure": {
|
|
278
|
+
title: "Module structure",
|
|
279
|
+
build: ({ projectName, totalFiles, maxDepth, dirsBlock, entriesBlock }) => ({
|
|
280
|
+
mission: `\u68B3\u7406 ${projectName} \u7684\u9AD8\u5C42 module \u5E03\u5C40\u4E0E\u4E3B\u8981 entry point\u3002`,
|
|
281
|
+
context: [
|
|
282
|
+
`\u6587\u4EF6\u603B\u6570\uFF1A${totalFiles}`,
|
|
283
|
+
`\u6700\u5927\u76EE\u5F55\u6DF1\u5EA6\uFF1A${maxDepth}`,
|
|
284
|
+
"",
|
|
285
|
+
"\u5173\u952E\u76EE\u5F55\uFF1A",
|
|
286
|
+
dirsBlock,
|
|
287
|
+
"",
|
|
288
|
+
"Entry points\uFF1A",
|
|
289
|
+
entriesBlock
|
|
290
|
+
].join("\n")
|
|
291
|
+
})
|
|
292
|
+
},
|
|
293
|
+
"build-config": {
|
|
294
|
+
title: "Build configuration",
|
|
295
|
+
build: ({ projectName, framework, configBlock }) => ({
|
|
296
|
+
mission: `\u8BB0\u5F55 ${projectName} \u6240\u4F9D\u8D56\u7684\u3001\u786E\u5B9A\u6027\u7684 build / bootstrap \u914D\u7F6E\u3002`,
|
|
297
|
+
businessLogic: [
|
|
298
|
+
`1. \u63A2\u6D4B framework\uFF1A\`${framework}\`\u3002`,
|
|
299
|
+
`2. \u6309\u58F0\u660E\u987A\u5E8F\u8BFB\u53D6 configuration files\u3002`,
|
|
300
|
+
`3. \u5728\u751F\u6210\u65B0\u4EE3\u7801\u4E4B\u524D\uFF0C\u5C0A\u91CD compiler / bundler \u7684\u8FB9\u754C\u3002`,
|
|
301
|
+
`4. \u628A config \u6F02\u79FB\u89C6\u4E3A fact-check \u4FE1\u53F7 \u2014\u2014 \u4FEE\u6539\u540E\u91CD\u65B0\u8FD0\u884C \`fab scan\`\u3002`
|
|
302
|
+
].join("\n"),
|
|
303
|
+
context: [
|
|
304
|
+
`Framework\uFF1A${framework}`,
|
|
305
|
+
"",
|
|
306
|
+
"Configuration files\uFF1A",
|
|
307
|
+
configBlock
|
|
308
|
+
].join("\n")
|
|
309
|
+
})
|
|
310
|
+
},
|
|
311
|
+
"code-style": {
|
|
312
|
+
title: "Code style guidelines",
|
|
313
|
+
build: ({ projectName, rulesBlock, patternsBlock }) => ({
|
|
314
|
+
mission: `\u56FA\u5316 ${projectName} \u4E2D\u53CD\u590D\u51FA\u73B0\u7684\u5199\u7801\u7EA6\u5B9A\u3002`,
|
|
315
|
+
mandatoryInjection: [
|
|
316
|
+
"\u5728\u672C\u4ED3\u5E93\u5185\u751F\u6210\u6216\u4FEE\u6539\u6E90\u7801\u6587\u4EF6\u65F6\uFF0CAI agent \u5FC5\u987B\uFF1A",
|
|
317
|
+
rulesBlock
|
|
318
|
+
].join("\n"),
|
|
319
|
+
context: [
|
|
320
|
+
"\u89C2\u5BDF\u5230\u7684\u6A21\u5F0F\uFF1A",
|
|
321
|
+
patternsBlock
|
|
322
|
+
].join("\n")
|
|
323
|
+
})
|
|
324
|
+
},
|
|
325
|
+
"readme-first-paragraph": {
|
|
326
|
+
title: "README first paragraph",
|
|
327
|
+
build: ({ lineCount, quality, excerpt }) => ({
|
|
328
|
+
mission: `\u628A README \u7684\u6807\u9898\u4E0E\u9996\u6BB5\u4FDD\u7559\u4E3A\u9879\u76EE\u5BF9\u5916\u7684 canonical elevator pitch\u3002`,
|
|
329
|
+
context: [
|
|
330
|
+
`\u6765\u6E90\uFF1AREADME.md\uFF08${lineCount} \u884C\uFF0Cquality=${quality}\uFF09`,
|
|
331
|
+
"",
|
|
332
|
+
"\u6458\u5F55\uFF1A",
|
|
333
|
+
"> " + excerpt.split("\n").join("\n> ")
|
|
334
|
+
].join("\n")
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
function resolveTemplateTitle(template, inputs) {
|
|
340
|
+
return typeof template.title === "function" ? template.title(inputs) : template.title;
|
|
341
|
+
}
|
|
342
|
+
function resolveKnowledgeLanguage(configured, target) {
|
|
343
|
+
if (configured === "en" || configured === "zh-CN") {
|
|
344
|
+
return configured;
|
|
345
|
+
}
|
|
346
|
+
return detectExistingLanguage(target);
|
|
347
|
+
}
|
|
348
|
+
function detectExistingLanguage(target) {
|
|
349
|
+
const ZH_CN_RATIO_THRESHOLD = 0.3;
|
|
350
|
+
const samples = [];
|
|
351
|
+
const readmePath = join(target, "README.md");
|
|
352
|
+
if (existsSync(readmePath)) {
|
|
353
|
+
try {
|
|
354
|
+
samples.push(readFileSync(readmePath, "utf8"));
|
|
355
|
+
} catch {
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const docsDir = join(target, "docs");
|
|
359
|
+
if (existsSync(docsDir)) {
|
|
360
|
+
try {
|
|
361
|
+
const stat = statSync(docsDir);
|
|
362
|
+
if (stat.isDirectory()) {
|
|
363
|
+
for (const entry of readdirSync(docsDir, { withFileTypes: true })) {
|
|
364
|
+
if (!entry.isFile()) continue;
|
|
365
|
+
if (!/\.(md|mdx|txt)$/iu.test(entry.name)) continue;
|
|
366
|
+
try {
|
|
367
|
+
samples.push(readFileSync(join(docsDir, entry.name), "utf8"));
|
|
368
|
+
} catch {
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (samples.length === 0) {
|
|
376
|
+
return "en";
|
|
377
|
+
}
|
|
378
|
+
let cjkCount = 0;
|
|
379
|
+
let asciiLetterCount = 0;
|
|
380
|
+
for (const sample of samples) {
|
|
381
|
+
for (const ch of sample) {
|
|
382
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
383
|
+
if (code >= 19968 && code <= 40959) {
|
|
384
|
+
cjkCount += 1;
|
|
385
|
+
} else if (code >= 65 && code <= 90 || code >= 97 && code <= 122) {
|
|
386
|
+
asciiLetterCount += 1;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const denominator = cjkCount + asciiLetterCount;
|
|
391
|
+
if (denominator === 0) {
|
|
392
|
+
return "en";
|
|
393
|
+
}
|
|
394
|
+
const ratio = cjkCount / denominator;
|
|
395
|
+
return ratio > ZH_CN_RATIO_THRESHOLD ? "zh-CN" : "en";
|
|
396
|
+
}
|
|
397
|
+
function buildTechStackEntry(forensic, nowIso, tags, language = "en") {
|
|
182
398
|
const framework = forensic.framework;
|
|
183
399
|
const byExt = forensic.topology.by_ext ?? {};
|
|
184
400
|
const topExtensions = Object.entries(byExt).sort(([, a], [, b]) => b - a).slice(0, 5).map(([ext, count]) => `${ext} (${count})`);
|
|
401
|
+
const frameworkSummary = `${framework.kind}${framework.version ? ` ${framework.version}` : ""}${framework.subkind ? ` / ${framework.subkind}` : ""}`;
|
|
402
|
+
const topExtensionsLine = topExtensions.length > 0 ? topExtensions.join(", ") : "(none)";
|
|
185
403
|
const evidenceLines = framework.evidence.length > 0 ? framework.evidence.slice(0, 6) : ["(no explicit framework evidence)"];
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
404
|
+
const inputs = {
|
|
405
|
+
projectName: forensic.project_name,
|
|
406
|
+
frameworkSummary,
|
|
407
|
+
topExtensionsLine,
|
|
408
|
+
evidenceLines
|
|
409
|
+
};
|
|
410
|
+
const template = BASELINE_TEMPLATES[language]["tech-stack"];
|
|
411
|
+
const sections = template.build(inputs);
|
|
412
|
+
const body = renderSections(sections);
|
|
413
|
+
const relevancePaths = ["package.json", "pnpm-workspace.yaml"];
|
|
195
414
|
return {
|
|
196
415
|
type: "model",
|
|
197
416
|
layer: "team",
|
|
198
417
|
maturity: "verified",
|
|
199
418
|
layer_reason: LAYER_REASON,
|
|
200
419
|
created_at: nowIso,
|
|
201
|
-
title:
|
|
420
|
+
title: resolveTemplateTitle(template, inputs),
|
|
202
421
|
body,
|
|
203
422
|
target_subdir: "models",
|
|
204
423
|
slug: "tech-stack",
|
|
205
|
-
tags
|
|
424
|
+
tags,
|
|
425
|
+
relevance_scope: "narrow",
|
|
426
|
+
relevance_paths: relevancePaths
|
|
206
427
|
};
|
|
207
428
|
}
|
|
208
|
-
function buildModuleStructureEntry(forensic, nowIso, tags) {
|
|
429
|
+
function buildModuleStructureEntry(forensic, nowIso, tags, language = "en") {
|
|
209
430
|
const keyDirs = forensic.topology.key_dirs ?? [];
|
|
210
431
|
const entryPoints = forensic.entry_points ?? [];
|
|
211
432
|
const totalFiles = forensic.topology.total_files ?? 0;
|
|
212
433
|
const dirsBlock = keyDirs.length > 0 ? keyDirs.slice(0, 12).map((dir) => `- ${dir}`).join("\n") : "- (no key directories detected)";
|
|
213
434
|
const entriesBlock = entryPoints.length > 0 ? entryPoints.slice(0, 8).map((ep) => `- ${ep.path} \u2014 ${ep.reason}`).join("\n") : "- (no entry points detected)";
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
entriesBlock
|
|
225
|
-
].join("\n")
|
|
226
|
-
});
|
|
435
|
+
const inputs = {
|
|
436
|
+
projectName: forensic.project_name,
|
|
437
|
+
totalFiles,
|
|
438
|
+
maxDepth: forensic.topology.max_depth ?? 0,
|
|
439
|
+
dirsBlock,
|
|
440
|
+
entriesBlock
|
|
441
|
+
};
|
|
442
|
+
const template = BASELINE_TEMPLATES[language]["module-structure"];
|
|
443
|
+
const body = renderSections(template.build(inputs));
|
|
444
|
+
const relevancePaths = ["packages/**/package.json"];
|
|
227
445
|
return {
|
|
228
446
|
type: "model",
|
|
229
447
|
layer: "team",
|
|
230
448
|
maturity: "verified",
|
|
231
449
|
layer_reason: LAYER_REASON,
|
|
232
450
|
created_at: nowIso,
|
|
233
|
-
title:
|
|
451
|
+
title: resolveTemplateTitle(template, inputs),
|
|
234
452
|
body,
|
|
235
453
|
target_subdir: "models",
|
|
236
454
|
slug: "module-structure",
|
|
237
|
-
tags
|
|
455
|
+
tags,
|
|
456
|
+
relevance_scope: "narrow",
|
|
457
|
+
relevance_paths: relevancePaths
|
|
238
458
|
};
|
|
239
459
|
}
|
|
240
|
-
function buildBuildConfigEntry(forensic, nowIso, tags) {
|
|
460
|
+
function buildBuildConfigEntry(forensic, nowIso, tags, language = "en") {
|
|
241
461
|
const configFiles = (forensic.candidate_files ?? []).filter((entry) => entry.family === "config").map((entry) => entry.path);
|
|
242
462
|
const framework = forensic.framework.kind;
|
|
243
463
|
const configBlock = configFiles.length > 0 ? configFiles.map((file) => `- ${file}`).join("\n") : "- (no config files detected)";
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
`Framework: ${framework}`,
|
|
254
|
-
"",
|
|
255
|
-
"Configuration files:",
|
|
256
|
-
configBlock
|
|
257
|
-
].join("\n")
|
|
258
|
-
});
|
|
464
|
+
const inputs = {
|
|
465
|
+
projectName: forensic.project_name,
|
|
466
|
+
framework,
|
|
467
|
+
configBlock
|
|
468
|
+
};
|
|
469
|
+
const template = BASELINE_TEMPLATES[language]["build-config"];
|
|
470
|
+
const body = renderSections(template.build(inputs));
|
|
471
|
+
const discovered = configFiles.filter((path) => isBuildConfigPath(path));
|
|
472
|
+
const relevancePaths = discovered.length > 0 ? Array.from(new Set(discovered)) : ["tsconfig.json", "tsconfig.*.json", "vite.config.*", "rollup.config.*", "webpack.config.*"];
|
|
259
473
|
return {
|
|
260
474
|
type: "process",
|
|
261
475
|
layer: "team",
|
|
262
476
|
maturity: "verified",
|
|
263
477
|
layer_reason: LAYER_REASON,
|
|
264
478
|
created_at: nowIso,
|
|
265
|
-
title:
|
|
479
|
+
title: resolveTemplateTitle(template, inputs),
|
|
266
480
|
body,
|
|
267
481
|
target_subdir: "processes",
|
|
268
482
|
slug: "build-config",
|
|
269
|
-
tags
|
|
483
|
+
tags,
|
|
484
|
+
relevance_scope: "narrow",
|
|
485
|
+
relevance_paths: relevancePaths
|
|
270
486
|
};
|
|
271
487
|
}
|
|
272
|
-
function buildCodeStyleEntry(forensic, nowIso, tags) {
|
|
488
|
+
function buildCodeStyleEntry(forensic, nowIso, tags, language = "en") {
|
|
273
489
|
const dominantPatterns = (forensic.assertions ?? []).filter((a) => a.type === "pattern" || a.type === "domain").slice(0, 4).map((a) => `- ${a.statement}`);
|
|
274
490
|
const proposedRules = (forensic.assertions ?? []).map((a) => a.proposed_rule).filter((rule) => typeof rule === "string" && rule.length > 0).slice(0, 4);
|
|
275
491
|
const patternsBlock = dominantPatterns.length > 0 ? dominantPatterns.join("\n") : "- (no dominant patterns detected)";
|
|
276
492
|
const rulesBlock = proposedRules.length > 0 ? proposedRules.map((rule) => `- ${rule}`).join("\n") : "- Follow existing module/file patterns; do not introduce new conventions without team agreement.";
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
493
|
+
const inputs = {
|
|
494
|
+
projectName: forensic.project_name,
|
|
495
|
+
rulesBlock,
|
|
496
|
+
patternsBlock
|
|
497
|
+
};
|
|
498
|
+
const template = BASELINE_TEMPLATES[language]["code-style"];
|
|
499
|
+
const body = renderSections(template.build(inputs));
|
|
500
|
+
const relevancePaths = [
|
|
501
|
+
".prettierrc",
|
|
502
|
+
".prettierrc.*",
|
|
503
|
+
".editorconfig",
|
|
504
|
+
"eslint.config.*",
|
|
505
|
+
".eslintrc",
|
|
506
|
+
".eslintrc.*"
|
|
507
|
+
];
|
|
288
508
|
return {
|
|
289
509
|
type: "guideline",
|
|
290
510
|
layer: "team",
|
|
291
511
|
maturity: "verified",
|
|
292
512
|
layer_reason: LAYER_REASON,
|
|
293
513
|
created_at: nowIso,
|
|
294
|
-
title:
|
|
514
|
+
title: resolveTemplateTitle(template, inputs),
|
|
295
515
|
body,
|
|
296
516
|
target_subdir: "guidelines",
|
|
297
517
|
slug: "code-style",
|
|
298
|
-
tags
|
|
518
|
+
tags,
|
|
519
|
+
relevance_scope: "narrow",
|
|
520
|
+
relevance_paths: relevancePaths
|
|
299
521
|
};
|
|
300
522
|
}
|
|
301
523
|
function buildCIConfigEntry(forensic, nowIso, tags) {
|
|
@@ -319,6 +541,12 @@ function buildCIConfigEntry(forensic, nowIso, tags) {
|
|
|
319
541
|
filesBlock
|
|
320
542
|
].join("\n")
|
|
321
543
|
});
|
|
544
|
+
const relevancePaths = [
|
|
545
|
+
".github/workflows/**",
|
|
546
|
+
".gitlab-ci.yml",
|
|
547
|
+
".circleci/**",
|
|
548
|
+
"Jenkinsfile"
|
|
549
|
+
];
|
|
322
550
|
return {
|
|
323
551
|
type: "process",
|
|
324
552
|
layer: "team",
|
|
@@ -329,10 +557,12 @@ function buildCIConfigEntry(forensic, nowIso, tags) {
|
|
|
329
557
|
body,
|
|
330
558
|
target_subdir: "processes",
|
|
331
559
|
slug: "ci-config",
|
|
332
|
-
tags
|
|
560
|
+
tags,
|
|
561
|
+
relevance_scope: "narrow",
|
|
562
|
+
relevance_paths: relevancePaths
|
|
333
563
|
};
|
|
334
564
|
}
|
|
335
|
-
function buildReadmeFirstParaEntry(target, forensic, nowIso, tags) {
|
|
565
|
+
function buildReadmeFirstParaEntry(target, forensic, nowIso, tags, language = "en") {
|
|
336
566
|
if (forensic.readme.quality === "missing") {
|
|
337
567
|
return null;
|
|
338
568
|
}
|
|
@@ -345,26 +575,29 @@ function buildReadmeFirstParaEntry(target, forensic, nowIso, tags) {
|
|
|
345
575
|
if (firstPara === null) {
|
|
346
576
|
return null;
|
|
347
577
|
}
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
].join("\n")
|
|
356
|
-
});
|
|
578
|
+
const inputs = {
|
|
579
|
+
lineCount: forensic.readme.line_count,
|
|
580
|
+
quality: forensic.readme.quality,
|
|
581
|
+
excerpt: firstPara
|
|
582
|
+
};
|
|
583
|
+
const template = BASELINE_TEMPLATES[language]["readme-first-paragraph"];
|
|
584
|
+
const body = renderSections(template.build(inputs));
|
|
357
585
|
return {
|
|
358
586
|
type: "model",
|
|
359
587
|
layer: "team",
|
|
360
588
|
maturity: "verified",
|
|
361
589
|
layer_reason: LAYER_REASON,
|
|
362
590
|
created_at: nowIso,
|
|
363
|
-
title:
|
|
591
|
+
title: resolveTemplateTitle(template, inputs),
|
|
364
592
|
body,
|
|
365
593
|
target_subdir: "models",
|
|
366
594
|
slug: "readme-first-paragraph",
|
|
367
|
-
tags
|
|
595
|
+
tags,
|
|
596
|
+
// v2.0-rc.7 T2: broad by design — single repo-root file, the Phase 1.5
|
|
597
|
+
// PreToolUse blacklist already covers README. Anchoring this entry to
|
|
598
|
+
// README.md would surface it on every README edit, which is noise.
|
|
599
|
+
relevance_scope: "broad",
|
|
600
|
+
relevance_paths: []
|
|
368
601
|
};
|
|
369
602
|
}
|
|
370
603
|
function buildProjectBriefEntry(target, forensic, nowIso, tags) {
|
|
@@ -399,7 +632,13 @@ function buildProjectBriefEntry(target, forensic, nowIso, tags) {
|
|
|
399
632
|
body,
|
|
400
633
|
target_subdir: "models",
|
|
401
634
|
slug: "project-brief",
|
|
402
|
-
tags
|
|
635
|
+
tags,
|
|
636
|
+
// v2.0-rc.7 T2: broad — project brief is a cross-cutting description
|
|
637
|
+
// with no path anchor. Narrowing it to README.md would duplicate the
|
|
638
|
+
// readme-first-paragraph surface; keeping it broad lets the
|
|
639
|
+
// SessionStart broad hint do the right thing.
|
|
640
|
+
relevance_scope: "broad",
|
|
641
|
+
relevance_paths: []
|
|
403
642
|
};
|
|
404
643
|
}
|
|
405
644
|
function renderMarkdown(entry) {
|
|
@@ -413,6 +652,7 @@ ${entry.body}
|
|
|
413
652
|
}
|
|
414
653
|
function renderFrontmatter(entry) {
|
|
415
654
|
const tagsLine = entry.tags.length > 0 ? `tags: [${entry.tags.join(", ")}]` : "tags: []";
|
|
655
|
+
const relevancePathsLine = entry.relevance_paths.length > 0 ? `relevance_paths: [${entry.relevance_paths.map((p) => quoteIfNeeded(p)).join(", ")}]` : "relevance_paths: []";
|
|
416
656
|
const lines = [
|
|
417
657
|
"---",
|
|
418
658
|
`id: ${entry.id}`,
|
|
@@ -422,6 +662,8 @@ function renderFrontmatter(entry) {
|
|
|
422
662
|
`layer_reason: ${quoteIfNeeded(entry.layer_reason)}`,
|
|
423
663
|
`created_at: ${entry.created_at}`,
|
|
424
664
|
tagsLine,
|
|
665
|
+
`relevance_scope: ${entry.relevance_scope}`,
|
|
666
|
+
relevancePathsLine,
|
|
425
667
|
"---"
|
|
426
668
|
];
|
|
427
669
|
return lines.join("\n");
|
|
@@ -545,6 +787,19 @@ function findExistingIdForFile(sidecar, targetPath, target) {
|
|
|
545
787
|
function isCIConfigPath(path) {
|
|
546
788
|
return path.startsWith(".github/workflows/") || path.startsWith(".gitlab-ci") || path === "azure-pipelines.yml" || path === ".circleci/config.yml" || path === "Jenkinsfile" || path === ".travis.yml";
|
|
547
789
|
}
|
|
790
|
+
function isBuildConfigPath(path) {
|
|
791
|
+
const lower = path.toLowerCase();
|
|
792
|
+
const basename = lower.split("/").pop() ?? lower;
|
|
793
|
+
if (basename.startsWith("tsconfig") && basename.endsWith(".json")) return true;
|
|
794
|
+
if (basename === "package.json") return true;
|
|
795
|
+
if (basename === "pnpm-workspace.yaml" || basename === "pnpm-workspace.yml") return true;
|
|
796
|
+
if (basename.startsWith("vite.config.")) return true;
|
|
797
|
+
if (basename.startsWith("rollup.config.")) return true;
|
|
798
|
+
if (basename.startsWith("webpack.config.")) return true;
|
|
799
|
+
if (basename.startsWith("vitest.config.")) return true;
|
|
800
|
+
if (basename === "turbo.json" || basename === "nx.json") return true;
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
548
803
|
function extractFirstParagraph(readme) {
|
|
549
804
|
const lines = readme.split(/\r?\n/);
|
|
550
805
|
let i = 0;
|
|
@@ -724,8 +979,13 @@ var __testing__ = {
|
|
|
724
979
|
renderMarkdown,
|
|
725
980
|
stripFrontmatter,
|
|
726
981
|
isCIConfigPath,
|
|
982
|
+
isBuildConfigPath,
|
|
727
983
|
extractFirstParagraph,
|
|
728
|
-
extractExplicitDescription
|
|
984
|
+
extractExplicitDescription,
|
|
985
|
+
// TASK-008: bilingual template registry + language detection
|
|
986
|
+
detectExistingLanguage,
|
|
987
|
+
resolveKnowledgeLanguage,
|
|
988
|
+
BASELINE_TEMPLATES
|
|
729
989
|
};
|
|
730
990
|
|
|
731
991
|
export {
|
|
@@ -1,46 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/colors.ts
|
|
4
|
-
import pc from "picocolors";
|
|
5
|
-
import stringWidth from "string-width";
|
|
6
|
-
function isColorEnabled() {
|
|
7
|
-
return !process.env.NO_COLOR && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
|
|
8
|
-
}
|
|
9
|
-
function colorize(painter) {
|
|
10
|
-
return (value) => isColorEnabled() ? painter(value) : value;
|
|
11
|
-
}
|
|
12
|
-
var paint = {
|
|
13
|
-
success: colorize(pc.green),
|
|
14
|
-
warn: colorize(pc.yellow),
|
|
15
|
-
error: colorize(pc.red),
|
|
16
|
-
drift: colorize(pc.magenta),
|
|
17
|
-
ai: colorize(pc.blue),
|
|
18
|
-
human: colorize(pc.cyan),
|
|
19
|
-
muted: colorize(pc.dim)
|
|
20
|
-
};
|
|
21
|
-
var symbol = {
|
|
22
|
-
get ok() {
|
|
23
|
-
return isColorEnabled() ? paint.success("\u2713") : "[ok]";
|
|
24
|
-
},
|
|
25
|
-
get warn() {
|
|
26
|
-
return isColorEnabled() ? paint.warn("!") : "[warn]";
|
|
27
|
-
},
|
|
28
|
-
get error() {
|
|
29
|
-
return isColorEnabled() ? paint.error("x") : "[error]";
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
function displayWidth(value) {
|
|
33
|
-
return stringWidth(value);
|
|
34
|
-
}
|
|
35
|
-
function padEnd(value, width, char = " ") {
|
|
36
|
-
const fill = char.length > 0 ? char : " ";
|
|
37
|
-
let result = value;
|
|
38
|
-
while (displayWidth(result) < width) {
|
|
39
|
-
result += fill;
|
|
40
|
-
}
|
|
41
|
-
return result;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
3
|
// src/dev-mode.ts
|
|
45
4
|
import { existsSync, readFileSync } from "fs";
|
|
46
5
|
import { isAbsolute, join, resolve } from "path";
|
|
@@ -97,17 +56,8 @@ function formatResolutionStep(source, value) {
|
|
|
97
56
|
return `${source}: ${value ?? "<unset>"}`;
|
|
98
57
|
}
|
|
99
58
|
|
|
100
|
-
// src/i18n.ts
|
|
101
|
-
import { createTranslator, detectNodeLocale } from "@fenglimg/fabric-shared";
|
|
102
|
-
var locale = detectNodeLocale();
|
|
103
|
-
var t = createTranslator(locale);
|
|
104
|
-
|
|
105
59
|
export {
|
|
106
|
-
|
|
107
|
-
symbol,
|
|
108
|
-
displayWidth,
|
|
109
|
-
padEnd,
|
|
60
|
+
readFabricConfig,
|
|
110
61
|
resolveDevMode,
|
|
111
|
-
createDebugLogger
|
|
112
|
-
t
|
|
62
|
+
createDebugLogger
|
|
113
63
|
};
|