argos-harness 0.1.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 (217) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/assets/agents/auditor.md +140 -0
  4. package/assets/agents/commit-pr-pilot.md +154 -0
  5. package/assets/agents/explorer.md +93 -0
  6. package/assets/agents/implementer.md +117 -0
  7. package/assets/agents/leader.md +149 -0
  8. package/assets/agents/researcher.md +89 -0
  9. package/assets/agents/review-readability.md +87 -0
  10. package/assets/agents/review-reliability.md +100 -0
  11. package/assets/agents/review-resilience.md +87 -0
  12. package/assets/agents/review-risk.md +87 -0
  13. package/assets/agents/reviewer.md +167 -0
  14. package/assets/agents/ticket-audit.md +129 -0
  15. package/assets/hooks/argos-guard-destructive.sh +127 -0
  16. package/assets/hooks/argos-quality-gate.sh +129 -0
  17. package/assets/managed/aterrizaje.md +24 -0
  18. package/assets/managed/formato-respuesta.md +21 -0
  19. package/assets/managed/identidad.md +48 -0
  20. package/assets/managed/operaciones-seguras.md +14 -0
  21. package/assets/managed/orquestacion.md +169 -0
  22. package/assets/output-styles/argos.md +71 -0
  23. package/assets/skills/ai-sdk-5/SKILL.md +230 -0
  24. package/assets/skills/angular/SKILL.md +19 -0
  25. package/assets/skills/angular/references/architecture.md +137 -0
  26. package/assets/skills/angular/references/core.md +197 -0
  27. package/assets/skills/angular/references/forms.md +115 -0
  28. package/assets/skills/angular/references/performance.md +124 -0
  29. package/assets/skills/apollo-client/SKILL.md +61 -0
  30. package/assets/skills/app-blueprint/SKILL.md +45 -0
  31. package/assets/skills/app-blueprint/assets/module-template.md +48 -0
  32. package/assets/skills/app-blueprint/assets/system-template.md +38 -0
  33. package/assets/skills/app-blueprint/references/workflow.md +101 -0
  34. package/assets/skills/app-builder/SKILL.md +43 -0
  35. package/assets/skills/app-builder/phases/0-product.md +58 -0
  36. package/assets/skills/app-builder/phases/1-scaffold.md +33 -0
  37. package/assets/skills/app-builder/phases/10-store.md +40 -0
  38. package/assets/skills/app-builder/phases/2-data.md +34 -0
  39. package/assets/skills/app-builder/phases/3-domain.md +33 -0
  40. package/assets/skills/app-builder/phases/4-ui-nav.md +32 -0
  41. package/assets/skills/app-builder/phases/5-identity.md +30 -0
  42. package/assets/skills/app-builder/phases/6-polish.md +34 -0
  43. package/assets/skills/app-builder/phases/7-brand.md +31 -0
  44. package/assets/skills/app-builder/phases/8-web.md +30 -0
  45. package/assets/skills/app-builder/phases/9-docs.md +31 -0
  46. package/assets/skills/app-ia/SKILL.md +54 -0
  47. package/assets/skills/astro/SKILL.md +39 -0
  48. package/assets/skills/axios/SKILL.md +61 -0
  49. package/assets/skills/branch-pr/SKILL.md +200 -0
  50. package/assets/skills/bullmq/SKILL.md +55 -0
  51. package/assets/skills/chained-pr/SKILL.md +48 -0
  52. package/assets/skills/chained-pr/references/chaining-details.md +99 -0
  53. package/assets/skills/cognitive-doc-design/SKILL.md +81 -0
  54. package/assets/skills/comment-writer/SKILL.md +74 -0
  55. package/assets/skills/dashboard-ia/SKILL.md +54 -0
  56. package/assets/skills/django-drf/SKILL.md +180 -0
  57. package/assets/skills/go-testing/SKILL.md +47 -0
  58. package/assets/skills/go-testing/references/examples.md +89 -0
  59. package/assets/skills/issue-creation/SKILL.md +223 -0
  60. package/assets/skills/jira-epic/SKILL.md +306 -0
  61. package/assets/skills/jira-task/SKILL.md +382 -0
  62. package/assets/skills/judgment-day/SKILL.md +52 -0
  63. package/assets/skills/judgment-day/references/prompts-and-formats.md +98 -0
  64. package/assets/skills/lightsail-deploy/SKILL.md +44 -0
  65. package/assets/skills/lightsail-deploy/references/runbook.md +102 -0
  66. package/assets/skills/loop-back-debug/SKILL.md +102 -0
  67. package/assets/skills/mantine-form/SKILL.md +57 -0
  68. package/assets/skills/mongoose/SKILL.md +66 -0
  69. package/assets/skills/nextjs-15/SKILL.md +144 -0
  70. package/assets/skills/not-boring-mobile/SKILL.md +43 -0
  71. package/assets/skills/not-boring-mobile/references/not-boring-playbook.md +65 -0
  72. package/assets/skills/playwright/SKILL.md +315 -0
  73. package/assets/skills/pr-comments/SKILL.md +93 -0
  74. package/assets/skills/pr-create/SKILL.md +64 -0
  75. package/assets/skills/promo-video/SKILL.md +52 -0
  76. package/assets/skills/promo-video/assets/package.template.json +22 -0
  77. package/assets/skills/promo-video/assets/promo.template.tsx +469 -0
  78. package/assets/skills/promo-video/assets/theme.template.ts +27 -0
  79. package/assets/skills/promo-video/references/pipeline.md +119 -0
  80. package/assets/skills/promo-video-web/SKILL.md +51 -0
  81. package/assets/skills/promo-video-web/assets/browser-promo.template.tsx +385 -0
  82. package/assets/skills/promo-video-web/assets/capture.template.ts +70 -0
  83. package/assets/skills/promo-video-web/assets/package.template.json +26 -0
  84. package/assets/skills/promo-video-web/references/pipeline.md +84 -0
  85. package/assets/skills/pytest/SKILL.md +180 -0
  86. package/assets/skills/react-19/SKILL.md +118 -0
  87. package/assets/skills/react-hook-form/SKILL.md +59 -0
  88. package/assets/skills/react-router/SKILL.md +60 -0
  89. package/assets/skills/redux-toolkit/SKILL.md +60 -0
  90. package/assets/skills/review-diff/SKILL.md +101 -0
  91. package/assets/skills/ship-docs/SKILL.md +45 -0
  92. package/assets/skills/ship-docs/references/ship-docs-playbook.md +31 -0
  93. package/assets/skills/skill-creator/SKILL.md +97 -0
  94. package/assets/skills/skill-creator/assets/SKILL-TEMPLATE.md +68 -0
  95. package/assets/skills/skill-creator/references/skill-style-guide.md +79 -0
  96. package/assets/skills/skill-improver/SKILL.md +50 -0
  97. package/assets/skills/skill-improver/references/skill-style-guide.md +79 -0
  98. package/assets/skills/socketio/SKILL.md +58 -0
  99. package/assets/skills/spec-bootstrap/SKILL.md +62 -0
  100. package/assets/skills/store-ship/SKILL.md +52 -0
  101. package/assets/skills/store-ship/assets/android-supply.template.md +22 -0
  102. package/assets/skills/store-ship/assets/eas.template.json +32 -0
  103. package/assets/skills/store-ship/assets/maestro-flow.template.yaml +27 -0
  104. package/assets/skills/store-ship/assets/store.config.template.json +28 -0
  105. package/assets/skills/store-ship/references/pipeline.md +198 -0
  106. package/assets/skills/stripe/SKILL.md +83 -0
  107. package/assets/skills/tailwind-4/SKILL.md +193 -0
  108. package/assets/skills/tamagui/SKILL.md +60 -0
  109. package/assets/skills/tanstack-query/SKILL.md +58 -0
  110. package/assets/skills/ticket-intake/SKILL.md +55 -0
  111. package/assets/skills/typescript/SKILL.md +134 -0
  112. package/assets/skills/verify-before-done/SKILL.md +111 -0
  113. package/assets/skills/webapp-rebuilder/SKILL.md +48 -0
  114. package/assets/skills/webapp-rebuilder/assets/charter-template.md +46 -0
  115. package/assets/skills/webapp-rebuilder/references/workflow.md +47 -0
  116. package/assets/skills/winston-logging/SKILL.md +61 -0
  117. package/assets/skills/work-unit-commits/SKILL.md +84 -0
  118. package/assets/skills/zod-4/SKILL.md +210 -0
  119. package/assets/skills/zustand-5/SKILL.md +216 -0
  120. package/bin/argos.js +6 -0
  121. package/dist/commands/adopt.d.ts +35 -0
  122. package/dist/commands/adopt.d.ts.map +1 -0
  123. package/dist/commands/adopt.js +347 -0
  124. package/dist/commands/adopt.js.map +1 -0
  125. package/dist/commands/doctor.d.ts +20 -0
  126. package/dist/commands/doctor.d.ts.map +1 -0
  127. package/dist/commands/doctor.js +478 -0
  128. package/dist/commands/doctor.js.map +1 -0
  129. package/dist/commands/init.d.ts +32 -0
  130. package/dist/commands/init.d.ts.map +1 -0
  131. package/dist/commands/init.js +358 -0
  132. package/dist/commands/init.js.map +1 -0
  133. package/dist/commands/remove.d.ts +62 -0
  134. package/dist/commands/remove.d.ts.map +1 -0
  135. package/dist/commands/remove.js +487 -0
  136. package/dist/commands/remove.js.map +1 -0
  137. package/dist/commands/workspace.d.ts +73 -0
  138. package/dist/commands/workspace.d.ts.map +1 -0
  139. package/dist/commands/workspace.js +354 -0
  140. package/dist/commands/workspace.js.map +1 -0
  141. package/dist/index.d.ts +2 -0
  142. package/dist/index.d.ts.map +1 -0
  143. package/dist/index.js +24 -0
  144. package/dist/index.js.map +1 -0
  145. package/dist/lib/assets.d.ts +29 -0
  146. package/dist/lib/assets.d.ts.map +1 -0
  147. package/dist/lib/assets.js +57 -0
  148. package/dist/lib/assets.js.map +1 -0
  149. package/dist/lib/atomic-write.d.ts +17 -0
  150. package/dist/lib/atomic-write.d.ts.map +1 -0
  151. package/dist/lib/atomic-write.js +41 -0
  152. package/dist/lib/atomic-write.js.map +1 -0
  153. package/dist/lib/backup.d.ts +11 -0
  154. package/dist/lib/backup.d.ts.map +1 -0
  155. package/dist/lib/backup.js +42 -0
  156. package/dist/lib/backup.js.map +1 -0
  157. package/dist/lib/config.d.ts +36 -0
  158. package/dist/lib/config.d.ts.map +1 -0
  159. package/dist/lib/config.js +52 -0
  160. package/dist/lib/config.js.map +1 -0
  161. package/dist/lib/detect.d.ts +58 -0
  162. package/dist/lib/detect.d.ts.map +1 -0
  163. package/dist/lib/detect.js +330 -0
  164. package/dist/lib/detect.js.map +1 -0
  165. package/dist/lib/ficha.d.ts +10 -0
  166. package/dist/lib/ficha.d.ts.map +1 -0
  167. package/dist/lib/ficha.js +38 -0
  168. package/dist/lib/ficha.js.map +1 -0
  169. package/dist/lib/git.d.ts +27 -0
  170. package/dist/lib/git.d.ts.map +1 -0
  171. package/dist/lib/git.js +79 -0
  172. package/dist/lib/git.js.map +1 -0
  173. package/dist/lib/managed-files.d.ts +35 -0
  174. package/dist/lib/managed-files.d.ts.map +1 -0
  175. package/dist/lib/managed-files.js +97 -0
  176. package/dist/lib/managed-files.js.map +1 -0
  177. package/dist/lib/markers.d.ts +64 -0
  178. package/dist/lib/markers.d.ts.map +1 -0
  179. package/dist/lib/markers.js +157 -0
  180. package/dist/lib/markers.js.map +1 -0
  181. package/dist/lib/navori-import.d.ts +42 -0
  182. package/dist/lib/navori-import.d.ts.map +1 -0
  183. package/dist/lib/navori-import.js +65 -0
  184. package/dist/lib/navori-import.js.map +1 -0
  185. package/dist/lib/openclaw-agents.d.ts +53 -0
  186. package/dist/lib/openclaw-agents.d.ts.map +1 -0
  187. package/dist/lib/openclaw-agents.js +118 -0
  188. package/dist/lib/openclaw-agents.js.map +1 -0
  189. package/dist/lib/package-root.d.ts +11 -0
  190. package/dist/lib/package-root.d.ts.map +1 -0
  191. package/dist/lib/package-root.js +23 -0
  192. package/dist/lib/package-root.js.map +1 -0
  193. package/dist/lib/paths.d.ts +11 -0
  194. package/dist/lib/paths.d.ts.map +1 -0
  195. package/dist/lib/paths.js +16 -0
  196. package/dist/lib/paths.js.map +1 -0
  197. package/dist/lib/settings-merge.d.ts +125 -0
  198. package/dist/lib/settings-merge.d.ts.map +1 -0
  199. package/dist/lib/settings-merge.js +373 -0
  200. package/dist/lib/settings-merge.js.map +1 -0
  201. package/dist/lib/version.d.ts +3 -0
  202. package/dist/lib/version.d.ts.map +1 -0
  203. package/dist/lib/version.js +13 -0
  204. package/dist/lib/version.js.map +1 -0
  205. package/dist/lib/which.d.ts +8 -0
  206. package/dist/lib/which.d.ts.map +1 -0
  207. package/dist/lib/which.js +30 -0
  208. package/dist/lib/which.js.map +1 -0
  209. package/dist/lib/workspaces.d.ts +133 -0
  210. package/dist/lib/workspaces.d.ts.map +1 -0
  211. package/dist/lib/workspaces.js +241 -0
  212. package/dist/lib/workspaces.js.map +1 -0
  213. package/dist/lib/zod-messages.d.ts +4 -0
  214. package/dist/lib/zod-messages.d.ts.map +1 -0
  215. package/dist/lib/zod-messages.js +28 -0
  216. package/dist/lib/zod-messages.js.map +1 -0
  217. package/package.json +44 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/lib/git.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,iFAAiF;IACjF,UAAU,EAAE,OAAO,CAAC;CACrB;AAMD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAUxD;AAED,sDAAsD;AACtD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED,yEAAyE;AACzE,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAexE"}
@@ -0,0 +1,79 @@
1
+ import { execFileSync } from "node:child_process";
2
+ function isSpawnEnoent(err) {
3
+ return typeof err === "object" && err !== null && err.code === "ENOENT";
4
+ }
5
+ /**
6
+ * Check whether `cwd` sits inside a git working tree, distinguishing a real
7
+ * infra problem (the `git` binary itself is missing) from the normal "not a
8
+ * repo" case — collapsing both into one message hides a broken environment
9
+ * behind what looks like an ordinary state.
10
+ */ export function checkGitRepo(cwd) {
11
+ try {
12
+ execFileSync("git", [
13
+ "rev-parse",
14
+ "--is-inside-work-tree"
15
+ ], {
16
+ cwd,
17
+ stdio: [
18
+ "ignore",
19
+ "pipe",
20
+ "ignore"
21
+ ]
22
+ });
23
+ return {
24
+ isRepo: true,
25
+ gitMissing: false
26
+ };
27
+ } catch (err) {
28
+ return {
29
+ isRepo: false,
30
+ gitMissing: isSpawnEnoent(err)
31
+ };
32
+ }
33
+ }
34
+ /** True when `cwd` sits inside a git working tree. */ export function isGitRepo(cwd) {
35
+ return checkGitRepo(cwd).isRepo;
36
+ }
37
+ /** `git remote get-url origin`, or null when there is no such remote. */ export function getRemoteOriginUrl(cwd) {
38
+ try {
39
+ return execFileSync("git", [
40
+ "remote",
41
+ "get-url",
42
+ "origin"
43
+ ], {
44
+ cwd,
45
+ stdio: [
46
+ "ignore",
47
+ "pipe",
48
+ "ignore"
49
+ ],
50
+ encoding: "utf-8"
51
+ }).trim();
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+ /**
57
+ * Derive a short identity token from a git remote URL, e.g.
58
+ * `https://github.com/bonum/repo.git` or `git@github.com:bonum/repo.git`
59
+ * both become `github.com-bonum` (host + first path segment, which is the
60
+ * org/user account on every common host — GitHub, GitLab, Bitbucket).
61
+ *
62
+ * Returns null when the URL doesn't match either the scp-like SSH form
63
+ * (`user@host:path`) or a URL form (`scheme://host/path`).
64
+ */ export function parseIdentityFromRemote(remoteUrl) {
65
+ // scp-like SSH form: git@host:org/repo(.git)?
66
+ const scpMatch = /^[^@/]+@([^:/]+):([^/]+)\//.exec(remoteUrl);
67
+ if (scpMatch) return `${scpMatch[1]}-${scpMatch[2]}`;
68
+ // URL form: ssh://git@host/org/repo(.git)?, https://host/org/repo(.git)?
69
+ try {
70
+ const url = new URL(remoteUrl);
71
+ const segments = url.pathname.split("/").filter(Boolean);
72
+ if (segments.length >= 1 && url.hostname) return `${url.hostname}-${segments[0]}`;
73
+ } catch {
74
+ // not a parseable URL — give up
75
+ }
76
+ return null;
77
+ }
78
+
79
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/git.ts"],"sourcesContent":["import { execFileSync } from \"node:child_process\";\n\nexport interface GitCheckResult {\n isRepo: boolean;\n /** True when `git` itself could not be spawned (not installed / not on PATH). */\n gitMissing: boolean;\n}\n\nfunction isSpawnEnoent(err: unknown): boolean {\n return typeof err === \"object\" && err !== null && (err as { code?: unknown }).code === \"ENOENT\";\n}\n\n/**\n * Check whether `cwd` sits inside a git working tree, distinguishing a real\n * infra problem (the `git` binary itself is missing) from the normal \"not a\n * repo\" case — collapsing both into one message hides a broken environment\n * behind what looks like an ordinary state.\n */\nexport function checkGitRepo(cwd: string): GitCheckResult {\n try {\n execFileSync(\"git\", [\"rev-parse\", \"--is-inside-work-tree\"], {\n cwd,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n });\n return { isRepo: true, gitMissing: false };\n } catch (err) {\n return { isRepo: false, gitMissing: isSpawnEnoent(err) };\n }\n}\n\n/** True when `cwd` sits inside a git working tree. */\nexport function isGitRepo(cwd: string): boolean {\n return checkGitRepo(cwd).isRepo;\n}\n\n/** `git remote get-url origin`, or null when there is no such remote. */\nexport function getRemoteOriginUrl(cwd: string): string | null {\n try {\n return execFileSync(\"git\", [\"remote\", \"get-url\", \"origin\"], {\n cwd,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n encoding: \"utf-8\",\n }).trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Derive a short identity token from a git remote URL, e.g.\n * `https://github.com/bonum/repo.git` or `git@github.com:bonum/repo.git`\n * both become `github.com-bonum` (host + first path segment, which is the\n * org/user account on every common host — GitHub, GitLab, Bitbucket).\n *\n * Returns null when the URL doesn't match either the scp-like SSH form\n * (`user@host:path`) or a URL form (`scheme://host/path`).\n */\nexport function parseIdentityFromRemote(remoteUrl: string): string | null {\n // scp-like SSH form: git@host:org/repo(.git)?\n const scpMatch = /^[^@/]+@([^:/]+):([^/]+)\\//.exec(remoteUrl);\n if (scpMatch) return `${scpMatch[1]}-${scpMatch[2]}`;\n\n // URL form: ssh://git@host/org/repo(.git)?, https://host/org/repo(.git)?\n try {\n const url = new URL(remoteUrl);\n const segments = url.pathname.split(\"/\").filter(Boolean);\n if (segments.length >= 1 && url.hostname) return `${url.hostname}-${segments[0]}`;\n } catch {\n // not a parseable URL — give up\n }\n\n return null;\n}\n"],"names":["execFileSync","isSpawnEnoent","err","code","checkGitRepo","cwd","stdio","isRepo","gitMissing","isGitRepo","getRemoteOriginUrl","encoding","trim","parseIdentityFromRemote","remoteUrl","scpMatch","exec","url","URL","segments","pathname","split","filter","Boolean","length","hostname"],"mappings":"AAAA,SAASA,YAAY,QAAQ,qBAAqB;AAQlD,SAASC,cAAcC,GAAY;IACjC,OAAO,OAAOA,QAAQ,YAAYA,QAAQ,QAAQ,AAACA,IAA2BC,IAAI,KAAK;AACzF;AAEA;;;;;CAKC,GACD,OAAO,SAASC,aAAaC,GAAW;IACtC,IAAI;QACFL,aAAa,OAAO;YAAC;YAAa;SAAwB,EAAE;YAC1DK;YACAC,OAAO;gBAAC;gBAAU;gBAAQ;aAAS;QACrC;QACA,OAAO;YAAEC,QAAQ;YAAMC,YAAY;QAAM;IAC3C,EAAE,OAAON,KAAK;QACZ,OAAO;YAAEK,QAAQ;YAAOC,YAAYP,cAAcC;QAAK;IACzD;AACF;AAEA,oDAAoD,GACpD,OAAO,SAASO,UAAUJ,GAAW;IACnC,OAAOD,aAAaC,KAAKE,MAAM;AACjC;AAEA,uEAAuE,GACvE,OAAO,SAASG,mBAAmBL,GAAW;IAC5C,IAAI;QACF,OAAOL,aAAa,OAAO;YAAC;YAAU;YAAW;SAAS,EAAE;YAC1DK;YACAC,OAAO;gBAAC;gBAAU;gBAAQ;aAAS;YACnCK,UAAU;QACZ,GAAGC,IAAI;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASC,wBAAwBC,SAAiB;IACvD,8CAA8C;IAC9C,MAAMC,WAAW,6BAA6BC,IAAI,CAACF;IACnD,IAAIC,UAAU,OAAO,GAAGA,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAEA,QAAQ,CAAC,EAAE,EAAE;IAEpD,yEAAyE;IACzE,IAAI;QACF,MAAME,MAAM,IAAIC,IAAIJ;QACpB,MAAMK,WAAWF,IAAIG,QAAQ,CAACC,KAAK,CAAC,KAAKC,MAAM,CAACC;QAChD,IAAIJ,SAASK,MAAM,IAAI,KAAKP,IAAIQ,QAAQ,EAAE,OAAO,GAAGR,IAAIQ,QAAQ,CAAC,CAAC,EAAEN,QAAQ,CAAC,EAAE,EAAE;IACnF,EAAE,OAAM;IACN,gCAAgC;IAClC;IAEA,OAAO;AACT"}
@@ -0,0 +1,35 @@
1
+ export declare const SHELL_VERSION_PLACEHOLDER = "__ARGOS_VERSION__";
2
+ /** True when `content` carries an `argos:file` ownership marker (any version). */
3
+ export declare function hasArgosFileMarker(content: string): boolean;
4
+ /**
5
+ * Insert the `argos:file` marker immediately after the leading YAML
6
+ * frontmatter block, so the frontmatter stays the very first thing in the
7
+ * file (required by tools that parse it, e.g. Claude Code agents/skills).
8
+ * Falls back to prefixing the marker when there is no frontmatter.
9
+ */
10
+ export declare function withFileMarker(content: string, version: string): string;
11
+ export type FileStatus = "created" | "updated" | "unchanged" | "skipped-foreign";
12
+ /**
13
+ * Write a full-file Argos-owned asset (agent, skill, output-style) to
14
+ * `destPath`, honoring the ownership marker policy:
15
+ * - absent → write it (created)
16
+ * - present with the argos:file marker → overwrite (updated/unchanged)
17
+ * - present without the marker (foreign) → never touch it (skipped-foreign)
18
+ */
19
+ export declare function writeManagedFile(destPath: string, sourceContent: string, version: string): FileStatus;
20
+ /** True when `content` carries a shell-comment `# argos:file` ownership marker (any version). */
21
+ export declare function hasArgosShellFileMarker(content: string): boolean;
22
+ /**
23
+ * Write a full-file Argos-owned shell script (a hook) to `destPath`.
24
+ * `sourceContent` must already contain the literal
25
+ * `# argos:file v="__ARGOS_VERSION__"` marker line as the first line after
26
+ * the shebang (see assets/hooks/*.sh) — this stamps it with the real
27
+ * `version` (simple placeholder replace, no structural splicing needed since
28
+ * the marker line is already in the right place in the source) and chmods
29
+ * the result executable (0o755). Same ownership policy as `writeManagedFile`:
30
+ * - absent → write it (created)
31
+ * - present with the shell marker → overwrite (updated/unchanged)
32
+ * - present without the marker (foreign) → never touch it (skipped-foreign)
33
+ */
34
+ export declare function writeManagedShellFile(destPath: string, sourceContent: string, version: string): FileStatus;
35
+ //# sourceMappingURL=managed-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managed-files.d.ts","sourceRoot":"","sources":["../../src/lib/managed-files.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,yBAAyB,sBAAsB,CAAC;AAM7D,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAOvE;AAED,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,iBAAiB,CAAC;AAEjF;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAerG;AAED,iGAAiG;AACjG,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CA6B1G"}
@@ -0,0 +1,97 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { writeFileAtomic } from "./atomic-write.js";
4
+ const MARKER_PREFIX = '<!-- argos:file v="';
5
+ const FRONTMATTER_RE = /^---\n[\s\S]*?\n---\n/;
6
+ // Shell assets (hooks) can't carry the HTML-comment marker above (`.sh` has
7
+ // no such syntax) or YAML frontmatter, so they get their own shell-comment
8
+ // marker form: `# argos:file v="<version>"`, hardcoded as the literal first
9
+ // line after the shebang in the asset source (see assets/hooks/*.sh) with a
10
+ // `SHELL_VERSION_PLACEHOLDER` token in place of the version — stamped with
11
+ // the real version at install time by `writeManagedShellFile`.
12
+ const SHELL_MARKER_PREFIX = '# argos:file v="';
13
+ export const SHELL_VERSION_PLACEHOLDER = "__ARGOS_VERSION__";
14
+ function fileMarker(version) {
15
+ return `${MARKER_PREFIX}${version}" -->`;
16
+ }
17
+ /** True when `content` carries an `argos:file` ownership marker (any version). */ export function hasArgosFileMarker(content) {
18
+ return content.includes(MARKER_PREFIX);
19
+ }
20
+ /**
21
+ * Insert the `argos:file` marker immediately after the leading YAML
22
+ * frontmatter block, so the frontmatter stays the very first thing in the
23
+ * file (required by tools that parse it, e.g. Claude Code agents/skills).
24
+ * Falls back to prefixing the marker when there is no frontmatter.
25
+ */ export function withFileMarker(content, version) {
26
+ const marker = fileMarker(version);
27
+ const fmMatch = FRONTMATTER_RE.exec(content);
28
+ if (!fmMatch) return `${marker}\n\n${content}`;
29
+ const frontmatter = fmMatch[0];
30
+ const rest = content.slice(frontmatter.length);
31
+ return `${frontmatter}${marker}\n${rest}`;
32
+ }
33
+ /**
34
+ * Write a full-file Argos-owned asset (agent, skill, output-style) to
35
+ * `destPath`, honoring the ownership marker policy:
36
+ * - absent → write it (created)
37
+ * - present with the argos:file marker → overwrite (updated/unchanged)
38
+ * - present without the marker (foreign) → never touch it (skipped-foreign)
39
+ */ export function writeManagedFile(destPath, sourceContent, version) {
40
+ const finalContent = withFileMarker(sourceContent, version);
41
+ if (!existsSync(destPath)) {
42
+ mkdirSync(dirname(destPath), {
43
+ recursive: true
44
+ });
45
+ writeFileAtomic(destPath, finalContent);
46
+ return "created";
47
+ }
48
+ const current = readFileSync(destPath, "utf-8");
49
+ if (!hasArgosFileMarker(current)) return "skipped-foreign";
50
+ if (current === finalContent) return "unchanged";
51
+ writeFileAtomic(destPath, finalContent);
52
+ return "updated";
53
+ }
54
+ /** True when `content` carries a shell-comment `# argos:file` ownership marker (any version). */ export function hasArgosShellFileMarker(content) {
55
+ return content.includes(SHELL_MARKER_PREFIX);
56
+ }
57
+ /**
58
+ * Write a full-file Argos-owned shell script (a hook) to `destPath`.
59
+ * `sourceContent` must already contain the literal
60
+ * `# argos:file v="__ARGOS_VERSION__"` marker line as the first line after
61
+ * the shebang (see assets/hooks/*.sh) — this stamps it with the real
62
+ * `version` (simple placeholder replace, no structural splicing needed since
63
+ * the marker line is already in the right place in the source) and chmods
64
+ * the result executable (0o755). Same ownership policy as `writeManagedFile`:
65
+ * - absent → write it (created)
66
+ * - present with the shell marker → overwrite (updated/unchanged)
67
+ * - present without the marker (foreign) → never touch it (skipped-foreign)
68
+ */ export function writeManagedShellFile(destPath, sourceContent, version) {
69
+ const finalContent = sourceContent.replaceAll(SHELL_VERSION_PLACEHOLDER, version);
70
+ if (!existsSync(destPath)) {
71
+ mkdirSync(dirname(destPath), {
72
+ recursive: true
73
+ });
74
+ writeFileAtomic(destPath, finalContent, 0o755);
75
+ return "created";
76
+ }
77
+ const current = readFileSync(destPath, "utf-8");
78
+ if (!hasArgosShellFileMarker(current)) return "skipped-foreign";
79
+ if (current === finalContent) {
80
+ // Re-assert the executable bit even when content is unchanged, in case
81
+ // it drifted (e.g. a user ran `chmod -x` on it by hand). Best-effort:
82
+ // hooks are invoked as `bash "<path>"` (see lib/settings-merge.ts), so
83
+ // the executable bit is inert for Claude Code's own purposes — a chmod
84
+ // failure here (e.g. EPERM on an unusual filesystem) shouldn't fail the
85
+ // whole `argos init` run over a cosmetic permission bit.
86
+ try {
87
+ chmodSync(destPath, 0o755);
88
+ } catch {
89
+ // Swallowed on purpose — see comment above.
90
+ }
91
+ return "unchanged";
92
+ }
93
+ writeFileAtomic(destPath, finalContent, 0o755);
94
+ return "updated";
95
+ }
96
+
97
+ //# sourceMappingURL=managed-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/managed-files.ts"],"sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { writeFileAtomic } from \"./atomic-write.js\";\n\nconst MARKER_PREFIX = '<!-- argos:file v=\"';\nconst FRONTMATTER_RE = /^---\\n[\\s\\S]*?\\n---\\n/;\n\n// Shell assets (hooks) can't carry the HTML-comment marker above (`.sh` has\n// no such syntax) or YAML frontmatter, so they get their own shell-comment\n// marker form: `# argos:file v=\"<version>\"`, hardcoded as the literal first\n// line after the shebang in the asset source (see assets/hooks/*.sh) with a\n// `SHELL_VERSION_PLACEHOLDER` token in place of the version — stamped with\n// the real version at install time by `writeManagedShellFile`.\nconst SHELL_MARKER_PREFIX = '# argos:file v=\"';\nexport const SHELL_VERSION_PLACEHOLDER = \"__ARGOS_VERSION__\";\n\nfunction fileMarker(version: string): string {\n return `${MARKER_PREFIX}${version}\" -->`;\n}\n\n/** True when `content` carries an `argos:file` ownership marker (any version). */\nexport function hasArgosFileMarker(content: string): boolean {\n return content.includes(MARKER_PREFIX);\n}\n\n/**\n * Insert the `argos:file` marker immediately after the leading YAML\n * frontmatter block, so the frontmatter stays the very first thing in the\n * file (required by tools that parse it, e.g. Claude Code agents/skills).\n * Falls back to prefixing the marker when there is no frontmatter.\n */\nexport function withFileMarker(content: string, version: string): string {\n const marker = fileMarker(version);\n const fmMatch = FRONTMATTER_RE.exec(content);\n if (!fmMatch) return `${marker}\\n\\n${content}`;\n const frontmatter = fmMatch[0];\n const rest = content.slice(frontmatter.length);\n return `${frontmatter}${marker}\\n${rest}`;\n}\n\nexport type FileStatus = \"created\" | \"updated\" | \"unchanged\" | \"skipped-foreign\";\n\n/**\n * Write a full-file Argos-owned asset (agent, skill, output-style) to\n * `destPath`, honoring the ownership marker policy:\n * - absent → write it (created)\n * - present with the argos:file marker → overwrite (updated/unchanged)\n * - present without the marker (foreign) → never touch it (skipped-foreign)\n */\nexport function writeManagedFile(destPath: string, sourceContent: string, version: string): FileStatus {\n const finalContent = withFileMarker(sourceContent, version);\n\n if (!existsSync(destPath)) {\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileAtomic(destPath, finalContent);\n return \"created\";\n }\n\n const current = readFileSync(destPath, \"utf-8\");\n if (!hasArgosFileMarker(current)) return \"skipped-foreign\";\n if (current === finalContent) return \"unchanged\";\n\n writeFileAtomic(destPath, finalContent);\n return \"updated\";\n}\n\n/** True when `content` carries a shell-comment `# argos:file` ownership marker (any version). */\nexport function hasArgosShellFileMarker(content: string): boolean {\n return content.includes(SHELL_MARKER_PREFIX);\n}\n\n/**\n * Write a full-file Argos-owned shell script (a hook) to `destPath`.\n * `sourceContent` must already contain the literal\n * `# argos:file v=\"__ARGOS_VERSION__\"` marker line as the first line after\n * the shebang (see assets/hooks/*.sh) — this stamps it with the real\n * `version` (simple placeholder replace, no structural splicing needed since\n * the marker line is already in the right place in the source) and chmods\n * the result executable (0o755). Same ownership policy as `writeManagedFile`:\n * - absent → write it (created)\n * - present with the shell marker → overwrite (updated/unchanged)\n * - present without the marker (foreign) → never touch it (skipped-foreign)\n */\nexport function writeManagedShellFile(destPath: string, sourceContent: string, version: string): FileStatus {\n const finalContent = sourceContent.replaceAll(SHELL_VERSION_PLACEHOLDER, version);\n\n if (!existsSync(destPath)) {\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileAtomic(destPath, finalContent, 0o755);\n return \"created\";\n }\n\n const current = readFileSync(destPath, \"utf-8\");\n if (!hasArgosShellFileMarker(current)) return \"skipped-foreign\";\n\n if (current === finalContent) {\n // Re-assert the executable bit even when content is unchanged, in case\n // it drifted (e.g. a user ran `chmod -x` on it by hand). Best-effort:\n // hooks are invoked as `bash \"<path>\"` (see lib/settings-merge.ts), so\n // the executable bit is inert for Claude Code's own purposes — a chmod\n // failure here (e.g. EPERM on an unusual filesystem) shouldn't fail the\n // whole `argos init` run over a cosmetic permission bit.\n try {\n chmodSync(destPath, 0o755);\n } catch {\n // Swallowed on purpose — see comment above.\n }\n return \"unchanged\";\n }\n\n writeFileAtomic(destPath, finalContent, 0o755);\n return \"updated\";\n}\n"],"names":["chmodSync","existsSync","mkdirSync","readFileSync","dirname","writeFileAtomic","MARKER_PREFIX","FRONTMATTER_RE","SHELL_MARKER_PREFIX","SHELL_VERSION_PLACEHOLDER","fileMarker","version","hasArgosFileMarker","content","includes","withFileMarker","marker","fmMatch","exec","frontmatter","rest","slice","length","writeManagedFile","destPath","sourceContent","finalContent","recursive","current","hasArgosShellFileMarker","writeManagedShellFile","replaceAll"],"mappings":"AAAA,SAASA,SAAS,EAAEC,UAAU,EAAEC,SAAS,EAAEC,YAAY,QAAQ,UAAU;AACzE,SAASC,OAAO,QAAQ,YAAY;AACpC,SAASC,eAAe,QAAQ,oBAAoB;AAEpD,MAAMC,gBAAgB;AACtB,MAAMC,iBAAiB;AAEvB,4EAA4E;AAC5E,2EAA2E;AAC3E,4EAA4E;AAC5E,4EAA4E;AAC5E,2EAA2E;AAC3E,+DAA+D;AAC/D,MAAMC,sBAAsB;AAC5B,OAAO,MAAMC,4BAA4B,oBAAoB;AAE7D,SAASC,WAAWC,OAAe;IACjC,OAAO,GAAGL,gBAAgBK,QAAQ,KAAK,CAAC;AAC1C;AAEA,gFAAgF,GAChF,OAAO,SAASC,mBAAmBC,OAAe;IAChD,OAAOA,QAAQC,QAAQ,CAACR;AAC1B;AAEA;;;;;CAKC,GACD,OAAO,SAASS,eAAeF,OAAe,EAAEF,OAAe;IAC7D,MAAMK,SAASN,WAAWC;IAC1B,MAAMM,UAAUV,eAAeW,IAAI,CAACL;IACpC,IAAI,CAACI,SAAS,OAAO,GAAGD,OAAO,IAAI,EAAEH,SAAS;IAC9C,MAAMM,cAAcF,OAAO,CAAC,EAAE;IAC9B,MAAMG,OAAOP,QAAQQ,KAAK,CAACF,YAAYG,MAAM;IAC7C,OAAO,GAAGH,cAAcH,OAAO,EAAE,EAAEI,MAAM;AAC3C;AAIA;;;;;;CAMC,GACD,OAAO,SAASG,iBAAiBC,QAAgB,EAAEC,aAAqB,EAAEd,OAAe;IACvF,MAAMe,eAAeX,eAAeU,eAAed;IAEnD,IAAI,CAACV,WAAWuB,WAAW;QACzBtB,UAAUE,QAAQoB,WAAW;YAAEG,WAAW;QAAK;QAC/CtB,gBAAgBmB,UAAUE;QAC1B,OAAO;IACT;IAEA,MAAME,UAAUzB,aAAaqB,UAAU;IACvC,IAAI,CAACZ,mBAAmBgB,UAAU,OAAO;IACzC,IAAIA,YAAYF,cAAc,OAAO;IAErCrB,gBAAgBmB,UAAUE;IAC1B,OAAO;AACT;AAEA,+FAA+F,GAC/F,OAAO,SAASG,wBAAwBhB,OAAe;IACrD,OAAOA,QAAQC,QAAQ,CAACN;AAC1B;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASsB,sBAAsBN,QAAgB,EAAEC,aAAqB,EAAEd,OAAe;IAC5F,MAAMe,eAAeD,cAAcM,UAAU,CAACtB,2BAA2BE;IAEzE,IAAI,CAACV,WAAWuB,WAAW;QACzBtB,UAAUE,QAAQoB,WAAW;YAAEG,WAAW;QAAK;QAC/CtB,gBAAgBmB,UAAUE,cAAc;QACxC,OAAO;IACT;IAEA,MAAME,UAAUzB,aAAaqB,UAAU;IACvC,IAAI,CAACK,wBAAwBD,UAAU,OAAO;IAE9C,IAAIA,YAAYF,cAAc;QAC5B,uEAAuE;QACvE,sEAAsE;QACtE,uEAAuE;QACvE,uEAAuE;QACvE,wEAAwE;QACxE,yDAAyD;QACzD,IAAI;YACF1B,UAAUwB,UAAU;QACtB,EAAE,OAAM;QACN,4CAA4C;QAC9C;QACA,OAAO;IACT;IAEAnB,gBAAgBmB,UAAUE,cAAc;IACxC,OAAO;AACT"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Argos managed-block primitives for Markdown files.
3
+ *
4
+ * Block format:
5
+ * <!-- argos:managed id="<id>" v="<version>" -->
6
+ * ...content...
7
+ * <!-- argos:managed end id="<id>" -->
8
+ *
9
+ * HARD RULE: never modify anything outside argos markers. Foreign file
10
+ * content (anything the user wrote, or another tool's own markers) is
11
+ * untouchable — every function here only reads/writes inside its own
12
+ * `id`'s block boundaries.
13
+ *
14
+ * KNOWN LIMITATION: ownership here is purely text-pattern based — anything
15
+ * that quotes the exact marker comment text (e.g. `<!-- argos:managed id="x"
16
+ * v="1" -->`) inside otherwise-foreign content is indistinguishable from a
17
+ * real Argos block and can be spoofed or accidentally matched. This is a
18
+ * deliberate tradeoff for staying dependency-free; the mitigation is that
19
+ * every mutation site backs up the file before touching it (see
20
+ * `createBackup` usage in `init.ts`/`adopt.ts`), so a bad match is always
21
+ * recoverable.
22
+ */
23
+ export interface InjectBlockResult {
24
+ content: string;
25
+ /** Count of duplicate blocks (beyond the first) removed by this call. */
26
+ healedDuplicates: number;
27
+ }
28
+ /**
29
+ * Insert or update a managed block by id, self-healing duplicate blocks left
30
+ * behind by crash residue (e.g. a process killed mid-write that appended a
31
+ * second block instead of replacing the first).
32
+ * - No existing block with this id → append at the end, separated by a
33
+ * blank line from whatever precedes it.
34
+ * - One or more existing blocks with this id → replace the FIRST one's open
35
+ * marker (version) and body in place, and remove every other block sharing
36
+ * the id; everything else before/after is untouched.
37
+ */
38
+ export declare function injectBlockDetailed(fileContent: string, id: string, version: string, content: string): InjectBlockResult;
39
+ /** String-only convenience wrapper over `injectBlockDetailed` for existing call sites. */
40
+ export declare function injectBlock(fileContent: string, id: string, version: string, content: string): string;
41
+ /**
42
+ * Remove a managed block by id. No-op if the block does not exist.
43
+ * Also removes a single trailing newline so removal doesn't leave a
44
+ * double-blank line behind.
45
+ */
46
+ export declare function removeBlock(fileContent: string, id: string): string;
47
+ export interface ListedBlock {
48
+ id: string;
49
+ version: string | null;
50
+ }
51
+ /** Enumerate every managed block found in `fileContent`, in document order. */
52
+ export declare function listBlocks(fileContent: string): ListedBlock[];
53
+ /**
54
+ * Ids of every block whose OPEN marker in `fileContent` has no matching
55
+ * CLOSE marker anywhere in the file — a dangling/unclosed block, usually
56
+ * crash residue or hand-edited corruption. `listBlocks` alone can't answer
57
+ * this: it only scans for open markers and doesn't verify a close exists
58
+ * (see its implementation above), which is exactly why a dangling block
59
+ * still shows up as "present" there. Callers that need to remove blocks
60
+ * (see `removeBlock`) must guard against this: a dangling id makes
61
+ * `removeBlock` a no-op forever, since there is nothing valid to cut.
62
+ */
63
+ export declare function listDanglingBlockIds(fileContent: string): string[];
64
+ //# sourceMappingURL=markers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markers.d.ts","sourceRoot":"","sources":["../../src/lib/markers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAuEH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,iBAAiB,CA2BnB;AAED,0FAA0F;AAC1F,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAErG;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAcnE;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,EAAE,CAU7D;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAOlE"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Argos managed-block primitives for Markdown files.
3
+ *
4
+ * Block format:
5
+ * <!-- argos:managed id="<id>" v="<version>" -->
6
+ * ...content...
7
+ * <!-- argos:managed end id="<id>" -->
8
+ *
9
+ * HARD RULE: never modify anything outside argos markers. Foreign file
10
+ * content (anything the user wrote, or another tool's own markers) is
11
+ * untouchable — every function here only reads/writes inside its own
12
+ * `id`'s block boundaries.
13
+ *
14
+ * KNOWN LIMITATION: ownership here is purely text-pattern based — anything
15
+ * that quotes the exact marker comment text (e.g. `<!-- argos:managed id="x"
16
+ * v="1" -->`) inside otherwise-foreign content is indistinguishable from a
17
+ * real Argos block and can be spoofed or accidentally matched. This is a
18
+ * deliberate tradeoff for staying dependency-free; the mitigation is that
19
+ * every mutation site backs up the file before touching it (see
20
+ * `createBackup` usage in `init.ts`/`adopt.ts`), so a bad match is always
21
+ * recoverable.
22
+ */ const OPEN_PREFIX = '<!-- argos:managed id="';
23
+ const CLOSE_PREFIX = '<!-- argos:managed end id="';
24
+ function escapeRegex(value) {
25
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26
+ }
27
+ function openMarker(id, version) {
28
+ return `<!-- argos:managed id="${id}" v="${version}" -->`;
29
+ }
30
+ function closeMarker(id) {
31
+ return `${CLOSE_PREFIX}${id}" -->`;
32
+ }
33
+ /** Find the first block with `id` starting at or after `fromIndex`. */ function findBlockFrom(fileContent, id, fromIndex) {
34
+ const openRegex = new RegExp(`${escapeRegex(OPEN_PREFIX)}${escapeRegex(id)}"(?:\\s+v="([^"]*)")?\\s*-->`, "g");
35
+ openRegex.lastIndex = fromIndex;
36
+ const openMatch = openRegex.exec(fileContent);
37
+ if (!openMatch) return null;
38
+ const close = closeMarker(id);
39
+ const openEnd = openMatch.index + openMatch[0].length;
40
+ const closeStart = fileContent.indexOf(close, openEnd);
41
+ if (closeStart < 0) return null;
42
+ const rawContent = fileContent.slice(openEnd, closeStart);
43
+ const content = rawContent.replace(/^\n/, "").replace(/\n$/, "");
44
+ return {
45
+ openStart: openMatch.index,
46
+ openEnd,
47
+ closeStart,
48
+ closeEnd: closeStart + close.length,
49
+ version: openMatch[1] ?? null,
50
+ content
51
+ };
52
+ }
53
+ function findBlock(fileContent, id) {
54
+ return findBlockFrom(fileContent, id, 0);
55
+ }
56
+ /** Find every block with `id`, in document order. Duplicates included. */ function findAllBlocks(fileContent, id) {
57
+ const blocks = [];
58
+ let fromIndex = 0;
59
+ for(;;){
60
+ const match = findBlockFrom(fileContent, id, fromIndex);
61
+ if (!match) break;
62
+ blocks.push(match);
63
+ fromIndex = match.closeEnd;
64
+ }
65
+ return blocks;
66
+ }
67
+ /**
68
+ * Insert or update a managed block by id, self-healing duplicate blocks left
69
+ * behind by crash residue (e.g. a process killed mid-write that appended a
70
+ * second block instead of replacing the first).
71
+ * - No existing block with this id → append at the end, separated by a
72
+ * blank line from whatever precedes it.
73
+ * - One or more existing blocks with this id → replace the FIRST one's open
74
+ * marker (version) and body in place, and remove every other block sharing
75
+ * the id; everything else before/after is untouched.
76
+ */ export function injectBlockDetailed(fileContent, id, version, content) {
77
+ const block = `${openMarker(id, version)}\n${content}\n${closeMarker(id)}`;
78
+ const matches = findAllBlocks(fileContent, id);
79
+ if (matches.length === 0) {
80
+ if (fileContent.length === 0) return {
81
+ content: `${block}\n`,
82
+ healedDuplicates: 0
83
+ };
84
+ const sep = fileContent.endsWith("\n\n") ? "" : fileContent.endsWith("\n") ? "\n" : "\n\n";
85
+ return {
86
+ content: `${fileContent}${sep}${block}\n`,
87
+ healedDuplicates: 0
88
+ };
89
+ }
90
+ const first = matches[0];
91
+ // Drop every duplicate after the first, from the end backwards so offsets
92
+ // computed against the original `fileContent` (including `first`'s, which
93
+ // always sit before every duplicate) stay valid throughout.
94
+ let healed = fileContent;
95
+ for(let i = matches.length - 1; i >= 1; i--){
96
+ const dup = matches[i];
97
+ let endCut = dup.closeEnd;
98
+ if (healed[endCut] === "\n") endCut++;
99
+ let startCut = dup.openStart;
100
+ if (healed.slice(0, startCut).endsWith("\n\n")) startCut -= 1;
101
+ healed = healed.slice(0, startCut) + healed.slice(endCut);
102
+ }
103
+ const result = healed.slice(0, first.openStart) + block + healed.slice(first.closeEnd);
104
+ return {
105
+ content: result,
106
+ healedDuplicates: matches.length - 1
107
+ };
108
+ }
109
+ /** String-only convenience wrapper over `injectBlockDetailed` for existing call sites. */ export function injectBlock(fileContent, id, version, content) {
110
+ return injectBlockDetailed(fileContent, id, version, content).content;
111
+ }
112
+ /**
113
+ * Remove a managed block by id. No-op if the block does not exist.
114
+ * Also removes a single trailing newline so removal doesn't leave a
115
+ * double-blank line behind.
116
+ */ export function removeBlock(fileContent, id) {
117
+ const match = findBlock(fileContent, id);
118
+ if (!match) return fileContent;
119
+ let endCut = match.closeEnd;
120
+ if (fileContent[endCut] === "\n") endCut++;
121
+ // Collapse the blank-line separator injectBlock adds before an appended
122
+ // block, so removing a cleanly-appended block restores the original bytes
123
+ // instead of leaving a stray blank line behind.
124
+ let startCut = match.openStart;
125
+ if (fileContent.slice(0, startCut).endsWith("\n\n")) startCut -= 1;
126
+ return fileContent.slice(0, startCut) + fileContent.slice(endCut);
127
+ }
128
+ /** Enumerate every managed block found in `fileContent`, in document order. */ export function listBlocks(fileContent) {
129
+ const openRegex = new RegExp(`${escapeRegex(OPEN_PREFIX)}([^"]+)"(?:\\s+v="([^"]*)")?\\s*-->`, "g");
130
+ const blocks = [];
131
+ for (const m of fileContent.matchAll(openRegex)){
132
+ blocks.push({
133
+ id: m[1],
134
+ version: m[2] ?? null
135
+ });
136
+ }
137
+ return blocks;
138
+ }
139
+ /**
140
+ * Ids of every block whose OPEN marker in `fileContent` has no matching
141
+ * CLOSE marker anywhere in the file — a dangling/unclosed block, usually
142
+ * crash residue or hand-edited corruption. `listBlocks` alone can't answer
143
+ * this: it only scans for open markers and doesn't verify a close exists
144
+ * (see its implementation above), which is exactly why a dangling block
145
+ * still shows up as "present" there. Callers that need to remove blocks
146
+ * (see `removeBlock`) must guard against this: a dangling id makes
147
+ * `removeBlock` a no-op forever, since there is nothing valid to cut.
148
+ */ export function listDanglingBlockIds(fileContent) {
149
+ const ids = new Set(listBlocks(fileContent).map((b)=>b.id));
150
+ const dangling = [];
151
+ for (const id of ids){
152
+ if (findBlock(fileContent, id) === null) dangling.push(id);
153
+ }
154
+ return dangling;
155
+ }
156
+
157
+ //# sourceMappingURL=markers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/markers.ts"],"sourcesContent":["/**\n * Argos managed-block primitives for Markdown files.\n *\n * Block format:\n * <!-- argos:managed id=\"<id>\" v=\"<version>\" -->\n * ...content...\n * <!-- argos:managed end id=\"<id>\" -->\n *\n * HARD RULE: never modify anything outside argos markers. Foreign file\n * content (anything the user wrote, or another tool's own markers) is\n * untouchable — every function here only reads/writes inside its own\n * `id`'s block boundaries.\n *\n * KNOWN LIMITATION: ownership here is purely text-pattern based — anything\n * that quotes the exact marker comment text (e.g. `<!-- argos:managed id=\"x\"\n * v=\"1\" -->`) inside otherwise-foreign content is indistinguishable from a\n * real Argos block and can be spoofed or accidentally matched. This is a\n * deliberate tradeoff for staying dependency-free; the mitigation is that\n * every mutation site backs up the file before touching it (see\n * `createBackup` usage in `init.ts`/`adopt.ts`), so a bad match is always\n * recoverable.\n */\n\nconst OPEN_PREFIX = '<!-- argos:managed id=\"';\nconst CLOSE_PREFIX = '<!-- argos:managed end id=\"';\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction openMarker(id: string, version: string): string {\n return `<!-- argos:managed id=\"${id}\" v=\"${version}\" -->`;\n}\n\nfunction closeMarker(id: string): string {\n return `${CLOSE_PREFIX}${id}\" -->`;\n}\n\ninterface BlockMatch {\n openStart: number;\n openEnd: number;\n closeStart: number;\n closeEnd: number;\n version: string | null;\n content: string;\n}\n\n/** Find the first block with `id` starting at or after `fromIndex`. */\nfunction findBlockFrom(fileContent: string, id: string, fromIndex: number): BlockMatch | null {\n const openRegex = new RegExp(\n `${escapeRegex(OPEN_PREFIX)}${escapeRegex(id)}\"(?:\\\\s+v=\"([^\"]*)\")?\\\\s*-->`,\n \"g\",\n );\n openRegex.lastIndex = fromIndex;\n const openMatch = openRegex.exec(fileContent);\n if (!openMatch) return null;\n\n const close = closeMarker(id);\n const openEnd = openMatch.index + openMatch[0].length;\n const closeStart = fileContent.indexOf(close, openEnd);\n if (closeStart < 0) return null;\n\n const rawContent = fileContent.slice(openEnd, closeStart);\n const content = rawContent.replace(/^\\n/, \"\").replace(/\\n$/, \"\");\n\n return {\n openStart: openMatch.index,\n openEnd,\n closeStart,\n closeEnd: closeStart + close.length,\n version: openMatch[1] ?? null,\n content,\n };\n}\n\nfunction findBlock(fileContent: string, id: string): BlockMatch | null {\n return findBlockFrom(fileContent, id, 0);\n}\n\n/** Find every block with `id`, in document order. Duplicates included. */\nfunction findAllBlocks(fileContent: string, id: string): BlockMatch[] {\n const blocks: BlockMatch[] = [];\n let fromIndex = 0;\n for (;;) {\n const match = findBlockFrom(fileContent, id, fromIndex);\n if (!match) break;\n blocks.push(match);\n fromIndex = match.closeEnd;\n }\n return blocks;\n}\n\nexport interface InjectBlockResult {\n content: string;\n /** Count of duplicate blocks (beyond the first) removed by this call. */\n healedDuplicates: number;\n}\n\n/**\n * Insert or update a managed block by id, self-healing duplicate blocks left\n * behind by crash residue (e.g. a process killed mid-write that appended a\n * second block instead of replacing the first).\n * - No existing block with this id → append at the end, separated by a\n * blank line from whatever precedes it.\n * - One or more existing blocks with this id → replace the FIRST one's open\n * marker (version) and body in place, and remove every other block sharing\n * the id; everything else before/after is untouched.\n */\nexport function injectBlockDetailed(\n fileContent: string,\n id: string,\n version: string,\n content: string,\n): InjectBlockResult {\n const block = `${openMarker(id, version)}\\n${content}\\n${closeMarker(id)}`;\n const matches = findAllBlocks(fileContent, id);\n\n if (matches.length === 0) {\n if (fileContent.length === 0) return { content: `${block}\\n`, healedDuplicates: 0 };\n const sep = fileContent.endsWith(\"\\n\\n\") ? \"\" : fileContent.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n return { content: `${fileContent}${sep}${block}\\n`, healedDuplicates: 0 };\n }\n\n const first = matches[0]!;\n\n // Drop every duplicate after the first, from the end backwards so offsets\n // computed against the original `fileContent` (including `first`'s, which\n // always sit before every duplicate) stay valid throughout.\n let healed = fileContent;\n for (let i = matches.length - 1; i >= 1; i--) {\n const dup = matches[i]!;\n let endCut = dup.closeEnd;\n if (healed[endCut] === \"\\n\") endCut++;\n let startCut = dup.openStart;\n if (healed.slice(0, startCut).endsWith(\"\\n\\n\")) startCut -= 1;\n healed = healed.slice(0, startCut) + healed.slice(endCut);\n }\n\n const result = healed.slice(0, first.openStart) + block + healed.slice(first.closeEnd);\n return { content: result, healedDuplicates: matches.length - 1 };\n}\n\n/** String-only convenience wrapper over `injectBlockDetailed` for existing call sites. */\nexport function injectBlock(fileContent: string, id: string, version: string, content: string): string {\n return injectBlockDetailed(fileContent, id, version, content).content;\n}\n\n/**\n * Remove a managed block by id. No-op if the block does not exist.\n * Also removes a single trailing newline so removal doesn't leave a\n * double-blank line behind.\n */\nexport function removeBlock(fileContent: string, id: string): string {\n const match = findBlock(fileContent, id);\n if (!match) return fileContent;\n\n let endCut = match.closeEnd;\n if (fileContent[endCut] === \"\\n\") endCut++;\n\n // Collapse the blank-line separator injectBlock adds before an appended\n // block, so removing a cleanly-appended block restores the original bytes\n // instead of leaving a stray blank line behind.\n let startCut = match.openStart;\n if (fileContent.slice(0, startCut).endsWith(\"\\n\\n\")) startCut -= 1;\n\n return fileContent.slice(0, startCut) + fileContent.slice(endCut);\n}\n\nexport interface ListedBlock {\n id: string;\n version: string | null;\n}\n\n/** Enumerate every managed block found in `fileContent`, in document order. */\nexport function listBlocks(fileContent: string): ListedBlock[] {\n const openRegex = new RegExp(\n `${escapeRegex(OPEN_PREFIX)}([^\"]+)\"(?:\\\\s+v=\"([^\"]*)\")?\\\\s*-->`,\n \"g\",\n );\n const blocks: ListedBlock[] = [];\n for (const m of fileContent.matchAll(openRegex)) {\n blocks.push({ id: m[1]!, version: m[2] ?? null });\n }\n return blocks;\n}\n\n/**\n * Ids of every block whose OPEN marker in `fileContent` has no matching\n * CLOSE marker anywhere in the file — a dangling/unclosed block, usually\n * crash residue or hand-edited corruption. `listBlocks` alone can't answer\n * this: it only scans for open markers and doesn't verify a close exists\n * (see its implementation above), which is exactly why a dangling block\n * still shows up as \"present\" there. Callers that need to remove blocks\n * (see `removeBlock`) must guard against this: a dangling id makes\n * `removeBlock` a no-op forever, since there is nothing valid to cut.\n */\nexport function listDanglingBlockIds(fileContent: string): string[] {\n const ids = new Set(listBlocks(fileContent).map((b) => b.id));\n const dangling: string[] = [];\n for (const id of ids) {\n if (findBlock(fileContent, id) === null) dangling.push(id);\n }\n return dangling;\n}\n"],"names":["OPEN_PREFIX","CLOSE_PREFIX","escapeRegex","value","replace","openMarker","id","version","closeMarker","findBlockFrom","fileContent","fromIndex","openRegex","RegExp","lastIndex","openMatch","exec","close","openEnd","index","length","closeStart","indexOf","rawContent","slice","content","openStart","closeEnd","findBlock","findAllBlocks","blocks","match","push","injectBlockDetailed","block","matches","healedDuplicates","sep","endsWith","first","healed","i","dup","endCut","startCut","result","injectBlock","removeBlock","listBlocks","m","matchAll","listDanglingBlockIds","ids","Set","map","b","dangling"],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ export declare const NAVORI_CONFIG_FILENAME = "navori.config.json";
2
+ /** Refuse to parse foreign input larger than this — unsanitized host-repo content. */
3
+ export declare const NAVORI_CONFIG_MAX_BYTES = 1000000;
4
+ export interface NaviorImport {
5
+ name?: string;
6
+ qualityGate?: {
7
+ fast?: string;
8
+ full?: string;
9
+ };
10
+ project?: {
11
+ criticalAreas?: string[];
12
+ legacyPaths?: string[];
13
+ };
14
+ workspace?: string;
15
+ branchBase?: string;
16
+ prTarget?: string;
17
+ }
18
+ /**
19
+ * Discriminated read result for `navori.config.json`. Per "lo no visto se
20
+ * declara": a present-but-broken file must never collapse silently into
21
+ * "absent" — callers need to be able to tell the two apart and warn.
22
+ */
23
+ export type NaviorImportResult = {
24
+ kind: "absent";
25
+ } | {
26
+ kind: "imported";
27
+ data: NaviorImport;
28
+ } | {
29
+ kind: "unreadable";
30
+ error: string;
31
+ };
32
+ /** True when a `navori.config.json` exists in `dir` (harness coexistence). */
33
+ export declare function hasNaviorConfig(dir: string): boolean;
34
+ /**
35
+ * Best-effort import of `navori.config.json` fields Argos also models. This
36
+ * is a foreign file with its own (unvalidated) shape — read leniently, take
37
+ * only the fields we recognize, and never throw on unexpected shapes. A
38
+ * present file that can't be read/parsed/used is reported as `"unreadable"`,
39
+ * never silently treated as `"absent"`.
40
+ */
41
+ export declare function readNaviorConfig(dir: string): NaviorImportResult;
42
+ //# sourceMappingURL=navori-import.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navori-import.d.ts","sourceRoot":"","sources":["../../src/lib/navori-import.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,sBAAsB,uBAAuB,CAAC;AAE3D,sFAAsF;AACtF,eAAO,MAAM,uBAAuB,UAAY,CAAC;AAEjD,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1C,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAgDhE"}
@@ -0,0 +1,65 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ export const NAVORI_CONFIG_FILENAME = "navori.config.json";
4
+ /** Refuse to parse foreign input larger than this — unsanitized host-repo content. */ export const NAVORI_CONFIG_MAX_BYTES = 1_000_000;
5
+ /** True when a `navori.config.json` exists in `dir` (harness coexistence). */ export function hasNaviorConfig(dir) {
6
+ return existsSync(join(dir, NAVORI_CONFIG_FILENAME));
7
+ }
8
+ /**
9
+ * Best-effort import of `navori.config.json` fields Argos also models. This
10
+ * is a foreign file with its own (unvalidated) shape — read leniently, take
11
+ * only the fields we recognize, and never throw on unexpected shapes. A
12
+ * present file that can't be read/parsed/used is reported as `"unreadable"`,
13
+ * never silently treated as `"absent"`.
14
+ */ export function readNaviorConfig(dir) {
15
+ const path = join(dir, NAVORI_CONFIG_FILENAME);
16
+ if (!existsSync(path)) return {
17
+ kind: "absent"
18
+ };
19
+ try {
20
+ if (statSync(path).size > NAVORI_CONFIG_MAX_BYTES) {
21
+ return {
22
+ kind: "unreadable",
23
+ error: `navori.config.json excede el máximo permitido de ${NAVORI_CONFIG_MAX_BYTES} bytes`
24
+ };
25
+ }
26
+ const raw = JSON.parse(readFileSync(path, "utf-8"));
27
+ if (typeof raw !== "object" || raw === null) {
28
+ return {
29
+ kind: "unreadable",
30
+ error: "navori.config.json no es un objeto JSON"
31
+ };
32
+ }
33
+ const obj = raw;
34
+ const result = {};
35
+ if (typeof obj.name === "string") result.name = obj.name;
36
+ if (typeof obj.workspace === "string") result.workspace = obj.workspace;
37
+ if (typeof obj.branchBase === "string") result.branchBase = obj.branchBase;
38
+ if (typeof obj.prTarget === "string") result.prTarget = obj.prTarget;
39
+ if (typeof obj.qualityGate === "object" && obj.qualityGate !== null) {
40
+ const qg = obj.qualityGate;
41
+ result.qualityGate = {
42
+ fast: typeof qg.fast === "string" ? qg.fast : undefined,
43
+ full: typeof qg.full === "string" ? qg.full : undefined
44
+ };
45
+ }
46
+ if (typeof obj.project === "object" && obj.project !== null) {
47
+ const project = obj.project;
48
+ result.project = {
49
+ criticalAreas: Array.isArray(project.criticalAreas) ? project.criticalAreas.filter((v)=>typeof v === "string") : undefined,
50
+ legacyPaths: Array.isArray(project.legacyPaths) ? project.legacyPaths.filter((v)=>typeof v === "string") : undefined
51
+ };
52
+ }
53
+ return {
54
+ kind: "imported",
55
+ data: result
56
+ };
57
+ } catch (err) {
58
+ return {
59
+ kind: "unreadable",
60
+ error: err instanceof Error ? err.message : String(err)
61
+ };
62
+ }
63
+ }
64
+
65
+ //# sourceMappingURL=navori-import.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/navori-import.ts"],"sourcesContent":["import { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport const NAVORI_CONFIG_FILENAME = \"navori.config.json\";\n\n/** Refuse to parse foreign input larger than this — unsanitized host-repo content. */\nexport const NAVORI_CONFIG_MAX_BYTES = 1_000_000;\n\nexport interface NaviorImport {\n name?: string;\n qualityGate?: { fast?: string; full?: string };\n project?: { criticalAreas?: string[]; legacyPaths?: string[] };\n workspace?: string;\n branchBase?: string;\n prTarget?: string;\n}\n\n/**\n * Discriminated read result for `navori.config.json`. Per \"lo no visto se\n * declara\": a present-but-broken file must never collapse silently into\n * \"absent\" — callers need to be able to tell the two apart and warn.\n */\nexport type NaviorImportResult =\n | { kind: \"absent\" }\n | { kind: \"imported\"; data: NaviorImport }\n | { kind: \"unreadable\"; error: string };\n\n/** True when a `navori.config.json` exists in `dir` (harness coexistence). */\nexport function hasNaviorConfig(dir: string): boolean {\n return existsSync(join(dir, NAVORI_CONFIG_FILENAME));\n}\n\n/**\n * Best-effort import of `navori.config.json` fields Argos also models. This\n * is a foreign file with its own (unvalidated) shape — read leniently, take\n * only the fields we recognize, and never throw on unexpected shapes. A\n * present file that can't be read/parsed/used is reported as `\"unreadable\"`,\n * never silently treated as `\"absent\"`.\n */\nexport function readNaviorConfig(dir: string): NaviorImportResult {\n const path = join(dir, NAVORI_CONFIG_FILENAME);\n if (!existsSync(path)) return { kind: \"absent\" };\n\n try {\n if (statSync(path).size > NAVORI_CONFIG_MAX_BYTES) {\n return {\n kind: \"unreadable\",\n error: `navori.config.json excede el máximo permitido de ${NAVORI_CONFIG_MAX_BYTES} bytes`,\n };\n }\n\n const raw: unknown = JSON.parse(readFileSync(path, \"utf-8\"));\n if (typeof raw !== \"object\" || raw === null) {\n return { kind: \"unreadable\", error: \"navori.config.json no es un objeto JSON\" };\n }\n const obj = raw as Record<string, unknown>;\n\n const result: NaviorImport = {};\n if (typeof obj.name === \"string\") result.name = obj.name;\n if (typeof obj.workspace === \"string\") result.workspace = obj.workspace;\n if (typeof obj.branchBase === \"string\") result.branchBase = obj.branchBase;\n if (typeof obj.prTarget === \"string\") result.prTarget = obj.prTarget;\n\n if (typeof obj.qualityGate === \"object\" && obj.qualityGate !== null) {\n const qg = obj.qualityGate as Record<string, unknown>;\n result.qualityGate = {\n fast: typeof qg.fast === \"string\" ? qg.fast : undefined,\n full: typeof qg.full === \"string\" ? qg.full : undefined,\n };\n }\n\n if (typeof obj.project === \"object\" && obj.project !== null) {\n const project = obj.project as Record<string, unknown>;\n result.project = {\n criticalAreas: Array.isArray(project.criticalAreas)\n ? project.criticalAreas.filter((v): v is string => typeof v === \"string\")\n : undefined,\n legacyPaths: Array.isArray(project.legacyPaths)\n ? project.legacyPaths.filter((v): v is string => typeof v === \"string\")\n : undefined,\n };\n }\n\n return { kind: \"imported\", data: result };\n } catch (err) {\n return { kind: \"unreadable\", error: err instanceof Error ? err.message : String(err) };\n }\n}\n"],"names":["existsSync","readFileSync","statSync","join","NAVORI_CONFIG_FILENAME","NAVORI_CONFIG_MAX_BYTES","hasNaviorConfig","dir","readNaviorConfig","path","kind","size","error","raw","JSON","parse","obj","result","name","workspace","branchBase","prTarget","qualityGate","qg","fast","undefined","full","project","criticalAreas","Array","isArray","filter","v","legacyPaths","data","err","Error","message","String"],"mappings":"AAAA,SAASA,UAAU,EAAEC,YAAY,EAAEC,QAAQ,QAAQ,UAAU;AAC7D,SAASC,IAAI,QAAQ,YAAY;AAEjC,OAAO,MAAMC,yBAAyB,qBAAqB;AAE3D,oFAAoF,GACpF,OAAO,MAAMC,0BAA0B,UAAU;AAqBjD,4EAA4E,GAC5E,OAAO,SAASC,gBAAgBC,GAAW;IACzC,OAAOP,WAAWG,KAAKI,KAAKH;AAC9B;AAEA;;;;;;CAMC,GACD,OAAO,SAASI,iBAAiBD,GAAW;IAC1C,MAAME,OAAON,KAAKI,KAAKH;IACvB,IAAI,CAACJ,WAAWS,OAAO,OAAO;QAAEC,MAAM;IAAS;IAE/C,IAAI;QACF,IAAIR,SAASO,MAAME,IAAI,GAAGN,yBAAyB;YACjD,OAAO;gBACLK,MAAM;gBACNE,OAAO,CAAC,iDAAiD,EAAEP,wBAAwB,MAAM,CAAC;YAC5F;QACF;QAEA,MAAMQ,MAAeC,KAAKC,KAAK,CAACd,aAAaQ,MAAM;QACnD,IAAI,OAAOI,QAAQ,YAAYA,QAAQ,MAAM;YAC3C,OAAO;gBAAEH,MAAM;gBAAcE,OAAO;YAA0C;QAChF;QACA,MAAMI,MAAMH;QAEZ,MAAMI,SAAuB,CAAC;QAC9B,IAAI,OAAOD,IAAIE,IAAI,KAAK,UAAUD,OAAOC,IAAI,GAAGF,IAAIE,IAAI;QACxD,IAAI,OAAOF,IAAIG,SAAS,KAAK,UAAUF,OAAOE,SAAS,GAAGH,IAAIG,SAAS;QACvE,IAAI,OAAOH,IAAII,UAAU,KAAK,UAAUH,OAAOG,UAAU,GAAGJ,IAAII,UAAU;QAC1E,IAAI,OAAOJ,IAAIK,QAAQ,KAAK,UAAUJ,OAAOI,QAAQ,GAAGL,IAAIK,QAAQ;QAEpE,IAAI,OAAOL,IAAIM,WAAW,KAAK,YAAYN,IAAIM,WAAW,KAAK,MAAM;YACnE,MAAMC,KAAKP,IAAIM,WAAW;YAC1BL,OAAOK,WAAW,GAAG;gBACnBE,MAAM,OAAOD,GAAGC,IAAI,KAAK,WAAWD,GAAGC,IAAI,GAAGC;gBAC9CC,MAAM,OAAOH,GAAGG,IAAI,KAAK,WAAWH,GAAGG,IAAI,GAAGD;YAChD;QACF;QAEA,IAAI,OAAOT,IAAIW,OAAO,KAAK,YAAYX,IAAIW,OAAO,KAAK,MAAM;YAC3D,MAAMA,UAAUX,IAAIW,OAAO;YAC3BV,OAAOU,OAAO,GAAG;gBACfC,eAAeC,MAAMC,OAAO,CAACH,QAAQC,aAAa,IAC9CD,QAAQC,aAAa,CAACG,MAAM,CAAC,CAACC,IAAmB,OAAOA,MAAM,YAC9DP;gBACJQ,aAAaJ,MAAMC,OAAO,CAACH,QAAQM,WAAW,IAC1CN,QAAQM,WAAW,CAACF,MAAM,CAAC,CAACC,IAAmB,OAAOA,MAAM,YAC5DP;YACN;QACF;QAEA,OAAO;YAAEf,MAAM;YAAYwB,MAAMjB;QAAO;IAC1C,EAAE,OAAOkB,KAAK;QACZ,OAAO;YAAEzB,MAAM;YAAcE,OAAOuB,eAAeC,QAAQD,IAAIE,OAAO,GAAGC,OAAOH;QAAK;IACvF;AACF"}