argos-harness 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) 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 +38 -0
  15. package/dist/commands/init.d.ts.map +1 -1
  16. package/dist/commands/init.js +222 -59
  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/prompter.d.ts +63 -0
  31. package/dist/lib/prompter.d.ts.map +1 -0
  32. package/dist/lib/prompter.js +30 -0
  33. package/dist/lib/prompter.js.map +1 -0
  34. package/dist/lib/settings-merge.d.ts +78 -0
  35. package/dist/lib/settings-merge.d.ts.map +1 -1
  36. package/dist/lib/settings-merge.js +204 -0
  37. package/dist/lib/settings-merge.js.map +1 -1
  38. package/package.json +1 -1
@@ -1,9 +1,11 @@
1
1
  /**
2
- * Ordered ids of the 5 managed CLAUDE.md blocks. Order defines the order the
2
+ * Ordered ids of the 6 managed CLAUDE.md blocks. Order defines the order the
3
3
  * blocks appear in the rendered file. Ids are the basenames (without `.md`)
4
- * of `assets/managed/*.md`.
4
+ * of `assets/managed/*.md`. This array is the single shared source for both
5
+ * `init.ts` (installs/injects each block, in this order) and `doctor.ts`
6
+ * (audits presence/version drift for each block) — never duplicate it.
5
7
  */
6
- export declare const MANAGED_BLOCK_IDS: readonly ["identidad", "formato-respuesta", "aterrizaje", "orquestacion", "operaciones-seguras"];
8
+ export declare const MANAGED_BLOCK_IDS: readonly ["identidad", "formato-respuesta", "disciplina-skills", "aterrizaje", "orquestacion", "operaciones-seguras"];
7
9
  /**
8
10
  * Resolve the Argos assets directory (`managed/`, `agents/`, `skills/`,
9
11
  * `output-styles/`) relative to the currently running module. See
@@ -1 +1 @@
1
- {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../src/lib/assets.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,kGAMpB,CAAC;AAEX;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAE1E;AAED,+DAA+D;AAC/D,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAEzE;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAKxD;AAED,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAKxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAY3E"}
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../src/lib/assets.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,uHAOpB,CAAC;AAEX;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,MAAwB,GAAG,MAAM,CAE1E;AAED,+DAA+D;AAC/D,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAEzE;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAKxD;AAED,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAKxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAY3E"}
@@ -2,12 +2,15 @@ import { readdirSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { resolvePackageRoot } from "./package-root.js";
4
4
  /**
5
- * Ordered ids of the 5 managed CLAUDE.md blocks. Order defines the order the
5
+ * Ordered ids of the 6 managed CLAUDE.md blocks. Order defines the order the
6
6
  * blocks appear in the rendered file. Ids are the basenames (without `.md`)
7
- * of `assets/managed/*.md`.
7
+ * of `assets/managed/*.md`. This array is the single shared source for both
8
+ * `init.ts` (installs/injects each block, in this order) and `doctor.ts`
9
+ * (audits presence/version drift for each block) — never duplicate it.
8
10
  */ export const MANAGED_BLOCK_IDS = [
9
11
  "identidad",
10
12
  "formato-respuesta",
13
+ "disciplina-skills",
11
14
  "aterrizaje",
12
15
  "orquestacion",
13
16
  "operaciones-seguras"
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/assets.ts"],"sourcesContent":["import { readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { resolvePackageRoot } from \"./package-root.js\";\n\n/**\n * Ordered ids of the 5 managed CLAUDE.md blocks. Order defines the order the\n * blocks appear in the rendered file. Ids are the basenames (without `.md`)\n * of `assets/managed/*.md`.\n */\nexport const MANAGED_BLOCK_IDS = [\n \"identidad\",\n \"formato-respuesta\",\n \"aterrizaje\",\n \"orquestacion\",\n \"operaciones-seguras\",\n] as const;\n\n/**\n * Resolve the Argos assets directory (`managed/`, `agents/`, `skills/`,\n * `output-styles/`) relative to the currently running module. See\n * `resolvePackageRoot` for the resolution strategy.\n */\nexport function resolveAssetsDir(fromUrl: string = import.meta.url): string {\n return join(resolvePackageRoot(fromUrl), \"assets\");\n}\n\n/** Read a text asset file relative to the assets directory. */\nexport function readAsset(assetsDir: string, ...relPath: string[]): string {\n return readFileSync(join(assetsDir, ...relPath), \"utf-8\");\n}\n\n/** List agent ids (basenames without `.md`) found under `assets/agents/`. */\nexport function listAgentIds(assetsDir: string): string[] {\n return readdirSync(join(assetsDir, \"agents\"))\n .filter((f) => f.endsWith(\".md\"))\n .map((f) => f.slice(0, -3))\n .sort();\n}\n\n/** List skill ids (directory names) found under `assets/skills/`. */\nexport function listSkillIds(assetsDir: string): string[] {\n return readdirSync(join(assetsDir, \"skills\"), { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name)\n .sort();\n}\n\n/**\n * Recursively list every file shipped under a skill's asset directory\n * (`assets/skills/<skillId>/`), as relative paths from that directory with\n * forward slashes regardless of platform (e.g. `SKILL.md`,\n * `references/core.md`, `phases/0-product.md`). Sorted for deterministic\n * output. Some skills are a single `SKILL.md`; ~15 also ship load-bearing\n * subdirectories (`references/`, `phases/`, `assets/`) that this walks too —\n * without it, only `SKILL.md` would ever reach the user's `~/.claude/skills`.\n */\nexport function listSkillFiles(assetsDir: string, skillId: string): string[] {\n const root = join(assetsDir, \"skills\", skillId);\n const out: string[] = [];\n const walk = (dir: string, prefix: string) => {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isDirectory()) walk(join(dir, entry.name), rel);\n else out.push(rel);\n }\n };\n walk(root, \"\");\n return out.sort();\n}\n"],"names":["readdirSync","readFileSync","join","resolvePackageRoot","MANAGED_BLOCK_IDS","resolveAssetsDir","fromUrl","url","readAsset","assetsDir","relPath","listAgentIds","filter","f","endsWith","map","slice","sort","listSkillIds","withFileTypes","entry","isDirectory","name","listSkillFiles","skillId","root","out","walk","dir","prefix","rel","push"],"mappings":"AAAA,SAASA,WAAW,EAAEC,YAAY,QAAQ,UAAU;AACpD,SAASC,IAAI,QAAQ,YAAY;AACjC,SAASC,kBAAkB,QAAQ,oBAAoB;AAEvD;;;;CAIC,GACD,OAAO,MAAMC,oBAAoB;IAC/B;IACA;IACA;IACA;IACA;CACD,CAAU;AAEX;;;;CAIC,GACD,OAAO,SAASC,iBAAiBC,UAAkB,YAAYC,GAAG;IAChE,OAAOL,KAAKC,mBAAmBG,UAAU;AAC3C;AAEA,6DAA6D,GAC7D,OAAO,SAASE,UAAUC,SAAiB,EAAE,GAAGC,OAAiB;IAC/D,OAAOT,aAAaC,KAAKO,cAAcC,UAAU;AACnD;AAEA,2EAA2E,GAC3E,OAAO,SAASC,aAAaF,SAAiB;IAC5C,OAAOT,YAAYE,KAAKO,WAAW,WAChCG,MAAM,CAAC,CAACC,IAAMA,EAAEC,QAAQ,CAAC,QACzBC,GAAG,CAAC,CAACF,IAAMA,EAAEG,KAAK,CAAC,GAAG,CAAC,IACvBC,IAAI;AACT;AAEA,mEAAmE,GACnE,OAAO,SAASC,aAAaT,SAAiB;IAC5C,OAAOT,YAAYE,KAAKO,WAAW,WAAW;QAAEU,eAAe;IAAK,GACjEP,MAAM,CAAC,CAACQ,QAAUA,MAAMC,WAAW,IACnCN,GAAG,CAAC,CAACK,QAAUA,MAAME,IAAI,EACzBL,IAAI;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASM,eAAed,SAAiB,EAAEe,OAAe;IAC/D,MAAMC,OAAOvB,KAAKO,WAAW,UAAUe;IACvC,MAAME,MAAgB,EAAE;IACxB,MAAMC,OAAO,CAACC,KAAaC;QACzB,KAAK,MAAMT,SAASpB,YAAY4B,KAAK;YAAET,eAAe;QAAK,GAAI;YAC7D,MAAMW,MAAMD,SAAS,GAAGA,OAAO,CAAC,EAAET,MAAME,IAAI,EAAE,GAAGF,MAAME,IAAI;YAC3D,IAAIF,MAAMC,WAAW,IAAIM,KAAKzB,KAAK0B,KAAKR,MAAME,IAAI,GAAGQ;iBAChDJ,IAAIK,IAAI,CAACD;QAChB;IACF;IACAH,KAAKF,MAAM;IACX,OAAOC,IAAIT,IAAI;AACjB"}
1
+ {"version":3,"sources":["../../src/lib/assets.ts"],"sourcesContent":["import { readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { resolvePackageRoot } from \"./package-root.js\";\n\n/**\n * Ordered ids of the 6 managed CLAUDE.md blocks. Order defines the order the\n * blocks appear in the rendered file. Ids are the basenames (without `.md`)\n * of `assets/managed/*.md`. This array is the single shared source for both\n * `init.ts` (installs/injects each block, in this order) and `doctor.ts`\n * (audits presence/version drift for each block) — never duplicate it.\n */\nexport const MANAGED_BLOCK_IDS = [\n \"identidad\",\n \"formato-respuesta\",\n \"disciplina-skills\",\n \"aterrizaje\",\n \"orquestacion\",\n \"operaciones-seguras\",\n] as const;\n\n/**\n * Resolve the Argos assets directory (`managed/`, `agents/`, `skills/`,\n * `output-styles/`) relative to the currently running module. See\n * `resolvePackageRoot` for the resolution strategy.\n */\nexport function resolveAssetsDir(fromUrl: string = import.meta.url): string {\n return join(resolvePackageRoot(fromUrl), \"assets\");\n}\n\n/** Read a text asset file relative to the assets directory. */\nexport function readAsset(assetsDir: string, ...relPath: string[]): string {\n return readFileSync(join(assetsDir, ...relPath), \"utf-8\");\n}\n\n/** List agent ids (basenames without `.md`) found under `assets/agents/`. */\nexport function listAgentIds(assetsDir: string): string[] {\n return readdirSync(join(assetsDir, \"agents\"))\n .filter((f) => f.endsWith(\".md\"))\n .map((f) => f.slice(0, -3))\n .sort();\n}\n\n/** List skill ids (directory names) found under `assets/skills/`. */\nexport function listSkillIds(assetsDir: string): string[] {\n return readdirSync(join(assetsDir, \"skills\"), { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => entry.name)\n .sort();\n}\n\n/**\n * Recursively list every file shipped under a skill's asset directory\n * (`assets/skills/<skillId>/`), as relative paths from that directory with\n * forward slashes regardless of platform (e.g. `SKILL.md`,\n * `references/core.md`, `phases/0-product.md`). Sorted for deterministic\n * output. Some skills are a single `SKILL.md`; ~15 also ship load-bearing\n * subdirectories (`references/`, `phases/`, `assets/`) that this walks too —\n * without it, only `SKILL.md` would ever reach the user's `~/.claude/skills`.\n */\nexport function listSkillFiles(assetsDir: string, skillId: string): string[] {\n const root = join(assetsDir, \"skills\", skillId);\n const out: string[] = [];\n const walk = (dir: string, prefix: string) => {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isDirectory()) walk(join(dir, entry.name), rel);\n else out.push(rel);\n }\n };\n walk(root, \"\");\n return out.sort();\n}\n"],"names":["readdirSync","readFileSync","join","resolvePackageRoot","MANAGED_BLOCK_IDS","resolveAssetsDir","fromUrl","url","readAsset","assetsDir","relPath","listAgentIds","filter","f","endsWith","map","slice","sort","listSkillIds","withFileTypes","entry","isDirectory","name","listSkillFiles","skillId","root","out","walk","dir","prefix","rel","push"],"mappings":"AAAA,SAASA,WAAW,EAAEC,YAAY,QAAQ,UAAU;AACpD,SAASC,IAAI,QAAQ,YAAY;AACjC,SAASC,kBAAkB,QAAQ,oBAAoB;AAEvD;;;;;;CAMC,GACD,OAAO,MAAMC,oBAAoB;IAC/B;IACA;IACA;IACA;IACA;IACA;CACD,CAAU;AAEX;;;;CAIC,GACD,OAAO,SAASC,iBAAiBC,UAAkB,YAAYC,GAAG;IAChE,OAAOL,KAAKC,mBAAmBG,UAAU;AAC3C;AAEA,6DAA6D,GAC7D,OAAO,SAASE,UAAUC,SAAiB,EAAE,GAAGC,OAAiB;IAC/D,OAAOT,aAAaC,KAAKO,cAAcC,UAAU;AACnD;AAEA,2EAA2E,GAC3E,OAAO,SAASC,aAAaF,SAAiB;IAC5C,OAAOT,YAAYE,KAAKO,WAAW,WAChCG,MAAM,CAAC,CAACC,IAAMA,EAAEC,QAAQ,CAAC,QACzBC,GAAG,CAAC,CAACF,IAAMA,EAAEG,KAAK,CAAC,GAAG,CAAC,IACvBC,IAAI;AACT;AAEA,mEAAmE,GACnE,OAAO,SAASC,aAAaT,SAAiB;IAC5C,OAAOT,YAAYE,KAAKO,WAAW,WAAW;QAAEU,eAAe;IAAK,GACjEP,MAAM,CAAC,CAACQ,QAAUA,MAAMC,WAAW,IACnCN,GAAG,CAAC,CAACK,QAAUA,MAAME,IAAI,EACzBL,IAAI;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASM,eAAed,SAAiB,EAAEe,OAAe;IAC/D,MAAMC,OAAOvB,KAAKO,WAAW,UAAUe;IACvC,MAAME,MAAgB,EAAE;IACxB,MAAMC,OAAO,CAACC,KAAaC;QACzB,KAAK,MAAMT,SAASpB,YAAY4B,KAAK;YAAET,eAAe;QAAK,GAAI;YAC7D,MAAMW,MAAMD,SAAS,GAAGA,OAAO,CAAC,EAAET,MAAME,IAAI,EAAE,GAAGF,MAAME,IAAI;YAC3D,IAAIF,MAAMC,WAAW,IAAIM,KAAKzB,KAAK0B,KAAKR,MAAME,IAAI,GAAGQ;iBAChDJ,IAAIK,IAAI,CAACD;QAChB;IACF;IACAH,KAAKF,MAAM;IACX,OAAOC,IAAIT,IAAI;AACjB"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Interactive CLI layer (spec 0004, F5). The `Prompter` interface is the
3
+ * injectable seam every interactive surface (`argos init`, `argos adopt`,
4
+ * `argos remove`, `argos workspace link`) depends on instead of importing
5
+ * `@clack/prompts` directly — same idiom as `OpenclawRunner` in
6
+ * `lib/openclaw-agents.ts`: a typed interface, a real default implementation
7
+ * that's a thin pass-through over the real library, and every surface takes
8
+ * an optional `prompter?: Prompter` that defaults to it. Tests then inject a
9
+ * trivial plain-object fake instead of depending on `@clack` internals.
10
+ *
11
+ * Shaped to mirror `@clack/prompts`' actual API 1:1 (same option names,
12
+ * same `Value | symbol` return convention for cancellable prompts) so the
13
+ * default implementation below is a pure pass-through.
14
+ */
15
+ export interface SelectOption<Value extends string> {
16
+ value: Value;
17
+ label?: string;
18
+ hint?: string;
19
+ }
20
+ export interface SelectPromptOptions<Value extends string> {
21
+ message: string;
22
+ options: SelectOption<Value>[];
23
+ initialValue?: Value;
24
+ }
25
+ export interface ConfirmPromptOptions {
26
+ message: string;
27
+ initialValue?: boolean;
28
+ }
29
+ export interface TextPromptOptions {
30
+ message: string;
31
+ placeholder?: string;
32
+ defaultValue?: string;
33
+ initialValue?: string;
34
+ /**
35
+ * Signature matches `@clack/prompts`' own `Validate<string>` (accepts
36
+ * `undefined` too — the prompt hasn't been submitted yet on first render)
37
+ * so the default implementation below is a pure pass-through.
38
+ */
39
+ validate?: (value: string | undefined) => string | Error | undefined;
40
+ }
41
+ export interface Prompter {
42
+ select<Value extends string>(opts: SelectPromptOptions<Value>): Promise<Value | symbol>;
43
+ confirm(opts: ConfirmPromptOptions): Promise<boolean | symbol>;
44
+ text(opts: TextPromptOptions): Promise<string | symbol>;
45
+ isCancel(value: unknown): value is symbol;
46
+ cancel(message?: string): void;
47
+ note(message?: string, title?: string): void;
48
+ intro(title?: string): void;
49
+ outro(message?: string): void;
50
+ }
51
+ /** Real default implementation, backed by `@clack/prompts`. */
52
+ export declare const clackPrompter: Prompter;
53
+ /**
54
+ * TTY gate every interactive surface checks independently before layering
55
+ * prompts on top of its own flag-driven defaults (spec 0004's hard rule):
56
+ * both stdout and stdin must be a real TTY, AND `--yes` must not have been
57
+ * passed — `--yes` forces non-interactive behavior even under a real TTY,
58
+ * since that's how a human operator opts out of the wizard on demand.
59
+ */
60
+ export declare function isInteractive(options: {
61
+ yes?: boolean;
62
+ }): boolean;
63
+ //# sourceMappingURL=prompter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompter.d.ts","sourceRoot":"","sources":["../../src/lib/prompter.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAQH,MAAM,WAAW,YAAY,CAAC,KAAK,SAAS,MAAM;IAChD,KAAK,EAAE,KAAK,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB,CAAC,KAAK,SAAS,MAAM;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;IAC/B,YAAY,CAAC,EAAE,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;CACtE;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,KAAK,SAAS,MAAM,EAAE,IAAI,EAAE,mBAAmB,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC;IACxF,OAAO,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC;IAC/D,IAAI,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IACxD,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC;IAC1C,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,+DAA+D;AAC/D,eAAO,MAAM,aAAa,EAAE,QAkB3B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAEjE"}
@@ -0,0 +1,30 @@
1
+ import { cancel, confirm, intro, isCancel, note, outro, select, text } from "@clack/prompts";
2
+ /** Real default implementation, backed by `@clack/prompts`. */ export const clackPrompter = {
3
+ // `@clack/prompts`' `Option<Value>` is a conditional type keyed on
4
+ // `Value extends Primitive` — TS can't reduce that conditional for our
5
+ // still-generic `Value` type parameter here (it only resolves once a
6
+ // caller picks a concrete `Value`), so it refuses to unify
7
+ // `SelectOption<Value>[]` with `Option<Value>[]` even though every
8
+ // concrete instantiation (`Value extends string`) is structurally
9
+ // identical. Narrow, single-purpose cast at this one boundary — not `any`
10
+ // on the whole function — since `opts` is otherwise fully typed above.
11
+ select: (opts)=>select(opts),
12
+ confirm: (opts)=>confirm(opts),
13
+ text: (opts)=>text(opts),
14
+ isCancel: (value)=>isCancel(value),
15
+ cancel: (message)=>cancel(message),
16
+ note: (message, title)=>note(message, title),
17
+ intro: (title)=>intro(title),
18
+ outro: (message)=>outro(message)
19
+ };
20
+ /**
21
+ * TTY gate every interactive surface checks independently before layering
22
+ * prompts on top of its own flag-driven defaults (spec 0004's hard rule):
23
+ * both stdout and stdin must be a real TTY, AND `--yes` must not have been
24
+ * passed — `--yes` forces non-interactive behavior even under a real TTY,
25
+ * since that's how a human operator opts out of the wizard on demand.
26
+ */ export function isInteractive(options) {
27
+ return process.stdout.isTTY === true && process.stdin.isTTY === true && !options.yes;
28
+ }
29
+
30
+ //# sourceMappingURL=prompter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/prompter.ts"],"sourcesContent":["import { cancel, confirm, intro, isCancel, note, outro, select, text } from \"@clack/prompts\";\n\n/**\n * Interactive CLI layer (spec 0004, F5). The `Prompter` interface is the\n * injectable seam every interactive surface (`argos init`, `argos adopt`,\n * `argos remove`, `argos workspace link`) depends on instead of importing\n * `@clack/prompts` directly — same idiom as `OpenclawRunner` in\n * `lib/openclaw-agents.ts`: a typed interface, a real default implementation\n * that's a thin pass-through over the real library, and every surface takes\n * an optional `prompter?: Prompter` that defaults to it. Tests then inject a\n * trivial plain-object fake instead of depending on `@clack` internals.\n *\n * Shaped to mirror `@clack/prompts`' actual API 1:1 (same option names,\n * same `Value | symbol` return convention for cancellable prompts) so the\n * default implementation below is a pure pass-through.\n */\n\n// `Value extends string` (rather than unconstrained) deliberately mirrors\n// every actual use of `select` in this codebase (language codes, workspace\n// names, match candidates) — it's also what makes `SelectOption` line up\n// with `@clack/prompts`' own `Option<Value>` (whose `label` is optional only\n// for `Value extends Readonly<string | boolean | number>`), so the default\n// implementation below can pass options straight through with no adapter.\nexport interface SelectOption<Value extends string> {\n value: Value;\n label?: string;\n hint?: string;\n}\n\nexport interface SelectPromptOptions<Value extends string> {\n message: string;\n options: SelectOption<Value>[];\n initialValue?: Value;\n}\n\nexport interface ConfirmPromptOptions {\n message: string;\n initialValue?: boolean;\n}\n\nexport interface TextPromptOptions {\n message: string;\n placeholder?: string;\n defaultValue?: string;\n initialValue?: string;\n /**\n * Signature matches `@clack/prompts`' own `Validate<string>` (accepts\n * `undefined` too — the prompt hasn't been submitted yet on first render)\n * so the default implementation below is a pure pass-through.\n */\n validate?: (value: string | undefined) => string | Error | undefined;\n}\n\nexport interface Prompter {\n select<Value extends string>(opts: SelectPromptOptions<Value>): Promise<Value | symbol>;\n confirm(opts: ConfirmPromptOptions): Promise<boolean | symbol>;\n text(opts: TextPromptOptions): Promise<string | symbol>;\n isCancel(value: unknown): value is symbol;\n cancel(message?: string): void;\n note(message?: string, title?: string): void;\n intro(title?: string): void;\n outro(message?: string): void;\n}\n\n/** Real default implementation, backed by `@clack/prompts`. */\nexport const clackPrompter: Prompter = {\n // `@clack/prompts`' `Option<Value>` is a conditional type keyed on\n // `Value extends Primitive` — TS can't reduce that conditional for our\n // still-generic `Value` type parameter here (it only resolves once a\n // caller picks a concrete `Value`), so it refuses to unify\n // `SelectOption<Value>[]` with `Option<Value>[]` even though every\n // concrete instantiation (`Value extends string`) is structurally\n // identical. Narrow, single-purpose cast at this one boundary — not `any`\n // on the whole function — since `opts` is otherwise fully typed above.\n select: <Value extends string>(opts: SelectPromptOptions<Value>) =>\n select(opts as Parameters<typeof select>[0]) as Promise<Value | symbol>,\n confirm: (opts) => confirm(opts),\n text: (opts) => text(opts),\n isCancel: (value): value is symbol => isCancel(value),\n cancel: (message) => cancel(message),\n note: (message, title) => note(message, title),\n intro: (title) => intro(title),\n outro: (message) => outro(message),\n};\n\n/**\n * TTY gate every interactive surface checks independently before layering\n * prompts on top of its own flag-driven defaults (spec 0004's hard rule):\n * both stdout and stdin must be a real TTY, AND `--yes` must not have been\n * passed — `--yes` forces non-interactive behavior even under a real TTY,\n * since that's how a human operator opts out of the wizard on demand.\n */\nexport function isInteractive(options: { yes?: boolean }): boolean {\n return process.stdout.isTTY === true && process.stdin.isTTY === true && !options.yes;\n}\n"],"names":["cancel","confirm","intro","isCancel","note","outro","select","text","clackPrompter","opts","value","message","title","isInteractive","options","process","stdout","isTTY","stdin","yes"],"mappings":"AAAA,SAASA,MAAM,EAAEC,OAAO,EAAEC,KAAK,EAAEC,QAAQ,EAAEC,IAAI,EAAEC,KAAK,EAAEC,MAAM,EAAEC,IAAI,QAAQ,iBAAiB;AAgE7F,6DAA6D,GAC7D,OAAO,MAAMC,gBAA0B;IACrC,mEAAmE;IACnE,uEAAuE;IACvE,qEAAqE;IACrE,2DAA2D;IAC3D,mEAAmE;IACnE,kEAAkE;IAClE,0EAA0E;IAC1E,uEAAuE;IACvEF,QAAQ,CAAuBG,OAC7BH,OAAOG;IACTR,SAAS,CAACQ,OAASR,QAAQQ;IAC3BF,MAAM,CAACE,OAASF,KAAKE;IACrBN,UAAU,CAACO,QAA2BP,SAASO;IAC/CV,QAAQ,CAACW,UAAYX,OAAOW;IAC5BP,MAAM,CAACO,SAASC,QAAUR,KAAKO,SAASC;IACxCV,OAAO,CAACU,QAAUV,MAAMU;IACxBP,OAAO,CAACM,UAAYN,MAAMM;AAC5B,EAAE;AAEF;;;;;;CAMC,GACD,OAAO,SAASE,cAAcC,OAA0B;IACtD,OAAOC,QAAQC,MAAM,CAACC,KAAK,KAAK,QAAQF,QAAQG,KAAK,CAACD,KAAK,KAAK,QAAQ,CAACH,QAAQK,GAAG;AACtF"}
@@ -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