facult 2.5.1 → 2.6.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 +3 -0
- package/package.json +1 -1
- package/src/global-docs.ts +38 -12
- package/src/manage.ts +66 -13
package/README.md
CHANGED
|
@@ -445,6 +445,7 @@ The canonical store can contain several distinct asset classes:
|
|
|
445
445
|
- `skills/`: workflow-specific capability folders
|
|
446
446
|
- `mcp/`: canonical MCP server definitions
|
|
447
447
|
- `tools/<tool>/config.toml`: canonical tool config
|
|
448
|
+
- `tools/<tool>/config.local.toml`: machine-local tool config overlay
|
|
448
449
|
- `tools/<tool>/rules/*.rules`: canonical tool rules
|
|
449
450
|
- global docs such as `AGENTS.global.md` and `AGENTS.override.global.md`
|
|
450
451
|
|
|
@@ -479,6 +480,8 @@ Built-ins currently include:
|
|
|
479
480
|
Recommended split:
|
|
480
481
|
- `~/.ai/config.toml` or `<repo>/.ai/config.toml`: tracked, portable, non-secret refs/defaults
|
|
481
482
|
- `~/.ai/config.local.toml` or `<repo>/.ai/config.local.toml`: ignored, machine-local paths and secrets
|
|
483
|
+
- `~/.ai/tools/<tool>/config.toml` or `<repo>/.ai/tools/<tool>/config.toml`: tracked tool defaults
|
|
484
|
+
- `~/.ai/tools/<tool>/config.local.toml` or `<repo>/.ai/tools/<tool>/config.local.toml`: ignored, machine-local tool overrides merged after tracked tool config during sync
|
|
482
485
|
- `[builtin].sync_defaults = false`: disable builtin default sync/materialization for this root
|
|
483
486
|
- `fclt sync --builtin-conflicts overwrite`: allow packaged builtin defaults to overwrite locally modified generated targets
|
|
484
487
|
|
package/package.json
CHANGED
package/src/global-docs.ts
CHANGED
|
@@ -426,7 +426,16 @@ export async function planToolConfigSync(args: {
|
|
|
426
426
|
previouslyManaged?: boolean;
|
|
427
427
|
}): Promise<ToolConfigPlan> {
|
|
428
428
|
const sourcePath = join(args.rootDir, "tools", args.tool, "config.toml");
|
|
429
|
-
|
|
429
|
+
const localSourcePath = join(
|
|
430
|
+
args.rootDir,
|
|
431
|
+
"tools",
|
|
432
|
+
args.tool,
|
|
433
|
+
"config.local.toml"
|
|
434
|
+
);
|
|
435
|
+
const hasTrackedSource = await fileExists(sourcePath);
|
|
436
|
+
const hasLocalSource = await fileExists(localSourcePath);
|
|
437
|
+
|
|
438
|
+
if (!(hasTrackedSource || hasLocalSource)) {
|
|
430
439
|
return {
|
|
431
440
|
targetPath: args.toolConfigPath,
|
|
432
441
|
write: false,
|
|
@@ -437,14 +446,28 @@ export async function planToolConfigSync(args: {
|
|
|
437
446
|
};
|
|
438
447
|
}
|
|
439
448
|
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
449
|
+
const trackedRendered = hasTrackedSource
|
|
450
|
+
? await renderSourceTarget({
|
|
451
|
+
homeDir: args.homeDir,
|
|
452
|
+
rootDir: args.rootDir,
|
|
453
|
+
sourcePath,
|
|
454
|
+
targetPath: args.toolConfigPath,
|
|
455
|
+
tool: args.tool,
|
|
456
|
+
})
|
|
457
|
+
: null;
|
|
458
|
+
const localRendered = hasLocalSource
|
|
459
|
+
? await renderSourceTarget({
|
|
460
|
+
homeDir: args.homeDir,
|
|
461
|
+
rootDir: args.rootDir,
|
|
462
|
+
sourcePath: localSourcePath,
|
|
463
|
+
targetPath: args.toolConfigPath,
|
|
464
|
+
tool: args.tool,
|
|
465
|
+
})
|
|
466
|
+
: null;
|
|
467
|
+
const canonicalConfig = trackedRendered
|
|
468
|
+
? Bun.TOML.parse(trackedRendered)
|
|
469
|
+
: {};
|
|
470
|
+
const localConfig = localRendered ? Bun.TOML.parse(localRendered) : {};
|
|
448
471
|
const existingConfig =
|
|
449
472
|
(await readTomlFile(args.toolConfigPath)) ??
|
|
450
473
|
(args.existingConfigPath
|
|
@@ -452,8 +475,11 @@ export async function planToolConfigSync(args: {
|
|
|
452
475
|
: null) ??
|
|
453
476
|
({} as Record<string, unknown>);
|
|
454
477
|
const merged = mergeTomlObjects(
|
|
455
|
-
|
|
456
|
-
|
|
478
|
+
mergeTomlObjects(
|
|
479
|
+
existingConfig,
|
|
480
|
+
isPlainObject(canonicalConfig) ? canonicalConfig : {}
|
|
481
|
+
),
|
|
482
|
+
isPlainObject(localConfig) ? localConfig : {}
|
|
457
483
|
);
|
|
458
484
|
const nextContents = stringifyTomlObject(merged);
|
|
459
485
|
const current = await readTextIfExists(args.toolConfigPath);
|
|
@@ -462,7 +488,7 @@ export async function planToolConfigSync(args: {
|
|
|
462
488
|
write: current !== `${nextContents}\n`,
|
|
463
489
|
remove: false,
|
|
464
490
|
contents: nextContents,
|
|
465
|
-
sourcePath,
|
|
491
|
+
sourcePath: hasLocalSource ? localSourcePath : sourcePath,
|
|
466
492
|
managedConfig: true,
|
|
467
493
|
};
|
|
468
494
|
}
|
package/src/manage.ts
CHANGED
|
@@ -586,15 +586,34 @@ async function loadCanonicalAutomations(
|
|
|
586
586
|
return await loadAutomationEntries(join(rootDir, "automations"));
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
+
function isAutomationRuntimeRelativePath(relPath: string): boolean {
|
|
590
|
+
return relPath === "memory.md";
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function isAutomationRuntimeTargetPath(targetPath: string): boolean {
|
|
594
|
+
return basename(targetPath) === "memory.md";
|
|
595
|
+
}
|
|
596
|
+
|
|
589
597
|
function automationEntriesEqual(
|
|
590
598
|
left: AutomationEntry,
|
|
591
599
|
right: AutomationEntry
|
|
592
600
|
): boolean {
|
|
593
|
-
|
|
601
|
+
const leftFiles = new Map(
|
|
602
|
+
[...left.files.entries()].filter(
|
|
603
|
+
([relPath]) => !isAutomationRuntimeRelativePath(relPath)
|
|
604
|
+
)
|
|
605
|
+
);
|
|
606
|
+
const rightFiles = new Map(
|
|
607
|
+
[...right.files.entries()].filter(
|
|
608
|
+
([relPath]) => !isAutomationRuntimeRelativePath(relPath)
|
|
609
|
+
)
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
if (leftFiles.size !== rightFiles.size) {
|
|
594
613
|
return false;
|
|
595
614
|
}
|
|
596
|
-
for (const [relPath, leftRaw] of
|
|
597
|
-
if (
|
|
615
|
+
for (const [relPath, leftRaw] of leftFiles.entries()) {
|
|
616
|
+
if (rightFiles.get(relPath) !== leftRaw) {
|
|
598
617
|
return false;
|
|
599
618
|
}
|
|
600
619
|
}
|
|
@@ -816,23 +835,27 @@ async function planAutomationFileChanges(args: {
|
|
|
816
835
|
const contents = new Map<string, string>();
|
|
817
836
|
const sources = new Map<string, string>();
|
|
818
837
|
const desiredPaths = new Set<string>();
|
|
838
|
+
const add = new Set<string>();
|
|
819
839
|
|
|
820
840
|
for (const automation of automations) {
|
|
821
841
|
for (const [relPath, raw] of automation.files.entries()) {
|
|
822
842
|
const targetPath = join(args.automationDir, automation.name, relPath);
|
|
823
843
|
const sourcePath = join(automation.sourceDir, relPath);
|
|
824
|
-
desiredPaths.add(targetPath);
|
|
825
844
|
contents.set(targetPath, raw);
|
|
826
|
-
sources.set(targetPath, sourcePath);
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
845
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
846
|
+
if (isAutomationRuntimeRelativePath(relPath)) {
|
|
847
|
+
if ((await readTextIfExists(targetPath)) == null) {
|
|
848
|
+
add.add(targetPath);
|
|
849
|
+
}
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
desiredPaths.add(targetPath);
|
|
854
|
+
sources.set(targetPath, sourcePath);
|
|
855
|
+
const current = await readTextIfExists(targetPath);
|
|
856
|
+
if (current !== raw) {
|
|
857
|
+
add.add(targetPath);
|
|
858
|
+
}
|
|
836
859
|
}
|
|
837
860
|
}
|
|
838
861
|
|
|
@@ -841,6 +864,7 @@ async function planAutomationFileChanges(args: {
|
|
|
841
864
|
(args.previouslyManagedTargets ?? []).filter(
|
|
842
865
|
(targetPath) =>
|
|
843
866
|
targetPath.startsWith(join(args.automationDir, "")) &&
|
|
867
|
+
!isAutomationRuntimeTargetPath(targetPath) &&
|
|
844
868
|
!desiredPaths.has(targetPath)
|
|
845
869
|
)
|
|
846
870
|
)
|
|
@@ -2973,6 +2997,30 @@ function updateRenderedTargetState(args: {
|
|
|
2973
2997
|
args.entry.renderedTargets = next;
|
|
2974
2998
|
}
|
|
2975
2999
|
|
|
3000
|
+
function pruneAutomationRuntimeRenderedTargets(args: {
|
|
3001
|
+
entry: ManagedToolState;
|
|
3002
|
+
automationDir?: string;
|
|
3003
|
+
}) {
|
|
3004
|
+
if (!(args.automationDir && args.entry.renderedTargets)) {
|
|
3005
|
+
return;
|
|
3006
|
+
}
|
|
3007
|
+
const prefix = join(args.automationDir, "");
|
|
3008
|
+
const next = { ...args.entry.renderedTargets };
|
|
3009
|
+
let changed = false;
|
|
3010
|
+
for (const targetPath of Object.keys(next)) {
|
|
3011
|
+
if (
|
|
3012
|
+
targetPath.startsWith(prefix) &&
|
|
3013
|
+
isAutomationRuntimeTargetPath(targetPath)
|
|
3014
|
+
) {
|
|
3015
|
+
delete next[targetPath];
|
|
3016
|
+
changed = true;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
if (changed) {
|
|
3020
|
+
args.entry.renderedTargets = next;
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
|
|
2976
3024
|
function logSyncDryRun({
|
|
2977
3025
|
tool,
|
|
2978
3026
|
entry,
|
|
@@ -3177,6 +3225,11 @@ async function syncManagedToolEntry({
|
|
|
3177
3225
|
dryRun?: boolean;
|
|
3178
3226
|
builtinConflictMode?: "warn" | "overwrite";
|
|
3179
3227
|
}) {
|
|
3228
|
+
pruneAutomationRuntimeRenderedTargets({
|
|
3229
|
+
entry,
|
|
3230
|
+
automationDir: entry.automationDir,
|
|
3231
|
+
});
|
|
3232
|
+
|
|
3180
3233
|
const adoptedSkills = dryRun
|
|
3181
3234
|
? []
|
|
3182
3235
|
: await repairManagedCanonicalContent({
|