argos-harness 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/assets/hooks/argos-guard-destructive.sh +1 -1
  2. package/assets/managed/disciplina-skills.md +11 -0
  3. package/assets/managed/formato-respuesta.md +2 -2
  4. package/assets/managed/identidad.md +7 -7
  5. package/assets/managed/operaciones-seguras.md +6 -6
  6. package/assets/output-styles/argos.md +12 -12
  7. package/dist/commands/adopt.d.ts +41 -1
  8. package/dist/commands/adopt.d.ts.map +1 -1
  9. package/dist/commands/adopt.js +243 -6
  10. package/dist/commands/adopt.js.map +1 -1
  11. package/dist/commands/doctor.d.ts.map +1 -1
  12. package/dist/commands/doctor.js +36 -2
  13. package/dist/commands/doctor.js.map +1 -1
  14. package/dist/commands/init.d.ts +56 -0
  15. package/dist/commands/init.d.ts.map +1 -1
  16. package/dist/commands/init.js +330 -64
  17. package/dist/commands/init.js.map +1 -1
  18. package/dist/commands/remove.d.ts +24 -0
  19. package/dist/commands/remove.d.ts.map +1 -1
  20. package/dist/commands/remove.js +94 -4
  21. package/dist/commands/remove.js.map +1 -1
  22. package/dist/commands/workspace.d.ts +32 -0
  23. package/dist/commands/workspace.d.ts.map +1 -1
  24. package/dist/commands/workspace.js +97 -3
  25. package/dist/commands/workspace.js.map +1 -1
  26. package/dist/lib/assets.d.ts +5 -3
  27. package/dist/lib/assets.d.ts.map +1 -1
  28. package/dist/lib/assets.js +5 -2
  29. package/dist/lib/assets.js.map +1 -1
  30. package/dist/lib/managed-files.d.ts +19 -5
  31. package/dist/lib/managed-files.d.ts.map +1 -1
  32. package/dist/lib/managed-files.js +18 -6
  33. package/dist/lib/managed-files.js.map +1 -1
  34. package/dist/lib/prompter.d.ts +63 -0
  35. package/dist/lib/prompter.d.ts.map +1 -0
  36. package/dist/lib/prompter.js +30 -0
  37. package/dist/lib/prompter.js.map +1 -0
  38. package/dist/lib/settings-merge.d.ts +78 -0
  39. package/dist/lib/settings-merge.d.ts.map +1 -1
  40. package/dist/lib/settings-merge.js +204 -0
  41. package/dist/lib/settings-merge.js.map +1 -1
  42. package/package.json +1 -1
@@ -75,6 +75,84 @@ export interface MergeHooksOptions {
75
75
  * content, not necessarily byte-identical bytes).
76
76
  */
77
77
  export declare function mergeHooksIntoSettings(settingsPath: string, specs: ArgosHookSpec[], options?: MergeHooksOptions): SettingsMergeResult;
78
+ /**
79
+ * True when `value` (a `settings.json.outputStyle` value) points at the
80
+ * predecessor harness's voice (`navori`) — matched EXACTLY, never by
81
+ * substring. Claude Code custom output styles are typically just the bare
82
+ * style name (e.g. `"navori"`, backed by `~/.claude/output-styles/navori.md`),
83
+ * but a hand-edited value could plausibly carry a path-ish form instead
84
+ * (`.../output-styles/navori.md`) — this also matches that shape, but only
85
+ * when the FINAL path segment, split on `/` or `\`, is exactly `navori.md`
86
+ * (case-insensitive extension). A bare `.includes("navori.md")` or
87
+ * `.includes("output-styles/navori")` substring check would false-positive
88
+ * on a user's own unrelated voice whose name merely starts with "navori"
89
+ * (e.g. `navori-fork.md`, `navori-team-voice`) — and since
90
+ * `applyOutputStylePolicy` defaults to taking this over unconditionally on
91
+ * the non-interactive path (`--yes`/no-TTY, the common CI/agent path), a
92
+ * false match there would silently clobber an unrelated user setting with
93
+ * zero confirmation. Anchoring to exact equality closes that hole.
94
+ */
95
+ export declare function isNavoriOutputStyle(value: unknown): boolean;
96
+ export type OutputStyleStatus = "created" | "updated" | "unchanged" | "untouched" | "error";
97
+ export interface ApplyOutputStyleResult {
98
+ status: OutputStyleStatus;
99
+ detail?: string;
100
+ }
101
+ export interface ApplyOutputStyleOptions {
102
+ /**
103
+ * Whether to take over a `navori`-matched value into `"Argos"`. Only
104
+ * consulted when the current value matches `isNavoriOutputStyle` — ignored
105
+ * when the key is absent (always set) or matches some other foreign voice
106
+ * (never touched, regardless of this flag). Default `true`, matching the
107
+ * `--yes`/no-TTY "replace unconditionally" behavior from spec 0004;
108
+ * callers backing an interactive confirm prompt pass `false` when the user
109
+ * declines.
110
+ */
111
+ takeoverNavori?: boolean;
112
+ /** Same test-only race seam as `MergeHooksOptions.onBeforeWrite` above. */
113
+ onBeforeWrite?: () => void;
114
+ }
115
+ /**
116
+ * Surgical policy for `settings.json.outputStyle` (spec 0004 "Activación de
117
+ * la voz"), same mechanics as `mergeHooksIntoSettings`: read (missing file →
118
+ * `{}`), mtime captured at read time, corrupt/unexpected JSON shape → writes
119
+ * nothing and returns `status: "error"`, mutate in memory, re-stat right
120
+ * before an atomic write and bail if the file moved underneath us.
121
+ *
122
+ * - Key absent → set to `"Argos"` (`created`/`updated` depending on whether
123
+ * the file itself existed).
124
+ * - Key already `"Argos"` → `unchanged`, no write.
125
+ * - Key matches the predecessor voice (`isNavoriOutputStyle`) → takeover
126
+ * governed by `options.takeoverNavori` (default `true`): accepted →
127
+ * overwritten to `"Argos"`, `status: "updated"` with an explicit detail
128
+ * naming the takeover; declined → `status: "untouched"`, nothing written.
129
+ * - Key matches any other value → NEVER touched, `status: "untouched"`.
130
+ *
131
+ * Every other top-level key in `settings.json` is left exactly as found —
132
+ * same surgical contract as the hooks merge.
133
+ */
134
+ export declare function applyOutputStylePolicy(settingsPath: string, options?: ApplyOutputStyleOptions): ApplyOutputStyleResult;
135
+ export type RemoveOutputStyleStatus = "removed" | "unchanged" | "error";
136
+ export interface RemoveOutputStyleResult {
137
+ status: RemoveOutputStyleStatus;
138
+ detail?: string;
139
+ }
140
+ export interface RemoveOutputStyleOptions {
141
+ /** Same `argos remove` preview-mode seam as `RemoveHooksOptions.dryRun` above. */
142
+ dryRun?: boolean;
143
+ /** Same test-only race seam as `MergeHooksOptions.onBeforeWrite` above. */
144
+ onBeforeWrite?: () => void;
145
+ }
146
+ /**
147
+ * `argos remove`'s mirror of `applyOutputStylePolicy`: removes
148
+ * `settings.json.outputStyle` ONLY when it's exactly `"Argos"` — Argos's own
149
+ * value. Any other value (including a foreign voice `argos init` never
150
+ * touched, or the key simply absent) is left exactly as found; this command
151
+ * only ever cleans up after itself, never after the user's own choice of
152
+ * voice. Same missing-file/corrupt-JSON/mtime-guard/atomic-write mechanics
153
+ * as the rest of this module.
154
+ */
155
+ export declare function removeOutputStyleIfArgos(settingsPath: string, options?: RemoveOutputStyleOptions): RemoveOutputStyleResult;
78
156
  export type RemoveHooksStatus = "removed" | "unchanged" | "error";
79
157
  export interface RemoveHooksResult {
80
158
  status: RemoveHooksStatus;
@@ -1 +1 @@
1
- {"version":3,"file":"settings-merge.d.ts","sourceRoot":"","sources":["../../src/lib/settings-merge.ts"],"names":[],"mappings":"AAsBA;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,MAAM,CAEtE;AAqCD,MAAM,WAAW,aAAa;IAC5B,uGAAuG;IACvG,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;AAEhF,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,aAAa,EAAE,EACtB,OAAO,GAAE,iBAAsB,GAC9B,mBAAmB,CA8HrB;AAED,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;AAElE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,+BAA+B,CAC7C,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CAmGnB"}
1
+ {"version":3,"file":"settings-merge.d.ts","sourceRoot":"","sources":["../../src/lib/settings-merge.ts"],"names":[],"mappings":"AAsBA;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,MAAM,CAEtE;AAqCD,MAAM,WAAW,aAAa;IAC5B,uGAAuG;IACvG,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;AAEhF,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,aAAa,EAAE,EACtB,OAAO,GAAE,iBAAsB,GAC9B,mBAAmB,CA8HrB;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAK3D;AAED,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,OAAO,CAAC;AAE5F,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,2EAA2E;IAC3E,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CAkFxB;AAED,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;AAExE,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,uBAAuB,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,kFAAkF;IAClF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,wBAA6B,GACrC,uBAAuB,CAkDzB;AAED,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAC;AAElE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,+BAA+B,CAC7C,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,kBAAuB,GAC/B,iBAAiB,CAmGnB"}
@@ -226,6 +226,210 @@ function errorMessage(err) {
226
226
  status: existed ? "updated" : "created"
227
227
  };
228
228
  }
229
+ // --- output-style ("voice activation") — spec 0004 "Activación de la voz" ---
230
+ /**
231
+ * True when `value` (a `settings.json.outputStyle` value) points at the
232
+ * predecessor harness's voice (`navori`) — matched EXACTLY, never by
233
+ * substring. Claude Code custom output styles are typically just the bare
234
+ * style name (e.g. `"navori"`, backed by `~/.claude/output-styles/navori.md`),
235
+ * but a hand-edited value could plausibly carry a path-ish form instead
236
+ * (`.../output-styles/navori.md`) — this also matches that shape, but only
237
+ * when the FINAL path segment, split on `/` or `\`, is exactly `navori.md`
238
+ * (case-insensitive extension). A bare `.includes("navori.md")` or
239
+ * `.includes("output-styles/navori")` substring check would false-positive
240
+ * on a user's own unrelated voice whose name merely starts with "navori"
241
+ * (e.g. `navori-fork.md`, `navori-team-voice`) — and since
242
+ * `applyOutputStylePolicy` defaults to taking this over unconditionally on
243
+ * the non-interactive path (`--yes`/no-TTY, the common CI/agent path), a
244
+ * false match there would silently clobber an unrelated user setting with
245
+ * zero confirmation. Anchoring to exact equality closes that hole.
246
+ */ export function isNavoriOutputStyle(value) {
247
+ if (typeof value !== "string") return false;
248
+ if (value === "navori") return true;
249
+ const lastSegment = value.split(/[/\\]/).pop() ?? "";
250
+ return lastSegment.toLowerCase() === "navori.md";
251
+ }
252
+ /**
253
+ * Surgical policy for `settings.json.outputStyle` (spec 0004 "Activación de
254
+ * la voz"), same mechanics as `mergeHooksIntoSettings`: read (missing file →
255
+ * `{}`), mtime captured at read time, corrupt/unexpected JSON shape → writes
256
+ * nothing and returns `status: "error"`, mutate in memory, re-stat right
257
+ * before an atomic write and bail if the file moved underneath us.
258
+ *
259
+ * - Key absent → set to `"Argos"` (`created`/`updated` depending on whether
260
+ * the file itself existed).
261
+ * - Key already `"Argos"` → `unchanged`, no write.
262
+ * - Key matches the predecessor voice (`isNavoriOutputStyle`) → takeover
263
+ * governed by `options.takeoverNavori` (default `true`): accepted →
264
+ * overwritten to `"Argos"`, `status: "updated"` with an explicit detail
265
+ * naming the takeover; declined → `status: "untouched"`, nothing written.
266
+ * - Key matches any other value → NEVER touched, `status: "untouched"`.
267
+ *
268
+ * Every other top-level key in `settings.json` is left exactly as found —
269
+ * same surgical contract as the hooks merge.
270
+ */ export function applyOutputStylePolicy(settingsPath, options = {}) {
271
+ const existed = existsSync(settingsPath);
272
+ let raw = "{}";
273
+ let mtimeAtRead;
274
+ if (existed) {
275
+ try {
276
+ raw = readFileSync(settingsPath, "utf-8");
277
+ mtimeAtRead = statSync(settingsPath).mtimeMs;
278
+ } catch (err) {
279
+ return {
280
+ status: "error",
281
+ detail: errorMessage(err)
282
+ };
283
+ }
284
+ }
285
+ let settings;
286
+ try {
287
+ const parsed = raw.trim().length === 0 ? {} : JSON.parse(raw);
288
+ if (!isPlainObject(parsed)) {
289
+ return {
290
+ status: "error",
291
+ detail: "settings.json existente no es un objeto JSON — arreglalo a mano."
292
+ };
293
+ }
294
+ settings = parsed;
295
+ } catch (err) {
296
+ return {
297
+ status: "error",
298
+ detail: `settings.json existente tiene JSON inválido (${errorMessage(err)}) — arreglalo a mano y volvé a correr argos init.`
299
+ };
300
+ }
301
+ const current = settings.outputStyle;
302
+ if (current === "Argos") {
303
+ return {
304
+ status: "unchanged"
305
+ };
306
+ }
307
+ if (current !== undefined && !isNavoriOutputStyle(current)) {
308
+ return {
309
+ status: "untouched",
310
+ detail: `outputStyle ya apunta a otra voz ('${String(current)}') — no se toca`
311
+ };
312
+ }
313
+ if (current !== undefined && isNavoriOutputStyle(current)) {
314
+ const takeover = options.takeoverNavori ?? true;
315
+ if (!takeover) {
316
+ return {
317
+ status: "untouched",
318
+ detail: `outputStyle sigue en '${String(current)}' (voz del harness predecesor) — takeover declinado`
319
+ };
320
+ }
321
+ }
322
+ const previous = current;
323
+ settings.outputStyle = "Argos";
324
+ options.onBeforeWrite?.();
325
+ if (existed && mtimeAtRead !== undefined) {
326
+ let mtimeNow;
327
+ try {
328
+ mtimeNow = statSync(settingsPath).mtimeMs;
329
+ } catch {
330
+ mtimeNow = undefined;
331
+ }
332
+ if (mtimeNow !== undefined && mtimeNow !== mtimeAtRead) {
333
+ return {
334
+ status: "error",
335
+ detail: "settings.json cambió durante el merge — reintenta."
336
+ };
337
+ }
338
+ }
339
+ try {
340
+ writeFileAtomic(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
341
+ } catch (err) {
342
+ return {
343
+ status: "error",
344
+ detail: errorMessage(err)
345
+ };
346
+ }
347
+ if (previous !== undefined) {
348
+ return {
349
+ status: "updated",
350
+ detail: `reemplazó ${String(previous)} → Argos`
351
+ };
352
+ }
353
+ // `previous === undefined` here means the KEY was absent (not that the
354
+ // file itself was absent) — always "created" regardless of `existed`,
355
+ // since another writer (e.g. `mergeHooksIntoSettings`, in the same
356
+ // `runInit` call) may have already created the file moments earlier for
357
+ // an unrelated reason.
358
+ return {
359
+ status: "created"
360
+ };
361
+ }
362
+ /**
363
+ * `argos remove`'s mirror of `applyOutputStylePolicy`: removes
364
+ * `settings.json.outputStyle` ONLY when it's exactly `"Argos"` — Argos's own
365
+ * value. Any other value (including a foreign voice `argos init` never
366
+ * touched, or the key simply absent) is left exactly as found; this command
367
+ * only ever cleans up after itself, never after the user's own choice of
368
+ * voice. Same missing-file/corrupt-JSON/mtime-guard/atomic-write mechanics
369
+ * as the rest of this module.
370
+ */ export function removeOutputStyleIfArgos(settingsPath, options = {}) {
371
+ if (!existsSync(settingsPath)) return {
372
+ status: "unchanged"
373
+ };
374
+ let raw;
375
+ let mtimeAtRead;
376
+ try {
377
+ raw = readFileSync(settingsPath, "utf-8");
378
+ mtimeAtRead = statSync(settingsPath).mtimeMs;
379
+ } catch (err) {
380
+ return {
381
+ status: "error",
382
+ detail: errorMessage(err)
383
+ };
384
+ }
385
+ let settings;
386
+ try {
387
+ const parsed = raw.trim().length === 0 ? {} : JSON.parse(raw);
388
+ if (!isPlainObject(parsed)) {
389
+ return {
390
+ status: "error",
391
+ detail: "settings.json existente no es un objeto JSON — arreglalo a mano."
392
+ };
393
+ }
394
+ settings = parsed;
395
+ } catch (err) {
396
+ return {
397
+ status: "error",
398
+ detail: `settings.json existente tiene JSON inválido (${errorMessage(err)}) — arreglalo a mano.`
399
+ };
400
+ }
401
+ if (settings.outputStyle !== "Argos") return {
402
+ status: "unchanged"
403
+ };
404
+ if (options.dryRun) return {
405
+ status: "removed"
406
+ };
407
+ delete settings.outputStyle;
408
+ options.onBeforeWrite?.();
409
+ let mtimeNow;
410
+ try {
411
+ mtimeNow = statSync(settingsPath).mtimeMs;
412
+ } catch {
413
+ mtimeNow = undefined;
414
+ }
415
+ if (mtimeNow !== undefined && mtimeNow !== mtimeAtRead) {
416
+ return {
417
+ status: "error",
418
+ detail: "settings.json cambió durante el remove — reintenta."
419
+ };
420
+ }
421
+ try {
422
+ writeFileAtomic(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
423
+ } catch (err) {
424
+ return {
425
+ status: "error",
426
+ detail: errorMessage(err)
427
+ };
428
+ }
429
+ return {
430
+ status: "removed"
431
+ };
432
+ }
229
433
  /**
230
434
  * Symmetric counterpart to `mergeHooksIntoSettings`: strips every Argos-owned
231
435
  * hook entry (any command matched by `isArgosOwnedHookCommand` against
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/settings-merge.ts"],"sourcesContent":["import { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { writeFileAtomic } from \"./atomic-write.js\";\n\n/**\n * Surgical merge of Argos's `hooks.PreToolUse` entries into\n * `~/.claude/settings.json` — the JSON analogue of the managed-block model\n * used for CLAUDE.md (see `lib/markers.ts`): Argos manages only its own\n * entries and leaves every other key, hook, and array position in the\n * user's file untouched.\n *\n * Ownership identification: a hook command is Argos-owned when it points at\n * a script under `.../hooks/argos-*` — `isArgosHookCommand` below is the\n * general check (any Argos hook), while the merge itself pins each entry to\n * ONE specific `scriptPath`, so re-running `argos init` updates the exact\n * same array slot instead of appending a duplicate.\n */\n\nconst ARGOS_HOOK_PATH_RE = /\\/hooks\\/argos-/;\nconst BASH_HOOK_COMMAND_RE = /^bash \"(.+)\"$/;\nconst ARGOS_HOOK_BASENAME_RE = /^argos-/;\n\n/**\n * True when a hook `command` string points at an Argos-owned script (`.../hooks/argos-*`).\n *\n * This is a bare substring match — good enough for the non-destructive\n * `doctor` diagnostic that uses it, but NOT anchored to any real directory.\n * Destructive operations (deleting hook entries from the user's\n * settings.json) must use `isArgosOwnedHookCommand` below instead, which\n * requires the script to actually resolve inside the managed hooks\n * directory.\n */\nexport function isArgosHookCommand(command: unknown): command is string {\n return typeof command === \"string\" && ARGOS_HOOK_PATH_RE.test(command);\n}\n\n/** Extract the script path from Argos's own `bash \"<scriptPath>\"` hook command shape (see `buildHookCommand`). */\nfunction extractBashScriptPath(command: string): string | undefined {\n return BASH_HOOK_COMMAND_RE.exec(command)?.[1];\n}\n\n/** True when `candidate` resolves to a path inside (a descendant of) `dir`, via real path resolution — not string matching. */\nfunction isPathInsideDir(candidate: string, dir: string): boolean {\n const rel = relative(resolve(dir), resolve(candidate));\n if (rel === \"\" || isAbsolute(rel)) return false;\n return rel !== \"..\" && !rel.startsWith(`..${sep}`);\n}\n\n/**\n * True when a hook `command` string is Argos-owned for the purposes of\n * DESTRUCTIVE removal: it must match Argos's own `bash \"<scriptPath>\"`\n * invocation shape, have a basename starting with `argos-`, AND resolve to a\n * location actually inside `hooksDir` (the real, resolved\n * `<claudeDir>/hooks` directory this install manages).\n *\n * This is intentionally stricter than `isArgosHookCommand`/`ARGOS_HOOK_PATH_RE`,\n * which only substring-match `/hooks/argos-` anywhere in the command string.\n * A user's own hook living at an unrelated path that happens to contain that\n * substring — e.g. `/Users/x/my/hooks/argos-custom.sh`, which is NOT under\n * the managed hooks directory — would false-match the substring check and be\n * silently deleted. Anchoring to the resolved hooks directory (via\n * `path.resolve` + an ancestor check, not `.includes()`) closes that hole.\n */\nfunction isArgosOwnedHookCommand(command: unknown, hooksDir: string): command is string {\n if (typeof command !== \"string\") return false;\n const scriptPath = extractBashScriptPath(command);\n if (!scriptPath) return false;\n if (!ARGOS_HOOK_BASENAME_RE.test(basename(scriptPath))) return false;\n return isPathInsideDir(scriptPath, hooksDir);\n}\n\nexport interface ArgosHookSpec {\n /** Absolute path to the installed hook script, e.g. `<claudeDir>/hooks/argos-guard-destructive.sh`. */\n scriptPath: string;\n /** Claude Code hook matcher, e.g. \"Bash\". */\n matcher: string;\n /** Hook timeout in seconds (Claude Code's own outer bound on the hook process). */\n timeout?: number;\n statusMessage?: string;\n}\n\nexport type SettingsMergeStatus = \"created\" | \"updated\" | \"unchanged\" | \"error\";\n\nexport interface SettingsMergeResult {\n status: SettingsMergeStatus;\n detail?: string;\n}\n\nexport interface MergeHooksOptions {\n /**\n * Script paths whose entries should be stripped out of `hooks.PreToolUse`\n * entirely (not overwritten with a new `specs` entry — REMOVED), wherever\n * they're found. Used by `argos init` for a hook whose script write just\n * failed: an entry pointing at a script that doesn't exist (or is broken)\n * is a dangling PreToolUse entry that hard-blocks every Bash call, whether\n * it was never written this run or used to work and just broke.\n */\n removeScriptPaths?: string[];\n /**\n * Test-only seam: invoked immediately before the pre-write mtime re-check\n * (see the concurrency guard below), so a test can simulate a concurrent\n * writer racing this merge — mutate `settingsPath` from inside the\n * callback, then let the merge proceed and observe it refuse. Never used\n * outside tests.\n */\n onBeforeWrite?: () => void;\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction buildHookCommand(spec: ArgosHookSpec): Record<string, unknown> {\n const hook: Record<string, unknown> = { type: \"command\", command: `bash \"${spec.scriptPath}\"` };\n if (spec.timeout !== undefined) hook.timeout = spec.timeout;\n if (spec.statusMessage !== undefined) hook.statusMessage = spec.statusMessage;\n return hook;\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Merge `specs` into `settingsPath` (normally `resolveClaudeDir()/settings.json`).\n *\n * - Missing file → starts from `{}` (created).\n * - An existing hook entry whose command already targets a spec's\n * `scriptPath` → overwritten in place, at its current array index (never\n * moved, never removed).\n * - No existing entry for a spec → appended into the first `PreToolUse`\n * bucket whose `matcher` equals the spec's matcher, or a new bucket is\n * pushed at the end of `PreToolUse` if none matches. Every other bucket,\n * hook, and top-level key is left exactly as found.\n * - `options.removeScriptPaths` → any existing entry whose command targets\n * one of those paths is stripped out entirely (not replaced) — for a hook\n * whose script write just failed, so it never leaves a dangling entry.\n * - Corrupt JSON (unparsable, not an object, `hooks` present but not an\n * object, or `hooks.PreToolUse` present but not an array) → returns\n * `status: \"error\"` and writes NOTHING. Never clobber a file the caller\n * can't safely reason about, and never silently discard a foreign `hooks`\n * value just because its shape is unexpected.\n * - Concurrency guard: the file's mtime is captured at read time and\n * re-checked immediately before the write; if it moved, someone else wrote\n * settings.json in the meantime and this call refuses with `status:\n * \"error\"` rather than silently clobbering that write (last-writer-wins).\n * - The write itself is atomic (temp file + rename, see\n * lib/atomic-write.ts) — a crash mid-write can never leave settings.json\n * torn, which would otherwise hard-block every subsequent Bash call.\n *\n * The file is always re-serialized with 2-space indentation on a write —\n * this normalizes the user's original formatting/whitespace, but every key\n * and value it did not own is preserved (structurally byte-identical\n * content, not necessarily byte-identical bytes).\n */\nexport function mergeHooksIntoSettings(\n settingsPath: string,\n specs: ArgosHookSpec[],\n options: MergeHooksOptions = {},\n): SettingsMergeResult {\n const existed = existsSync(settingsPath);\n let raw = \"{}\";\n let mtimeAtRead: number | undefined;\n if (existed) {\n try {\n raw = readFileSync(settingsPath, \"utf-8\");\n mtimeAtRead = statSync(settingsPath).mtimeMs;\n } catch (err) {\n return { status: \"error\", detail: errorMessage(err) };\n }\n }\n\n let settings: Record<string, unknown>;\n try {\n const parsed: unknown = raw.trim().length === 0 ? {} : JSON.parse(raw);\n if (!isPlainObject(parsed)) {\n return { status: \"error\", detail: \"settings.json existente no es un objeto JSON — arreglalo a mano.\" };\n }\n settings = parsed;\n } catch (err) {\n return {\n status: \"error\",\n detail: `settings.json existente tiene JSON inválido (${errorMessage(err)}) — arreglalo a mano y volvé a correr argos init.`,\n };\n }\n\n const before = JSON.stringify(settings);\n\n if (settings.hooks !== undefined && !isPlainObject(settings.hooks)) {\n return {\n status: \"error\",\n detail: \"settings.json tiene hooks que no es un objeto — arreglalo a mano.\",\n };\n }\n const hooksRoot: Record<string, unknown> = isPlainObject(settings.hooks)\n ? (settings.hooks as Record<string, unknown>)\n : {};\n\n const preToolUseRaw = hooksRoot.PreToolUse;\n if (preToolUseRaw !== undefined && !Array.isArray(preToolUseRaw)) {\n return {\n status: \"error\",\n detail: \"settings.json tiene hooks.PreToolUse que no es un array — arreglalo a mano.\",\n };\n }\n const preToolUse: unknown[] = Array.isArray(preToolUseRaw) ? preToolUseRaw : [];\n\n const removeScriptPaths = options.removeScriptPaths ?? [];\n if (removeScriptPaths.length > 0) {\n for (const bucket of preToolUse) {\n if (!isPlainObject(bucket) || !Array.isArray(bucket.hooks)) continue;\n bucket.hooks = (bucket.hooks as unknown[]).filter((h) => {\n if (!isPlainObject(h) || typeof h.command !== \"string\") return true;\n const command = h.command;\n return !removeScriptPaths.some((p) => command.includes(p));\n });\n }\n }\n\n for (const spec of specs) {\n const desired = buildHookCommand(spec);\n\n // Find our own previous entry anywhere in PreToolUse (matched by\n // scriptPath), to update it in place without moving it.\n let found = false;\n for (const bucket of preToolUse) {\n if (!isPlainObject(bucket) || !Array.isArray(bucket.hooks)) continue;\n const bucketHooks = bucket.hooks as unknown[];\n const idx = bucketHooks.findIndex(\n (h) => isPlainObject(h) && typeof h.command === \"string\" && h.command.includes(spec.scriptPath),\n );\n if (idx >= 0) {\n bucketHooks[idx] = desired;\n found = true;\n break;\n }\n }\n\n if (!found) {\n let bucket = preToolUse.find(\n (b): b is Record<string, unknown> => isPlainObject(b) && b.matcher === spec.matcher && Array.isArray(b.hooks),\n );\n if (!bucket) {\n bucket = { matcher: spec.matcher, hooks: [] };\n preToolUse.push(bucket);\n }\n (bucket.hooks as unknown[]).push(desired);\n }\n }\n\n hooksRoot.PreToolUse = preToolUse;\n settings.hooks = hooksRoot;\n\n if (existed && JSON.stringify(settings) === before) {\n return { status: \"unchanged\" };\n }\n\n options.onBeforeWrite?.();\n\n // Cheap concurrency guard: this is a read-modify-write with no locking, so\n // re-stat the file right before writing and bail if its mtime moved since\n // we read it — someone else wrote settings.json in between, and a plain\n // last-writer-wins here would silently drop their change.\n if (existed && mtimeAtRead !== undefined) {\n let mtimeNow: number | undefined;\n try {\n mtimeNow = statSync(settingsPath).mtimeMs;\n } catch {\n mtimeNow = undefined;\n }\n if (mtimeNow !== undefined && mtimeNow !== mtimeAtRead) {\n return {\n status: \"error\",\n detail: \"settings.json cambió durante el merge — reintenta.\",\n };\n }\n }\n\n try {\n writeFileAtomic(settingsPath, `${JSON.stringify(settings, null, 2)}\\n`);\n } catch (err) {\n return { status: \"error\", detail: errorMessage(err) };\n }\n\n return { status: existed ? \"updated\" : \"created\" };\n}\n\nexport type RemoveHooksStatus = \"removed\" | \"unchanged\" | \"error\";\n\nexport interface RemoveHooksResult {\n status: RemoveHooksStatus;\n detail?: string;\n /** Count of Argos-owned hook entries actually removed (0 for \"unchanged\"/\"error\"). */\n removedCount: number;\n}\n\nexport interface RemoveHooksOptions {\n /**\n * Compute what WOULD be removed without writing anything — `argos remove`'s\n * preview mode. Corrupt-JSON refusal still applies identically in dry-run:\n * a caller must not be told \"removed\" for a file it can't safely reason\n * about.\n */\n dryRun?: boolean;\n /** Same test-only seam as `MergeHooksOptions.onBeforeWrite` above. */\n onBeforeWrite?: () => void;\n}\n\n/**\n * Symmetric counterpart to `mergeHooksIntoSettings`: strips every Argos-owned\n * hook entry (any command matched by `isArgosOwnedHookCommand` against\n * `hooksDir`, not just the current `HOOK_IDS`) out of `hooks.PreToolUse`,\n * wherever it's found. Foreign hooks, buckets, and every other top-level key\n * are left exactly as found — same ownership/refusal contract as the merge\n * path:\n * - `hooksDir` is the resolved `<claudeDir>/hooks` directory this install\n * manages (normally `join(resolveClaudeDir(), \"hooks\")`). A hook is only\n * ever treated as Argos-owned — and therefore deletable — when its script\n * path actually resolves inside `hooksDir` AND its basename starts with\n * `argos-`; this is deliberately NOT the same as `isArgosHookCommand`'s\n * bare `/hooks/argos-` substring match, which would also match (and\n * delete) an unrelated user hook at a path like\n * `/Users/x/my/hooks/argos-custom.sh`.\n * - Missing file, or no `hooks`/`hooks.PreToolUse` at all → \"unchanged\",\n * nothing to remove.\n * - Corrupt JSON (unparsable, not an object, `hooks` present but not an\n * object, or `hooks.PreToolUse` present but not an array) → `status:\n * \"error\"`, writes NOTHING. Never guess at a shape it can't verify.\n * - A `PreToolUse` bucket that becomes empty after removing Argos's own\n * entries is dropped entirely (it only ever existed to hold them — see\n * `mergeHooksIntoSettings`, which creates a fresh bucket per matcher on\n * demand); a bucket that still has foreign hooks left (including one with\n * ZERO Argos-owned hooks in it) keeps its matcher, its hooks, and its\n * position untouched.\n * - Same mtime concurrency guard and atomic write as the merge path.\n */\nexport function removeAllArgosHooksFromSettings(\n settingsPath: string,\n hooksDir: string,\n options: RemoveHooksOptions = {},\n): RemoveHooksResult {\n if (!existsSync(settingsPath)) return { status: \"unchanged\", removedCount: 0 };\n\n let raw: string;\n let mtimeAtRead: number;\n try {\n raw = readFileSync(settingsPath, \"utf-8\");\n mtimeAtRead = statSync(settingsPath).mtimeMs;\n } catch (err) {\n return { status: \"error\", detail: errorMessage(err), removedCount: 0 };\n }\n\n let settings: Record<string, unknown>;\n try {\n const parsed: unknown = raw.trim().length === 0 ? {} : JSON.parse(raw);\n if (!isPlainObject(parsed)) {\n return {\n status: \"error\",\n detail: \"settings.json existente no es un objeto JSON — arreglalo a mano.\",\n removedCount: 0,\n };\n }\n settings = parsed;\n } catch (err) {\n return {\n status: \"error\",\n detail: `settings.json existente tiene JSON inválido (${errorMessage(err)}) — arreglalo a mano.`,\n removedCount: 0,\n };\n }\n\n if (settings.hooks !== undefined && !isPlainObject(settings.hooks)) {\n return {\n status: \"error\",\n detail: \"settings.json tiene hooks que no es un objeto — arreglalo a mano.\",\n removedCount: 0,\n };\n }\n const hooksRoot = settings.hooks as Record<string, unknown> | undefined;\n if (!hooksRoot) return { status: \"unchanged\", removedCount: 0 };\n\n const preToolUseRaw = hooksRoot.PreToolUse;\n if (preToolUseRaw !== undefined && !Array.isArray(preToolUseRaw)) {\n return {\n status: \"error\",\n detail: \"settings.json tiene hooks.PreToolUse que no es un array — arreglalo a mano.\",\n removedCount: 0,\n };\n }\n if (!Array.isArray(preToolUseRaw) || preToolUseRaw.length === 0) {\n return { status: \"unchanged\", removedCount: 0 };\n }\n\n let removedCount = 0;\n const nextPreToolUse: unknown[] = [];\n for (const bucket of preToolUseRaw) {\n if (!isPlainObject(bucket) || !Array.isArray(bucket.hooks)) {\n nextPreToolUse.push(bucket);\n continue;\n }\n const before = bucket.hooks.length;\n const filteredHooks = (bucket.hooks as unknown[]).filter(\n (h) => !(isPlainObject(h) && isArgosOwnedHookCommand(h.command, hooksDir)),\n );\n removedCount += before - filteredHooks.length;\n if (filteredHooks.length === 0) continue; // bucket only ever held Argos's own entries\n nextPreToolUse.push({ ...bucket, hooks: filteredHooks });\n }\n\n if (removedCount === 0) return { status: \"unchanged\", removedCount: 0 };\n if (options.dryRun) return { status: \"removed\", removedCount };\n\n hooksRoot.PreToolUse = nextPreToolUse;\n settings.hooks = hooksRoot;\n\n options.onBeforeWrite?.();\n\n // Same cheap concurrency guard as mergeHooksIntoSettings.\n let mtimeNow: number | undefined;\n try {\n mtimeNow = statSync(settingsPath).mtimeMs;\n } catch {\n mtimeNow = undefined;\n }\n if (mtimeNow !== undefined && mtimeNow !== mtimeAtRead) {\n return {\n status: \"error\",\n detail: \"settings.json cambió durante el remove — reintenta.\",\n removedCount: 0,\n };\n }\n\n try {\n writeFileAtomic(settingsPath, `${JSON.stringify(settings, null, 2)}\\n`);\n } catch (err) {\n return { status: \"error\", detail: errorMessage(err), removedCount: 0 };\n }\n\n return { status: \"removed\", removedCount };\n}\n"],"names":["existsSync","readFileSync","statSync","basename","isAbsolute","relative","resolve","sep","writeFileAtomic","ARGOS_HOOK_PATH_RE","BASH_HOOK_COMMAND_RE","ARGOS_HOOK_BASENAME_RE","isArgosHookCommand","command","test","extractBashScriptPath","exec","isPathInsideDir","candidate","dir","rel","startsWith","isArgosOwnedHookCommand","hooksDir","scriptPath","isPlainObject","v","Array","isArray","buildHookCommand","spec","hook","type","timeout","undefined","statusMessage","errorMessage","err","Error","message","String","mergeHooksIntoSettings","settingsPath","specs","options","existed","raw","mtimeAtRead","mtimeMs","status","detail","settings","parsed","trim","length","JSON","parse","before","stringify","hooks","hooksRoot","preToolUseRaw","PreToolUse","preToolUse","removeScriptPaths","bucket","filter","h","some","p","includes","desired","found","bucketHooks","idx","findIndex","find","b","matcher","push","onBeforeWrite","mtimeNow","removeAllArgosHooksFromSettings","removedCount","nextPreToolUse","filteredHooks","dryRun"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/lib/settings-merge.ts"],"sourcesContent":["import { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport { writeFileAtomic } from \"./atomic-write.js\";\n\n/**\n * Surgical merge of Argos's `hooks.PreToolUse` entries into\n * `~/.claude/settings.json` — the JSON analogue of the managed-block model\n * used for CLAUDE.md (see `lib/markers.ts`): Argos manages only its own\n * entries and leaves every other key, hook, and array position in the\n * user's file untouched.\n *\n * Ownership identification: a hook command is Argos-owned when it points at\n * a script under `.../hooks/argos-*` — `isArgosHookCommand` below is the\n * general check (any Argos hook), while the merge itself pins each entry to\n * ONE specific `scriptPath`, so re-running `argos init` updates the exact\n * same array slot instead of appending a duplicate.\n */\n\nconst ARGOS_HOOK_PATH_RE = /\\/hooks\\/argos-/;\nconst BASH_HOOK_COMMAND_RE = /^bash \"(.+)\"$/;\nconst ARGOS_HOOK_BASENAME_RE = /^argos-/;\n\n/**\n * True when a hook `command` string points at an Argos-owned script (`.../hooks/argos-*`).\n *\n * This is a bare substring match — good enough for the non-destructive\n * `doctor` diagnostic that uses it, but NOT anchored to any real directory.\n * Destructive operations (deleting hook entries from the user's\n * settings.json) must use `isArgosOwnedHookCommand` below instead, which\n * requires the script to actually resolve inside the managed hooks\n * directory.\n */\nexport function isArgosHookCommand(command: unknown): command is string {\n return typeof command === \"string\" && ARGOS_HOOK_PATH_RE.test(command);\n}\n\n/** Extract the script path from Argos's own `bash \"<scriptPath>\"` hook command shape (see `buildHookCommand`). */\nfunction extractBashScriptPath(command: string): string | undefined {\n return BASH_HOOK_COMMAND_RE.exec(command)?.[1];\n}\n\n/** True when `candidate` resolves to a path inside (a descendant of) `dir`, via real path resolution — not string matching. */\nfunction isPathInsideDir(candidate: string, dir: string): boolean {\n const rel = relative(resolve(dir), resolve(candidate));\n if (rel === \"\" || isAbsolute(rel)) return false;\n return rel !== \"..\" && !rel.startsWith(`..${sep}`);\n}\n\n/**\n * True when a hook `command` string is Argos-owned for the purposes of\n * DESTRUCTIVE removal: it must match Argos's own `bash \"<scriptPath>\"`\n * invocation shape, have a basename starting with `argos-`, AND resolve to a\n * location actually inside `hooksDir` (the real, resolved\n * `<claudeDir>/hooks` directory this install manages).\n *\n * This is intentionally stricter than `isArgosHookCommand`/`ARGOS_HOOK_PATH_RE`,\n * which only substring-match `/hooks/argos-` anywhere in the command string.\n * A user's own hook living at an unrelated path that happens to contain that\n * substring — e.g. `/Users/x/my/hooks/argos-custom.sh`, which is NOT under\n * the managed hooks directory — would false-match the substring check and be\n * silently deleted. Anchoring to the resolved hooks directory (via\n * `path.resolve` + an ancestor check, not `.includes()`) closes that hole.\n */\nfunction isArgosOwnedHookCommand(command: unknown, hooksDir: string): command is string {\n if (typeof command !== \"string\") return false;\n const scriptPath = extractBashScriptPath(command);\n if (!scriptPath) return false;\n if (!ARGOS_HOOK_BASENAME_RE.test(basename(scriptPath))) return false;\n return isPathInsideDir(scriptPath, hooksDir);\n}\n\nexport interface ArgosHookSpec {\n /** Absolute path to the installed hook script, e.g. `<claudeDir>/hooks/argos-guard-destructive.sh`. */\n scriptPath: string;\n /** Claude Code hook matcher, e.g. \"Bash\". */\n matcher: string;\n /** Hook timeout in seconds (Claude Code's own outer bound on the hook process). */\n timeout?: number;\n statusMessage?: string;\n}\n\nexport type SettingsMergeStatus = \"created\" | \"updated\" | \"unchanged\" | \"error\";\n\nexport interface SettingsMergeResult {\n status: SettingsMergeStatus;\n detail?: string;\n}\n\nexport interface MergeHooksOptions {\n /**\n * Script paths whose entries should be stripped out of `hooks.PreToolUse`\n * entirely (not overwritten with a new `specs` entry — REMOVED), wherever\n * they're found. Used by `argos init` for a hook whose script write just\n * failed: an entry pointing at a script that doesn't exist (or is broken)\n * is a dangling PreToolUse entry that hard-blocks every Bash call, whether\n * it was never written this run or used to work and just broke.\n */\n removeScriptPaths?: string[];\n /**\n * Test-only seam: invoked immediately before the pre-write mtime re-check\n * (see the concurrency guard below), so a test can simulate a concurrent\n * writer racing this merge — mutate `settingsPath` from inside the\n * callback, then let the merge proceed and observe it refuse. Never used\n * outside tests.\n */\n onBeforeWrite?: () => void;\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction buildHookCommand(spec: ArgosHookSpec): Record<string, unknown> {\n const hook: Record<string, unknown> = { type: \"command\", command: `bash \"${spec.scriptPath}\"` };\n if (spec.timeout !== undefined) hook.timeout = spec.timeout;\n if (spec.statusMessage !== undefined) hook.statusMessage = spec.statusMessage;\n return hook;\n}\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/**\n * Merge `specs` into `settingsPath` (normally `resolveClaudeDir()/settings.json`).\n *\n * - Missing file → starts from `{}` (created).\n * - An existing hook entry whose command already targets a spec's\n * `scriptPath` → overwritten in place, at its current array index (never\n * moved, never removed).\n * - No existing entry for a spec → appended into the first `PreToolUse`\n * bucket whose `matcher` equals the spec's matcher, or a new bucket is\n * pushed at the end of `PreToolUse` if none matches. Every other bucket,\n * hook, and top-level key is left exactly as found.\n * - `options.removeScriptPaths` → any existing entry whose command targets\n * one of those paths is stripped out entirely (not replaced) — for a hook\n * whose script write just failed, so it never leaves a dangling entry.\n * - Corrupt JSON (unparsable, not an object, `hooks` present but not an\n * object, or `hooks.PreToolUse` present but not an array) → returns\n * `status: \"error\"` and writes NOTHING. Never clobber a file the caller\n * can't safely reason about, and never silently discard a foreign `hooks`\n * value just because its shape is unexpected.\n * - Concurrency guard: the file's mtime is captured at read time and\n * re-checked immediately before the write; if it moved, someone else wrote\n * settings.json in the meantime and this call refuses with `status:\n * \"error\"` rather than silently clobbering that write (last-writer-wins).\n * - The write itself is atomic (temp file + rename, see\n * lib/atomic-write.ts) — a crash mid-write can never leave settings.json\n * torn, which would otherwise hard-block every subsequent Bash call.\n *\n * The file is always re-serialized with 2-space indentation on a write —\n * this normalizes the user's original formatting/whitespace, but every key\n * and value it did not own is preserved (structurally byte-identical\n * content, not necessarily byte-identical bytes).\n */\nexport function mergeHooksIntoSettings(\n settingsPath: string,\n specs: ArgosHookSpec[],\n options: MergeHooksOptions = {},\n): SettingsMergeResult {\n const existed = existsSync(settingsPath);\n let raw = \"{}\";\n let mtimeAtRead: number | undefined;\n if (existed) {\n try {\n raw = readFileSync(settingsPath, \"utf-8\");\n mtimeAtRead = statSync(settingsPath).mtimeMs;\n } catch (err) {\n return { status: \"error\", detail: errorMessage(err) };\n }\n }\n\n let settings: Record<string, unknown>;\n try {\n const parsed: unknown = raw.trim().length === 0 ? {} : JSON.parse(raw);\n if (!isPlainObject(parsed)) {\n return { status: \"error\", detail: \"settings.json existente no es un objeto JSON — arreglalo a mano.\" };\n }\n settings = parsed;\n } catch (err) {\n return {\n status: \"error\",\n detail: `settings.json existente tiene JSON inválido (${errorMessage(err)}) — arreglalo a mano y volvé a correr argos init.`,\n };\n }\n\n const before = JSON.stringify(settings);\n\n if (settings.hooks !== undefined && !isPlainObject(settings.hooks)) {\n return {\n status: \"error\",\n detail: \"settings.json tiene hooks que no es un objeto — arreglalo a mano.\",\n };\n }\n const hooksRoot: Record<string, unknown> = isPlainObject(settings.hooks)\n ? (settings.hooks as Record<string, unknown>)\n : {};\n\n const preToolUseRaw = hooksRoot.PreToolUse;\n if (preToolUseRaw !== undefined && !Array.isArray(preToolUseRaw)) {\n return {\n status: \"error\",\n detail: \"settings.json tiene hooks.PreToolUse que no es un array — arreglalo a mano.\",\n };\n }\n const preToolUse: unknown[] = Array.isArray(preToolUseRaw) ? preToolUseRaw : [];\n\n const removeScriptPaths = options.removeScriptPaths ?? [];\n if (removeScriptPaths.length > 0) {\n for (const bucket of preToolUse) {\n if (!isPlainObject(bucket) || !Array.isArray(bucket.hooks)) continue;\n bucket.hooks = (bucket.hooks as unknown[]).filter((h) => {\n if (!isPlainObject(h) || typeof h.command !== \"string\") return true;\n const command = h.command;\n return !removeScriptPaths.some((p) => command.includes(p));\n });\n }\n }\n\n for (const spec of specs) {\n const desired = buildHookCommand(spec);\n\n // Find our own previous entry anywhere in PreToolUse (matched by\n // scriptPath), to update it in place without moving it.\n let found = false;\n for (const bucket of preToolUse) {\n if (!isPlainObject(bucket) || !Array.isArray(bucket.hooks)) continue;\n const bucketHooks = bucket.hooks as unknown[];\n const idx = bucketHooks.findIndex(\n (h) => isPlainObject(h) && typeof h.command === \"string\" && h.command.includes(spec.scriptPath),\n );\n if (idx >= 0) {\n bucketHooks[idx] = desired;\n found = true;\n break;\n }\n }\n\n if (!found) {\n let bucket = preToolUse.find(\n (b): b is Record<string, unknown> => isPlainObject(b) && b.matcher === spec.matcher && Array.isArray(b.hooks),\n );\n if (!bucket) {\n bucket = { matcher: spec.matcher, hooks: [] };\n preToolUse.push(bucket);\n }\n (bucket.hooks as unknown[]).push(desired);\n }\n }\n\n hooksRoot.PreToolUse = preToolUse;\n settings.hooks = hooksRoot;\n\n if (existed && JSON.stringify(settings) === before) {\n return { status: \"unchanged\" };\n }\n\n options.onBeforeWrite?.();\n\n // Cheap concurrency guard: this is a read-modify-write with no locking, so\n // re-stat the file right before writing and bail if its mtime moved since\n // we read it — someone else wrote settings.json in between, and a plain\n // last-writer-wins here would silently drop their change.\n if (existed && mtimeAtRead !== undefined) {\n let mtimeNow: number | undefined;\n try {\n mtimeNow = statSync(settingsPath).mtimeMs;\n } catch {\n mtimeNow = undefined;\n }\n if (mtimeNow !== undefined && mtimeNow !== mtimeAtRead) {\n return {\n status: \"error\",\n detail: \"settings.json cambió durante el merge — reintenta.\",\n };\n }\n }\n\n try {\n writeFileAtomic(settingsPath, `${JSON.stringify(settings, null, 2)}\\n`);\n } catch (err) {\n return { status: \"error\", detail: errorMessage(err) };\n }\n\n return { status: existed ? \"updated\" : \"created\" };\n}\n\n// --- output-style (\"voice activation\") — spec 0004 \"Activación de la voz\" ---\n\n/**\n * True when `value` (a `settings.json.outputStyle` value) points at the\n * predecessor harness's voice (`navori`) — matched EXACTLY, never by\n * substring. Claude Code custom output styles are typically just the bare\n * style name (e.g. `\"navori\"`, backed by `~/.claude/output-styles/navori.md`),\n * but a hand-edited value could plausibly carry a path-ish form instead\n * (`.../output-styles/navori.md`) — this also matches that shape, but only\n * when the FINAL path segment, split on `/` or `\\`, is exactly `navori.md`\n * (case-insensitive extension). A bare `.includes(\"navori.md\")` or\n * `.includes(\"output-styles/navori\")` substring check would false-positive\n * on a user's own unrelated voice whose name merely starts with \"navori\"\n * (e.g. `navori-fork.md`, `navori-team-voice`) — and since\n * `applyOutputStylePolicy` defaults to taking this over unconditionally on\n * the non-interactive path (`--yes`/no-TTY, the common CI/agent path), a\n * false match there would silently clobber an unrelated user setting with\n * zero confirmation. Anchoring to exact equality closes that hole.\n */\nexport function isNavoriOutputStyle(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n if (value === \"navori\") return true;\n const lastSegment = value.split(/[/\\\\]/).pop() ?? \"\";\n return lastSegment.toLowerCase() === \"navori.md\";\n}\n\nexport type OutputStyleStatus = \"created\" | \"updated\" | \"unchanged\" | \"untouched\" | \"error\";\n\nexport interface ApplyOutputStyleResult {\n status: OutputStyleStatus;\n detail?: string;\n}\n\nexport interface ApplyOutputStyleOptions {\n /**\n * Whether to take over a `navori`-matched value into `\"Argos\"`. Only\n * consulted when the current value matches `isNavoriOutputStyle` — ignored\n * when the key is absent (always set) or matches some other foreign voice\n * (never touched, regardless of this flag). Default `true`, matching the\n * `--yes`/no-TTY \"replace unconditionally\" behavior from spec 0004;\n * callers backing an interactive confirm prompt pass `false` when the user\n * declines.\n */\n takeoverNavori?: boolean;\n /** Same test-only race seam as `MergeHooksOptions.onBeforeWrite` above. */\n onBeforeWrite?: () => void;\n}\n\n/**\n * Surgical policy for `settings.json.outputStyle` (spec 0004 \"Activación de\n * la voz\"), same mechanics as `mergeHooksIntoSettings`: read (missing file →\n * `{}`), mtime captured at read time, corrupt/unexpected JSON shape → writes\n * nothing and returns `status: \"error\"`, mutate in memory, re-stat right\n * before an atomic write and bail if the file moved underneath us.\n *\n * - Key absent → set to `\"Argos\"` (`created`/`updated` depending on whether\n * the file itself existed).\n * - Key already `\"Argos\"` → `unchanged`, no write.\n * - Key matches the predecessor voice (`isNavoriOutputStyle`) → takeover\n * governed by `options.takeoverNavori` (default `true`): accepted →\n * overwritten to `\"Argos\"`, `status: \"updated\"` with an explicit detail\n * naming the takeover; declined → `status: \"untouched\"`, nothing written.\n * - Key matches any other value → NEVER touched, `status: \"untouched\"`.\n *\n * Every other top-level key in `settings.json` is left exactly as found —\n * same surgical contract as the hooks merge.\n */\nexport function applyOutputStylePolicy(\n settingsPath: string,\n options: ApplyOutputStyleOptions = {},\n): ApplyOutputStyleResult {\n const existed = existsSync(settingsPath);\n let raw = \"{}\";\n let mtimeAtRead: number | undefined;\n if (existed) {\n try {\n raw = readFileSync(settingsPath, \"utf-8\");\n mtimeAtRead = statSync(settingsPath).mtimeMs;\n } catch (err) {\n return { status: \"error\", detail: errorMessage(err) };\n }\n }\n\n let settings: Record<string, unknown>;\n try {\n const parsed: unknown = raw.trim().length === 0 ? {} : JSON.parse(raw);\n if (!isPlainObject(parsed)) {\n return { status: \"error\", detail: \"settings.json existente no es un objeto JSON — arreglalo a mano.\" };\n }\n settings = parsed;\n } catch (err) {\n return {\n status: \"error\",\n detail: `settings.json existente tiene JSON inválido (${errorMessage(err)}) — arreglalo a mano y volvé a correr argos init.`,\n };\n }\n\n const current = settings.outputStyle;\n\n if (current === \"Argos\") {\n return { status: \"unchanged\" };\n }\n\n if (current !== undefined && !isNavoriOutputStyle(current)) {\n return {\n status: \"untouched\",\n detail: `outputStyle ya apunta a otra voz ('${String(current)}') — no se toca`,\n };\n }\n\n if (current !== undefined && isNavoriOutputStyle(current)) {\n const takeover = options.takeoverNavori ?? true;\n if (!takeover) {\n return {\n status: \"untouched\",\n detail: `outputStyle sigue en '${String(current)}' (voz del harness predecesor) — takeover declinado`,\n };\n }\n }\n\n const previous = current;\n settings.outputStyle = \"Argos\";\n\n options.onBeforeWrite?.();\n\n if (existed && mtimeAtRead !== undefined) {\n let mtimeNow: number | undefined;\n try {\n mtimeNow = statSync(settingsPath).mtimeMs;\n } catch {\n mtimeNow = undefined;\n }\n if (mtimeNow !== undefined && mtimeNow !== mtimeAtRead) {\n return { status: \"error\", detail: \"settings.json cambió durante el merge — reintenta.\" };\n }\n }\n\n try {\n writeFileAtomic(settingsPath, `${JSON.stringify(settings, null, 2)}\\n`);\n } catch (err) {\n return { status: \"error\", detail: errorMessage(err) };\n }\n\n if (previous !== undefined) {\n return { status: \"updated\", detail: `reemplazó ${String(previous)} → Argos` };\n }\n // `previous === undefined` here means the KEY was absent (not that the\n // file itself was absent) — always \"created\" regardless of `existed`,\n // since another writer (e.g. `mergeHooksIntoSettings`, in the same\n // `runInit` call) may have already created the file moments earlier for\n // an unrelated reason.\n return { status: \"created\" };\n}\n\nexport type RemoveOutputStyleStatus = \"removed\" | \"unchanged\" | \"error\";\n\nexport interface RemoveOutputStyleResult {\n status: RemoveOutputStyleStatus;\n detail?: string;\n}\n\nexport interface RemoveOutputStyleOptions {\n /** Same `argos remove` preview-mode seam as `RemoveHooksOptions.dryRun` above. */\n dryRun?: boolean;\n /** Same test-only race seam as `MergeHooksOptions.onBeforeWrite` above. */\n onBeforeWrite?: () => void;\n}\n\n/**\n * `argos remove`'s mirror of `applyOutputStylePolicy`: removes\n * `settings.json.outputStyle` ONLY when it's exactly `\"Argos\"` — Argos's own\n * value. Any other value (including a foreign voice `argos init` never\n * touched, or the key simply absent) is left exactly as found; this command\n * only ever cleans up after itself, never after the user's own choice of\n * voice. Same missing-file/corrupt-JSON/mtime-guard/atomic-write mechanics\n * as the rest of this module.\n */\nexport function removeOutputStyleIfArgos(\n settingsPath: string,\n options: RemoveOutputStyleOptions = {},\n): RemoveOutputStyleResult {\n if (!existsSync(settingsPath)) return { status: \"unchanged\" };\n\n let raw: string;\n let mtimeAtRead: number;\n try {\n raw = readFileSync(settingsPath, \"utf-8\");\n mtimeAtRead = statSync(settingsPath).mtimeMs;\n } catch (err) {\n return { status: \"error\", detail: errorMessage(err) };\n }\n\n let settings: Record<string, unknown>;\n try {\n const parsed: unknown = raw.trim().length === 0 ? {} : JSON.parse(raw);\n if (!isPlainObject(parsed)) {\n return { status: \"error\", detail: \"settings.json existente no es un objeto JSON — arreglalo a mano.\" };\n }\n settings = parsed;\n } catch (err) {\n return {\n status: \"error\",\n detail: `settings.json existente tiene JSON inválido (${errorMessage(err)}) — arreglalo a mano.`,\n };\n }\n\n if (settings.outputStyle !== \"Argos\") return { status: \"unchanged\" };\n if (options.dryRun) return { status: \"removed\" };\n\n delete settings.outputStyle;\n\n options.onBeforeWrite?.();\n\n let mtimeNow: number | undefined;\n try {\n mtimeNow = statSync(settingsPath).mtimeMs;\n } catch {\n mtimeNow = undefined;\n }\n if (mtimeNow !== undefined && mtimeNow !== mtimeAtRead) {\n return { status: \"error\", detail: \"settings.json cambió durante el remove — reintenta.\" };\n }\n\n try {\n writeFileAtomic(settingsPath, `${JSON.stringify(settings, null, 2)}\\n`);\n } catch (err) {\n return { status: \"error\", detail: errorMessage(err) };\n }\n\n return { status: \"removed\" };\n}\n\nexport type RemoveHooksStatus = \"removed\" | \"unchanged\" | \"error\";\n\nexport interface RemoveHooksResult {\n status: RemoveHooksStatus;\n detail?: string;\n /** Count of Argos-owned hook entries actually removed (0 for \"unchanged\"/\"error\"). */\n removedCount: number;\n}\n\nexport interface RemoveHooksOptions {\n /**\n * Compute what WOULD be removed without writing anything — `argos remove`'s\n * preview mode. Corrupt-JSON refusal still applies identically in dry-run:\n * a caller must not be told \"removed\" for a file it can't safely reason\n * about.\n */\n dryRun?: boolean;\n /** Same test-only seam as `MergeHooksOptions.onBeforeWrite` above. */\n onBeforeWrite?: () => void;\n}\n\n/**\n * Symmetric counterpart to `mergeHooksIntoSettings`: strips every Argos-owned\n * hook entry (any command matched by `isArgosOwnedHookCommand` against\n * `hooksDir`, not just the current `HOOK_IDS`) out of `hooks.PreToolUse`,\n * wherever it's found. Foreign hooks, buckets, and every other top-level key\n * are left exactly as found — same ownership/refusal contract as the merge\n * path:\n * - `hooksDir` is the resolved `<claudeDir>/hooks` directory this install\n * manages (normally `join(resolveClaudeDir(), \"hooks\")`). A hook is only\n * ever treated as Argos-owned — and therefore deletable — when its script\n * path actually resolves inside `hooksDir` AND its basename starts with\n * `argos-`; this is deliberately NOT the same as `isArgosHookCommand`'s\n * bare `/hooks/argos-` substring match, which would also match (and\n * delete) an unrelated user hook at a path like\n * `/Users/x/my/hooks/argos-custom.sh`.\n * - Missing file, or no `hooks`/`hooks.PreToolUse` at all → \"unchanged\",\n * nothing to remove.\n * - Corrupt JSON (unparsable, not an object, `hooks` present but not an\n * object, or `hooks.PreToolUse` present but not an array) → `status:\n * \"error\"`, writes NOTHING. Never guess at a shape it can't verify.\n * - A `PreToolUse` bucket that becomes empty after removing Argos's own\n * entries is dropped entirely (it only ever existed to hold them — see\n * `mergeHooksIntoSettings`, which creates a fresh bucket per matcher on\n * demand); a bucket that still has foreign hooks left (including one with\n * ZERO Argos-owned hooks in it) keeps its matcher, its hooks, and its\n * position untouched.\n * - Same mtime concurrency guard and atomic write as the merge path.\n */\nexport function removeAllArgosHooksFromSettings(\n settingsPath: string,\n hooksDir: string,\n options: RemoveHooksOptions = {},\n): RemoveHooksResult {\n if (!existsSync(settingsPath)) return { status: \"unchanged\", removedCount: 0 };\n\n let raw: string;\n let mtimeAtRead: number;\n try {\n raw = readFileSync(settingsPath, \"utf-8\");\n mtimeAtRead = statSync(settingsPath).mtimeMs;\n } catch (err) {\n return { status: \"error\", detail: errorMessage(err), removedCount: 0 };\n }\n\n let settings: Record<string, unknown>;\n try {\n const parsed: unknown = raw.trim().length === 0 ? {} : JSON.parse(raw);\n if (!isPlainObject(parsed)) {\n return {\n status: \"error\",\n detail: \"settings.json existente no es un objeto JSON — arreglalo a mano.\",\n removedCount: 0,\n };\n }\n settings = parsed;\n } catch (err) {\n return {\n status: \"error\",\n detail: `settings.json existente tiene JSON inválido (${errorMessage(err)}) — arreglalo a mano.`,\n removedCount: 0,\n };\n }\n\n if (settings.hooks !== undefined && !isPlainObject(settings.hooks)) {\n return {\n status: \"error\",\n detail: \"settings.json tiene hooks que no es un objeto — arreglalo a mano.\",\n removedCount: 0,\n };\n }\n const hooksRoot = settings.hooks as Record<string, unknown> | undefined;\n if (!hooksRoot) return { status: \"unchanged\", removedCount: 0 };\n\n const preToolUseRaw = hooksRoot.PreToolUse;\n if (preToolUseRaw !== undefined && !Array.isArray(preToolUseRaw)) {\n return {\n status: \"error\",\n detail: \"settings.json tiene hooks.PreToolUse que no es un array — arreglalo a mano.\",\n removedCount: 0,\n };\n }\n if (!Array.isArray(preToolUseRaw) || preToolUseRaw.length === 0) {\n return { status: \"unchanged\", removedCount: 0 };\n }\n\n let removedCount = 0;\n const nextPreToolUse: unknown[] = [];\n for (const bucket of preToolUseRaw) {\n if (!isPlainObject(bucket) || !Array.isArray(bucket.hooks)) {\n nextPreToolUse.push(bucket);\n continue;\n }\n const before = bucket.hooks.length;\n const filteredHooks = (bucket.hooks as unknown[]).filter(\n (h) => !(isPlainObject(h) && isArgosOwnedHookCommand(h.command, hooksDir)),\n );\n removedCount += before - filteredHooks.length;\n if (filteredHooks.length === 0) continue; // bucket only ever held Argos's own entries\n nextPreToolUse.push({ ...bucket, hooks: filteredHooks });\n }\n\n if (removedCount === 0) return { status: \"unchanged\", removedCount: 0 };\n if (options.dryRun) return { status: \"removed\", removedCount };\n\n hooksRoot.PreToolUse = nextPreToolUse;\n settings.hooks = hooksRoot;\n\n options.onBeforeWrite?.();\n\n // Same cheap concurrency guard as mergeHooksIntoSettings.\n let mtimeNow: number | undefined;\n try {\n mtimeNow = statSync(settingsPath).mtimeMs;\n } catch {\n mtimeNow = undefined;\n }\n if (mtimeNow !== undefined && mtimeNow !== mtimeAtRead) {\n return {\n status: \"error\",\n detail: \"settings.json cambió durante el remove — reintenta.\",\n removedCount: 0,\n };\n }\n\n try {\n writeFileAtomic(settingsPath, `${JSON.stringify(settings, null, 2)}\\n`);\n } catch (err) {\n return { status: \"error\", detail: errorMessage(err), removedCount: 0 };\n }\n\n return { status: \"removed\", removedCount };\n}\n"],"names":["existsSync","readFileSync","statSync","basename","isAbsolute","relative","resolve","sep","writeFileAtomic","ARGOS_HOOK_PATH_RE","BASH_HOOK_COMMAND_RE","ARGOS_HOOK_BASENAME_RE","isArgosHookCommand","command","test","extractBashScriptPath","exec","isPathInsideDir","candidate","dir","rel","startsWith","isArgosOwnedHookCommand","hooksDir","scriptPath","isPlainObject","v","Array","isArray","buildHookCommand","spec","hook","type","timeout","undefined","statusMessage","errorMessage","err","Error","message","String","mergeHooksIntoSettings","settingsPath","specs","options","existed","raw","mtimeAtRead","mtimeMs","status","detail","settings","parsed","trim","length","JSON","parse","before","stringify","hooks","hooksRoot","preToolUseRaw","PreToolUse","preToolUse","removeScriptPaths","bucket","filter","h","some","p","includes","desired","found","bucketHooks","idx","findIndex","find","b","matcher","push","onBeforeWrite","mtimeNow","isNavoriOutputStyle","value","lastSegment","split","pop","toLowerCase","applyOutputStylePolicy","current","outputStyle","takeover","takeoverNavori","previous","removeOutputStyleIfArgos","dryRun","removeAllArgosHooksFromSettings","removedCount","nextPreToolUse","filteredHooks"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "argos-harness",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "Argos — global-first CLI harness for setting up and managing Claude Code across your projects",
6
6
  "license": "MIT",