@stravica-ai/digital-operator-pa 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 (372) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/bin/desk +9 -0
  4. package/bin/distill +2 -0
  5. package/bin/estate-sync +2 -0
  6. package/bin/launch +2 -0
  7. package/bin/memory-sync +2 -0
  8. package/bin/migrate-inspect +2 -0
  9. package/bin/pa +2 -0
  10. package/bin/pa-coordinator-hook +2 -0
  11. package/bin/pa-init +2 -0
  12. package/bin/research +2 -0
  13. package/bin/scheduler +2 -0
  14. package/bin/secrets +2 -0
  15. package/bin/slice +2 -0
  16. package/bin/smoke +9 -0
  17. package/bin/sync +2 -0
  18. package/dist/engineering-pa-cli/README.md +205 -0
  19. package/dist/engineering-pa-cli/bin/estate-sync +2 -0
  20. package/dist/engineering-pa-cli/bin/launch +2 -0
  21. package/dist/engineering-pa-cli/bin/pa +2 -0
  22. package/dist/engineering-pa-cli/bin/pa-coordinator-hook +12 -0
  23. package/dist/engineering-pa-cli/bin/research +2 -0
  24. package/dist/engineering-pa-cli/bin/scheduler +2 -0
  25. package/dist/engineering-pa-cli/bin/secrets +2 -0
  26. package/dist/engineering-pa-cli/bin/smoke +9 -0
  27. package/dist/engineering-pa-cli/bin/sync +2 -0
  28. package/dist/engineering-pa-cli/config/coordinator-allowlist.json +118 -0
  29. package/dist/engineering-pa-cli/docs/coordinator-hook.md +211 -0
  30. package/dist/engineering-pa-cli/docs/estate-sync.md +412 -0
  31. package/dist/engineering-pa-cli/docs/gsc.md +175 -0
  32. package/dist/engineering-pa-cli/docs/lint-linkedin.md +139 -0
  33. package/dist/engineering-pa-cli/docs/secrets.md +221 -0
  34. package/dist/engineering-pa-cli/docs/smoke.md +113 -0
  35. package/dist/engineering-pa-cli/docs/staging-alias.md +114 -0
  36. package/dist/engineering-pa-cli/index.mjs +17 -0
  37. package/dist/engineering-pa-cli/package.json +37 -0
  38. package/dist/engineering-pa-cli/src/autonomy/cli.mjs +329 -0
  39. package/dist/engineering-pa-cli/src/autonomy/envelope.mjs +301 -0
  40. package/dist/engineering-pa-cli/src/coordinator-hook/cli.mjs +140 -0
  41. package/dist/engineering-pa-cli/src/coordinator-hook/config.mjs +134 -0
  42. package/dist/engineering-pa-cli/src/coordinator-hook/message.mjs +47 -0
  43. package/dist/engineering-pa-cli/src/coordinator-hook/policy.mjs +211 -0
  44. package/dist/engineering-pa-cli/src/coordinator-hook/repo-root.mjs +44 -0
  45. package/dist/engineering-pa-cli/src/coordinator-hook/self.mjs +30 -0
  46. package/dist/engineering-pa-cli/src/environment/cli.mjs +287 -0
  47. package/dist/engineering-pa-cli/src/environment/probes.mjs +302 -0
  48. package/dist/engineering-pa-cli/src/gsc/auth.mjs +148 -0
  49. package/dist/engineering-pa-cli/src/gsc/cli.mjs +93 -0
  50. package/dist/engineering-pa-cli/src/gsc/cloudflare.mjs +318 -0
  51. package/dist/engineering-pa-cli/src/gsc/commands/add-property.mjs +70 -0
  52. package/dist/engineering-pa-cli/src/gsc/commands/list-properties.mjs +33 -0
  53. package/dist/engineering-pa-cli/src/gsc/commands/pull-analytics.mjs +130 -0
  54. package/dist/engineering-pa-cli/src/gsc/commands/submit-sitemap.mjs +41 -0
  55. package/dist/engineering-pa-cli/src/gsc/commands/verify-via-cf-dns.mjs +189 -0
  56. package/dist/engineering-pa-cli/src/knowledge/call-log.mjs +59 -0
  57. package/dist/engineering-pa-cli/src/knowledge/cli.mjs +242 -0
  58. package/dist/engineering-pa-cli/src/knowledge/eval.mjs +446 -0
  59. package/dist/engineering-pa-cli/src/knowledge/formatters.mjs +331 -0
  60. package/dist/engineering-pa-cli/src/knowledge/graph.mjs +710 -0
  61. package/dist/engineering-pa-cli/src/launch/README.md +214 -0
  62. package/dist/engineering-pa-cli/src/launch/cli.mjs +51 -0
  63. package/dist/engineering-pa-cli/src/launch/commands/launch.mjs +106 -0
  64. package/dist/engineering-pa-cli/src/launch/identity.mjs +43 -0
  65. package/dist/engineering-pa-cli/src/launch/planner.mjs +44 -0
  66. package/dist/engineering-pa-cli/src/launch/repo-root.mjs +53 -0
  67. package/dist/engineering-pa-cli/src/launch/runner.mjs +147 -0
  68. package/dist/engineering-pa-cli/src/launch/task-loader.mjs +85 -0
  69. package/dist/engineering-pa-cli/src/lint/cli.mjs +61 -0
  70. package/dist/engineering-pa-cli/src/lint/commands/linkedin.mjs +133 -0
  71. package/dist/engineering-pa-cli/src/lint/detectors.mjs +90 -0
  72. package/dist/engineering-pa-cli/src/lint/linter.mjs +69 -0
  73. package/dist/engineering-pa-cli/src/lint/reporter.mjs +32 -0
  74. package/dist/engineering-pa-cli/src/lint/rules.mjs +132 -0
  75. package/dist/engineering-pa-cli/src/pa/cli.mjs +369 -0
  76. package/dist/engineering-pa-cli/src/pa/doc-groups.mjs +164 -0
  77. package/dist/engineering-pa-cli/src/pa/io.mjs +80 -0
  78. package/dist/engineering-pa-cli/src/pa/project-cli.mjs +385 -0
  79. package/dist/engineering-pa-cli/src/pa/projects.mjs +352 -0
  80. package/dist/engineering-pa-cli/src/pa/repo-root.mjs +64 -0
  81. package/dist/engineering-pa-cli/src/pa/session.mjs +254 -0
  82. package/dist/engineering-pa-cli/src/pa/work-cli.mjs +164 -0
  83. package/dist/engineering-pa-cli/src/pa/work.mjs +196 -0
  84. package/dist/engineering-pa-cli/src/pa/workstreams.mjs +201 -0
  85. package/dist/engineering-pa-cli/src/research/README.md +360 -0
  86. package/dist/engineering-pa-cli/src/research/briefs/extractor.md +103 -0
  87. package/dist/engineering-pa-cli/src/research/briefs/searcher.md +113 -0
  88. package/dist/engineering-pa-cli/src/research/briefs/synthesizer.md +124 -0
  89. package/dist/engineering-pa-cli/src/research/briefs/verifier.md +86 -0
  90. package/dist/engineering-pa-cli/src/research/cli.mjs +141 -0
  91. package/dist/engineering-pa-cli/src/research/commands/gates.mjs +176 -0
  92. package/dist/engineering-pa-cli/src/research/commands/list.mjs +56 -0
  93. package/dist/engineering-pa-cli/src/research/commands/mark.mjs +97 -0
  94. package/dist/engineering-pa-cli/src/research/commands/paths.mjs +69 -0
  95. package/dist/engineering-pa-cli/src/research/commands/prune.mjs +86 -0
  96. package/dist/engineering-pa-cli/src/research/commands/run.mjs +94 -0
  97. package/dist/engineering-pa-cli/src/research/commands/show.mjs +43 -0
  98. package/dist/engineering-pa-cli/src/research/docs/operator-present-flow.md +120 -0
  99. package/dist/engineering-pa-cli/src/research/format.mjs +59 -0
  100. package/dist/engineering-pa-cli/src/research/promote.mjs +260 -0
  101. package/dist/engineering-pa-cli/src/research/schema/finding.schema.json +55 -0
  102. package/dist/engineering-pa-cli/src/research/schema/gates.schema.json +34 -0
  103. package/dist/engineering-pa-cli/src/research/schema/manifest.schema.json +59 -0
  104. package/dist/engineering-pa-cli/src/research/schema/plan.schema.json +41 -0
  105. package/dist/engineering-pa-cli/src/research/schema/sources.schema.json +31 -0
  106. package/dist/engineering-pa-cli/src/research/store/findings.mjs +61 -0
  107. package/dist/engineering-pa-cli/src/research/store/gates.mjs +73 -0
  108. package/dist/engineering-pa-cli/src/research/store/index.mjs +312 -0
  109. package/dist/engineering-pa-cli/src/research/store/manifest.mjs +56 -0
  110. package/dist/engineering-pa-cli/src/research/store/plan.mjs +59 -0
  111. package/dist/engineering-pa-cli/src/research/store/sources.mjs +44 -0
  112. package/dist/engineering-pa-cli/src/research/store/validate.mjs +92 -0
  113. package/dist/engineering-pa-cli/src/research/workflows/research-normal.js +573 -0
  114. package/dist/engineering-pa-cli/src/scheduler/cli.mjs +197 -0
  115. package/dist/engineering-pa-cli/src/secrets/cli.mjs +254 -0
  116. package/dist/engineering-pa-cli/src/secrets/commands/backup-template.mjs +67 -0
  117. package/dist/engineering-pa-cli/src/secrets/commands/backup.mjs +85 -0
  118. package/dist/engineering-pa-cli/src/secrets/commands/delete.mjs +58 -0
  119. package/dist/engineering-pa-cli/src/secrets/commands/find.mjs +36 -0
  120. package/dist/engineering-pa-cli/src/secrets/commands/get.mjs +49 -0
  121. package/dist/engineering-pa-cli/src/secrets/commands/init.mjs +69 -0
  122. package/dist/engineering-pa-cli/src/secrets/commands/list.mjs +37 -0
  123. package/dist/engineering-pa-cli/src/secrets/commands/rotate.mjs +46 -0
  124. package/dist/engineering-pa-cli/src/secrets/commands/set.mjs +123 -0
  125. package/dist/engineering-pa-cli/src/secrets/commands/status.mjs +146 -0
  126. package/dist/engineering-pa-cli/src/secrets/commands/vaults.mjs +57 -0
  127. package/dist/engineering-pa-cli/src/secrets/commands/verify.mjs +143 -0
  128. package/dist/engineering-pa-cli/src/secrets/vault.mjs +766 -0
  129. package/dist/engineering-pa-cli/src/smoke/checks.mjs +199 -0
  130. package/dist/engineering-pa-cli/src/smoke/cli.mjs +108 -0
  131. package/dist/engineering-pa-cli/src/smoke/commands/run.mjs +237 -0
  132. package/dist/engineering-pa-cli/src/smoke/runner.mjs +191 -0
  133. package/dist/engineering-pa-cli/src/staging-alias/cli.mjs +91 -0
  134. package/dist/engineering-pa-cli/src/staging-alias/cloudflare.mjs +380 -0
  135. package/dist/engineering-pa-cli/src/staging-alias/commands/list.mjs +36 -0
  136. package/dist/engineering-pa-cli/src/staging-alias/commands/set.mjs +188 -0
  137. package/dist/engineering-pa-cli/src/staging-alias/commands/status.mjs +113 -0
  138. package/dist/engineering-pa-cli/src/staging-alias/commands/unset.mjs +95 -0
  139. package/dist/engineering-pa-cli/src/staging-alias/sites.mjs +105 -0
  140. package/dist/engineering-pa-cli/src/sync/cli.mjs +151 -0
  141. package/dist/engineering-pa-cli/src/sync/git.mjs +149 -0
  142. package/dist/engineering-pa-cli/src/sync/lock.mjs +30 -0
  143. package/dist/engineering-pa-cli/src/sync/regenerated-artifacts.mjs +114 -0
  144. package/dist/engineering-pa-cli/src/sync/registry-scope.mjs +260 -0
  145. package/dist/engineering-pa-cli/src/sync/run.mjs +531 -0
  146. package/dist/engineering-pa-cli/src/sync/runner.mjs +62 -0
  147. package/dist/engineering-pa-cli/src/sync/runs-digest.mjs +138 -0
  148. package/dist/engineering-pa-core/README.md +60 -0
  149. package/dist/engineering-pa-core/bin/desk +9 -0
  150. package/dist/engineering-pa-core/bin/distill +2 -0
  151. package/dist/engineering-pa-core/bin/memory-sync +2 -0
  152. package/dist/engineering-pa-core/bin/slice +2 -0
  153. package/dist/engineering-pa-core/docs/autonomy-envelope-tracker.md +111 -0
  154. package/dist/engineering-pa-core/docs/blueprints/README.md +45 -0
  155. package/dist/engineering-pa-core/docs/blueprints/docker-services/blueprint.md +168 -0
  156. package/dist/engineering-pa-core/docs/blueprints/git-identity/blueprint.md +116 -0
  157. package/dist/engineering-pa-core/docs/blueprints/ideas/custom-workspace-views.md +9 -0
  158. package/dist/engineering-pa-core/docs/blueprints/ideas/knowledge-domain-patterns.md +15 -0
  159. package/dist/engineering-pa-core/docs/blueprints/ideas/office-integration.md +11 -0
  160. package/dist/engineering-pa-core/docs/blueprints/ideas/team-scaling.md +9 -0
  161. package/dist/engineering-pa-core/docs/blueprints/ideas/work-personal-separation.md +9 -0
  162. package/dist/engineering-pa-core/docs/blueprints/scheduled-distillation/blueprint.md +129 -0
  163. package/dist/engineering-pa-core/docs/blueprints/tool-integration/blueprint.md +110 -0
  164. package/dist/engineering-pa-core/docs/commands/init.md +159 -0
  165. package/dist/engineering-pa-core/docs/commands/schedule.md +98 -0
  166. package/dist/engineering-pa-core/docs/commands/upgrade.md +67 -0
  167. package/dist/engineering-pa-core/docs/entity-location-map.md +93 -0
  168. package/dist/engineering-pa-core/docs/knowledge-scaffold.md +121 -0
  169. package/dist/engineering-pa-core/docs/lock.md +155 -0
  170. package/dist/engineering-pa-core/docs/memory-sync.md +28 -0
  171. package/dist/engineering-pa-core/docs/pa-operating-model.md +457 -0
  172. package/dist/engineering-pa-core/docs/scheduling.md +35 -0
  173. package/dist/engineering-pa-core/docs/standards/README.md +24 -0
  174. package/dist/engineering-pa-core/docs/standards/briefing-contract.md +161 -0
  175. package/dist/engineering-pa-core/docs/standards/tooling.md +240 -0
  176. package/dist/engineering-pa-core/docs/tools/distill.md +230 -0
  177. package/dist/engineering-pa-core/docs/tools/memory-sync.md +98 -0
  178. package/dist/engineering-pa-core/docs/tools/scheduler.md +243 -0
  179. package/dist/engineering-pa-core/docs/tools/slice.md +150 -0
  180. package/dist/engineering-pa-core/docs/upstream-issue-filing.md +88 -0
  181. package/dist/engineering-pa-core/docs/working-directory.md +29 -0
  182. package/dist/engineering-pa-core/docs/workspace-operations.md +46 -0
  183. package/dist/engineering-pa-core/index.mjs +21 -0
  184. package/dist/engineering-pa-core/knowledge/README.md +53 -0
  185. package/dist/engineering-pa-core/knowledge/canonical/.gitkeep +0 -0
  186. package/dist/engineering-pa-core/knowledge/content-voice/README.md +64 -0
  187. package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-baseline.md +133 -0
  188. package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-shape.md +58 -0
  189. package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-taxonomy.md +33 -0
  190. package/dist/engineering-pa-core/knowledge/content-voice/content-pipeline-routing.md +58 -0
  191. package/dist/engineering-pa-core/knowledge/content-voice/new-tell-capture-procedure.md +40 -0
  192. package/dist/engineering-pa-core/knowledge/content-voice/voice-guide-shape.md +43 -0
  193. package/dist/engineering-pa-core/knowledge/delegation-dispatch/README.md +34 -0
  194. package/dist/engineering-pa-core/knowledge/delegation-dispatch/briefing-contract.md +77 -0
  195. package/dist/engineering-pa-core/knowledge/delegation-dispatch/delegation-modes.md +84 -0
  196. package/dist/engineering-pa-core/knowledge/delegation-dispatch/dispatch-envelope.md +238 -0
  197. package/dist/engineering-pa-core/knowledge/delegation-dispatch/task-groups-and-orchestrators.md +62 -0
  198. package/dist/engineering-pa-core/knowledge/delegation-dispatch/team-improvement-loop.md +48 -0
  199. package/dist/engineering-pa-core/knowledge/delegation-dispatch/tool-development-policy.md +48 -0
  200. package/dist/engineering-pa-core/knowledge/engineering-discipline/README.md +33 -0
  201. package/dist/engineering-pa-core/knowledge/engineering-discipline/blast-radius-and-safety.md +50 -0
  202. package/dist/engineering-pa-core/knowledge/engineering-discipline/investigate-root-causes.md +48 -0
  203. package/dist/engineering-pa-core/knowledge/engineering-discipline/role-ladder.md +57 -0
  204. package/dist/engineering-pa-core/knowledge/engineering-discipline/verify-runtime-behaviour.md +53 -0
  205. package/dist/engineering-pa-core/knowledge/engineering-discipline/worktree-isolation.md +50 -0
  206. package/dist/engineering-pa-core/knowledge/knowledge-memory/README.md +33 -0
  207. package/dist/engineering-pa-core/knowledge/knowledge-memory/claude-md-hygiene.md +47 -0
  208. package/dist/engineering-pa-core/knowledge/knowledge-memory/journal-and-self-improvement.md +53 -0
  209. package/dist/engineering-pa-core/knowledge/knowledge-memory/knowledge-retrieval-discipline.md +62 -0
  210. package/dist/engineering-pa-core/knowledge/knowledge-memory/librarian-synthesis-mandate.md +56 -0
  211. package/dist/engineering-pa-core/knowledge/knowledge-memory/staging-and-observations.md +58 -0
  212. package/dist/engineering-pa-core/knowledge/operator-interface/README.md +32 -0
  213. package/dist/engineering-pa-core/knowledge/operator-interface/communication-style.md +33 -0
  214. package/dist/engineering-pa-core/knowledge/operator-interface/escalation-and-correction.md +45 -0
  215. package/dist/engineering-pa-core/knowledge/operator-interface/push-back-discipline.md +42 -0
  216. package/dist/engineering-pa-core/knowledge/operator-interface/session-boot-and-end.md +56 -0
  217. package/dist/engineering-pa-core/knowledge/platform-tooling/README.md +37 -0
  218. package/dist/engineering-pa-core/knowledge/platform-tooling/desk-cli-reference.md +52 -0
  219. package/dist/engineering-pa-core/knowledge/platform-tooling/environment-awareness.md +91 -0
  220. package/dist/engineering-pa-core/knowledge/platform-tooling/mcp-servers-catalogue.md +52 -0
  221. package/dist/engineering-pa-core/knowledge/platform-tooling/pa-cli-reference.md +55 -0
  222. package/dist/engineering-pa-core/knowledge/platform-tooling/scheduling-and-launchd.md +56 -0
  223. package/dist/engineering-pa-core/knowledge/platform-tooling/tool-discovery-discipline.md +49 -0
  224. package/dist/engineering-pa-core/knowledge/platform-tooling/working-directory-vs-project-root.md +43 -0
  225. package/dist/engineering-pa-core/knowledge/platform-tooling/workspace-server.md +51 -0
  226. package/dist/engineering-pa-core/knowledge/raw/.gitkeep +0 -0
  227. package/dist/engineering-pa-core/knowledge/scaffold/domains.json +5 -0
  228. package/dist/engineering-pa-core/knowledge/schema/.gitkeep +0 -0
  229. package/dist/engineering-pa-core/knowledge/work-management/README.md +33 -0
  230. package/dist/engineering-pa-core/knowledge/work-management/desk-cli.md +84 -0
  231. package/dist/engineering-pa-core/knowledge/work-management/progress-md.md +48 -0
  232. package/dist/engineering-pa-core/knowledge/work-management/projects.md +64 -0
  233. package/dist/engineering-pa-core/knowledge/work-management/spawn-log.md +48 -0
  234. package/dist/engineering-pa-core/knowledge/work-management/work-items.md +67 -0
  235. package/dist/engineering-pa-core/package.json +26 -0
  236. package/dist/engineering-pa-core/roles/agent-ops/definition.md +79 -0
  237. package/dist/engineering-pa-core/roles/dispatch-briefer/definition.md +195 -0
  238. package/dist/engineering-pa-core/roles/librarian/definition.md +176 -0
  239. package/dist/engineering-pa-core/roles/pa/definition.md +93 -0
  240. package/dist/engineering-pa-core/roles/system-admin/definition.md +106 -0
  241. package/dist/engineering-pa-core/roles/tool-developer/definition.md +116 -0
  242. package/dist/engineering-pa-core/roles/writer/definition.md +130 -0
  243. package/dist/engineering-pa-core/schema/autonomy-envelope.schema.json +61 -0
  244. package/dist/engineering-pa-core/schema/project.json +114 -0
  245. package/dist/engineering-pa-core/schema/schedule.json +63 -0
  246. package/dist/engineering-pa-core/schema/work-item.json +92 -0
  247. package/dist/engineering-pa-core/src/config/pa-package-name.mjs +30 -0
  248. package/dist/engineering-pa-core/src/desk/cli.mjs +95 -0
  249. package/dist/engineering-pa-core/src/desk/commands.mjs +398 -0
  250. package/dist/engineering-pa-core/src/desk/format.mjs +84 -0
  251. package/dist/engineering-pa-core/src/desk/repo.mjs +172 -0
  252. package/dist/engineering-pa-core/src/desk/team-scope.mjs +142 -0
  253. package/dist/engineering-pa-core/src/distill/cli.mjs +112 -0
  254. package/dist/engineering-pa-core/src/distill/prereqs.mjs +171 -0
  255. package/dist/engineering-pa-core/src/distill/prompt.mjs +74 -0
  256. package/dist/engineering-pa-core/src/distill/run.mjs +230 -0
  257. package/dist/engineering-pa-core/src/distill/runner.mjs +86 -0
  258. package/dist/engineering-pa-core/src/distill/staging.mjs +59 -0
  259. package/dist/engineering-pa-core/src/knowledge/retrieval/backlink-indexer.mjs +154 -0
  260. package/dist/engineering-pa-core/src/knowledge/retrieval/graph-builder.mjs +186 -0
  261. package/dist/engineering-pa-core/src/knowledge/retrieval/index-builder.mjs +225 -0
  262. package/dist/engineering-pa-core/src/knowledge/retrieval/query.mjs +263 -0
  263. package/dist/engineering-pa-core/src/lock/lock.mjs +202 -0
  264. package/dist/engineering-pa-core/src/memory-sync/cli-support.mjs +12 -0
  265. package/dist/engineering-pa-core/src/memory-sync/cli.mjs +56 -0
  266. package/dist/engineering-pa-core/src/memory-sync/commands/pull.mjs +44 -0
  267. package/dist/engineering-pa-core/src/memory-sync/commands/push.mjs +46 -0
  268. package/dist/engineering-pa-core/src/memory-sync/mirror.mjs +114 -0
  269. package/dist/engineering-pa-core/src/memory-sync/repo.mjs +65 -0
  270. package/dist/engineering-pa-core/src/scheduler/detect.mjs +111 -0
  271. package/dist/engineering-pa-core/src/scheduler/host.mjs +38 -0
  272. package/dist/engineering-pa-core/src/scheduler/index.mjs +13 -0
  273. package/dist/engineering-pa-core/src/scheduler/install.mjs +854 -0
  274. package/dist/engineering-pa-core/src/scheduler/list.mjs +168 -0
  275. package/dist/engineering-pa-core/src/scheduler/remove.mjs +176 -0
  276. package/dist/engineering-pa-core/src/scheduler/verify.mjs +435 -0
  277. package/dist/engineering-pa-core/src/session-boot/autonomy-injection.mjs +84 -0
  278. package/dist/engineering-pa-core/src/slice/cli.mjs +139 -0
  279. package/dist/engineering-pa-core/src/slice/inputs.mjs +174 -0
  280. package/dist/engineering-pa-core/src/slice/prompt.mjs +94 -0
  281. package/dist/engineering-pa-core/src/slice/prompts/dave-boot.md +91 -0
  282. package/dist/engineering-pa-core/src/slice/run.mjs +334 -0
  283. package/dist/engineering-pa-core-extensions-atlassian/README.md +31 -0
  284. package/dist/engineering-pa-core-extensions-atlassian/index.mjs +9 -0
  285. package/dist/engineering-pa-core-extensions-atlassian/package.json +19 -0
  286. package/dist/engineering-pa-core-extensions-n8n/README.md +29 -0
  287. package/dist/engineering-pa-core-extensions-n8n/index.mjs +8 -0
  288. package/dist/engineering-pa-core-extensions-n8n/package.json +19 -0
  289. package/dist/engineering-pa-librarian/README.md +38 -0
  290. package/dist/engineering-pa-librarian/index.mjs +12 -0
  291. package/dist/engineering-pa-librarian/package.json +19 -0
  292. package/dist/engineering-pa-migrate-inspect/README.md +131 -0
  293. package/dist/engineering-pa-migrate-inspect/bin/migrate-inspect +2 -0
  294. package/dist/engineering-pa-migrate-inspect/package.json +21 -0
  295. package/dist/engineering-pa-migrate-inspect/src/age-key-migrate.mjs +83 -0
  296. package/dist/engineering-pa-migrate-inspect/src/cli.mjs +97 -0
  297. package/dist/engineering-pa-migrate-inspect/src/inspector.mjs +237 -0
  298. package/dist/engineering-pa-migrate-inspect/src/platforms/linux.mjs +87 -0
  299. package/dist/engineering-pa-migrate-inspect/src/platforms/macos.mjs +105 -0
  300. package/dist/engineering-pa-migrate-inspect/src/runner.mjs +87 -0
  301. package/dist/engineering-pa-runbook/README.md +103 -0
  302. package/dist/engineering-pa-runbook/package.json +18 -0
  303. package/dist/engineering-pa-runbook/reference/dex-template.runbook.json +66 -0
  304. package/dist/engineering-pa-runbook/reference/hq-dave.runbook.json +69 -0
  305. package/dist/engineering-pa-runbook/reference/smarthome01.runbook.json +92 -0
  306. package/dist/engineering-pa-runbook/schema/inspection.schema.json +113 -0
  307. package/dist/engineering-pa-runbook/schema/runbook.schema.json +67 -0
  308. package/dist/engineering-pa-runbook/src/validator.mjs +167 -0
  309. package/dist/engineering-pa-scaffold/README.md +164 -0
  310. package/dist/engineering-pa-scaffold/bin/pa-init +2 -0
  311. package/dist/engineering-pa-scaffold/index.mjs +42 -0
  312. package/dist/engineering-pa-scaffold/package.json +22 -0
  313. package/dist/engineering-pa-scaffold/src/cli.mjs +115 -0
  314. package/dist/engineering-pa-scaffold/src/commands/init.mjs +169 -0
  315. package/dist/engineering-pa-scaffold/src/generator.mjs +354 -0
  316. package/dist/engineering-pa-scaffold/src/prompts.mjs +125 -0
  317. package/dist/engineering-pa-scaffold/src/runner.mjs +142 -0
  318. package/dist/engineering-pa-scaffold/templates/CLAUDE.md.template +196 -0
  319. package/dist/engineering-pa-scaffold/templates/operator-README.md.template +44 -0
  320. package/dist/engineering-pa-types-engineering/README.md +35 -0
  321. package/dist/engineering-pa-types-engineering/docs/commands/product-build.md +120 -0
  322. package/dist/engineering-pa-types-engineering/index.mjs +9 -0
  323. package/dist/engineering-pa-types-engineering/package.json +19 -0
  324. package/dist/engineering-pa-types-engineering/roles/product-engineer/definition.md +650 -0
  325. package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/build-spec.md +82 -0
  326. package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/preflight-checklist.md +158 -0
  327. package/dist/engineering-pa-types-engineering/roles/product-engineer/templates/worker-brief.md +130 -0
  328. package/dist/engineering-pa-workspace/README.md +197 -0
  329. package/dist/engineering-pa-workspace/app/assets/md.js +187 -0
  330. package/dist/engineering-pa-workspace/app/assets/modal.js +120 -0
  331. package/dist/engineering-pa-workspace/app/assets/theme.css +132 -0
  332. package/dist/engineering-pa-workspace/app/assets/theme.js +159 -0
  333. package/dist/engineering-pa-workspace/app/banner.js +320 -0
  334. package/dist/engineering-pa-workspace/app/delegations.html +345 -0
  335. package/dist/engineering-pa-workspace/app/home.html +1060 -0
  336. package/dist/engineering-pa-workspace/app/index.html +361 -0
  337. package/dist/engineering-pa-workspace/app/knowledge-map.html +163 -0
  338. package/dist/engineering-pa-workspace/app/knowledge.html +304 -0
  339. package/dist/engineering-pa-workspace/app/nav.json +75 -0
  340. package/dist/engineering-pa-workspace/app/notes.html +199 -0
  341. package/dist/engineering-pa-workspace/app/onboarding.html +592 -0
  342. package/dist/engineering-pa-workspace/app/profile.html +243 -0
  343. package/dist/engineering-pa-workspace/app/project-detail.html +432 -0
  344. package/dist/engineering-pa-workspace/app/projects.html +297 -0
  345. package/dist/engineering-pa-workspace/app/repos.html +598 -0
  346. package/dist/engineering-pa-workspace/app/schedules.html +486 -0
  347. package/dist/engineering-pa-workspace/app/secrets.html +164 -0
  348. package/dist/engineering-pa-workspace/app/staging.html +319 -0
  349. package/dist/engineering-pa-workspace/app/tasks.html +465 -0
  350. package/dist/engineering-pa-workspace/app/vendor/chart.umd.min.js +21 -0
  351. package/dist/engineering-pa-workspace/app/viewer.html +116 -0
  352. package/dist/engineering-pa-workspace/docs/DAEMON.md +104 -0
  353. package/dist/engineering-pa-workspace/docs/page-blueprint.md +286 -0
  354. package/dist/engineering-pa-workspace/docs/status-mon.md +168 -0
  355. package/dist/engineering-pa-workspace/index.mjs +12 -0
  356. package/dist/engineering-pa-workspace/nav.json.example +8 -0
  357. package/dist/engineering-pa-workspace/package.json +27 -0
  358. package/dist/engineering-pa-workspace/src/api.mjs +2086 -0
  359. package/dist/engineering-pa-workspace/src/install-daemon.mjs +573 -0
  360. package/dist/engineering-pa-workspace/src/nav.mjs +190 -0
  361. package/dist/engineering-pa-workspace/src/secrets-api.mjs +175 -0
  362. package/dist/engineering-pa-workspace/src/server.mjs +516 -0
  363. package/dist/engineering-pa-workspace/src/status-mon/poll.mjs +220 -0
  364. package/dist/engineering-pa-workspace/src/status-mon/poller.mjs +113 -0
  365. package/dist/engineering-pa-workspace/src/status-mon/run.mjs +133 -0
  366. package/dist/engineering-pa-workspace/src/supervisor.mjs +264 -0
  367. package/dist/engineering-pa-workspace/src/ws.mjs +633 -0
  368. package/index.mjs +60 -0
  369. package/package.json +58 -0
  370. package/templates/banned-ai-tells.md +102 -0
  371. package/templates/purpose.md +105 -0
  372. package/templates/voice-guide.md +98 -0
@@ -0,0 +1,766 @@
1
+ // vault.mjs — domain + chokepoint for SOPS/age shell-outs.
2
+ //
3
+ // Every external-effect call (spawning sops, spawning age, touching the
4
+ // filesystem in the vault/key paths) routes through a function in this
5
+ // module. Commands in src/commands/ consume this module; they never call
6
+ // child_process themselves.
7
+ //
8
+ // Path resolution is deliberately injectable via `paths()` so tests can
9
+ // operate against a temp vault and a temp key dir without ever touching
10
+ // the real ~/.config/dave or repo .vault.
11
+
12
+ import { spawn } from 'node:child_process';
13
+ import { homedir } from 'node:os';
14
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync, chmodSync } from 'node:fs';
15
+ import { join, dirname, resolve, basename, sep } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { mkdtempSync } from 'node:fs';
18
+ import { tmpdir } from 'node:os';
19
+
20
+ // ── Path resolution ──────────────────────────────────────────────────
21
+
22
+ // Workspace root resolution, in priority order:
23
+ // 1. Upward walk from cwd for a VAULT-BEARING directory — one containing
24
+ // a `.vault/` dir, or a `vault/` dir with at least one *.enc.json
25
+ // (canonical-clone layout). A bare git repo WITHOUT a vault does not
26
+ // stop resolution: it falls through to the next tiers, LOUDLY — a
27
+ // stderr notice names the store actually targeted on every such
28
+ // invocation. Visible redirects are a feature; invisible ones are
29
+ // the wrong-store incident class this exists to prevent.
30
+ // (v1 history: root was `resolve(__dirname, '../../../..')`, which
31
+ // landed at the operator repo root by accident in v1's
32
+ // `core/tools/secrets/src/` layout; in v2 the module lives in
33
+ // `node_modules/` and __dirname-navigation lands outside the tree.)
34
+ // 2. DAVE_ROOT / DAVE_REPO_ROOT / PA_REPO_ROOT env.
35
+ // 3. `workspaceRoot` key in ~/.config/stravica-pa/defaults.json
36
+ // (written by the user or provisioning — this CLI never creates it).
37
+ // 4. Error listing exactly what was tried, in order.
38
+ //
39
+ // PRECEDENCE CHANGE vs the previously-shipped behaviour: env used to be
40
+ // checked BEFORE the cwd walk. Now the walk wins — standing inside
41
+ // workspace A with DAVE_ROOT pointing at workspace B targets A. And the
42
+ // old ultimate fallback of bare `cwd` (which invented a phantom
43
+ // `<cwd>/.vault/` and the misleading "No recipients file" error whenever
44
+ // the CLI ran outside a workspace) is gone entirely: tiers 2-4 replace it.
45
+ //
46
+ // `secrets init` is the one deliberate exception (allowGitRootWithoutVault):
47
+ // init CREATES a vault, so a vault-less git repo you are standing in IS its
48
+ // legitimate target — same bootstrap behaviour as before this change.
49
+
50
+ function hasVaultMarker(d) {
51
+ if (existsSync(join(d, '.vault'))) return true;
52
+ const canon = join(d, 'vault');
53
+ if (existsSync(canon)) {
54
+ try {
55
+ return readdirSync(canon).some((f) => f.endsWith('.enc.json'));
56
+ } catch { /* unreadable — not a marker */ }
57
+ }
58
+ return false;
59
+ }
60
+
61
+ function walkForWorkspace(start) {
62
+ let d = start;
63
+ let prev;
64
+ let gitRootWithoutVault = null;
65
+ while (d && d !== prev) {
66
+ if (hasVaultMarker(d)) return { root: d, gitRootWithoutVault };
67
+ if (!gitRootWithoutVault && existsSync(join(d, '.git'))) gitRootWithoutVault = d;
68
+ prev = d;
69
+ d = dirname(d);
70
+ }
71
+ return { root: null, gitRootWithoutVault };
72
+ }
73
+
74
+ export function defaultsFilePath(home) {
75
+ return join(home, '.config', 'stravica-pa', 'defaults.json');
76
+ }
77
+
78
+ function readDefaultsWorkspaceRoot(home) {
79
+ const file = defaultsFilePath(home);
80
+ if (!existsSync(file)) return { file, root: null, reason: 'file not present' };
81
+ try {
82
+ const parsed = JSON.parse(readFileSync(file, 'utf-8'));
83
+ if (parsed && typeof parsed.workspaceRoot === 'string' && parsed.workspaceRoot.trim() !== '') {
84
+ return { file, root: resolve(parsed.workspaceRoot), reason: null };
85
+ }
86
+ return { file, root: null, reason: 'workspaceRoot key not set' };
87
+ } catch {
88
+ return { file, root: null, reason: 'file is not valid JSON' };
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Resolve the workspace root using the tiered fallback above. Returns
94
+ * { root, source } where source is 'cwd-walk' | 'env' | 'defaults'.
95
+ * Prints a mandatory stderr notice whenever the nearest git repo has no
96
+ * vault and resolution redirected elsewhere. Throws VaultError (exit 1,
97
+ * message already printed to stderr) listing every tier tried when
98
+ * nothing matches.
99
+ */
100
+ export function resolveWorkspaceRoot(opts = {}) {
101
+ const cwd = opts.cwd ?? process.cwd();
102
+ const env = opts.env ?? process.env;
103
+ const home = opts.home ?? homedir();
104
+ const tried = [];
105
+
106
+ const { root: walked, gitRootWithoutVault } = walkForWorkspace(cwd);
107
+
108
+ // init bootstrap: a vault-less git repo you're standing in is init's
109
+ // legitimate creation target, not a redirect case.
110
+ if (opts.allowGitRootWithoutVault && !walked && gitRootWithoutVault) {
111
+ return { root: gitRootWithoutVault, source: 'cwd-walk' };
112
+ }
113
+
114
+ const sourceLabel = {
115
+ 'cwd-walk': (r) => `an enclosing workspace at ${r}`,
116
+ env: (r) => `workspace ${r} (from $DAVE_ROOT/$DAVE_REPO_ROOT/$PA_REPO_ROOT)`,
117
+ defaults: (r, file) => `workspace ${r} (from "workspaceRoot" in ${file})`,
118
+ };
119
+ const finish = (root, source, defaultsFile) => {
120
+ if (gitRootWithoutVault && root !== gitRootWithoutVault) {
121
+ console.error(`notice: no vault in ${gitRootWithoutVault} — using ${sourceLabel[source](root, defaultsFile)}`);
122
+ }
123
+ return { root, source };
124
+ };
125
+
126
+ if (walked) return finish(walked, 'cwd-walk');
127
+ tried.push(
128
+ `walked up from ${cwd} looking for a workspace (.vault/ or vault/*.enc.json) — none found`
129
+ + (gitRootWithoutVault ? ` (nearest git repo ${gitRootWithoutVault} has no vault)` : ''),
130
+ );
131
+
132
+ const envRoot = env.DAVE_ROOT || env.DAVE_REPO_ROOT || env.PA_REPO_ROOT;
133
+ if (envRoot) return finish(resolve(envRoot), 'env');
134
+ tried.push('$DAVE_ROOT / $DAVE_REPO_ROOT / $PA_REPO_ROOT — not set');
135
+
136
+ const defaults = readDefaultsWorkspaceRoot(home);
137
+ if (defaults.root) return finish(defaults.root, 'defaults', defaults.file);
138
+ tried.push(`"workspaceRoot" in ${defaults.file} — ${defaults.reason}`);
139
+
140
+ const message = [
141
+ 'Error: Could not resolve a workspace root. Tried, in order:',
142
+ ...tried.map((t, i) => ` ${i + 1}. ${t}`),
143
+ `Run from inside a workspace, set DAVE_ROOT, or write {"workspaceRoot": "/path/to/workspace"} to ${defaults.file}.`,
144
+ ].join('\n');
145
+ console.error(message);
146
+ throw new VaultError(message, 1);
147
+ }
148
+
149
+ export function defaultPaths(overrides = {}) {
150
+ const root = overrides.root
151
+ ?? resolveWorkspaceRoot({
152
+ cwd: overrides.cwd,
153
+ env: overrides.env,
154
+ home: overrides.home,
155
+ allowGitRootWithoutVault: overrides.allowGitRootWithoutVault,
156
+ }).root;
157
+ const home = overrides.home ?? homedir();
158
+ const vaultDir = overrides.vaultDir ?? join(root, '.vault');
159
+ const keyDir = overrides.keyDir ?? join(home, '.config', 'stravica-pa');
160
+ return {
161
+ root,
162
+ vaultDir,
163
+ secretsFile: join(vaultDir, 'secrets.enc.json'),
164
+ recipientsFile: join(vaultDir, 'recipients.txt'),
165
+ sopsConfig: join(vaultDir, '.sops.yaml'),
166
+ keyDir,
167
+ keyFile: join(keyDir, 'age-key.txt'),
168
+ };
169
+ }
170
+
171
+ // ── Scoped vault resolution ──────────────────────────────────────────
172
+ //
173
+ // Two on-disk layouts, both sops-encrypted JSON with per-scope age
174
+ // recipients declared in a shared .sops.yaml (creation_rules keyed by
175
+ // filename, NOT a single recipients.txt — that's the whole point of
176
+ // scoping: each scope can have a different recipient set):
177
+ //
178
+ // workspace layout (estate hosts): <root>/.vault/scopes/<scope>.enc.json
179
+ // <root>/.vault/scopes/.sops.yaml
180
+ // canonical layout (operator repos): <root>/vault/<scope>.enc.json
181
+ // <root>/vault/.sops.yaml
182
+ //
183
+ // `root` is resolved the same way as the legacy vault: the tiered
184
+ // resolveWorkspaceRoot fallback (cwd walk → env → defaults.json).
185
+
186
+ const SCOPE_NAME_RE = /^[A-Za-z0-9_-]+$/;
187
+
188
+ // Print-then-throw, matching the command modules' convention — main()'s
189
+ // catch deliberately does NOT print messages for errors that carry an
190
+ // exitCode (it assumes the throw site already reported), so a bare
191
+ // `throw new VaultError(...)` here would exit silently through the real
192
+ // binary. That silent-exit was a live bug in the first --scope cut.
193
+ function scopeError(message, exitCode = 1) {
194
+ console.error(message);
195
+ return new VaultError(message, exitCode);
196
+ }
197
+
198
+ /**
199
+ * Resolve paths for `--scope <name>` or `--file <path>`. Throws VaultError
200
+ * (exit 1) for an invalid scope name, an ambiguous scope (present in both
201
+ * layouts), or a scope found in neither layout. `--file` bypasses all of
202
+ * this and points directly at the given encrypted file.
203
+ */
204
+ export function resolveScopePaths(opts = {}) {
205
+ const home = opts.home ?? homedir();
206
+ const keyDir = opts.keyDir ?? join(home, '.config', 'stravica-pa');
207
+ const keyFile = opts.keyFile ?? join(keyDir, 'age-key.txt');
208
+
209
+ if (opts.file) {
210
+ const secretsFile = resolve(opts.file);
211
+ const vaultDir = dirname(secretsFile);
212
+ return {
213
+ root: vaultDir,
214
+ vaultDir,
215
+ secretsFile,
216
+ recipientsFile: null,
217
+ sopsConfig: opts.sopsConfig ? resolve(opts.sopsConfig) : join(vaultDir, '.sops.yaml'),
218
+ keyDir,
219
+ keyFile,
220
+ scope: opts.scope ?? null,
221
+ scoped: true,
222
+ layout: 'file',
223
+ };
224
+ }
225
+
226
+ if (!opts.scope) {
227
+ throw scopeError('Error: resolveScopePaths requires --scope or --file');
228
+ }
229
+ if (!SCOPE_NAME_RE.test(opts.scope)) {
230
+ throw scopeError(`Error: Invalid scope name "${opts.scope}". Scope names must match ${SCOPE_NAME_RE}.`);
231
+ }
232
+
233
+ const { root } = resolveWorkspaceRoot({ cwd: opts.cwd, env: opts.env, home: opts.home });
234
+ const workspaceDir = join(root, '.vault', 'scopes');
235
+ const workspaceFile = join(workspaceDir, `${opts.scope}.enc.json`);
236
+ const canonicalDir = join(root, 'vault');
237
+ const canonicalFile = join(canonicalDir, `${opts.scope}.enc.json`);
238
+
239
+ const workspaceExists = existsSync(workspaceFile);
240
+ const canonicalExists = existsSync(canonicalFile);
241
+
242
+ if (workspaceExists && canonicalExists) {
243
+ throw scopeError([
244
+ `Error: Scope "${opts.scope}" is ambiguous — found in both layouts:`,
245
+ ` workspace: ${workspaceFile}`,
246
+ ` canonical: ${canonicalFile}`,
247
+ 'Use --file <path> to pick one explicitly.',
248
+ ].join('\n'));
249
+ }
250
+
251
+ if (workspaceExists) {
252
+ return {
253
+ root,
254
+ vaultDir: workspaceDir,
255
+ secretsFile: workspaceFile,
256
+ recipientsFile: null,
257
+ sopsConfig: join(workspaceDir, '.sops.yaml'),
258
+ keyDir,
259
+ keyFile,
260
+ scope: opts.scope,
261
+ scoped: true,
262
+ layout: 'workspace',
263
+ };
264
+ }
265
+
266
+ if (canonicalExists) {
267
+ return {
268
+ root,
269
+ vaultDir: canonicalDir,
270
+ secretsFile: canonicalFile,
271
+ recipientsFile: null,
272
+ sopsConfig: join(canonicalDir, '.sops.yaml'),
273
+ keyDir,
274
+ keyFile,
275
+ scope: opts.scope,
276
+ scoped: true,
277
+ layout: 'canonical',
278
+ };
279
+ }
280
+
281
+ throw scopeError([
282
+ `Error: No vault found for scope "${opts.scope}". Checked:`,
283
+ ` ${workspaceFile}`,
284
+ ` ${canonicalFile}`,
285
+ 'Use --file <path> to point at an explicit vault file.',
286
+ ].join('\n'));
287
+ }
288
+
289
+ // ── Default-scope resolution (get/set only) ──────────────────────────
290
+ //
291
+ // When `get`/`set` run WITHOUT --scope/--file, the target is no longer
292
+ // silently "the legacy single-file vault" — a default scope can now come
293
+ // from, in priority order:
294
+ // 1. $SECRETS_DEFAULT_SCOPE env var.
295
+ // 2. "defaultScope" key in ~/.config/stravica-pa/defaults.json — same
296
+ // file/convention as "workspaceRoot" (readDefaultsWorkspaceRoot
297
+ // above), additive key, this CLI never writes it.
298
+ // 3. The calling PA's own identity file: agents/<name>/identity.json's
299
+ // "defaultScope" field. Located by walking up from cwd for the
300
+ // nearest ancestor directory that both (a) sits under an "agents"
301
+ // path segment and (b) contains identity.json — mirrors how a PA's
302
+ // own cwd is conventionally agents/<name>/... (Dave) or nested
303
+ // deeper, e.g. agents/barry/dex/... (Dex). $DAVE_ROOT is checked
304
+ // too, but ONLY as the single well-known path
305
+ // $DAVE_ROOT/agents/dave/identity.json — the one PA with an
306
+ // established root env var; no other agent name is ever guessed
307
+ // from an env var.
308
+ // 4. null — the caller falls through to the legacy single-file vault,
309
+ // with the mandatory stderr notice below (resolveCommandPaths)
310
+ // making that fallback visible instead of silent.
311
+ //
312
+ // This convention is NOT yet backed by any identity.json files on disk
313
+ // (none exist anywhere in the estate as of this change) — the resolver
314
+ // only reads them if present. Creating Dave's and Dex's identity.json is
315
+ // tracked as a follow-up.
316
+ //
317
+ // A malformed identity.json is a stderr WARNING and a fall-through, not
318
+ // a crash — this is a convenience default, never a hard dependency the
319
+ // CLI can refuse to run without.
320
+
321
+ function readDefaultsDefaultScope(home) {
322
+ const file = defaultsFilePath(home);
323
+ if (!existsSync(file)) return { file, scope: null, reason: 'file not present' };
324
+ try {
325
+ const parsed = JSON.parse(readFileSync(file, 'utf-8'));
326
+ if (parsed && typeof parsed.defaultScope === 'string' && parsed.defaultScope.trim() !== '') {
327
+ return { file, scope: parsed.defaultScope.trim(), reason: null };
328
+ }
329
+ return { file, scope: null, reason: 'defaultScope key not set' };
330
+ } catch {
331
+ return { file, scope: null, reason: 'file is not valid JSON' };
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Walk up from `cwd` for the nearest ancestor directory that sits under
337
+ * an "agents" path segment AND contains identity.json. Returns the file
338
+ * path, or null if none found before the filesystem root.
339
+ */
340
+ function findIdentityFile(cwd) {
341
+ let d = resolve(cwd);
342
+ let prev;
343
+ while (d && d !== prev) {
344
+ if (d.split(sep).includes('agents') && existsSync(join(d, 'identity.json'))) {
345
+ return join(d, 'identity.json');
346
+ }
347
+ prev = d;
348
+ d = dirname(d);
349
+ }
350
+ return null;
351
+ }
352
+
353
+ function readIdentityDefaultScope(cwd, env) {
354
+ const walked = findIdentityFile(cwd);
355
+ const daveFallback = env.DAVE_ROOT ? join(resolve(env.DAVE_ROOT), 'agents', 'dave', 'identity.json') : null;
356
+ const file = walked || (daveFallback && existsSync(daveFallback) ? daveFallback : null);
357
+ if (!file) {
358
+ return { file: null, scope: null, reason: 'no agents/<name>/identity.json found (cwd walk or $DAVE_ROOT)' };
359
+ }
360
+ try {
361
+ const parsed = JSON.parse(readFileSync(file, 'utf-8'));
362
+ if (parsed && typeof parsed.defaultScope === 'string' && parsed.defaultScope.trim() !== '') {
363
+ return { file, scope: parsed.defaultScope.trim(), reason: null };
364
+ }
365
+ return { file, scope: null, reason: 'defaultScope key not set' };
366
+ } catch {
367
+ console.error(`warning: ${file} is malformed JSON — ignoring its "defaultScope" default`);
368
+ return { file, scope: null, reason: 'file is not valid JSON' };
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Resolve the default scope (source-tagged) for get/set when neither
374
+ * --scope nor --file was passed. Returns { scope, source, detail }, with
375
+ * scope: null meaning nothing is configured anywhere — the legacy
376
+ * fallback case.
377
+ */
378
+ export function resolveDefaultScope(opts = {}) {
379
+ const env = opts.env ?? process.env;
380
+ const home = opts.home ?? homedir();
381
+ const cwd = opts.cwd ?? process.cwd();
382
+
383
+ if (typeof env.SECRETS_DEFAULT_SCOPE === 'string' && env.SECRETS_DEFAULT_SCOPE.trim() !== '') {
384
+ return { scope: env.SECRETS_DEFAULT_SCOPE.trim(), source: 'env', detail: 'SECRETS_DEFAULT_SCOPE env var' };
385
+ }
386
+
387
+ const defaults = readDefaultsDefaultScope(home);
388
+ if (defaults.scope) {
389
+ return { scope: defaults.scope, source: 'defaults', detail: defaults.file };
390
+ }
391
+
392
+ const identity = readIdentityDefaultScope(cwd, env);
393
+ if (identity.scope) {
394
+ return { scope: identity.scope, source: 'identity', detail: identity.file };
395
+ }
396
+
397
+ return { scope: null, source: null, detail: null };
398
+ }
399
+
400
+ /**
401
+ * Resolve the paths get/set should operate on, printing the interim
402
+ * stderr notice (change A) naming the target vault BEFORE the operation
403
+ * runs. If overrides.paths is already set — an explicit --scope/--file
404
+ * resolved upstream in cli.mjs, or a test-only DI injection — this is a
405
+ * pure pass-through: no default-scope resolution, no notice. An explicit
406
+ * caller choice is never second-guessed.
407
+ */
408
+ export function resolveCommandPaths(overrides = {}) {
409
+ if (overrides.paths) return overrides.paths;
410
+
411
+ const { scope, detail } = resolveDefaultScope(overrides);
412
+ if (scope) {
413
+ const paths = resolveScopePaths({
414
+ scope,
415
+ cwd: overrides.cwd,
416
+ env: overrides.env,
417
+ home: overrides.home,
418
+ keyDir: overrides.keyDir,
419
+ keyFile: overrides.keyFile,
420
+ });
421
+ console.error(`note: writing to '${scope}' pot (default from ${detail})`);
422
+ return paths;
423
+ }
424
+
425
+ console.error("note: writing to '(legacy)' pot — no scope specified and no default configured. Use --scope <name> or configure a default.");
426
+ return defaultPaths(overrides);
427
+ }
428
+
429
+ // ── Vault discovery (no decryption) ──────────────────────────────────
430
+ //
431
+ // sops's JSON mode encrypts VALUES but keeps the top-level structure —
432
+ // including key NAMES — in plaintext (values become "ENC[AES256_GCM,...]"
433
+ // strings; a `sops` metadata key is appended). That means enumeration and
434
+ // key-name search never need the age private key and never touch secret
435
+ // material. Verified against real fixtures in the test suite; if a vault
436
+ // were ever created with encrypted_regex hiding key names, the reads
437
+ // below would simply not match and the count degrades to null ('?').
438
+
439
+ /**
440
+ * Read the plaintext top-level key names of a sops-encrypted JSON file
441
+ * WITHOUT decrypting. Returns null when the file is missing/unparseable.
442
+ */
443
+ export function readVaultKeyNames(filePath) {
444
+ try {
445
+ const parsed = JSON.parse(readFileSync(filePath, 'utf-8'));
446
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null;
447
+ return Object.keys(parsed).filter((k) => k !== 'sops').sort();
448
+ } catch {
449
+ return null;
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Enumerate every vault visible from a workspace root:
455
+ * legacy .vault/secrets.enc.json, workspace scopes .vault/scopes/*.enc.json,
456
+ * canonical vault/*.enc.json. Returns [{ scope, layout, path }].
457
+ */
458
+ export function enumerateVaults(root) {
459
+ const found = [];
460
+ const legacy = join(root, '.vault', 'secrets.enc.json');
461
+ if (existsSync(legacy)) {
462
+ found.push({ scope: '(legacy)', layout: 'legacy', path: legacy });
463
+ }
464
+ const scopesDir = join(root, '.vault', 'scopes');
465
+ if (existsSync(scopesDir)) {
466
+ try {
467
+ for (const f of readdirSync(scopesDir).sort()) {
468
+ if (f.endsWith('.enc.json')) {
469
+ found.push({ scope: f.slice(0, -'.enc.json'.length), layout: 'workspace-scoped', path: join(scopesDir, f) });
470
+ }
471
+ }
472
+ } catch { /* unreadable dir — skip */ }
473
+ }
474
+ const canonDir = join(root, 'vault');
475
+ if (existsSync(canonDir)) {
476
+ try {
477
+ for (const f of readdirSync(canonDir).sort()) {
478
+ if (f.endsWith('.enc.json')) {
479
+ found.push({ scope: f.slice(0, -'.enc.json'.length), layout: 'canonical', path: join(canonDir, f) });
480
+ }
481
+ }
482
+ } catch { /* unreadable dir — skip */ }
483
+ }
484
+ return found;
485
+ }
486
+
487
+ // ── Spawn chokepoint ─────────────────────────────────────────────────
488
+
489
+ /**
490
+ * Run a command and return { code, stdout, stderr }. Args passed as an
491
+ * array — never string-joined — so there is no shell interpolation
492
+ * surface. Optional env vars (e.g. SOPS_AGE_KEY_FILE) are merged into
493
+ * process.env for the child.
494
+ */
495
+ export function runCmd(cmd, args, options = {}) {
496
+ return new Promise((resolvePromise, rejectPromise) => {
497
+ const child = spawn(cmd, args, {
498
+ env: { ...process.env, ...(options.env || {}) },
499
+ stdio: ['pipe', 'pipe', 'pipe'],
500
+ });
501
+ let stdout = '';
502
+ let stderr = '';
503
+ child.stdout.on('data', (d) => { stdout += d.toString(); });
504
+ child.stderr.on('data', (d) => { stderr += d.toString(); });
505
+ child.on('error', (err) => rejectPromise(err));
506
+ child.on('close', (code) => resolvePromise({ code, stdout, stderr }));
507
+ if (options.stdin != null) {
508
+ child.stdin.write(options.stdin);
509
+ child.stdin.end();
510
+ } else {
511
+ child.stdin.end();
512
+ }
513
+ });
514
+ }
515
+
516
+ // ── Dependency checks ────────────────────────────────────────────────
517
+
518
+ export async function hasBinary(name) {
519
+ try {
520
+ const { code } = await runCmd('/bin/sh', ['-c', `command -v ${name}`]);
521
+ return code === 0;
522
+ } catch {
523
+ return false;
524
+ }
525
+ }
526
+
527
+ export async function checkDeps() {
528
+ const missing = [];
529
+ if (!(await hasBinary('sops'))) missing.push('sops');
530
+ if (!(await hasBinary('age'))) missing.push('age');
531
+ return missing;
532
+ }
533
+
534
+ export function depInstallMessage(name) {
535
+ if (name === 'sops') {
536
+ return [
537
+ 'Error: sops is not installed.',
538
+ '',
539
+ 'Install options:',
540
+ ' macOS: brew install sops',
541
+ ' Debian/Ubuntu: Download from https://github.com/getsops/sops/releases',
542
+ ' Manual: https://github.com/getsops/sops/releases/latest',
543
+ ].join('\n');
544
+ }
545
+ if (name === 'age') {
546
+ return [
547
+ 'Error: age is not installed.',
548
+ '',
549
+ 'Install options:',
550
+ ' macOS: brew install age',
551
+ ' Debian/Ubuntu: sudo apt install age',
552
+ ' Manual: https://github.com/FiloSottile/age/releases/latest',
553
+ ].join('\n');
554
+ }
555
+ return `Error: ${name} is not installed.`;
556
+ }
557
+
558
+ // ── Vault state checks ───────────────────────────────────────────────
559
+
560
+ export function checkVault(paths) {
561
+ // A missing private key is treated as an operational failure (exit 2):
562
+ // the operator either lost their key or never installed it on this host,
563
+ // and remediation requires backup-restore or key transfer — not just
564
+ // running `secrets init` (which would generate a NEW key and orphan the
565
+ // encrypted store). A missing recipients file, by contrast, is a clean
566
+ // "vault not initialised" state (exit 1) — `secrets init` is the right
567
+ // next step.
568
+ if (!existsSync(paths.keyFile)) {
569
+ return {
570
+ ok: false,
571
+ reason: 'missing-key',
572
+ exitCode: 2,
573
+ message: `Error: No age private key found at ${paths.keyFile}\nRestore your key from backup, or run 'secrets init' to start a fresh vault.`,
574
+ };
575
+ }
576
+ // Scoped vaults have no recipients.txt — recipients live in the scope's
577
+ // own .sops.yaml creation_rules — so the equivalent "not set up" signal
578
+ // is a missing sops config rather than a missing recipients file.
579
+ if (paths.scoped) {
580
+ if (!existsSync(paths.sopsConfig)) {
581
+ return {
582
+ ok: false,
583
+ reason: 'missing-sops-config',
584
+ exitCode: 1,
585
+ message: `Error: No SOPS config found at ${paths.sopsConfig}\nScoped vaults are provisioned by the estate's scope tooling, not 'secrets init'.`,
586
+ };
587
+ }
588
+ return { ok: true };
589
+ }
590
+ if (!existsSync(paths.recipientsFile)) {
591
+ return {
592
+ ok: false,
593
+ reason: 'missing-recipients',
594
+ exitCode: 1,
595
+ message: `Error: No recipients file at ${paths.recipientsFile}\nRun 'secrets init' to set up the vault.`,
596
+ };
597
+ }
598
+ return { ok: true };
599
+ }
600
+
601
+ export function getPublicKey(paths) {
602
+ const contents = readFileSync(paths.recipientsFile, 'utf-8');
603
+ const first = contents.split('\n')[0];
604
+ return (first || '').trim();
605
+ }
606
+
607
+ // ── SOPS/age helpers ─────────────────────────────────────────────────
608
+
609
+ /** age-keygen -o <keyFile> */
610
+ export async function ageKeygen(paths) {
611
+ const { code, stderr } = await runCmd('age-keygen', ['-o', paths.keyFile]);
612
+ if (code !== 0) {
613
+ throw Object.assign(new Error(`age-keygen failed: ${stderr.trim()}`), { exitCode: 3 });
614
+ }
615
+ }
616
+
617
+ /** Extract the public key line from an age key file. */
618
+ export function publicKeyFromKeyFile(keyFile) {
619
+ const contents = readFileSync(keyFile, 'utf-8');
620
+ const line = contents.split('\n').find((l) => l.includes('public key:'));
621
+ if (!line) return null;
622
+ const parts = line.trim().split(/\s+/);
623
+ return parts[parts.length - 1];
624
+ }
625
+
626
+ /** Extract the AGE-SECRET-KEY- line from an age key file. */
627
+ export function secretKeyFromKeyFile(keyFile) {
628
+ const contents = readFileSync(keyFile, 'utf-8');
629
+ const line = contents.split('\n').find((l) => l.startsWith('AGE-SECRET-KEY-'));
630
+ return line || null;
631
+ }
632
+
633
+ /** Decrypt an encrypted file, return the JSON object it contains. */
634
+ export async function sopsDecrypt(paths, filePath) {
635
+ const { code, stdout, stderr } = await runCmd('sops', ['--config', paths.sopsConfig, '--decrypt', filePath], {
636
+ env: { SOPS_AGE_KEY_FILE: paths.keyFile },
637
+ });
638
+ if (code !== 0) {
639
+ throw Object.assign(new Error(`sops decrypt failed: ${stderr.trim()}`), { exitCode: 2 });
640
+ }
641
+ return JSON.parse(stdout);
642
+ }
643
+
644
+ /** Encrypt a JSON object to the secrets file with the given public key recipient. */
645
+ export async function sopsEncrypt(paths, pubKey, obj) {
646
+ const tmp = mkdtempSync(join(tmpdir(), 'vault-'));
647
+ // Name the tmpfile with the .enc.json suffix so the creation_rule in
648
+ // the vault's own .sops.yaml matches (path_regex: \.enc\.json$). If the
649
+ // suffix doesn't match, sops errors with "no matching creation rules".
650
+ const tmpFile = join(tmp, 'secrets.enc.json');
651
+ writeFileSync(tmpFile, JSON.stringify(obj, null, 2) + '\n');
652
+ try {
653
+ // --config pins the sops config to the vault's own .sops.yaml so sops
654
+ // doesn't walk up from CWD and hit an unrelated ambient .sops.yaml
655
+ // (e.g. an older vault's config at a higher directory).
656
+ const { code, stdout, stderr } = await runCmd('sops', ['--config', paths.sopsConfig, '--encrypt', '--age', pubKey, tmpFile], {
657
+ env: { SOPS_AGE_KEY_FILE: paths.keyFile },
658
+ });
659
+ if (code !== 0) {
660
+ throw Object.assign(new Error(`sops encrypt failed: ${stderr.trim()}`), { exitCode: 2 });
661
+ }
662
+ writeFileSync(paths.secretsFile, stdout);
663
+ } finally {
664
+ try { unlinkSync(tmpFile); } catch { /* ignore */ }
665
+ }
666
+ }
667
+
668
+ /**
669
+ * Decrypt an EXISTING store as the precondition for a write (set/delete/
670
+ * rotate). A failed decrypt REFUSES the operation — proceeding to encrypt
671
+ * would replace the store's entire contents with whatever the caller was
672
+ * about to write (the vault-clobber/data-loss class: any host whose age key
673
+ * is not a recipient of this vault would wipe it with a single `set`).
674
+ * Prints the refusal (main() does not print messages for errors carrying an
675
+ * exitCode) naming the store and the key resolution attempted, then throws
676
+ * VaultError exit 2.
677
+ */
678
+ export async function decryptStoreForWrite(paths) {
679
+ try {
680
+ return await sopsDecrypt(paths, paths.secretsFile);
681
+ } catch (err) {
682
+ const detail = err && err.message ? err.message : String(err);
683
+ const message = [
684
+ `Error: refusing to modify ${paths.secretsFile}`,
685
+ ` the existing store could not be decrypted with the age key at ${paths.keyFile} (via SOPS_AGE_KEY_FILE)`,
686
+ ` cause: ${detail}`,
687
+ "Proceeding would re-encrypt and REPLACE the store's contents. Fix key access first — is this host's key a recipient of this vault?",
688
+ ].join('\n');
689
+ console.error(message);
690
+ throw new VaultError(message, 2);
691
+ }
692
+ }
693
+
694
+ /**
695
+ * Encrypt a JSON object to a SCOPED secrets file. Unlike sopsEncrypt(), no
696
+ * `--age` recipient is passed on the command line — recipients come purely
697
+ * from the scope's own .sops.yaml creation_rule, matched by filename. The
698
+ * tmp file is named to match paths.secretsFile's basename (not the fixed
699
+ * 'secrets.enc.json' sopsEncrypt() uses) so the creation_rule's path_regex
700
+ * (e.g. `wsd\.enc\.json$`) actually matches.
701
+ */
702
+ export async function sopsEncryptScoped(paths, obj) {
703
+ const tmp = mkdtempSync(join(tmpdir(), 'vault-'));
704
+ const tmpFile = join(tmp, basename(paths.secretsFile));
705
+ writeFileSync(tmpFile, JSON.stringify(obj, null, 2) + '\n');
706
+ try {
707
+ const { code, stdout, stderr } = await runCmd('sops', ['--config', paths.sopsConfig, '--encrypt', tmpFile], {
708
+ env: { SOPS_AGE_KEY_FILE: paths.keyFile },
709
+ });
710
+ if (code !== 0) {
711
+ throw Object.assign(new Error(`sops encrypt failed: ${stderr.trim()}`), { exitCode: 2 });
712
+ }
713
+ writeFileSync(paths.secretsFile, stdout);
714
+ } finally {
715
+ try { unlinkSync(tmpFile); } catch { /* ignore */ }
716
+ }
717
+ }
718
+
719
+ // ── Filesystem helpers ───────────────────────────────────────────────
720
+
721
+ export function ensureKeyDir(paths) {
722
+ if (!existsSync(paths.keyDir)) {
723
+ mkdirSync(paths.keyDir, { recursive: true, mode: 0o700 });
724
+ } else {
725
+ try { chmodSync(paths.keyDir, 0o700); } catch { /* non-fatal */ }
726
+ }
727
+ }
728
+
729
+ export function ensureVaultDir(paths) {
730
+ if (!existsSync(paths.vaultDir)) {
731
+ mkdirSync(paths.vaultDir, { recursive: true });
732
+ }
733
+ }
734
+
735
+ export function chmodKeyFile(paths) {
736
+ try { chmodSync(paths.keyFile, 0o600); } catch { /* non-fatal */ }
737
+ }
738
+
739
+ export function writeRecipients(paths, pubKey) {
740
+ writeFileSync(paths.recipientsFile, `${pubKey}\n`);
741
+ }
742
+
743
+ export function writeSopsConfig(paths, pubKey) {
744
+ const body = [
745
+ 'creation_rules:',
746
+ ' - path_regex: \\.enc\\.json$',
747
+ ' age: >-',
748
+ ` ${pubKey}`,
749
+ '',
750
+ ].join('\n');
751
+ writeFileSync(paths.sopsConfig, body);
752
+ }
753
+
754
+ export function keyFileExists(paths) { return existsSync(paths.keyFile); }
755
+ export function secretsFileExists(paths) { return existsSync(paths.secretsFile); }
756
+ export function recipientsFileExists(paths) { return existsSync(paths.recipientsFile); }
757
+ export function sopsConfigExists(paths) { return existsSync(paths.sopsConfig); }
758
+
759
+ // ── Error with exit code ─────────────────────────────────────────────
760
+
761
+ export class VaultError extends Error {
762
+ constructor(message, exitCode = 1) {
763
+ super(message);
764
+ this.exitCode = exitCode;
765
+ }
766
+ }