@fenglimg/fabric-server 1.5.1 → 1.5.2
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/dist/{chunk-E3RZ276F.js → chunk-4G6VFG5N.js} +198 -56
- package/dist/{http-BVF4GWIM.js → http-U4S7BUP4.js} +44 -15
- package/dist/index.d.ts +16 -1
- package/dist/index.js +167 -12
- package/dist/static/assets/{index-BRegf31x.js → index-D22ASzsl.js} +7 -7
- package/dist/static/index.html +1 -1
- package/package.json +3 -3
|
@@ -82,23 +82,29 @@ var ContextCache = class {
|
|
|
82
82
|
};
|
|
83
83
|
var contextCache = new ContextCache(5e3);
|
|
84
84
|
|
|
85
|
-
// src/services/read-human-lock.ts
|
|
86
|
-
import { readFile } from "fs/promises";
|
|
87
|
-
import { join } from "path";
|
|
88
|
-
import { humanLockEntrySchema } from "@fenglimg/fabric-shared";
|
|
89
|
-
|
|
90
85
|
// src/services/_shared.ts
|
|
91
|
-
import { resolve, sep } from "path";
|
|
86
|
+
import { dirname, join, resolve, sep } from "path";
|
|
92
87
|
import { createHash } from "crypto";
|
|
93
|
-
import { rename, writeFile } from "fs/promises";
|
|
88
|
+
import { mkdir, rename, writeFile } from "fs/promises";
|
|
94
89
|
var FABRIC_DIR = ".fabric";
|
|
95
90
|
var HUMAN_LOCK_FILE = "human-lock.json";
|
|
96
91
|
var LEDGER_FILE = ".intent-ledger.jsonl";
|
|
92
|
+
var LEDGER_PATH = `${FABRIC_DIR}/${LEDGER_FILE}`;
|
|
93
|
+
var LEGACY_LEDGER_PATH = LEDGER_FILE;
|
|
97
94
|
async function atomicWriteText(path, content) {
|
|
98
95
|
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
99
96
|
await writeFile(tempPath, content, "utf8");
|
|
100
97
|
await rename(tempPath, path);
|
|
101
98
|
}
|
|
99
|
+
function getLedgerPath(projectRoot) {
|
|
100
|
+
return join(projectRoot, LEDGER_PATH);
|
|
101
|
+
}
|
|
102
|
+
function getLegacyLedgerPath(projectRoot) {
|
|
103
|
+
return join(projectRoot, LEGACY_LEDGER_PATH);
|
|
104
|
+
}
|
|
105
|
+
async function ensureParentDirectory(path) {
|
|
106
|
+
await mkdir(dirname(path), { recursive: true });
|
|
107
|
+
}
|
|
102
108
|
function sha256(content) {
|
|
103
109
|
return `sha256:${createHash("sha256").update(content).digest("hex")}`;
|
|
104
110
|
}
|
|
@@ -116,6 +122,9 @@ function assertPathWithinProjectRoot(projectRoot, file) {
|
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
// src/services/read-human-lock.ts
|
|
125
|
+
import { readFile } from "fs/promises";
|
|
126
|
+
import { join as join2 } from "path";
|
|
127
|
+
import { humanLockEntrySchema } from "@fenglimg/fabric-shared";
|
|
119
128
|
async function readHumanLock(projectRoot) {
|
|
120
129
|
const document = await readHumanLockDocument(projectRoot);
|
|
121
130
|
return await Promise.all(
|
|
@@ -134,7 +143,7 @@ async function readHumanLockEntry(projectRoot, file) {
|
|
|
134
143
|
return entries.find((entry) => entry.file === file) ?? null;
|
|
135
144
|
}
|
|
136
145
|
async function readHumanLockDocument(projectRoot) {
|
|
137
|
-
const humanLockPath =
|
|
146
|
+
const humanLockPath = join2(projectRoot, FABRIC_DIR, HUMAN_LOCK_FILE);
|
|
138
147
|
const raw = await readFile(humanLockPath, "utf8");
|
|
139
148
|
const parsed = JSON.parse(raw);
|
|
140
149
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
@@ -177,7 +186,7 @@ import { detectFramework } from "@fenglimg/fabric-shared/node";
|
|
|
177
186
|
|
|
178
187
|
// src/meta-reader.ts
|
|
179
188
|
import { readFile as readFile2 } from "fs/promises";
|
|
180
|
-
import { join as
|
|
189
|
+
import { join as join3 } from "path";
|
|
181
190
|
import { agentsMetaSchema } from "@fenglimg/fabric-shared";
|
|
182
191
|
import { agentsMetaNodeSchema, agentsMetaSchema as agentsMetaSchema2 } from "@fenglimg/fabric-shared";
|
|
183
192
|
var AgentsMetaFileMissingError = class extends Error {
|
|
@@ -200,7 +209,7 @@ var AgentsMetaInvalidError = class extends Error {
|
|
|
200
209
|
code = "FABRIC_META_INVALID";
|
|
201
210
|
};
|
|
202
211
|
function getAgentsMetaPath(projectRoot) {
|
|
203
|
-
return
|
|
212
|
+
return join3(projectRoot, ".fabric", "agents.meta.json");
|
|
204
213
|
}
|
|
205
214
|
function resolveProjectRoot() {
|
|
206
215
|
return process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
|
|
@@ -231,8 +240,8 @@ async function readAgentsMeta(projectRoot) {
|
|
|
231
240
|
}
|
|
232
241
|
|
|
233
242
|
// src/services/audit-log.ts
|
|
234
|
-
import { appendFile, mkdir, open, stat } from "fs/promises";
|
|
235
|
-
import { isAbsolute, join as
|
|
243
|
+
import { appendFile, mkdir as mkdir2, open, stat } from "fs/promises";
|
|
244
|
+
import { isAbsolute, join as join4, posix, relative, resolve as resolve2 } from "path";
|
|
236
245
|
var AUDIT_LOG_FILE = `${FABRIC_DIR}/audit.jsonl`;
|
|
237
246
|
var DEFAULT_AUDIT_WINDOW_MS = 5 * 60 * 1e3;
|
|
238
247
|
async function appendGetRulesAuditEvent(projectRoot, input) {
|
|
@@ -285,7 +294,7 @@ async function readAuditLog(projectRoot, opts) {
|
|
|
285
294
|
return readAuditLogWindowed(projectRoot, opts.ts, opts.windowMs);
|
|
286
295
|
}
|
|
287
296
|
async function readAuditLogFull(projectRoot) {
|
|
288
|
-
const auditPath =
|
|
297
|
+
const auditPath = join4(projectRoot, AUDIT_LOG_FILE);
|
|
289
298
|
let raw;
|
|
290
299
|
try {
|
|
291
300
|
const fileStat = await stat(auditPath);
|
|
@@ -306,7 +315,7 @@ async function readAuditLogFull(projectRoot) {
|
|
|
306
315
|
return parseAuditLogText(raw);
|
|
307
316
|
}
|
|
308
317
|
async function readAuditLogWindowed(projectRoot, ts, windowMs) {
|
|
309
|
-
const auditPath =
|
|
318
|
+
const auditPath = join4(projectRoot, AUDIT_LOG_FILE);
|
|
310
319
|
let fileSize;
|
|
311
320
|
try {
|
|
312
321
|
const fileStat = await stat(auditPath);
|
|
@@ -394,9 +403,9 @@ function isGetRulesAuditEntry(entry) {
|
|
|
394
403
|
return entry.event === "get_rules";
|
|
395
404
|
}
|
|
396
405
|
async function appendAuditLogEntries(projectRoot, entries) {
|
|
397
|
-
const auditPath =
|
|
398
|
-
const auditDir =
|
|
399
|
-
await
|
|
406
|
+
const auditPath = join4(projectRoot, AUDIT_LOG_FILE);
|
|
407
|
+
const auditDir = join4(projectRoot, FABRIC_DIR);
|
|
408
|
+
await mkdir2(auditDir, { recursive: true });
|
|
400
409
|
await appendFile(auditPath, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}
|
|
401
410
|
`, "utf8");
|
|
402
411
|
}
|
|
@@ -436,14 +445,27 @@ function parseAuditLogLine(line) {
|
|
|
436
445
|
|
|
437
446
|
// src/services/read-ledger.ts
|
|
438
447
|
import { randomUUID } from "crypto";
|
|
439
|
-
import { appendFile as appendFile2, readFile as readFile3 } from "fs/promises";
|
|
440
|
-
import { join as join4 } from "path";
|
|
448
|
+
import { access, appendFile as appendFile2, copyFile, readFile as readFile3, rm } from "fs/promises";
|
|
441
449
|
import { ledgerEntrySchema } from "@fenglimg/fabric-shared";
|
|
450
|
+
async function resolveLedgerPaths(projectRoot) {
|
|
451
|
+
const primaryPath = getLedgerPath(projectRoot);
|
|
452
|
+
const legacyPath = getLegacyLedgerPath(projectRoot);
|
|
453
|
+
const [primaryExists, legacyExists] = await Promise.all([
|
|
454
|
+
pathExists(primaryPath),
|
|
455
|
+
pathExists(legacyPath)
|
|
456
|
+
]);
|
|
457
|
+
return {
|
|
458
|
+
primaryPath,
|
|
459
|
+
legacyPath,
|
|
460
|
+
readPath: primaryExists ? primaryPath : legacyPath,
|
|
461
|
+
usingLegacy: !primaryExists && legacyExists
|
|
462
|
+
};
|
|
463
|
+
}
|
|
442
464
|
async function readLedger(projectRoot, options = {}) {
|
|
443
|
-
const
|
|
465
|
+
const { readPath } = await resolveLedgerPaths(projectRoot);
|
|
444
466
|
let raw;
|
|
445
467
|
try {
|
|
446
|
-
raw = await readFile3(
|
|
468
|
+
raw = await readFile3(readPath, "utf8");
|
|
447
469
|
} catch (error) {
|
|
448
470
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
449
471
|
return [];
|
|
@@ -453,15 +475,40 @@ async function readLedger(projectRoot, options = {}) {
|
|
|
453
475
|
return raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).map((line, index) => parseLedgerLine(line, index)).filter((entry) => entry !== null).filter((entry) => options.source === void 0 || entry.source === options.source).filter((entry) => options.since === void 0 || entry.ts >= options.since);
|
|
454
476
|
}
|
|
455
477
|
async function appendLedgerEntry(projectRoot, entry) {
|
|
456
|
-
const ledgerPath =
|
|
478
|
+
const ledgerPath = getLedgerPath(projectRoot);
|
|
457
479
|
const nextEntry = ledgerEntrySchema.parse({
|
|
458
480
|
...entry,
|
|
459
481
|
id: entry.id ?? `ledger:${randomUUID()}`
|
|
460
482
|
});
|
|
483
|
+
await ensureParentDirectory(ledgerPath);
|
|
461
484
|
await appendFile2(ledgerPath, `${JSON.stringify(nextEntry)}
|
|
462
485
|
`, "utf8");
|
|
463
486
|
return nextEntry;
|
|
464
487
|
}
|
|
488
|
+
async function migrateLegacyLedger(projectRoot) {
|
|
489
|
+
const { primaryPath, legacyPath } = await resolveLedgerPaths(projectRoot);
|
|
490
|
+
const [primaryExists, legacyExists] = await Promise.all([
|
|
491
|
+
pathExists(primaryPath),
|
|
492
|
+
pathExists(legacyPath)
|
|
493
|
+
]);
|
|
494
|
+
if (!legacyExists) {
|
|
495
|
+
return {
|
|
496
|
+
migrated: false,
|
|
497
|
+
from: null,
|
|
498
|
+
to: primaryPath
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
if (!primaryExists) {
|
|
502
|
+
await ensureParentDirectory(primaryPath);
|
|
503
|
+
await copyFile(legacyPath, primaryPath);
|
|
504
|
+
}
|
|
505
|
+
await rm(legacyPath, { force: true });
|
|
506
|
+
return {
|
|
507
|
+
migrated: true,
|
|
508
|
+
from: legacyPath,
|
|
509
|
+
to: primaryPath
|
|
510
|
+
};
|
|
511
|
+
}
|
|
465
512
|
function parseLedgerLine(line, index) {
|
|
466
513
|
try {
|
|
467
514
|
const parsed = JSON.parse(line);
|
|
@@ -483,6 +530,17 @@ function parseLedgerLine(line, index) {
|
|
|
483
530
|
function createDerivedId(index, line) {
|
|
484
531
|
return `ledger:${index + 1}:${sha256(line).slice("sha256:".length)}`;
|
|
485
532
|
}
|
|
533
|
+
async function pathExists(path) {
|
|
534
|
+
try {
|
|
535
|
+
await access(path);
|
|
536
|
+
return true;
|
|
537
|
+
} catch (error) {
|
|
538
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
throw error;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
486
544
|
|
|
487
545
|
// src/services/doctor.ts
|
|
488
546
|
var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx"]);
|
|
@@ -538,6 +596,9 @@ async function runDoctorReport(target) {
|
|
|
538
596
|
lastLedgerEntryTs: ledgerSnapshot.lastEntryTs,
|
|
539
597
|
lastLedgerEntryAgeMs: ledgerSnapshot.lastEntryAgeMs,
|
|
540
598
|
metaRevision: metaSnapshot.revision,
|
|
599
|
+
ledgerPath: ledgerSnapshot.primaryPath,
|
|
600
|
+
legacyLedgerPath: ledgerSnapshot.legacyPath,
|
|
601
|
+
legacyLedgerDetected: ledgerSnapshot.usingLegacy,
|
|
541
602
|
audit: auditReport.skipped ? null : {
|
|
542
603
|
enabled: true,
|
|
543
604
|
mode: auditReport.mode,
|
|
@@ -549,6 +610,17 @@ async function runDoctorReport(target) {
|
|
|
549
610
|
audit: auditReport.skipped ? null : auditReport
|
|
550
611
|
};
|
|
551
612
|
}
|
|
613
|
+
async function runDoctorFix(target) {
|
|
614
|
+
const projectRoot = normalizeTarget(target);
|
|
615
|
+
const migration = await migrateLegacyLedger(projectRoot);
|
|
616
|
+
const report = await runDoctorReport(projectRoot);
|
|
617
|
+
return {
|
|
618
|
+
changed: migration.migrated,
|
|
619
|
+
migratedLedger: migration.migrated,
|
|
620
|
+
message: migration.migrated ? `Migrated legacy ledger from ${migration.from} to ${migration.to}.` : `No legacy ledger migration needed. Canonical ledger path: ${migration.to}.`,
|
|
621
|
+
report
|
|
622
|
+
};
|
|
623
|
+
}
|
|
552
624
|
async function runDoctorAuditReport(target, options = {}) {
|
|
553
625
|
const projectRoot = normalizeTarget(target);
|
|
554
626
|
const mode = options.mode ?? readDoctorAuditMode(projectRoot);
|
|
@@ -647,6 +719,15 @@ function createMetaRevisionCheck(snapshot) {
|
|
|
647
719
|
message: `agents.meta.json revision ${snapshot.revision} is stale: ${parts.join(" \xB7 ")}.`
|
|
648
720
|
};
|
|
649
721
|
}
|
|
722
|
+
if (snapshot.derivedIdentityFiles.length > 0) {
|
|
723
|
+
const [firstFile] = snapshot.derivedIdentityFiles;
|
|
724
|
+
const suffix = snapshot.derivedIdentityFiles.length > 1 ? ` (+${snapshot.derivedIdentityFiles.length - 1} more)` : "";
|
|
725
|
+
return {
|
|
726
|
+
name: "Meta revision",
|
|
727
|
+
status: "warn",
|
|
728
|
+
message: `agents.meta.json revision ${snapshot.revision} matches ${snapshot.nodeCount} tracked AGENTS files, but ${snapshot.derivedIdentityFiles.length} rule node${snapshot.derivedIdentityFiles.length === 1 ? "" : "s"} still use derived identities. Add \`<!-- fab:rule-id ... -->\` to the rule file header instead of editing meta directly (${firstFile}${suffix}).`
|
|
729
|
+
};
|
|
730
|
+
}
|
|
650
731
|
return {
|
|
651
732
|
name: "Meta revision",
|
|
652
733
|
status: "ok",
|
|
@@ -675,6 +756,13 @@ function createProtectedPathsCheck(snapshot) {
|
|
|
675
756
|
};
|
|
676
757
|
}
|
|
677
758
|
function createLedgerCheck(snapshot) {
|
|
759
|
+
if (snapshot.usingLegacy) {
|
|
760
|
+
return {
|
|
761
|
+
name: "Intent ledger",
|
|
762
|
+
status: "warn",
|
|
763
|
+
message: `Legacy ledger path detected at ${snapshot.legacyPath}. Fabric now reads ${snapshot.primaryPath} by default; run fab doctor --fix to migrate.`
|
|
764
|
+
};
|
|
765
|
+
}
|
|
678
766
|
if (snapshot.lastEntryTs === null || snapshot.lastEntryAgeMs === null) {
|
|
679
767
|
return {
|
|
680
768
|
name: "Intent ledger",
|
|
@@ -756,8 +844,9 @@ async function inspectMetaRevision(projectRoot) {
|
|
|
756
844
|
const meta = await readAgentsMeta(projectRoot);
|
|
757
845
|
const entries = Object.entries(meta.nodes).sort(([left], [right]) => left.localeCompare(right));
|
|
758
846
|
const missingFiles = [];
|
|
847
|
+
const derivedIdentityFiles = [];
|
|
759
848
|
let driftCount = 0;
|
|
760
|
-
const revisionSource = entries.map(([, node]) => {
|
|
849
|
+
const revisionSource = entries.map(([id, node]) => {
|
|
761
850
|
const absolutePath = join5(projectRoot, node.file);
|
|
762
851
|
if (!existsSync(absolutePath)) {
|
|
763
852
|
missingFiles.push(node.file);
|
|
@@ -768,15 +857,19 @@ async function inspectMetaRevision(projectRoot) {
|
|
|
768
857
|
if (actualHash !== node.hash) {
|
|
769
858
|
driftCount += 1;
|
|
770
859
|
}
|
|
771
|
-
|
|
772
|
-
|
|
860
|
+
if (node.file !== ".fabric/bootstrap/README.md" && node.identity_source !== "declared") {
|
|
861
|
+
derivedIdentityFiles.push(node.file);
|
|
862
|
+
}
|
|
863
|
+
return [id, actualHash, node.stable_id ?? "", node.identity_source ?? ""].join("|");
|
|
864
|
+
}).join("\n");
|
|
773
865
|
const revision = sha2562(revisionSource);
|
|
774
866
|
return {
|
|
775
867
|
present: true,
|
|
776
868
|
revision: meta.revision,
|
|
777
869
|
nodeCount: entries.length,
|
|
778
870
|
driftCount: revision === meta.revision ? driftCount : Math.max(driftCount, 1),
|
|
779
|
-
missingFiles
|
|
871
|
+
missingFiles,
|
|
872
|
+
derivedIdentityFiles
|
|
780
873
|
};
|
|
781
874
|
} catch (error) {
|
|
782
875
|
return {
|
|
@@ -785,6 +878,7 @@ async function inspectMetaRevision(projectRoot) {
|
|
|
785
878
|
nodeCount: 0,
|
|
786
879
|
driftCount: 0,
|
|
787
880
|
missingFiles: [],
|
|
881
|
+
derivedIdentityFiles: [],
|
|
788
882
|
unexpectedError: error instanceof Error ? error.message : String(error)
|
|
789
883
|
};
|
|
790
884
|
}
|
|
@@ -815,6 +909,7 @@ async function inspectHumanLock(projectRoot) {
|
|
|
815
909
|
}
|
|
816
910
|
}
|
|
817
911
|
async function inspectLedger(projectRoot) {
|
|
912
|
+
const paths = await resolveLedgerPaths(projectRoot);
|
|
818
913
|
const entries = await readLedger(projectRoot);
|
|
819
914
|
const lastEntry = entries.reduce(
|
|
820
915
|
(latest, entry) => latest === null || entry.ts > latest ? entry.ts : latest,
|
|
@@ -823,7 +918,10 @@ async function inspectLedger(projectRoot) {
|
|
|
823
918
|
return {
|
|
824
919
|
count: entries.length,
|
|
825
920
|
lastEntryTs: lastEntry,
|
|
826
|
-
lastEntryAgeMs: lastEntry === null ? null : Math.max(Date.now() - lastEntry, 0)
|
|
921
|
+
lastEntryAgeMs: lastEntry === null ? null : Math.max(Date.now() - lastEntry, 0),
|
|
922
|
+
primaryPath: paths.primaryPath,
|
|
923
|
+
legacyPath: paths.legacyPath,
|
|
924
|
+
usingLegacy: paths.usingLegacy
|
|
827
925
|
};
|
|
828
926
|
}
|
|
829
927
|
function collectAuditViolations(projectRoot, ledgerEntries, getRulesEntries, windowMs) {
|
|
@@ -1106,56 +1204,76 @@ async function loadGetRulesContext(projectRoot) {
|
|
|
1106
1204
|
return context;
|
|
1107
1205
|
}
|
|
1108
1206
|
async function resolveRulesForPath(projectRoot, context, path, options = {}) {
|
|
1109
|
-
const
|
|
1110
|
-
const
|
|
1111
|
-
return
|
|
1112
|
-
L0: context.l0Content,
|
|
1113
|
-
L1,
|
|
1114
|
-
L2,
|
|
1115
|
-
human_locked_nearby: context.humanLockedNearby,
|
|
1116
|
-
description_stubs: stubs.length > 0 ? dedupeDescriptionStubsByPath(stubs) : void 0
|
|
1117
|
-
};
|
|
1207
|
+
const matchedNodes = matchRuleNodes(context.meta, path);
|
|
1208
|
+
const loaded = await loadMatchedRules(projectRoot, matchedNodes);
|
|
1209
|
+
return buildRulesPayload(context, loaded, options);
|
|
1118
1210
|
}
|
|
1119
1211
|
function normalizeRulesPath(value) {
|
|
1120
1212
|
return value.replaceAll("\\", "/");
|
|
1121
1213
|
}
|
|
1122
|
-
function
|
|
1123
|
-
if (nodeId.startsWith("L1/")) {
|
|
1124
|
-
return "L1";
|
|
1125
|
-
}
|
|
1126
|
-
if (nodeId.startsWith("L2/")) {
|
|
1127
|
-
return "L2";
|
|
1128
|
-
}
|
|
1129
|
-
return null;
|
|
1130
|
-
}
|
|
1131
|
-
async function loadRulesForPath(projectRoot, meta, path) {
|
|
1214
|
+
function matchRuleNodes(meta, path) {
|
|
1132
1215
|
const requestedPath = normalizeRulesPath(path);
|
|
1133
|
-
|
|
1216
|
+
return Object.entries(meta.nodes).filter(([, node]) => shouldLoadNodeForPath(requestedPath, node)).sort((left, right) => {
|
|
1134
1217
|
const [leftId, leftNode] = left;
|
|
1135
1218
|
const [rightId, rightNode] = right;
|
|
1136
1219
|
const priorityDelta = PRIORITY_ORDER[leftNode.priority] - PRIORITY_ORDER[rightNode.priority];
|
|
1137
1220
|
return priorityDelta !== 0 ? priorityDelta : leftId.localeCompare(rightId);
|
|
1138
|
-
})
|
|
1221
|
+
}).map(([nodeId, node]) => ({
|
|
1222
|
+
node_id: nodeId,
|
|
1223
|
+
level: classifyNode(nodeId, node),
|
|
1224
|
+
stable_id: node.stable_id ?? nodeId,
|
|
1225
|
+
identity_source: node.identity_source ?? "derived",
|
|
1226
|
+
node
|
|
1227
|
+
}));
|
|
1228
|
+
}
|
|
1229
|
+
async function loadMatchedRules(projectRoot, matchedNodes, fileContentCache = /* @__PURE__ */ new Map()) {
|
|
1139
1230
|
const rules = [];
|
|
1140
1231
|
const stubs = [];
|
|
1141
|
-
for (const
|
|
1142
|
-
if (
|
|
1232
|
+
for (const matchedNode of matchedNodes) {
|
|
1233
|
+
if (matchedNode.level === null) {
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
if (matchedNode.node.activation?.tier === "description") {
|
|
1143
1237
|
stubs.push({
|
|
1144
|
-
|
|
1145
|
-
|
|
1238
|
+
stable_id: matchedNode.stable_id,
|
|
1239
|
+
identity_source: matchedNode.identity_source,
|
|
1240
|
+
level: matchedNode.level,
|
|
1241
|
+
path: matchedNode.node.file,
|
|
1242
|
+
description: matchedNode.node.activation.description ?? ""
|
|
1146
1243
|
});
|
|
1147
1244
|
continue;
|
|
1148
1245
|
}
|
|
1149
1246
|
rules.push({
|
|
1150
|
-
level:
|
|
1247
|
+
level: matchedNode.level,
|
|
1248
|
+
stable_id: matchedNode.stable_id,
|
|
1249
|
+
identity_source: matchedNode.identity_source,
|
|
1151
1250
|
entry: {
|
|
1152
|
-
path: node.file,
|
|
1153
|
-
content: await
|
|
1251
|
+
path: matchedNode.node.file,
|
|
1252
|
+
content: await readRuleContent(projectRoot, matchedNode.node.file, fileContentCache)
|
|
1154
1253
|
}
|
|
1155
1254
|
});
|
|
1156
1255
|
}
|
|
1157
1256
|
return { rules, stubs };
|
|
1158
1257
|
}
|
|
1258
|
+
function buildRulesPayload(context, loaded, options = {}) {
|
|
1259
|
+
const { L1, L2 } = partitionRulesByLevel(loaded.rules, options.dedupeByPath ?? false);
|
|
1260
|
+
return {
|
|
1261
|
+
L0: context.l0Content,
|
|
1262
|
+
L1,
|
|
1263
|
+
L2,
|
|
1264
|
+
human_locked_nearby: context.humanLockedNearby,
|
|
1265
|
+
description_stubs: loaded.stubs.length > 0 ? dedupeDescriptionStubsByPath(loaded.stubs).map(toDescriptionStub) : void 0
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
function classifyNode(nodeId, node) {
|
|
1269
|
+
if (nodeId.startsWith("L1/")) {
|
|
1270
|
+
return "L1";
|
|
1271
|
+
}
|
|
1272
|
+
if (nodeId.startsWith("L2/")) {
|
|
1273
|
+
return "L2";
|
|
1274
|
+
}
|
|
1275
|
+
return node.layer === "L0" ? null : node.layer;
|
|
1276
|
+
}
|
|
1159
1277
|
function partitionRulesByLevel(loadedRules, dedupeByPath) {
|
|
1160
1278
|
const l1 = [];
|
|
1161
1279
|
const l2 = [];
|
|
@@ -1204,6 +1322,21 @@ function dedupeDescriptionStubsByPath(stubs) {
|
|
|
1204
1322
|
return true;
|
|
1205
1323
|
});
|
|
1206
1324
|
}
|
|
1325
|
+
function toDescriptionStub(stub) {
|
|
1326
|
+
return {
|
|
1327
|
+
path: stub.path,
|
|
1328
|
+
description: stub.description
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
async function readRuleContent(projectRoot, file, fileContentCache) {
|
|
1332
|
+
const cached = fileContentCache.get(file);
|
|
1333
|
+
if (cached !== void 0) {
|
|
1334
|
+
return await cached;
|
|
1335
|
+
}
|
|
1336
|
+
const pending = readFile5(join6(projectRoot, file), "utf8");
|
|
1337
|
+
fileContentCache.set(file, pending);
|
|
1338
|
+
return await pending;
|
|
1339
|
+
}
|
|
1207
1340
|
|
|
1208
1341
|
export {
|
|
1209
1342
|
AGENTS_MD_RESOURCE_URI,
|
|
@@ -1213,18 +1346,27 @@ export {
|
|
|
1213
1346
|
resolveProjectRoot,
|
|
1214
1347
|
readAgentsMeta,
|
|
1215
1348
|
FABRIC_DIR,
|
|
1349
|
+
LEDGER_PATH,
|
|
1350
|
+
LEGACY_LEDGER_PATH,
|
|
1216
1351
|
atomicWriteText,
|
|
1352
|
+
getLedgerPath,
|
|
1353
|
+
getLegacyLedgerPath,
|
|
1354
|
+
ensureParentDirectory,
|
|
1217
1355
|
sha256,
|
|
1218
1356
|
appendEditIntentAuditEvents,
|
|
1357
|
+
resolveLedgerPaths,
|
|
1219
1358
|
readLedger,
|
|
1220
1359
|
appendLedgerEntry,
|
|
1221
1360
|
readHumanLock,
|
|
1222
1361
|
readHumanLockEntry,
|
|
1223
1362
|
getRules,
|
|
1224
1363
|
loadGetRulesContext,
|
|
1225
|
-
resolveRulesForPath,
|
|
1226
1364
|
normalizeRulesPath,
|
|
1365
|
+
matchRuleNodes,
|
|
1366
|
+
loadMatchedRules,
|
|
1367
|
+
buildRulesPayload,
|
|
1227
1368
|
runDoctorReport,
|
|
1369
|
+
runDoctorFix,
|
|
1228
1370
|
runDoctorAuditReport,
|
|
1229
1371
|
approveHumanLock
|
|
1230
1372
|
};
|
|
@@ -2,21 +2,26 @@ import {
|
|
|
2
2
|
AGENTS_MD_RESOURCE_URI,
|
|
3
3
|
AgentsMetaFileMissingError,
|
|
4
4
|
AgentsMetaInvalidError,
|
|
5
|
+
LEDGER_PATH,
|
|
6
|
+
LEGACY_LEDGER_PATH,
|
|
5
7
|
appendLedgerEntry,
|
|
6
8
|
approveHumanLock,
|
|
7
9
|
contextCache,
|
|
10
|
+
ensureParentDirectory,
|
|
11
|
+
getLedgerPath,
|
|
12
|
+
getLegacyLedgerPath,
|
|
8
13
|
getRules,
|
|
9
14
|
readAgentsMeta,
|
|
10
15
|
readHumanLock,
|
|
11
16
|
readHumanLockEntry,
|
|
12
17
|
readLedger,
|
|
18
|
+
resolveLedgerPaths,
|
|
13
19
|
runDoctorReport
|
|
14
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-4G6VFG5N.js";
|
|
15
21
|
|
|
16
22
|
// src/http.ts
|
|
17
23
|
import { randomUUID } from "crypto";
|
|
18
24
|
import { appendFile, readFile as readFile2 } from "fs/promises";
|
|
19
|
-
import { join as join3 } from "path";
|
|
20
25
|
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
21
26
|
import {
|
|
22
27
|
StreamableHTTPServerTransport
|
|
@@ -126,8 +131,7 @@ import chokidar from "chokidar";
|
|
|
126
131
|
var AGENTS_META_PATH = ".fabric/agents.meta.json";
|
|
127
132
|
var HUMAN_LOCK_PATH = ".fabric/human-lock.json";
|
|
128
133
|
var FORENSIC_PATH = ".fabric/forensic.json";
|
|
129
|
-
var
|
|
130
|
-
var WATCHED_PATHS = [AGENTS_META_PATH, HUMAN_LOCK_PATH, FORENSIC_PATH, LEDGER_PATH];
|
|
134
|
+
var WATCHED_PATHS = [AGENTS_META_PATH, HUMAN_LOCK_PATH, FORENSIC_PATH, LEDGER_PATH, LEGACY_LEDGER_PATH];
|
|
131
135
|
var CONNECTION_LIMIT = 10;
|
|
132
136
|
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
133
137
|
var WATCH_DEBOUNCE_MS = 75;
|
|
@@ -166,6 +170,7 @@ function createEventsHandler(options) {
|
|
|
166
170
|
const state = {
|
|
167
171
|
clients: /* @__PURE__ */ new Set(),
|
|
168
172
|
pendingTimers: /* @__PURE__ */ new Map(),
|
|
173
|
+
activeLedgerPath: getLedgerPath(projectRoot),
|
|
169
174
|
ledgerOffset: 0,
|
|
170
175
|
ledgerRemainder: "",
|
|
171
176
|
humanLockSnapshot: createEmptyHumanLockSnapshot(),
|
|
@@ -243,10 +248,12 @@ async function ensureWatcher(state, projectRoot) {
|
|
|
243
248
|
if (state.watcher !== void 0) {
|
|
244
249
|
return;
|
|
245
250
|
}
|
|
246
|
-
|
|
251
|
+
const ledgerState = await resolveLedgerWatchState(projectRoot);
|
|
252
|
+
state.activeLedgerPath = ledgerState.path;
|
|
253
|
+
state.ledgerOffset = ledgerState.size;
|
|
247
254
|
state.ledgerRemainder = "";
|
|
248
255
|
state.humanLockSnapshot = await readHumanLockSnapshot(projectRoot);
|
|
249
|
-
const watcher = chokidar.watch(
|
|
256
|
+
const watcher = chokidar.watch(WATCHED_PATHS, {
|
|
250
257
|
cwd: projectRoot,
|
|
251
258
|
ignoreInitial: true,
|
|
252
259
|
awaitWriteFinish: {
|
|
@@ -306,7 +313,7 @@ async function readEventsForFile(state, projectRoot, relativePath) {
|
|
|
306
313
|
const event = await readDriftDetectedEvent(projectRoot);
|
|
307
314
|
return event === null ? [] : [event];
|
|
308
315
|
}
|
|
309
|
-
if (relativePath === LEDGER_PATH) {
|
|
316
|
+
if (relativePath === LEDGER_PATH || relativePath === LEGACY_LEDGER_PATH) {
|
|
310
317
|
return await readLedgerAppendedEvents(state, projectRoot);
|
|
311
318
|
}
|
|
312
319
|
return [];
|
|
@@ -370,8 +377,14 @@ async function readHumanLockEvents(state, projectRoot) {
|
|
|
370
377
|
return events;
|
|
371
378
|
}
|
|
372
379
|
async function readLedgerAppendedEvents(state, projectRoot) {
|
|
373
|
-
const
|
|
374
|
-
const
|
|
380
|
+
const ledgerState = await resolveLedgerWatchState(projectRoot);
|
|
381
|
+
const ledgerPath = ledgerState.path;
|
|
382
|
+
const nextSize = ledgerState.size;
|
|
383
|
+
if (ledgerPath !== state.activeLedgerPath) {
|
|
384
|
+
state.activeLedgerPath = ledgerPath;
|
|
385
|
+
state.ledgerOffset = 0;
|
|
386
|
+
state.ledgerRemainder = "";
|
|
387
|
+
}
|
|
375
388
|
if (nextSize < state.ledgerOffset) {
|
|
376
389
|
state.ledgerOffset = 0;
|
|
377
390
|
state.ledgerRemainder = "";
|
|
@@ -394,6 +407,12 @@ async function readLedgerAppendedEvents(state, projectRoot) {
|
|
|
394
407
|
await handle.close();
|
|
395
408
|
}
|
|
396
409
|
}
|
|
410
|
+
async function resolveLedgerWatchState(projectRoot) {
|
|
411
|
+
const paths = await resolveLedgerPaths(projectRoot);
|
|
412
|
+
const path = paths.usingLegacy ? paths.legacyPath : paths.primaryPath;
|
|
413
|
+
const size = await readFileSize(path);
|
|
414
|
+
return { path, size };
|
|
415
|
+
}
|
|
397
416
|
function parseLedgerAppendedEvent(line) {
|
|
398
417
|
try {
|
|
399
418
|
const parsed = JSON.parse(line);
|
|
@@ -1084,12 +1103,13 @@ function hashToken(token) {
|
|
|
1084
1103
|
|
|
1085
1104
|
// src/http.ts
|
|
1086
1105
|
var DEFAULT_HOST = "127.0.0.1";
|
|
1087
|
-
var LEDGER_FILE = ".intent-ledger.jsonl";
|
|
1088
1106
|
var NOTIFY_DEBOUNCE_MS = 200;
|
|
1089
1107
|
var JsonlEventStore = class {
|
|
1090
|
-
constructor(ledgerPath) {
|
|
1108
|
+
constructor(projectRoot, ledgerPath) {
|
|
1109
|
+
this.projectRoot = projectRoot;
|
|
1091
1110
|
this.ledgerPath = ledgerPath;
|
|
1092
1111
|
}
|
|
1112
|
+
projectRoot;
|
|
1093
1113
|
ledgerPath;
|
|
1094
1114
|
async storeEvent(streamId, message) {
|
|
1095
1115
|
const eventId = randomUUID();
|
|
@@ -1099,6 +1119,7 @@ var JsonlEventStore = class {
|
|
|
1099
1119
|
streamId,
|
|
1100
1120
|
message
|
|
1101
1121
|
};
|
|
1122
|
+
await ensureParentDirectory(this.ledgerPath);
|
|
1102
1123
|
await appendFile(this.ledgerPath, `${JSON.stringify(entry)}
|
|
1103
1124
|
`, "utf8");
|
|
1104
1125
|
return eventId;
|
|
@@ -1131,9 +1152,17 @@ var JsonlEventStore = class {
|
|
|
1131
1152
|
raw = await readFile2(this.ledgerPath, "utf8");
|
|
1132
1153
|
} catch (error) {
|
|
1133
1154
|
if (isNodeError2(error) && error.code === "ENOENT") {
|
|
1134
|
-
|
|
1155
|
+
try {
|
|
1156
|
+
raw = await readFile2(getLegacyLedgerPath(this.projectRoot), "utf8");
|
|
1157
|
+
} catch (legacyError) {
|
|
1158
|
+
if (isNodeError2(legacyError) && legacyError.code === "ENOENT") {
|
|
1159
|
+
return [];
|
|
1160
|
+
}
|
|
1161
|
+
throw legacyError;
|
|
1162
|
+
}
|
|
1163
|
+
} else {
|
|
1164
|
+
throw error;
|
|
1135
1165
|
}
|
|
1136
|
-
throw error;
|
|
1137
1166
|
}
|
|
1138
1167
|
return raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => parseStoredMcpEvent(line)).filter((event) => event !== null);
|
|
1139
1168
|
}
|
|
@@ -1141,8 +1170,8 @@ var JsonlEventStore = class {
|
|
|
1141
1170
|
function createFabricHttpApp(options) {
|
|
1142
1171
|
const { projectRoot, host = DEFAULT_HOST, authToken, dashboardDistPath, dev } = options;
|
|
1143
1172
|
const app = createMcpExpressApp({ host });
|
|
1144
|
-
const ledgerPath =
|
|
1145
|
-
const eventStore = new JsonlEventStore(ledgerPath);
|
|
1173
|
+
const ledgerPath = getLedgerPath(projectRoot);
|
|
1174
|
+
const eventStore = new JsonlEventStore(projectRoot, ledgerPath);
|
|
1146
1175
|
const sessions = /* @__PURE__ */ new Map();
|
|
1147
1176
|
process.env.FABRIC_PROJECT_ROOT = projectRoot;
|
|
1148
1177
|
const cacheWatcher = chokidar2.watch(
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { Server } from 'node:http';
|
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { AuditMode, LedgerEntry, HumanLockEntry } from '@fenglimg/fabric-shared';
|
|
4
4
|
|
|
5
|
+
declare const LEDGER_PATH = ".fabric/.intent-ledger.jsonl";
|
|
6
|
+
declare const LEGACY_LEDGER_PATH = ".intent-ledger.jsonl";
|
|
7
|
+
declare function getLedgerPath(projectRoot: string): string;
|
|
8
|
+
declare function getLegacyLedgerPath(projectRoot: string): string;
|
|
9
|
+
|
|
5
10
|
type DoctorStatus = "ok" | "warn" | "error";
|
|
6
11
|
type DoctorCheck = {
|
|
7
12
|
name: string;
|
|
@@ -25,6 +30,9 @@ type DoctorSummary = {
|
|
|
25
30
|
lastLedgerEntryTs: number | null;
|
|
26
31
|
lastLedgerEntryAgeMs: number | null;
|
|
27
32
|
metaRevision: string | null;
|
|
33
|
+
ledgerPath: string;
|
|
34
|
+
legacyLedgerPath: string;
|
|
35
|
+
legacyLedgerDetected: boolean;
|
|
28
36
|
audit: {
|
|
29
37
|
enabled: boolean;
|
|
30
38
|
mode: AuditMode;
|
|
@@ -39,6 +47,12 @@ type DoctorReport = {
|
|
|
39
47
|
summary: DoctorSummary;
|
|
40
48
|
audit: DoctorAuditReport | null;
|
|
41
49
|
};
|
|
50
|
+
type DoctorFixReport = {
|
|
51
|
+
changed: boolean;
|
|
52
|
+
migratedLedger: boolean;
|
|
53
|
+
message: string;
|
|
54
|
+
report: DoctorReport;
|
|
55
|
+
};
|
|
42
56
|
type DoctorAuditViolation = {
|
|
43
57
|
editTs: number;
|
|
44
58
|
entryId: string;
|
|
@@ -55,6 +69,7 @@ type DoctorAuditReport = {
|
|
|
55
69
|
violations: DoctorAuditViolation[];
|
|
56
70
|
};
|
|
57
71
|
declare function runDoctorReport(target: string): Promise<DoctorReport>;
|
|
72
|
+
declare function runDoctorFix(target: string): Promise<DoctorFixReport>;
|
|
58
73
|
declare function runDoctorAuditReport(target: string, options?: {
|
|
59
74
|
force?: boolean;
|
|
60
75
|
mode?: AuditMode;
|
|
@@ -102,4 +117,4 @@ declare function startHttpServer(options: {
|
|
|
102
117
|
dev?: boolean;
|
|
103
118
|
}): Promise<Server>;
|
|
104
119
|
|
|
105
|
-
export { AGENTS_MD_RESOURCE_URI, type ApproveHumanLockInput, type ApproveHumanLockResult, type DoctorAuditReport, type DoctorReport, type HumanLockStatus, approveHumanLock, createFabricServer, readHumanLock, readHumanLockEntry, runDoctorAuditReport, runDoctorReport, startHttpServer, startStdioServer };
|
|
120
|
+
export { AGENTS_MD_RESOURCE_URI, type ApproveHumanLockInput, type ApproveHumanLockResult, type DoctorAuditReport, type DoctorFixReport, type DoctorReport, type HumanLockStatus, LEDGER_PATH, LEGACY_LEDGER_PATH, approveHumanLock, createFabricServer, getLedgerPath, getLegacyLedgerPath, readHumanLock, readHumanLockEntry, runDoctorAuditReport, runDoctorFix, runDoctorReport, startHttpServer, startStdioServer };
|