@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,149 @@
1
+ // sync — git operations.
2
+ //
3
+ // Thin wrappers around the specific git invocations sync performs. Every
4
+ // function delegates spawning to the runner chokepoint — nothing here
5
+ // touches the outside world directly.
6
+ //
7
+ // The set is intentionally minimal: status, branch, ahead/behind, fetch,
8
+ // pull --rebase, rebase --abort, add -A, commit, push. Sync is the SOLE
9
+ // git writer on a host running HQ crons; if we ever need a new git
10
+ // operation here, it should be considered carefully — a wider surface
11
+ // invites the kind of race this tool exists to eliminate.
12
+
13
+ // `--untracked-files=all` is load-bearing, not cosmetic: plain
14
+ // `git status --porcelain` collapses a WHOLLY untracked directory to a
15
+ // single ancestor line (e.g. `?? agents/` when nothing under `agents/`
16
+ // is tracked), rather than listing each untracked file individually.
17
+ // registry-scope.mjs's exclusion check is a `startsWith` match against
18
+ // mount `to` prefixes (e.g. `agents/dave/`) — a collapsed `agents/` line
19
+ // does not start with `agents/dave/`, so it survives the exclusion
20
+ // filter and everything under it (mount-owned content included) gets
21
+ // staged. This only bites when the mount target has no tracked anchor
22
+ // file anywhere in its ancestry (fresh onboarding, no prior commits
23
+ // under that subtree) — exactly the case a brand-new mount hits. `=all`
24
+ // makes git enumerate every untracked file individually, so each path
25
+ // is checked against the exclusion prefixes on its own merits.
26
+ export async function statusPorcelain(runner, { cwd }) {
27
+ return runner.spawn('git', ['status', '--porcelain', '--untracked-files=all'], { cwd });
28
+ }
29
+
30
+ export async function currentBranch(runner, { cwd }) {
31
+ const r = await runner.spawn('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd });
32
+ return r.stdout.trim();
33
+ }
34
+
35
+ // Are we ahead of / behind the upstream tracking branch?
36
+ //
37
+ // Returns { ahead, behind }. Either may be NaN if upstream is missing —
38
+ // callers treat NaN ahead as "0 ahead" (nothing to push) and NaN behind as
39
+ // "no upstream tracking, do not pull".
40
+ export async function aheadBehind(runner, { cwd }) {
41
+ const r = await runner.spawn(
42
+ 'git',
43
+ ['rev-list', '--left-right', '--count', 'HEAD...@{u}'],
44
+ { cwd },
45
+ );
46
+ if (r.code !== 0) {
47
+ return { ahead: NaN, behind: NaN };
48
+ }
49
+ const parts = r.stdout.trim().split(/\s+/);
50
+ const ahead = Number.parseInt(parts[0], 10);
51
+ const behind = Number.parseInt(parts[1], 10);
52
+ return { ahead, behind };
53
+ }
54
+
55
+ export async function fetchOrigin(runner, { cwd }) {
56
+ return runner.spawn('git', ['fetch', 'origin'], { cwd });
57
+ }
58
+
59
+ export async function pullRebase(runner, { cwd, branch }) {
60
+ return runner.spawn('git', ['pull', '--rebase', '--quiet', 'origin', branch], { cwd });
61
+ }
62
+
63
+ export async function rebaseAbort(runner, { cwd }) {
64
+ return runner.spawn('git', ['rebase', '--abort'], { cwd });
65
+ }
66
+
67
+ export async function addAll(runner, { cwd }) {
68
+ return runner.spawn('git', ['add', '-A'], { cwd });
69
+ }
70
+
71
+ export async function hasStagedChanges(runner, { cwd }) {
72
+ const r = await runner.spawn('git', ['diff', '--cached', '--quiet'], { cwd });
73
+ return r.code !== 0;
74
+ }
75
+
76
+ // Commit with a title + optional multi-paragraph body. Passes message
77
+ // through argv (-m / -m) — runner spawns with shell:false so embedded
78
+ // quotes / newlines are safe without further escaping.
79
+ export async function commit(runner, { cwd, title, body }) {
80
+ const args = ['commit', '-m', title];
81
+ if (body && body.length > 0) {
82
+ args.push('-m', body);
83
+ }
84
+ return runner.spawn('git', args, { cwd });
85
+ }
86
+
87
+ export async function push(runner, { cwd }) {
88
+ return runner.spawn('git', ['push'], { cwd });
89
+ }
90
+
91
+ // Parse `git status --porcelain` output for paths currently in conflict.
92
+ // Git marks unmerged entries with an XY code from a fixed set (see
93
+ // git-status(1) `Short Format` — "Unmerged entries have the following
94
+ // second column"): DD, AU, UD, UA, DU, AA, UU. Anything else is a normal
95
+ // modification (M ), untracked (??), etc.
96
+ export async function listConflictedPaths(runner, { cwd }) {
97
+ const r = await runner.spawn('git', ['status', '--porcelain'], { cwd });
98
+ if (r.code !== 0) {
99
+ return { code: r.code, stderr: r.stderr, paths: [] };
100
+ }
101
+ const paths = [];
102
+ for (const line of r.stdout.split('\n')) {
103
+ // Porcelain v1 line: `XY <path>` where XY is exactly two chars
104
+ // followed by a space. Renamed entries use `XY <old> -> <new>` — the
105
+ // conflict codes below never apply to renames, so we treat the tail
106
+ // as the path verbatim.
107
+ if (line.length < 4) continue;
108
+ const xy = line.slice(0, 2);
109
+ if (xy === 'DD' || xy === 'AU' || xy === 'UD' || xy === 'UA'
110
+ || xy === 'DU' || xy === 'AA' || xy === 'UU') {
111
+ paths.push(line.slice(3));
112
+ }
113
+ }
114
+ return { code: 0, stderr: '', paths };
115
+ }
116
+
117
+ // Take upstream's version of `paths` during a rebase.
118
+ //
119
+ // GOTCHA — rebase inverts --ours / --theirs vs merge. During a rebase,
120
+ // `--ours` refers to the branch we are rebasing ONTO (upstream, i.e.
121
+ // origin's version) and `--theirs` refers to the branch that holds the
122
+ // commit being replayed (our local work). This is the opposite of merge,
123
+ // where `--ours` is the current branch. Since we want to keep origin's
124
+ // version and drop our fresh regeneration, we pass `--ours`. See the
125
+ // note in git-checkout(1): "Note that during git rebase and git pull
126
+ // --rebase, ours and theirs may appear swapped."
127
+ export async function checkoutOursDuringRebase(runner, { cwd, paths }) {
128
+ if (paths.length === 0) return { code: 0, stdout: '', stderr: '' };
129
+ return runner.spawn('git', ['checkout', '--ours', '--', ...paths], { cwd });
130
+ }
131
+
132
+ // Stage specific paths after conflict resolution. Uses `git add --` to
133
+ // stop any leading `-`-prefixed path being treated as a flag.
134
+ export async function addPaths(runner, { cwd, paths }) {
135
+ if (paths.length === 0) return { code: 0, stdout: '', stderr: '' };
136
+ return runner.spawn('git', ['add', '--', ...paths], { cwd });
137
+ }
138
+
139
+ // Continue an in-progress rebase after conflict resolution. `git rebase
140
+ // --continue` may want to open $EDITOR to confirm the message of the
141
+ // replayed commit; forcing GIT_EDITOR=true (the `true` binary, exit 0)
142
+ // makes it accept the existing message unmodified and continue.
143
+ export async function rebaseContinue(runner, { cwd }) {
144
+ return runner.spawn(
145
+ 'git',
146
+ ['rebase', '--continue'],
147
+ { cwd, env: { ...process.env, GIT_EDITOR: 'true' } },
148
+ );
149
+ }
@@ -0,0 +1,30 @@
1
+ // sync — process lock.
2
+ //
3
+ // Two sync runs on the same host MUST NOT overlap. A second instance
4
+ // trying to push while the first is mid-commit would just hit the same
5
+ // remote-rejected → rebase-retry path; on a slow host it could land in a
6
+ // loop. Cheaper to short-circuit at start.
7
+ //
8
+ // Implementation: delegates to the shared core lock utility
9
+ // (`@stravica-ai/engineering-pa-core` → `acquireLock`, see
10
+ // packages/core/docs/lock.md) with sync's own staleness window: 10
11
+ // minutes. The core default (2 hours) is sized for long agent sweeps;
12
+ // sync runs are seconds long on a 30-minute cron tick, so a 10-minute
13
+ // window is a safe, much faster self-heal for a wedged run. On stale,
14
+ // the lock is stolen; on contention with a live PID the caller exits `0`
15
+ // with status `noop-locked` — the next tick will retry.
16
+ //
17
+ // `acquire` keeps the historical sync signature and return shape
18
+ // ({ acquired, reason } | { acquired, record, release }); tests inject
19
+ // `now()`, `isProcessAlive`, and a fake fs exactly as before.
20
+
21
+ // Subpath import (not the bare package) — the engineering-pa dist bundle
22
+ // rewrites cross-package import strings to relative paths, and a bare
23
+ // package import would rewrite to a directory path ESM cannot resolve.
24
+ import { acquireLock } from '../../../engineering-pa-core/src/lock/lock.mjs';
25
+
26
+ const STALE_AFTER_MS = 10 * 60 * 1000; // 10 minutes
27
+
28
+ export async function acquire(lockPath, options = {}) {
29
+ return acquireLock(lockPath, { staleAfterMs: STALE_AFTER_MS, ...options });
30
+ }
@@ -0,0 +1,114 @@
1
+ // sync — regenerated-artifacts allowlist for auto-conflict-resolution.
2
+ //
3
+ // The problem this solves: on a multi-host HQ deploy, distillation runs
4
+ // on more than one host and regenerates the same knowledge-artifact files
5
+ // (`.graph.json`, `domains.json`, `profile.md`, auto-classified system
6
+ // pages, …). The output is byte-different across hosts (timestamps,
7
+ // map-key ordering, whitespace) but semantically equivalent — every host
8
+ // gets the same information, just written slightly differently. When two
9
+ // hosts push their regenerations within the same cron window, sync's
10
+ // `pull --rebase` conflicts on those files, exits `fail-rebase`, and
11
+ // wedges the cron until a human `git reset --hard origin/main`s the loser.
12
+ //
13
+ // Fix: when a rebase conflicts AND every conflicted path is in the
14
+ // allowlist below, sync takes origin's version, continues the rebase, and
15
+ // pushes. The regeneration we discarded will land in origin naturally on
16
+ // the next distill cycle — that cycle will fold in whatever staging
17
+ // observations we contributed AND whatever the other host contributed.
18
+ // Nothing is lost.
19
+ //
20
+ // Conflicts on any path outside the allowlist still fail-rebase exit 2 —
21
+ // real conflicts must surface for a human to resolve.
22
+ //
23
+ // Extension: repos can add project-specific regenerable paths via a
24
+ // `.sync-config.json` at the repo root:
25
+ // { "regeneratedArtifacts": ["path/to/generated/**"] }
26
+ // Entries are additive to the built-in defaults; unknown fields are
27
+ // ignored so future config additions do not need this module updated.
28
+
29
+ import { readFile } from 'node:fs/promises';
30
+ import { join, matchesGlob } from 'node:path';
31
+
32
+ // Files/globs that distillation regenerates each cycle. Any rebase
33
+ // conflict on these paths is a byte-race between two hosts' regenerations
34
+ // and can safely be auto-resolved by taking origin's version. Paths are
35
+ // relative to the repo root. Globs use POSIX-style `*` (single segment)
36
+ // and `**` (any number of segments) as understood by `path.matchesGlob`
37
+ // (stable in Node ≥ 22.5).
38
+ export const DEFAULT_REGENERATED_ARTIFACTS = Object.freeze([
39
+ 'operator/knowledge/.graph.json',
40
+ 'operator/knowledge/.backlinks.json',
41
+ 'operator/knowledge/domains.json',
42
+ 'operator/profile/profile.md',
43
+ 'operator/profile/operator.json',
44
+ 'operator/knowledge/systems/web-properties/*.md',
45
+ ]);
46
+
47
+ const CONFIG_FILENAME = '.sync-config.json';
48
+
49
+ // Load the effective allowlist: defaults ∪ any operator-supplied globs
50
+ // from `<repoRoot>/.sync-config.json`. Failure modes are non-fatal:
51
+ // - no file → defaults only, source = 'defaults'
52
+ // - unreadable file → defaults only, source = 'defaults', warning set
53
+ // - malformed JSON → defaults only, source = 'defaults', warning set
54
+ // - malformed field → defaults only, source = 'defaults' (silently
55
+ // drops non-string entries; array must be array)
56
+ // Callers should log the returned `warning` (if any) so a broken config
57
+ // is visible without wedging the cron.
58
+ export async function loadRegeneratedArtifacts({ repoRoot, fs = { readFile } } = {}) {
59
+ const defaults = [...DEFAULT_REGENERATED_ARTIFACTS];
60
+ const result = {
61
+ patterns: defaults,
62
+ source: 'defaults',
63
+ configPath: null,
64
+ added: 0,
65
+ warning: null,
66
+ };
67
+ if (!repoRoot) return result;
68
+ const configPath = join(repoRoot, CONFIG_FILENAME);
69
+ result.configPath = configPath;
70
+ let raw;
71
+ try {
72
+ raw = await fs.readFile(configPath, 'utf8');
73
+ } catch (err) {
74
+ if (err.code === 'ENOENT') return result;
75
+ result.warning = `read failed: ${err.message}`;
76
+ return result;
77
+ }
78
+ let parsed;
79
+ try {
80
+ parsed = JSON.parse(raw);
81
+ } catch (err) {
82
+ result.warning = `parse failed: ${err.message}`;
83
+ return result;
84
+ }
85
+ const extra = Array.isArray(parsed?.regeneratedArtifacts)
86
+ ? parsed.regeneratedArtifacts.filter((p) => typeof p === 'string' && p.length > 0)
87
+ : [];
88
+ if (extra.length === 0) return result;
89
+ return {
90
+ ...result,
91
+ patterns: [...defaults, ...extra],
92
+ source: 'defaults+config',
93
+ added: extra.length,
94
+ };
95
+ }
96
+
97
+ // Does `relPath` (repo-root-relative, forward slashes) match any pattern?
98
+ export function isAllowedPath(relPath, patterns) {
99
+ for (const pat of patterns) {
100
+ if (matchesGlob(relPath, pat)) return true;
101
+ }
102
+ return false;
103
+ }
104
+
105
+ // Split `paths` by the allowlist. Preserves input order in each bucket.
106
+ export function classifyConflicts(paths, patterns) {
107
+ const allowed = [];
108
+ const disallowed = [];
109
+ for (const p of paths) {
110
+ if (isAllowedPath(p, patterns)) allowed.push(p);
111
+ else disallowed.push(p);
112
+ }
113
+ return { allowed, disallowed };
114
+ }
@@ -0,0 +1,260 @@
1
+ // estate-sync — registry-scoped commit set.
2
+ //
3
+ // Replaces the blind `git add -A` with a computed COMMIT SCOPE: when
4
+ // `<repoRoot>/operator/registry/{devices.json,repos.json}` exist, this
5
+ // module resolves the current device (same hostname short-name algorithm
6
+ // as `operator/extensions/tools/library-sync/run.mjs`'s resolveDeviceId),
7
+ // derives every mount `to` subtree for repos this device is entitled to,
8
+ // and returns that set as the EXCLUSION list. `.vault/**` is excluded
9
+ // unconditionally regardless of registry state.
10
+ //
11
+ // Per the library-sync-solidification architecture decision (D1/D2): the
12
+ // in-scope (committable) set is everything NOT under any mount `to` path.
13
+ // This is deliberately simpler than library-sync's own down-path owner-
14
+ // exclusion (which carves out THIS device's own upPath-owned mount) —
15
+ // estate-sync excludes ALL mount `to` subtrees unconditionally, including
16
+ // the device's own agent tree, because D2 moves `agents/*` run-state
17
+ // wholly onto the library pipeline; estate-sync (the git layer) must never
18
+ // be a second writer for any mount-owned path, owned-by-this-device or not.
19
+ //
20
+ // Degrade-gracefully-never-crash: any registry read/parse/resolve failure
21
+ // (missing files, malformed JSON, unknown device) falls back to
22
+ // `registryPresent: false` with a warning explaining why — the caller
23
+ // (run.mjs) then falls back to the pre-rewrite unscoped `git add -A`
24
+ // behaviour with a loud warning, never a silent unscoped commit and never
25
+ // a crashed sync run.
26
+
27
+ import { existsSync, readFileSync } from 'node:fs';
28
+ import { hostname as osHostname } from 'node:os';
29
+ import { join } from 'node:path';
30
+
31
+ /**
32
+ * Resolve which device id to run as, matching os.hostname() against each
33
+ * device's `host` field (exact, then prefix-before-first-dot) or against
34
+ * the device id itself. Mirrors
35
+ * `operator/extensions/tools/library-sync/run.mjs`'s resolveDeviceId
36
+ * exactly, so the two tools never disagree about "which device am I".
37
+ *
38
+ * @param {object} devicesDoc
39
+ * @param {string} currentHostname
40
+ * @returns {string|null}
41
+ */
42
+ export function resolveDeviceId(devicesDoc, currentHostname) {
43
+ const shortCurrent = currentHostname.split('.')[0];
44
+ const devices = (devicesDoc && devicesDoc.devices) || {};
45
+ for (const [id, device] of Object.entries(devices)) {
46
+ if (device.host === currentHostname) return id;
47
+ if (id === currentHostname || id === shortCurrent) return id;
48
+ }
49
+ for (const [id, device] of Object.entries(devices)) {
50
+ if (typeof device.host === 'string' && device.host.split('.')[0] === shortCurrent) return id;
51
+ }
52
+ return null;
53
+ }
54
+
55
+ /**
56
+ * Resolve the repos a device is entitled to, paired with their mount
57
+ * definitions. Never throws — an entitlement referencing a repo id absent
58
+ * from repos.json is skipped with a warning (registry drift, not a reason
59
+ * to crash a 30-minute cron).
60
+ *
61
+ * @param {object} registry - { devices, repos } parsed JSON docs
62
+ * @param {string} deviceId
63
+ * @returns {{ entitled: { repoId: string, repo: object }[], warnings: string[] }}
64
+ */
65
+ export function resolveEntitlements(registry, deviceId) {
66
+ const warnings = [];
67
+ const device = registry.devices?.devices?.[deviceId];
68
+ if (!device) {
69
+ warnings.push(`device "${deviceId}" not found in devices.json`);
70
+ return { entitled: [], warnings };
71
+ }
72
+ const repoIds = device.entitlements?.repos;
73
+ if (!Array.isArray(repoIds)) {
74
+ warnings.push(`device "${deviceId}" has no entitlements.repos array`);
75
+ return { entitled: [], warnings };
76
+ }
77
+ const entitled = [];
78
+ for (const repoId of repoIds) {
79
+ const repo = registry.repos?.repos?.[repoId];
80
+ if (!repo) {
81
+ warnings.push(`device "${deviceId}" entitles repo "${repoId}" not present in repos.json`);
82
+ continue;
83
+ }
84
+ entitled.push({ repoId, repo });
85
+ }
86
+ return { entitled, warnings };
87
+ }
88
+
89
+ /** Normalise a mount `to` path to a POSIX-style directory prefix ending in "/". */
90
+ function normalizeDir(to) {
91
+ let p = String(to).split('\\').join('/');
92
+ if (p.startsWith('./')) p = p.slice(2);
93
+ if (!p.endsWith('/')) p += '/';
94
+ return p;
95
+ }
96
+
97
+ /**
98
+ * Derive the exclusion set: every entitled repo's mount `to` subtree,
99
+ * unconditionally (D1/D2 — no ownership carve-out; see module doc), plus
100
+ * `.vault/**` unconditionally. Sorted, deduped.
101
+ *
102
+ * @param {{ repoId: string, repo: object }[]} entitled
103
+ * @returns {string[]} POSIX-relative directory prefixes, each ending in "/"
104
+ */
105
+ export function computeExcludeDirs(entitled) {
106
+ const dirs = new Set();
107
+ for (const { repo } of entitled) {
108
+ const mounts = Array.isArray(repo.mounts) ? repo.mounts : [];
109
+ for (const mount of mounts) {
110
+ if (mount && typeof mount.to === 'string' && mount.to.length > 0) {
111
+ dirs.add(normalizeDir(mount.to));
112
+ }
113
+ }
114
+ }
115
+ dirs.add('.vault/');
116
+ return [...dirs].sort();
117
+ }
118
+
119
+ function readJson(path) {
120
+ return JSON.parse(readFileSync(path, 'utf8'));
121
+ }
122
+
123
+ /**
124
+ * Compute the full commit-scope decision for this device/repo, degrading
125
+ * gracefully at every failure point (see module doc).
126
+ *
127
+ * @param {object} opts
128
+ * @param {string} opts.repoRoot
129
+ * @param {() => string} [opts.hostnameFn] - injectable for tests
130
+ * @returns {{
131
+ * registryPresent: boolean,
132
+ * deviceId?: string,
133
+ * excludeDirs: string[],
134
+ * warnings: string[],
135
+ * }}
136
+ */
137
+ export function computeCommitScope({ repoRoot, hostnameFn = osHostname }) {
138
+ const devicesPath = join(repoRoot, 'operator/registry/devices.json');
139
+ const reposPath = join(repoRoot, 'operator/registry/repos.json');
140
+
141
+ if (!existsSync(devicesPath) || !existsSync(reposPath)) {
142
+ return { registryPresent: false, excludeDirs: [], warnings: [] };
143
+ }
144
+
145
+ let devicesDoc;
146
+ let reposDoc;
147
+ try {
148
+ devicesDoc = readJson(devicesPath);
149
+ reposDoc = readJson(reposPath);
150
+ } catch (err) {
151
+ return {
152
+ registryPresent: false,
153
+ excludeDirs: [],
154
+ warnings: [`registry present but unreadable (${err.message}) — falling back to unscoped`],
155
+ };
156
+ }
157
+
158
+ const deviceId = resolveDeviceId(devicesDoc, hostnameFn());
159
+ if (!deviceId) {
160
+ return {
161
+ registryPresent: false,
162
+ excludeDirs: [],
163
+ warnings: [`could not resolve a device id from hostname "${hostnameFn()}" against ${devicesPath} — falling back to unscoped`],
164
+ };
165
+ }
166
+
167
+ const { entitled, warnings } = resolveEntitlements({ devices: devicesDoc, repos: reposDoc }, deviceId);
168
+ const excludeDirs = computeExcludeDirs(entitled);
169
+
170
+ return { registryPresent: true, deviceId, excludeDirs, warnings };
171
+ }
172
+
173
+ /**
174
+ * Parse `git status --porcelain` output into a flat list of paths. Rename
175
+ * entries (`R old -> new`) contribute BOTH paths — `git add -- <path>`
176
+ * correctly stages a deletion when given an explicit path that no longer
177
+ * exists on disk (git >= 2.0), so staging both sides of a rename is
178
+ * sufficient without needing `-A` semantics.
179
+ *
180
+ * Shares the same C-style-quoting limitation as `listConflictedPaths` in
181
+ * git.mjs (paths with unusual characters are not unquoted) — acceptable,
182
+ * matches existing precedent in this module.
183
+ *
184
+ * @param {string} porcelainOutput
185
+ * @returns {string[]}
186
+ */
187
+ export function parsePorcelainPaths(porcelainOutput) {
188
+ const out = [];
189
+ for (const rawLine of porcelainOutput.split('\n')) {
190
+ if (rawLine.length < 4) continue;
191
+ const rest = rawLine.slice(3);
192
+ const arrowIdx = rest.indexOf(' -> ');
193
+ if (arrowIdx !== -1) {
194
+ out.push(rest.slice(0, arrowIdx));
195
+ out.push(rest.slice(arrowIdx + 4));
196
+ } else {
197
+ out.push(rest);
198
+ }
199
+ }
200
+ return out;
201
+ }
202
+
203
+ /** Is `path` under one of `excludeDirs` (each ending in "/")? */
204
+ export function isExcluded(path, excludeDirs) {
205
+ return excludeDirs.some((dir) => path === dir.slice(0, -1) || path.startsWith(dir));
206
+ }
207
+
208
+ /**
209
+ * Filter porcelain output down to the in-scope (committable) paths —
210
+ * everything NOT under an excluded subtree. Dedupes (a path can appear
211
+ * twice from a rename pair sharing a prefix) while preserving first-seen
212
+ * order.
213
+ *
214
+ * @param {string} porcelainOutput
215
+ * @param {string[]} excludeDirs
216
+ * @returns {{ inScope: string[], excludedCount: number }}
217
+ */
218
+ export function filterInScopePaths(porcelainOutput, excludeDirs) {
219
+ const allPaths = parsePorcelainPaths(porcelainOutput);
220
+ const seen = new Set();
221
+ const inScope = [];
222
+ let excludedCount = 0;
223
+ for (const p of allPaths) {
224
+ if (isExcluded(p, excludeDirs)) {
225
+ excludedCount += 1;
226
+ continue;
227
+ }
228
+ if (!seen.has(p)) {
229
+ seen.add(p);
230
+ inScope.push(p);
231
+ }
232
+ }
233
+ return { inScope, excludedCount };
234
+ }
235
+
236
+ /**
237
+ * Render the scope-decision section for the commit body (requirement:
238
+ * "report the scope decision in the commit body — counts: staged N paths,
239
+ * excluded M subtrees by rule").
240
+ *
241
+ * @param {object} scope - result of computeCommitScope
242
+ * @param {object} counts
243
+ * @param {number} counts.staged
244
+ * @param {number} counts.excludedPaths - dirty paths that were excluded
245
+ * @returns {string}
246
+ */
247
+ export function renderScopeSection(scope, { staged, excludedPaths }) {
248
+ if (!scope.registryPresent) {
249
+ return 'estate-sync: no registry found, unscoped commit';
250
+ }
251
+ const lines = [
252
+ `estate-sync scope: registry-scoped (device=${scope.deviceId})`,
253
+ ` staged: ${staged} path(s)`,
254
+ ` excluded: ${scope.excludeDirs.length} subtree(s) by rule (${excludedPaths} dirty path(s) matched) — ${scope.excludeDirs.join(', ')}`,
255
+ ];
256
+ if (scope.warnings.length > 0) {
257
+ lines.push(` warnings: ${scope.warnings.join('; ')}`);
258
+ }
259
+ return lines.join('\n');
260
+ }