@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,214 @@
1
+ # pa launch — agent launcher
2
+
3
+ Identity: `@stravica-ai/engineering-pa-cli` › `launch` — boot a scoped agent instance bound to an identity and task.
4
+
5
+ ## Status
6
+
7
+ **Shipped.** Handles single-agent single-task execution against any operator repo with roster and schedule files. Dry-run mode. Audit log per tooling standard §15. Per-task concurrency lock, default-on (see "Concurrency lock" below).
8
+
9
+ ## Install / invoke
10
+
11
+ ```bash
12
+ # Via pa top-level CLI
13
+ pnpm exec pa launch --agent=<name> --task=<id> [--dry-run]
14
+
15
+ # Via standalone bin
16
+ pnpm exec launch --agent=<name> --task=<id> [--dry-run]
17
+ ```
18
+
19
+ ## Commands
20
+
21
+ ### `launch --agent=<name> --task=<id> [--dry-run]`
22
+
23
+ Boots a scoped agent instance:
24
+
25
+ 1. Resolves the operator repo root (`DAVE_REPO_ROOT` / `PA_REPO_ROOT` env var, or `.git` walk from `cwd`).
26
+ 2. Loads identity from `operator/knowledge/hr/roster/<name>.json`.
27
+ 3. Loads task definition from `schedules/<id>.json`.
28
+ 4. Builds a `LaunchPlan` (pure — no I/O).
29
+ 5. On `--dry-run`: prints the plan as JSON, exits 0.
30
+ 6. Otherwise: via the chokepoint runner — creates intake folder, spawns task command, writes audit record.
31
+
32
+ **Flags:**
33
+
34
+ | Flag | Required | Description |
35
+ |---|---|---|
36
+ | `--agent` | yes | Agent name — maps to `operator/knowledge/hr/roster/<name>.json` |
37
+ | `--task` | yes | Task ID — maps to `schedules/<id>.json` |
38
+ | `--dry-run` | no | Print LaunchPlan as JSON; skip execution and audit write |
39
+ | `-h`, `--help` | no | Show usage |
40
+
41
+ ## Identity file schema
42
+
43
+ Loaded from `operator/knowledge/hr/roster/<name>.json`. **Format: JSON.** No YAML/frontmatter parser — the roster is already JSON across the platform.
44
+
45
+ Required fields:
46
+
47
+ | Field | Type | Description |
48
+ |---|---|---|
49
+ | `id` | string | Agent identifier (must match filename stem) |
50
+ | `name` | string | Display name |
51
+ | `roleId` | string | Role identifier (e.g. `system-admin`) |
52
+ | `runFolder` | string | Path relative to repo root (e.g. `agents/ian`) |
53
+ | `status` | string | Agent status (`active` \| other) |
54
+
55
+ Optional fields (`currentTeam`, `createdBy`, `createdDate`, `history`) are preserved but not read by the launcher.
56
+
57
+ ## Task definition schema
58
+
59
+ Loaded from `schedules/<id>.json`. **Format: JSON.**
60
+
61
+ Required fields:
62
+
63
+ | Field | Type | Description |
64
+ |---|---|---|
65
+ | `id` | string | Task identifier (must match filename stem) |
66
+ | `command` | string | Command to execute — split on whitespace, passed to `spawn` without a shell. **Must not contain `'` or `"`** (no shell-quoting — see below) |
67
+
68
+ Optional fields:
69
+
70
+ | Field | Type | Description |
71
+ |---|---|---|
72
+ | `workingDirectory` | string | cwd for spawn; supports `${DAVE_ROOT}`, `${PA_REPO_ROOT}`, `${DAVE_REPO_ROOT}` tokens |
73
+ | `name` | string | Display name |
74
+ | `description` | string | Human-readable description |
75
+ | `schedule` | string | Cron expression (read by cron — not used by launcher) |
76
+ | `enabled` | boolean | Read by scheduler — not used by launcher |
77
+ | `lockStaleAfterMinutes` | number | Per-task staleness override for the concurrency lock (see below). Default: the core lock's 2-hour threshold |
78
+
79
+ ## `command` is not a shell expression
80
+
81
+ The runner spawns the command **directly** — no shell, no expansion, no operators. The string is split on whitespace and handed to `child_process.spawn()` as `(bin, args[])`.
82
+
83
+ If the command contains `'` or `"`, `task-loader` rejects it at load time with exit code 2 (a `--dry-run` also fails). This is a deliberate guard: silently splitting `bash -c "a && b"` on whitespace shreds it to `spawn('bash', ['-c', '"a', '&&', 'b"'])`, bash exits 2 with `unexpected EOF`, and cron mails the operator every interval until someone notices.
84
+
85
+ If you need shell features, you have two options:
86
+
87
+ 1. **Wrap the work in a script file** and put the script path in `command`: `command: "/path/to/wrapper.sh"`. The runner spawns the script; the script does whatever it likes.
88
+ 2. **Move the shell wrapper to the cron entry**, not the task command. The scheduler installer can take a quoted bash invocation in the cron crontab; the launcher's `command` field cannot.
89
+
90
+ ## Concurrency lock (default-on)
91
+
92
+ Two runs of the **same task** must not overlap — a second concurrent run
93
+ consumes the same inputs (staging areas) and produces duplicate effects and
94
+ git races. Every non-dry-run launch therefore acquires a per-task lock via
95
+ the shared core utility (`@stravica-ai/engineering-pa-core` › `acquireLock` —
96
+ full contract in `packages/core/docs/lock.md`) before spawning. No opt-in
97
+ flag; protection is the default.
98
+
99
+ - **Lock path:** `<repoRoot>/.cache/launch/<taskId>.lock` — per task, so two
100
+ *different* tasks overlap freely. `.cache/` is the gitignored
101
+ operational-state area (tooling standard §16).
102
+ - **On contention** (live same-task lock): the run **exits 0** with a
103
+ greppable skip line on stdout —
104
+ `[launch] <agent>/<task>: skipped — lock held (held by pid <pid> since <ts>; lock <path>)`
105
+ — writes a `noop-locked` audit record, and spawns nothing. Exit 0 (not 2)
106
+ because an overlap skip is a healthy no-op on a cron schedule, matching
107
+ `sync`'s `noop-locked` precedent; exit 2 would generate cron mail for
108
+ expected behaviour. Automation that needs to detect skips reads the audit
109
+ record or greps the log line.
110
+ - **Stale locks are broken automatically** with a
111
+ `[launch] warning: broke stale lock ...` line on stderr, and the run
112
+ proceeds. Staleness: dead pid (primary — recovers crashed runs
113
+ immediately), or age past the threshold (backstop for pid reuse /
114
+ unreadable lock files). Threshold: the core default of **2 hours**
115
+ (sized so legitimately long sweeps — observed 1288+ seconds — are never
116
+ stolen mid-run), overridable per task via `lockStaleAfterMinutes` in
117
+ `schedules/<id>.json`.
118
+ - **Release:** on every exit path (success, task failure, spawn error). A
119
+ crashed run leaves a dead-pid lock that the next tick breaks.
120
+ - **First run:** no lock file or `.cache/launch/` directory needed; the
121
+ empty state is the common case.
122
+
123
+ ## LaunchPlan object
124
+
125
+ The planner produces a `LaunchPlan` (JSON-serialisable, no I/O):
126
+
127
+ ```jsonc
128
+ {
129
+ "repoRoot": "/abs/path/to/operator-repo",
130
+ "agent": { "id": "ian", "name": "Ian", "roleId": "system-admin", "runFolder": "agents/ian" },
131
+ "task": { "id": "upstream-triage", "command": "node ...", "workingDirectory": null, "description": null },
132
+ "intakeFolderPath": "/abs/path/to/agents/ian/staging",
133
+ "auditLogPath": "/abs/path/to/agents/ian/runs.jsonl",
134
+ "lockPath": "/abs/path/to/.cache/launch/upstream-triage.lock",
135
+ "lockStaleAfterMs": null // null = core default (2 h); set via task lockStaleAfterMinutes
136
+ }
137
+ ```
138
+
139
+ ## Chokepoint discipline
140
+
141
+ `runner.mjs` is the **sole module** that:
142
+ - Creates directories (`mkdir`)
143
+ - Appends audit records (`appendFile`)
144
+ - Acquires/releases the per-task concurrency lock
145
+ - Spawns child processes (`spawn`)
146
+
147
+ The lock lives in the chokepoint so programmatic callers cannot bypass it:
148
+ a plan handed to `execute()` without a `lockPath` still gets a derived
149
+ per-task lock.
150
+
151
+ All other modules (`identity.mjs`, `task-loader.mjs`, `planner.mjs`) are pure — no I/O, no spawning. Tests inject a fake spawner into the runner; no mock libraries.
152
+
153
+ Launched agents **must not** do git operations. They write files to disk; the sync tool (`pa sync`) picks up changes on its tick.
154
+
155
+ ## Exit codes (tooling standard §6)
156
+
157
+ | Code | Meaning |
158
+ |---|---|
159
+ | `0` | OK |
160
+ | `1` | Usage error (missing flags, unknown flag) |
161
+ | `2` | Logical failure (agent not found, task not found, missing required field) |
162
+ | `3` | Transport / IO failure (file unreadable, spawn failed) |
163
+
164
+ ## Audit record contract (tooling standard §15)
165
+
166
+ Every non-dry-run exit path appends one JSON line to `<runFolder>/runs.jsonl`:
167
+
168
+ ```jsonc
169
+ { "ts": "2026-05-17T12:00:00Z", "status": "ok", "agentId": "ian", "taskId": "upstream-triage", "exitCode": 0 }
170
+ ```
171
+
172
+ `status` vocabulary: `ok` | `noop-locked` | `fail-exit-<N>` | `fail-spawn`
173
+
174
+ `noop-locked` records carry a `reason` field naming the lock holder
175
+ (`"lock contention: held by pid <pid> since <ts>"`). Per §15 monitors treat
176
+ `noop-*` as healthy/fresh.
177
+
178
+ `runs.jsonl` is an operational artefact — it must be gitignored in the operator repo. It is never committed.
179
+
180
+ ## Project layout
181
+
182
+ ```
183
+ packages/cli/src/launch/
184
+ README.md this file — the contract
185
+ cli.mjs arg parsing, flag validation, exit-code translation
186
+ commands/
187
+ launch.mjs thin glue: load → plan → execute
188
+ identity.mjs pure: load + validate agent identity from roster JSON
189
+ task-loader.mjs pure: load + validate task definition from schedules JSON
190
+ planner.mjs pure: build LaunchPlan object (no I/O)
191
+ repo-root.mjs pure: operator repo root discovery (env var + .git walk)
192
+ runner.mjs CHOKEPOINT: all file writes + process spawn
193
+ packages/cli/test/launch/
194
+ cli.test.mjs end-to-end: --help, --dry-run, live execution, error paths
195
+ identity.test.mjs unit: happy path, missing agent, invalid JSON, missing field
196
+ task-loader.test.mjs unit: happy path, missing task, invalid JSON, missing field
197
+ planner.test.mjs unit: output shape, null defaults, purity
198
+ runner.test.mjs unit: ok/fail/spawn-error paths via fake spawner
199
+ fixtures/ synthetic roster + schedule for tests
200
+ ```
201
+
202
+ ## Tests
203
+
204
+ ```bash
205
+ # Run launch tests only
206
+ node --test 'packages/cli/test/launch/**/*.test.mjs'
207
+
208
+ # Run all CLI package tests
209
+ pnpm --filter @stravica-ai/engineering-pa-cli test
210
+ ```
211
+
212
+ ## Standards
213
+
214
+ Follows the [tooling standard](../../../../core/knowledge/standards/tooling.md): Node.js only, zero npm deps, `node:test`, `node:util.parseArgs` with `strict: true`, camelCase JSON, chokepoint pattern (§7), audit record contract (§15), gitignore operational artefacts (§16).
@@ -0,0 +1,51 @@
1
+ // cli.mjs — pa launch: boot a scoped agent instance bound to an identity and task.
2
+ import { parseArgs } from 'node:util';
3
+
4
+ const USAGE = `pa launch — boot a scoped agent instance bound to an identity and task
5
+
6
+ Usage:
7
+ pa launch --agent=<name> --task=<id> [--dry-run]
8
+ pa launch -h, --help
9
+
10
+ Options:
11
+ --agent Agent name — maps to operator/knowledge/hr/roster/<name>.json
12
+ --task Task ID — maps to schedules/<id>.json
13
+ --dry-run Print the LaunchPlan as JSON without executing
14
+ -h, --help Show this help
15
+
16
+ Exit codes (tooling standard §6):
17
+ 0 ok | 1 usage error | 2 operational failure | 3 transport/IO failure
18
+ `;
19
+
20
+ export async function run(argv) {
21
+ if (argv.includes('-h') || argv.includes('--help')) {
22
+ process.stdout.write(USAGE);
23
+ return 0;
24
+ }
25
+
26
+ let values;
27
+ try {
28
+ ({ values } = parseArgs({
29
+ args: argv,
30
+ options: {
31
+ agent: { type: 'string' },
32
+ task: { type: 'string' },
33
+ 'dry-run': { type: 'boolean', default: false },
34
+ },
35
+ strict: true,
36
+ }));
37
+ } catch (err) {
38
+ process.stderr.write(`[pa launch] ${err.message}\n`);
39
+ process.stderr.write(USAGE);
40
+ return 1;
41
+ }
42
+
43
+ if (!values.agent || !values.task) {
44
+ process.stderr.write('[pa launch] --agent and --task are required\n');
45
+ process.stderr.write(USAGE);
46
+ return 1;
47
+ }
48
+
49
+ const { executeLaunch } = await import('./commands/launch.mjs');
50
+ return await executeLaunch(values.agent, values.task, { dryRun: values['dry-run'] });
51
+ }
@@ -0,0 +1,106 @@
1
+ // launch.mjs — thin glue: load identity → load task → build plan → execute.
2
+ import { hostname } from 'node:os';
3
+
4
+ import { hostMatches } from '../../../../engineering-pa-core/src/scheduler/host.mjs';
5
+
6
+ import { getRepoRoot } from '../repo-root.mjs';
7
+ import { loadIdentity } from '../identity.mjs';
8
+ import { loadTask } from '../task-loader.mjs';
9
+ import { buildPlan } from '../planner.mjs';
10
+ import { execute } from '../runner.mjs';
11
+
12
+ // Override env var for the host-enforcement gate below — mirrors
13
+ // `scheduler install`'s `--force-host` CLI flag, but as an env var since
14
+ // `launch` is invoked automatically by cron/launchd/systemd (no operator
15
+ // present to pass a flag). Same override SEMANTICS as install, different
16
+ // delivery mechanism because the calling context is different.
17
+ const FORCE_HOST_ENV = 'DAVE_FORCE_HOST';
18
+
19
+ export async function executeLaunch(agentName, taskId, { dryRun = false } = {}) {
20
+ let repoRoot;
21
+ try {
22
+ repoRoot = await getRepoRoot();
23
+ } catch (err) {
24
+ process.stderr.write(`[launch] ${err.message}\n`);
25
+ return err.exitCode ?? 2;
26
+ }
27
+
28
+ let identity;
29
+ try {
30
+ identity = await loadIdentity(repoRoot, agentName);
31
+ } catch (err) {
32
+ process.stderr.write(`[launch] identity: ${err.message}\n`);
33
+ return err.exitCode ?? 2;
34
+ }
35
+
36
+ let task;
37
+ try {
38
+ task = await loadTask(repoRoot, taskId);
39
+ } catch (err) {
40
+ process.stderr.write(`[launch] task: ${err.message}\n`);
41
+ return err.exitCode ?? 2;
42
+ }
43
+
44
+ // Host-enforcement gate — refuse to RUN a task whose schedule file
45
+ // declares a foreign host, mirroring `scheduler install`'s refusal.
46
+ // Missing `host` field is not a constraint (data backfill is
47
+ // estate-side); DAVE_FORCE_HOST=1 overrides a mismatch, same semantics
48
+ // as install's --force-host, just delivered via env since launch is
49
+ // invoked non-interactively. --dry-run is judgement-call exempt from
50
+ // the hard refusal (it never executes anything — a plan preview from a
51
+ // non-owning host, e.g. a Project Lead debugging another host's task,
52
+ // is legitimate) but still surfaces the mismatch as a warning so it's
53
+ // never silently invisible.
54
+ if (task.host) {
55
+ const currentHost = hostname();
56
+ const forceHost = process.env[FORCE_HOST_ENV] === '1' || process.env[FORCE_HOST_ENV] === 'true';
57
+ if (!hostMatches(task.host, currentHost)) {
58
+ if (dryRun) {
59
+ process.stderr.write(
60
+ `[launch] warning: task "${taskId}" declares host "${task.host}" but this machine is `
61
+ + `"${currentHost}" — dry-run proceeds (would refuse on a real run without ${FORCE_HOST_ENV}=1)\n`,
62
+ );
63
+ } else if (forceHost) {
64
+ process.stderr.write(
65
+ `[launch] warning: task "${taskId}" declares host "${task.host}" but this machine is `
66
+ + `"${currentHost}" — proceeding anyway due to ${FORCE_HOST_ENV}=1\n`,
67
+ );
68
+ } else {
69
+ process.stderr.write(
70
+ `[launch] task "${taskId}" declares host "${task.host}" but this machine is `
71
+ + `"${currentHost}" — refusing to run. Set ${FORCE_HOST_ENV}=1 to override.\n`,
72
+ );
73
+ return 2;
74
+ }
75
+ }
76
+ }
77
+
78
+ const plan = buildPlan(repoRoot, identity, task);
79
+
80
+ if (dryRun) {
81
+ process.stdout.write(JSON.stringify(plan, null, 2) + '\n');
82
+ return 0;
83
+ }
84
+
85
+ try {
86
+ const result = await execute(plan);
87
+ if (result.status === 'noop-locked') {
88
+ // Overlapping run of the same task — healthy skip, exit 0 (cron
89
+ // treats it as a no-op; sync's noop-locked precedent). Greppable.
90
+ process.stdout.write(
91
+ `[launch] ${agentName}/${taskId}: skipped — lock held (${result.reason}; lock ${result.lockPath})\n`,
92
+ );
93
+ return 0;
94
+ }
95
+ if (result.status === 'ok') {
96
+ process.stdout.write(`[launch] ${agentName}/${taskId}: ok (exit ${result.exitCode})\n`);
97
+ return 0;
98
+ } else {
99
+ process.stderr.write(`[launch] ${agentName}/${taskId}: ${result.status} (exit ${result.exitCode})\n`);
100
+ return 2;
101
+ }
102
+ } catch (err) {
103
+ process.stderr.write(`[launch] runner: ${err.message}\n`);
104
+ return err.exitCode ?? 3;
105
+ }
106
+ }
@@ -0,0 +1,43 @@
1
+ // identity.mjs — pure: load + validate an agent identity from the operator roster.
2
+ // Roster path: <repoRoot>/operator/knowledge/hr/roster/<name>.json
3
+ // Required fields: id, name, roleId, runFolder, status
4
+ import { readFile } from 'node:fs/promises';
5
+ import { join } from 'node:path';
6
+
7
+ const REQUIRED_FIELDS = ['id', 'name', 'roleId', 'runFolder', 'status'];
8
+
9
+ export async function loadIdentity(repoRoot, agentName) {
10
+ const path = join(repoRoot, 'operator', 'knowledge', 'hr', 'roster', `${agentName}.json`);
11
+ let raw;
12
+ try {
13
+ raw = await readFile(path, 'utf8');
14
+ } catch (err) {
15
+ if (err.code === 'ENOENT') {
16
+ const e = new Error(`agent "${agentName}" not found in roster (${path})`);
17
+ e.exitCode = 2;
18
+ throw e;
19
+ }
20
+ const e = new Error(`could not read roster file for "${agentName}": ${err.message}`);
21
+ e.exitCode = 3;
22
+ throw e;
23
+ }
24
+
25
+ let data;
26
+ try {
27
+ data = JSON.parse(raw);
28
+ } catch (err) {
29
+ const e = new Error(`roster file for "${agentName}" is not valid JSON: ${err.message}`);
30
+ e.exitCode = 2;
31
+ throw e;
32
+ }
33
+
34
+ for (const field of REQUIRED_FIELDS) {
35
+ if (!data[field]) {
36
+ const e = new Error(`identity "${agentName}": missing required field "${field}"`);
37
+ e.exitCode = 2;
38
+ throw e;
39
+ }
40
+ }
41
+
42
+ return data;
43
+ }
@@ -0,0 +1,44 @@
1
+ // planner.mjs — pure: produce a LaunchPlan from loaded identity + task.
2
+ // No I/O. No spawning. Returns a plain object.
3
+ import { join } from 'node:path';
4
+
5
+ /**
6
+ * LaunchPlan shape:
7
+ * repoRoot — absolute path to the operator repo
8
+ * agent — { id, name, roleId, runFolder }
9
+ * task — { id, command, workingDirectory, description }
10
+ * intakeFolderPath — absolute path to agents/<name>/staging/
11
+ * auditLogPath — absolute path to agents/<name>/runs.jsonl
12
+ * lockPath — absolute path to .cache/launch/<taskId>.lock
13
+ * (per-task: two different tasks may overlap freely;
14
+ * two runs of the SAME task must not. `.cache/` is
15
+ * the platform's gitignored operational-state area —
16
+ * tooling standard §16.)
17
+ * lockStaleAfterMs — per-task staleness override in ms, from the task's
18
+ * optional `lockStaleAfterMinutes` field; null means
19
+ * "use the core lock default" (2 h — see
20
+ * packages/core/docs/lock.md for the rationale).
21
+ */
22
+ export function buildPlan(repoRoot, identity, task) {
23
+ return {
24
+ repoRoot,
25
+ agent: {
26
+ id: identity.id,
27
+ name: identity.name,
28
+ roleId: identity.roleId,
29
+ runFolder: identity.runFolder,
30
+ },
31
+ task: {
32
+ id: task.id,
33
+ command: task.command,
34
+ workingDirectory: task.workingDirectory ?? null,
35
+ description: task.description ?? null,
36
+ },
37
+ intakeFolderPath: join(repoRoot, identity.runFolder, 'staging'),
38
+ auditLogPath: join(repoRoot, identity.runFolder, 'runs.jsonl'),
39
+ lockPath: join(repoRoot, '.cache', 'launch', `${task.id}.lock`),
40
+ lockStaleAfterMs: Number.isFinite(task.lockStaleAfterMinutes) && task.lockStaleAfterMinutes > 0
41
+ ? task.lockStaleAfterMinutes * 60 * 1000
42
+ : null,
43
+ };
44
+ }
@@ -0,0 +1,53 @@
1
+ // repo-root.mjs — operator repo root discovery for the launch CLI.
2
+ // Mirrors the pattern in packages/core/src/hq/repo.mjs; own copy avoids
3
+ // cross-package coupling for a pure utility.
4
+ import { stat } from 'node:fs/promises';
5
+ import { join, resolve, dirname } from 'node:path';
6
+
7
+ async function pathKind(p) {
8
+ try {
9
+ const s = await stat(p);
10
+ if (s.isDirectory()) return 'dir';
11
+ if (s.isFile()) return 'file';
12
+ return 'other';
13
+ } catch {
14
+ return 'missing';
15
+ }
16
+ }
17
+
18
+ export async function findRepoRoot(start) {
19
+ let d = start;
20
+ while (d && d !== dirname(d)) {
21
+ const k = await pathKind(join(d, '.git'));
22
+ if (k === 'dir' || k === 'file') return d;
23
+ d = dirname(d);
24
+ }
25
+ return null;
26
+ }
27
+
28
+ let _cache = null;
29
+
30
+ export async function getRepoRoot() {
31
+ if (_cache) return _cache;
32
+ const envRoot = process.env.DAVE_REPO_ROOT || process.env.PA_REPO_ROOT;
33
+ if (envRoot) {
34
+ _cache = resolve(envRoot);
35
+ return _cache;
36
+ }
37
+ const found = await findRepoRoot(process.cwd());
38
+ if (!found) {
39
+ const err = new Error(
40
+ 'could not find operator repo root from ' + process.cwd() +
41
+ ' (no .git ancestor; set DAVE_REPO_ROOT or PA_REPO_ROOT to override)',
42
+ );
43
+ err.exitCode = 2;
44
+ throw err;
45
+ }
46
+ _cache = found;
47
+ return _cache;
48
+ }
49
+
50
+ // Test seam: reset the cached root between test cases.
51
+ export function _resetRepoRoot() {
52
+ _cache = null;
53
+ }
@@ -0,0 +1,147 @@
1
+ // runner.mjs — CHOKEPOINT: all file writes outside this module + all process spawn.
2
+ //
3
+ // Discipline: pure modules (identity, task-loader, planner) never touch disk.
4
+ // The runner is the sole module that creates directories, appends audit logs,
5
+ // acquires the per-task lock, and spawns child processes. Tests inject a fake
6
+ // spawner to avoid real I/O.
7
+ //
8
+ // Concurrency lock (default-on): before spawning, the runner acquires the
9
+ // per-task lock at plan.lockPath via the shared core utility
10
+ // (packages/core/docs/lock.md). Two runs of the same task must not overlap —
11
+ // the second run returns `noop-locked` (no spawn, audit record written, the
12
+ // caller exits 0 so cron treats the skip as a healthy no-op, matching sync's
13
+ // precedent). Stale locks (dead pid, or older than the staleness threshold —
14
+ // core default 2 h, per-task override via the task's `lockStaleAfterMinutes`)
15
+ // are broken automatically with a logged warning and the run proceeds. The
16
+ // lock is released on every exit path; a crashed run leaves a dead-pid lock
17
+ // that the next tick breaks.
18
+ //
19
+ // Audit record contract (tooling standard §15):
20
+ // status — "ok" | "noop-locked" | "fail-exit-<N>" | "fail-spawn"
21
+ // ts — RFC-3339 (YYYY-MM-DDTHH:MM:SSZ, no milliseconds)
22
+ import { mkdir, appendFile } from 'node:fs/promises';
23
+ import { spawn } from 'node:child_process';
24
+ import { join } from 'node:path';
25
+ // Subpath import (not the bare package) — the engineering-pa dist bundle
26
+ // rewrites cross-package import strings to relative paths, and a bare
27
+ // package import would rewrite to a directory path ESM cannot resolve.
28
+ import { acquireLock } from '../../../engineering-pa-core/src/lock/lock.mjs';
29
+ import { assertNoShellQuoting } from './task-loader.mjs';
30
+
31
+ function resolveWorkdir(workdir, repoRoot) {
32
+ if (!workdir) return repoRoot;
33
+ return workdir
34
+ .replace(/\$\{DAVE_ROOT\}/g, repoRoot)
35
+ .replace(/\$\{PA_REPO_ROOT\}/g, repoRoot)
36
+ .replace(/\$\{DAVE_REPO_ROOT\}/g, repoRoot);
37
+ }
38
+
39
+ /**
40
+ * Parse a task command string into { bin, args } suitable for spawn().
41
+ *
42
+ * Contract: task commands are STRUCTURED (bin + literal args), not shell
43
+ * expressions. The string is split on whitespace and passed to spawn()
44
+ * WITHOUT a shell — no quoting, globbing, expansion, or operators are
45
+ * honoured. Any single (') or double (") quote character in the command
46
+ * is a configuration error: rejected via assertNoShellQuoting with
47
+ * exitCode 2 (task-loader also asserts this at load time so --dry-run
48
+ * catches it; the check here is the chokepoint guarantee for any caller
49
+ * that bypasses the loader).
50
+ *
51
+ * Why reject instead of parse: silently splitting a quoted command on
52
+ * whitespace shreds it ("bash -c \"a && b\"" → spawn('bash',
53
+ * ['-c', '"a', '&&', 'b"']) — bash exits 2, cron mails operator forever).
54
+ * If you need shell features, put them in a script file and invoke the
55
+ * script, or move the bash wrapper to the cron entry.
56
+ *
57
+ * @param {string} command command string from schedules/<id>.json
58
+ * @returns {{ bin: string, args: string[] }}
59
+ * @throws {Error & { exitCode: 2 }} on quoted or empty commands
60
+ */
61
+ function parseCommand(command) {
62
+ assertNoShellQuoting(command);
63
+ const parts = command.trim().split(/\s+/);
64
+ if (!parts[0]) {
65
+ const e = new Error('command is empty');
66
+ e.exitCode = 2;
67
+ throw e;
68
+ }
69
+ return { bin: parts[0], args: parts.slice(1) };
70
+ }
71
+
72
+ async function defaultSpawner(plan) {
73
+ const { bin, args } = parseCommand(plan.task.command);
74
+ const cwd = resolveWorkdir(plan.task.workingDirectory, plan.repoRoot);
75
+ return new Promise((resolve, reject) => {
76
+ const child = spawn(bin, args, { cwd, stdio: 'inherit' });
77
+ child.on('close', (code) => resolve(code ?? 0));
78
+ child.on('error', (err) => {
79
+ const e = new Error(`spawn failed for "${bin}": ${err.message}`);
80
+ e.exitCode = 3;
81
+ reject(e);
82
+ });
83
+ });
84
+ }
85
+
86
+ function nowIso() {
87
+ return new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
88
+ }
89
+
90
+ async function writeAuditRecord(plan, status, exitCode, extra = {}) {
91
+ const record = {
92
+ ts: nowIso(),
93
+ status,
94
+ agentId: plan.agent.id,
95
+ taskId: plan.task.id,
96
+ exitCode,
97
+ ...extra,
98
+ };
99
+ await appendFile(plan.auditLogPath, JSON.stringify(record) + '\n', 'utf8');
100
+ }
101
+
102
+ export async function execute(plan, {
103
+ spawner = defaultSpawner,
104
+ lockAcquire = acquireLock,
105
+ stderr = process.stderr,
106
+ } = {}) {
107
+ await mkdir(plan.intakeFolderPath, { recursive: true });
108
+
109
+ // Per-task concurrency lock — default-on. Programmatic callers that hand
110
+ // the runner a plan without lockPath still get protection: the chokepoint
111
+ // derives the same per-task path the planner would have chosen.
112
+ const lockPath = plan.lockPath ?? join(plan.repoRoot, '.cache', 'launch', `${plan.task.id}.lock`);
113
+ const lockOptions = {};
114
+ if (Number.isFinite(plan.lockStaleAfterMs) && plan.lockStaleAfterMs > 0) {
115
+ lockOptions.staleAfterMs = plan.lockStaleAfterMs;
116
+ }
117
+ const lock = await lockAcquire(lockPath, lockOptions);
118
+ if (!lock.acquired) {
119
+ // Same-task run already in flight — skip cleanly, spawn nothing.
120
+ await writeAuditRecord(plan, 'noop-locked', 0, { reason: `lock contention: ${lock.reason}` });
121
+ return { status: 'noop-locked', exitCode: 0, reason: lock.reason, lockPath };
122
+ }
123
+ if (lock.stale) {
124
+ stderr.write(
125
+ `[launch] warning: broke stale lock at ${lockPath} ` +
126
+ `(${lock.stale.reason}; pid ${lock.stale.pid ?? 'unknown'}, ` +
127
+ `acquired ${lock.stale.acquiredAt ?? 'unknown'}) — proceeding\n`,
128
+ );
129
+ }
130
+
131
+ try {
132
+ let exitCode;
133
+ try {
134
+ exitCode = await spawner(plan);
135
+ } catch (err) {
136
+ await writeAuditRecord(plan, 'fail-spawn', -1);
137
+ throw err;
138
+ }
139
+
140
+ const status = exitCode === 0 ? 'ok' : `fail-exit-${exitCode}`;
141
+ await writeAuditRecord(plan, status, exitCode);
142
+
143
+ return { status, exitCode };
144
+ } finally {
145
+ await lock.release();
146
+ }
147
+ }