facult 2.12.0 → 2.13.1
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 +209 -715
- package/assets/packs/facult-operating-model/instructions/CAPABILITY_COMPOSITION.md +1 -1
- package/assets/packs/facult-operating-model/instructions/EVOLUTION.md +1 -1
- package/assets/packs/facult-operating-model/instructions/LEARNING_AND_WRITEBACK.md +1 -1
- package/bin/fclt.cjs +103 -56
- package/docs/README.md +19 -9
- package/docs/assets/fclt-capability-loop.png +0 -0
- package/docs/automations.md +68 -0
- package/docs/built-in-pack.md +10 -4
- package/docs/composable-capability.md +8 -0
- package/docs/concepts.md +11 -1
- package/docs/managed-mode.md +10 -4
- package/docs/project-ai.md +10 -4
- package/docs/reference.md +112 -0
- package/docs/roadmap.md +18 -7
- package/docs/security-trust.md +89 -0
- package/docs/writeback-evolution.md +10 -2
- package/package.json +2 -1
- package/src/builtin-assets.ts +1 -1
- package/src/doctor.ts +307 -5
- package/src/index.ts +6 -0
- package/src/paths-command.ts +223 -0
package/src/doctor.ts
CHANGED
|
@@ -25,8 +25,10 @@ import { parseCliContextArgs, resolveCliContextRoot } from "./cli-context";
|
|
|
25
25
|
import { loadManagedState } from "./manage";
|
|
26
26
|
import { extractServersObject } from "./mcp-config";
|
|
27
27
|
import {
|
|
28
|
+
facultAiEvolutionReviewDir,
|
|
28
29
|
facultAiGraphPath,
|
|
29
30
|
facultAiIndexPath,
|
|
31
|
+
facultAiWritebackReviewDir,
|
|
30
32
|
facultConfigPath,
|
|
31
33
|
facultRootDir,
|
|
32
34
|
facultStateDir,
|
|
@@ -38,9 +40,72 @@ import {
|
|
|
38
40
|
loadConfiguredProjectSyncTools,
|
|
39
41
|
writeProjectSyncPolicy,
|
|
40
42
|
} from "./project-sync";
|
|
43
|
+
import { packageVersion } from "./status";
|
|
41
44
|
|
|
42
45
|
const TOML_FILE_SUFFIX_RE = /\.toml$/;
|
|
43
46
|
|
|
47
|
+
type DoctorHealthState =
|
|
48
|
+
| "healthy"
|
|
49
|
+
| "uninitialized"
|
|
50
|
+
| "partial_global_config"
|
|
51
|
+
| "project_generated_only"
|
|
52
|
+
| "project_policy_attention"
|
|
53
|
+
| "stale_or_missing_generated_state"
|
|
54
|
+
| "legacy_state_attention";
|
|
55
|
+
|
|
56
|
+
interface DoctorIssue {
|
|
57
|
+
severity: "info" | "warning" | "error";
|
|
58
|
+
code: string;
|
|
59
|
+
message: string;
|
|
60
|
+
fix?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface DoctorAction {
|
|
64
|
+
id: string;
|
|
65
|
+
label: string;
|
|
66
|
+
command: string;
|
|
67
|
+
risk:
|
|
68
|
+
| "read_only"
|
|
69
|
+
| "generated_state_write"
|
|
70
|
+
| "canonical_write"
|
|
71
|
+
| "tool_home_write";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface DoctorReport {
|
|
75
|
+
version: 1;
|
|
76
|
+
packageVersion: string;
|
|
77
|
+
cwd: string;
|
|
78
|
+
homeDir: string;
|
|
79
|
+
rootDir: string;
|
|
80
|
+
projectRoot: string | null;
|
|
81
|
+
health: {
|
|
82
|
+
state: DoctorHealthState;
|
|
83
|
+
ok: boolean;
|
|
84
|
+
};
|
|
85
|
+
paths: {
|
|
86
|
+
configPath: string;
|
|
87
|
+
generatedIndex: string;
|
|
88
|
+
generatedGraph: string;
|
|
89
|
+
stateDir: string;
|
|
90
|
+
legacyIndex: string;
|
|
91
|
+
writebackReviewDir: string;
|
|
92
|
+
evolutionReviewDir: string;
|
|
93
|
+
};
|
|
94
|
+
checks: {
|
|
95
|
+
rootExists: boolean;
|
|
96
|
+
canonicalSourceExists: boolean;
|
|
97
|
+
generatedOnlyProjectRoot: boolean;
|
|
98
|
+
generatedIndexSource: "generated" | "legacy" | "rebuilt" | "missing";
|
|
99
|
+
generatedGraphExists: boolean;
|
|
100
|
+
writebackReviewDirExists: boolean;
|
|
101
|
+
evolutionReviewDirExists: boolean;
|
|
102
|
+
projectSyncRepairNeeded: boolean;
|
|
103
|
+
projectSyncRepairTools: string[];
|
|
104
|
+
};
|
|
105
|
+
issues: DoctorIssue[];
|
|
106
|
+
actions: DoctorAction[];
|
|
107
|
+
}
|
|
108
|
+
|
|
44
109
|
function legacyDefaultRoot(home: string): string {
|
|
45
110
|
return join(home, "agents", ".facult");
|
|
46
111
|
}
|
|
@@ -573,29 +638,266 @@ function printHelp() {
|
|
|
573
638
|
console.log(`fclt doctor — inspect and repair local fclt state
|
|
574
639
|
|
|
575
640
|
Usage:
|
|
576
|
-
fclt doctor [--repair] [--root <path> | --global | --project]
|
|
641
|
+
fclt doctor [--json] [--repair] [--root <path> | --global | --project]
|
|
577
642
|
|
|
578
643
|
Options:
|
|
644
|
+
--json Print read-only setup health, issues, and recommended actions
|
|
579
645
|
--repair Reconcile legacy Facult state, canonical root config, AI index/graph, and autosync service config when needed
|
|
580
646
|
`);
|
|
581
647
|
}
|
|
582
648
|
|
|
649
|
+
export async function buildDoctorReport(opts?: {
|
|
650
|
+
cwd?: string;
|
|
651
|
+
homeDir?: string;
|
|
652
|
+
rootArg?: string;
|
|
653
|
+
scope?: "merged" | "global" | "project";
|
|
654
|
+
}): Promise<DoctorReport> {
|
|
655
|
+
const home = opts?.homeDir ?? process.env.HOME?.trim() ?? homedir();
|
|
656
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
657
|
+
const rootDir =
|
|
658
|
+
opts?.rootArg || opts?.scope === "project"
|
|
659
|
+
? resolveCliContextRoot({
|
|
660
|
+
rootArg: opts?.rootArg,
|
|
661
|
+
scope: opts?.scope ?? "merged",
|
|
662
|
+
cwd,
|
|
663
|
+
homeDir: home,
|
|
664
|
+
})
|
|
665
|
+
: facultRootDir(home);
|
|
666
|
+
const projectRoot = projectRootFromAiRoot(rootDir, home);
|
|
667
|
+
const generated = facultAiIndexPath(home, rootDir);
|
|
668
|
+
const generatedGraph = facultAiGraphPath(home, rootDir);
|
|
669
|
+
const legacy = legacyAiIndexPath(rootDir);
|
|
670
|
+
const writebackReviewDir = facultAiWritebackReviewDir(home, rootDir);
|
|
671
|
+
const evolutionReviewDir = facultAiEvolutionReviewDir(home, rootDir);
|
|
672
|
+
|
|
673
|
+
const [
|
|
674
|
+
rootExists,
|
|
675
|
+
canonicalSourceExists,
|
|
676
|
+
generatedOnlyProjectRoot,
|
|
677
|
+
result,
|
|
678
|
+
generatedGraphExists,
|
|
679
|
+
writebackReviewDirExists,
|
|
680
|
+
evolutionReviewDirExists,
|
|
681
|
+
projectSyncPlan,
|
|
682
|
+
] = await Promise.all([
|
|
683
|
+
pathExists(rootDir),
|
|
684
|
+
hasCanonicalSource(rootDir),
|
|
685
|
+
isGeneratedOnlyProjectRoot({ home, rootDir }),
|
|
686
|
+
ensureAiIndexPath({ homeDir: home, rootDir, repair: false }),
|
|
687
|
+
pathExists(generatedGraph),
|
|
688
|
+
pathExists(writebackReviewDir),
|
|
689
|
+
pathExists(evolutionReviewDir),
|
|
690
|
+
planProjectSyncPolicyRepair({ home, rootDir }),
|
|
691
|
+
]);
|
|
692
|
+
|
|
693
|
+
const projectSyncRepairTools = Object.keys(projectSyncPlan.toolPolicies).sort(
|
|
694
|
+
(a, b) => a.localeCompare(b)
|
|
695
|
+
);
|
|
696
|
+
const issues: DoctorIssue[] = [];
|
|
697
|
+
const actions: DoctorAction[] = [];
|
|
698
|
+
|
|
699
|
+
if (!rootExists) {
|
|
700
|
+
issues.push({
|
|
701
|
+
severity: "error",
|
|
702
|
+
code: "missing-root",
|
|
703
|
+
message: "Canonical .ai root does not exist.",
|
|
704
|
+
fix: "Initialize the global operating model or project .ai root.",
|
|
705
|
+
});
|
|
706
|
+
actions.push({
|
|
707
|
+
id: "init-global-operating-model",
|
|
708
|
+
label: "Initialize global operating model",
|
|
709
|
+
command: "fclt templates init operating-model --global",
|
|
710
|
+
risk: "canonical_write",
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (!canonicalSourceExists) {
|
|
715
|
+
issues.push({
|
|
716
|
+
severity: projectRoot ? "error" : "warning",
|
|
717
|
+
code: "missing-canonical-source",
|
|
718
|
+
message:
|
|
719
|
+
"No canonical capability source was found in the selected .ai root.",
|
|
720
|
+
fix: projectRoot
|
|
721
|
+
? "Run fclt templates init project-ai from the repo or restore canonical project assets."
|
|
722
|
+
: "Run fclt templates init operating-model --global.",
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (generatedOnlyProjectRoot) {
|
|
727
|
+
issues.push({
|
|
728
|
+
severity: "error",
|
|
729
|
+
code: "project-generated-only",
|
|
730
|
+
message:
|
|
731
|
+
"Project .ai contains generated state but no canonical project source.",
|
|
732
|
+
fix: "Initialize, restore, or detach canonical project capability before managed project sync.",
|
|
733
|
+
});
|
|
734
|
+
actions.push({
|
|
735
|
+
id: "init-project-ai",
|
|
736
|
+
label: "Initialize project AI root",
|
|
737
|
+
command: "fclt templates init project-ai",
|
|
738
|
+
risk: "canonical_write",
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (result.source === "missing") {
|
|
743
|
+
issues.push({
|
|
744
|
+
severity: "warning",
|
|
745
|
+
code: "missing-generated-index",
|
|
746
|
+
message: "Generated AI index is missing.",
|
|
747
|
+
fix: "Run fclt index or fclt doctor --repair.",
|
|
748
|
+
});
|
|
749
|
+
actions.push({
|
|
750
|
+
id: "rebuild-index",
|
|
751
|
+
label: "Rebuild generated index",
|
|
752
|
+
command: "fclt index",
|
|
753
|
+
risk: "generated_state_write",
|
|
754
|
+
});
|
|
755
|
+
} else if (result.source === "legacy") {
|
|
756
|
+
issues.push({
|
|
757
|
+
severity: "warning",
|
|
758
|
+
code: "legacy-generated-index",
|
|
759
|
+
message: "Generated AI index is still in a legacy location.",
|
|
760
|
+
fix: "Run fclt doctor --repair.",
|
|
761
|
+
});
|
|
762
|
+
actions.push({
|
|
763
|
+
id: "repair-generated-state",
|
|
764
|
+
label: "Repair generated state",
|
|
765
|
+
command: "fclt doctor --repair",
|
|
766
|
+
risk: "generated_state_write",
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
if (!generatedGraphExists) {
|
|
771
|
+
issues.push({
|
|
772
|
+
severity: "info",
|
|
773
|
+
code: "missing-generated-graph",
|
|
774
|
+
message: "Generated capability graph is missing.",
|
|
775
|
+
fix: "Run fclt index or fclt doctor --repair.",
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (!writebackReviewDirExists) {
|
|
780
|
+
issues.push({
|
|
781
|
+
severity: "info",
|
|
782
|
+
code: "missing-writeback-review-dir",
|
|
783
|
+
message: "Global writeback review directory is not present yet.",
|
|
784
|
+
fix: "It will be created when writebacks are recorded or the operating model is initialized.",
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
if (!evolutionReviewDirExists) {
|
|
789
|
+
issues.push({
|
|
790
|
+
severity: "info",
|
|
791
|
+
code: "missing-evolution-review-dir",
|
|
792
|
+
message: "Global evolution review directory is not present yet.",
|
|
793
|
+
fix: "It will be created when proposals are drafted or the operating model is initialized.",
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
if (projectSyncPlan.needed) {
|
|
798
|
+
issues.push({
|
|
799
|
+
severity: "warning",
|
|
800
|
+
code: "implicit-project-sync-policy",
|
|
801
|
+
message: `Project sync is still implicit for managed tools: ${projectSyncRepairTools.join(", ")}.`,
|
|
802
|
+
fix: "Run fclt doctor --repair to materialize explicit project sync policy.",
|
|
803
|
+
});
|
|
804
|
+
actions.push({
|
|
805
|
+
id: "materialize-project-sync-policy",
|
|
806
|
+
label: "Materialize project sync policy",
|
|
807
|
+
command: "fclt doctor --repair",
|
|
808
|
+
risk: "canonical_write",
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
let state: DoctorHealthState = "healthy";
|
|
813
|
+
if (generatedOnlyProjectRoot) {
|
|
814
|
+
state = "project_generated_only";
|
|
815
|
+
} else if (!(rootExists && canonicalSourceExists)) {
|
|
816
|
+
state = "uninitialized";
|
|
817
|
+
} else if (!(writebackReviewDirExists && evolutionReviewDirExists)) {
|
|
818
|
+
state = "partial_global_config";
|
|
819
|
+
} else if (projectSyncPlan.needed) {
|
|
820
|
+
state = "project_policy_attention";
|
|
821
|
+
} else if (result.source === "missing") {
|
|
822
|
+
state = "stale_or_missing_generated_state";
|
|
823
|
+
} else if (result.source === "legacy") {
|
|
824
|
+
state = "legacy_state_attention";
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
return {
|
|
828
|
+
version: 1,
|
|
829
|
+
packageVersion: await packageVersion(),
|
|
830
|
+
cwd,
|
|
831
|
+
homeDir: home,
|
|
832
|
+
rootDir,
|
|
833
|
+
projectRoot,
|
|
834
|
+
health: {
|
|
835
|
+
state,
|
|
836
|
+
ok: state === "healthy" || state === "partial_global_config",
|
|
837
|
+
},
|
|
838
|
+
paths: {
|
|
839
|
+
configPath: facultConfigPath(home),
|
|
840
|
+
generatedIndex: generated,
|
|
841
|
+
generatedGraph,
|
|
842
|
+
stateDir: facultStateDir(home, rootDir),
|
|
843
|
+
legacyIndex: legacy,
|
|
844
|
+
writebackReviewDir,
|
|
845
|
+
evolutionReviewDir,
|
|
846
|
+
},
|
|
847
|
+
checks: {
|
|
848
|
+
rootExists,
|
|
849
|
+
canonicalSourceExists,
|
|
850
|
+
generatedOnlyProjectRoot,
|
|
851
|
+
generatedIndexSource: result.source,
|
|
852
|
+
generatedGraphExists,
|
|
853
|
+
writebackReviewDirExists,
|
|
854
|
+
evolutionReviewDirExists,
|
|
855
|
+
projectSyncRepairNeeded: projectSyncPlan.needed,
|
|
856
|
+
projectSyncRepairTools,
|
|
857
|
+
},
|
|
858
|
+
issues,
|
|
859
|
+
actions,
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
583
863
|
export async function doctorCommand(argv: string[]) {
|
|
584
864
|
if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
|
|
585
865
|
printHelp();
|
|
586
866
|
return;
|
|
587
867
|
}
|
|
588
868
|
|
|
869
|
+
const json = argv.includes("--json");
|
|
589
870
|
const repair = argv.includes("--repair");
|
|
590
871
|
const home = process.env.HOME?.trim() || homedir();
|
|
591
872
|
|
|
592
873
|
try {
|
|
593
|
-
const parsed = parseCliContextArgs(
|
|
874
|
+
const parsed = parseCliContextArgs(
|
|
875
|
+
argv.filter((arg) => arg !== "--json" && arg !== "--repair")
|
|
876
|
+
);
|
|
877
|
+
if (json) {
|
|
878
|
+
if (repair) {
|
|
879
|
+
console.error(
|
|
880
|
+
"doctor --json is read-only; run doctor --repair without --json to mutate state."
|
|
881
|
+
);
|
|
882
|
+
process.exitCode = 1;
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
const report = await buildDoctorReport({
|
|
886
|
+
cwd: process.cwd(),
|
|
887
|
+
homeDir: home,
|
|
888
|
+
rootArg: parsed.rootArg,
|
|
889
|
+
scope: parsed.scope,
|
|
890
|
+
});
|
|
891
|
+
console.log(JSON.stringify(report, null, 2));
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const textParsed = parseCliContextArgs(argv);
|
|
594
896
|
const rootDir =
|
|
595
|
-
|
|
897
|
+
textParsed.rootArg || textParsed.scope === "project"
|
|
596
898
|
? resolveCliContextRoot({
|
|
597
|
-
rootArg:
|
|
598
|
-
scope:
|
|
899
|
+
rootArg: textParsed.rootArg,
|
|
900
|
+
scope: textParsed.scope,
|
|
599
901
|
cwd: process.cwd(),
|
|
600
902
|
homeDir: home,
|
|
601
903
|
})
|
package/src/index.ts
CHANGED
|
@@ -116,6 +116,7 @@ function printHelp() {
|
|
|
116
116
|
"status",
|
|
117
117
|
"Show active roots, managed tools, graph/index, and sync risks",
|
|
118
118
|
],
|
|
119
|
+
["paths", "Show canonical, generated, runtime, and review paths"],
|
|
119
120
|
[
|
|
120
121
|
"audit",
|
|
121
122
|
"Run security audits with interactive or scripted flows",
|
|
@@ -1286,6 +1287,11 @@ async function main(argv: string[]) {
|
|
|
1286
1287
|
case "status":
|
|
1287
1288
|
await import("./status").then(({ statusCommand }) => statusCommand(rest));
|
|
1288
1289
|
return;
|
|
1290
|
+
case "paths":
|
|
1291
|
+
await import("./paths-command").then(({ pathsCommand }) =>
|
|
1292
|
+
pathsCommand(rest)
|
|
1293
|
+
);
|
|
1294
|
+
return;
|
|
1289
1295
|
case "audit":
|
|
1290
1296
|
await import("./audit").then(({ auditCommand }) => auditCommand(rest));
|
|
1291
1297
|
return;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import {
|
|
3
|
+
type CapabilityScopeMode,
|
|
4
|
+
parseCliContextArgs,
|
|
5
|
+
resolveCliContextRoot,
|
|
6
|
+
} from "./cli-context";
|
|
7
|
+
import { renderCode, renderKeyValue, renderPage } from "./cli-ui";
|
|
8
|
+
import { loadManagedState } from "./manage";
|
|
9
|
+
import {
|
|
10
|
+
facultAiDraftDir,
|
|
11
|
+
facultAiEvolutionReviewDir,
|
|
12
|
+
facultAiGraphPath,
|
|
13
|
+
facultAiIndexPath,
|
|
14
|
+
facultAiJournalPath,
|
|
15
|
+
facultAiProposalDir,
|
|
16
|
+
facultAiRuntimeScopeDir,
|
|
17
|
+
facultAiStateDir,
|
|
18
|
+
facultAiWritebackQueuePath,
|
|
19
|
+
facultAiWritebackReviewDir,
|
|
20
|
+
facultConfigPath,
|
|
21
|
+
facultGeneratedStateDir,
|
|
22
|
+
facultInstallStatePath,
|
|
23
|
+
facultLocalCacheRoot,
|
|
24
|
+
facultLocalStateRoot,
|
|
25
|
+
facultMachineStateDir,
|
|
26
|
+
facultRootDir,
|
|
27
|
+
facultRuntimeCacheDir,
|
|
28
|
+
machineStateProjectKey,
|
|
29
|
+
preferredGlobalAiRoot,
|
|
30
|
+
projectRootFromAiRoot,
|
|
31
|
+
} from "./paths";
|
|
32
|
+
|
|
33
|
+
export interface FacultPaths {
|
|
34
|
+
version: 1;
|
|
35
|
+
cwd: string;
|
|
36
|
+
homeDir: string;
|
|
37
|
+
scope: CapabilityScopeMode;
|
|
38
|
+
globalRoot: string;
|
|
39
|
+
contextRoot: string;
|
|
40
|
+
projectRoot: string | null;
|
|
41
|
+
projectKey: string | null;
|
|
42
|
+
canonical: {
|
|
43
|
+
globalRoot: string;
|
|
44
|
+
contextRoot: string;
|
|
45
|
+
configPath: string;
|
|
46
|
+
};
|
|
47
|
+
generated: {
|
|
48
|
+
stateDir: string;
|
|
49
|
+
aiStateDir: string;
|
|
50
|
+
indexPath: string;
|
|
51
|
+
graphPath: string;
|
|
52
|
+
};
|
|
53
|
+
runtime: {
|
|
54
|
+
localStateRoot: string;
|
|
55
|
+
localCacheRoot: string;
|
|
56
|
+
installStatePath: string;
|
|
57
|
+
runtimeCacheDir: string;
|
|
58
|
+
machineStateDir: string;
|
|
59
|
+
aiRuntimeScopeDir: string;
|
|
60
|
+
journalPath: string;
|
|
61
|
+
writebackQueuePath: string;
|
|
62
|
+
proposalDir: string;
|
|
63
|
+
draftDir: string;
|
|
64
|
+
};
|
|
65
|
+
review: {
|
|
66
|
+
writebackDir: string;
|
|
67
|
+
evolutionDir: string;
|
|
68
|
+
};
|
|
69
|
+
managedTools: string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function buildPaths(opts?: {
|
|
73
|
+
cwd?: string;
|
|
74
|
+
homeDir?: string;
|
|
75
|
+
rootArg?: string;
|
|
76
|
+
scope?: CapabilityScopeMode;
|
|
77
|
+
}): Promise<FacultPaths> {
|
|
78
|
+
const homeDir = opts?.homeDir ?? process.env.HOME?.trim() ?? homedir();
|
|
79
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
80
|
+
const scope = opts?.scope ?? "merged";
|
|
81
|
+
const globalRoot = facultRootDir(homeDir);
|
|
82
|
+
const contextRoot = resolveCliContextRoot({
|
|
83
|
+
homeDir,
|
|
84
|
+
cwd,
|
|
85
|
+
rootArg: opts?.rootArg,
|
|
86
|
+
scope,
|
|
87
|
+
});
|
|
88
|
+
const projectRoot = projectRootFromAiRoot(contextRoot, homeDir);
|
|
89
|
+
const managed = await loadManagedState(homeDir, contextRoot);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
version: 1,
|
|
93
|
+
cwd,
|
|
94
|
+
homeDir,
|
|
95
|
+
scope,
|
|
96
|
+
globalRoot,
|
|
97
|
+
contextRoot,
|
|
98
|
+
projectRoot,
|
|
99
|
+
projectKey: projectRoot
|
|
100
|
+
? machineStateProjectKey(contextRoot, homeDir)
|
|
101
|
+
: null,
|
|
102
|
+
canonical: {
|
|
103
|
+
globalRoot: preferredGlobalAiRoot(homeDir),
|
|
104
|
+
contextRoot,
|
|
105
|
+
configPath: facultConfigPath(homeDir),
|
|
106
|
+
},
|
|
107
|
+
generated: {
|
|
108
|
+
stateDir: facultGeneratedStateDir({
|
|
109
|
+
home: homeDir,
|
|
110
|
+
rootDir: contextRoot,
|
|
111
|
+
}),
|
|
112
|
+
aiStateDir: facultAiStateDir(homeDir, contextRoot),
|
|
113
|
+
indexPath: facultAiIndexPath(homeDir, contextRoot),
|
|
114
|
+
graphPath: facultAiGraphPath(homeDir, contextRoot),
|
|
115
|
+
},
|
|
116
|
+
runtime: {
|
|
117
|
+
localStateRoot: facultLocalStateRoot(homeDir),
|
|
118
|
+
localCacheRoot: facultLocalCacheRoot(homeDir),
|
|
119
|
+
installStatePath: facultInstallStatePath(homeDir),
|
|
120
|
+
runtimeCacheDir: facultRuntimeCacheDir(homeDir),
|
|
121
|
+
machineStateDir: facultMachineStateDir(homeDir, contextRoot),
|
|
122
|
+
aiRuntimeScopeDir: facultAiRuntimeScopeDir(homeDir, contextRoot),
|
|
123
|
+
journalPath: facultAiJournalPath(homeDir, contextRoot),
|
|
124
|
+
writebackQueuePath: facultAiWritebackQueuePath(homeDir, contextRoot),
|
|
125
|
+
proposalDir: facultAiProposalDir(homeDir, contextRoot),
|
|
126
|
+
draftDir: facultAiDraftDir(homeDir, contextRoot),
|
|
127
|
+
},
|
|
128
|
+
review: {
|
|
129
|
+
writebackDir: facultAiWritebackReviewDir(homeDir, contextRoot),
|
|
130
|
+
evolutionDir: facultAiEvolutionReviewDir(homeDir, contextRoot),
|
|
131
|
+
},
|
|
132
|
+
managedTools: Object.keys(managed.tools).sort(),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function printHelp() {
|
|
137
|
+
console.log(
|
|
138
|
+
renderPage({
|
|
139
|
+
title: "fclt paths",
|
|
140
|
+
subtitle: "Show canonical, generated, runtime, and review paths.",
|
|
141
|
+
sections: [
|
|
142
|
+
{
|
|
143
|
+
title: "Usage",
|
|
144
|
+
lines: [
|
|
145
|
+
renderCode("fclt paths"),
|
|
146
|
+
renderCode("fclt paths --json"),
|
|
147
|
+
renderCode("fclt paths --project --json"),
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function printPaths(paths: FacultPaths) {
|
|
156
|
+
console.log(
|
|
157
|
+
renderPage({
|
|
158
|
+
title: "fclt paths",
|
|
159
|
+
subtitle: paths.contextRoot,
|
|
160
|
+
sections: [
|
|
161
|
+
{
|
|
162
|
+
title: "Canonical",
|
|
163
|
+
lines: renderKeyValue([
|
|
164
|
+
["global root", paths.globalRoot],
|
|
165
|
+
["context root", paths.contextRoot],
|
|
166
|
+
["project root", paths.projectRoot ?? "(none)"],
|
|
167
|
+
["config", paths.canonical.configPath],
|
|
168
|
+
]),
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
title: "Generated",
|
|
172
|
+
lines: renderKeyValue([
|
|
173
|
+
["state", paths.generated.stateDir],
|
|
174
|
+
["index", paths.generated.indexPath],
|
|
175
|
+
["graph", paths.generated.graphPath],
|
|
176
|
+
]),
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
title: "Runtime",
|
|
180
|
+
lines: renderKeyValue([
|
|
181
|
+
["machine state", paths.runtime.machineStateDir],
|
|
182
|
+
["writeback queue", paths.runtime.writebackQueuePath],
|
|
183
|
+
["proposal dir", paths.runtime.proposalDir],
|
|
184
|
+
["draft dir", paths.runtime.draftDir],
|
|
185
|
+
]),
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
title: "Review",
|
|
189
|
+
lines: renderKeyValue([
|
|
190
|
+
["writebacks", paths.review.writebackDir],
|
|
191
|
+
["evolution", paths.review.evolutionDir],
|
|
192
|
+
]),
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export async function pathsCommand(argv: string[]) {
|
|
200
|
+
if (argv.includes("--help") || argv.includes("-h") || argv[0] === "help") {
|
|
201
|
+
printHelp();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const json = argv.includes("--json");
|
|
206
|
+
try {
|
|
207
|
+
const parsed = parseCliContextArgs(argv.filter((arg) => arg !== "--json"));
|
|
208
|
+
const paths = await buildPaths({
|
|
209
|
+
cwd: process.cwd(),
|
|
210
|
+
homeDir: process.env.HOME?.trim() || homedir(),
|
|
211
|
+
rootArg: parsed.rootArg,
|
|
212
|
+
scope: parsed.scope,
|
|
213
|
+
});
|
|
214
|
+
if (json) {
|
|
215
|
+
console.log(JSON.stringify(paths, null, 2));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
printPaths(paths);
|
|
219
|
+
} catch (err) {
|
|
220
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
221
|
+
process.exitCode = 1;
|
|
222
|
+
}
|
|
223
|
+
}
|