@cleocode/adapters 2026.9.19 → 2026.9.21

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 (71) hide show
  1. package/dist/cant-context.d.ts +7 -5
  2. package/dist/cant-context.d.ts.map +1 -1
  3. package/dist/index.js +748 -594
  4. package/dist/index.js.map +3 -3
  5. package/dist/providers/claude-code/adapter.d.ts.map +1 -1
  6. package/dist/providers/claude-code/hooks.d.ts +18 -2
  7. package/dist/providers/claude-code/hooks.d.ts.map +1 -1
  8. package/dist/providers/claude-code/install.d.ts.map +1 -1
  9. package/dist/providers/claude-code/paths.d.ts +96 -0
  10. package/dist/providers/claude-code/paths.d.ts.map +1 -1
  11. package/dist/providers/claude-code/spawn.d.ts +2 -2
  12. package/dist/providers/claude-code/spawn.d.ts.map +1 -1
  13. package/dist/providers/claude-sdk/spawn.d.ts +1 -1
  14. package/dist/providers/codex/adapter.d.ts.map +1 -1
  15. package/dist/providers/codex/spawn.d.ts +1 -1
  16. package/dist/providers/codex/spawn.d.ts.map +1 -1
  17. package/dist/providers/cursor/install.d.ts +11 -22
  18. package/dist/providers/cursor/install.d.ts.map +1 -1
  19. package/dist/providers/gemini-cli/adapter.d.ts.map +1 -1
  20. package/dist/providers/gemini-cli/spawn.d.ts +1 -1
  21. package/dist/providers/gemini-cli/spawn.d.ts.map +1 -1
  22. package/dist/providers/kimi/adapter.d.ts.map +1 -1
  23. package/dist/providers/kimi/spawn.d.ts.map +1 -1
  24. package/dist/providers/opencode/adapter.d.ts.map +1 -1
  25. package/dist/providers/opencode/spawn.d.ts +3 -2
  26. package/dist/providers/opencode/spawn.d.ts.map +1 -1
  27. package/dist/providers/pi/adapter.d.ts +1 -1
  28. package/dist/providers/pi/adapter.d.ts.map +1 -1
  29. package/dist/providers/pi/spawn.d.ts +3 -3
  30. package/dist/providers/pi/spawn.d.ts.map +1 -1
  31. package/dist/providers/shared/agent-spawn-wrapper.d.ts +5 -0
  32. package/dist/providers/shared/agent-spawn-wrapper.d.ts.map +1 -1
  33. package/dist/providers/shared/cli-spawn.d.ts +28 -0
  34. package/dist/providers/shared/cli-spawn.d.ts.map +1 -0
  35. package/dist/providers/shared/credentials.d.ts +2 -2
  36. package/dist/providers/shared/credentials.d.ts.map +1 -1
  37. package/dist/providers/shared/prompt-file.d.ts +32 -0
  38. package/dist/providers/shared/prompt-file.d.ts.map +1 -0
  39. package/package.json +5 -5
  40. package/src/__tests__/agent-spawn-wrapper-win32.test.ts +42 -0
  41. package/src/__tests__/cant-context.test.ts +89 -37
  42. package/src/__tests__/cli-spawn-win32.test.ts +105 -0
  43. package/src/__tests__/spawn-prompt-file.test.ts +37 -0
  44. package/src/cant-context.ts +11 -15
  45. package/src/providers/claude-code/__tests__/adapter.test.ts +1 -0
  46. package/src/providers/claude-code/__tests__/hooks-install.test.ts +63 -2
  47. package/src/providers/claude-code/adapter.ts +6 -11
  48. package/src/providers/claude-code/hooks.ts +60 -89
  49. package/src/providers/claude-code/install.ts +70 -96
  50. package/src/providers/claude-code/paths.ts +155 -0
  51. package/src/providers/claude-code/spawn.ts +11 -25
  52. package/src/providers/claude-sdk/spawn.ts +1 -1
  53. package/src/providers/codex/adapter.ts +6 -11
  54. package/src/providers/codex/spawn.ts +17 -31
  55. package/src/providers/cursor/__tests__/adapter.test.ts +7 -0
  56. package/src/providers/cursor/__tests__/hooks-install.test.ts +36 -0
  57. package/src/providers/cursor/install.ts +86 -133
  58. package/src/providers/gemini-cli/adapter.ts +6 -11
  59. package/src/providers/gemini-cli/spawn.ts +13 -18
  60. package/src/providers/kimi/adapter.ts +6 -11
  61. package/src/providers/kimi/spawn.ts +3 -11
  62. package/src/providers/opencode/__tests__/spawn-prompt-delivery.test.ts +123 -0
  63. package/src/providers/opencode/adapter.ts +6 -11
  64. package/src/providers/opencode/spawn.ts +23 -25
  65. package/src/providers/pi/adapter.ts +8 -18
  66. package/src/providers/pi/spawn.ts +15 -33
  67. package/src/providers/shared/__tests__/credentials-cleo-home.test.ts +42 -0
  68. package/src/providers/shared/agent-spawn-wrapper.ts +22 -2
  69. package/src/providers/shared/cli-spawn.ts +41 -0
  70. package/src/providers/shared/credentials.ts +6 -5
  71. package/src/providers/shared/prompt-file.ts +44 -0
package/dist/index.js CHANGED
@@ -28,9 +28,9 @@ __export(cant_context_exports, {
28
28
  });
29
29
  import { execFile } from "node:child_process";
30
30
  import { existsSync, readdirSync, readFileSync } from "node:fs";
31
- import { homedir } from "node:os";
32
31
  import { basename, join } from "node:path";
33
32
  import { promisify } from "node:util";
33
+ import { getCleoHome, getCleoPlatformPaths } from "@cleocode/paths";
34
34
  function discoverCantFiles(dir) {
35
35
  try {
36
36
  const entries = readdirSync(dir, { recursive: true, withFileTypes: true });
@@ -47,12 +47,9 @@ function discoverCantFiles(dir) {
47
47
  }
48
48
  }
49
49
  function resolveThreeTierPaths(projectDir) {
50
- const home = homedir();
51
- const xdgData = process.env["XDG_DATA_HOME"] ?? join(home, ".local", "share");
52
- const xdgConfig = process.env["XDG_CONFIG_HOME"] ?? join(home, ".config");
53
50
  return {
54
- global: join(xdgData, "cleo", "cant"),
55
- user: join(xdgConfig, "cleo", "cant"),
51
+ global: join(getCleoHome(), "cant"),
52
+ user: join(getCleoPlatformPaths().config, "cant"),
56
53
  project: join(projectDir, ".cleo", "cant")
57
54
  };
58
55
  }
@@ -139,9 +136,7 @@ function readIdentityFile(projectDir) {
139
136
  } catch {
140
137
  }
141
138
  }
142
- const home = homedir();
143
- const xdgData = process.env["XDG_DATA_HOME"] ?? join(home, ".local", "share");
144
- const globalPath = join(xdgData, "cleo", "CLEOOS-IDENTITY.md");
139
+ const globalPath = join(getCleoHome(), "CLEOOS-IDENTITY.md");
145
140
  if (existsSync(globalPath)) {
146
141
  try {
147
142
  const content = readFileSync(globalPath, "utf-8");
@@ -747,7 +742,17 @@ var init_acceptance_gate_schema = __esm({
747
742
  message: "Absent input requires both null digest and size"
748
743
  });
749
744
  })
750
- )
745
+ ),
746
+ tree: z.object({
747
+ headSha: z.string().regex(/^[0-9a-f]{40}$/),
748
+ clean: z.boolean(),
749
+ cwd: z.string().min(1).refine(
750
+ (v) => !/^(?:\/|[A-Za-z]:[\\/]|\\\\)/.test(v) && !v.split(/[\\/]/).includes(".."),
751
+ "Tree-relative cwd must be relative and stay inside the tree"
752
+ ),
753
+ inputsHash: z.string().regex(/^[a-f0-9]{64}$/),
754
+ baseSha: z.string().regex(/^[0-9a-f]{40}$/).optional()
755
+ }).strict().optional()
751
756
  }).strict().superRefine((binding, context) => {
752
757
  if (new Set(binding.artifacts.map(({ path }) => path)).size !== binding.artifacts.length)
753
758
  context.addIssue({
@@ -777,8 +782,18 @@ var init_acceptance_gate_schema = __esm({
777
782
  errorMessage: z.string().optional(),
778
783
  /** ISO 8601 timestamp. */
779
784
  checkedAt: z.string().datetime(),
780
- checkedBy: z.string().min(1)
785
+ checkedBy: z.string().min(1),
786
+ /** T12621: `executed` in this run, or an authenticated pass reused from the cache. */
787
+ source: z.enum(["executed", "cache"]).optional(),
788
+ /** T12621: creation time of the reused cache entry. */
789
+ cachedAt: z.string().datetime().optional()
781
790
  }).superRefine((result, context) => {
791
+ if (result.source === "cache" !== (result.cachedAt !== void 0))
792
+ context.addIssue({
793
+ code: "custom",
794
+ path: ["cachedAt"],
795
+ message: "A cached result carries its cache time, and only a cached result does"
796
+ });
782
797
  const binding = result.binding;
783
798
  if (binding) {
784
799
  if (binding.identity.actor !== result.checkedBy)
@@ -2778,7 +2793,8 @@ var init_tasks = __esm({
2778
2793
  }
2779
2794
  }
2780
2795
  },
2781
- removeRelates: { type: "array", items: { type: "string" } }
2796
+ removeRelates: { type: "array", items: { type: "string" } },
2797
+ expectedUpdatedAt: { type: "string", minLength: 1 }
2782
2798
  }
2783
2799
  };
2784
2800
  tasksUpdateInputContract = {
@@ -5735,6 +5751,13 @@ var init_operations_registry = __esm({
5735
5751
  required: false,
5736
5752
  description: "Disable automatic parent completion",
5737
5753
  cli: { flag: "no-auto-complete" }
5754
+ },
5755
+ {
5756
+ name: "expectedUpdatedAt",
5757
+ type: "string",
5758
+ required: false,
5759
+ description: "Optimistic-concurrency guard: fail with E_CONFLICT unless the task updatedAt still equals this value",
5760
+ cli: { flag: "expected-updated-at" }
5738
5761
  }
5739
5762
  ],
5740
5763
  inputSchema: tasksUpdateInputContract,
@@ -9513,6 +9536,42 @@ var init_operations_registry = __esm({
9513
9536
  }
9514
9537
  ]
9515
9538
  },
9539
+ {
9540
+ gateway: "mutate",
9541
+ domain: "nexus",
9542
+ operation: "projects.status",
9543
+ description: "nexus.projects.status (mutate) \u2014 probe and record git state of every project location on this device (bounded, parallel; no network unless fetch)",
9544
+ tier: 1,
9545
+ idempotent: true,
9546
+ sessionRequired: false,
9547
+ requiredParams: [],
9548
+ params: [
9549
+ {
9550
+ name: "fetch",
9551
+ type: "boolean",
9552
+ required: false,
9553
+ description: "Run git fetch per location first (network; off by default)"
9554
+ },
9555
+ {
9556
+ name: "concurrency",
9557
+ type: "number",
9558
+ required: false,
9559
+ description: "Locations probed at once (default 8, max 64)"
9560
+ },
9561
+ {
9562
+ name: "timeoutMs",
9563
+ type: "number",
9564
+ required: false,
9565
+ description: "Per-location git budget in ms (default 10000; 30000 with fetch)"
9566
+ },
9567
+ {
9568
+ name: "staleAfterMs",
9569
+ type: "number",
9570
+ required: false,
9571
+ description: "A fetch older than this marks remote state stale (default 24h)"
9572
+ }
9573
+ ]
9574
+ },
9516
9575
  {
9517
9576
  gateway: "mutate",
9518
9577
  domain: "nexus",
@@ -10688,7 +10747,13 @@ var init_operations_registry = __esm({
10688
10747
  },
10689
10748
  { name: "title", type: "string", required: false, description: "Override PR title" },
10690
10749
  { name: "body", type: "string", required: false, description: "Override PR body" },
10691
- { name: "base", type: "string", required: false, description: "PR base branch" }
10750
+ { name: "base", type: "string", required: false, description: "PR base branch" },
10751
+ {
10752
+ name: "taskId",
10753
+ type: "string",
10754
+ required: false,
10755
+ description: "Owning task id (T####) for the PR commit subject"
10756
+ }
10692
10757
  ]
10693
10758
  },
10694
10759
  {
@@ -10721,7 +10786,13 @@ var init_operations_registry = __esm({
10721
10786
  },
10722
10787
  { name: "title", type: "string", required: false, description: "Override PR title" },
10723
10788
  { name: "body", type: "string", required: false, description: "Override PR body" },
10724
- { name: "base", type: "string", required: false, description: "PR base branch" }
10789
+ { name: "base", type: "string", required: false, description: "PR base branch" },
10790
+ {
10791
+ name: "taskId",
10792
+ type: "string",
10793
+ required: false,
10794
+ description: "Owning task id (T####) for the PR commit subject"
10795
+ }
10725
10796
  ]
10726
10797
  },
10727
10798
  {
@@ -13237,7 +13308,7 @@ var init_errors = __esm({
13237
13308
 
13238
13309
  // packages/contracts/src/evidence-atom-schema.ts
13239
13310
  import { z as z10 } from "zod";
13240
- var EVIDENCE_ATOM_KINDS, PARSER_PREFIXES, KIND_BOUNDARY_REGEX, commitAtomSchema, filesAtomSchema, testRunAtomSchema, toolAtomSchema, urlAtomSchema, noteAtomSchema, decisionAtomSchema, prAtomSchema, locDropAtomSchema, callsiteCoverageAtomSchema, AC_UUID_REGEX, AC_ALIAS_REGEX, SATISFIES_TASK_ID_REGEX, SATISFIES_VERSION_PIN_REGEX, satisfiesAtomSchema, EvidenceAtomSchema, GATE_EVIDENCE_REQUIREMENTS, ATOM_EXAMPLES;
13311
+ var EVIDENCE_ATOM_KINDS, PARSER_PREFIXES, KIND_BOUNDARY_REGEX, commitAtomSchema, filesAtomSchema, testRunAtomSchema, toolAtomSchema, urlAtomSchema, noteAtomSchema, decisionAtomSchema, prAtomSchema, ciAtomSchema, locDropAtomSchema, callsiteCoverageAtomSchema, AC_UUID_REGEX, AC_ALIAS_REGEX, SATISFIES_TASK_ID_REGEX, SATISFIES_VERSION_PIN_REGEX, satisfiesAtomSchema, EvidenceAtomSchema, GATE_EVIDENCE_REQUIREMENTS, ATOM_EXAMPLES;
13241
13312
  var init_evidence_atom_schema = __esm({
13242
13313
  "packages/contracts/src/evidence-atom-schema.ts"() {
13243
13314
  "use strict";
@@ -13250,6 +13321,7 @@ var init_evidence_atom_schema = __esm({
13250
13321
  "note",
13251
13322
  "decision",
13252
13323
  "pr",
13324
+ "ci",
13253
13325
  "loc-drop",
13254
13326
  "callsite-coverage",
13255
13327
  "satisfies"
@@ -13288,6 +13360,10 @@ var init_evidence_atom_schema = __esm({
13288
13360
  kind: z10.literal("pr"),
13289
13361
  prNumber: z10.number().int().positive("pr atom requires a positive integer PR number")
13290
13362
  });
13363
+ ciAtomSchema = z10.object({
13364
+ kind: z10.literal("ci"),
13365
+ prNumber: z10.number().int().positive("ci atom requires a positive integer PR number")
13366
+ });
13291
13367
  locDropAtomSchema = z10.object({
13292
13368
  kind: z10.literal("loc-drop"),
13293
13369
  fromLines: z10.number().int().nonnegative("loc-drop fromLines must be \u2265 0"),
@@ -13325,6 +13401,7 @@ var init_evidence_atom_schema = __esm({
13325
13401
  noteAtomSchema,
13326
13402
  decisionAtomSchema,
13327
13403
  prAtomSchema,
13404
+ ciAtomSchema,
13328
13405
  locDropAtomSchema,
13329
13406
  callsiteCoverageAtomSchema,
13330
13407
  satisfiesAtomSchema
@@ -13339,8 +13416,8 @@ var init_evidence_atom_schema = __esm({
13339
13416
  ["pr", "files"]
13340
13417
  ]
13341
13418
  },
13342
- testsPassed: { oneOf: [["test-run"], ["tool"]] },
13343
- qaPassed: { oneOf: [["tool"]] },
13419
+ testsPassed: { oneOf: [["test-run"], ["tool"], ["ci"]] },
13420
+ qaPassed: { oneOf: [["tool"], ["ci"]] },
13344
13421
  documented: { oneOf: [["files"], ["url"]] },
13345
13422
  securityPassed: { oneOf: [["tool"], ["note"]] },
13346
13423
  cleanupDone: { oneOf: [["note"]] },
@@ -13355,6 +13432,7 @@ var init_evidence_atom_schema = __esm({
13355
13432
  note: "note:<short description>",
13356
13433
  decision: "decision:D-arch-001",
13357
13434
  pr: "pr:357",
13435
+ ci: "ci:357",
13358
13436
  "loc-drop": "loc-drop:<fromLines>:<toLines>",
13359
13437
  "callsite-coverage": "callsite-coverage:<symbolName>:<relativeSourcePath>",
13360
13438
  satisfies: "satisfies:T1234#AC2"
@@ -15163,6 +15241,13 @@ var init_peer = __esm({
15163
15241
  }
15164
15242
  });
15165
15243
 
15244
+ // packages/contracts/src/process-probe.ts
15245
+ var init_process_probe = __esm({
15246
+ "packages/contracts/src/process-probe.ts"() {
15247
+ "use strict";
15248
+ }
15249
+ });
15250
+
15166
15251
  // packages/contracts/src/release/evidence-atoms.ts
15167
15252
  import { z as z16 } from "zod";
15168
15253
  var parsedPrEvidenceAtomSchema, prEvidenceStateModifierSchema, ghPrViewSchema, PR_REQUIRED_WORKFLOWS;
@@ -15249,7 +15334,12 @@ var init_plan = __esm({
15249
15334
  "hotfix"
15250
15335
  ];
15251
15336
  IMPACT = ["major", "minor", "patch"];
15252
- RESOLVED_SOURCE = ["project-context", "language-default", "legacy-alias"];
15337
+ RESOLVED_SOURCE = [
15338
+ "project-context",
15339
+ "package-script",
15340
+ "language-default",
15341
+ "legacy-alias"
15342
+ ];
15253
15343
  ReleaseChannelSchema = z17.enum(RELEASE_CHANNEL);
15254
15344
  ReleaseSchemeSchema = z17.enum(RELEASE_SCHEME);
15255
15345
  ReleaseKindSchema = z17.enum(RELEASE_KIND);
@@ -17165,6 +17255,117 @@ var init_collection_keys = __esm({
17165
17255
  }
17166
17256
  });
17167
17257
 
17258
+ // packages/contracts/src/decide.ts
17259
+ import { z as z24 } from "zod";
17260
+ var DECISION_QUESTION_TYPES, DECISION_OUTCOME_SOURCES, DECISION_REQUEST_LIMITS, decisionQuestionTypeSchema, decisionOutcomeSourceSchema, decisionInstructionsSchema, unitIntervalSchema, decisionStateSchema, noulDecisionQuestionSchema, choiceDecisionQuestionSchema, scoreDecisionQuestionSchema, decisionQuestionSchema, decisionRequestSchema, noulDecisionAnswerSchema, choiceDecisionAnswerSchema, scoreDecisionAnswerSchema, decisionAnswerSchema, decisionOutcomeSchema, decisionProviderConfigSchema;
17261
+ var init_decide = __esm({
17262
+ "packages/contracts/src/decide.ts"() {
17263
+ "use strict";
17264
+ DECISION_QUESTION_TYPES = ["noul", "choice", "score"];
17265
+ DECISION_OUTCOME_SOURCES = ["provider", "cache", "fallback"];
17266
+ DECISION_REQUEST_LIMITS = {
17267
+ /**
17268
+ * Maximum size of `state`, in UTF-16 characters. A string state is measured
17269
+ * directly; a structured (object/array) state is measured on its
17270
+ * `JSON.stringify` serialization, which is what goes over the wire.
17271
+ */
17272
+ maxStateChars: 32e3,
17273
+ /** Minimum number of questions in one request. */
17274
+ minQuestions: 1,
17275
+ /** Maximum number of questions in one request. */
17276
+ maxQuestions: 32,
17277
+ /** Minimum number of options a `choice` question must offer. */
17278
+ minChoiceOptions: 2,
17279
+ /** Minimum number of levels on a `score` question's scale. */
17280
+ minScoreLevels: 2,
17281
+ /** Maximum number of levels on a `score` question's scale. */
17282
+ maxScoreLevels: 10
17283
+ };
17284
+ decisionQuestionTypeSchema = z24.enum(DECISION_QUESTION_TYPES);
17285
+ decisionOutcomeSourceSchema = z24.enum(DECISION_OUTCOME_SOURCES);
17286
+ decisionInstructionsSchema = z24.union([z24.string(), z24.record(z24.string(), z24.json())]);
17287
+ unitIntervalSchema = z24.number().min(0).max(1);
17288
+ decisionStateSchema = z24.union([z24.string(), z24.array(z24.json()), z24.record(z24.string(), z24.json())]).superRefine((state, context) => {
17289
+ const size = typeof state === "string" ? state.length : JSON.stringify(state).length;
17290
+ if (size > DECISION_REQUEST_LIMITS.maxStateChars) {
17291
+ context.addIssue({
17292
+ code: "custom",
17293
+ message: `state is ${size} characters; the limit is ${DECISION_REQUEST_LIMITS.maxStateChars}`
17294
+ });
17295
+ }
17296
+ });
17297
+ noulDecisionQuestionSchema = z24.object({
17298
+ type: z24.literal("noul"),
17299
+ instructions: decisionInstructionsSchema.optional(),
17300
+ criteria: z24.string().min(1)
17301
+ });
17302
+ choiceDecisionQuestionSchema = z24.object({
17303
+ type: z24.literal("choice"),
17304
+ instructions: decisionInstructionsSchema.optional(),
17305
+ criteria: z24.record(z24.string().min(1), z24.string()).refine((options) => Object.keys(options).length >= DECISION_REQUEST_LIMITS.minChoiceOptions, {
17306
+ message: `a choice question needs at least ${DECISION_REQUEST_LIMITS.minChoiceOptions} options`
17307
+ })
17308
+ });
17309
+ scoreDecisionQuestionSchema = z24.object({
17310
+ type: z24.literal("score"),
17311
+ instructions: decisionInstructionsSchema.optional(),
17312
+ criteria: z24.array(z24.string()).min(DECISION_REQUEST_LIMITS.minScoreLevels).max(DECISION_REQUEST_LIMITS.maxScoreLevels)
17313
+ });
17314
+ decisionQuestionSchema = z24.discriminatedUnion("type", [
17315
+ noulDecisionQuestionSchema,
17316
+ choiceDecisionQuestionSchema,
17317
+ scoreDecisionQuestionSchema
17318
+ ]);
17319
+ decisionRequestSchema = z24.object({
17320
+ model: z24.string().min(1).optional(),
17321
+ state: decisionStateSchema,
17322
+ questions: z24.record(z24.string().min(1), decisionQuestionSchema).refine(
17323
+ (questions) => {
17324
+ const count = Object.keys(questions).length;
17325
+ return count >= DECISION_REQUEST_LIMITS.minQuestions && count <= DECISION_REQUEST_LIMITS.maxQuestions;
17326
+ },
17327
+ {
17328
+ message: `a request needs ${DECISION_REQUEST_LIMITS.minQuestions}\u2013${DECISION_REQUEST_LIMITS.maxQuestions} questions`
17329
+ }
17330
+ )
17331
+ });
17332
+ noulDecisionAnswerSchema = z24.object({
17333
+ type: z24.literal("noul"),
17334
+ value: z24.boolean(),
17335
+ probability: unitIntervalSchema,
17336
+ confidence: unitIntervalSchema
17337
+ });
17338
+ choiceDecisionAnswerSchema = z24.object({
17339
+ type: z24.literal("choice"),
17340
+ value: z24.string().min(1),
17341
+ probabilities: z24.record(z24.string().min(1), unitIntervalSchema),
17342
+ confidence: unitIntervalSchema
17343
+ });
17344
+ scoreDecisionAnswerSchema = z24.object({
17345
+ type: z24.literal("score"),
17346
+ value: z24.number().int().nonnegative(),
17347
+ probabilities: z24.array(unitIntervalSchema).min(DECISION_REQUEST_LIMITS.minScoreLevels).max(DECISION_REQUEST_LIMITS.maxScoreLevels),
17348
+ confidence: unitIntervalSchema
17349
+ });
17350
+ decisionAnswerSchema = z24.discriminatedUnion("type", [
17351
+ noulDecisionAnswerSchema,
17352
+ choiceDecisionAnswerSchema,
17353
+ scoreDecisionAnswerSchema
17354
+ ]);
17355
+ decisionOutcomeSchema = z24.object({
17356
+ answers: z24.record(z24.string().min(1), decisionAnswerSchema),
17357
+ source: decisionOutcomeSourceSchema,
17358
+ requestId: z24.string().min(1).optional(),
17359
+ latencyMs: z24.number().nonnegative(),
17360
+ costUsd: z24.number().nonnegative().optional(),
17361
+ inputTokens: z24.number().int().nonnegative().optional()
17362
+ });
17363
+ decisionProviderConfigSchema = z24.object({
17364
+ baseUrl: z24.url()
17365
+ });
17366
+ }
17367
+ });
17368
+
17168
17369
  // packages/contracts/src/task-id.ts
17169
17370
  var init_task_id = __esm({
17170
17371
  "packages/contracts/src/task-id.ts"() {
@@ -17219,11 +17420,13 @@ var init_src = __esm({
17219
17420
  init_entities();
17220
17421
  init_operations();
17221
17422
  init_nexus_scope_map();
17423
+ init_nexus_user_profile();
17222
17424
  init_output_contracts_data();
17223
17425
  init_params();
17224
17426
  init_service();
17225
17427
  init_tasks();
17226
17428
  init_peer();
17429
+ init_process_probe();
17227
17430
  init_evidence_atoms();
17228
17431
  init_plan();
17229
17432
  init_render();
@@ -17243,20 +17446,21 @@ var init_src = __esm({
17243
17446
  init_workgraph();
17244
17447
  init_caamp_markers();
17245
17448
  init_collection_keys();
17449
+ init_decide();
17246
17450
  init_task_id();
17247
17451
  }
17248
17452
  });
17249
17453
 
17250
17454
  // packages/adapters/src/providers/shared/credentials.ts
17251
17455
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
17252
- import { homedir as homedir2 } from "node:os";
17456
+ import { homedir } from "node:os";
17253
17457
  import { join as join2 } from "node:path";
17458
+ import { getCleoHome as getCleoHome2 } from "@cleocode/paths";
17254
17459
  function resolveCredentials(_provider) {
17255
17460
  const envKey = process.env["ANTHROPIC_API_KEY"];
17256
17461
  if (envKey?.trim()) return { apiKey: envKey.trim() };
17257
17462
  try {
17258
- const xdg = process.env["XDG_DATA_HOME"] ?? join2(homedir2(), ".local", "share");
17259
- const keyFile = join2(xdg, "cleo", "anthropic-key");
17463
+ const keyFile = join2(getCleoHome2(), "anthropic-key");
17260
17464
  if (existsSync2(keyFile)) {
17261
17465
  const stored = readFileSync2(keyFile, "utf-8").trim();
17262
17466
  if (stored) return { apiKey: stored };
@@ -17264,7 +17468,7 @@ function resolveCredentials(_provider) {
17264
17468
  } catch {
17265
17469
  }
17266
17470
  try {
17267
- const credPath = join2(homedir2(), ".claude", ".credentials.json");
17471
+ const credPath = join2(homedir(), ".claude", ".credentials.json");
17268
17472
  if (!existsSync2(credPath)) return { apiKey: null };
17269
17473
  const raw = readFileSync2(credPath, "utf-8");
17270
17474
  const cred = parseClaudeCodeCredentials(raw);
@@ -31263,7 +31467,7 @@ function resolveRef(ref, ctx) {
31263
31467
  function convertBaseSchema(schema, ctx) {
31264
31468
  if (schema.not !== void 0) {
31265
31469
  if (typeof schema.not === "object" && Object.keys(schema.not).length === 0) {
31266
- return z24.never();
31470
+ return z25.never();
31267
31471
  }
31268
31472
  throw new Error("not is not supported in Zod (except { not: {} } for never)");
31269
31473
  }
@@ -31285,7 +31489,7 @@ function convertBaseSchema(schema, ctx) {
31285
31489
  return ctx.refs.get(refPath);
31286
31490
  }
31287
31491
  if (ctx.processing.has(refPath)) {
31288
- return z24.lazy(() => {
31492
+ return z25.lazy(() => {
31289
31493
  if (!ctx.refs.has(refPath)) {
31290
31494
  throw new Error(`Circular reference not resolved: ${refPath}`);
31291
31495
  }
@@ -31302,25 +31506,25 @@ function convertBaseSchema(schema, ctx) {
31302
31506
  if (schema.enum !== void 0) {
31303
31507
  const enumValues = schema.enum;
31304
31508
  if (ctx.version === "openapi-3.0" && schema.nullable === true && enumValues.length === 1 && enumValues[0] === null) {
31305
- return z24.null();
31509
+ return z25.null();
31306
31510
  }
31307
31511
  if (enumValues.length === 0) {
31308
- return z24.never();
31512
+ return z25.never();
31309
31513
  }
31310
31514
  if (enumValues.length === 1) {
31311
- return z24.literal(enumValues[0]);
31515
+ return z25.literal(enumValues[0]);
31312
31516
  }
31313
31517
  if (enumValues.every((v) => typeof v === "string")) {
31314
- return z24.enum(enumValues);
31518
+ return z25.enum(enumValues);
31315
31519
  }
31316
- const literalSchemas = enumValues.map((v) => z24.literal(v));
31520
+ const literalSchemas = enumValues.map((v) => z25.literal(v));
31317
31521
  if (literalSchemas.length < 2) {
31318
31522
  return literalSchemas[0];
31319
31523
  }
31320
- return z24.union([literalSchemas[0], literalSchemas[1], ...literalSchemas.slice(2)]);
31524
+ return z25.union([literalSchemas[0], literalSchemas[1], ...literalSchemas.slice(2)]);
31321
31525
  }
31322
31526
  if (schema.const !== void 0) {
31323
- return z24.literal(schema.const);
31527
+ return z25.literal(schema.const);
31324
31528
  }
31325
31529
  const type = schema.type;
31326
31530
  if (Array.isArray(type)) {
@@ -31329,68 +31533,68 @@ function convertBaseSchema(schema, ctx) {
31329
31533
  return convertBaseSchema(typeSchema, ctx);
31330
31534
  });
31331
31535
  if (typeSchemas.length === 0) {
31332
- return z24.never();
31536
+ return z25.never();
31333
31537
  }
31334
31538
  if (typeSchemas.length === 1) {
31335
31539
  return typeSchemas[0];
31336
31540
  }
31337
- return z24.union(typeSchemas);
31541
+ return z25.union(typeSchemas);
31338
31542
  }
31339
31543
  if (!type) {
31340
- return z24.any();
31544
+ return z25.any();
31341
31545
  }
31342
31546
  let zodSchema2;
31343
31547
  switch (type) {
31344
31548
  case "string": {
31345
- let stringSchema = z24.string();
31549
+ let stringSchema = z25.string();
31346
31550
  if (schema.format) {
31347
31551
  const format = schema.format;
31348
31552
  if (format === "email") {
31349
- stringSchema = stringSchema.check(z24.email());
31553
+ stringSchema = stringSchema.check(z25.email());
31350
31554
  } else if (format === "uri" || format === "uri-reference") {
31351
- stringSchema = stringSchema.check(z24.url());
31555
+ stringSchema = stringSchema.check(z25.url());
31352
31556
  } else if (format === "uuid" || format === "guid") {
31353
- stringSchema = stringSchema.check(z24.uuid());
31557
+ stringSchema = stringSchema.check(z25.uuid());
31354
31558
  } else if (format === "date-time") {
31355
- stringSchema = stringSchema.check(z24.iso.datetime());
31559
+ stringSchema = stringSchema.check(z25.iso.datetime());
31356
31560
  } else if (format === "date") {
31357
- stringSchema = stringSchema.check(z24.iso.date());
31561
+ stringSchema = stringSchema.check(z25.iso.date());
31358
31562
  } else if (format === "time") {
31359
- stringSchema = stringSchema.check(z24.iso.time());
31563
+ stringSchema = stringSchema.check(z25.iso.time());
31360
31564
  } else if (format === "duration") {
31361
- stringSchema = stringSchema.check(z24.iso.duration());
31565
+ stringSchema = stringSchema.check(z25.iso.duration());
31362
31566
  } else if (format === "ipv4") {
31363
- stringSchema = stringSchema.check(z24.ipv4());
31567
+ stringSchema = stringSchema.check(z25.ipv4());
31364
31568
  } else if (format === "ipv6") {
31365
- stringSchema = stringSchema.check(z24.ipv6());
31569
+ stringSchema = stringSchema.check(z25.ipv6());
31366
31570
  } else if (format === "mac") {
31367
- stringSchema = stringSchema.check(z24.mac());
31571
+ stringSchema = stringSchema.check(z25.mac());
31368
31572
  } else if (format === "cidr") {
31369
- stringSchema = stringSchema.check(z24.cidrv4());
31573
+ stringSchema = stringSchema.check(z25.cidrv4());
31370
31574
  } else if (format === "cidr-v6") {
31371
- stringSchema = stringSchema.check(z24.cidrv6());
31575
+ stringSchema = stringSchema.check(z25.cidrv6());
31372
31576
  } else if (format === "base64") {
31373
- stringSchema = stringSchema.check(z24.base64());
31577
+ stringSchema = stringSchema.check(z25.base64());
31374
31578
  } else if (format === "base64url") {
31375
- stringSchema = stringSchema.check(z24.base64url());
31579
+ stringSchema = stringSchema.check(z25.base64url());
31376
31580
  } else if (format === "e164") {
31377
- stringSchema = stringSchema.check(z24.e164());
31581
+ stringSchema = stringSchema.check(z25.e164());
31378
31582
  } else if (format === "jwt") {
31379
- stringSchema = stringSchema.check(z24.jwt());
31583
+ stringSchema = stringSchema.check(z25.jwt());
31380
31584
  } else if (format === "emoji") {
31381
- stringSchema = stringSchema.check(z24.emoji());
31585
+ stringSchema = stringSchema.check(z25.emoji());
31382
31586
  } else if (format === "nanoid") {
31383
- stringSchema = stringSchema.check(z24.nanoid());
31587
+ stringSchema = stringSchema.check(z25.nanoid());
31384
31588
  } else if (format === "cuid") {
31385
- stringSchema = stringSchema.check(z24.cuid());
31589
+ stringSchema = stringSchema.check(z25.cuid());
31386
31590
  } else if (format === "cuid2") {
31387
- stringSchema = stringSchema.check(z24.cuid2());
31591
+ stringSchema = stringSchema.check(z25.cuid2());
31388
31592
  } else if (format === "ulid") {
31389
- stringSchema = stringSchema.check(z24.ulid());
31593
+ stringSchema = stringSchema.check(z25.ulid());
31390
31594
  } else if (format === "xid") {
31391
- stringSchema = stringSchema.check(z24.xid());
31595
+ stringSchema = stringSchema.check(z25.xid());
31392
31596
  } else if (format === "ksuid") {
31393
- stringSchema = stringSchema.check(z24.ksuid());
31597
+ stringSchema = stringSchema.check(z25.ksuid());
31394
31598
  }
31395
31599
  }
31396
31600
  if (typeof schema.minLength === "number") {
@@ -31407,7 +31611,7 @@ function convertBaseSchema(schema, ctx) {
31407
31611
  }
31408
31612
  case "number":
31409
31613
  case "integer": {
31410
- let numberSchema = type === "integer" ? z24.number().int() : z24.number();
31614
+ let numberSchema = type === "integer" ? z25.number().int() : z25.number();
31411
31615
  if (typeof schema.minimum === "number") {
31412
31616
  numberSchema = numberSchema.min(schema.minimum);
31413
31617
  }
@@ -31431,11 +31635,11 @@ function convertBaseSchema(schema, ctx) {
31431
31635
  break;
31432
31636
  }
31433
31637
  case "boolean": {
31434
- zodSchema2 = z24.boolean();
31638
+ zodSchema2 = z25.boolean();
31435
31639
  break;
31436
31640
  }
31437
31641
  case "null": {
31438
- zodSchema2 = z24.null();
31642
+ zodSchema2 = z25.null();
31439
31643
  break;
31440
31644
  }
31441
31645
  case "object": {
@@ -31448,14 +31652,14 @@ function convertBaseSchema(schema, ctx) {
31448
31652
  }
31449
31653
  if (schema.propertyNames) {
31450
31654
  const keySchema = convertSchema(schema.propertyNames, ctx);
31451
- const valueSchema = schema.additionalProperties && typeof schema.additionalProperties === "object" ? convertSchema(schema.additionalProperties, ctx) : z24.any();
31655
+ const valueSchema = schema.additionalProperties && typeof schema.additionalProperties === "object" ? convertSchema(schema.additionalProperties, ctx) : z25.any();
31452
31656
  if (Object.keys(shape).length === 0) {
31453
- zodSchema2 = z24.record(keySchema, valueSchema);
31657
+ zodSchema2 = z25.record(keySchema, valueSchema);
31454
31658
  break;
31455
31659
  }
31456
- const objectSchema2 = z24.object(shape).passthrough();
31457
- const recordSchema = z24.looseRecord(keySchema, valueSchema);
31458
- zodSchema2 = z24.intersection(objectSchema2, recordSchema);
31660
+ const objectSchema2 = z25.object(shape).passthrough();
31661
+ const recordSchema = z25.looseRecord(keySchema, valueSchema);
31662
+ zodSchema2 = z25.intersection(objectSchema2, recordSchema);
31459
31663
  break;
31460
31664
  }
31461
31665
  if (schema.patternProperties) {
@@ -31464,28 +31668,28 @@ function convertBaseSchema(schema, ctx) {
31464
31668
  const looseRecords = [];
31465
31669
  for (const pattern of patternKeys) {
31466
31670
  const patternValue = convertSchema(patternProps[pattern], ctx);
31467
- const keySchema = z24.string().regex(new RegExp(pattern));
31468
- looseRecords.push(z24.looseRecord(keySchema, patternValue));
31671
+ const keySchema = z25.string().regex(new RegExp(pattern));
31672
+ looseRecords.push(z25.looseRecord(keySchema, patternValue));
31469
31673
  }
31470
31674
  const schemasToIntersect = [];
31471
31675
  if (Object.keys(shape).length > 0) {
31472
- schemasToIntersect.push(z24.object(shape).passthrough());
31676
+ schemasToIntersect.push(z25.object(shape).passthrough());
31473
31677
  }
31474
31678
  schemasToIntersect.push(...looseRecords);
31475
31679
  if (schemasToIntersect.length === 0) {
31476
- zodSchema2 = z24.object({}).passthrough();
31680
+ zodSchema2 = z25.object({}).passthrough();
31477
31681
  } else if (schemasToIntersect.length === 1) {
31478
31682
  zodSchema2 = schemasToIntersect[0];
31479
31683
  } else {
31480
- let result = z24.intersection(schemasToIntersect[0], schemasToIntersect[1]);
31684
+ let result = z25.intersection(schemasToIntersect[0], schemasToIntersect[1]);
31481
31685
  for (let i = 2; i < schemasToIntersect.length; i++) {
31482
- result = z24.intersection(result, schemasToIntersect[i]);
31686
+ result = z25.intersection(result, schemasToIntersect[i]);
31483
31687
  }
31484
31688
  zodSchema2 = result;
31485
31689
  }
31486
31690
  break;
31487
31691
  }
31488
- const objectSchema = z24.object(shape);
31692
+ const objectSchema = z25.object(shape);
31489
31693
  if (schema.additionalProperties === false) {
31490
31694
  zodSchema2 = objectSchema.strict();
31491
31695
  } else if (typeof schema.additionalProperties === "object") {
@@ -31502,33 +31706,33 @@ function convertBaseSchema(schema, ctx) {
31502
31706
  const tupleItems = prefixItems.map((item) => convertSchema(item, ctx));
31503
31707
  const rest = items && typeof items === "object" && !Array.isArray(items) ? convertSchema(items, ctx) : void 0;
31504
31708
  if (rest) {
31505
- zodSchema2 = z24.tuple(tupleItems).rest(rest);
31709
+ zodSchema2 = z25.tuple(tupleItems).rest(rest);
31506
31710
  } else {
31507
- zodSchema2 = z24.tuple(tupleItems);
31711
+ zodSchema2 = z25.tuple(tupleItems);
31508
31712
  }
31509
31713
  if (typeof schema.minItems === "number") {
31510
- zodSchema2 = zodSchema2.check(z24.minLength(schema.minItems));
31714
+ zodSchema2 = zodSchema2.check(z25.minLength(schema.minItems));
31511
31715
  }
31512
31716
  if (typeof schema.maxItems === "number") {
31513
- zodSchema2 = zodSchema2.check(z24.maxLength(schema.maxItems));
31717
+ zodSchema2 = zodSchema2.check(z25.maxLength(schema.maxItems));
31514
31718
  }
31515
31719
  } else if (Array.isArray(items)) {
31516
31720
  const tupleItems = items.map((item) => convertSchema(item, ctx));
31517
31721
  const rest = schema.additionalItems && typeof schema.additionalItems === "object" ? convertSchema(schema.additionalItems, ctx) : void 0;
31518
31722
  if (rest) {
31519
- zodSchema2 = z24.tuple(tupleItems).rest(rest);
31723
+ zodSchema2 = z25.tuple(tupleItems).rest(rest);
31520
31724
  } else {
31521
- zodSchema2 = z24.tuple(tupleItems);
31725
+ zodSchema2 = z25.tuple(tupleItems);
31522
31726
  }
31523
31727
  if (typeof schema.minItems === "number") {
31524
- zodSchema2 = zodSchema2.check(z24.minLength(schema.minItems));
31728
+ zodSchema2 = zodSchema2.check(z25.minLength(schema.minItems));
31525
31729
  }
31526
31730
  if (typeof schema.maxItems === "number") {
31527
- zodSchema2 = zodSchema2.check(z24.maxLength(schema.maxItems));
31731
+ zodSchema2 = zodSchema2.check(z25.maxLength(schema.maxItems));
31528
31732
  }
31529
31733
  } else if (items !== void 0) {
31530
31734
  const element = convertSchema(items, ctx);
31531
- let arraySchema = z24.array(element);
31735
+ let arraySchema = z25.array(element);
31532
31736
  if (typeof schema.minItems === "number") {
31533
31737
  arraySchema = arraySchema.min(schema.minItems);
31534
31738
  }
@@ -31537,7 +31741,7 @@ function convertBaseSchema(schema, ctx) {
31537
31741
  }
31538
31742
  zodSchema2 = arraySchema;
31539
31743
  } else {
31540
- zodSchema2 = z24.array(z24.any());
31744
+ zodSchema2 = z25.array(z25.any());
31541
31745
  }
31542
31746
  break;
31543
31747
  }
@@ -31554,37 +31758,37 @@ function convertBaseSchema(schema, ctx) {
31554
31758
  }
31555
31759
  function convertSchema(schema, ctx) {
31556
31760
  if (typeof schema === "boolean") {
31557
- return schema ? z24.any() : z24.never();
31761
+ return schema ? z25.any() : z25.never();
31558
31762
  }
31559
31763
  let baseSchema = convertBaseSchema(schema, ctx);
31560
31764
  const hasExplicitType = schema.type || schema.enum !== void 0 || schema.const !== void 0;
31561
31765
  if (schema.anyOf && Array.isArray(schema.anyOf)) {
31562
31766
  const options = schema.anyOf.map((s) => convertSchema(s, ctx));
31563
- const anyOfUnion = z24.union(options);
31564
- baseSchema = hasExplicitType ? z24.intersection(baseSchema, anyOfUnion) : anyOfUnion;
31767
+ const anyOfUnion = z25.union(options);
31768
+ baseSchema = hasExplicitType ? z25.intersection(baseSchema, anyOfUnion) : anyOfUnion;
31565
31769
  }
31566
31770
  if (schema.oneOf && Array.isArray(schema.oneOf)) {
31567
31771
  const options = schema.oneOf.map((s) => convertSchema(s, ctx));
31568
- const oneOfUnion = z24.xor(options);
31569
- baseSchema = hasExplicitType ? z24.intersection(baseSchema, oneOfUnion) : oneOfUnion;
31772
+ const oneOfUnion = z25.xor(options);
31773
+ baseSchema = hasExplicitType ? z25.intersection(baseSchema, oneOfUnion) : oneOfUnion;
31570
31774
  }
31571
31775
  if (schema.allOf && Array.isArray(schema.allOf)) {
31572
31776
  if (schema.allOf.length === 0) {
31573
- baseSchema = hasExplicitType ? baseSchema : z24.any();
31777
+ baseSchema = hasExplicitType ? baseSchema : z25.any();
31574
31778
  } else {
31575
31779
  let result = hasExplicitType ? baseSchema : convertSchema(schema.allOf[0], ctx);
31576
31780
  const startIdx = hasExplicitType ? 0 : 1;
31577
31781
  for (let i = startIdx; i < schema.allOf.length; i++) {
31578
- result = z24.intersection(result, convertSchema(schema.allOf[i], ctx));
31782
+ result = z25.intersection(result, convertSchema(schema.allOf[i], ctx));
31579
31783
  }
31580
31784
  baseSchema = result;
31581
31785
  }
31582
31786
  }
31583
31787
  if (schema.nullable === true && ctx.version === "openapi-3.0") {
31584
- baseSchema = z24.nullable(baseSchema);
31788
+ baseSchema = z25.nullable(baseSchema);
31585
31789
  }
31586
31790
  if (schema.readOnly === true) {
31587
- baseSchema = z24.readonly(baseSchema);
31791
+ baseSchema = z25.readonly(baseSchema);
31588
31792
  }
31589
31793
  const extraMeta = {};
31590
31794
  const coreMetadataKeys = ["$id", "id", "$comment", "$anchor", "$vocabulary", "$dynamicRef", "$dynamicAnchor"];
@@ -31611,7 +31815,7 @@ function convertSchema(schema, ctx) {
31611
31815
  }
31612
31816
  function fromJSONSchema(schema, params) {
31613
31817
  if (typeof schema === "boolean") {
31614
- return schema ? z24.any() : z24.never();
31818
+ return schema ? z25.any() : z25.never();
31615
31819
  }
31616
31820
  const version2 = detectVersion(schema, params?.defaultTarget);
31617
31821
  const defs = schema.$defs || schema.definitions || {};
@@ -31625,14 +31829,14 @@ function fromJSONSchema(schema, params) {
31625
31829
  };
31626
31830
  return convertSchema(schema, ctx);
31627
31831
  }
31628
- var z24, RECOGNIZED_KEYS;
31832
+ var z25, RECOGNIZED_KEYS;
31629
31833
  var init_from_json_schema = __esm({
31630
31834
  "node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/from-json-schema.js"() {
31631
31835
  init_registries();
31632
31836
  init_checks2();
31633
31837
  init_iso();
31634
31838
  init_schemas2();
31635
- z24 = {
31839
+ z25 = {
31636
31840
  ...schemas_exports2,
31637
31841
  ...checks_exports2,
31638
31842
  iso: iso_exports
@@ -43141,7 +43345,7 @@ var init_spawn = __esm({
43141
43345
  *
43142
43346
  * Uses 3-tier key resolution so the provider works with:
43143
43347
  * - `ANTHROPIC_API_KEY` environment variable (explicit)
43144
- * - `~/.local/share/cleo/anthropic-key` (user-stored via cleo config)
43348
+ * - `<getCleoHome()>/anthropic-key` (user-stored via cleo config)
43145
43349
  * - Claude Code OAuth token (zero-config for Claude Code users)
43146
43350
  *
43147
43351
  * @returns `true` when any Anthropic credential is available
@@ -43272,16 +43476,62 @@ var init_spawn = __esm({
43272
43476
  });
43273
43477
 
43274
43478
  // packages/adapters/src/providers/claude-code/paths.ts
43275
- import { homedir as homedir3 } from "node:os";
43479
+ import { homedir as homedir2 } from "node:os";
43276
43480
  import { join as join3 } from "node:path";
43277
- var ClaudeCodePathProvider;
43481
+ function claudeSettingsPath() {
43482
+ return new ClaudeCodePathProvider().getSettingsPath() ?? join3(homedir2(), ".claude", "settings.json");
43483
+ }
43484
+ function isPlainObject2(value) {
43485
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43486
+ }
43487
+ function isCleoHookEntry(entry, commandIncludes) {
43488
+ if (!isPlainObject2(entry) || !Array.isArray(entry.hooks)) return false;
43489
+ return entry.hooks.some(
43490
+ (hook) => isPlainObject2(hook) && typeof hook.command === "string" && hook.command.includes(CLEO_HOOK_MARKER) && (commandIncludes === void 0 || hook.command.includes(commandIncludes))
43491
+ );
43492
+ }
43493
+ function hasCleoHook(entries, commandIncludes) {
43494
+ return Array.isArray(entries) && entries.some((e) => isCleoHookEntry(e, commandIncludes));
43495
+ }
43496
+ function hookMap(settings) {
43497
+ const existing = settings.hooks;
43498
+ if (existing === void 0) {
43499
+ const created = {};
43500
+ settings.hooks = created;
43501
+ return created;
43502
+ }
43503
+ if (!isPlainObject2(existing)) {
43504
+ throw new Error('settings.json "hooks" is not an object; not modifying it');
43505
+ }
43506
+ return existing;
43507
+ }
43508
+ function appendHookEntry(hooks, event, entry) {
43509
+ const existing = hooks[event];
43510
+ if (existing !== void 0 && !Array.isArray(existing)) {
43511
+ throw new Error(`settings.json "hooks.${event}" is not an array; not modifying it`);
43512
+ }
43513
+ hooks[event] = [...existing ?? [], entry];
43514
+ }
43515
+ function removeCleoHookEntries(hooks) {
43516
+ let changed = false;
43517
+ for (const [event, entries] of Object.entries(hooks)) {
43518
+ if (!Array.isArray(entries)) continue;
43519
+ const kept = entries.filter((e) => !isCleoHookEntry(e));
43520
+ if (kept.length !== entries.length) {
43521
+ hooks[event] = kept;
43522
+ changed = true;
43523
+ }
43524
+ }
43525
+ return changed;
43526
+ }
43527
+ var ClaudeCodePathProvider, CLEO_HOOK_MARKER;
43278
43528
  var init_paths = __esm({
43279
43529
  "packages/adapters/src/providers/claude-code/paths.ts"() {
43280
43530
  "use strict";
43281
43531
  ClaudeCodePathProvider = class {
43282
43532
  /** Get the provider's root configuration directory. */
43283
43533
  getProviderDir() {
43284
- return process.env["CLAUDE_HOME"] ?? join3(homedir3(), ".claude");
43534
+ return process.env["CLAUDE_HOME"] ?? join3(homedir2(), ".claude");
43285
43535
  }
43286
43536
  /** Get the path to the provider's settings file, or null if unavailable. */
43287
43537
  getSettingsPath() {
@@ -43293,16 +43543,17 @@ var init_paths = __esm({
43293
43543
  }
43294
43544
  /** Get the path to the provider's memory database, or null if unsupported. */
43295
43545
  getMemoryDbPath() {
43296
- return process.env["CLAUDE_MEM_DB"] ?? join3(homedir3(), ".claude-mem", "claude-mem.db");
43546
+ return process.env["CLAUDE_MEM_DB"] ?? join3(homedir2(), ".claude-mem", "claude-mem.db");
43297
43547
  }
43298
43548
  };
43549
+ CLEO_HOOK_MARKER = "# cleo-hook";
43299
43550
  }
43300
43551
  });
43301
43552
 
43302
43553
  // packages/adapters/src/providers/claude-code/context-monitor.ts
43303
43554
  import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync } from "node:fs";
43304
43555
  import { mkdir } from "node:fs/promises";
43305
- import { homedir as homedir4 } from "node:os";
43556
+ import { homedir as homedir3 } from "node:os";
43306
43557
  import { dirname, join as join4 } from "node:path";
43307
43558
  import { resolveLegacyCleoDir } from "@cleocode/paths";
43308
43559
  function getContextStatusFromPercentage(percentage) {
@@ -43388,7 +43639,7 @@ var init_context_monitor = __esm({
43388
43639
  if (cmd.includes("context-monitor.sh") || cmd.includes("cleo-statusline") || cmd.includes(".context-state.json") || cmd.includes("context-states")) {
43389
43640
  return "configured";
43390
43641
  }
43391
- const scriptPath = cmd.startsWith("~") ? cmd.replace("~", homedir4()) : cmd;
43642
+ const scriptPath = cmd.startsWith("~") ? cmd.replace("~", homedir3()) : cmd;
43392
43643
  if (existsSync3(scriptPath)) {
43393
43644
  try {
43394
43645
  const content = readFileSync3(scriptPath, "utf-8");
@@ -43427,14 +43678,15 @@ var init_context_monitor = __esm({
43427
43678
  });
43428
43679
 
43429
43680
  // packages/adapters/src/providers/claude-code/hooks.ts
43430
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
43681
+ import { existsSync as existsSync4 } from "node:fs";
43431
43682
  import { readdir, readFile } from "node:fs/promises";
43432
- import { homedir as homedir5 } from "node:os";
43433
43683
  import { join as join5 } from "node:path";
43684
+ import { updateJsonConfigFile } from "@cleocode/caamp";
43434
43685
  var PROVIDER_ID, CLAUDE_CODE_EVENT_MAP, ClaudeCodeHookProvider;
43435
43686
  var init_hooks = __esm({
43436
43687
  "packages/adapters/src/providers/claude-code/hooks.ts"() {
43437
43688
  "use strict";
43689
+ init_paths();
43438
43690
  PROVIDER_ID = "claude-code";
43439
43691
  CLAUDE_CODE_EVENT_MAP = {
43440
43692
  // CAAMP: toNative('SessionStart', 'claude-code') = 'SessionStart'
@@ -43506,39 +43758,16 @@ var init_hooks = __esm({
43506
43758
  async registerNativeHooks(projectDir) {
43507
43759
  this.projectDir = projectDir;
43508
43760
  this.registered = true;
43509
- try {
43510
- const home = homedir5();
43511
- const settingsPath = join5(home, ".claude", "settings.json");
43512
- let settings = {};
43513
- if (existsSync4(settingsPath)) {
43514
- try {
43515
- settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
43516
- } catch {
43517
- }
43518
- }
43519
- const hooks = settings.hooks ?? {};
43520
- const alreadyRegistered = Object.values(hooks).some(
43521
- (entries) => Array.isArray(entries) && entries.some(
43522
- (e) => typeof e === "object" && e !== null && Array.isArray(e.hooks) && e.hooks.some(
43523
- (h) => typeof h.command === "string" && h.command.includes("# cleo-hook")
43524
- )
43525
- )
43526
- );
43527
- if (alreadyRegistered) {
43528
- return;
43761
+ await this.updateSettings((settings) => {
43762
+ const hooks = hookMap(settings);
43763
+ if (Object.values(hooks).some((entries) => hasCleoHook(entries))) {
43764
+ return false;
43529
43765
  }
43530
- if (!hooks.Stop) hooks.Stop = [];
43531
- hooks.Stop.push({
43766
+ appendHookEntry(hooks, "Stop", {
43532
43767
  matcher: "",
43533
- hooks: [
43534
- {
43535
- type: "command",
43536
- command: `cleo session end --quiet # cleo-hook`
43537
- }
43538
- ]
43768
+ hooks: [{ type: "command", command: `cleo session end --quiet # cleo-hook` }]
43539
43769
  });
43540
- if (!hooks.PostToolUse) hooks.PostToolUse = [];
43541
- hooks.PostToolUse.push({
43770
+ appendHookEntry(hooks, "PostToolUse", {
43542
43771
  matcher: "Write|Edit",
43543
43772
  hooks: [
43544
43773
  {
@@ -43553,48 +43782,54 @@ var init_hooks = __esm({
43553
43782
  }
43554
43783
  ]
43555
43784
  });
43556
- settings.hooks = hooks;
43557
- mkdirSync(join5(home, ".claude"), { recursive: true });
43558
- writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
43559
- } catch {
43560
- }
43785
+ return true;
43786
+ });
43561
43787
  }
43562
43788
  /**
43563
43789
  * Unregister native hooks.
43564
43790
  *
43565
- * Removes CLEO hook entries from `~/.claude/settings.json` by filtering out
43566
- * entries containing the `# cleo-hook` marker.
43791
+ * Removes CLEO hook entries from Claude Code's `settings.json` by filtering
43792
+ * out entries containing the `# cleo-hook` marker. Same locking, atomicity
43793
+ * and parse-error rules as {@link registerNativeHooks}.
43567
43794
  *
43568
43795
  * @task T164 @task T555
43569
43796
  */
43570
43797
  async unregisterNativeHooks() {
43571
43798
  this.registered = false;
43572
43799
  this.projectDir = null;
43800
+ if (!existsSync4(claudeSettingsPath())) return;
43801
+ await this.updateSettings((settings) => {
43802
+ if (settings.hooks === void 0) return false;
43803
+ return removeCleoHookEntries(hookMap(settings));
43804
+ });
43805
+ }
43806
+ /** The last settings.json failure, or `null` when the last update succeeded. */
43807
+ settingsError = null;
43808
+ /**
43809
+ * Why the last settings.json update did not happen, or `null`.
43810
+ *
43811
+ * @remarks
43812
+ * Hook registration stays non-fatal for adapter initialisation, so a
43813
+ * refused write (malformed settings.json, lock timeout) is surfaced here and
43814
+ * on stderr instead of being swallowed (T12385).
43815
+ *
43816
+ * @returns The failure message, or `null`
43817
+ */
43818
+ getSettingsError() {
43819
+ return this.settingsError;
43820
+ }
43821
+ /** Apply `mutate` to settings.json via CAAMP's locked, atomic JSON writer. */
43822
+ async updateSettings(mutate) {
43823
+ const settingsPath = claudeSettingsPath();
43573
43824
  try {
43574
- const home = homedir5();
43575
- const settingsPath = join5(home, ".claude", "settings.json");
43576
- if (!existsSync4(settingsPath)) return;
43577
- const settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
43578
- const hooks = settings.hooks;
43579
- if (!hooks) return;
43580
- let changed = false;
43581
- for (const [event, entries] of Object.entries(hooks)) {
43582
- if (!Array.isArray(entries)) continue;
43583
- const filtered = entries.filter(
43584
- (e) => !(typeof e === "object" && e !== null && Array.isArray(e.hooks) && e.hooks.some(
43585
- (h) => typeof h.command === "string" && h.command.includes("# cleo-hook")
43586
- ))
43587
- );
43588
- if (filtered.length !== entries.length) {
43589
- hooks[event] = filtered;
43590
- changed = true;
43591
- }
43592
- }
43593
- if (changed) {
43594
- settings.hooks = hooks;
43595
- writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
43596
- }
43597
- } catch {
43825
+ await updateJsonConfigFile(settingsPath, mutate);
43826
+ this.settingsError = null;
43827
+ } catch (err) {
43828
+ this.settingsError = err instanceof Error ? err.message : String(err);
43829
+ process.stderr.write(
43830
+ `[cleo:claude-code] hooks not written to ${settingsPath}: ${this.settingsError}
43831
+ `
43832
+ );
43598
43833
  }
43599
43834
  }
43600
43835
  /**
@@ -43773,7 +44008,7 @@ var init_hooks = __esm({
43773
44008
  });
43774
44009
 
43775
44010
  // packages/adapters/src/providers/shared/hook-template-installer.ts
43776
- import { copyFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, statSync } from "node:fs";
44011
+ import { copyFileSync, existsSync as existsSync5, mkdirSync, statSync } from "node:fs";
43777
44012
  import { dirname as dirname2, join as join6 } from "node:path";
43778
44013
  import { fileURLToPath } from "node:url";
43779
44014
  function resolveProviderCandidates() {
@@ -43807,7 +44042,7 @@ function copyTemplate(src, dest, force) {
43807
44042
  return { wrote: false };
43808
44043
  }
43809
44044
  }
43810
- mkdirSync2(dirname2(dest), { recursive: true });
44045
+ mkdirSync(dirname2(dest), { recursive: true });
43811
44046
  copyFileSync(src, dest);
43812
44047
  return { wrote: true };
43813
44048
  }
@@ -43819,7 +44054,7 @@ function installProviderHookTemplates(options) {
43819
44054
  installedFiles: [],
43820
44055
  skipped: []
43821
44056
  };
43822
- mkdirSync2(targetDir, { recursive: true });
44057
+ mkdirSync(targetDir, { recursive: true });
43823
44058
  const coreDest = join6(targetDir, SHARED_CORE_FILE);
43824
44059
  const coreOutcome = copyTemplate(sharedCoreSource(), coreDest, force);
43825
44060
  if (coreOutcome.wrote) result.installedFiles.push(coreDest);
@@ -43852,20 +44087,13 @@ var init_hook_template_installer = __esm({
43852
44087
  });
43853
44088
 
43854
44089
  // packages/adapters/src/providers/claude-code/install.ts
43855
- import {
43856
- copyFileSync as copyFileSync2,
43857
- existsSync as existsSync6,
43858
- mkdirSync as mkdirSync3,
43859
- readdirSync as readdirSync2,
43860
- readFileSync as readFileSync5,
43861
- writeFileSync as writeFileSync3
43862
- } from "node:fs";
43863
- import { homedir as homedir6 } from "node:os";
44090
+ import { copyFileSync as copyFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "node:fs";
44091
+ import { homedir as homedir4 } from "node:os";
43864
44092
  import { dirname as dirname3, join as join7 } from "node:path";
43865
44093
  import { fileURLToPath as fileURLToPath2 } from "node:url";
43866
- import { ensureProviderInstructionFile } from "@cleocode/caamp";
44094
+ import { ensureProviderInstructionFile, updateJsonConfigFile as updateJsonConfigFile2 } from "@cleocode/caamp";
43867
44095
  function getClaudeConfigDir() {
43868
- return process.env["CLAUDE_HOME"] ?? join7(homedir6(), ".claude");
44096
+ return process.env["CLAUDE_HOME"] ?? join7(homedir4(), ".claude");
43869
44097
  }
43870
44098
  function getAdapterCommandsDir() {
43871
44099
  const thisDir = dirname3(fileURLToPath2(import.meta.url));
@@ -43876,6 +44104,7 @@ var init_install = __esm({
43876
44104
  "packages/adapters/src/providers/claude-code/install.ts"() {
43877
44105
  "use strict";
43878
44106
  init_hook_template_installer();
44107
+ init_paths();
43879
44108
  ClaudeCodeInstallProvider = class {
43880
44109
  /**
43881
44110
  * Install CLEO into a Claude Code project.
@@ -43897,16 +44126,28 @@ var init_install = __esm({
43897
44126
  if (commandsInstalled.length > 0) {
43898
44127
  details.commands = commandsInstalled;
43899
44128
  }
43900
- const pluginResult = this.registerPlugin();
43901
- if (pluginResult) {
43902
- details.plugin = pluginResult;
44129
+ const settingsErrors = [];
44130
+ try {
44131
+ const pluginResult = await this.registerPlugin();
44132
+ if (pluginResult) {
44133
+ details.plugin = pluginResult;
44134
+ }
44135
+ } catch (err) {
44136
+ settingsErrors.push(err instanceof Error ? err.message : String(err));
43903
44137
  }
43904
- const hookResult = this.installHookTemplates();
43905
- if (hookResult) {
43906
- details.hookTemplates = hookResult;
44138
+ try {
44139
+ const hookResult = await this.installHookTemplates();
44140
+ if (hookResult) {
44141
+ details.hookTemplates = hookResult;
44142
+ }
44143
+ } catch (err) {
44144
+ settingsErrors.push(err instanceof Error ? err.message : String(err));
44145
+ }
44146
+ if (settingsErrors.length > 0) {
44147
+ details.settingsErrors = settingsErrors;
43907
44148
  }
43908
44149
  return {
43909
- success: true,
44150
+ success: settingsErrors.length === 0,
43910
44151
  installedAt,
43911
44152
  instructionFileUpdated,
43912
44153
  details
@@ -43925,10 +44166,10 @@ var init_install = __esm({
43925
44166
  * Checks for plugin enabled in ~/.claude/settings.json.
43926
44167
  */
43927
44168
  async isInstalled() {
43928
- const settingsPath = join7(getClaudeConfigDir(), "settings.json");
44169
+ const settingsPath = claudeSettingsPath();
43929
44170
  if (existsSync6(settingsPath)) {
43930
44171
  try {
43931
- const settings = JSON.parse(readFileSync5(settingsPath, "utf-8"));
44172
+ const settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
43932
44173
  const plugins = settings.enabledPlugins;
43933
44174
  if (plugins && plugins["cleo@cleocode"] === true) {
43934
44175
  return true;
@@ -43964,7 +44205,7 @@ var init_install = __esm({
43964
44205
  return [];
43965
44206
  }
43966
44207
  const targetDir = join7(projectDir, ".claude", "commands");
43967
- mkdirSync3(targetDir, { recursive: true });
44208
+ mkdirSync2(targetDir, { recursive: true });
43968
44209
  const installed = [];
43969
44210
  const files = readdirSync2(adapterCommandsDir).filter((f) => f.endsWith(".md"));
43970
44211
  for (const file2 of files) {
@@ -43980,29 +44221,24 @@ var init_install = __esm({
43980
44221
  *
43981
44222
  * @returns Description of what was registered, or null if no change needed
43982
44223
  */
43983
- registerPlugin() {
43984
- const claudeDir = getClaudeConfigDir();
43985
- const settingsPath = join7(claudeDir, "settings.json");
43986
- let settings = {};
43987
- if (existsSync6(settingsPath)) {
43988
- try {
43989
- settings = JSON.parse(readFileSync5(settingsPath, "utf-8"));
43990
- } catch {
43991
- }
43992
- }
43993
- const enabledPlugins = settings.enabledPlugins ?? {};
44224
+ async registerPlugin() {
43994
44225
  const pluginKey = "cleo@cleocode";
43995
- if (enabledPlugins[pluginKey] === true) {
43996
- return null;
43997
- }
43998
- if (enabledPlugins["claude-mem@thedotmack"] === true) {
43999
- enabledPlugins["claude-mem@thedotmack"] = false;
44000
- }
44001
- enabledPlugins[pluginKey] = true;
44002
- settings.enabledPlugins = enabledPlugins;
44003
- mkdirSync3(claudeDir, { recursive: true });
44004
- writeFileSync3(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
44005
- return `Enabled ${pluginKey} in ~/.claude/settings.json`;
44226
+ const wrote = await updateJsonConfigFile2(claudeSettingsPath(), (settings) => {
44227
+ const enabledPlugins = settings.enabledPlugins ?? {};
44228
+ if (!isPlainObject2(enabledPlugins)) {
44229
+ throw new Error('settings.json "enabledPlugins" is not an object; not modifying it');
44230
+ }
44231
+ if (enabledPlugins[pluginKey] === true) {
44232
+ return false;
44233
+ }
44234
+ if (enabledPlugins["claude-mem@thedotmack"] === true) {
44235
+ enabledPlugins["claude-mem@thedotmack"] = false;
44236
+ }
44237
+ enabledPlugins[pluginKey] = true;
44238
+ settings.enabledPlugins = enabledPlugins;
44239
+ return true;
44240
+ });
44241
+ return wrote ? `Enabled ${pluginKey} in ~/.claude/settings.json` : null;
44006
44242
  }
44007
44243
  /**
44008
44244
  * Install the CLEO PreCompact hook templates for Claude Code (T1013).
@@ -44024,7 +44260,7 @@ var init_install = __esm({
44024
44260
  *
44025
44261
  * @task T1013
44026
44262
  */
44027
- installHookTemplates() {
44263
+ async installHookTemplates() {
44028
44264
  const claudeDir = getClaudeConfigDir();
44029
44265
  const hooksDir = join7(claudeDir, "hooks");
44030
44266
  let templates;
@@ -44036,7 +44272,7 @@ var init_install = __esm({
44036
44272
  } catch {
44037
44273
  return null;
44038
44274
  }
44039
- const settingsEntryAdded = this.registerPreCompactHook(
44275
+ const settingsEntryAdded = await this.registerPreCompactHook(
44040
44276
  join7(hooksDir, "precompact-safestop.sh")
44041
44277
  );
44042
44278
  if (templates.installedFiles.length === 0 && !settingsEntryAdded) {
@@ -44058,41 +44294,24 @@ var init_install = __esm({
44058
44294
  *
44059
44295
  * @task T1013
44060
44296
  */
44061
- registerPreCompactHook(shimPath) {
44062
- const claudeDir = getClaudeConfigDir();
44063
- const settingsPath = join7(claudeDir, "settings.json");
44064
- let settings = {};
44065
- if (existsSync6(settingsPath)) {
44066
- try {
44067
- settings = JSON.parse(readFileSync5(settingsPath, "utf-8"));
44068
- } catch {
44297
+ async registerPreCompactHook(shimPath) {
44298
+ return updateJsonConfigFile2(claudeSettingsPath(), (settings) => {
44299
+ const hooks = hookMap(settings);
44300
+ if (hasCleoHook(hooks.PreCompact, "precompact-safestop.sh")) {
44301
+ return false;
44069
44302
  }
44070
- }
44071
- const hooks = settings.hooks ?? {};
44072
- const preCompactEntries = hooks.PreCompact ?? [];
44073
- const alreadyWired = preCompactEntries.some(
44074
- (entry) => typeof entry === "object" && entry !== null && Array.isArray(entry.hooks) && entry.hooks.some(
44075
- (h) => typeof h.command === "string" && h.command.includes("# cleo-hook") && h.command.includes("precompact-safestop.sh")
44076
- )
44077
- );
44078
- if (alreadyWired) {
44079
- return false;
44080
- }
44081
- preCompactEntries.push({
44082
- matcher: "",
44083
- hooks: [
44084
- {
44085
- type: "command",
44086
- command: `"${shimPath}" # cleo-hook`,
44087
- timeout: 30
44088
- }
44089
- ]
44303
+ appendHookEntry(hooks, "PreCompact", {
44304
+ matcher: "",
44305
+ hooks: [
44306
+ {
44307
+ type: "command",
44308
+ command: `"${shimPath}" # cleo-hook`,
44309
+ timeout: 30
44310
+ }
44311
+ ]
44312
+ });
44313
+ return true;
44090
44314
  });
44091
- hooks.PreCompact = preCompactEntries;
44092
- settings.hooks = hooks;
44093
- mkdirSync3(claudeDir, { recursive: true });
44094
- writeFileSync3(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
44095
- return true;
44096
44315
  }
44097
44316
  };
44098
44317
  }
@@ -44100,7 +44319,8 @@ var init_install = __esm({
44100
44319
 
44101
44320
  // packages/adapters/src/providers/shared/agent-spawn-wrapper.ts
44102
44321
  import { spawnSync } from "node:child_process";
44103
- import { readFileSync as readFileSync6 } from "node:fs";
44322
+ import { readFileSync as readFileSync5 } from "node:fs";
44323
+ import { resolveSpawnInvocation } from "@cleocode/paths";
44104
44324
  function hasSystemdRun() {
44105
44325
  if (_systemdRunAvailable !== void 0) return _systemdRunAvailable;
44106
44326
  if (process.platform !== "linux") {
@@ -44114,7 +44334,7 @@ function hasSystemdRun() {
44114
44334
  function readMemTotalBytes() {
44115
44335
  if (process.platform !== "linux") return 32 * 1024 * 1024 * 1024;
44116
44336
  try {
44117
- const raw = readFileSync6("/proc/meminfo", "utf8");
44337
+ const raw = readFileSync5("/proc/meminfo", "utf8");
44118
44338
  const m = raw.match(/^MemTotal:\s+(\d+)\s+kB/m);
44119
44339
  if (m?.[1]) return parseInt(m[1], 10) * 1024;
44120
44340
  } catch {
@@ -44123,12 +44343,21 @@ function readMemTotalBytes() {
44123
44343
  }
44124
44344
  function buildAgentSpawnArgs(command, args, scopeId) {
44125
44345
  if (!hasSystemdRun()) {
44126
- if (!_demotionLogged) {
44346
+ if (!_demotionLogged && process.platform === "linux" && process.env["CLEO_DEBUG"]) {
44127
44347
  _demotionLogged = true;
44128
44348
  process.stderr.write(
44129
44349
  "[cleo:agent-spawn-wrapper] systemd-run unavailable \u2014 falling back to plain pgid (detached) spawn; no cgroup containment\n"
44130
44350
  );
44131
44351
  }
44352
+ if (process.platform === "win32") {
44353
+ const inv = resolveSpawnInvocation(command, args);
44354
+ return {
44355
+ command: inv.file,
44356
+ args: inv.args,
44357
+ ...inv.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {},
44358
+ ownership: { mode: "pgid" }
44359
+ };
44360
+ }
44132
44361
  return {
44133
44362
  command: "sh",
44134
44363
  args: ["-c", 'ulimit -c 0; exec "$@"', "sh", command, ...args],
@@ -44186,6 +44415,25 @@ var init_agent_spawn_wrapper = __esm({
44186
44415
  }
44187
44416
  });
44188
44417
 
44418
+ // packages/adapters/src/providers/shared/prompt-file.ts
44419
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
44420
+ import { tmpdir } from "node:os";
44421
+ import { dirname as dirname4, join as join8 } from "node:path";
44422
+ async function writeSpawnPromptFile(prefix, prompt) {
44423
+ const dir = await mkdtemp(join8(tmpdir(), `cleo-${prefix}-`));
44424
+ const file2 = join8(dir, "prompt.txt");
44425
+ await writeFile(file2, prompt, "utf-8");
44426
+ return file2;
44427
+ }
44428
+ async function removeSpawnPromptFile(file2) {
44429
+ await rm(dirname4(file2), { recursive: true, force: true }).catch(() => void 0);
44430
+ }
44431
+ var init_prompt_file = __esm({
44432
+ "packages/adapters/src/providers/shared/prompt-file.ts"() {
44433
+ "use strict";
44434
+ }
44435
+ });
44436
+
44189
44437
  // packages/adapters/src/providers/claude-code/suite-reaper.ts
44190
44438
  import { spawnSync as spawnSync2 } from "node:child_process";
44191
44439
  async function reapAgentSuite(ownership) {
@@ -44281,32 +44529,26 @@ var init_suite_reaper = __esm({
44281
44529
  });
44282
44530
 
44283
44531
  // packages/adapters/src/providers/claude-code/spawn.ts
44284
- import { exec, spawn as nodeSpawn } from "node:child_process";
44285
- import { unlink, writeFile } from "node:fs/promises";
44286
- import { promisify as promisify2 } from "node:util";
44287
- var execAsync, ClaudeCodeSpawnProvider;
44532
+ import { spawn as nodeSpawn } from "node:child_process";
44533
+ import { findOnPath } from "@cleocode/paths";
44534
+ var ClaudeCodeSpawnProvider;
44288
44535
  var init_spawn2 = __esm({
44289
44536
  "packages/adapters/src/providers/claude-code/spawn.ts"() {
44290
44537
  "use strict";
44291
44538
  init_src();
44292
44539
  init_agent_spawn_wrapper();
44540
+ init_prompt_file();
44293
44541
  init_suite_reaper();
44294
- execAsync = promisify2(exec);
44295
44542
  ClaudeCodeSpawnProvider = class {
44296
44543
  /** Map of instance IDs to tracked process info. */
44297
44544
  processMap = /* @__PURE__ */ new Map();
44298
44545
  /**
44299
44546
  * Check if the Claude CLI is available in PATH.
44300
44547
  *
44301
- * @returns true if `claude` is found via `which`
44548
+ * @returns true if `claude` is found on PATH (PATHEXT-aware on Windows)
44302
44549
  */
44303
44550
  async canSpawn() {
44304
- try {
44305
- await execAsync("which claude");
44306
- return true;
44307
- } catch {
44308
- return false;
44309
- }
44551
+ return findOnPath("claude") !== null;
44310
44552
  }
44311
44553
  /**
44312
44554
  * Spawn a subagent via Claude CLI.
@@ -44336,8 +44578,7 @@ var init_spawn2 = __esm({
44336
44578
  });
44337
44579
  } catch {
44338
44580
  }
44339
- tmpFile = `/tmp/claude-spawn-${instanceId}.txt`;
44340
- await writeFile(tmpFile, enrichedPrompt, "utf-8");
44581
+ tmpFile = await writeSpawnPromptFile("claude-spawn", enrichedPrompt);
44341
44582
  const claudeArgs = [
44342
44583
  "--print",
44343
44584
  "--dangerously-skip-permissions",
@@ -44349,7 +44590,9 @@ var init_spawn2 = __esm({
44349
44590
  const isSystemd = spawnBuild.ownership.mode === "systemd";
44350
44591
  const spawnOpts = {
44351
44592
  detached: !isSystemd,
44352
- stdio: ["ignore", "pipe", "pipe"]
44593
+ stdio: ["ignore", "pipe", "pipe"],
44594
+ // win32 `.cmd` shim routed through cmd.exe: keep its quoting (T12618).
44595
+ ...spawnBuild.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
44353
44596
  };
44354
44597
  if (context.workingDirectory) {
44355
44598
  spawnOpts.cwd = context.workingDirectory;
@@ -44376,10 +44619,7 @@ var init_spawn2 = __esm({
44376
44619
  const capturedTmpFile = tmpFile;
44377
44620
  child.on("exit", async () => {
44378
44621
  this.processMap.delete(instanceId);
44379
- try {
44380
- await unlink(capturedTmpFile);
44381
- } catch {
44382
- }
44622
+ await removeSpawnPromptFile(capturedTmpFile);
44383
44623
  });
44384
44624
  return {
44385
44625
  instanceId,
@@ -44394,10 +44634,7 @@ var init_spawn2 = __esm({
44394
44634
  } catch (error48) {
44395
44635
  console.error(`[ClaudeCodeSpawnProvider] Failed to spawn: ${getErrorMessage(error48)}`);
44396
44636
  if (tmpFile) {
44397
- try {
44398
- await unlink(tmpFile);
44399
- } catch {
44400
- }
44637
+ await removeSpawnPromptFile(tmpFile);
44401
44638
  }
44402
44639
  return {
44403
44640
  instanceId,
@@ -44482,7 +44719,7 @@ var init_spawn2 = __esm({
44482
44719
 
44483
44720
  // packages/adapters/src/providers/claude-code/task-sync.ts
44484
44721
  import { readFile as readFile2, stat } from "node:fs/promises";
44485
- import { join as join8 } from "node:path";
44722
+ import { join as join9 } from "node:path";
44486
44723
  function parseTaskId(content) {
44487
44724
  const match = content.match(/^\[T(\d+)\]/);
44488
44725
  return match ? `T${match[1]}` : null;
@@ -44503,7 +44740,7 @@ function mapStatus(twStatus) {
44503
44740
  }
44504
44741
  }
44505
44742
  function getTodoWriteFilePath(projectDir) {
44506
- return join8(projectDir, ".cleo", "sync", "todowrite-state.json");
44743
+ return join9(projectDir, ".cleo", "sync", "todowrite-state.json");
44507
44744
  }
44508
44745
  var ClaudeCodeTaskSyncProvider;
44509
44746
  var init_task_sync = __esm({
@@ -44574,12 +44811,11 @@ var init_transport = __esm({
44574
44811
  });
44575
44812
 
44576
44813
  // packages/adapters/src/providers/claude-code/adapter.ts
44577
- import { exec as exec2 } from "node:child_process";
44578
44814
  import { existsSync as existsSync7 } from "node:fs";
44579
- import { homedir as homedir7 } from "node:os";
44580
- import { join as join9 } from "node:path";
44581
- import { promisify as promisify3 } from "node:util";
44582
- var execAsync2, ClaudeCodeAdapter;
44815
+ import { homedir as homedir5 } from "node:os";
44816
+ import { join as join10 } from "node:path";
44817
+ import { findOnPath as findOnPath2 } from "@cleocode/paths";
44818
+ var ClaudeCodeAdapter;
44583
44819
  var init_adapter = __esm({
44584
44820
  "packages/adapters/src/providers/claude-code/adapter.ts"() {
44585
44821
  "use strict";
@@ -44591,7 +44827,6 @@ var init_adapter = __esm({
44591
44827
  init_spawn2();
44592
44828
  init_task_sync();
44593
44829
  init_transport();
44594
- execAsync2 = promisify3(exec2);
44595
44830
  ClaudeCodeAdapter = class {
44596
44831
  /** Unique provider identifier. */
44597
44832
  id = "claude-code";
@@ -44727,14 +44962,11 @@ var init_adapter = __esm({
44727
44962
  };
44728
44963
  }
44729
44964
  let cliAvailable = false;
44730
- try {
44731
- const { stdout } = await execAsync2("which claude");
44732
- cliAvailable = stdout.trim().length > 0;
44733
- details.cliPath = stdout.trim();
44734
- } catch {
44735
- details.cliAvailable = false;
44736
- }
44737
- const claudeConfigDir = join9(homedir7(), ".claude");
44965
+ const resolvedCli = findOnPath2("claude");
44966
+ cliAvailable = resolvedCli !== null;
44967
+ if (resolvedCli) details.cliPath = resolvedCli;
44968
+ else details.cliAvailable = false;
44969
+ const claudeConfigDir = join10(homedir5(), ".claude");
44738
44970
  const configExists = existsSync7(claudeConfigDir);
44739
44971
  details.configDirExists = configExists;
44740
44972
  const entrypointSet = process.env.CLAUDE_CODE_ENTRYPOINT !== void 0;
@@ -44764,17 +44996,17 @@ var init_adapter = __esm({
44764
44996
  });
44765
44997
 
44766
44998
  // packages/adapters/src/providers/claude-code/statusline.ts
44767
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
44768
- import { homedir as homedir8 } from "node:os";
44769
- import { join as join10 } from "node:path";
44999
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
45000
+ import { homedir as homedir6 } from "node:os";
45001
+ import { join as join11 } from "node:path";
44770
45002
  function getClaudeSettingsPath() {
44771
- return process.env["CLAUDE_SETTINGS"] ?? join10(process.env["CLAUDE_HOME"] ?? join10(homedir8(), ".claude"), "settings.json");
45003
+ return process.env["CLAUDE_SETTINGS"] ?? join11(process.env["CLAUDE_HOME"] ?? join11(homedir6(), ".claude"), "settings.json");
44772
45004
  }
44773
45005
  function checkStatuslineIntegration() {
44774
45006
  const settingsPath = getClaudeSettingsPath();
44775
45007
  if (!existsSync8(settingsPath)) return "no_settings";
44776
45008
  try {
44777
- const settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
45009
+ const settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
44778
45010
  const statusLine = settings.statusLine;
44779
45011
  if (!statusLine?.type) return "not_configured";
44780
45012
  if (statusLine.type !== "command") return "custom_no_cleo";
@@ -44782,10 +45014,10 @@ function checkStatuslineIntegration() {
44782
45014
  if (cmd.includes("context-monitor.sh") || cmd.includes("cleo-statusline") || cmd.includes(".context-state.json") || cmd.includes("context-states")) {
44783
45015
  return "configured";
44784
45016
  }
44785
- const scriptPath = cmd.startsWith("~") ? cmd.replace("~", homedir8()) : cmd;
45017
+ const scriptPath = cmd.startsWith("~") ? cmd.replace("~", homedir6()) : cmd;
44786
45018
  if (existsSync8(scriptPath)) {
44787
45019
  try {
44788
- const content = readFileSync7(scriptPath, "utf-8");
45020
+ const content = readFileSync6(scriptPath, "utf-8");
44789
45021
  if (content.includes("context-state.json")) return "configured";
44790
45022
  } catch {
44791
45023
  }
@@ -44799,7 +45031,7 @@ function getStatuslineConfig(cleoHome) {
44799
45031
  return {
44800
45032
  statusLine: {
44801
45033
  type: "command",
44802
- command: join10(cleoHome, "lib", "session", "context-monitor.sh")
45034
+ command: join11(cleoHome, "lib", "session", "context-monitor.sh")
44803
45035
  }
44804
45036
  };
44805
45037
  }
@@ -44864,6 +45096,23 @@ var init_paths2 = __esm({
44864
45096
  }
44865
45097
  });
44866
45098
 
45099
+ // packages/adapters/src/providers/shared/cli-spawn.ts
45100
+ import { spawn } from "node:child_process";
45101
+ import { resolveSpawnInvocation as resolveSpawnInvocation2 } from "@cleocode/paths";
45102
+ function spawnCli(command, args, options) {
45103
+ const inv = resolveSpawnInvocation2(command, args);
45104
+ return spawn(
45105
+ inv.file,
45106
+ inv.args,
45107
+ inv.windowsVerbatimArguments ? { ...options, windowsVerbatimArguments: true } : options
45108
+ );
45109
+ }
45110
+ var init_cli_spawn = __esm({
45111
+ "packages/adapters/src/providers/shared/cli-spawn.ts"() {
45112
+ "use strict";
45113
+ }
45114
+ });
45115
+
44867
45116
  // packages/adapters/src/providers/cursor/hooks.ts
44868
45117
  var PROVIDER_ID2, CURSOR_EVENT_MAP, CursorHookProvider;
44869
45118
  var init_hooks2 = __esm({
@@ -45018,15 +45267,30 @@ var init_hooks2 = __esm({
45018
45267
  });
45019
45268
 
45020
45269
  // packages/adapters/src/providers/cursor/install.ts
45021
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
45022
- import { join as join16 } from "node:path";
45023
- import { ensureProviderInstructionFile as ensureProviderInstructionFile3 } from "@cleocode/caamp";
45024
- var CursorInstallProvider;
45270
+ import { existsSync as existsSync11, readFileSync as readFileSync7 } from "node:fs";
45271
+ import { join as join17 } from "node:path";
45272
+ import {
45273
+ ensureProviderInstructionFile as ensureProviderInstructionFile3,
45274
+ ensureProviderRuleFile,
45275
+ updateJsonConfigFile as updateJsonConfigFile3
45276
+ } from "@cleocode/caamp";
45277
+ function isRecord(value) {
45278
+ return typeof value === "object" && value !== null && !Array.isArray(value);
45279
+ }
45280
+ var CLEO_MDC_FRONTMATTER, CursorInstallProvider;
45025
45281
  var init_install2 = __esm({
45026
45282
  "packages/adapters/src/providers/cursor/install.ts"() {
45027
45283
  "use strict";
45028
45284
  init_hook_template_installer();
45029
45285
  init_paths2();
45286
+ CLEO_MDC_FRONTMATTER = [
45287
+ "---",
45288
+ "description: CLEO task management protocol references",
45289
+ 'globs: "**/*"',
45290
+ "alwaysApply: true",
45291
+ "---",
45292
+ ""
45293
+ ].join("\n");
45030
45294
  CursorInstallProvider = class {
45031
45295
  /**
45032
45296
  * Install CLEO into a Cursor project.
@@ -45040,17 +45304,23 @@ var init_install2 = __esm({
45040
45304
  let instructionFileUpdated = false;
45041
45305
  const details = {};
45042
45306
  const instructionResult = await ensureProviderInstructionFile3("cursor", projectDir, {});
45043
- const cursorFilesUpdated = this.updateInstructionFiles(projectDir);
45307
+ const cursorFilesUpdated = await this.updateInstructionFiles(projectDir);
45044
45308
  instructionFileUpdated = instructionResult.action !== "intact" || cursorFilesUpdated;
45045
45309
  if (instructionFileUpdated) {
45046
45310
  details.instructionFiles = this.getUpdatedFileList(projectDir);
45047
45311
  }
45048
- const hookResult = this.installHookTemplates(projectDir);
45049
- if (hookResult) {
45050
- details.hookTemplates = hookResult;
45312
+ let hooksError = null;
45313
+ try {
45314
+ const hookResult = await this.installHookTemplates(projectDir);
45315
+ if (hookResult) {
45316
+ details.hookTemplates = hookResult;
45317
+ }
45318
+ } catch (err) {
45319
+ hooksError = err instanceof Error ? err.message : String(err);
45320
+ details.hooksError = hooksError;
45051
45321
  }
45052
45322
  return {
45053
- success: true,
45323
+ success: hooksError === null,
45054
45324
  installedAt,
45055
45325
  instructionFileUpdated,
45056
45326
  details
@@ -45069,14 +45339,14 @@ var init_install2 = __esm({
45069
45339
  * Checks for .cursor/rules/cleo.mdc or .cursorrules with CLEO references.
45070
45340
  */
45071
45341
  async isInstalled() {
45072
- const mdcPath = join16(process.cwd(), ".cursor", "rules", "cleo.mdc");
45342
+ const mdcPath = join17(process.cwd(), ".cursor", "rules", "cleo.mdc");
45073
45343
  if (existsSync11(mdcPath)) {
45074
45344
  return true;
45075
45345
  }
45076
- const rulesPath = join16(process.cwd(), ".cursorrules");
45346
+ const rulesPath = join17(process.cwd(), ".cursorrules");
45077
45347
  if (existsSync11(rulesPath)) {
45078
45348
  try {
45079
- const content = readFileSync8(rulesPath, "utf-8");
45349
+ const content = readFileSync7(rulesPath, "utf-8");
45080
45350
  const injectionRef = `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`;
45081
45351
  if (content.includes(injectionRef) || content.includes("@.cleo/memory-bridge.md")) {
45082
45352
  return true;
@@ -45096,98 +45366,46 @@ var init_install2 = __esm({
45096
45366
  */
45097
45367
  async ensureInstructionReferences(projectDir) {
45098
45368
  await ensureProviderInstructionFile3("cursor", projectDir, {});
45099
- this.updateInstructionFiles(projectDir);
45369
+ await this.updateInstructionFiles(projectDir);
45100
45370
  }
45101
45371
  /**
45102
- * Update instruction files with CLEO @-references.
45103
- *
45104
- * Handles both legacy (.cursorrules) and modern (.cursor/rules/cleo.mdc) formats.
45372
+ * Update Cursor's auxiliary rule files through the CAAMP writer.
45105
45373
  *
45106
- * @returns true if any file was created or modified
45107
- */
45108
- updateInstructionFiles(projectDir) {
45109
- let updated = false;
45110
- if (this.updateLegacyRules(projectDir)) {
45111
- updated = true;
45112
- }
45113
- if (this.updateModernRules(projectDir)) {
45114
- updated = true;
45115
- }
45116
- return updated;
45117
- }
45118
- /**
45119
- * Update legacy .cursorrules file with @-references.
45374
+ * @remarks
45375
+ * ADR-064 / T12385: both files are written by CAAMP's
45376
+ * `ensureProviderRuleFile` — locked, atomic, with references taken from the
45377
+ * provider registry — instead of raw `writeFileSync` with a local copy of
45378
+ * the references.
45120
45379
  *
45121
- * References are sourced from the CAAMP provider registry (T1919) rather than
45122
- * a local constant, keeping this adapter in sync with the single source of truth.
45123
- * Only modifies the file if it already exists (does not create it).
45380
+ * - Legacy `.cursorrules` is only updated when it already exists; CLEO's
45381
+ * references live in a CAAMP block and user text is preserved.
45382
+ * - Modern `.cursor/rules/cleo.mdc` is CLEO-owned: MDC frontmatter followed
45383
+ * by the CAAMP block.
45124
45384
  *
45125
- * @returns true if the file was modified
45126
- */
45127
- updateLegacyRules(projectDir) {
45128
- const rulesPath = join16(projectDir, ".cursorrules");
45129
- if (!existsSync11(rulesPath)) {
45130
- return false;
45131
- }
45132
- let content = readFileSync8(rulesPath, "utf-8");
45133
- const cursorRefs = [
45134
- `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`,
45135
- "@.cleo/memory-bridge.md"
45136
- ];
45137
- const missingRefs = cursorRefs.filter((ref) => !content.includes(ref));
45138
- if (missingRefs.length === 0) {
45139
- return false;
45140
- }
45141
- const separator = content.endsWith("\n") ? "" : "\n";
45142
- content = content + separator + missingRefs.join("\n") + "\n";
45143
- writeFileSync4(rulesPath, content, "utf-8");
45144
- return true;
45145
- }
45146
- /**
45147
- * Create or update .cursor/rules/cleo.mdc with CLEO references.
45148
- *
45149
- * MDC (Markdown Component) format is Cursor's modern rule file format.
45150
- * Each .mdc file in .cursor/rules/ is loaded as a rule set.
45151
- * References are sourced from the CAAMP provider registry (T1919).
45152
- *
45153
- * @returns true if the file was created or modified
45385
+ * @returns true if any file was created or modified
45154
45386
  */
45155
- updateModernRules(projectDir) {
45156
- const rulesDir = join16(projectDir, ".cursor", "rules");
45157
- const mdcPath = join16(rulesDir, "cleo.mdc");
45158
- const cursorRefs = [
45159
- `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`,
45160
- "@.cleo/memory-bridge.md"
45387
+ async updateInstructionFiles(projectDir) {
45388
+ const results = [
45389
+ await ensureProviderRuleFile("cursor", projectDir, {
45390
+ relativePath: ".cursorrules",
45391
+ onlyIfExists: true
45392
+ }),
45393
+ await ensureProviderRuleFile("cursor", projectDir, {
45394
+ relativePath: join17(".cursor", "rules", "cleo.mdc"),
45395
+ preamble: CLEO_MDC_FRONTMATTER
45396
+ })
45161
45397
  ];
45162
- const expectedContent = [
45163
- "---",
45164
- "description: CLEO task management protocol references",
45165
- 'globs: "**/*"',
45166
- "alwaysApply: true",
45167
- "---",
45168
- "",
45169
- ...cursorRefs,
45170
- ""
45171
- ].join("\n");
45172
- if (existsSync11(mdcPath)) {
45173
- const existing = readFileSync8(mdcPath, "utf-8");
45174
- if (existing === expectedContent) {
45175
- return false;
45176
- }
45177
- }
45178
- mkdirSync4(rulesDir, { recursive: true });
45179
- writeFileSync4(mdcPath, expectedContent, "utf-8");
45180
- return true;
45398
+ return results.some((r) => r !== null && r.action !== "intact");
45181
45399
  }
45182
45400
  /**
45183
45401
  * Get list of instruction files that were updated.
45184
45402
  */
45185
45403
  getUpdatedFileList(projectDir) {
45186
45404
  const files = [];
45187
- if (existsSync11(join16(projectDir, ".cursorrules"))) {
45188
- files.push(join16(projectDir, ".cursorrules"));
45405
+ if (existsSync11(join17(projectDir, ".cursorrules"))) {
45406
+ files.push(join17(projectDir, ".cursorrules"));
45189
45407
  }
45190
- files.push(join16(projectDir, ".cursor", "rules", "cleo.mdc"));
45408
+ files.push(join17(projectDir, ".cursor", "rules", "cleo.mdc"));
45191
45409
  return files;
45192
45410
  }
45193
45411
  /**
@@ -45208,8 +45426,8 @@ var init_install2 = __esm({
45208
45426
  *
45209
45427
  * @task T1013
45210
45428
  */
45211
- installHookTemplates(projectDir) {
45212
- const hooksDir = join16(projectDir, ".cursor", "hooks");
45429
+ async installHookTemplates(projectDir) {
45430
+ const hooksDir = join17(projectDir, ".cursor", "hooks");
45213
45431
  let templates;
45214
45432
  try {
45215
45433
  templates = installProviderHookTemplates({
@@ -45219,9 +45437,9 @@ var init_install2 = __esm({
45219
45437
  } catch {
45220
45438
  return null;
45221
45439
  }
45222
- const hooksJsonEntryAdded = this.registerPreCompactHook(
45440
+ const hooksJsonEntryAdded = await this.registerPreCompactHook(
45223
45441
  projectDir,
45224
- join16(hooksDir, "precompact.sh")
45442
+ join17(hooksDir, "precompact.sh")
45225
45443
  );
45226
45444
  if (templates.installedFiles.length === 0 && !hooksJsonEntryAdded) {
45227
45445
  return null;
@@ -45241,33 +45459,30 @@ var init_install2 = __esm({
45241
45459
  *
45242
45460
  * @task T1013
45243
45461
  */
45244
- registerPreCompactHook(projectDir, shimPath) {
45245
- const hooksJsonPath = join16(projectDir, ".cursor", "hooks.json");
45246
- let config2 = {};
45247
- if (existsSync11(hooksJsonPath)) {
45248
- try {
45249
- config2 = JSON.parse(readFileSync8(hooksJsonPath, "utf-8"));
45250
- } catch {
45462
+ async registerPreCompactHook(projectDir, shimPath) {
45463
+ const hooksJsonPath = join17(projectDir, ".cursor", "hooks.json");
45464
+ return updateJsonConfigFile3(hooksJsonPath, (config2) => {
45465
+ const hooks = config2.hooks ?? {};
45466
+ if (!isRecord(hooks)) {
45467
+ throw new Error('.cursor/hooks.json "hooks" is not an object; not modifying it');
45468
+ }
45469
+ const entries = hooks.preCompact ?? [];
45470
+ if (!Array.isArray(entries)) {
45471
+ throw new Error('.cursor/hooks.json "hooks.preCompact" is not an array; not modifying it');
45472
+ }
45473
+ const alreadyWired = entries.some(
45474
+ (entry) => isRecord(entry) && typeof entry.command === "string" && entry.command.includes("# cleo-hook") && entry.command.includes("precompact.sh")
45475
+ );
45476
+ if (alreadyWired) {
45477
+ return false;
45251
45478
  }
45252
- }
45253
- const hooks = config2.hooks ?? {};
45254
- const entries = hooks.preCompact ?? [];
45255
- const alreadyWired = entries.some(
45256
- (entry) => typeof entry === "object" && entry !== null && typeof entry.command === "string" && entry.command.includes("# cleo-hook") && entry.command.includes("precompact.sh")
45257
- );
45258
- if (alreadyWired) {
45259
- return false;
45260
- }
45261
- entries.push({
45262
- type: "command",
45263
- command: `"${shimPath}" # cleo-hook`,
45264
- timeout: 30
45479
+ hooks.preCompact = [
45480
+ ...entries,
45481
+ { type: "command", command: `"${shimPath}" # cleo-hook`, timeout: 30 }
45482
+ ];
45483
+ config2.hooks = hooks;
45484
+ return true;
45265
45485
  });
45266
- hooks.preCompact = entries;
45267
- config2.hooks = hooks;
45268
- mkdirSync4(join16(projectDir, ".cursor"), { recursive: true });
45269
- writeFileSync4(hooksJsonPath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
45270
- return true;
45271
45486
  }
45272
45487
  };
45273
45488
  }
@@ -45275,7 +45490,7 @@ var init_install2 = __esm({
45275
45490
 
45276
45491
  // packages/adapters/src/providers/cursor/adapter.ts
45277
45492
  import { existsSync as existsSync12 } from "node:fs";
45278
- import { join as join17 } from "node:path";
45493
+ import { join as join18 } from "node:path";
45279
45494
  var CursorAdapter;
45280
45495
  var init_adapter2 = __esm({
45281
45496
  "packages/adapters/src/providers/cursor/adapter.ts"() {
@@ -45369,14 +45584,14 @@ var init_adapter2 = __esm({
45369
45584
  }
45370
45585
  let configExists = false;
45371
45586
  if (this.projectDir) {
45372
- const cursorConfigDir = join17(this.projectDir, ".cursor");
45587
+ const cursorConfigDir = join18(this.projectDir, ".cursor");
45373
45588
  configExists = existsSync12(cursorConfigDir);
45374
45589
  details.configDirExists = configExists;
45375
45590
  }
45376
45591
  const editorEnvSet = process.env.CURSOR_EDITOR !== void 0;
45377
45592
  details.editorEnvSet = editorEnvSet;
45378
45593
  if (this.projectDir) {
45379
- const legacyRulesExist = existsSync12(join17(this.projectDir, ".cursorrules"));
45594
+ const legacyRulesExist = existsSync12(join18(this.projectDir, ".cursorrules"));
45380
45595
  details.legacyRulesExist = legacyRulesExist;
45381
45596
  }
45382
45597
  const healthy = configExists || editorEnvSet;
@@ -45580,8 +45795,8 @@ var init_hooks3 = __esm({
45580
45795
  });
45581
45796
 
45582
45797
  // packages/adapters/src/providers/opencode/install.ts
45583
- import { existsSync as existsSync17, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
45584
- import { join as join23 } from "node:path";
45798
+ import { existsSync as existsSync17, mkdirSync as mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync2 } from "node:fs";
45799
+ import { join as join24 } from "node:path";
45585
45800
  import { ensureProviderInstructionFile as ensureProviderInstructionFile6 } from "@cleocode/caamp";
45586
45801
  var OpenCodeInstallProvider;
45587
45802
  var init_install3 = __esm({
@@ -45606,7 +45821,7 @@ var init_install3 = __esm({
45606
45821
  });
45607
45822
  const instructionFileUpdated = instructResult.action !== "intact";
45608
45823
  if (instructionFileUpdated) {
45609
- details.instructionFile = join23(projectDir, instructResult.instructFile);
45824
+ details.instructionFile = join24(projectDir, instructResult.instructFile);
45610
45825
  }
45611
45826
  const hookResult = this.installHookTemplates(projectDir);
45612
45827
  if (hookResult) {
@@ -45679,8 +45894,8 @@ var init_install3 = __esm({
45679
45894
  * @task T1013
45680
45895
  */
45681
45896
  installHookTemplates(projectDir) {
45682
- const pluginsDir = join23(projectDir, ".opencode", "plugins");
45683
- const hooksDir = join23(pluginsDir, "hooks");
45897
+ const pluginsDir = join24(projectDir, ".opencode", "plugins");
45898
+ const hooksDir = join24(pluginsDir, "hooks");
45684
45899
  let templates;
45685
45900
  try {
45686
45901
  templates = installProviderHookTemplates({
@@ -45692,7 +45907,7 @@ var init_install3 = __esm({
45692
45907
  }
45693
45908
  let pluginWritten = false;
45694
45909
  try {
45695
- pluginWritten = this.writePrecompactPlugin(pluginsDir, join23(hooksDir, "precompact.sh"));
45910
+ pluginWritten = this.writePrecompactPlugin(pluginsDir, join24(hooksDir, "precompact.sh"));
45696
45911
  } catch {
45697
45912
  }
45698
45913
  if (templates.installedFiles.length === 0 && !pluginWritten) {
@@ -45716,7 +45931,7 @@ var init_install3 = __esm({
45716
45931
  * @task T1013
45717
45932
  */
45718
45933
  writePrecompactPlugin(pluginsDir, shimPath) {
45719
- const pluginPath = join23(pluginsDir, "cleo-precompact.js");
45934
+ const pluginPath = join24(pluginsDir, "cleo-precompact.js");
45720
45935
  const generated = [
45721
45936
  "// CLEO PreCompact plugin for OpenCode (generated by @cleocode/adapters).",
45722
45937
  "// Bridges the canonical CAAMP `PreCompact` event",
@@ -45744,14 +45959,14 @@ var init_install3 = __esm({
45744
45959
  ].join("\n");
45745
45960
  if (existsSync17(pluginPath)) {
45746
45961
  try {
45747
- if (readFileSync11(pluginPath, "utf-8") === generated) {
45962
+ if (readFileSync10(pluginPath, "utf-8") === generated) {
45748
45963
  return false;
45749
45964
  }
45750
45965
  } catch {
45751
45966
  }
45752
45967
  }
45753
- mkdirSync5(pluginsDir, { recursive: true });
45754
- writeFileSync5(pluginPath, generated, "utf-8");
45968
+ mkdirSync3(pluginsDir, { recursive: true });
45969
+ writeFileSync2(pluginPath, generated, "utf-8");
45755
45970
  return true;
45756
45971
  }
45757
45972
  };
@@ -45759,10 +45974,9 @@ var init_install3 = __esm({
45759
45974
  });
45760
45975
 
45761
45976
  // packages/adapters/src/providers/opencode/spawn.ts
45762
- import { exec as exec9, spawn as nodeSpawn4 } from "node:child_process";
45763
45977
  import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
45764
- import { join as join24 } from "node:path";
45765
- import { promisify as promisify10 } from "node:util";
45978
+ import { join as join25 } from "node:path";
45979
+ import { findOnPath as findOnPath9 } from "@cleocode/paths";
45766
45980
  function buildOpenCodeAgentMarkdown(description, instructions) {
45767
45981
  const normalizedDesc = description.replace(/\s+/g, " ").trim();
45768
45982
  return [
@@ -45777,8 +45991,8 @@ function buildOpenCodeAgentMarkdown(description, instructions) {
45777
45991
  ].join("\n");
45778
45992
  }
45779
45993
  async function ensureSubagentDefinition(workingDirectory, enrichedInstructions) {
45780
- const agentDir = join24(workingDirectory, ".opencode", "agent");
45781
- const agentPath = join24(agentDir, `${OPENCODE_SUBAGENT_NAME}.md`);
45994
+ const agentDir = join25(workingDirectory, ".opencode", "agent");
45995
+ const agentPath = join25(agentDir, `${OPENCODE_SUBAGENT_NAME}.md`);
45782
45996
  const description = "CLEO task executor with protocol compliance and CANT context.";
45783
45997
  const instructions = enrichedInstructions ?? [
45784
45998
  "# CLEO Subagent",
@@ -45801,12 +46015,12 @@ async function ensureSubagentDefinition(workingDirectory, enrichedInstructions)
45801
46015
  }
45802
46016
  return OPENCODE_SUBAGENT_NAME;
45803
46017
  }
45804
- var execAsync9, OPENCODE_SUBAGENT_NAME, OPENCODE_FALLBACK_AGENT, OpenCodeSpawnProvider;
46018
+ var OPENCODE_SUBAGENT_NAME, OPENCODE_FALLBACK_AGENT, OpenCodeSpawnProvider;
45805
46019
  var init_spawn3 = __esm({
45806
46020
  "packages/adapters/src/providers/opencode/spawn.ts"() {
45807
46021
  "use strict";
46022
+ init_cli_spawn();
45808
46023
  init_paths2();
45809
- execAsync9 = promisify10(exec9);
45810
46024
  OPENCODE_SUBAGENT_NAME = "cleo-subagent";
45811
46025
  OPENCODE_FALLBACK_AGENT = "general";
45812
46026
  OpenCodeSpawnProvider = class {
@@ -45815,15 +46029,10 @@ var init_spawn3 = __esm({
45815
46029
  /**
45816
46030
  * Check if the OpenCode CLI is available in PATH.
45817
46031
  *
45818
- * @returns true if `opencode` is found via `which`
46032
+ * @returns true if `opencode` is found on PATH (PATHEXT-aware on Windows)
45819
46033
  */
45820
46034
  async canSpawn() {
45821
- try {
45822
- await execAsync9("which opencode");
45823
- return true;
45824
- } catch {
45825
- return false;
45826
- }
46035
+ return findOnPath9("opencode") !== null;
45827
46036
  }
45828
46037
  /**
45829
46038
  * Spawn a subagent via OpenCode CLI.
@@ -45856,24 +46065,17 @@ var init_spawn3 = __esm({
45856
46065
  } catch {
45857
46066
  agentName = OPENCODE_FALLBACK_AGENT;
45858
46067
  }
45859
- const child = nodeSpawn4(
46068
+ const child = spawnCli(
45860
46069
  "opencode",
45861
- [
45862
- "run",
45863
- "--format",
45864
- "json",
45865
- "--agent",
45866
- agentName,
45867
- "--title",
45868
- `CLEO ${context.taskId}`,
45869
- context.prompt
45870
- ],
46070
+ ["run", "--format", "json", "--agent", agentName, "--title", `CLEO ${context.taskId}`],
45871
46071
  {
45872
46072
  cwd: workingDirectory,
45873
46073
  detached: true,
45874
- stdio: "ignore"
46074
+ stdio: ["pipe", "ignore", "ignore"]
45875
46075
  }
45876
46076
  );
46077
+ child.stdin?.on("error", () => void 0);
46078
+ child.stdin?.end(context.prompt, "utf-8");
45877
46079
  child.unref();
45878
46080
  if (child.pid) {
45879
46081
  this.processMap.set(instanceId, {
@@ -45885,6 +46087,9 @@ var init_spawn3 = __esm({
45885
46087
  child.on("exit", () => {
45886
46088
  this.processMap.delete(instanceId);
45887
46089
  });
46090
+ child.on("error", () => {
46091
+ this.processMap.delete(instanceId);
46092
+ });
45888
46093
  return {
45889
46094
  instanceId,
45890
46095
  taskId: context.taskId,
@@ -45951,18 +46156,16 @@ var init_spawn3 = __esm({
45951
46156
  });
45952
46157
 
45953
46158
  // packages/adapters/src/providers/opencode/adapter.ts
45954
- import { exec as exec10 } from "node:child_process";
45955
46159
  import { existsSync as existsSync18 } from "node:fs";
45956
- import { join as join25 } from "node:path";
45957
- import { promisify as promisify11 } from "node:util";
45958
- var execAsync10, OpenCodeAdapter;
46160
+ import { join as join26 } from "node:path";
46161
+ import { findOnPath as findOnPath10 } from "@cleocode/paths";
46162
+ var OpenCodeAdapter;
45959
46163
  var init_adapter3 = __esm({
45960
46164
  "packages/adapters/src/providers/opencode/adapter.ts"() {
45961
46165
  "use strict";
45962
46166
  init_hooks3();
45963
46167
  init_install3();
45964
46168
  init_spawn3();
45965
- execAsync10 = promisify11(exec10);
45966
46169
  OpenCodeAdapter = class {
45967
46170
  /** Unique provider identifier. */
45968
46171
  id = "opencode";
@@ -46057,15 +46260,12 @@ var init_adapter3 = __esm({
46057
46260
  };
46058
46261
  }
46059
46262
  let cliAvailable = false;
46060
- try {
46061
- const { stdout } = await execAsync10("which opencode");
46062
- cliAvailable = stdout.trim().length > 0;
46063
- details.cliPath = stdout.trim();
46064
- } catch {
46065
- details.cliAvailable = false;
46066
- }
46263
+ const resolvedCli = findOnPath10("opencode");
46264
+ cliAvailable = resolvedCli !== null;
46265
+ if (resolvedCli) details.cliPath = resolvedCli;
46266
+ else details.cliAvailable = false;
46067
46267
  if (this.projectDir) {
46068
- const openCodeConfigDir = join25(this.projectDir, ".opencode");
46268
+ const openCodeConfigDir = join26(this.projectDir, ".opencode");
46069
46269
  const configExists = existsSync18(openCodeConfigDir);
46070
46270
  details.configDirExists = configExists;
46071
46271
  }
@@ -46275,7 +46475,7 @@ var init_hooks4 = __esm({
46275
46475
  });
46276
46476
 
46277
46477
  // packages/adapters/src/providers/pi/install.ts
46278
- import { join as join26 } from "node:path";
46478
+ import { join as join27 } from "node:path";
46279
46479
  import { ensureProviderInstructionFile as ensureProviderInstructionFile7 } from "@cleocode/caamp";
46280
46480
  function getCleoReferences() {
46281
46481
  return [`@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`, "@.cleo/memory-bridge.md"];
@@ -46302,7 +46502,7 @@ var init_install4 = __esm({
46302
46502
  });
46303
46503
  const projectUpdated = projectResult.action !== "intact";
46304
46504
  if (projectUpdated) {
46305
- details.instructionFile = join26(projectDir, projectResult.instructFile);
46505
+ details.instructionFile = join27(projectDir, projectResult.instructFile);
46306
46506
  }
46307
46507
  let globalUpdated = false;
46308
46508
  try {
@@ -46373,40 +46573,30 @@ var init_install4 = __esm({
46373
46573
  });
46374
46574
 
46375
46575
  // packages/adapters/src/providers/pi/spawn.ts
46376
- import { exec as exec11, spawn as nodeSpawn5 } from "node:child_process";
46377
- import { unlink as unlink2, writeFile as writeFile3 } from "node:fs/promises";
46378
- import { promisify as promisify12 } from "node:util";
46576
+ import { findOnPath as findOnPath11 } from "@cleocode/paths";
46379
46577
  function getPiCliPath() {
46380
46578
  return process.env["PI_CLI_PATH"] ?? "pi";
46381
46579
  }
46382
- var execAsync11, PiSpawnProvider;
46580
+ var PiSpawnProvider;
46383
46581
  var init_spawn4 = __esm({
46384
46582
  "packages/adapters/src/providers/pi/spawn.ts"() {
46385
46583
  "use strict";
46386
46584
  init_src();
46387
- execAsync11 = promisify12(exec11);
46585
+ init_cli_spawn();
46586
+ init_prompt_file();
46388
46587
  PiSpawnProvider = class {
46389
46588
  /** Map of instance IDs to tracked process info. */
46390
46589
  processMap = /* @__PURE__ */ new Map();
46391
46590
  /**
46392
46591
  * Check if the Pi CLI is available.
46393
46592
  *
46394
- * Checks `PI_CLI_PATH` env var first, then tries `which pi`.
46593
+ * Checks `PI_CLI_PATH` env var first, then searches PATH for `pi`.
46395
46594
  *
46396
46595
  * @returns true if the Pi CLI is accessible
46397
46596
  */
46398
46597
  async canSpawn() {
46399
46598
  const cliPath = getPiCliPath();
46400
- try {
46401
- if (cliPath !== "pi") {
46402
- const { stdout } = await execAsync11(`test -x "${cliPath}" && echo ok`);
46403
- return stdout.trim() === "ok";
46404
- }
46405
- await execAsync11("which pi");
46406
- return true;
46407
- } catch {
46408
- return false;
46409
- }
46599
+ return findOnPath11(cliPath) !== null;
46410
46600
  }
46411
46601
  /**
46412
46602
  * Spawn a subagent via Pi CLI.
@@ -46422,8 +46612,7 @@ var init_spawn4 = __esm({
46422
46612
  const startTime = (/* @__PURE__ */ new Date()).toISOString();
46423
46613
  let tmpFile;
46424
46614
  try {
46425
- tmpFile = `/tmp/pi-spawn-${instanceId}.txt`;
46426
- await writeFile3(tmpFile, context.prompt, "utf-8");
46615
+ tmpFile = await writeSpawnPromptFile("pi-spawn", context.prompt);
46427
46616
  const cliPath = getPiCliPath();
46428
46617
  const args = [tmpFile];
46429
46618
  const spawnOpts = {
@@ -46433,7 +46622,7 @@ var init_spawn4 = __esm({
46433
46622
  if (context.workingDirectory) {
46434
46623
  spawnOpts.cwd = context.workingDirectory;
46435
46624
  }
46436
- const child = nodeSpawn5(cliPath, args, spawnOpts);
46625
+ const child = spawnCli(cliPath, args, spawnOpts);
46437
46626
  child.unref();
46438
46627
  if (child.pid) {
46439
46628
  this.processMap.set(instanceId, {
@@ -46445,10 +46634,7 @@ var init_spawn4 = __esm({
46445
46634
  const capturedTmpFile = tmpFile;
46446
46635
  child.on("exit", async () => {
46447
46636
  this.processMap.delete(instanceId);
46448
- try {
46449
- await unlink2(capturedTmpFile);
46450
- } catch {
46451
- }
46637
+ await removeSpawnPromptFile(capturedTmpFile);
46452
46638
  });
46453
46639
  return {
46454
46640
  instanceId,
@@ -46460,10 +46646,7 @@ var init_spawn4 = __esm({
46460
46646
  } catch (error48) {
46461
46647
  console.error(`[PiSpawnProvider] Failed to spawn: ${getErrorMessage(error48)}`);
46462
46648
  if (tmpFile) {
46463
- try {
46464
- await unlink2(tmpFile);
46465
- } catch {
46466
- }
46649
+ await removeSpawnPromptFile(tmpFile);
46467
46650
  }
46468
46651
  return {
46469
46652
  instanceId,
@@ -46524,32 +46707,30 @@ var init_spawn4 = __esm({
46524
46707
  });
46525
46708
 
46526
46709
  // packages/adapters/src/providers/pi/adapter.ts
46527
- import { exec as exec12 } from "node:child_process";
46528
46710
  import { existsSync as existsSync19 } from "node:fs";
46529
- import { homedir as homedir14 } from "node:os";
46530
- import { join as join27 } from "node:path";
46531
- import { promisify as promisify13 } from "node:util";
46711
+ import { homedir as homedir12 } from "node:os";
46712
+ import { join as join28 } from "node:path";
46713
+ import { findOnPath as findOnPath12 } from "@cleocode/paths";
46532
46714
  function getPiAgentDir() {
46533
46715
  const env = process.env["PI_CODING_AGENT_DIR"];
46534
46716
  if (env !== void 0 && env.length > 0) {
46535
- if (env === "~") return homedir14();
46536
- if (env.startsWith("~/")) return join27(homedir14(), env.slice(2));
46717
+ if (env === "~") return homedir12();
46718
+ if (env.startsWith("~/")) return join28(homedir12(), env.slice(2));
46537
46719
  return env;
46538
46720
  }
46539
46721
  const piHome = process.env["PI_HOME"];
46540
46722
  if (piHome !== void 0 && piHome.length > 0) {
46541
- return join27(piHome, "agent");
46723
+ return join28(piHome, "agent");
46542
46724
  }
46543
- return join27(homedir14(), ".pi", "agent");
46725
+ return join28(homedir12(), ".pi", "agent");
46544
46726
  }
46545
- var execAsync12, PiAdapter;
46727
+ var PiAdapter;
46546
46728
  var init_adapter4 = __esm({
46547
46729
  "packages/adapters/src/providers/pi/adapter.ts"() {
46548
46730
  "use strict";
46549
46731
  init_hooks4();
46550
46732
  init_install4();
46551
46733
  init_spawn4();
46552
- execAsync12 = promisify13(exec12);
46553
46734
  PiAdapter = class {
46554
46735
  /** Unique provider identifier. */
46555
46736
  id = "pi";
@@ -46628,7 +46809,7 @@ var init_adapter4 = __esm({
46628
46809
  *
46629
46810
  * Checks:
46630
46811
  * 1. Adapter has been initialized
46631
- * 2. Pi CLI is available (via PI_CLI_PATH or `which pi`)
46812
+ * 2. Pi CLI is available (via PI_CLI_PATH or a PATH search for `pi`)
46632
46813
  * 3. Pi global state root (~/.pi/agent/ or PI_CODING_AGENT_DIR) exists
46633
46814
  *
46634
46815
  * @returns Health status with details about each check
@@ -46644,25 +46825,16 @@ var init_adapter4 = __esm({
46644
46825
  }
46645
46826
  let cliAvailable = false;
46646
46827
  const cliPath = process.env["PI_CLI_PATH"] ?? "pi";
46647
- try {
46648
- if (cliPath !== "pi") {
46649
- const { stdout } = await execAsync12(`test -x "${cliPath}" && echo ok`);
46650
- cliAvailable = stdout.trim() === "ok";
46651
- details.cliPath = cliPath;
46652
- } else {
46653
- const { stdout } = await execAsync12("which pi");
46654
- cliAvailable = stdout.trim().length > 0;
46655
- details.cliPath = stdout.trim();
46656
- }
46657
- } catch {
46658
- details.cliAvailable = false;
46659
- }
46828
+ const resolvedCli = findOnPath12(cliPath);
46829
+ cliAvailable = resolvedCli !== null;
46830
+ if (resolvedCli) details.cliPath = cliPath === "pi" ? resolvedCli : cliPath;
46831
+ else details.cliAvailable = false;
46660
46832
  const agentDir = getPiAgentDir();
46661
46833
  const agentDirExists = existsSync19(agentDir);
46662
46834
  details.agentDirExists = agentDirExists;
46663
46835
  details.agentDir = agentDir;
46664
46836
  if (this.projectDir) {
46665
- const projectPiDir = join27(this.projectDir, ".pi");
46837
+ const projectPiDir = join28(this.projectDir, ".pi");
46666
46838
  details.projectPiDirExists = existsSync19(projectPiDir);
46667
46839
  }
46668
46840
  details.piCodingAgentDirSet = process.env["PI_CODING_AGENT_DIR"] !== void 0;
@@ -46837,14 +47009,14 @@ var ClaudeSDKAdapter = class {
46837
47009
 
46838
47010
  // packages/adapters/src/providers/claude-sdk/mcp-registry.ts
46839
47011
  import { existsSync as existsSync9 } from "node:fs";
46840
- import { join as join11 } from "node:path";
47012
+ import { join as join12 } from "node:path";
46841
47013
  var CLEO_MCP_CANDIDATES = [
46842
47014
  { name: "brain", binary: "cleo-mcp-brain" },
46843
47015
  { name: "nexus", binary: "cleo-mcp-nexus" },
46844
47016
  { name: "tasks", binary: "cleo-mcp-tasks" }
46845
47017
  ];
46846
47018
  function findInNodeModules(binary, workingDir) {
46847
- const binPath = join11(workingDir, "node_modules", ".bin", binary);
47019
+ const binPath = join12(workingDir, "node_modules", ".bin", binary);
46848
47020
  if (existsSync9(binPath)) {
46849
47021
  return binPath;
46850
47022
  }
@@ -46874,19 +47046,18 @@ function createAdapter2() {
46874
47046
  }
46875
47047
 
46876
47048
  // packages/adapters/src/providers/codex/adapter.ts
46877
- import { exec as exec3 } from "node:child_process";
46878
47049
  import { existsSync as existsSync10 } from "node:fs";
46879
- import { homedir as homedir10 } from "node:os";
46880
- import { join as join15 } from "node:path";
46881
- import { promisify as promisify4 } from "node:util";
47050
+ import { homedir as homedir8 } from "node:os";
47051
+ import { join as join16 } from "node:path";
47052
+ import { findOnPath as findOnPath3 } from "@cleocode/paths";
46882
47053
 
46883
47054
  // packages/adapters/src/providers/codex/hooks.ts
46884
- import { homedir as homedir9 } from "node:os";
46885
- import { join as join13 } from "node:path";
47055
+ import { homedir as homedir7 } from "node:os";
47056
+ import { join as join14 } from "node:path";
46886
47057
 
46887
47058
  // packages/adapters/src/providers/shared/transcript-reader.ts
46888
47059
  import { readdir as readdir2, readFile as readFile3 } from "node:fs/promises";
46889
- import { join as join12 } from "node:path";
47060
+ import { join as join13 } from "node:path";
46890
47061
  function parseTranscriptLines(raw) {
46891
47062
  const turns = [];
46892
47063
  const lines = raw.split("\n").filter((l) => l.trim());
@@ -46911,7 +47082,7 @@ async function readLatestTranscript(providerDir) {
46911
47082
  if (!entry.isFile()) continue;
46912
47083
  const name15 = entry.name;
46913
47084
  if (name15.endsWith(".json") || name15.endsWith(".jsonl")) {
46914
- allFiles.push(join12(providerDir, name15));
47085
+ allFiles.push(join13(providerDir, name15));
46915
47086
  }
46916
47087
  }
46917
47088
  } catch {
@@ -47001,13 +47172,13 @@ var CodexHookProvider = class {
47001
47172
  * @task T162 @epic T134
47002
47173
  */
47003
47174
  async getTranscript(_sessionId, _projectDir) {
47004
- return readLatestTranscript(join13(homedir9(), ".codex"));
47175
+ return readLatestTranscript(join14(homedir7(), ".codex"));
47005
47176
  }
47006
47177
  };
47007
47178
 
47008
47179
  // packages/adapters/src/providers/codex/install.ts
47009
47180
  init_paths2();
47010
- import { join as join14 } from "node:path";
47181
+ import { join as join15 } from "node:path";
47011
47182
  import { ensureProviderInstructionFile as ensureProviderInstructionFile2 } from "@cleocode/caamp";
47012
47183
  var CodexInstallProvider = class {
47013
47184
  /**
@@ -47027,7 +47198,7 @@ var CodexInstallProvider = class {
47027
47198
  });
47028
47199
  const instructionFileUpdated = result.action !== "intact";
47029
47200
  if (instructionFileUpdated) {
47030
- details.instructionFile = join14(projectDir, result.instructFile);
47201
+ details.instructionFile = join15(projectDir, result.instructFile);
47031
47202
  }
47032
47203
  return {
47033
47204
  success: true,
@@ -47081,7 +47252,6 @@ var CodexInstallProvider = class {
47081
47252
  };
47082
47253
 
47083
47254
  // packages/adapters/src/providers/codex/adapter.ts
47084
- var execAsync3 = promisify4(exec3);
47085
47255
  var CodexAdapter = class {
47086
47256
  /** Unique provider identifier. */
47087
47257
  id = "codex";
@@ -47158,14 +47328,11 @@ var CodexAdapter = class {
47158
47328
  };
47159
47329
  }
47160
47330
  let cliAvailable = false;
47161
- try {
47162
- const { stdout } = await execAsync3("which codex");
47163
- cliAvailable = stdout.trim().length > 0;
47164
- details.cliPath = stdout.trim();
47165
- } catch {
47166
- details.cliAvailable = false;
47167
- }
47168
- const codexConfigDir = join15(homedir10(), ".codex");
47331
+ const resolvedCli = findOnPath3("codex");
47332
+ cliAvailable = resolvedCli !== null;
47333
+ if (resolvedCli) details.cliPath = resolvedCli;
47334
+ else details.cliAvailable = false;
47335
+ const codexConfigDir = join16(homedir8(), ".codex");
47169
47336
  const configExists = existsSync10(codexConfigDir);
47170
47337
  details.configDirExists = configExists;
47171
47338
  const healthy = cliAvailable;
@@ -47193,10 +47360,10 @@ var CodexAdapter = class {
47193
47360
  };
47194
47361
 
47195
47362
  // packages/adapters/src/providers/codex/spawn.ts
47196
- import { exec as exec4, spawn as nodeSpawn2 } from "node:child_process";
47197
47363
  init_src();
47198
- import { promisify as promisify5 } from "node:util";
47199
- var execAsync4 = promisify5(exec4);
47364
+ init_cli_spawn();
47365
+ init_prompt_file();
47366
+ import { findOnPath as findOnPath4 } from "@cleocode/paths";
47200
47367
 
47201
47368
  // packages/adapters/src/providers/codex/index.ts
47202
47369
  function createAdapter3() {
@@ -47207,15 +47374,14 @@ function createAdapter3() {
47207
47374
  init_cursor();
47208
47375
 
47209
47376
  // packages/adapters/src/providers/gemini-cli/adapter.ts
47210
- import { exec as exec5 } from "node:child_process";
47211
47377
  import { existsSync as existsSync14 } from "node:fs";
47212
- import { homedir as homedir12 } from "node:os";
47213
- import { join as join20 } from "node:path";
47214
- import { promisify as promisify6 } from "node:util";
47378
+ import { homedir as homedir10 } from "node:os";
47379
+ import { join as join21 } from "node:path";
47380
+ import { findOnPath as findOnPath5 } from "@cleocode/paths";
47215
47381
 
47216
47382
  // packages/adapters/src/providers/gemini-cli/hooks.ts
47217
- import { homedir as homedir11 } from "node:os";
47218
- import { join as join18 } from "node:path";
47383
+ import { homedir as homedir9 } from "node:os";
47384
+ import { join as join19 } from "node:path";
47219
47385
  var GEMINI_CLI_EVENT_MAP = {
47220
47386
  SessionStart: "SessionStart",
47221
47387
  SessionEnd: "SessionEnd",
@@ -47293,14 +47459,14 @@ var GeminiCliHookProvider = class {
47293
47459
  * @task T161 @epic T134
47294
47460
  */
47295
47461
  async getTranscript(_sessionId, _projectDir) {
47296
- return readLatestTranscript(join18(homedir11(), ".gemini"));
47462
+ return readLatestTranscript(join19(homedir9(), ".gemini"));
47297
47463
  }
47298
47464
  };
47299
47465
 
47300
47466
  // packages/adapters/src/providers/gemini-cli/install.ts
47301
47467
  init_paths2();
47302
- import { existsSync as existsSync13, readFileSync as readFileSync9 } from "node:fs";
47303
- import { join as join19 } from "node:path";
47468
+ import { existsSync as existsSync13, readFileSync as readFileSync8 } from "node:fs";
47469
+ import { join as join20 } from "node:path";
47304
47470
  import { ensureProviderInstructionFile as ensureProviderInstructionFile4 } from "@cleocode/caamp";
47305
47471
  var GeminiCliInstallProvider = class {
47306
47472
  /**
@@ -47345,10 +47511,10 @@ var GeminiCliInstallProvider = class {
47345
47511
  */
47346
47512
  async isInstalled() {
47347
47513
  const instructionRef = `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`;
47348
- const geminiMdPath = join19(process.cwd(), "GEMINI.md");
47514
+ const geminiMdPath = join20(process.cwd(), "GEMINI.md");
47349
47515
  if (existsSync13(geminiMdPath)) {
47350
47516
  try {
47351
- const content = readFileSync9(geminiMdPath, "utf-8");
47517
+ const content = readFileSync8(geminiMdPath, "utf-8");
47352
47518
  if (content.includes(instructionRef)) {
47353
47519
  return true;
47354
47520
  }
@@ -47374,7 +47540,6 @@ var GeminiCliInstallProvider = class {
47374
47540
  };
47375
47541
 
47376
47542
  // packages/adapters/src/providers/gemini-cli/adapter.ts
47377
- var execAsync5 = promisify6(exec5);
47378
47543
  var GeminiCliAdapter = class {
47379
47544
  /** Unique provider identifier. */
47380
47545
  id = "gemini-cli";
@@ -47462,14 +47627,11 @@ var GeminiCliAdapter = class {
47462
47627
  };
47463
47628
  }
47464
47629
  let cliAvailable = false;
47465
- try {
47466
- const { stdout } = await execAsync5("which gemini");
47467
- cliAvailable = stdout.trim().length > 0;
47468
- details.cliPath = stdout.trim();
47469
- } catch {
47470
- details.cliAvailable = false;
47471
- }
47472
- const geminiConfigDir = join20(homedir12(), ".gemini");
47630
+ const resolvedCli = findOnPath5("gemini");
47631
+ cliAvailable = resolvedCli !== null;
47632
+ if (resolvedCli) details.cliPath = resolvedCli;
47633
+ else details.cliAvailable = false;
47634
+ const geminiConfigDir = join21(homedir10(), ".gemini");
47473
47635
  const configExists = existsSync14(geminiConfigDir);
47474
47636
  details.configDirExists = configExists;
47475
47637
  const healthy = cliAvailable;
@@ -47498,9 +47660,8 @@ var GeminiCliAdapter = class {
47498
47660
 
47499
47661
  // packages/adapters/src/providers/gemini-cli/spawn.ts
47500
47662
  init_src();
47501
- import { exec as exec6, spawn as nodeSpawn3 } from "node:child_process";
47502
- import { promisify as promisify7 } from "node:util";
47503
- var execAsync6 = promisify7(exec6);
47663
+ init_cli_spawn();
47664
+ import { findOnPath as findOnPath6 } from "@cleocode/paths";
47504
47665
 
47505
47666
  // packages/adapters/src/providers/gemini-cli/index.ts
47506
47667
  function createAdapter5() {
@@ -47508,11 +47669,10 @@ function createAdapter5() {
47508
47669
  }
47509
47670
 
47510
47671
  // packages/adapters/src/providers/kimi/adapter.ts
47511
- import { exec as exec7 } from "node:child_process";
47512
47672
  import { existsSync as existsSync16 } from "node:fs";
47513
- import { homedir as homedir13 } from "node:os";
47514
- import { join as join22 } from "node:path";
47515
- import { promisify as promisify8 } from "node:util";
47673
+ import { homedir as homedir11 } from "node:os";
47674
+ import { join as join23 } from "node:path";
47675
+ import { findOnPath as findOnPath7 } from "@cleocode/paths";
47516
47676
 
47517
47677
  // packages/adapters/src/providers/kimi/hooks.ts
47518
47678
  var KimiHookProvider = class {
@@ -47571,8 +47731,8 @@ var KimiHookProvider = class {
47571
47731
 
47572
47732
  // packages/adapters/src/providers/kimi/install.ts
47573
47733
  init_paths2();
47574
- import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
47575
- import { join as join21 } from "node:path";
47734
+ import { existsSync as existsSync15, readFileSync as readFileSync9 } from "node:fs";
47735
+ import { join as join22 } from "node:path";
47576
47736
  import { ensureProviderInstructionFile as ensureProviderInstructionFile5 } from "@cleocode/caamp";
47577
47737
  var KimiInstallProvider = class {
47578
47738
  /**
@@ -47617,10 +47777,10 @@ var KimiInstallProvider = class {
47617
47777
  */
47618
47778
  async isInstalled() {
47619
47779
  const instructionRef = `@${getCleoTemplatesTildePath()}/CLEO-INJECTION.md`;
47620
- const agentsMdPath = join21(process.cwd(), "AGENTS.md");
47780
+ const agentsMdPath = join22(process.cwd(), "AGENTS.md");
47621
47781
  if (existsSync15(agentsMdPath)) {
47622
47782
  try {
47623
- const content = readFileSync10(agentsMdPath, "utf-8");
47783
+ const content = readFileSync9(agentsMdPath, "utf-8");
47624
47784
  if (content.includes(instructionRef)) {
47625
47785
  return true;
47626
47786
  }
@@ -47646,7 +47806,6 @@ var KimiInstallProvider = class {
47646
47806
  };
47647
47807
 
47648
47808
  // packages/adapters/src/providers/kimi/adapter.ts
47649
- var execAsync7 = promisify8(exec7);
47650
47809
  var KimiAdapter = class {
47651
47810
  /** Unique provider identifier. */
47652
47811
  id = "kimi";
@@ -47721,14 +47880,11 @@ var KimiAdapter = class {
47721
47880
  };
47722
47881
  }
47723
47882
  let cliAvailable = false;
47724
- try {
47725
- const { stdout } = await execAsync7("which kimi");
47726
- cliAvailable = stdout.trim().length > 0;
47727
- details.cliPath = stdout.trim();
47728
- } catch {
47729
- details.cliAvailable = false;
47730
- }
47731
- const kimiConfigDir = join22(homedir13(), ".kimi");
47883
+ const resolvedCli = findOnPath7("kimi");
47884
+ cliAvailable = resolvedCli !== null;
47885
+ if (resolvedCli) details.cliPath = resolvedCli;
47886
+ else details.cliAvailable = false;
47887
+ const kimiConfigDir = join23(homedir11(), ".kimi");
47732
47888
  const configExists = existsSync16(kimiConfigDir);
47733
47889
  details.configDirExists = configExists;
47734
47890
  const healthy = cliAvailable;
@@ -47757,9 +47913,7 @@ var KimiAdapter = class {
47757
47913
 
47758
47914
  // packages/adapters/src/providers/kimi/spawn.ts
47759
47915
  init_src();
47760
- import { exec as exec8 } from "node:child_process";
47761
- import { promisify as promisify9 } from "node:util";
47762
- var execAsync8 = promisify9(exec8);
47916
+ import { findOnPath as findOnPath8 } from "@cleocode/paths";
47763
47917
 
47764
47918
  // packages/adapters/src/providers/kimi/index.ts
47765
47919
  function createAdapter6() {
@@ -47771,17 +47925,17 @@ init_opencode();
47771
47925
  init_hook_template_installer();
47772
47926
 
47773
47927
  // packages/adapters/src/registry.ts
47774
- import { readFileSync as readFileSync12 } from "node:fs";
47775
- import { dirname as dirname4, join as join28, resolve as resolve2 } from "node:path";
47928
+ import { readFileSync as readFileSync11 } from "node:fs";
47929
+ import { dirname as dirname5, join as join29, resolve as resolve2 } from "node:path";
47776
47930
  import { fileURLToPath as fileURLToPath3 } from "node:url";
47777
47931
  var PROVIDER_IDS = ["claude-code", "opencode", "cursor", "pi"];
47778
47932
  function getProviderManifests() {
47779
47933
  const manifests = [];
47780
- const baseDir = resolve2(dirname4(fileURLToPath3(import.meta.url)), "providers");
47934
+ const baseDir = resolve2(dirname5(fileURLToPath3(import.meta.url)), "providers");
47781
47935
  for (const providerId of PROVIDER_IDS) {
47782
47936
  try {
47783
- const manifestPath = join28(baseDir, providerId, "manifest.json");
47784
- const raw = readFileSync12(manifestPath, "utf-8");
47937
+ const manifestPath = join29(baseDir, providerId, "manifest.json");
47938
+ const raw = readFileSync11(manifestPath, "utf-8");
47785
47939
  manifests.push(JSON.parse(raw));
47786
47940
  } catch {
47787
47941
  }