facult 1.1.0 → 1.2.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 CHANGED
@@ -15,14 +15,16 @@
15
15
  </a>
16
16
  </div>
17
17
 
18
- `facult` is a CLI for managing coding-agent configuration across tools.
18
+ `facult` is a CLI for managing canonical AI capability across tools, users, and projects.
19
19
 
20
20
  It helps you:
21
21
  - discover what is installed on your machine
22
22
  - consolidate everything into one canonical store
23
23
  - review trust/security before installing remote content
24
24
  - sync managed outputs into Codex, Cursor, and Claude
25
- - manage a git-backed personal AI store under `~/.ai`
25
+ - manage a git-backed AI store under `~/.ai` and repo-local `.ai/`
26
+ - model relationships between instructions, snippets, agents, skills, and rendered tool outputs
27
+ - preserve learning through writeback and evolve canonical assets over time
26
28
 
27
29
  ## What facult Is
28
30
 
@@ -32,7 +34,126 @@ Think of it as:
32
34
  - inventory + auditing for agent assets
33
35
  - package manager interface for skill/MCP catalogs
34
36
  - sync layer that applies your chosen setup to each tool
35
- - canonical source manager for global AI instructions, agents, snippets, tool configs, and rules
37
+ - canonical source manager for global and project AI instructions, snippets, agents, skills, tool configs, and rules
38
+ - a local capability graph for discovering what exists and what depends on what
39
+ - a writeback/evolution loop for turning repeated friction into durable improvements
40
+
41
+ ## What facult Does
42
+
43
+ `facult` is not just a skill manager.
44
+
45
+ It provides five connected layers:
46
+
47
+ 1. Canonical source
48
+ - global capability in `~/.ai`
49
+ - project capability in `<repo>/.ai`
50
+ - optional built-in Facult capability packs for bootstrap and defaults
51
+ 2. Discovery
52
+ - inventory across skills, agents, snippets, instructions, MCP, and rendered surfaces
53
+ - merged views across builtin, global, and project provenance
54
+ - explicit dependency graph queries
55
+ 3. Sync
56
+ - managed tool outputs for Codex, Claude, Cursor, and other file-backed surfaces
57
+ - rendered docs, agents, skills, MCP, config, and rules
58
+ 4. Automation
59
+ - background autosync for local propagation
60
+ - optional git autosync for the canonical store
61
+ 5. Evolution
62
+ - writeback capture
63
+ - proposal drafting and review
64
+ - controlled apply back into canonical assets
65
+
66
+ ## Default Operating Model
67
+
68
+ `facult` ships with a built-in Facult operating-model pack. That pack includes default:
69
+
70
+ - instructions for evolution, integration, and project capability
71
+ - specialist agents such as `writeback-curator`, `evolution-planner`, and `scope-promoter`
72
+ - skills such as `capability-evolution` and `project-operating-layer-design`
73
+
74
+ When managed sync is enabled, these built-in assets are available by default even if you never copy them into `~/.ai`.
75
+
76
+ That means:
77
+ - builtin skills sync into managed tool skill directories by default
78
+ - builtin agents sync into tool agent directories when the tool supports agents
79
+ - if you do not author your own `AGENTS.global.md`, `facult` renders a builtin global baseline doc into tool-native global docs
80
+
81
+ This is intentionally virtual at the canonical level:
82
+ - builtin defaults remain part of the packaged tool
83
+ - your personal `~/.ai` stays clean unless you explicitly vendor or override something
84
+ - the live tool output on disk still contains the rendered defaults, so users and agents can read them directly
85
+
86
+ In practice, this means you do not need to drive writeback and evolution through the CLI alone. The default skills, agents, and global docs are meant to make that operating model available automatically.
87
+
88
+ If you want to disable the builtin default layer for a specific global or project canonical root:
89
+
90
+ ```toml
91
+ version = 1
92
+
93
+ [builtin]
94
+ sync_defaults = false
95
+ ```
96
+
97
+ Put that in `config.toml` or `config.local.toml` under the active canonical root.
98
+
99
+ ## Core Concepts
100
+
101
+ ### Canonical vs rendered
102
+
103
+ `facult` separates source-of-truth from tool-native output.
104
+
105
+ - canonical source lives in `~/.ai` or `<repo>/.ai`
106
+ - rendered outputs live in tool homes like `~/.codex`, `<repo>/.codex`, `~/.claude`, or `~/.cursor`
107
+ - generated state lives in `~/.facult` or `<repo>/.facult`
108
+
109
+ This keeps authored capability portable and reviewable while still producing the exact files each tool expects.
110
+
111
+ ### Global vs project capability
112
+
113
+ Use global `~/.ai` for reusable personal defaults:
114
+ - cross-project instructions
115
+ - reusable specialist agents
116
+ - shared skills
117
+ - default tool config and rules
118
+
119
+ Use project `.ai/` for repo-owned capability:
120
+ - project-specific instructions and snippets
121
+ - local architecture/testing doctrine
122
+ - project agents and skills that should travel with the codebase
123
+ - repo-local rendered outputs for teammates
124
+
125
+ Project capability is allowed to extend or shadow global capability in merged views, but it does not silently mutate the global source of truth.
126
+
127
+ ### The capability graph
128
+
129
+ `facult` builds a generated graph of explicit relationships between canonical assets and rendered outputs.
130
+
131
+ That graph tracks things like:
132
+ - snippet markers
133
+ - `@ai/...` and `@project/...` refs
134
+ - `${refs.*}` symbolic refs
135
+ - rendered-target edges from canonical source to live tool files
136
+
137
+ This makes it possible to answer:
138
+ - what capability do I already have?
139
+ - what instructions or snippets does this agent depend on?
140
+ - what rendered files change if I update this canonical asset?
141
+ - what project asset is shadowing a global asset?
142
+
143
+ ### Writeback and evolution
144
+
145
+ `facult` treats repeated failures, weak loops, missing context, and reusable patterns as signal worth preserving.
146
+
147
+ Writeback is the act of recording that signal in a structured way.
148
+ Evolution is the act of grouping that signal into reviewable proposals and applying it back into canonical assets.
149
+
150
+ This matters because otherwise the same problems repeat in chat without ever improving the actual operating layer. With `facult`, you can:
151
+ - record a weak verification pattern
152
+ - group repeated writebacks around an instruction or agent
153
+ - draft a proposal to tighten that canonical asset
154
+ - review and apply the change in a controlled way
155
+
156
+ The result is that your AI system can get better over time without hiding mutations in tool-specific state or losing the reasoning behind a change.
36
157
 
37
158
  ## Quick Start
38
159
 
@@ -96,21 +217,57 @@ facult index
96
217
 
97
218
  Why `keep-current`: it is deterministic and non-interactive for duplicate sources.
98
219
 
99
- Canonical source root: `~/.ai`. Generated state remains under `~/.facult`.
220
+ Canonical source root: `~/.ai` for global work, or `<repo>/.ai` for project-local work. Generated state lives next to the active canonical root:
221
+ - global: `~/.facult`
222
+ - project: `<repo>/.facult`
223
+
224
+ ### 3b. Bootstrap a repo-local `.ai`
225
+
226
+ ```bash
227
+ cd /path/to/repo
228
+ bunx facult templates init project-ai
229
+ bunx facult index
230
+ ```
231
+
232
+ This seeds `<repo>/.ai` from the built-in Facult operating-model pack and writes a merged project index/graph under `<repo>/.facult/ai/`.
100
233
 
101
234
  ### 4. Inspect what you have
102
235
 
103
236
  ```bash
104
237
  facult list skills
238
+ facult list instructions
105
239
  facult list mcp
106
240
  facult show requesting-code-review
241
+ facult show instruction:WRITING
107
242
  facult show mcp:github
243
+ facult find verification
244
+ facult graph show instruction:WRITING
245
+ facult graph deps AGENTS.global.md
246
+ facult graph dependents @ai/instructions/WRITING.md
247
+ facult ai writeback add --kind weak_verification --summary "Checks were too shallow" --asset instruction:VERIFICATION
248
+ facult ai evolve propose
249
+ facult ai evolve draft EV-00001
250
+ facult ai evolve accept EV-00001
251
+ facult ai evolve apply EV-00001
252
+ ```
253
+
254
+ Context controls:
255
+
256
+ ```bash
257
+ facult list instructions --global
258
+ facult list instructions --project
259
+ facult find verification --scope merged --source project
260
+ facult sync codex --project
261
+ facult autosync status --global
262
+ facult list agents --root /path/to/repo/.ai
108
263
  ```
109
264
 
110
265
  ### 5. Enable managed mode for your tools
111
266
 
112
267
  ```bash
113
- facult manage codex
268
+ facult manage codex --dry-run
269
+ facult manage codex --adopt-existing
270
+ facult sync codex --builtin-conflicts overwrite
114
271
  facult manage cursor
115
272
  facult manage claude
116
273
 
@@ -119,6 +276,9 @@ facult sync
119
276
  ```
120
277
 
121
278
  At this point, your selected skills are actively synced to all managed tools.
279
+ If you run these commands from inside a repo that has `<repo>/.ai`, `facult` targets the project-local canonical store and repo-local tool outputs by default.
280
+ On first entry to managed mode, use `--dry-run` first if the live tool already has local content. `facult` will show what it would adopt into the active canonical store across skills, agents, docs, rules, config, and MCP, plus any conflicts. Then rerun with `--adopt-existing`; if names or files collide, add `--existing-conflicts keep-canonical` or `--existing-conflicts keep-existing`.
281
+ For builtin-backed rendered defaults, `facult` now tracks the last managed render hash. If a user edits the generated target locally, normal sync warns and preserves that local edit instead of silently overwriting it. To replace the local edit with the latest packaged builtin default, rerun sync with `--builtin-conflicts overwrite`.
122
282
 
123
283
  ### 6. Turn on background autosync
124
284
 
@@ -127,8 +287,8 @@ facult autosync install --git-remote origin --git-branch main --git-interval-min
127
287
  facult autosync status
128
288
  ```
129
289
 
130
- This installs a per-user macOS LaunchAgent that:
131
- - watches `~/.ai` for local changes and syncs managed tool outputs automatically
290
+ This installs a macOS LaunchAgent that:
291
+ - watches the active canonical root (`~/.ai` or `<repo>/.ai`) for local changes and syncs managed tool outputs automatically
132
292
  - tracks dirty state for the canonical repo
133
293
  - runs a slower git autosync loop that batches changes, auto-commits them, rebases on the configured remote branch, and pushes on success
134
294
 
@@ -175,9 +335,9 @@ facult sync
175
335
 
176
336
  Note: `templates init mcp ...` is a scaffold, not a running server by itself.
177
337
 
178
- ## The `~/.ai` Model
338
+ ## The `.ai` Model
179
339
 
180
- `facult` now treats `~/.ai` as the canonical, git-backed source of truth for personal AI configuration.
340
+ `facult` treats both `~/.ai` and `<repo>/.ai` as canonical AI stores. The global store is for personal reusable capability; the project store is for repo-owned capability that should travel with the codebase.
181
341
 
182
342
  Typical layout:
183
343
 
@@ -197,18 +357,42 @@ Typical layout:
197
357
  codex/
198
358
  config.toml
199
359
  rules/
200
- projects/
201
- <slug>/
202
- config.toml
203
- config.local.toml
204
- snippets/
205
- instructions/
360
+ <repo>/
361
+ .ai/
362
+ config.toml
363
+ instructions/
364
+ snippets/
365
+ agents/
366
+ skills/
367
+ tools/
368
+ .facult/
369
+ ai/
370
+ index.json
371
+ graph.json
372
+ .codex/
373
+ .claude/
206
374
  ```
207
375
 
208
376
  Important split:
209
- - `~/.ai` is canonical source
210
- - `~/.facult` is generated state, trust state, managed tool state, autosync state, and caches
211
- - tool homes such as `~/.codex` are rendered outputs
377
+ - `.ai/` is canonical source
378
+ - `.facult/` is generated state, trust state, managed tool state, autosync state, and caches
379
+ - tool homes such as `.codex/` and `.claude/` are rendered outputs
380
+ - the generated capability graph lives at `.facult/ai/graph.json`
381
+
382
+ ### Asset types
383
+
384
+ The canonical store can contain several distinct asset classes:
385
+
386
+ - `instructions/`: reusable doctrine and deeper conceptual guidance
387
+ - `snippets/`: small composable blocks that can be inserted into rendered markdown
388
+ - `agents/`: role-specific agent manifests
389
+ - `skills/`: workflow-specific capability folders
390
+ - `mcp/`: canonical MCP server definitions
391
+ - `tools/<tool>/config.toml`: canonical tool config
392
+ - `tools/<tool>/rules/*.rules`: canonical tool rules
393
+ - global docs such as `AGENTS.global.md` and `AGENTS.override.global.md`
394
+
395
+ Not every asset syncs directly to a tool. Some exist primarily to support rendered outputs or to be discovered and reused by other canonical assets.
212
396
 
213
397
  ### Canonical conventions
214
398
 
@@ -216,6 +400,8 @@ Important split:
216
400
  - Use `snippets/` for composable partial blocks injected into markdown templates
217
401
  - Use `tools/codex/rules/*.rules` for actual Codex approval-policy rules
218
402
  - Use logical refs such as `@ai/instructions/WRITING.md` in tracked source
403
+ - Use `@builtin/facult-operating-model/...` for packaged Facult defaults
404
+ - Use `@project/...` when a tracked ref must resolve inside a repo-local `.ai`
219
405
  - Use config-backed refs in prompts where you want stable named references such as `${refs.writing_rule}`
220
406
 
221
407
  ### Config and env layering
@@ -239,6 +425,8 @@ Built-ins currently include:
239
425
  Recommended split:
240
426
  - `config.toml`: tracked, portable, non-secret refs/defaults
241
427
  - `config.local.toml`: ignored, machine-local paths and secrets
428
+ - `[builtin].sync_defaults = false`: disable builtin default sync/materialization for this root
429
+ - `facult sync --builtin-conflicts overwrite`: allow packaged builtin defaults to overwrite locally modified generated targets
242
430
 
243
431
  ### Snippets
244
432
 
@@ -263,6 +451,92 @@ facult snippets sync [--dry-run] [file...]
263
451
 
264
452
  Snippets are already used during global Codex `AGENTS.md` rendering.
265
453
 
454
+ ### Graph inspection
455
+
456
+ The generated graph in `.facult/ai/graph.json` is queryable directly:
457
+
458
+ ```bash
459
+ facult graph show instruction:WRITING
460
+ facult graph deps AGENTS.global.md
461
+ facult graph dependents @project/instructions/TESTING.md
462
+ ```
463
+
464
+ This is the explicit dependency layer for:
465
+ - snippet markers like `<!-- fclty:... -->`
466
+ - config-backed refs like `${refs.*}`
467
+ - canonical refs like `@ai/...`
468
+ - project refs like `@project/...`
469
+ - rendered outputs such as managed agents, docs, MCP configs, tool configs, and tool rules
470
+
471
+ ### Writeback and evolution
472
+
473
+ `facult` also has a local writeback/evolution substrate built on top of the graph:
474
+
475
+ ```bash
476
+ facult ai writeback add \
477
+ --kind weak_verification \
478
+ --summary "Verification guidance did not distinguish shallow checks from meaningful proof." \
479
+ --asset instruction:VERIFICATION \
480
+ --tag verification \
481
+ --tag false-positive
482
+
483
+ facult ai writeback list
484
+ facult ai writeback show WB-00001
485
+ facult ai writeback group --by asset
486
+ facult ai writeback summarize --by kind
487
+ facult ai evolve propose
488
+ facult ai evolve list
489
+ facult ai evolve show EV-00001
490
+ facult ai evolve draft EV-00001
491
+ facult ai evolve review EV-00001
492
+ facult ai evolve accept EV-00001
493
+ facult ai evolve reject EV-00001 --reason "Needs a tighter draft"
494
+ facult ai evolve supersede EV-00001 --by EV-00002
495
+ facult ai evolve apply EV-00001
496
+ facult ai evolve promote EV-00003 --to global --project
497
+ ```
498
+
499
+ Runtime state stays generated and local:
500
+ - global writeback state: `~/.facult/ai/global/...`
501
+ - project writeback state: `~/.facult/ai/projects/<slug>/...`
502
+
503
+ That split is intentional:
504
+ - canonical source remains in `~/.ai` or `<repo>/.ai`
505
+ - writeback queues, journals, and proposal records stay outside the canonical git-backed tree by default
506
+
507
+ Use writeback when:
508
+ - a task exposed a weak or misleading verification loop
509
+ - an instruction or agent was missing key context
510
+ - a pattern proved reusable enough to become doctrine
511
+ - a project-local pattern deserves promotion toward global capability
512
+
513
+ Do not think of writeback as “taking notes.” Think of it as preserving signal that should change the system, not just the current conversation.
514
+
515
+ For many users, the normal entrypoint is not the CLI directly. The builtin operating-model layer is designed so synced agents, skills, and global docs can push the system toward writeback and evolution by default, while the `facult ai ...` commands remain the explicit operator surface when you want direct control.
516
+
517
+ Current apply semantics are intentionally policy-bound:
518
+ - targets are resolved through the generated graph when possible and fall back to canonical ref resolution for missing assets
519
+ - apply is limited to markdown canonical assets
520
+ - proposals must be drafted before they can be applied; higher-risk proposals still require explicit acceptance
521
+ - supported proposal kinds currently include `create_instruction`, `update_instruction`, `create_agent`, `update_agent`, `update_asset`, `create_asset`, `extract_snippet`, `add_skill`, and `promote_asset`
522
+ - low-risk project-scoped additive proposals such as `create_instruction` can be applied directly after drafting, while global and higher-risk proposals still require review/acceptance
523
+
524
+ Current review/draft semantics:
525
+ - `writeback group` and `writeback summarize` expose recurring patterns across `asset`, `kind`, and `domain` without mutating canonical assets
526
+ - drafted proposals emit both a human-readable markdown draft and a patch artifact under generated state
527
+ - rerunning `evolve draft <id> --append ...` revises the draft and records draft history
528
+ - `evolve promote --to global` creates a new high-risk global proposal from a project-scoped proposal; that promoted proposal can then be drafted, reviewed, and applied into `~/.ai`
529
+
530
+ ### Scope and source selection
531
+
532
+ Most inventory and sync commands support explicit canonical-root selection:
533
+
534
+ - `--global` to force `~/.ai`
535
+ - `--project` to force the nearest repo-local `.ai`
536
+ - `--root /path/to/.ai` to point at a specific canonical root
537
+ - `--scope merged|global|project` for discovery views
538
+ - `--source builtin|global|project` to filter provenance in list/find/show/graph flows
539
+
266
540
  ## Security and Trust
267
541
 
268
542
  `facult` has two trust layers:
@@ -308,9 +582,11 @@ Recommended security flow:
308
582
  - Inventory and discovery
309
583
  ```bash
310
584
  facult scan [--from <path>] [--json] [--show-duplicates]
311
- facult list [skills|mcp|agents|snippets] [--enabled-for <tool>] [--untrusted] [--flagged] [--pending]
585
+ facult list [skills|mcp|agents|snippets|instructions] [--enabled-for <tool>] [--untrusted] [--flagged] [--pending]
312
586
  facult show <name>
587
+ facult show instruction:<name>
313
588
  facult show mcp:<name> [--show-secrets]
589
+ facult find <query> [--json]
314
590
  ```
315
591
 
316
592
  - Canonical store and migration
@@ -322,13 +598,13 @@ facult migrate [--from <path>] [--dry-run] [--move] [--write-config]
322
598
 
323
599
  - Managed mode and rollout
324
600
  ```bash
325
- facult manage <tool>
601
+ facult manage <tool> [--dry-run] [--adopt-existing] [--existing-conflicts keep-canonical|keep-existing]
326
602
  facult unmanage <tool>
327
603
  facult managed
328
604
  facult enable <name> [--for <tool1,tool2,...>]
329
605
  facult enable mcp:<name> [--for <tool1,tool2,...>]
330
606
  facult disable <name> [--for <tool1,tool2,...>]
331
- facult sync [tool] [--dry-run]
607
+ facult sync [tool] [--dry-run] [--builtin-conflicts overwrite]
332
608
  facult autosync install [tool] [--git-remote <name>] [--git-branch <name>] [--git-interval-minutes <n>] [--git-disable]
333
609
  facult autosync status [tool]
334
610
  facult autosync restart [tool]
@@ -351,6 +627,7 @@ facult sources clear <source>
351
627
  - Templates and snippets
352
628
  ```bash
353
629
  facult templates list
630
+ facult templates init project-ai
354
631
  facult templates init skill <name>
355
632
  facult templates init mcp <name>
356
633
  facult templates init snippet <marker>
@@ -374,9 +651,10 @@ facult <command> --help
374
651
 
375
652
  `facult` resolves the canonical root in this order:
376
653
  1. `FACULT_ROOT_DIR`
377
- 2. `~/.facult/config.json` (`rootDir`)
378
- 3. `~/.ai`
379
- 4. `~/agents/.facult` (or a detected legacy store under `~/agents/`)
654
+ 2. nearest project `.ai` from the current working directory for CLI-facing commands
655
+ 3. `~/.facult/config.json` (`rootDir`)
656
+ 4. `~/.ai`
657
+ 5. `~/agents/.facult` (or a detected legacy store under `~/agents/`)
380
658
 
381
659
  ### Runtime env vars
382
660
 
@@ -452,7 +730,7 @@ Default install path is `~/.facult/bin/facult`. You can pass a custom target dir
452
730
 
453
731
  Current v1 behavior:
454
732
  - macOS LaunchAgent-backed
455
- - immediate local managed-tool sync on `~/.ai` file changes
733
+ - immediate local managed-tool sync on the configured canonical root
456
734
  - periodic git autosync for the canonical repo
457
735
  - automatic autosync commits with source-tagged commit messages such as:
458
736
  - `chore(facult-autosync): sync canonical ai changes from <host> [service:all]`
@@ -464,6 +742,14 @@ facult autosync install
464
742
  facult autosync status
465
743
  ```
466
744
 
745
+ Project-local usage:
746
+
747
+ ```bash
748
+ cd /path/to/repo
749
+ facult autosync install codex
750
+ facult autosync status codex
751
+ ```
752
+
467
753
  Tool-scoped service:
468
754
 
469
755
  ```bash
@@ -554,7 +840,7 @@ Not as a first-party `facult mcp serve` runtime.
554
840
  Yes. The core model now includes:
555
841
  - canonical personal AI source in `~/.ai`
556
842
  - rendered managed outputs in tool homes such as `~/.codex`
557
- - global instruction docs such as `AGENTS.global.md`
843
+ - global instruction docs such as `AGENTS.global.md`, rendered by default into `~/.codex/AGENTS.md`, `~/.claude/CLAUDE.md`, and `~/.cursor/AGENTS.md`
558
844
  - tool-native configs such as `~/.codex/config.toml`
559
845
  - tool-native rule files such as `~/.codex/rules/*.rules`
560
846
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "facult",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Manage coding-agent skills and MCP configs across tools.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/agents.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import { join } from "node:path";
2
+ import { facultBuiltinPackRoot } from "./builtin";
2
3
 
3
4
  const AI_REF_RE = /(?<![\w@])@ai\/([^\s"'`<>]+)/g;
5
+ const BUILTIN_REF_RE = /(?<![\w@])@builtin\/([^\s"'`<>]+)/g;
6
+ const PROJECT_REF_RE = /(?<![\w@])@project\/([^\s"'`<>]+)/g;
4
7
  const INTERPOLATION_RE = /\$\{([^}]+)\}/g;
5
8
  const TRAILING_PUNCTUATION_RE = /[.,;:!?)}\]]+$/;
6
9
  const MAX_RENDER_PASSES = 10;
@@ -40,6 +43,24 @@ export function renderAiRefs(input: string, canonicalRoot: string): string {
40
43
  });
41
44
  }
42
45
 
46
+ export function renderBuiltinRefs(input: string): string {
47
+ const builtinRoot = facultBuiltinPackRoot();
48
+ return input.replace(BUILTIN_REF_RE, (_match, refPath: string) => {
49
+ const { path, suffix } = trimTrailingPunctuation(refPath);
50
+ const relative = path.startsWith("facult-operating-model/")
51
+ ? path.slice("facult-operating-model/".length)
52
+ : path;
53
+ return `${join(builtinRoot, relative)}${suffix}`;
54
+ });
55
+ }
56
+
57
+ export function renderProjectRefs(input: string, projectRoot: string): string {
58
+ return input.replace(PROJECT_REF_RE, (_match, refPath: string) => {
59
+ const { path, suffix } = trimTrailingPunctuation(refPath);
60
+ return `${join(projectRoot, ".ai", path)}${suffix}`;
61
+ });
62
+ }
63
+
43
64
  function isPlainObject(value: unknown): value is Record<string, unknown> {
44
65
  return !!value && typeof value === "object" && !Array.isArray(value);
45
66
  }
@@ -169,7 +190,11 @@ export async function renderCanonicalText(
169
190
  seen.add(rendered);
170
191
 
171
192
  const interpolated = interpolateString(rendered, context);
172
- const withRefs = renderAiRefs(interpolated, options.rootDir);
193
+ const withAiRefs = renderAiRefs(interpolated, options.rootDir);
194
+ const withBuiltinRefs = renderBuiltinRefs(withAiRefs);
195
+ const withRefs = options.projectRoot
196
+ ? renderProjectRefs(withBuiltinRefs, options.projectRoot)
197
+ : withBuiltinRefs;
173
198
  if (withRefs === rendered) {
174
199
  return withRefs;
175
200
  }
package/src/ai-state.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { copyFile, mkdir, stat } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import { buildIndex } from "./index-builder";
4
- import { facultAiIndexPath } from "./paths";
4
+ import { facultAiGraphPath, facultAiIndexPath } from "./paths";
5
5
 
6
6
  async function fileExists(path: string): Promise<boolean> {
7
7
  try {
@@ -24,7 +24,7 @@ export async function ensureAiIndexPath(args: {
24
24
  repaired: boolean;
25
25
  source: "generated" | "legacy" | "rebuilt" | "missing";
26
26
  }> {
27
- const generatedPath = facultAiIndexPath(args.homeDir);
27
+ const generatedPath = facultAiIndexPath(args.homeDir, args.rootDir);
28
28
  if (await fileExists(generatedPath)) {
29
29
  return { path: generatedPath, repaired: false, source: "generated" };
30
30
  }
@@ -53,3 +53,28 @@ export async function ensureAiIndexPath(args: {
53
53
 
54
54
  return { path: generatedPath, repaired: false, source: "missing" };
55
55
  }
56
+
57
+ export async function ensureAiGraphPath(args: {
58
+ homeDir: string;
59
+ rootDir: string;
60
+ repair?: boolean;
61
+ }): Promise<{
62
+ path: string;
63
+ rebuilt: boolean;
64
+ }> {
65
+ const generatedPath = facultAiGraphPath(args.homeDir, args.rootDir);
66
+ if (await fileExists(generatedPath)) {
67
+ return { path: generatedPath, rebuilt: false };
68
+ }
69
+
70
+ if (args.repair !== false) {
71
+ const { graphPath } = await buildIndex({
72
+ rootDir: args.rootDir,
73
+ homeDir: args.homeDir,
74
+ force: false,
75
+ });
76
+ return { path: graphPath, rebuilt: true };
77
+ }
78
+
79
+ return { path: generatedPath, rebuilt: false };
80
+ }