facult 2.8.4 → 2.8.6
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 +5 -3
- package/package.json +1 -1
- package/src/ai.ts +33 -31
- package/src/index-builder.ts +83 -12
- package/src/paths.ts +2 -1
package/README.md
CHANGED
|
@@ -441,13 +441,13 @@ fclt ai evolve apply EV-00001
|
|
|
441
441
|
fclt ai evolve promote EV-00003 --to global --project
|
|
442
442
|
```
|
|
443
443
|
|
|
444
|
-
Runtime state stays generated and local
|
|
445
|
-
- global writeback state:
|
|
444
|
+
Runtime writeback and evolution state stays generated and machine-local:
|
|
445
|
+
- global writeback state: machine-local Facult state under `.../global/ai/global/...`
|
|
446
446
|
- project writeback state: machine-local per-project Facult state under `.../projects/<slug-hash>/ai/project/...`
|
|
447
447
|
|
|
448
448
|
That split is intentional:
|
|
449
449
|
- canonical source remains in `~/.ai` or `<repo>/.ai`
|
|
450
|
-
- global generated state stays inside `~/.ai/.facult/`;
|
|
450
|
+
- global generated index and graph state stays inside `~/.ai/.facult/`; writebacks, journals, proposals, drafts, and managed runtime state stay outside canonical source in machine-local state
|
|
451
451
|
- those records let agents inspect what changed, why it changed, and how it was reviewed
|
|
452
452
|
|
|
453
453
|
Use writeback when:
|
|
@@ -698,6 +698,8 @@ Under canonical generated AI state (`~/.ai/.facult/` or `<repo>/.ai/.facult/`):
|
|
|
698
698
|
Under machine-local Facult state:
|
|
699
699
|
- `install.json` (machine-local install metadata)
|
|
700
700
|
- `global/managed.json` or `projects/<slug-hash>/managed.json` (managed tool state)
|
|
701
|
+
- `global/ai/global/writeback/queue.jsonl` and `projects/<slug-hash>/ai/project/writeback/queue.jsonl` (writeback queues)
|
|
702
|
+
- `global/ai/global/evolution/` and `projects/<slug-hash>/ai/project/evolution/` (proposal metadata, markdown drafts, and patch artifacts)
|
|
701
703
|
- `.../autosync/services/*.json` (autosync service configs)
|
|
702
704
|
- `.../autosync/state/*.json` (autosync runtime state)
|
|
703
705
|
- `.../autosync/logs/*` (autosync service logs)
|
package/package.json
CHANGED
package/src/ai.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
facultAiDraftDir,
|
|
10
10
|
facultAiJournalPath,
|
|
11
11
|
facultAiProposalDir,
|
|
12
|
+
facultAiStateDir,
|
|
12
13
|
facultAiWritebackQueuePath,
|
|
13
14
|
facultRootDir,
|
|
14
15
|
legacyFacultAiStateDirs,
|
|
@@ -233,9 +234,10 @@ function aiRuntimeScopeName(rootDir: string, homeDir: string): AssetScope {
|
|
|
233
234
|
|
|
234
235
|
function legacyAiRuntimeScopeDirs(homeDir: string, rootDir: string): string[] {
|
|
235
236
|
const scope = aiRuntimeScopeName(rootDir, homeDir);
|
|
236
|
-
return
|
|
237
|
-
join(
|
|
238
|
-
|
|
237
|
+
return uniqueStrings([
|
|
238
|
+
join(facultAiStateDir(homeDir, rootDir), scope),
|
|
239
|
+
...legacyFacultAiStateDirs(homeDir, rootDir).map((dir) => join(dir, scope)),
|
|
240
|
+
]);
|
|
239
241
|
}
|
|
240
242
|
|
|
241
243
|
function aiWritebackQueueReadPaths(homeDir: string, rootDir: string): string[] {
|
|
@@ -1604,15 +1606,15 @@ function parseEvidence(argv: string[]): WritebackEvidence[] {
|
|
|
1604
1606
|
}
|
|
1605
1607
|
|
|
1606
1608
|
async function writebackCommand(argv: string[]) {
|
|
1607
|
-
const
|
|
1608
|
-
const
|
|
1609
|
+
const parsed = parseCliContextArgs(argv);
|
|
1610
|
+
const [sub, ...commandArgs] = parsed.argv;
|
|
1609
1611
|
|
|
1610
1612
|
if (!sub || sub === "--help" || sub === "-h" || sub === "help") {
|
|
1611
1613
|
console.log(writebackHelp());
|
|
1612
1614
|
return;
|
|
1613
1615
|
}
|
|
1614
1616
|
|
|
1615
|
-
if (
|
|
1617
|
+
if (commandArgs.includes("--help") || commandArgs.includes("-h")) {
|
|
1616
1618
|
console.log(writebackHelp());
|
|
1617
1619
|
return;
|
|
1618
1620
|
}
|
|
@@ -1625,8 +1627,8 @@ async function writebackCommand(argv: string[]) {
|
|
|
1625
1627
|
|
|
1626
1628
|
try {
|
|
1627
1629
|
if (sub === "add") {
|
|
1628
|
-
const kind = parseStringFlag(
|
|
1629
|
-
const summary = parseStringFlag(
|
|
1630
|
+
const kind = parseStringFlag(commandArgs, "--kind");
|
|
1631
|
+
const summary = parseStringFlag(commandArgs, "--summary");
|
|
1630
1632
|
if (!(kind && summary)) {
|
|
1631
1633
|
throw new Error("writeback add requires --kind and --summary");
|
|
1632
1634
|
}
|
|
@@ -1634,18 +1636,18 @@ async function writebackCommand(argv: string[]) {
|
|
|
1634
1636
|
rootDir,
|
|
1635
1637
|
kind,
|
|
1636
1638
|
summary,
|
|
1637
|
-
asset: parseStringFlag(
|
|
1638
|
-
allowEmptyEvidence:
|
|
1639
|
+
asset: parseStringFlag(commandArgs, "--asset"),
|
|
1640
|
+
allowEmptyEvidence: commandArgs.includes("--allow-empty-evidence"),
|
|
1639
1641
|
confidence:
|
|
1640
|
-
(parseStringFlag(
|
|
1642
|
+
(parseStringFlag(commandArgs, "--confidence") as
|
|
1641
1643
|
| ConfidenceLevel
|
|
1642
1644
|
| undefined) ?? undefined,
|
|
1643
1645
|
suggestedDestination: parseStringFlag(
|
|
1644
|
-
|
|
1646
|
+
commandArgs,
|
|
1645
1647
|
"--suggested-destination"
|
|
1646
1648
|
),
|
|
1647
|
-
tags: parseRepeatedFlag(
|
|
1648
|
-
evidence: parseEvidence(
|
|
1649
|
+
tags: parseRepeatedFlag(commandArgs, "--tag"),
|
|
1650
|
+
evidence: parseEvidence(commandArgs),
|
|
1649
1651
|
});
|
|
1650
1652
|
console.log(`Recorded writeback ${record.id}`);
|
|
1651
1653
|
console.log(JSON.stringify(record, null, 2));
|
|
@@ -1654,7 +1656,7 @@ async function writebackCommand(argv: string[]) {
|
|
|
1654
1656
|
|
|
1655
1657
|
if (sub === "list") {
|
|
1656
1658
|
const rows = await listWritebacks({ rootDir });
|
|
1657
|
-
if (
|
|
1659
|
+
if (commandArgs.includes("--json")) {
|
|
1658
1660
|
console.log(JSON.stringify(rows, null, 2));
|
|
1659
1661
|
return;
|
|
1660
1662
|
}
|
|
@@ -1665,7 +1667,7 @@ async function writebackCommand(argv: string[]) {
|
|
|
1665
1667
|
}
|
|
1666
1668
|
|
|
1667
1669
|
if (sub === "group" || sub === "summarize") {
|
|
1668
|
-
const byValue = parseStringFlag(
|
|
1670
|
+
const byValue = parseStringFlag(commandArgs, "--by") ?? "asset";
|
|
1669
1671
|
if (byValue !== "asset" && byValue !== "kind" && byValue !== "domain") {
|
|
1670
1672
|
throw new Error(`Unsupported writeback grouping: ${byValue}`);
|
|
1671
1673
|
}
|
|
@@ -1673,7 +1675,7 @@ async function writebackCommand(argv: string[]) {
|
|
|
1673
1675
|
sub === "group"
|
|
1674
1676
|
? await groupWritebacks({ rootDir, by: byValue })
|
|
1675
1677
|
: await summarizeWritebacks({ rootDir, by: byValue });
|
|
1676
|
-
if (
|
|
1678
|
+
if (commandArgs.includes("--json")) {
|
|
1677
1679
|
console.log(JSON.stringify(rows, null, 2));
|
|
1678
1680
|
return;
|
|
1679
1681
|
}
|
|
@@ -1686,7 +1688,7 @@ async function writebackCommand(argv: string[]) {
|
|
|
1686
1688
|
}
|
|
1687
1689
|
|
|
1688
1690
|
if (sub === "show") {
|
|
1689
|
-
const id =
|
|
1691
|
+
const id = commandArgs.find((arg) => !arg.startsWith("-"));
|
|
1690
1692
|
if (!id) {
|
|
1691
1693
|
throw new Error("writeback show requires an id");
|
|
1692
1694
|
}
|
|
@@ -1699,7 +1701,7 @@ async function writebackCommand(argv: string[]) {
|
|
|
1699
1701
|
}
|
|
1700
1702
|
|
|
1701
1703
|
if (sub === "dismiss" || sub === "promote") {
|
|
1702
|
-
const id =
|
|
1704
|
+
const id = commandArgs.find((arg) => !arg.startsWith("-"));
|
|
1703
1705
|
if (!id) {
|
|
1704
1706
|
throw new Error(`writeback ${sub} requires an id`);
|
|
1705
1707
|
}
|
|
@@ -1720,15 +1722,15 @@ async function writebackCommand(argv: string[]) {
|
|
|
1720
1722
|
}
|
|
1721
1723
|
|
|
1722
1724
|
async function evolveCommand(argv: string[]) {
|
|
1723
|
-
const
|
|
1724
|
-
const
|
|
1725
|
+
const parsed = parseCliContextArgs(argv);
|
|
1726
|
+
const [sub, ...commandArgs] = parsed.argv;
|
|
1725
1727
|
|
|
1726
1728
|
if (!sub || sub === "--help" || sub === "-h" || sub === "help") {
|
|
1727
1729
|
console.log(evolveHelp());
|
|
1728
1730
|
return;
|
|
1729
1731
|
}
|
|
1730
1732
|
|
|
1731
|
-
if (
|
|
1733
|
+
if (commandArgs.includes("--help") || commandArgs.includes("-h")) {
|
|
1732
1734
|
console.log(evolveHelp());
|
|
1733
1735
|
return;
|
|
1734
1736
|
}
|
|
@@ -1743,9 +1745,9 @@ async function evolveCommand(argv: string[]) {
|
|
|
1743
1745
|
if (sub === "propose") {
|
|
1744
1746
|
const proposals = await proposeEvolution({
|
|
1745
1747
|
rootDir,
|
|
1746
|
-
asset: parseStringFlag(
|
|
1748
|
+
asset: parseStringFlag(commandArgs, "--asset"),
|
|
1747
1749
|
});
|
|
1748
|
-
if (
|
|
1750
|
+
if (commandArgs.includes("--json")) {
|
|
1749
1751
|
console.log(JSON.stringify(proposals, null, 2));
|
|
1750
1752
|
return;
|
|
1751
1753
|
}
|
|
@@ -1759,7 +1761,7 @@ async function evolveCommand(argv: string[]) {
|
|
|
1759
1761
|
|
|
1760
1762
|
if (sub === "list") {
|
|
1761
1763
|
const rows = await listProposals({ rootDir });
|
|
1762
|
-
if (
|
|
1764
|
+
if (commandArgs.includes("--json")) {
|
|
1763
1765
|
console.log(JSON.stringify(rows, null, 2));
|
|
1764
1766
|
return;
|
|
1765
1767
|
}
|
|
@@ -1770,7 +1772,7 @@ async function evolveCommand(argv: string[]) {
|
|
|
1770
1772
|
}
|
|
1771
1773
|
|
|
1772
1774
|
if (sub === "show") {
|
|
1773
|
-
const id =
|
|
1775
|
+
const id = commandArgs.find((arg) => !arg.startsWith("-"));
|
|
1774
1776
|
if (!id) {
|
|
1775
1777
|
throw new Error("evolve show requires an id");
|
|
1776
1778
|
}
|
|
@@ -1791,7 +1793,7 @@ async function evolveCommand(argv: string[]) {
|
|
|
1791
1793
|
sub === "apply" ||
|
|
1792
1794
|
sub === "promote"
|
|
1793
1795
|
) {
|
|
1794
|
-
const id =
|
|
1796
|
+
const id = commandArgs.find((arg) => !arg.startsWith("-"));
|
|
1795
1797
|
if (!id) {
|
|
1796
1798
|
throw new Error(`evolve ${sub} requires an id`);
|
|
1797
1799
|
}
|
|
@@ -1799,7 +1801,7 @@ async function evolveCommand(argv: string[]) {
|
|
|
1799
1801
|
sub === "draft"
|
|
1800
1802
|
? await draftProposal(id, {
|
|
1801
1803
|
rootDir,
|
|
1802
|
-
append: parseStringFlag(
|
|
1804
|
+
append: parseStringFlag(commandArgs, "--append"),
|
|
1803
1805
|
})
|
|
1804
1806
|
: sub === "review"
|
|
1805
1807
|
? await reviewProposal(id, { rootDir })
|
|
@@ -1809,7 +1811,7 @@ async function evolveCommand(argv: string[]) {
|
|
|
1809
1811
|
? await rejectProposal(id, {
|
|
1810
1812
|
rootDir,
|
|
1811
1813
|
reason:
|
|
1812
|
-
parseStringFlag(
|
|
1814
|
+
parseStringFlag(commandArgs, "--reason") ??
|
|
1813
1815
|
(() => {
|
|
1814
1816
|
throw new Error("evolve reject requires --reason");
|
|
1815
1817
|
})(),
|
|
@@ -1817,7 +1819,7 @@ async function evolveCommand(argv: string[]) {
|
|
|
1817
1819
|
: sub === "supersede"
|
|
1818
1820
|
? await supersedeProposal(
|
|
1819
1821
|
id,
|
|
1820
|
-
parseStringFlag(
|
|
1822
|
+
parseStringFlag(commandArgs, "--by") ??
|
|
1821
1823
|
(() => {
|
|
1822
1824
|
throw new Error("evolve supersede requires --by");
|
|
1823
1825
|
})(),
|
|
@@ -1827,7 +1829,7 @@ async function evolveCommand(argv: string[]) {
|
|
|
1827
1829
|
? await promoteProposal(id, {
|
|
1828
1830
|
rootDir,
|
|
1829
1831
|
to:
|
|
1830
|
-
(parseStringFlag(
|
|
1832
|
+
(parseStringFlag(commandArgs, "--to") as
|
|
1831
1833
|
| "global"
|
|
1832
1834
|
| undefined) ??
|
|
1833
1835
|
(() => {
|
package/src/index-builder.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mkdir, readdir } from "node:fs/promises";
|
|
2
|
-
import { basename, dirname, join, relative } from "node:path";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { getAdapter } from "./adapters";
|
|
5
5
|
import { parseCliContextArgs, resolveCliContextRoot } from "./cli-context";
|
|
@@ -215,7 +215,8 @@ function extractIndexMeta(entry: unknown): {
|
|
|
215
215
|
function findPreviousEntryByCanonicalRef(
|
|
216
216
|
previous: Record<string, unknown> | undefined,
|
|
217
217
|
canonicalRef: string | undefined,
|
|
218
|
-
fallbackName: string
|
|
218
|
+
fallbackName: string,
|
|
219
|
+
source: IndexedSource
|
|
219
220
|
): unknown {
|
|
220
221
|
if (!previous) {
|
|
221
222
|
return undefined;
|
|
@@ -233,7 +234,8 @@ function findPreviousEntryByCanonicalRef(
|
|
|
233
234
|
const legacyFallback = previous[fallbackName];
|
|
234
235
|
if (
|
|
235
236
|
isPlainObject(legacyFallback) &&
|
|
236
|
-
typeof legacyFallback.canonicalRef !== "string"
|
|
237
|
+
typeof legacyFallback.canonicalRef !== "string" &&
|
|
238
|
+
legacyFallbackMatchesSource(legacyFallback, source)
|
|
237
239
|
) {
|
|
238
240
|
return legacyFallback;
|
|
239
241
|
}
|
|
@@ -243,7 +245,8 @@ function findPreviousEntryByCanonicalRef(
|
|
|
243
245
|
function findPreviousMcpEntry(
|
|
244
246
|
previous: Record<string, unknown> | undefined,
|
|
245
247
|
canonicalRef: string | undefined,
|
|
246
|
-
name: string
|
|
248
|
+
name: string,
|
|
249
|
+
source: IndexedSource
|
|
247
250
|
): unknown {
|
|
248
251
|
if (!previous) {
|
|
249
252
|
return undefined;
|
|
@@ -252,11 +255,73 @@ function findPreviousMcpEntry(
|
|
|
252
255
|
if (!isPlainObject(candidate)) {
|
|
253
256
|
return undefined;
|
|
254
257
|
}
|
|
255
|
-
|
|
256
|
-
(
|
|
257
|
-
candidate
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
if (typeof candidate.canonicalRef !== "string") {
|
|
259
|
+
return legacyFallbackMatchesSource(candidate, source)
|
|
260
|
+
? candidate
|
|
261
|
+
: undefined;
|
|
262
|
+
}
|
|
263
|
+
if (
|
|
264
|
+
typeof canonicalRef === "string" &&
|
|
265
|
+
candidate.canonicalRef === canonicalRef
|
|
266
|
+
) {
|
|
267
|
+
return candidate;
|
|
268
|
+
}
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function pathIsWithinRoot(pathValue: string, rootDir: string): boolean {
|
|
273
|
+
if (!isAbsolute(pathValue)) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
const rel = relative(rootDir, pathValue);
|
|
277
|
+
return rel === "" || !(rel.startsWith("..") || isAbsolute(rel));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function legacyFallbackMatchesSource(
|
|
281
|
+
entry: Record<string, unknown>,
|
|
282
|
+
source: IndexedSource
|
|
283
|
+
): boolean {
|
|
284
|
+
let hasSourceSignal = false;
|
|
285
|
+
|
|
286
|
+
const entrySourceKind = entry.sourceKind;
|
|
287
|
+
if (typeof entrySourceKind === "string") {
|
|
288
|
+
hasSourceSignal = true;
|
|
289
|
+
if (entrySourceKind !== source.sourceKind) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const entryScope = entry.scope;
|
|
295
|
+
if (typeof entryScope === "string") {
|
|
296
|
+
hasSourceSignal = true;
|
|
297
|
+
if (entryScope !== source.scope) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const entryProjectRoot = entry.projectRoot;
|
|
303
|
+
if (typeof entryProjectRoot === "string") {
|
|
304
|
+
hasSourceSignal = true;
|
|
305
|
+
if (entryProjectRoot !== source.projectRoot) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const entrySourceRoot = entry.sourceRoot;
|
|
311
|
+
if (typeof entrySourceRoot === "string") {
|
|
312
|
+
hasSourceSignal = true;
|
|
313
|
+
if (entrySourceRoot !== source.rootDir) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const entryPath = entry.path;
|
|
319
|
+
if (typeof entryPath === "string") {
|
|
320
|
+
hasSourceSignal = true;
|
|
321
|
+
return pathIsWithinRoot(entryPath, source.rootDir);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return hasSourceSignal;
|
|
260
325
|
}
|
|
261
326
|
|
|
262
327
|
function stripQuotes(s: string): string {
|
|
@@ -556,7 +621,8 @@ async function indexSkills(
|
|
|
556
621
|
const prev = findPreviousEntryByCanonicalRef(
|
|
557
622
|
previous,
|
|
558
623
|
canonicalRef,
|
|
559
|
-
name
|
|
624
|
+
name,
|
|
625
|
+
source
|
|
560
626
|
);
|
|
561
627
|
const meta = extractIndexMeta(prev);
|
|
562
628
|
|
|
@@ -618,7 +684,7 @@ async function indexMcpServers(
|
|
|
618
684
|
const lm = await statIsoTime(mcpConfigPath);
|
|
619
685
|
for (const name of Object.keys(serversObj).sort()) {
|
|
620
686
|
const canonicalRef = canonicalRefForPath(source, "mcp", mcpConfigPath);
|
|
621
|
-
const prev = findPreviousMcpEntry(previous, canonicalRef, name);
|
|
687
|
+
const prev = findPreviousMcpEntry(previous, canonicalRef, name, source);
|
|
622
688
|
const meta = extractIndexMeta(prev);
|
|
623
689
|
out[name] = {
|
|
624
690
|
name,
|
|
@@ -666,7 +732,12 @@ async function indexAgents(
|
|
|
666
732
|
const name =
|
|
667
733
|
basename(p) === "agent.toml" ? basename(dirname(p)) : basename(p);
|
|
668
734
|
const canonicalRef = canonicalRefForPath(source, "agents", p);
|
|
669
|
-
const prev = findPreviousEntryByCanonicalRef(
|
|
735
|
+
const prev = findPreviousEntryByCanonicalRef(
|
|
736
|
+
previous,
|
|
737
|
+
canonicalRef,
|
|
738
|
+
name,
|
|
739
|
+
source
|
|
740
|
+
);
|
|
670
741
|
const meta = extractIndexMeta(prev);
|
|
671
742
|
let description: string | undefined;
|
|
672
743
|
try {
|
package/src/paths.ts
CHANGED
|
@@ -369,7 +369,8 @@ export function facultAiRuntimeScopeDir(
|
|
|
369
369
|
rootDir?: string
|
|
370
370
|
): string {
|
|
371
371
|
return join(
|
|
372
|
-
|
|
372
|
+
facultMachineStateDir(home, rootDir),
|
|
373
|
+
"ai",
|
|
373
374
|
projectRootFromAiRoot(rootDir ?? facultRootDir(home), home)
|
|
374
375
|
? "project"
|
|
375
376
|
: "global"
|