facult 1.0.3 → 1.2.0
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 +491 -15
- package/package.json +1 -1
- package/src/adapters/codex.ts +1 -0
- package/src/adapters/types.ts +1 -0
- package/src/agents.ts +205 -0
- package/src/ai-state.ts +80 -0
- package/src/ai.ts +1763 -0
- package/src/audit/update-index.ts +13 -10
- package/src/autosync.ts +1028 -0
- package/src/builtin.ts +61 -0
- package/src/cli-context.ts +198 -0
- package/src/doctor.ts +128 -0
- package/src/enable-disable.ts +13 -7
- package/src/global-docs.ts +505 -0
- package/src/graph-query.ts +175 -0
- package/src/graph.ts +119 -0
- package/src/index-builder.ts +1104 -44
- package/src/index.ts +458 -24
- package/src/manage.ts +2482 -215
- package/src/paths.ts +181 -17
- package/src/query.ts +147 -7
- package/src/remote.ts +145 -10
- package/src/snippets.ts +106 -0
- package/src/trust-list.ts +1 -0
- package/src/trust.ts +13 -11
package/src/snippets.ts
CHANGED
|
@@ -230,6 +230,12 @@ export interface SyncResult {
|
|
|
230
230
|
errors: string[];
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
export interface RenderSnippetTextResult {
|
|
234
|
+
text: string;
|
|
235
|
+
changes: SyncChange[];
|
|
236
|
+
errors: string[];
|
|
237
|
+
}
|
|
238
|
+
|
|
233
239
|
function isSafePathString(p: string): boolean {
|
|
234
240
|
// Protect filesystem APIs from null-byte paths.
|
|
235
241
|
return !p.includes("\0");
|
|
@@ -564,6 +570,106 @@ export async function syncFile(args: {
|
|
|
564
570
|
return { filePath, dryRun, changed: true, changes, errors: [] };
|
|
565
571
|
}
|
|
566
572
|
|
|
573
|
+
export async function renderSnippetText(args: {
|
|
574
|
+
text: string;
|
|
575
|
+
project?: string | null;
|
|
576
|
+
filePath?: string;
|
|
577
|
+
rootDir?: string;
|
|
578
|
+
}): Promise<RenderSnippetTextResult> {
|
|
579
|
+
const rootDir = args.rootDir ?? facultRootDir();
|
|
580
|
+
const text = args.text;
|
|
581
|
+
const filePath = args.filePath;
|
|
582
|
+
const errors: string[] = [];
|
|
583
|
+
const changes: SyncChange[] = [];
|
|
584
|
+
|
|
585
|
+
if (!hasMarkers(text)) {
|
|
586
|
+
return { text, changes, errors };
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const found = findMarkersInText(text, filePath);
|
|
590
|
+
if (found.errors.length) {
|
|
591
|
+
return {
|
|
592
|
+
text,
|
|
593
|
+
changes,
|
|
594
|
+
errors: found.errors,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
let lastEnd = -1;
|
|
599
|
+
for (const p of found.pairs) {
|
|
600
|
+
if (p.open.start < lastEnd) {
|
|
601
|
+
const location = filePath
|
|
602
|
+
? `${filePath}:${p.open.line}`
|
|
603
|
+
: `line ${p.open.line}`;
|
|
604
|
+
errors.push(
|
|
605
|
+
`${location}: snippet markers may not be nested or overlapping (found ${p.name})`
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
lastEnd = p.close.end;
|
|
609
|
+
}
|
|
610
|
+
if (errors.length) {
|
|
611
|
+
return { text, changes, errors };
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
type Replacement = { start: number; end: number; next: string };
|
|
615
|
+
const replacements: Replacement[] = [];
|
|
616
|
+
|
|
617
|
+
for (const pair of found.pairs) {
|
|
618
|
+
const marker = pair.name;
|
|
619
|
+
const existing = text.slice(pair.contentStart, pair.contentEnd);
|
|
620
|
+
const existingNorm = normalizeSnippetBody(existing);
|
|
621
|
+
|
|
622
|
+
const snippet = await findSnippet({
|
|
623
|
+
marker,
|
|
624
|
+
project: args.project ?? null,
|
|
625
|
+
rootDir,
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
if (!snippet) {
|
|
629
|
+
changes.push({ marker, status: "not-found" });
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const snippetNorm = normalizeSnippetBody(snippet.content);
|
|
634
|
+
if (existingNorm === snippetNorm) {
|
|
635
|
+
changes.push({
|
|
636
|
+
marker,
|
|
637
|
+
status: "unchanged",
|
|
638
|
+
snippetPath: snippet.path,
|
|
639
|
+
lines: countLines(snippetNorm),
|
|
640
|
+
});
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
replacements.push({
|
|
645
|
+
start: pair.contentStart,
|
|
646
|
+
end: pair.contentEnd,
|
|
647
|
+
next: formatSnippetInjection(snippet.content),
|
|
648
|
+
});
|
|
649
|
+
changes.push({
|
|
650
|
+
marker,
|
|
651
|
+
status: "updated",
|
|
652
|
+
snippetPath: snippet.path,
|
|
653
|
+
lines: countLines(snippetNorm),
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (replacements.length === 0) {
|
|
658
|
+
return { text, changes, errors };
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
let updated = text;
|
|
662
|
+
for (const r of replacements.sort((a, b) => b.start - a.start)) {
|
|
663
|
+
updated = updated.slice(0, r.start) + r.next + updated.slice(r.end);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return {
|
|
667
|
+
text: updated,
|
|
668
|
+
changes,
|
|
669
|
+
errors,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
567
673
|
export async function syncAll(args?: {
|
|
568
674
|
dryRun?: boolean;
|
|
569
675
|
/** Override canonical root (useful for tests). */
|
package/src/trust-list.ts
CHANGED
package/src/trust.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
-
import {
|
|
2
|
+
import { ensureAiIndexPath } from "./ai-state";
|
|
3
3
|
import type { FacultIndex } from "./index-builder";
|
|
4
|
-
import { facultRootDir } from "./paths";
|
|
4
|
+
import { facultAiIndexPath, facultRootDir } from "./paths";
|
|
5
5
|
|
|
6
6
|
type TrustMode = "trust" | "untrust";
|
|
7
7
|
|
|
@@ -17,6 +17,7 @@ function ensureIndexStructure(index: FacultIndex): FacultIndex {
|
|
|
17
17
|
mcp: index.mcp ?? { servers: {} },
|
|
18
18
|
agents: index.agents ?? {},
|
|
19
19
|
snippets: index.snippets ?? {},
|
|
20
|
+
instructions: index.instructions ?? {},
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -27,8 +28,12 @@ function parseEntryName(raw: string): { kind: "skill" | "mcp"; name: string } {
|
|
|
27
28
|
return { kind: "skill", name: raw };
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
async function loadIndex(
|
|
31
|
-
const indexPath =
|
|
31
|
+
async function loadIndex(homeDir: string): Promise<FacultIndex> {
|
|
32
|
+
const { path: indexPath } = await ensureAiIndexPath({
|
|
33
|
+
homeDir,
|
|
34
|
+
rootDir: facultRootDir(homeDir),
|
|
35
|
+
repair: true,
|
|
36
|
+
});
|
|
32
37
|
const file = Bun.file(indexPath);
|
|
33
38
|
if (!(await file.exists())) {
|
|
34
39
|
throw new Error(`Index not found at ${indexPath}. Run "facult index".`);
|
|
@@ -37,8 +42,8 @@ async function loadIndex(rootDir: string): Promise<FacultIndex> {
|
|
|
37
42
|
return JSON.parse(raw) as FacultIndex;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
async function writeIndex(
|
|
41
|
-
const indexPath =
|
|
45
|
+
async function writeIndex(homeDir: string, index: FacultIndex) {
|
|
46
|
+
const indexPath = facultAiIndexPath(homeDir);
|
|
42
47
|
await Bun.write(indexPath, `${JSON.stringify(index, null, 2)}\n`);
|
|
43
48
|
}
|
|
44
49
|
|
|
@@ -68,20 +73,17 @@ export async function applyTrust({
|
|
|
68
73
|
names,
|
|
69
74
|
mode,
|
|
70
75
|
homeDir,
|
|
71
|
-
rootDir,
|
|
72
76
|
}: {
|
|
73
77
|
names: string[];
|
|
74
78
|
mode: TrustMode;
|
|
75
79
|
homeDir?: string;
|
|
76
|
-
rootDir?: string;
|
|
77
80
|
}) {
|
|
78
81
|
if (!names.length) {
|
|
79
82
|
throw new Error("At least one name is required.");
|
|
80
83
|
}
|
|
81
84
|
const home = homeDir ?? homedir();
|
|
82
|
-
const root = rootDir ?? facultRootDir(home);
|
|
83
85
|
|
|
84
|
-
const index = ensureIndexStructure(await loadIndex(
|
|
86
|
+
const index = ensureIndexStructure(await loadIndex(home));
|
|
85
87
|
const now = new Date().toISOString();
|
|
86
88
|
|
|
87
89
|
const missing: string[] = [];
|
|
@@ -109,7 +111,7 @@ export async function applyTrust({
|
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
index.updatedAt = new Date().toISOString();
|
|
112
|
-
await writeIndex(
|
|
114
|
+
await writeIndex(home, index);
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
function parseNamesFromArgv(argv: string[]): string[] {
|