@fenglimg/fabric-server 2.0.0-rc.26 → 2.0.0-rc.28
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.
|
@@ -184,6 +184,46 @@ var EVENT_LEDGER_DEFAULT_RETENTION_DAYS = 30;
|
|
|
184
184
|
var EVENT_LEDGER_SIZE_WARN_BYTES = 50 * 1024 * 1024;
|
|
185
185
|
var EVENT_LEDGER_ARCHIVE_DIR = ".fabric/events.archive";
|
|
186
186
|
var warnedOversize = false;
|
|
187
|
+
var knownEventTypesCache = null;
|
|
188
|
+
function getKnownEventTypes() {
|
|
189
|
+
if (knownEventTypesCache !== null) return knownEventTypesCache;
|
|
190
|
+
const set = /* @__PURE__ */ new Set();
|
|
191
|
+
for (const opt of eventLedgerEventSchema.options) {
|
|
192
|
+
const shape = opt.shape;
|
|
193
|
+
if (shape && typeof shape.event_type?.value === "string") {
|
|
194
|
+
set.add(shape.event_type.value);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
knownEventTypesCache = set;
|
|
198
|
+
return set;
|
|
199
|
+
}
|
|
200
|
+
function classifyRejection(line, index) {
|
|
201
|
+
let parsed;
|
|
202
|
+
try {
|
|
203
|
+
parsed = JSON.parse(line);
|
|
204
|
+
} catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
if (parsed === null || typeof parsed !== "object") return null;
|
|
208
|
+
if ("schema_version" in parsed && parsed.schema_version !== 1 && (typeof parsed.schema_version === "number" || parsed.schema_version === null)) {
|
|
209
|
+
return {
|
|
210
|
+
kind: "schema_version_unsupported",
|
|
211
|
+
line_index: index,
|
|
212
|
+
schema_version: parsed.schema_version,
|
|
213
|
+
snippet_first_120: line.slice(0, 120)
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const known = getKnownEventTypes();
|
|
217
|
+
if (typeof parsed.event_type === "string" && !known.has(parsed.event_type)) {
|
|
218
|
+
return {
|
|
219
|
+
kind: "event_type_unknown",
|
|
220
|
+
line_index: index,
|
|
221
|
+
event_type: parsed.event_type,
|
|
222
|
+
snippet_first_120: line.slice(0, 120)
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
187
227
|
async function appendEventLedgerEvent(projectRoot, event) {
|
|
188
228
|
const eventPath = getEventLedgerPath(projectRoot);
|
|
189
229
|
const nextEvent = eventLedgerEventSchema.parse({
|
|
@@ -238,8 +278,20 @@ async function readEventLedger(projectRoot, options = {}) {
|
|
|
238
278
|
snippet_first_120: partialLine.slice(0, 120)
|
|
239
279
|
});
|
|
240
280
|
}
|
|
241
|
-
const
|
|
242
|
-
|
|
281
|
+
const trimmed = lines.map((line) => line.trim()).filter((line) => line.length > 0);
|
|
282
|
+
const events = [];
|
|
283
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
284
|
+
const line = trimmed[i];
|
|
285
|
+
const parsed = parseEventLedgerLine(line, i);
|
|
286
|
+
if (parsed !== null) {
|
|
287
|
+
events.push(parsed);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const rejection = classifyRejection(line, i);
|
|
291
|
+
if (rejection !== null) warnings.push(rejection);
|
|
292
|
+
}
|
|
293
|
+
const filtered = events.filter((entry) => options.event_type === void 0 || entry.event_type === options.event_type).filter((entry) => options.since === void 0 || entry.ts >= options.since).filter((entry) => options.correlation_id === void 0 || entry.correlation_id === options.correlation_id).filter((entry) => options.session_id === void 0 || entry.session_id === options.session_id);
|
|
294
|
+
return { events: filtered, warnings };
|
|
243
295
|
}
|
|
244
296
|
async function truncateLedgerToLastNewline(path2) {
|
|
245
297
|
const raw = await readFile2(path2);
|
|
@@ -951,16 +1003,18 @@ function extractRuleDescription(source) {
|
|
|
951
1003
|
}
|
|
952
1004
|
const heading = /^#\s+(.+?)\s*$/mu.exec(source);
|
|
953
1005
|
const summary = heading?.[1]?.trim();
|
|
954
|
-
|
|
1006
|
+
const knowledge = frontmatter !== null ? extractKnowledgeFieldsFromFrontmatter(frontmatter[1]) : void 0;
|
|
1007
|
+
const isStructurallyAKnowledgeEntry = summary !== void 0 && summary.length > 0 ? true : knowledge !== void 0 && (knowledge.id !== void 0 || knowledge.knowledge_type !== void 0 || knowledge.tags !== void 0 && knowledge.tags.length > 0);
|
|
1008
|
+
if (!isStructurallyAKnowledgeEntry) {
|
|
955
1009
|
return void 0;
|
|
956
1010
|
}
|
|
957
|
-
const
|
|
1011
|
+
const synthesizedSummary = summary !== void 0 && summary.length > 0 ? summary : knowledge?.id ?? (knowledge?.tags !== void 0 && knowledge.tags.length > 0 ? `(unnamed; tags: ${knowledge.tags.join(", ")})` : "(unnamed knowledge entry)");
|
|
958
1012
|
return {
|
|
959
|
-
summary,
|
|
1013
|
+
summary: synthesizedSummary,
|
|
960
1014
|
intent_clues: [],
|
|
961
1015
|
tech_stack: [],
|
|
962
1016
|
impact: [],
|
|
963
|
-
must_read_if:
|
|
1017
|
+
must_read_if: synthesizedSummary,
|
|
964
1018
|
// v2.0-rc.22: when frontmatter is present, merge its knowledge fields;
|
|
965
1019
|
// when fully absent (no `---` block), all knowledge fields stay
|
|
966
1020
|
// undefined, matching the original heading-only fallback contract.
|
|
@@ -1699,6 +1753,7 @@ async function runDoctorReport(target) {
|
|
|
1699
1753
|
inspectL2ManagedBlockDrift(projectRoot)
|
|
1700
1754
|
]);
|
|
1701
1755
|
const mcpConfigInWrongFile = inspectMcpConfigInWrongFile(projectRoot);
|
|
1756
|
+
const skillRefMirror = inspectSkillRefMirror(projectRoot);
|
|
1702
1757
|
const metaManuallyDiverged = await inspectMetaManuallyDiverged(projectRoot);
|
|
1703
1758
|
const knowledgeDirUnindexed = inspectKnowledgeDirUnindexed(projectRoot, meta);
|
|
1704
1759
|
const knowledgeDirMissing = inspectKnowledgeDirMissing(projectRoot);
|
|
@@ -1757,6 +1812,15 @@ async function runDoctorReport(target) {
|
|
|
1757
1812
|
createKnowledgeTestIndexCheck(t, knowledgeTestIndex),
|
|
1758
1813
|
createEventLedgerCheck(t, eventLedger),
|
|
1759
1814
|
createEventLedgerPartialWriteCheck(t, eventLedger),
|
|
1815
|
+
// v2.0.0-rc.27 TASK-010 (audit §2.24): forward-compat warning surface for
|
|
1816
|
+
// events.jsonl rows that fail Zod validation because of unknown
|
|
1817
|
+
// schema_version or event_type tokens. Previously silently dropped.
|
|
1818
|
+
createEventLedgerSchemaCompatCheck(t, eventLedger),
|
|
1819
|
+
// v2.0.0-rc.28 TASK-04 (audit §3.1 follow-up): SKILL ref/ mirror parity.
|
|
1820
|
+
// Detects hand-edits or partial install that breaks the byte-identical
|
|
1821
|
+
// contract between .claude/skills/<slug>/ref/ and .codex/skills/<slug>/
|
|
1822
|
+
// ref/. warning severity — fab install restores parity.
|
|
1823
|
+
createSkillRefMirrorCheck(t, skillRefMirror),
|
|
1760
1824
|
createMcpConfigInWrongFileCheck(t, mcpConfigInWrongFile),
|
|
1761
1825
|
createMetaManuallyDivergedCheck(t, metaManuallyDiverged),
|
|
1762
1826
|
createKnowledgeDirUnindexedCheck(t, knowledgeDirUnindexed),
|
|
@@ -1905,7 +1969,8 @@ async function runDoctorFix(target) {
|
|
|
1905
1969
|
"knowledge_test_index_missing",
|
|
1906
1970
|
"knowledge_test_index_stale",
|
|
1907
1971
|
"content_ref_missing",
|
|
1908
|
-
"knowledge_dir_unindexed"
|
|
1972
|
+
"knowledge_dir_unindexed",
|
|
1973
|
+
"meta_manually_diverged"
|
|
1909
1974
|
];
|
|
1910
1975
|
if (before.fixable_errors.some((issue) => reconcileCodes.includes(issue.code)) || before.warnings.some((issue) => reconcileCodes.includes(issue.code))) {
|
|
1911
1976
|
await reconcileKnowledge(projectRoot, { trigger: "doctor" });
|
|
@@ -2487,7 +2552,19 @@ async function inspectEventLedger(projectRoot) {
|
|
|
2487
2552
|
const path2 = getEventLedgerPath(projectRoot);
|
|
2488
2553
|
const exists = existsSync4(path2);
|
|
2489
2554
|
if (!exists) {
|
|
2490
|
-
return {
|
|
2555
|
+
return {
|
|
2556
|
+
exists: false,
|
|
2557
|
+
writable: false,
|
|
2558
|
+
parseable: false,
|
|
2559
|
+
hasPartialWrite: false,
|
|
2560
|
+
partialWriteByteOffset: 0,
|
|
2561
|
+
partialWriteByteLength: 0,
|
|
2562
|
+
schemaVersionUnsupportedCount: 0,
|
|
2563
|
+
eventTypeUnknownCount: 0,
|
|
2564
|
+
schemaVersionSamples: [],
|
|
2565
|
+
eventTypeSamples: [],
|
|
2566
|
+
path: path2
|
|
2567
|
+
};
|
|
2491
2568
|
}
|
|
2492
2569
|
try {
|
|
2493
2570
|
await access(path2, constants.W_OK);
|
|
@@ -2495,6 +2572,25 @@ async function inspectEventLedger(projectRoot) {
|
|
|
2495
2572
|
const raw = await readFile5(path2, "utf8");
|
|
2496
2573
|
const invalidLine = raw.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).find((line) => !isValidJsonLine(line));
|
|
2497
2574
|
const partialWarning = warnings.find((w) => w.kind === "partial_write_at_tail");
|
|
2575
|
+
const schemaVersionSamples = [];
|
|
2576
|
+
const eventTypeSamples = [];
|
|
2577
|
+
let schemaVersionUnsupportedCount = 0;
|
|
2578
|
+
let eventTypeUnknownCount = 0;
|
|
2579
|
+
for (const w of warnings) {
|
|
2580
|
+
if (w.kind === "schema_version_unsupported") {
|
|
2581
|
+
schemaVersionUnsupportedCount += 1;
|
|
2582
|
+
const token = String(w.schema_version);
|
|
2583
|
+
if (!schemaVersionSamples.includes(token) && schemaVersionSamples.length < 3) {
|
|
2584
|
+
schemaVersionSamples.push(token);
|
|
2585
|
+
}
|
|
2586
|
+
} else if (w.kind === "event_type_unknown") {
|
|
2587
|
+
eventTypeUnknownCount += 1;
|
|
2588
|
+
const token = String(w.event_type);
|
|
2589
|
+
if (!eventTypeSamples.includes(token) && eventTypeSamples.length < 3) {
|
|
2590
|
+
eventTypeSamples.push(token);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2498
2594
|
return {
|
|
2499
2595
|
exists: true,
|
|
2500
2596
|
writable: true,
|
|
@@ -2502,6 +2598,10 @@ async function inspectEventLedger(projectRoot) {
|
|
|
2502
2598
|
hasPartialWrite: partialWarning !== void 0,
|
|
2503
2599
|
partialWriteByteOffset: partialWarning?.byte_offset ?? 0,
|
|
2504
2600
|
partialWriteByteLength: partialWarning?.byte_length ?? 0,
|
|
2601
|
+
schemaVersionUnsupportedCount,
|
|
2602
|
+
eventTypeUnknownCount,
|
|
2603
|
+
schemaVersionSamples,
|
|
2604
|
+
eventTypeSamples,
|
|
2505
2605
|
path: path2,
|
|
2506
2606
|
error: invalidLine === void 0 ? void 0 : "events.jsonl contains an invalid JSON line."
|
|
2507
2607
|
};
|
|
@@ -2513,11 +2613,62 @@ async function inspectEventLedger(projectRoot) {
|
|
|
2513
2613
|
hasPartialWrite: false,
|
|
2514
2614
|
partialWriteByteOffset: 0,
|
|
2515
2615
|
partialWriteByteLength: 0,
|
|
2616
|
+
schemaVersionUnsupportedCount: 0,
|
|
2617
|
+
eventTypeUnknownCount: 0,
|
|
2618
|
+
schemaVersionSamples: [],
|
|
2619
|
+
eventTypeSamples: [],
|
|
2516
2620
|
path: path2,
|
|
2517
2621
|
error: error instanceof Error ? error.message : String(error)
|
|
2518
2622
|
};
|
|
2519
2623
|
}
|
|
2520
2624
|
}
|
|
2625
|
+
function inspectSkillRefMirror(projectRoot) {
|
|
2626
|
+
const skillSlugs = ["fabric-archive", "fabric-review", "fabric-import"];
|
|
2627
|
+
const driftedPaths = [];
|
|
2628
|
+
for (const slug of skillSlugs) {
|
|
2629
|
+
const claudeRef = join6(projectRoot, ".claude", "skills", slug, "ref");
|
|
2630
|
+
const codexRef = join6(projectRoot, ".codex", "skills", slug, "ref");
|
|
2631
|
+
let claudeFiles = null;
|
|
2632
|
+
let codexFiles = null;
|
|
2633
|
+
try {
|
|
2634
|
+
claudeFiles = readdirSync(claudeRef).filter((n) => n.endsWith(".md"));
|
|
2635
|
+
} catch {
|
|
2636
|
+
}
|
|
2637
|
+
try {
|
|
2638
|
+
codexFiles = readdirSync(codexRef).filter((n) => n.endsWith(".md"));
|
|
2639
|
+
} catch {
|
|
2640
|
+
}
|
|
2641
|
+
if (claudeFiles === null || codexFiles === null) continue;
|
|
2642
|
+
const claudeSet = new Set(claudeFiles);
|
|
2643
|
+
const codexSet = new Set(codexFiles);
|
|
2644
|
+
const union = /* @__PURE__ */ new Set([...claudeFiles, ...codexFiles]);
|
|
2645
|
+
for (const fname of union) {
|
|
2646
|
+
const inClaude = claudeSet.has(fname);
|
|
2647
|
+
const inCodex = codexSet.has(fname);
|
|
2648
|
+
if (!inClaude || !inCodex) {
|
|
2649
|
+
driftedPaths.push(`skills/${slug}/ref/${fname}`);
|
|
2650
|
+
continue;
|
|
2651
|
+
}
|
|
2652
|
+
let claudeBody;
|
|
2653
|
+
let codexBody;
|
|
2654
|
+
try {
|
|
2655
|
+
claudeBody = readFileSync2(join6(claudeRef, fname), "utf8");
|
|
2656
|
+
} catch {
|
|
2657
|
+
continue;
|
|
2658
|
+
}
|
|
2659
|
+
try {
|
|
2660
|
+
codexBody = readFileSync2(join6(codexRef, fname), "utf8");
|
|
2661
|
+
} catch {
|
|
2662
|
+
continue;
|
|
2663
|
+
}
|
|
2664
|
+
if (claudeBody !== codexBody) {
|
|
2665
|
+
driftedPaths.push(`skills/${slug}/ref/${fname}`);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
if (driftedPaths.length === 0) return { status: "ok" };
|
|
2670
|
+
return { status: "drift", driftedPaths };
|
|
2671
|
+
}
|
|
2521
2672
|
async function inspectKnowledgeTestIndex(projectRoot) {
|
|
2522
2673
|
const path2 = join6(projectRoot, ".fabric", ".cache", "knowledge-test.index.json");
|
|
2523
2674
|
const built = await tryBuildRuleMeta(projectRoot);
|
|
@@ -3053,6 +3204,66 @@ function createMcpConfigInWrongFileCheck(t, inspection) {
|
|
|
3053
3204
|
t("doctor.check.mcp_config_in_wrong_file.ok")
|
|
3054
3205
|
);
|
|
3055
3206
|
}
|
|
3207
|
+
function createEventLedgerSchemaCompatCheck(t, ledger) {
|
|
3208
|
+
if (!ledger.exists || !ledger.writable) {
|
|
3209
|
+
return okCheck(
|
|
3210
|
+
t("doctor.check.event_ledger_schema_compat.name"),
|
|
3211
|
+
t("doctor.check.event_ledger_schema_compat.ok.skipped")
|
|
3212
|
+
);
|
|
3213
|
+
}
|
|
3214
|
+
const hasUnsupportedVersion = ledger.schemaVersionUnsupportedCount > 0;
|
|
3215
|
+
const hasUnknownEventType = ledger.eventTypeUnknownCount > 0;
|
|
3216
|
+
if (!hasUnsupportedVersion && !hasUnknownEventType) {
|
|
3217
|
+
return okCheck(
|
|
3218
|
+
t("doctor.check.event_ledger_schema_compat.name"),
|
|
3219
|
+
t("doctor.check.event_ledger_schema_compat.ok.clean")
|
|
3220
|
+
);
|
|
3221
|
+
}
|
|
3222
|
+
const parts = [];
|
|
3223
|
+
if (hasUnsupportedVersion) {
|
|
3224
|
+
parts.push(
|
|
3225
|
+
t("doctor.check.event_ledger_schema_compat.message.schema_version", {
|
|
3226
|
+
count: String(ledger.schemaVersionUnsupportedCount),
|
|
3227
|
+
samples: ledger.schemaVersionSamples.join(", ")
|
|
3228
|
+
})
|
|
3229
|
+
);
|
|
3230
|
+
}
|
|
3231
|
+
if (hasUnknownEventType) {
|
|
3232
|
+
parts.push(
|
|
3233
|
+
t("doctor.check.event_ledger_schema_compat.message.event_type", {
|
|
3234
|
+
count: String(ledger.eventTypeUnknownCount),
|
|
3235
|
+
samples: ledger.eventTypeSamples.join(", ")
|
|
3236
|
+
})
|
|
3237
|
+
);
|
|
3238
|
+
}
|
|
3239
|
+
return issueCheck(
|
|
3240
|
+
t("doctor.check.event_ledger_schema_compat.name"),
|
|
3241
|
+
"warn",
|
|
3242
|
+
"warning",
|
|
3243
|
+
"event_ledger_schema_compat",
|
|
3244
|
+
parts.join(" "),
|
|
3245
|
+
t("doctor.check.event_ledger_schema_compat.remediation")
|
|
3246
|
+
);
|
|
3247
|
+
}
|
|
3248
|
+
function createSkillRefMirrorCheck(t, inspection) {
|
|
3249
|
+
if (inspection.status === "ok") {
|
|
3250
|
+
return okCheck(
|
|
3251
|
+
t("doctor.check.skill_ref_mirror.name"),
|
|
3252
|
+
t("doctor.check.skill_ref_mirror.ok")
|
|
3253
|
+
);
|
|
3254
|
+
}
|
|
3255
|
+
return issueCheck(
|
|
3256
|
+
t("doctor.check.skill_ref_mirror.name"),
|
|
3257
|
+
"warn",
|
|
3258
|
+
"warning",
|
|
3259
|
+
"skill_ref_mirror_drift",
|
|
3260
|
+
t("doctor.check.skill_ref_mirror.message", {
|
|
3261
|
+
count: String(inspection.driftedPaths.length),
|
|
3262
|
+
list: inspection.driftedPaths.join(", ")
|
|
3263
|
+
}),
|
|
3264
|
+
t("doctor.check.skill_ref_mirror.remediation")
|
|
3265
|
+
);
|
|
3266
|
+
}
|
|
3056
3267
|
function createEventLedgerPartialWriteCheck(t, ledger) {
|
|
3057
3268
|
if (!ledger.exists || !ledger.writable) {
|
|
3058
3269
|
return okCheck(
|
package/dist/index.d.ts
CHANGED
|
@@ -425,8 +425,14 @@ interface ReconcileKnowledgeOptions {
|
|
|
425
425
|
* v2.0.0-rc.23 TASK-005 (a-B): `auto-heal-description` added so plan_context
|
|
426
426
|
* can drive a full reconcile when it detects nodes with `description === undefined`
|
|
427
427
|
* (legacy meta drift the revision-hash gate cannot detect).
|
|
428
|
+
*
|
|
429
|
+
* v2.0.0-rc.27 TASK-001 (§2.9 root): `post-approve` / `post-modify` added so
|
|
430
|
+
* `fab_review` approve/modify-layer-flip can drive an immediate meta rebuild
|
|
431
|
+
* — without this the new entry's `nodes[id]` stays empty until the next
|
|
432
|
+
* plan_context call's auto-heal, which leaves the entry undiscoverable in
|
|
433
|
+
* the description_index window between approve and the next hint call.
|
|
428
434
|
*/
|
|
429
|
-
trigger?: "startup" | "doctor" | "manual" | "auto-heal-description";
|
|
435
|
+
trigger?: "startup" | "doctor" | "manual" | "auto-heal-description" | "post-approve" | "post-modify";
|
|
430
436
|
}
|
|
431
437
|
/**
|
|
432
438
|
* Full scan + rewrites agents.meta.json with ground-truth disk state + emits
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
sha256,
|
|
38
38
|
stableStringify,
|
|
39
39
|
writeKnowledgeMeta
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-EEVSGTHG.js";
|
|
41
41
|
|
|
42
42
|
// src/index.ts
|
|
43
43
|
import { existsSync as existsSync4 } from "fs";
|
|
@@ -250,7 +250,24 @@ async function extractKnowledge(projectRoot, input) {
|
|
|
250
250
|
const existing = await readFile(absolutePath, "utf8");
|
|
251
251
|
const existingKey = readFrontmatterKey(existing, "x-fabric-idempotency-key");
|
|
252
252
|
if (existingKey === idempotencyKey) {
|
|
253
|
-
const
|
|
253
|
+
const fresh2 = renderFreshEntry({
|
|
254
|
+
type: input.type,
|
|
255
|
+
sourceSessions,
|
|
256
|
+
idempotencyKey,
|
|
257
|
+
summary,
|
|
258
|
+
recentPaths: input.recent_paths,
|
|
259
|
+
layer,
|
|
260
|
+
proposedReason: input.proposed_reason,
|
|
261
|
+
sessionContext: input.session_context,
|
|
262
|
+
relevanceScope,
|
|
263
|
+
relevancePaths,
|
|
264
|
+
intentClues: input.intent_clues,
|
|
265
|
+
techStack: input.tech_stack,
|
|
266
|
+
impact: input.impact,
|
|
267
|
+
mustReadIf: input.must_read_if,
|
|
268
|
+
onboardSlot: input.onboard_slot
|
|
269
|
+
});
|
|
270
|
+
const augmented = mergeEvidenceNotes(existing, fresh2);
|
|
254
271
|
await atomicWriteText(absolutePath, augmented);
|
|
255
272
|
await emitEventBestEffort(projectRoot, {
|
|
256
273
|
event_type: "knowledge_proposed",
|
|
@@ -402,62 +419,18 @@ function renderEvidenceBlock(summary, recentPaths) {
|
|
|
402
419
|
`- ${summary.trim()}`
|
|
403
420
|
].join("\n");
|
|
404
421
|
}
|
|
405
|
-
function mergeEvidenceNotes(existing,
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
);
|
|
410
|
-
if (beforeMatch === null) {
|
|
411
|
-
const trimmed = existing.endsWith("\n") ? existing : `${existing}
|
|
412
|
-
`;
|
|
413
|
-
return `${trimmed}
|
|
414
|
-
## Evidence
|
|
415
|
-
|
|
416
|
-
${renderEvidenceBlock(newSummary, newRecentPaths)}
|
|
422
|
+
function mergeEvidenceNotes(existing, fresh) {
|
|
423
|
+
const freshSplit = splitAtEvidence(fresh);
|
|
424
|
+
if (freshSplit === null) {
|
|
425
|
+
return fresh.endsWith("\n") ? fresh : `${fresh}
|
|
417
426
|
`;
|
|
418
427
|
}
|
|
419
|
-
const
|
|
420
|
-
const
|
|
421
|
-
const
|
|
422
|
-
const evidenceBlockRe = /\n## Evidence(?:\s*\(call \d+\))?\s*\n([\s\S]*?)(?=\n## |$)/gu;
|
|
423
|
-
let m;
|
|
424
|
-
while ((m = evidenceBlockRe.exec(`${existing}
|
|
425
|
-
`)) !== null) {
|
|
426
|
-
const block = m[1] ?? "";
|
|
427
|
-
const pathSection = /Recent paths:\s*\n([\s\S]*?)(?:\n\s*Notes:|$)/u.exec(block);
|
|
428
|
-
if (pathSection !== null) {
|
|
429
|
-
for (const rawLine of (pathSection[1] ?? "").split(/\r?\n/u)) {
|
|
430
|
-
const t = rawLine.trim();
|
|
431
|
-
if (t.startsWith("- ")) {
|
|
432
|
-
existingPaths.push(t.slice(2).trim());
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
const notesSection = /Notes:\s*\n([\s\S]*?)$/u.exec(block);
|
|
437
|
-
const noteBody = (notesSection !== null ? notesSection[1] : block) ?? "";
|
|
438
|
-
const bulletLines = [];
|
|
439
|
-
let prose = [];
|
|
440
|
-
for (const rawLine of noteBody.split(/\r?\n/u)) {
|
|
441
|
-
const t = rawLine.trim();
|
|
442
|
-
if (t.length === 0) continue;
|
|
443
|
-
if (t.startsWith("- ")) {
|
|
444
|
-
if (prose.length > 0) {
|
|
445
|
-
existingNotes.push(prose.join(" ").trim());
|
|
446
|
-
prose = [];
|
|
447
|
-
}
|
|
448
|
-
bulletLines.push(t.slice(2).trim());
|
|
449
|
-
} else {
|
|
450
|
-
prose.push(t);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
if (prose.length > 0) existingNotes.push(prose.join(" ").trim());
|
|
454
|
-
for (const n of bulletLines) existingNotes.push(n);
|
|
455
|
-
}
|
|
428
|
+
const freshHead = freshSplit.head;
|
|
429
|
+
const oldEvidence = collectEvidenceItems(existing);
|
|
430
|
+
const freshEvidence = collectEvidenceItems(fresh);
|
|
456
431
|
const mergedNotes = [];
|
|
457
432
|
const seenNotes = /* @__PURE__ */ new Set();
|
|
458
|
-
const
|
|
459
|
-
const candidates = [...existingNotes, incomingNote];
|
|
460
|
-
for (const note of candidates) {
|
|
433
|
+
for (const note of [...oldEvidence.notes, ...freshEvidence.notes]) {
|
|
461
434
|
const key = note.replace(/\s+/gu, " ").trim();
|
|
462
435
|
if (key.length === 0) continue;
|
|
463
436
|
if (seenNotes.has(key)) continue;
|
|
@@ -466,7 +439,7 @@ ${renderEvidenceBlock(newSummary, newRecentPaths)}
|
|
|
466
439
|
}
|
|
467
440
|
const mergedPaths = [];
|
|
468
441
|
const seenPaths = /* @__PURE__ */ new Set();
|
|
469
|
-
for (const p of [...
|
|
442
|
+
for (const p of [...oldEvidence.paths, ...freshEvidence.paths]) {
|
|
470
443
|
const key = p.trim();
|
|
471
444
|
if (key.length === 0) continue;
|
|
472
445
|
if (seenPaths.has(key)) continue;
|
|
@@ -484,12 +457,58 @@ ${renderEvidenceBlock(newSummary, newRecentPaths)}
|
|
|
484
457
|
"",
|
|
485
458
|
noteLines
|
|
486
459
|
].join("\n");
|
|
487
|
-
return `${
|
|
460
|
+
return `${freshHead}
|
|
488
461
|
## Evidence
|
|
489
462
|
|
|
490
463
|
${evidenceBody}
|
|
491
464
|
`;
|
|
492
465
|
}
|
|
466
|
+
function splitAtEvidence(content) {
|
|
467
|
+
const tail = content.endsWith("\n") ? content : `${content}
|
|
468
|
+
`;
|
|
469
|
+
const match = /^([\s\S]*?)(\n## Evidence(?:\s*\(call \d+\))?\s*\n)/u.exec(tail);
|
|
470
|
+
if (match === null) return null;
|
|
471
|
+
return { head: match[1] ?? "" };
|
|
472
|
+
}
|
|
473
|
+
function collectEvidenceItems(content) {
|
|
474
|
+
const notes = [];
|
|
475
|
+
const paths = [];
|
|
476
|
+
const evidenceBlockRe = /\n## Evidence(?:\s*\(call \d+\))?\s*\n([\s\S]*?)(?=\n## |$)/gu;
|
|
477
|
+
let m;
|
|
478
|
+
while ((m = evidenceBlockRe.exec(`${content}
|
|
479
|
+
`)) !== null) {
|
|
480
|
+
const block = m[1] ?? "";
|
|
481
|
+
const pathSection = /Recent paths:\s*\n([\s\S]*?)(?:\n\s*Notes:|$)/u.exec(block);
|
|
482
|
+
if (pathSection !== null) {
|
|
483
|
+
for (const rawLine of (pathSection[1] ?? "").split(/\r?\n/u)) {
|
|
484
|
+
const t = rawLine.trim();
|
|
485
|
+
if (t.startsWith("- ")) {
|
|
486
|
+
paths.push(t.slice(2).trim());
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const notesSection = /Notes:\s*\n([\s\S]*?)$/u.exec(block);
|
|
491
|
+
const noteBody = (notesSection !== null ? notesSection[1] : block) ?? "";
|
|
492
|
+
const bulletLines = [];
|
|
493
|
+
let prose = [];
|
|
494
|
+
for (const rawLine of noteBody.split(/\r?\n/u)) {
|
|
495
|
+
const t = rawLine.trim();
|
|
496
|
+
if (t.length === 0) continue;
|
|
497
|
+
if (t.startsWith("- ")) {
|
|
498
|
+
if (prose.length > 0) {
|
|
499
|
+
notes.push(prose.join(" ").trim());
|
|
500
|
+
prose = [];
|
|
501
|
+
}
|
|
502
|
+
bulletLines.push(t.slice(2).trim());
|
|
503
|
+
} else {
|
|
504
|
+
prose.push(t);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (prose.length > 0) notes.push(prose.join(" ").trim());
|
|
508
|
+
for (const n of bulletLines) notes.push(n);
|
|
509
|
+
}
|
|
510
|
+
return { notes, paths };
|
|
511
|
+
}
|
|
493
512
|
function readFrontmatterKey(content, key) {
|
|
494
513
|
const match = /^---\n([\s\S]*?)\n---/u.exec(content);
|
|
495
514
|
if (match === null) {
|
|
@@ -567,7 +586,34 @@ import { minimatch } from "minimatch";
|
|
|
567
586
|
import { deriveAgentsMetaLayer } from "@fenglimg/fabric-shared";
|
|
568
587
|
var SELECTION_TOKEN_TTL_MS = 5 * 60 * 1e3;
|
|
569
588
|
var selectionTokenCache = /* @__PURE__ */ new Map();
|
|
589
|
+
function assertPathInSandbox(rawPath) {
|
|
590
|
+
if (rawPath === "**" || rawPath === "*") return;
|
|
591
|
+
const normalized = rawPath.replaceAll("\\", "/");
|
|
592
|
+
if (normalized.startsWith("/")) {
|
|
593
|
+
throw new Error(
|
|
594
|
+
`plan_context: absolute paths are not allowed (got "${rawPath}"); pass a path relative to the project root`
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
if (normalized.startsWith("~/") || normalized === "~") {
|
|
598
|
+
throw new Error(
|
|
599
|
+
`plan_context: shell sigil "~" is not allowed (got "${rawPath}"); expand to a project-relative path before calling`
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
if (normalized.split("/").some((seg) => seg === "..")) {
|
|
603
|
+
throw new Error(
|
|
604
|
+
`plan_context: ".." traversal is not allowed (got "${rawPath}"); pass a path that resolves under the project root`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
570
608
|
async function planContext(projectRoot, input) {
|
|
609
|
+
for (const p of input.paths) {
|
|
610
|
+
assertPathInSandbox(p);
|
|
611
|
+
}
|
|
612
|
+
if (input.target_paths !== void 0) {
|
|
613
|
+
for (const p of input.target_paths) {
|
|
614
|
+
assertPathInSandbox(p);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
571
617
|
let metaResult = await loadActiveMetaOrStale(projectRoot, { caller: "planContext" });
|
|
572
618
|
let meta = metaResult.meta;
|
|
573
619
|
let firstSeenPreviousRevision = metaResult.previous_revision_hash;
|
|
@@ -1042,6 +1088,23 @@ function resolveSandboxedPath(projectRoot, candidate, options = {}) {
|
|
|
1042
1088
|
}
|
|
1043
1089
|
throw new Error(`path escapes knowledge root: ${candidate}`);
|
|
1044
1090
|
}
|
|
1091
|
+
function extractBody(content) {
|
|
1092
|
+
const match = /^(?:\uFEFF)?---\r?\n[\s\S]*?\r?\n---\s*(?:\r?\n|$)/u.exec(content);
|
|
1093
|
+
if (match === null) {
|
|
1094
|
+
return content.trim();
|
|
1095
|
+
}
|
|
1096
|
+
return content.slice(match[0].length).trim();
|
|
1097
|
+
}
|
|
1098
|
+
function isVisibleByLifecycle(fm, filters) {
|
|
1099
|
+
if (fm.status === "rejected" && filters?.include_rejected !== true) {
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
if (fm.status === "deferred" && filters?.include_deferred !== true) {
|
|
1103
|
+
if (fm.deferred_until === void 0) return false;
|
|
1104
|
+
if (fm.deferred_until > (/* @__PURE__ */ new Date()).toISOString()) return false;
|
|
1105
|
+
}
|
|
1106
|
+
return true;
|
|
1107
|
+
}
|
|
1045
1108
|
async function listPending(projectRoot, filters) {
|
|
1046
1109
|
const items = [];
|
|
1047
1110
|
const typesToScan = filters?.type !== void 0 ? [filters.type] : PLURAL_TYPES;
|
|
@@ -1090,14 +1153,27 @@ async function listPending(projectRoot, filters) {
|
|
|
1090
1153
|
continue;
|
|
1091
1154
|
}
|
|
1092
1155
|
}
|
|
1156
|
+
if (!isVisibleByLifecycle(fm, filters)) {
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1093
1159
|
const reportedPath = source.origin === "personal" ? `~/${relative2(resolvePersonalRoot2(), absolutePath)}` : relative2(projectRoot, absolutePath);
|
|
1094
1160
|
items.push({
|
|
1095
1161
|
pending_path: reportedPath,
|
|
1162
|
+
// v2.0.0-rc.27 TASK-001 (§2.12): absolute path companion for
|
|
1163
|
+
// personal entries so programmatic consumers (Read, fs.readFile)
|
|
1164
|
+
// don't need to shell-expand the `~` themselves.
|
|
1165
|
+
...source.origin === "personal" ? { pending_path_absolute: absolutePath } : {},
|
|
1096
1166
|
type,
|
|
1097
1167
|
layer,
|
|
1098
1168
|
maturity,
|
|
1099
1169
|
origin: source.origin,
|
|
1100
|
-
...fm.tags !== void 0 && fm.tags.length > 0 ? { tags: fm.tags } : {}
|
|
1170
|
+
...fm.tags !== void 0 && fm.tags.length > 0 ? { tags: fm.tags } : {},
|
|
1171
|
+
...fm.status !== void 0 ? { status: fm.status } : {},
|
|
1172
|
+
...fm.deferred_until !== void 0 ? { deferred_until: fm.deferred_until } : {},
|
|
1173
|
+
// v2.0.0-rc.27 TASK-006 (audit §2.23): full body when caller
|
|
1174
|
+
// opted in. Reviewer UI consumes this to scan for prompt-injection
|
|
1175
|
+
// payloads hidden under `## Evidence` body.
|
|
1176
|
+
...filters?.include_body === true ? { body: extractBody(content) } : {}
|
|
1101
1177
|
});
|
|
1102
1178
|
}
|
|
1103
1179
|
}
|
|
@@ -1189,6 +1265,10 @@ async function approveOne(projectRoot, pendingPath, allocator) {
|
|
|
1189
1265
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1190
1266
|
reason: `approve:${slug}`
|
|
1191
1267
|
});
|
|
1268
|
+
try {
|
|
1269
|
+
await reconcileKnowledge(projectRoot, { trigger: "post-approve" });
|
|
1270
|
+
} catch {
|
|
1271
|
+
}
|
|
1192
1272
|
return { pending_path: pendingPath, stable_id: stableId };
|
|
1193
1273
|
} catch (err) {
|
|
1194
1274
|
if (writtenTarget && targetAbs !== void 0 && existsSync3(targetAbs)) {
|
|
@@ -1210,6 +1290,17 @@ async function approveOne(projectRoot, pendingPath, allocator) {
|
|
|
1210
1290
|
async function rejectAll(projectRoot, pendingPaths, reason) {
|
|
1211
1291
|
const rejected = [];
|
|
1212
1292
|
for (const pendingPath of pendingPaths) {
|
|
1293
|
+
try {
|
|
1294
|
+
const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
|
|
1295
|
+
if (existsSync3(sandboxed.abs)) {
|
|
1296
|
+
const content = await readFile3(sandboxed.abs, "utf8");
|
|
1297
|
+
const merged = rewriteFrontmatterMerge(content, { status: "rejected" });
|
|
1298
|
+
if (merged !== content) {
|
|
1299
|
+
await atomicWriteText(sandboxed.abs, merged);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
} catch {
|
|
1303
|
+
}
|
|
1213
1304
|
await emitEventBestEffort2(projectRoot, {
|
|
1214
1305
|
event_type: "knowledge_rejected",
|
|
1215
1306
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1268,6 +1359,11 @@ function extractSlug(path) {
|
|
|
1268
1359
|
return file.replace(/^K[PT]-(MOD|DEC|GLD|PIT|PRO)-\d+--/u, "");
|
|
1269
1360
|
}
|
|
1270
1361
|
async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
|
|
1362
|
+
if (target.absPath.includes("/pending/")) {
|
|
1363
|
+
throw new Error(
|
|
1364
|
+
"layer-flip not allowed on pending entries; approve first, then modify the canonical entry's layer"
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1271
1367
|
const fromLayer = fm.layer ?? "team";
|
|
1272
1368
|
const toLayer = changes.layer;
|
|
1273
1369
|
const pluralType = fm.type ?? target.inferredType;
|
|
@@ -1334,6 +1430,10 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
|
|
|
1334
1430
|
});
|
|
1335
1431
|
}
|
|
1336
1432
|
const responsePath = toLayer === "team" ? relative2(projectRoot, toAbs) : `~/${relative2(resolvePersonalRoot2(), toAbs)}`;
|
|
1433
|
+
try {
|
|
1434
|
+
await reconcileKnowledge(projectRoot, { trigger: "post-modify" });
|
|
1435
|
+
} catch {
|
|
1436
|
+
}
|
|
1337
1437
|
return {
|
|
1338
1438
|
action: "modify",
|
|
1339
1439
|
pending_path: responsePath,
|
|
@@ -1390,17 +1490,25 @@ async function searchEntries(projectRoot, query, filters) {
|
|
|
1390
1490
|
continue;
|
|
1391
1491
|
}
|
|
1392
1492
|
}
|
|
1493
|
+
if (!isVisibleByLifecycle(fm, filters)) {
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
const bodyForSearch = filters?.include_body === true ? extractBody(content) : "";
|
|
1393
1497
|
const haystacks = [
|
|
1394
1498
|
fm.title ?? "",
|
|
1395
1499
|
fm.summary ?? "",
|
|
1396
1500
|
...fm.tags ?? [],
|
|
1397
|
-
name
|
|
1501
|
+
name,
|
|
1502
|
+
bodyForSearch
|
|
1398
1503
|
].map((s) => s.toLowerCase());
|
|
1399
1504
|
const matches = haystacks.some((h) => h.includes(lowerQuery));
|
|
1400
1505
|
if (!matches) continue;
|
|
1401
1506
|
const reportedPath = source.isPersonal ? `~/${relative2(resolvePersonalRoot2(), absolutePath)}` : relative2(projectRoot, absolutePath);
|
|
1402
1507
|
items.push({
|
|
1403
1508
|
pending_path: reportedPath,
|
|
1509
|
+
// v2.0.0-rc.27 TASK-001 (§2.12): absolute companion for personal
|
|
1510
|
+
// entries (mirrors listPending).
|
|
1511
|
+
...source.isPersonal ? { pending_path_absolute: absolutePath } : {},
|
|
1404
1512
|
type,
|
|
1405
1513
|
layer,
|
|
1406
1514
|
maturity,
|
|
@@ -1409,7 +1517,14 @@ async function searchEntries(projectRoot, query, filters) {
|
|
|
1409
1517
|
...source.isPending ? { origin: source.isPersonal ? "personal" : "team" } : {},
|
|
1410
1518
|
...fm.tags !== void 0 && fm.tags.length > 0 ? { tags: fm.tags } : {},
|
|
1411
1519
|
...fm.title !== void 0 ? { title: fm.title } : {},
|
|
1412
|
-
...fm.summary !== void 0 ? { summary: fm.summary } : {}
|
|
1520
|
+
...fm.summary !== void 0 ? { summary: fm.summary } : {},
|
|
1521
|
+
...fm.status !== void 0 ? { status: fm.status } : {},
|
|
1522
|
+
...fm.deferred_until !== void 0 ? { deferred_until: fm.deferred_until } : {},
|
|
1523
|
+
// v2.0.0-rc.27 TASK-006 (audit §2.23): body emission when opted in.
|
|
1524
|
+
// Reuse the already-computed bodyForSearch to avoid a second pass
|
|
1525
|
+
// over the content (the search loop above extracted it iff
|
|
1526
|
+
// include_body=true).
|
|
1527
|
+
...filters?.include_body === true ? { body: bodyForSearch } : {}
|
|
1413
1528
|
});
|
|
1414
1529
|
}
|
|
1415
1530
|
}
|
|
@@ -1419,6 +1534,21 @@ async function searchEntries(projectRoot, query, filters) {
|
|
|
1419
1534
|
async function deferAll(projectRoot, pendingPaths, until, reason) {
|
|
1420
1535
|
const deferred = [];
|
|
1421
1536
|
for (const pendingPath of pendingPaths) {
|
|
1537
|
+
try {
|
|
1538
|
+
const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
|
|
1539
|
+
if (existsSync3(sandboxed.abs)) {
|
|
1540
|
+
const content = await readFile3(sandboxed.abs, "utf8");
|
|
1541
|
+
const patch = {
|
|
1542
|
+
status: "deferred",
|
|
1543
|
+
...until !== void 0 ? { deferred_until: until } : {}
|
|
1544
|
+
};
|
|
1545
|
+
const merged = rewriteFrontmatterMerge(content, patch);
|
|
1546
|
+
if (merged !== content) {
|
|
1547
|
+
await atomicWriteText(sandboxed.abs, merged);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
} catch {
|
|
1551
|
+
}
|
|
1422
1552
|
await emitEventBestEffort2(projectRoot, {
|
|
1423
1553
|
event_type: "knowledge_deferred",
|
|
1424
1554
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1488,6 +1618,14 @@ function parseFrontmatter(content) {
|
|
|
1488
1618
|
case "relevance_paths":
|
|
1489
1619
|
out.relevance_paths = parseFlowArray(value);
|
|
1490
1620
|
break;
|
|
1621
|
+
case "status":
|
|
1622
|
+
if (value === "active" || value === "rejected" || value === "deferred") {
|
|
1623
|
+
out.status = value;
|
|
1624
|
+
}
|
|
1625
|
+
break;
|
|
1626
|
+
case "deferred_until":
|
|
1627
|
+
out.deferred_until = stripQuotes(value);
|
|
1628
|
+
break;
|
|
1491
1629
|
default:
|
|
1492
1630
|
break;
|
|
1493
1631
|
}
|
|
@@ -1549,6 +1687,8 @@ ${content}`;
|
|
|
1549
1687
|
if (patch.tags !== void 0) updates.tags = `tags: [${patch.tags.join(", ")}]`;
|
|
1550
1688
|
if (patch.relevance_scope !== void 0) updates.relevance_scope = `relevance_scope: ${patch.relevance_scope}`;
|
|
1551
1689
|
if (patch.relevance_paths !== void 0) updates.relevance_paths = `relevance_paths: [${patch.relevance_paths.join(", ")}]`;
|
|
1690
|
+
if (patch.status !== void 0) updates.status = `status: ${patch.status}`;
|
|
1691
|
+
if (patch.deferred_until !== void 0) updates.deferred_until = `deferred_until: ${quoteIfNeeded(patch.deferred_until)}`;
|
|
1552
1692
|
const lines = block.split(/\r?\n/u);
|
|
1553
1693
|
const seen = /* @__PURE__ */ new Set();
|
|
1554
1694
|
const newLines = [];
|
|
@@ -1582,6 +1722,8 @@ function appendPatchLines(lines, patch) {
|
|
|
1582
1722
|
if (patch.tags !== void 0) lines.push(`tags: [${patch.tags.join(", ")}]`);
|
|
1583
1723
|
if (patch.relevance_scope !== void 0) lines.push(`relevance_scope: ${patch.relevance_scope}`);
|
|
1584
1724
|
if (patch.relevance_paths !== void 0) lines.push(`relevance_paths: [${patch.relevance_paths.join(", ")}]`);
|
|
1725
|
+
if (patch.status !== void 0) lines.push(`status: ${patch.status}`);
|
|
1726
|
+
if (patch.deferred_until !== void 0) lines.push(`deferred_until: ${quoteIfNeeded(patch.deferred_until)}`);
|
|
1585
1727
|
}
|
|
1586
1728
|
function quoteIfNeeded(value) {
|
|
1587
1729
|
if (/[\n\r]/u.test(value)) {
|
|
@@ -1661,7 +1803,7 @@ var PRIORITY_ORDER = {
|
|
|
1661
1803
|
medium: 1,
|
|
1662
1804
|
low: 2
|
|
1663
1805
|
};
|
|
1664
|
-
function
|
|
1806
|
+
function extractBody2(content) {
|
|
1665
1807
|
const match = /^(?:\uFEFF)?---\r?\n([\s\S]*?)\r?\n---\s*(?:\r?\n|$)/u.exec(content);
|
|
1666
1808
|
if (match === null) {
|
|
1667
1809
|
return content.replace(/^\uFEFF/u, "");
|
|
@@ -1681,7 +1823,7 @@ async function getKnowledgeSections(projectRoot, input) {
|
|
|
1681
1823
|
const rules = [];
|
|
1682
1824
|
for (const rule of selectedRules) {
|
|
1683
1825
|
const content = await readFile4(resolveRuleSourcePath(projectRoot, rule.path), "utf8");
|
|
1684
|
-
const body =
|
|
1826
|
+
const body = extractBody2(content);
|
|
1685
1827
|
const description = rule.node.description;
|
|
1686
1828
|
if (description !== void 0 && description.knowledge_type === void 0 && description.knowledge_layer === void 0) {
|
|
1687
1829
|
diagnostics.push({
|
|
@@ -1894,7 +2036,7 @@ function formatPreexistingRootMessage(projectRoot) {
|
|
|
1894
2036
|
function createFabricServer(tracker) {
|
|
1895
2037
|
const server = new McpServer({
|
|
1896
2038
|
name: "fabric-knowledge-server",
|
|
1897
|
-
version: "2.0.0-rc.
|
|
2039
|
+
version: "2.0.0-rc.28"
|
|
1898
2040
|
});
|
|
1899
2041
|
registerPlanContext(server, tracker);
|
|
1900
2042
|
registerKnowledgeSections(server, tracker);
|
|
@@ -2002,7 +2144,7 @@ function createShutdownHandler(deps) {
|
|
|
2002
2144
|
};
|
|
2003
2145
|
}
|
|
2004
2146
|
async function startHttpServer(options) {
|
|
2005
|
-
const { createFabricHttpApp } = await import("./http-
|
|
2147
|
+
const { createFabricHttpApp } = await import("./http-3IIPL3HQ.js");
|
|
2006
2148
|
const { port, projectRoot, host = "127.0.0.1", authToken } = options;
|
|
2007
2149
|
const app = createFabricHttpApp({ projectRoot, host, authToken });
|
|
2008
2150
|
return await new Promise((resolveServer, rejectServer) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fenglimg/fabric-server",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.28",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"express": "^5.2.1",
|
|
14
14
|
"minimatch": "^10.0.1",
|
|
15
15
|
"zod": "^3.25.0",
|
|
16
|
-
"@fenglimg/fabric-shared": "2.0.0-rc.
|
|
16
|
+
"@fenglimg/fabric-shared": "2.0.0-rc.28"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/express": "^5.0.6",
|