@runfusion/fusion 0.49.0 → 0.50.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.
Files changed (121) hide show
  1. package/dist/bin.js +36386 -9279
  2. package/dist/client/.vite/manifest.json +241 -241
  3. package/dist/client/assets/{AgentDetailView-B8_IDafM.js → AgentDetailView-D2oYUApl.js} +5 -5
  4. package/dist/client/assets/{AgentPermissionPolicyEditor-D4U6puHs.js → AgentPermissionPolicyEditor-DGgx46f3.js} +1 -1
  5. package/dist/client/assets/AgentsView-Dhf5Vq_T.js +29 -0
  6. package/dist/client/assets/ChatView-DpgpxHva.js +19 -0
  7. package/dist/client/assets/CommandCenter-B9q-zM9p.css +1 -0
  8. package/dist/client/assets/CommandCenter-DzQhGq_J.js +71 -0
  9. package/dist/client/assets/{DevServerView-C1qppz4E.js → DevServerView-DjJYp6Jj.js} +1 -1
  10. package/dist/client/assets/{DirectoryPicker-CW_Q-Bup.js → DirectoryPicker-CYPYp9T4.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-DGxrLknh.js → DocumentsView-CeQVRVdp.js} +1 -1
  12. package/dist/client/assets/{EvalsView-BMU01K7i.js → EvalsView-D250mVR5.js} +1 -1
  13. package/dist/client/assets/{ExperimentalAgentOnboardingModal-B5Mwwwbz.js → ExperimentalAgentOnboardingModal-B1EEY_bl.js} +1 -1
  14. package/dist/client/assets/{GoalsView-B9Vwqp8k.js → GoalsView-MftVwE9g.js} +1 -1
  15. package/dist/client/assets/{InsightsView-C9fVBn7l.js → InsightsView-CbWItYe8.js} +1 -1
  16. package/dist/client/assets/{MemoryView-Chm5uzjZ.js → MemoryView-Ct0nOTES.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-BhKzgNHh.js → PiExtensionsManager-DSQwngX-.js} +1 -1
  18. package/dist/client/assets/{PluginManager-pEIpbosd.js → PluginManager-DqLxCiNg.js} +1 -1
  19. package/dist/client/assets/{PullRequestView-DG1gPFRJ.js → PullRequestView-D0WgwdAi.js} +1 -1
  20. package/dist/client/assets/{ResearchView-wy7oLnPg.js → ResearchView-CJQL9XKC.js} +1 -1
  21. package/dist/client/assets/{SecretsView-Bid0TP7K.js → SecretsView-ChN9aTbi.js} +1 -1
  22. package/dist/client/assets/{SessionTerminal-B3XCQFRu.js → SessionTerminal-BVon9kbr.js} +1 -1
  23. package/dist/client/assets/SettingsModal-B4WANQnu.js +21 -0
  24. package/dist/client/assets/{SettingsModal-BMpP5CkL.js → SettingsModal-CIC5jhgo.js} +1 -1
  25. package/dist/client/assets/{SetupWizardModal-C_wMNV3H.js → SetupWizardModal-C6EGjmbd.js} +2 -2
  26. package/dist/client/assets/{ShadcnColorPicker-CXxr3ynz.js → ShadcnColorPicker-4e_aXoGX.js} +1 -1
  27. package/dist/client/assets/{SkillsView-DcNtAxQI.js → SkillsView-Dz8dDwfP.js} +1 -1
  28. package/dist/client/assets/{SkillsView-Cfs7HkwW.css → SkillsView-tAW7TTCz.css} +1 -1
  29. package/dist/client/assets/{TodoView-Bt5ZY51H.js → TodoView-DvJFzoOv.js} +1 -1
  30. package/dist/client/assets/WorkflowNodeEditor-DrdTCstS.js +8 -0
  31. package/dist/client/assets/{agentSkills-DlTZq-3b.js → agentSkills-B2dD5Nm5.js} +1 -1
  32. package/dist/client/assets/{app-DH6H4_Pi.js → app-BYq4S7Cb.js} +3 -3
  33. package/dist/client/assets/{app-B7XunYYI.js → app-D27ewSC0.js} +3 -3
  34. package/dist/client/assets/{app-BgStK3QE.js → app-DFLDkeOE.js} +3 -3
  35. package/dist/client/assets/{app-BV3tiBJo.js → app-OPrfgjjn.js} +3 -3
  36. package/dist/client/assets/{app-Cl4147h6.js → app-dATBczl3.js} +3 -3
  37. package/dist/client/assets/{app-ChEDGcMv.js → app-k1K9zAjp.js} +3 -3
  38. package/dist/client/assets/{architectureDiagram-3BPJPVTR-DPevXfXn.js → architectureDiagram-3BPJPVTR-ClO0MnzR.js} +1 -1
  39. package/dist/client/assets/{blockDiagram-GPEHLZMM-iREXBnwN.js → blockDiagram-GPEHLZMM-C4cyy4WE.js} +1 -1
  40. package/dist/client/assets/{c4Diagram-AAUBKEIU-Ds4gntrv.js → c4Diagram-AAUBKEIU-NHPC9TJo.js} +1 -1
  41. package/dist/client/assets/channel-Q2WKocEP.js +1 -0
  42. package/dist/client/assets/{chunk-2J33WTMH-DI8WjOJS.js → chunk-2J33WTMH-CGYIKTVR.js} +1 -1
  43. package/dist/client/assets/{chunk-4BX2VUAB-BSvj2r_a.js → chunk-4BX2VUAB-BkCCD0tE.js} +1 -1
  44. package/dist/client/assets/{chunk-55IACEB6-DXqk_68P.js → chunk-55IACEB6-Be3yOIDe.js} +1 -1
  45. package/dist/client/assets/{chunk-727SXJPM-CbVz6DAi.js → chunk-727SXJPM-DxjyMaQQ.js} +1 -1
  46. package/dist/client/assets/{chunk-AQP2D5EJ-O8p3lAmi.js → chunk-AQP2D5EJ-BC0PjN0W.js} +1 -1
  47. package/dist/client/assets/{chunk-FMBD7UC4-8KJJYFeG.js → chunk-FMBD7UC4-DoWxmM8k.js} +1 -1
  48. package/dist/client/assets/{chunk-ND2GUHAM-D_zeaF-K.js → chunk-ND2GUHAM-EOpuc9pP.js} +1 -1
  49. package/dist/client/assets/{chunk-QZHKN3VN-IzXnlcXm.js → chunk-QZHKN3VN-DiRyx9QZ.js} +1 -1
  50. package/dist/client/assets/classDiagram-4FO5ZUOK-CPFl2Aqd.js +1 -0
  51. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CPFl2Aqd.js +1 -0
  52. package/dist/client/assets/{cose-bilkent-S5V4N54A-DyB8nAG8.js → cose-bilkent-S5V4N54A-CfVoUUxT.js} +1 -1
  53. package/dist/client/assets/{dagre-BM42HDAG-BkFeh-yy.js → dagre-BM42HDAG-BfILvxLb.js} +1 -1
  54. package/dist/client/assets/{dashboard-view-zm0LyiGH.js → dashboard-view-C9YIcx-O.js} +2 -2
  55. package/dist/client/assets/{dashboard-view-Ddw7AZEw.js → dashboard-view-wFC1pe4d.js} +1 -1
  56. package/dist/client/assets/{diagram-2AECGRRQ-D6ccxFD_.js → diagram-2AECGRRQ-6mdmbH-5.js} +1 -1
  57. package/dist/client/assets/{diagram-5GNKFQAL-pYELzWoc.js → diagram-5GNKFQAL-CY7MMwO1.js} +1 -1
  58. package/dist/client/assets/{diagram-KO2AKTUF-DiX408pG.js → diagram-KO2AKTUF-D05XnQqd.js} +1 -1
  59. package/dist/client/assets/{diagram-LMA3HP47-CEZO6Pzz.js → diagram-LMA3HP47-B18FTOJG.js} +1 -1
  60. package/dist/client/assets/{diagram-OG6HWLK6-C4wPGJ9_.js → diagram-OG6HWLK6-xmXlYf5m.js} +1 -1
  61. package/dist/client/assets/{erDiagram-TEJ5UH35-UVoBlCdP.js → erDiagram-TEJ5UH35-4XlgVLCs.js} +1 -1
  62. package/dist/client/assets/{flowDiagram-I6XJVG4X-BHBVkZYH.js → flowDiagram-I6XJVG4X-BYl3ps7f.js} +1 -1
  63. package/dist/client/assets/{folder-open-BS746XqG.js → folder-open-BH6FwmO7.js} +1 -1
  64. package/dist/client/assets/{ganttDiagram-6RSMTGT7-JhbzzuUH.js → ganttDiagram-6RSMTGT7-4vxuH9Dx.js} +1 -1
  65. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-Bf9CFASb.js → gitGraphDiagram-PVQCEYII-BESmFgXT.js} +1 -1
  66. package/dist/client/assets/index-B9jtBwFy.css +1 -0
  67. package/dist/client/assets/index-DFeBJ3GM.js +1302 -0
  68. package/dist/client/assets/{infoDiagram-5YYISTIA-ODZX4ugg.js → infoDiagram-5YYISTIA-DvjdjtwB.js} +1 -1
  69. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BZ4EmuqN.js → ishikawaDiagram-YF4QCWOH-Cis5IoEm.js} +1 -1
  70. package/dist/client/assets/{journeyDiagram-JHISSGLW-DjPguSQp.js → journeyDiagram-JHISSGLW-C20pB1W8.js} +1 -1
  71. package/dist/client/assets/{kanban-definition-UN3LZRKU-CDlWdfMu.js → kanban-definition-UN3LZRKU-CiVfCeU3.js} +1 -1
  72. package/dist/client/assets/{mermaid.core-rI3nyVx8.js → mermaid.core-CW0vCXA6.js} +4 -4
  73. package/dist/client/assets/{mindmap-definition-RKZ34NQL-BduuAfgW.js → mindmap-definition-RKZ34NQL-E5rPHUdw.js} +1 -1
  74. package/dist/client/assets/{pieDiagram-4H26LBE5-Df_vjilh.js → pieDiagram-4H26LBE5-DVIFbPjH.js} +1 -1
  75. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DXVZfmAq.js → quadrantDiagram-W4KKPZXB-C8qPRe_x.js} +1 -1
  76. package/dist/client/assets/{requirementDiagram-4Y6WPE33-UJTrQjTm.js → requirementDiagram-4Y6WPE33-DQ-pwXh3.js} +1 -1
  77. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DDERoUXV.js → sankeyDiagram-5OEKKPKP-BnghLuUc.js} +1 -1
  78. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DaULWS9L.js → sequenceDiagram-3UESZ5HK-ChlGxGVk.js} +1 -1
  79. package/dist/client/assets/{shield-alert-BKmP809x.js → shield-alert-BfOnDO6O.js} +1 -1
  80. package/dist/client/assets/{stateDiagram-AJRCARHV-tbFQ1nvS.js → stateDiagram-AJRCARHV-ByUvNEch.js} +1 -1
  81. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-DQ0cOptx.js → stateDiagram-v2-BHNVJYJU-Df38ITSP.js} +1 -1
  82. package/dist/client/assets/{timeline-definition-PNZ67QCA-BE_RPHyh.js → timeline-definition-PNZ67QCA-bsAGc83D.js} +1 -1
  83. package/dist/client/assets/{upload-CKPEXc39.js → upload-BK1crTqn.js} +1 -1
  84. package/dist/client/assets/{users-BFmWR-bN.js → users-DW8jC2Z9.js} +1 -1
  85. package/dist/client/assets/{vennDiagram-CIIHVFJN-TwZvt3sz.js → vennDiagram-CIIHVFJN-BBIj6LFN.js} +1 -1
  86. package/dist/client/assets/{wardley-L42UT6IY-C6ZavOUS.js → wardley-L42UT6IY-De6JFroy.js} +1 -1
  87. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-B2i11Jb0.js → wardleyDiagram-YWT4CUSO-P1LOLx0_.js} +1 -1
  88. package/dist/client/assets/{xychartDiagram-2RQKCTM6-DbfolZx7.js → xychartDiagram-2RQKCTM6-v1G4ki6U.js} +1 -1
  89. package/dist/client/index.html +2 -2
  90. package/dist/client/version.json +1 -1
  91. package/dist/droid-cli/package.json +1 -1
  92. package/dist/extension.js +32507 -5725
  93. package/dist/pi-claude-cli/index.ts +12 -16
  94. package/dist/pi-claude-cli/package.json +1 -1
  95. package/dist/plugin-sdk/index.d.ts +26 -1
  96. package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
  97. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  98. package/dist/plugins/fusion-plugin-compound-engineering/bundled.js +250 -19
  99. package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
  100. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  101. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  102. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  103. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  104. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  105. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  106. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  107. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  108. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  109. package/package.json +1 -1
  110. package/skill/fusion/references/engine-tools.md +1 -1
  111. package/dist/client/assets/AgentsView-CafZRACA.js +0 -29
  112. package/dist/client/assets/ChatView-CZv_hIdK.js +0 -19
  113. package/dist/client/assets/CommandCenter-B1P49x__.css +0 -1
  114. package/dist/client/assets/CommandCenter-D9qV7yF_.js +0 -71
  115. package/dist/client/assets/SettingsModal-Dptq-qYL.js +0 -21
  116. package/dist/client/assets/WorkflowNodeEditor-AwHxfoz1.js +0 -8
  117. package/dist/client/assets/channel-FTiNgAnt.js +0 -1
  118. package/dist/client/assets/classDiagram-4FO5ZUOK-sIVsNnoj.js +0 -1
  119. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-sIVsNnoj.js +0 -1
  120. package/dist/client/assets/index-BNXyfteR.css +0 -1
  121. package/dist/client/assets/index-DZGZRdnt.js +0 -1302
@@ -25,10 +25,10 @@ import {
25
25
  } from "./src/mcp-config.js";
26
26
 
27
27
  /**
28
- * Route A kill-switch (U11). When `FUSION_CLAUDE_ACP=1` AND a bridge binary path
29
- * is available (`FUSION_CLAUDE_ACP_BRIDGE`, injected by the engine seam per
30
- * KTD10), the provider drives Claude through the ACP bridge instead of
31
- * `claude -p`. OFF by default: the live `-p` path is untouched until soak.
28
+ * FNXC:pi-claude-cli 2026-06-27-06:39:
29
+ * Route A drives Claude through the ACP bridge only when `FUSION_CLAUDE_ACP=1`
30
+ * AND a bridge binary path is injected via `FUSION_CLAUDE_ACP_BRIDGE`.
31
+ * OFF by default: the live `claude -p` path stays untouched until soak.
32
32
  */
33
33
  function resolveAcpBridgePath(): string | undefined {
34
34
  if (process.env.FUSION_CLAUDE_ACP !== "1") return undefined;
@@ -52,14 +52,12 @@ process.on("exit", killAllProcesses);
52
52
  const PROVIDER_ID = "pi-claude-cli";
53
53
 
54
54
  /**
55
- * Run CLI presence + auth probes at most once per process, asynchronously.
56
- *
57
- * The factory below is invoked on every `createFnAgent` call (the dashboard
58
- * does this per chat message). Doing the probes synchronously with execSync
59
- * froze the entire Node event loop for a few seconds while `claude` cold-
60
- * started. Memoizing as a Promise + spawning the probes async means the
61
- * factory returns immediately and other requests keep flowing; the result
62
- * is logged once on first run and reused thereafter.
55
+ * FNXC:pi-claude-cli 2026-06-27-06:39:
56
+ * The factory runs per `createFnAgent` call, which the dashboard does for each
57
+ * chat message. Synchronous `execSync` presence + auth probes froze the Node
58
+ * event loop while `claude` cold-started, so memoize the probes as an async
59
+ * Promise: the factory returns immediately, the result logs once, and later
60
+ * calls reuse it.
63
61
  */
64
62
  let cliValidationPromise: Promise<void> | undefined;
65
63
 
@@ -257,8 +255,7 @@ export default function (pi: ExtensionAPI) {
257
255
  parameters: Record<string, unknown>;
258
256
  }> }).tools;
259
257
 
260
- // Route A (U11): drive Claude through the ACP bridge when the kill-switch
261
- // is on AND a bridge path is injected. OFF by default → `-p` path below.
258
+ // FNXC:pi-claude-cli 2026-06-27-06:39: Route A drives Claude through the ACP bridge only when the kill-switch is on AND a bridge path is injected. OFF by default → `-p` path below.
262
259
  const bridgePath = resolveAcpBridgePath();
263
260
  if (bridgePath) {
264
261
  const toolDefs = resolveToolDefs(pi, contextTools);
@@ -268,8 +265,7 @@ export default function (pi: ExtensionAPI) {
268
265
  ...options,
269
266
  bridgePath,
270
267
  mcpServers: buildAcpMcpServers(toolDefs, hash, userMcpServers),
271
- // Forward only HOME/PATH so the bridged `claude` authenticates from the
272
- // login/keychain session (R17); never inherited process.env or API keys.
268
+ // FNXC:pi-claude-cli 2026-06-27-06:39: Forward only HOME/PATH so the bridged `claude` authenticates from the login/keychain session (R17); never inherited process.env or API keys.
273
269
  bridgeEnv: { HOME: process.env.HOME, PATH: process.env.PATH },
274
270
  });
275
271
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion/pi-claude-cli",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "main": "index.ts",
@@ -2172,6 +2172,12 @@ interface EnsureGitRepositoryOptions {
2172
2172
  }
2173
2173
  declare function ensureGitRepositoryForProjectPath(projectPath: string, options?: EnsureGitRepositoryOptions): Promise<GitRepositoryEnsureOutcome>;
2174
2174
 
2175
+ type RunningAgentCountSource = (projectIds: readonly string[]) => Promise<Record<string, number>> | Record<string, number>;
2176
+ interface RunningAgentCounts {
2177
+ currentlyActive: number;
2178
+ projectsActive: Record<string, number>;
2179
+ }
2180
+
2175
2181
  interface CentralCoreEvents {
2176
2182
  "project:registered": [project: RegisteredProject];
2177
2183
  "project:reattached": [project: RegisteredProject, reason: string];
@@ -2391,6 +2397,9 @@ declare class CentralCore extends EventEmitter<CentralCoreEvents> {
2391
2397
  getDefaultProjectId(): Promise<string | undefined>;
2392
2398
  setDefaultProjectId(projectId: string | null): Promise<void>;
2393
2399
  getGlobalConcurrencyState(): Promise<GlobalConcurrencyState>;
2400
+ getLiveRunningAgentCounts(options?: {
2401
+ source?: RunningAgentCountSource;
2402
+ }): Promise<RunningAgentCounts>;
2394
2403
  updateGlobalConcurrency(updates: Partial<Pick<GlobalConcurrencyState, "globalMaxConcurrent" | "currentlyActive" | "queuedCount">>): Promise<GlobalConcurrencyState>;
2395
2404
  acquireGlobalSlot(projectId: string): Promise<boolean>;
2396
2405
  releaseGlobalSlot(projectId: string): Promise<void>;
@@ -2696,6 +2705,13 @@ declare class TaskStore extends EventEmitter<TaskStoreEvents> {
2696
2705
  reason: string;
2697
2706
  }>;
2698
2707
  }>;
2708
+ reconcilePhantomCommittedReservations(): Promise<{
2709
+ reconciled: string[];
2710
+ skipped: Array<{
2711
+ id: string;
2712
+ reason: string;
2713
+ }>;
2714
+ }>;
2699
2715
  private readTaskJson;
2700
2716
  private writeTaskJsonFile;
2701
2717
  private atomicCreateTaskJson;
@@ -2720,6 +2736,7 @@ declare class TaskStore extends EventEmitter<TaskStoreEvents> {
2720
2736
  private writeConfig;
2721
2737
  resolveLocalNodeIdForTaskAllocation(): Promise<string>;
2722
2738
  private createTaskWithDistributedReservation;
2739
+ private rollbackFailedDistributedReservationCreate;
2723
2740
  private taskDir;
2724
2741
  private artifactRegistryDir;
2725
2742
  private static artifactStoredName;
@@ -2741,6 +2758,10 @@ declare class TaskStore extends EventEmitter<TaskStoreEvents> {
2741
2758
  prompt?: string;
2742
2759
  applyDefaultWorkflowSteps?: boolean;
2743
2760
  invokeTaskCreatedHook?: boolean;
2761
+ reservationCommit?: {
2762
+ reservationId: string;
2763
+ nodeId: string;
2764
+ };
2744
2765
  }): Promise<Task>;
2745
2766
  applyReplicatedTaskCreate(payload: MeshReplicatedTaskCreatePayload): Promise<MeshReplicatedTaskApplyResult>;
2746
2767
  private _createTaskInternal;
@@ -3989,6 +4010,7 @@ declare class Database {
3989
4010
  private applyMigration;
3990
4011
  private hasTable;
3991
4012
  isFts5CorruptionError(error: unknown): boolean;
4013
+ reindexMessages(): void;
3992
4014
  private getTableColumns;
3993
4015
  private hasColumn;
3994
4016
  private tableExists;
@@ -4363,6 +4385,7 @@ interface WorkflowStep {
4363
4385
  prompt: string;
4364
4386
  toolMode?: WorkflowStepToolMode;
4365
4387
  skillName?: string;
4388
+ requiresBrowser?: boolean;
4366
4389
  scriptName?: string;
4367
4390
  enabled: boolean;
4368
4391
  defaultOn?: boolean;
@@ -4372,7 +4395,7 @@ interface WorkflowStep {
4372
4395
  createdAt: string;
4373
4396
  updatedAt: string;
4374
4397
  }
4375
- type NtfyNotificationEvent = "in-review" | "merged" | "failed" | "awaiting-approval" | "awaiting-user-review" | "planning-awaiting-input" | "gridlock" | "board-stall-unrecovered" | "db-corruption-detected" | "fallback-used" | "memory-dreams-processed" | "token-budget" | "message:agent-to-user" | "message:agent-to-agent" | "message:room" | "oauth-token-expired" | "task-created" | "workflow-notify";
4398
+ type NtfyNotificationEvent = "in-review" | "merged" | "failed" | "awaiting-approval" | "awaiting-user-review" | "planning-awaiting-input" | "cli-agent-awaiting-input" | "gridlock" | "board-stall-unrecovered" | "db-corruption-detected" | "fallback-used" | "memory-dreams-processed" | "token-budget" | "message:agent-to-user" | "message:agent-to-agent" | "message:room" | "oauth-token-expired" | "task-created" | "workflow-notify";
4376
4399
  interface NotificationProviderConfig {
4377
4400
  id: string;
4378
4401
  name: string;
@@ -5652,6 +5675,7 @@ interface ProjectSettings {
5652
5675
  verificationCommandTimeoutMs?: number;
5653
5676
  scopeVerificationToChangedFiles?: boolean;
5654
5677
  requirePlanApproval?: boolean;
5678
+ planApprovalMode?: "workflow" | "auto-approve-all" | "require-all";
5655
5679
  ephemeralAgentsEnabled?: boolean;
5656
5680
  agentProvisioning?: {
5657
5681
  approvalMode?: AgentProvisioningApprovalMode;
@@ -6428,6 +6452,7 @@ interface Agent {
6428
6452
  totalOutputTokens?: number;
6429
6453
  lastError?: string;
6430
6454
  pendingApprovalCount?: number;
6455
+ taskColumn?: string;
6431
6456
  instructionsPath?: string;
6432
6457
  instructionsText?: string;
6433
6458
  soul?: string;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion-plugin-examples/acp-runtime",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusion-plugin-examples/cli-printing-press",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "exports": {
@@ -50,6 +50,18 @@ var COMPOUND_ENGINEERING_SKILLS = [
50
50
  enabled: true,
51
51
  triggerPatterns: ["do the work", "implement", "execute the plan", "finish this feature"]
52
52
  },
53
+ {
54
+ /*
55
+ * FNXC:CompoundEngineering 2026-06-27-00:00:
56
+ * FN-7144 bundles ce-doc-review because ce-plan and ce-brainstorm invoke it as a named markdown document-review skill, and the built-in CE workflow now exposes it as an optional advisory stage after planning.
57
+ */
58
+ skillId: "ce-doc-review",
59
+ name: "ce-doc-review",
60
+ description: "Review CE markdown plan documents for coherence, feasibility, scope alignment, and safe markdown-only fixes.",
61
+ skillFiles: ["skills/ce-doc-review/SKILL.md"],
62
+ enabled: true,
63
+ triggerPatterns: ["doc review", "document review", "review the plan", "pressure-test the requirements"]
64
+ },
53
65
  {
54
66
  skillId: "ce-code-review",
55
67
  name: "ce-code-review",
@@ -105,9 +117,23 @@ var COMPOUND_ENGINEERING_SKILLS = [
105
117
  ];
106
118
 
107
119
  // ../../plugins/fusion-plugin-compound-engineering/src/skill-installation.ts
108
- import { cpSync, existsSync, mkdirSync, readFileSync } from "node:fs";
120
+ import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
109
121
  import { dirname, isAbsolute, join, resolve, sep } from "node:path";
110
122
  import { fileURLToPath } from "node:url";
123
+
124
+ // ../../plugins/fusion-plugin-compound-engineering/src/upstream-provenance.ts
125
+ var CE_UPSTREAM_PROVENANCE = {
126
+ repo: "EveryInc/compound-engineering-plugin",
127
+ repoUrl: "https://github.com/EveryInc/compound-engineering-plugin",
128
+ releaseTag: "compound-engineering-v3.15.0",
129
+ releaseUrl: "https://github.com/EveryInc/compound-engineering-plugin/releases/tag/compound-engineering-v3.15.0",
130
+ sourceTarballUrl: "https://github.com/EveryInc/compound-engineering-plugin/archive/refs/tags/compound-engineering-v3.15.0.tar.gz",
131
+ commit: "2bbdbfb1d4287db95af407808b53266988ada974",
132
+ tarballSha256: "fce13e71bd709f8f572bf167c6af3753fc3fde0309c8f878498c78cb391c0b14",
133
+ vendoredAt: "2026-06-26"
134
+ };
135
+
136
+ // ../../plugins/fusion-plugin-compound-engineering/src/skill-installation.ts
111
137
  function resolveBundledSkillsRoot() {
112
138
  const here = fileURLToPath(import.meta.url);
113
139
  const dir = dirname(here);
@@ -143,17 +169,54 @@ function assertValidSkillSource(skillId, sourceDir) {
143
169
  );
144
170
  }
145
171
  }
172
+ var SKILL_INSTALL_PROVENANCE_FILE = ".fusion-ce-upstream-provenance.json";
173
+ function installProvenancePath(targetRoot) {
174
+ return join(targetRoot, SKILL_INSTALL_PROVENANCE_FILE);
175
+ }
176
+ function isCurrentInstalledProvenance(targetRoot) {
177
+ try {
178
+ const marker = JSON.parse(readFileSync(installProvenancePath(targetRoot), "utf-8"));
179
+ return marker.releaseTag === CE_UPSTREAM_PROVENANCE.releaseTag && marker.tarballSha256 === CE_UPSTREAM_PROVENANCE.tarballSha256;
180
+ } catch {
181
+ return false;
182
+ }
183
+ }
184
+ function writeInstalledProvenance(targetRoot) {
185
+ mkdirSync(targetRoot, { recursive: true });
186
+ writeFileSync(
187
+ installProvenancePath(targetRoot),
188
+ `${JSON.stringify(
189
+ {
190
+ repo: CE_UPSTREAM_PROVENANCE.repo,
191
+ releaseTag: CE_UPSTREAM_PROVENANCE.releaseTag,
192
+ commit: CE_UPSTREAM_PROVENANCE.commit,
193
+ tarballSha256: CE_UPSTREAM_PROVENANCE.tarballSha256,
194
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
195
+ },
196
+ null,
197
+ 2
198
+ )}
199
+ `
200
+ );
201
+ }
146
202
  function installBundledCeSkills(options = {}) {
147
203
  const targetRoot = options.targetRoot ? resolve(options.targetRoot) : resolveDefaultInstallTargetRoot();
148
204
  assertPluginLocalTarget(targetRoot);
149
205
  const sourceRoot = options.sourceRoot ? resolve(options.sourceRoot) : resolveBundledSkillsRoot();
206
+ const installIsCurrent = isCurrentInstalledProvenance(targetRoot);
150
207
  const results = COMPOUND_ENGINEERING_SKILLS.map((skill) => {
151
208
  const sourceDir = join(sourceRoot, skill.skillId);
152
209
  const targetDir = join(targetRoot, skill.skillId);
153
210
  try {
154
211
  assertValidSkillSource(skill.skillId, sourceDir);
155
212
  if (existsSync(targetDir)) {
156
- return { skillId: skill.skillId, sourceDir, targetDir, outcome: "skipped", reason: "existing install preserved" };
213
+ if (installIsCurrent) {
214
+ return { skillId: skill.skillId, sourceDir, targetDir, outcome: "skipped", reason: "current install preserved" };
215
+ }
216
+ rmSync(targetDir, { recursive: true, force: true });
217
+ mkdirSync(targetRoot, { recursive: true });
218
+ cpSync(sourceDir, targetDir, { recursive: true });
219
+ return { skillId: skill.skillId, sourceDir, targetDir, outcome: "refreshed", reason: "stale install refreshed" };
157
220
  }
158
221
  mkdirSync(targetRoot, { recursive: true });
159
222
  cpSync(sourceDir, targetDir, { recursive: true });
@@ -168,6 +231,9 @@ function installBundledCeSkills(options = {}) {
168
231
  };
169
232
  }
170
233
  });
234
+ if (results.every((result) => result.outcome !== "error")) {
235
+ writeInstalledProvenance(targetRoot);
236
+ }
171
237
  return { targetRoot, results };
172
238
  }
173
239
  function isPluginLocalPath(p) {
@@ -175,7 +241,7 @@ function isPluginLocalPath(p) {
175
241
  }
176
242
 
177
243
  // ../../plugins/fusion-plugin-compound-engineering/src/agent-installation.ts
178
- import { cpSync as cpSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
244
+ import { cpSync as cpSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
179
245
  import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, resolve as resolve2, sep as sep2 } from "node:path";
180
246
  import { fileURLToPath as fileURLToPath2 } from "node:url";
181
247
  function resolveBundledAgentsRoot() {
@@ -207,10 +273,41 @@ function assertValidAgentSource(agentId, sourceFile) {
207
273
  throw new Error(`Bundled agent def '${agentId}' at ${sourceFile} is missing a frontmatter 'name:' field`);
208
274
  }
209
275
  }
276
+ var AGENT_INSTALL_PROVENANCE_FILE = ".fusion-ce-upstream-provenance.json";
277
+ function installProvenancePath2(targetRoot) {
278
+ return join2(targetRoot, AGENT_INSTALL_PROVENANCE_FILE);
279
+ }
280
+ function isCurrentInstalledProvenance2(targetRoot) {
281
+ try {
282
+ const marker = JSON.parse(readFileSync2(installProvenancePath2(targetRoot), "utf-8"));
283
+ return marker.releaseTag === CE_UPSTREAM_PROVENANCE.releaseTag && marker.tarballSha256 === CE_UPSTREAM_PROVENANCE.tarballSha256;
284
+ } catch {
285
+ return false;
286
+ }
287
+ }
288
+ function writeInstalledProvenance2(targetRoot) {
289
+ mkdirSync2(targetRoot, { recursive: true });
290
+ writeFileSync2(
291
+ installProvenancePath2(targetRoot),
292
+ `${JSON.stringify(
293
+ {
294
+ repo: CE_UPSTREAM_PROVENANCE.repo,
295
+ releaseTag: CE_UPSTREAM_PROVENANCE.releaseTag,
296
+ commit: CE_UPSTREAM_PROVENANCE.commit,
297
+ tarballSha256: CE_UPSTREAM_PROVENANCE.tarballSha256,
298
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
299
+ },
300
+ null,
301
+ 2
302
+ )}
303
+ `
304
+ );
305
+ }
210
306
  function installBundledCeAgents(options = {}) {
211
307
  const targetRoot = options.targetRoot ? resolve2(options.targetRoot) : resolveDefaultAgentsInstallTargetRoot();
212
308
  assertPluginLocalAgentsTarget(targetRoot);
213
309
  const sourceRoot = options.sourceRoot ? resolve2(options.sourceRoot) : resolveBundledAgentsRoot();
310
+ const installIsCurrent = isCurrentInstalledProvenance2(targetRoot);
214
311
  const sourceFiles = existsSync2(sourceRoot) ? readdirSync(sourceRoot).filter((f) => f.endsWith(".md")) : [];
215
312
  const results = sourceFiles.map((file) => {
216
313
  const agentId = file.replace(/\.md$/, "");
@@ -219,7 +316,13 @@ function installBundledCeAgents(options = {}) {
219
316
  try {
220
317
  assertValidAgentSource(agentId, sourceFile);
221
318
  if (existsSync2(targetFile)) {
222
- return { agentId, sourceFile, targetFile, outcome: "skipped", reason: "existing install preserved" };
319
+ if (installIsCurrent) {
320
+ return { agentId, sourceFile, targetFile, outcome: "skipped", reason: "current install preserved" };
321
+ }
322
+ rmSync2(targetFile, { force: true });
323
+ mkdirSync2(targetRoot, { recursive: true });
324
+ cpSync2(sourceFile, targetFile);
325
+ return { agentId, sourceFile, targetFile, outcome: "refreshed", reason: "stale install refreshed" };
223
326
  }
224
327
  mkdirSync2(targetRoot, { recursive: true });
225
328
  cpSync2(sourceFile, targetFile);
@@ -234,6 +337,9 @@ function installBundledCeAgents(options = {}) {
234
337
  };
235
338
  }
236
339
  });
340
+ if (results.length > 0 && results.every((result) => result.outcome !== "error")) {
341
+ writeInstalledProvenance2(targetRoot);
342
+ }
237
343
  return { targetRoot, results };
238
344
  }
239
345
  function isPluginLocalAgentsPath(p) {
@@ -318,7 +424,7 @@ function ensureCeSchema(db) {
318
424
  }
319
425
 
320
426
  // ../../plugins/fusion-plugin-compound-engineering/src/session/orchestrator.ts
321
- import { mkdirSync as mkdirSync3, writeFileSync } from "node:fs";
427
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
322
428
  import { dirname as dirname3, isAbsolute as isAbsolute3, join as join3 } from "node:path";
323
429
 
324
430
  // ../../plugins/fusion-plugin-compound-engineering/src/sync/pipeline-store.ts
@@ -529,13 +635,17 @@ var STAGE_DEFINITIONS = [
529
635
  artifactGlob: "docs/ideation/**/*.md"
530
636
  },
531
637
  {
638
+ /*
639
+ * FNXC:CompoundEngineering 2026-06-27-00:21:
640
+ * Brainstorm and plan keep separate Fusion stage IDs and bundled skill IDs for session, pipeline, and board back-compat, but CE v3.15.0 aliases their durable artifact to one unified docs/plans plan. ce-brainstorm writes the requirements-only unified plan that ce-plan later enriches in place to implementation-ready.
641
+ */
532
642
  stageId: "brainstorm",
533
643
  order: 300,
534
644
  skillId: "ce-brainstorm",
535
- artifactLocation: "docs/brainstorms/",
645
+ artifactLocation: "docs/plans/",
536
646
  icon: "Sparkles",
537
647
  label: "Brainstorm",
538
- artifactGlob: "docs/brainstorms/**/*.md"
648
+ artifactGlob: "docs/plans/**/*.md"
539
649
  },
540
650
  {
541
651
  stageId: "plan",
@@ -891,6 +1001,29 @@ var CeTurnTimeoutError = class extends Error {
891
1001
  function resolveStageSkillPaths() {
892
1002
  return [resolveDefaultInstallTargetRoot()];
893
1003
  }
1004
+ function checkStageSkillResolution(stage, skillPaths = resolveStageSkillPaths()) {
1005
+ const expectedSkillMdPaths = skillPaths.map((root) => join3(root, stage.skillId, "SKILL.md"));
1006
+ return {
1007
+ skillId: stage.skillId,
1008
+ expectedSkillMdPaths,
1009
+ found: expectedSkillMdPaths.some((skillMd) => existsSync3(skillMd))
1010
+ };
1011
+ }
1012
+ function warnIfStageSkillMissing(logger, stage, additionalSkillPaths = resolveStageSkillPaths()) {
1013
+ const guard = checkStageSkillResolution(stage, additionalSkillPaths);
1014
+ if (!guard.found) {
1015
+ logger.warn(
1016
+ `Compound Engineering stage '${stage.stageId}' requested skill '${stage.skillId}', but no SKILL.md was found on the plugin-local discovery paths. The session will still request the skill so the engine can resolve it if installation completes, but the current install appears missing.`,
1017
+ {
1018
+ stageId: stage.stageId,
1019
+ skillId: stage.skillId,
1020
+ expectedSkillMdPaths: guard.expectedSkillMdPaths,
1021
+ additionalSkillPaths
1022
+ }
1023
+ );
1024
+ }
1025
+ return guard;
1026
+ }
894
1027
  function buildStageSystemPrompt(stage) {
895
1028
  return [
896
1029
  `You are running the Compound Engineering "${stage.stageId}" stage.`,
@@ -955,12 +1088,14 @@ var CeOrchestrator = class {
955
1088
  buildSessionOptions(stage, sessionId) {
956
1089
  const defaultProvider = getDefaultProvider(this.ctx.settings);
957
1090
  const defaultModelId = getDefaultModelId(this.ctx.settings);
1091
+ const additionalSkillPaths = resolveStageSkillPaths();
1092
+ warnIfStageSkillMissing(this.ctx.logger, stage, additionalSkillPaths);
958
1093
  return {
959
1094
  cwd: this.projectRoot,
960
1095
  systemPrompt: buildStageSystemPrompt(stage),
961
1096
  tools: "coding",
962
1097
  requestedSkillNames: [stage.skillId],
963
- additionalSkillPaths: resolveStageSkillPaths(),
1098
+ additionalSkillPaths,
964
1099
  onProgress: (event) => this.handleProgress(sessionId, event),
965
1100
  ...defaultProvider ? { defaultProvider } : {},
966
1101
  ...defaultModelId ? { defaultModelId } : {}
@@ -1450,7 +1585,7 @@ var CeOrchestrator = class {
1450
1585
  const target = location.endsWith("/") ? join3(location, `${session.stage}-${session.id}.md`) : location;
1451
1586
  const abs = isAbsolute3(target) ? target : join3(this.projectRoot, target);
1452
1587
  mkdirSync3(dirname3(abs), { recursive: true });
1453
- writeFileSync(abs, content, "utf-8");
1588
+ writeFileSync3(abs, content, "utf-8");
1454
1589
  return abs;
1455
1590
  }
1456
1591
  extractArtifactContent(data) {
@@ -1657,7 +1792,17 @@ function createSessionRoutes() {
1657
1792
  }
1658
1793
 
1659
1794
  // ../../plugins/fusion-plugin-compound-engineering/src/artifacts/discovery.ts
1660
- import { accessSync, constants, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync } from "node:fs";
1795
+ import {
1796
+ accessSync,
1797
+ closeSync,
1798
+ constants,
1799
+ lstatSync,
1800
+ openSync,
1801
+ readFileSync as readFileSync3,
1802
+ readSync,
1803
+ readdirSync as readdirSync2,
1804
+ realpathSync
1805
+ } from "node:fs";
1661
1806
  import { isAbsolute as isAbsolute4, join as join4, relative, sep as sep3 } from "node:path";
1662
1807
  var CONVENTIONAL_LOCATIONS = [
1663
1808
  { stage: "strategy", label: "Strategy", path: "STRATEGY.md", kind: "file" },
@@ -1668,6 +1813,7 @@ var CONVENTIONAL_LOCATIONS = [
1668
1813
  { stage: "concepts", label: "Concepts", path: "CONCEPTS.md", kind: "file" }
1669
1814
  ];
1670
1815
  var MAX_ARTIFACT_BYTES = 2e6;
1816
+ var FRONTMATTER_PREFIX_BYTES = 8 * 1024;
1671
1817
  function toPosix(p) {
1672
1818
  return p.split(sep3).join("/");
1673
1819
  }
@@ -1681,6 +1827,19 @@ function isWithin(root, locationAbs, candidate) {
1681
1827
  function makeId(stage, relPath) {
1682
1828
  return `${stage}:${relPath}`;
1683
1829
  }
1830
+ function isRealPathWithin(root, locationAbs, candidate) {
1831
+ try {
1832
+ const realRoot = realpathSync(root);
1833
+ const realLocation = realpathSync(locationAbs);
1834
+ const realCandidate = realpathSync(candidate);
1835
+ return isWithin(realRoot, realLocation, realCandidate);
1836
+ } catch {
1837
+ return false;
1838
+ }
1839
+ }
1840
+ function symlinkError(stage, relPath) {
1841
+ return makeError(stage, relPath, "Symlink artifacts are not allowed in CE discovery");
1842
+ }
1684
1843
  function makeError(stage, relPath, message) {
1685
1844
  return {
1686
1845
  id: makeId(stage, relPath),
@@ -1691,17 +1850,66 @@ function makeError(stage, relPath, message) {
1691
1850
  error: message
1692
1851
  };
1693
1852
  }
1853
+ function emptyMetadata() {
1854
+ return { artifactContract: null, artifactReadiness: null, productContractSource: null };
1855
+ }
1856
+ function cleanYamlScalar(value) {
1857
+ const trimmed = value.trim();
1858
+ if (!trimmed) return null;
1859
+ const commentStart = trimmed.indexOf(" #");
1860
+ const withoutComment = commentStart >= 0 ? trimmed.slice(0, commentStart).trim() : trimmed;
1861
+ return withoutComment.replace(/^['\"]|['\"]$/g, "") || null;
1862
+ }
1863
+ function parseArtifactMetadata(prefix) {
1864
+ const metadata = emptyMetadata();
1865
+ if (!prefix.startsWith("---")) return metadata;
1866
+ const end = prefix.indexOf("\n---", 3);
1867
+ if (end < 0) return metadata;
1868
+ const frontmatter = prefix.slice(3, end).split(/\r?\n/);
1869
+ for (const line of frontmatter) {
1870
+ const match = /^\s*([A-Za-z0-9_-]+)\s*:\s*(.*?)\s*$/.exec(line);
1871
+ if (!match) continue;
1872
+ const value = cleanYamlScalar(match[2]);
1873
+ if (match[1] === "artifact_contract") metadata.artifactContract = value;
1874
+ if (match[1] === "artifact_readiness") metadata.artifactReadiness = value;
1875
+ if (match[1] === "product_contract_source") metadata.productContractSource = value;
1876
+ }
1877
+ return metadata;
1878
+ }
1879
+ function readMetadataPrefix(abs, size) {
1880
+ let fd;
1881
+ try {
1882
+ fd = openSync(abs, "r");
1883
+ const buffer = Buffer.alloc(Math.min(size, FRONTMATTER_PREFIX_BYTES));
1884
+ const bytesRead = readSync(fd, buffer, 0, buffer.length, 0);
1885
+ return parseArtifactMetadata(buffer.toString("utf8", 0, bytesRead));
1886
+ } catch {
1887
+ return emptyMetadata();
1888
+ } finally {
1889
+ if (fd !== void 0) {
1890
+ try {
1891
+ closeSync(fd);
1892
+ } catch {
1893
+ }
1894
+ }
1895
+ }
1896
+ }
1694
1897
  function readArtifactEntry(stage, root, locationAbs, abs, relPath) {
1695
1898
  const name = relPath.split("/").pop() ?? relPath;
1696
1899
  if (!isWithin(root, locationAbs, abs)) {
1697
1900
  return makeError(stage, relPath, "Path is outside its conventional location");
1698
1901
  }
1699
1902
  try {
1700
- const st = statSync(abs);
1903
+ const st = lstatSync(abs);
1904
+ if (st.isSymbolicLink()) return symlinkError(stage, relPath);
1905
+ if (!isRealPathWithin(root, locationAbs, abs)) {
1906
+ return makeError(stage, relPath, "Path is outside its conventional location");
1907
+ }
1701
1908
  if (st.size > MAX_ARTIFACT_BYTES) {
1702
1909
  return makeError(stage, relPath, `Artifact too large to read (${st.size} bytes)`);
1703
1910
  }
1704
1911
  accessSync(abs, constants.R_OK);
1912
+ const metadata = readMetadataPrefix(abs, st.size);
1705
1913
  return {
1706
1914
  id: makeId(stage, relPath),
1707
1915
  stage,
@@ -1709,6 +1917,7 @@ function readArtifactEntry(stage, root, locationAbs, abs, relPath) {
1709
1917
  name,
1710
1918
  size: st.size,
1711
1919
  updatedAt: st.mtimeMs,
1920
+ ...metadata,
1712
1921
  kind: "artifact"
1713
1922
  };
1714
1923
  } catch (err) {
@@ -1728,13 +1937,17 @@ function discoverLocation(root, loc) {
1728
1937
  let present = false;
1729
1938
  let st;
1730
1939
  try {
1731
- st = statSync(locationAbs);
1940
+ st = lstatSync(locationAbs);
1732
1941
  present = true;
1733
1942
  } catch {
1734
1943
  return { stage: loc.stage, label: loc.label, present: false, entries: [] };
1735
1944
  }
1736
1945
  if (loc.kind === "file") {
1737
- if (st.isFile()) {
1946
+ if (st.isSymbolicLink()) {
1947
+ entries.push(symlinkError(loc.stage, toPosix(loc.path)));
1948
+ } else if (!isRealPathWithin(root, locationAbs, locationAbs)) {
1949
+ entries.push(makeError(loc.stage, toPosix(loc.path), "Path is outside its conventional location"));
1950
+ } else if (st.isFile()) {
1738
1951
  entries.push(readArtifactEntry(loc.stage, root, locationAbs, locationAbs, toPosix(loc.path)));
1739
1952
  } else {
1740
1953
  entries.push(
@@ -1749,6 +1962,14 @@ function discoverLocation(root, loc) {
1749
1962
  }
1750
1963
  let names = [];
1751
1964
  try {
1965
+ if (st.isSymbolicLink()) {
1966
+ entries.push(symlinkError(loc.stage, toPosix(loc.path)));
1967
+ return { stage: loc.stage, label: loc.label, present, entries: sortEntries(entries) };
1968
+ }
1969
+ if (!isRealPathWithin(root, locationAbs, locationAbs)) {
1970
+ entries.push(makeError(loc.stage, toPosix(loc.path), "Path is outside its conventional location"));
1971
+ return { stage: loc.stage, label: loc.label, present, entries: sortEntries(entries) };
1972
+ }
1752
1973
  if (!st.isDirectory()) {
1753
1974
  entries.push(
1754
1975
  makeError(
@@ -1771,11 +1992,15 @@ function discoverLocation(root, loc) {
1771
1992
  const relPath = toPosix(join4(loc.path, childName));
1772
1993
  let childStat;
1773
1994
  try {
1774
- childStat = statSync(abs);
1995
+ childStat = lstatSync(abs);
1775
1996
  } catch (err) {
1776
1997
  entries.push(makeError(loc.stage, relPath, err instanceof Error ? err.message : String(err)));
1777
1998
  continue;
1778
1999
  }
2000
+ if (childStat.isSymbolicLink()) {
2001
+ entries.push(symlinkError(loc.stage, relPath));
2002
+ continue;
2003
+ }
1779
2004
  if (!childStat.isFile()) continue;
1780
2005
  entries.push(readArtifactEntry(loc.stage, root, locationAbs, abs, relPath));
1781
2006
  }
@@ -1818,7 +2043,9 @@ function readArtifactById(projectRoot, id) {
1818
2043
  let mtimeMs;
1819
2044
  let size;
1820
2045
  try {
1821
- const st = statSync(abs);
2046
+ const st = lstatSync(abs);
2047
+ if (st.isSymbolicLink()) return { error: "Symlink artifacts are not allowed in CE discovery" };
2048
+ if (!isRealPathWithin(projectRoot, locationAbs, abs)) return void 0;
1822
2049
  if (!st.isFile()) return { error: "Artifact is not a readable file" };
1823
2050
  if (st.size > MAX_ARTIFACT_BYTES) return { error: `Artifact too large to read (${st.size} bytes)` };
1824
2051
  mtimeMs = st.mtimeMs;
@@ -1841,6 +2068,7 @@ function readArtifactById(projectRoot, id) {
1841
2068
  name,
1842
2069
  size,
1843
2070
  updatedAt: mtimeMs,
2071
+ ...parseArtifactMetadata(content.slice(0, FRONTMATTER_PREFIX_BYTES)),
1844
2072
  kind: "artifact"
1845
2073
  },
1846
2074
  content
@@ -2120,12 +2348,14 @@ var plugin = definePlugin({
2120
2348
  // Install the bundled, pinned ce-* SKILL.md files into a plugin-local,
2121
2349
  // discoverable directory on load. The engine ingests
2122
2350
  // PluginSkillContribution only as a name; physical discovery requires the
2123
- // files to exist on a path it scans (U2 finding). Install is idempotent
2124
- // (skip-if-exists) and guarded to never touch a global ~/.claude/skills.
2351
+ // files to exist on a path it scans (U2 finding). Install is provenance-aware:
2352
+ // current plugin-local copies are skipped, stale/unmarked copies are refreshed,
2353
+ // and global ~/.claude/skills is never touched.
2125
2354
  onLoad: async (ctx) => {
2126
2355
  try {
2127
2356
  const { targetRoot, results } = installBundledCeSkills();
2128
2357
  const installed = results.filter((r) => r.outcome === "installed").length;
2358
+ const refreshed = results.filter((r) => r.outcome === "refreshed").length;
2129
2359
  const errored = results.filter((r) => r.outcome === "error");
2130
2360
  if (errored.length > 0) {
2131
2361
  ctx.logger.warn(
@@ -2133,7 +2363,7 @@ var plugin = definePlugin({
2133
2363
  );
2134
2364
  }
2135
2365
  ctx.logger.info(
2136
- `Compound Engineering skills ready \u2014 installed=${installed} target=${targetRoot}`
2366
+ `Compound Engineering skills ready \u2014 installed=${installed} refreshed=${refreshed} target=${targetRoot}`
2137
2367
  );
2138
2368
  ctx.emitEvent("compound-engineering:skills-installed", { targetRoot, results });
2139
2369
  } catch (error) {
@@ -2143,6 +2373,7 @@ var plugin = definePlugin({
2143
2373
  try {
2144
2374
  const { targetRoot, results } = installBundledCeAgents();
2145
2375
  const installed = results.filter((r) => r.outcome === "installed").length;
2376
+ const refreshed = results.filter((r) => r.outcome === "refreshed").length;
2146
2377
  const errored = results.filter((r) => r.outcome === "error");
2147
2378
  if (errored.length > 0) {
2148
2379
  ctx.logger.warn(
@@ -2150,7 +2381,7 @@ var plugin = definePlugin({
2150
2381
  );
2151
2382
  }
2152
2383
  ctx.logger.info(
2153
- `Compound Engineering agent personas ready \u2014 installed=${installed} target=${targetRoot}`
2384
+ `Compound Engineering agent personas ready \u2014 installed=${installed} refreshed=${refreshed} target=${targetRoot}`
2154
2385
  );
2155
2386
  ctx.emitEvent("compound-engineering:agents-installed", { targetRoot, results });
2156
2387
  } catch (error) {