facult 2.2.0 → 2.3.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
@@ -694,13 +694,21 @@ fclt snippets sync [--dry-run] [file...]
694
694
 
695
695
  ### Codex automations
696
696
 
697
- `templates init automation` can scaffold two Codex automation forms:
697
+ `templates init automation` can scaffold three Codex automation forms:
698
698
 
699
699
  - `--scope project` (single repo): set `--project-root` (or infer from current working directory)
700
700
  - `--scope wide|global` (multiple repos): set `--cwds` explicitly; if omitted, created automation has no `cwds` by default.
701
701
  - If you run it interactively without `--scope`, `fclt` prompts for scope and, where possible, known workspaces (git worktrees, configured scan roots, and existing Codex automation paths).
702
702
  - Built-in automation templates are opinionated: they reference the global Codex operating model, point at relevant Codex skills, and tell Codex when to use focused subagents for bounded review work.
703
703
 
704
+ Recommended topology:
705
+
706
+ - Use `learning-review --scope project` for repo-local writeback and evolution. This keeps review state, verification, and follow-up scoped to the repo that actually produced the evidence.
707
+ - Use `evolution-review` on a slower cadence, usually weekly, to triage open proposals and proposal-worthy clusters and suggest the next operator action (`draft`, `review`, `accept`, `reject`, `promote`, or `apply`).
708
+ - Use a separate wide/global automation only for cross-repo or shared-surface review, such as global doctrine, shared skills, or repeated tool/agent patterns across repos.
709
+ - If you do use a wide learning review, keep the `cwds` list intentionally small and related. The prompt is designed to partition by cwd first, not to blur unrelated repos together.
710
+ - A practical default is daily `learning-review` plus weekly `evolution-review`. The first finds and records durable signal; the second keeps proposal review from stalling.
711
+
704
712
  Files are written to:
705
713
 
706
714
  - `~/.codex/automations/<name>/automation.toml`
@@ -725,6 +733,16 @@ fclt templates init automation learning-review \
725
733
  --status PAUSED
726
734
  ```
727
735
 
736
+ Example weekly evolution automation:
737
+
738
+ ```bash
739
+ fclt templates init automation evolution-review \
740
+ --scope wide \
741
+ --cwds /path/to/repo-a,/path/to/repo-b \
742
+ --name weekly-evolution-review \
743
+ --status PAUSED
744
+ ```
745
+
728
746
  Interactive prompt example:
729
747
 
730
748
  ```bash
package/bin/fclt.cjs CHANGED
@@ -38,15 +38,17 @@ async function main() {
38
38
  );
39
39
  const binaryName = resolved.platform === "windows" ? "fclt.exe" : "fclt";
40
40
  const binaryPath = path.join(installDir, binaryName);
41
+ const sourceEntry = path.join(__dirname, "..", "src", "index.ts");
42
+ let installedBinaryThisRun = false;
41
43
 
42
44
  if (!(await fileExists(binaryPath))) {
43
45
  const tag = `v${version}`;
44
46
  const assetName = `${PACKAGE_NAME}-${version}-${resolved.platform}-${resolved.arch}${resolved.ext}`;
45
47
  const url = `https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/${tag}/${assetName}`;
46
-
47
- await fsp.mkdir(installDir, { recursive: true });
48
48
  const tmpPath = `${binaryPath}.tmp-${Date.now()}`;
49
+
49
50
  try {
51
+ await fsp.mkdir(installDir, { recursive: true });
50
52
  await downloadWithRetry(url, tmpPath, {
51
53
  attempts: DOWNLOAD_RETRIES,
52
54
  delayMs: DOWNLOAD_RETRY_DELAY_MS,
@@ -55,8 +57,17 @@ async function main() {
55
57
  await fsp.chmod(tmpPath, 0o755);
56
58
  }
57
59
  await fsp.rename(tmpPath, binaryPath);
60
+ installedBinaryThisRun = true;
58
61
  } catch (error) {
59
62
  await safeUnlink(tmpPath);
63
+ if (await canUseSourceFallback(sourceEntry)) {
64
+ return runSourceFallback({
65
+ sourceEntry,
66
+ version,
67
+ packageManager: detectPackageManager(),
68
+ reason: error,
69
+ });
70
+ }
60
71
  const message =
61
72
  error instanceof Error ? error.message : String(error ?? "");
62
73
  console.error(
@@ -75,12 +86,14 @@ async function main() {
75
86
  }
76
87
 
77
88
  const packageManager = detectPackageManager();
78
- await writeInstallState({
79
- method: "npm-binary-cache",
80
- version,
81
- binaryPath,
82
- packageManager,
83
- });
89
+ if (installedBinaryThisRun) {
90
+ await bestEffortWriteInstallState({
91
+ method: "npm-binary-cache",
92
+ version,
93
+ binaryPath,
94
+ packageManager,
95
+ });
96
+ }
84
97
 
85
98
  const args = process.argv.slice(2);
86
99
  const result = spawnSync(binaryPath, args, {
@@ -100,6 +113,41 @@ async function main() {
100
113
  process.exit(1);
101
114
  }
102
115
 
116
+ async function canUseSourceFallback(sourceEntry) {
117
+ if (!(await fileExists(sourceEntry))) {
118
+ return false;
119
+ }
120
+ const result = spawnSync("bun", ["--version"], {
121
+ stdio: "ignore",
122
+ env: process.env,
123
+ });
124
+ return result.status === 0;
125
+ }
126
+
127
+ function runSourceFallback({ sourceEntry, version, packageManager, reason }) {
128
+ const message =
129
+ reason instanceof Error ? reason.message : String(reason ?? "");
130
+ console.error(
131
+ `fclt: cached runtime unavailable, falling back to Bun source entry (${message})`
132
+ );
133
+ const args = process.argv.slice(2);
134
+ const result = spawnSync("bun", [sourceEntry, ...args], {
135
+ stdio: "inherit",
136
+ env: {
137
+ ...process.env,
138
+ FACULT_INSTALL_METHOD: "npm-source-fallback",
139
+ FACULT_NPM_PACKAGE_VERSION: version,
140
+ FACULT_SOURCE_ENTRY: sourceEntry,
141
+ FACULT_INSTALL_PM: packageManager,
142
+ },
143
+ });
144
+
145
+ if (typeof result.status === "number") {
146
+ process.exit(result.status);
147
+ }
148
+ process.exit(1);
149
+ }
150
+
103
151
  function resolveTarget() {
104
152
  const platform = process.platform;
105
153
  const arch = process.arch;
@@ -257,6 +305,14 @@ async function writeInstallState(state) {
257
305
  await fsp.rename(`${installStatePath}.tmp`, installStatePath);
258
306
  }
259
307
 
308
+ async function bestEffortWriteInstallState(state) {
309
+ try {
310
+ await writeInstallState(state);
311
+ } catch {
312
+ // Install state is useful metadata, but it should not block normal CLI usage.
313
+ }
314
+ }
315
+
260
316
  main().catch((error) => {
261
317
  const message = error instanceof Error ? error.message : String(error ?? "");
262
318
  console.error(message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "facult",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "description": "Manage canonical AI capabilities, sync surfaces, and evolution state.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/ai.ts CHANGED
@@ -171,6 +171,7 @@ interface AddWritebackArgs {
171
171
  summary: string;
172
172
  asset?: string;
173
173
  evidence?: WritebackEvidence[];
174
+ allowEmptyEvidence?: boolean;
174
175
  confidence?: ConfidenceLevel;
175
176
  source?: string;
176
177
  suggestedDestination?: string;
@@ -348,6 +349,12 @@ export async function addWriteback(
348
349
  args: AddWritebackArgs
349
350
  ): Promise<AiWritebackRecord> {
350
351
  const homeDir = args.homeDir ?? process.env.HOME ?? "";
352
+ const evidence = args.evidence ?? [];
353
+ if (evidence.length === 0 && !args.allowEmptyEvidence) {
354
+ throw new Error(
355
+ "writeback add requires at least one evidence item; pass --evidence <type:ref> or use --allow-empty-evidence for scratch/demo notes"
356
+ );
357
+ }
351
358
  const scopeContext = resolveScopeContext(args.rootDir, homeDir);
352
359
  const latest = await latestWritebackMap({
353
360
  homeDir,
@@ -368,7 +375,7 @@ export async function addWriteback(
368
375
  projectRoot: scopeContext.projectRoot,
369
376
  kind: args.kind.trim(),
370
377
  summary: args.summary.trim(),
371
- evidence: args.evidence ?? [],
378
+ evidence,
372
379
  confidence: args.confidence ?? "medium",
373
380
  source: args.source ?? "facult:manual",
374
381
  assetRef: asset.assetRef,
@@ -669,6 +676,9 @@ export async function proposeEvolution(args: {
669
676
  if (entry.status === "dismissed" || entry.status === "superseded") {
670
677
  return false;
671
678
  }
679
+ if (entry.evidence.length === 0) {
680
+ return false;
681
+ }
672
682
  if (filterAsset) {
673
683
  return (
674
684
  entry.assetId === filterAsset.assetId ||
@@ -1405,7 +1415,7 @@ function writebackHelp(): string {
1405
1415
  return `fclt ai writeback
1406
1416
 
1407
1417
  Usage:
1408
- fclt ai writeback add --kind <kind> --summary <text> [--asset <selector>] [--tag <tag>] [--evidence <type:ref>]
1418
+ fclt ai writeback add --kind <kind> --summary <text> [--asset <selector>] [--tag <tag>] [--evidence <type:ref>] [--allow-empty-evidence]
1409
1419
  fclt ai writeback list [--json]
1410
1420
  fclt ai writeback show <id> [--json]
1411
1421
  fclt ai writeback group --by <asset|kind|domain> [--json]
@@ -1521,6 +1531,7 @@ async function writebackCommand(argv: string[]) {
1521
1531
  kind,
1522
1532
  summary,
1523
1533
  asset: parseStringFlag(parsed.argv, "--asset"),
1534
+ allowEmptyEvidence: parsed.argv.includes("--allow-empty-evidence"),
1524
1535
  confidence:
1525
1536
  (parseStringFlag(parsed.argv, "--confidence") as
1526
1537
  | ConfidenceLevel
package/src/remote.ts CHANGED
@@ -338,9 +338,11 @@ const BUILTIN_AUTOMATION_TEMPLATES: BuiltinAutomationTemplate[] = [
338
338
  Use this memory for pattern continuity:
339
339
 
340
340
  - Primary goal: convert repeated, evidence-backed session signal into durable writeback or evolution, not chat-only summary.
341
+ - For wide reviews, partition evidence by cwd first; do not let one repo's evidence stand in for another.
341
342
  - Grounding: prefer evidence from session messages, tool calls, shell commands, diffs, tests, commits, and touched files.
342
343
  - Threshold: only encode signal when you can name what was learned, why it matters, and the most plausible destination.
343
344
  - Scope: default to project writeback unless the signal clearly belongs in global doctrine or a shared capability.
345
+ - Promote to global only when the same signal appears across multiple repos or clearly targets shared doctrine, shared agents, or shared skills.
344
346
  - Verification: distinguish one-off friction from a repeated pattern before escalating it.
345
347
  - If available, use [$feedback-loop-setup]({{feedbackLoopSkill}}) when the review needs stronger feedback loops or verification framing.
346
348
  - If available, use [$capability-evolution]({{capabilityEvolutionSkill}}) when repeated signal should become a concrete proposal.
@@ -358,6 +360,7 @@ Before producing output:
358
360
 
359
361
  Grounding rules:
360
362
  - Work only from evidence in Codex sessions and nearby repo artifacts for the configured CWDs.
363
+ - Partition the review by cwd first. Name which configured cwds had real evidence this run and which did not.
361
364
  - Prefer evidence from session messages, tool calls, shell commands, diffs, tests, commits, and touched files.
362
365
  - Do not speculate about intent or propose changes that are not anchored in evidence.
363
366
  - Distinguish one-off friction from repeated signal. Escalate only when the signal is durable enough to matter.
@@ -366,6 +369,7 @@ Decision rules:
366
369
  - Use \`fclt ai writeback add\` when the signal, target asset, and scope are clear.
367
370
  - Use \`fclt ai evolve\` only when repeated signal is strong enough to justify a reviewable capability change.
368
371
  - Prefer project scope unless the learning clearly belongs in shared global doctrine, shared agents, shared skills, or other cross-project capability.
372
+ - For wide automations, require repeated evidence across more than one cwd before recommending a global/shared capability change unless the target is obviously global.
369
373
  - Skip weak, speculative, or purely anecdotal observations.
370
374
 
371
375
  Verification:
@@ -374,12 +378,77 @@ Verification:
374
378
  - Separate missing context, weak verification, failed execution, and reusable pattern; do not collapse them together.
375
379
 
376
380
  Output:
381
+ - Coverage: which cwds had concrete evidence, and which were effectively idle for this run.
377
382
  - Recorded writebacks: what you recorded, why, and the target asset or command used.
378
383
  - Evolution candidates: only the strongest repeated signals, with rationale and likely scope.
379
384
  - Watch list: promising signals not yet strong enough to encode.
380
385
  - Gaps in current operating model or verification harness: only if evidence supports them.
381
386
 
382
387
  Keep the result concise, high-signal, and operational. If nothing crosses the threshold, say what you reviewed and why no writeback or evolution was justified.`,
388
+ },
389
+ {
390
+ id: "evolution-review",
391
+ title: "Evolution Review Loop",
392
+ description:
393
+ "Weekly Codex review of open evolution proposals and strong writeback clusters, with suggested next actions for review, acceptance, rejection, promotion, or apply.",
394
+ defaultRRule: "RRULE:FREQ=WEEKLY;BYHOUR=16;BYMINUTE=0;BYDAY=FR",
395
+ defaultStatus: "PAUSED",
396
+ defaultModel: "gpt-5.4",
397
+ defaultReasoningEffort: "high",
398
+ scope: "wide",
399
+ memory: `# Evolution Review Loop
400
+
401
+ Use this memory for continuity:
402
+
403
+ - Primary goal: keep proposal review moving so durable changes do not stall after writeback.
404
+ - Review continuity matters: track which proposals were already seen, what changed since the last review, and which action was previously recommended.
405
+ - Prefer reviewing existing proposal and writeback state over rediscovering the entire history from scratch.
406
+ - Scope: default to project evolution unless the proposal clearly belongs in shared doctrine, shared agents, shared skills, or another cross-project capability.
407
+ - For wide reviews, partition by cwd first and only recommend shared/global promotion when evidence truly spans multiple repos or the target asset is obviously shared.
408
+ - Recommend actions, do not silently apply high-risk changes.
409
+ - If available, use [$capability-evolution]({{capabilityEvolutionSkill}}) when proposal shaping or promotion decisions need stronger structure.
410
+ - If available, use [$feedback-loop-setup]({{feedbackLoopSkill}}) when proposal validity depends on weak or stale verification.
411
+ - If available, delegate bounded slices to \`evolution-planner\`, \`scope-promoter\`, \`writeback-curator\`, or \`verification-auditor\` when that materially improves rigor.
412
+ `,
413
+ prompt: `Goal: review current evolution state in the configured CWDs, keep proposal continuity intact, and suggest the highest-signal next actions for draft, review, accept, reject, promote, supersede, or apply.
414
+
415
+ Before producing output:
416
+ - Treat [AGENTS.md]({{codexAgents}}) as the rendered operating-model baseline for this Codex environment.
417
+ - Use [EVOLUTION.md]({{aiEvolution}}), [LEARNING_AND_WRITEBACK.md]({{aiLearningAndWriteback}}), and [VERIFICATION.md]({{aiVerification}}) as the doctrine for proposal quality, thresholding, and proof.
418
+ - Use [FEEDBACK_LOOPS.md]({{aiFeedbackLoops}}) when a proposal depends on a weak or gameable verification loop.
419
+ - If available, use [$capability-evolution]({{capabilityEvolutionSkill}}) when you need stronger proposal-shaping or promotion judgment.
420
+ - If available, use [$feedback-loop-setup]({{feedbackLoopSkill}}) when proposal validity depends on missing or stale verification.
421
+ - If it will materially improve quality, explicitly ask Codex to spawn narrow subagents such as \`evolution-planner\`, \`scope-promoter\`, \`writeback-curator\`, or \`verification-auditor\`. Only use them for bounded, non-overlapping review slices.
422
+
423
+ Grounding rules:
424
+ - Work from concrete proposal and writeback artifacts first, then confirm with nearby repo evidence when needed.
425
+ - Preserve continuity: compare this run against the automation memory and note what is actually new, unchanged, strengthened, weakened, accepted, rejected, or stale.
426
+ - Partition the review by cwd first. Name which configured cwds had real proposal or writeback state this run and which did not.
427
+ - Do not speculate about intent or recommend advancing a proposal without citing the evidence that still supports it.
428
+ - Distinguish proposal quality problems from execution gaps, stale evidence, missing verification, and simple lack of reviewer attention.
429
+
430
+ Decision rules:
431
+ - Prefer suggesting the next operator action over narrating the whole proposal history.
432
+ - Recommend \`draft\` when a proposal exists but is under-specified.
433
+ - Recommend \`review\` or \`accept\` only when the rationale, scope, and evidence are strong enough.
434
+ - Recommend \`apply\` only for already-accepted proposals whose evidence still looks valid and whose risk is appropriate.
435
+ - Recommend \`reject\` or \`supersede\` when the proposal is stale, contradicted, duplicated, or too weak.
436
+ - Recommend \`promote --to global\` only when a project-scoped proposal now clearly belongs in shared doctrine, shared agents, shared skills, or another cross-project surface.
437
+ - For wide automations, require repeated evidence across more than one cwd before recommending shared/global promotion unless the target is obviously global.
438
+
439
+ Verification:
440
+ - Verify every recommendation against at least one concrete artifact.
441
+ - Call out residual uncertainty instead of overstating confidence.
442
+ - If a proposal should move forward but the proof is weak, say exactly what verification is missing.
443
+
444
+ Output:
445
+ - Coverage: which cwds had concrete proposal/writeback evidence, and which were effectively idle for this run.
446
+ - Proposal queue: the strongest active proposals or proposal-worthy clusters, with what changed since the last review.
447
+ - Recommended actions: for each important item, the next operator action and why.
448
+ - Hold or reject: proposals that should stay parked, be rejected, or be superseded.
449
+ - Verification gaps: only the missing proof that materially blocks a recommendation.
450
+
451
+ Keep the result concise, continuity-aware, and operational. If nothing is ready to move, say what you reviewed and why no proposal should advance this run.`,
383
452
  },
384
453
  {
385
454
  id: "tool-call-audit",
@@ -2874,7 +2943,7 @@ export async function templatesCommand(
2874
2943
  const templateId = positional[0];
2875
2944
  if (!templateId) {
2876
2945
  console.error(
2877
- "templates init automation requires a <template-id> (learning-review|tool-call-audit)"
2946
+ "templates init automation requires a <template-id> (learning-review|evolution-review|tool-call-audit)"
2878
2947
  );
2879
2948
  process.exitCode = 2;
2880
2949
  return;