@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,573 @@
1
+ export const meta = {
2
+ name: 'research-normal',
3
+ description: 'Run a mode-normal research session end-to-end: plan → search+extract → rank → 3-vote verify → deepen (1 round) → synthesize → finalise (mark done).',
4
+ phases: [
5
+ { title: 'Plan', detail: 'decompose into 2–4 sub-questions, write plan.json' },
6
+ { title: 'Search', detail: 'one searcher per sub-question (dependents chained)' },
7
+ { title: 'Extract', detail: 'quote-grounded claim extraction per sub-question' },
8
+ { title: 'Verify', detail: '3-vote adversarial verify on central claims' },
9
+ { title: 'Deepen', detail: '1 gap-driven round (reflection stop when sufficient)' },
10
+ { title: 'Synthesize', detail: 'write findings/sq-N.json + the session answer' },
11
+ { title: 'Finalise', detail: 'persist gate records, write sources.json, round-trip, research mark done' },
12
+ ],
13
+ };
14
+
15
+ // ── deterministic plumbing (build-spec §4.1: dedup / ranking / budget math = code) ──
16
+
17
+ const CLAIM_BUDGET = 25; // harness-skill cap; §4 does not override for normal
18
+ const IMPORTANCE_W = { central: 3, supporting: 2, incidental: 1 };
19
+ const QUALITY_W = { primary: 3, secondary: 2, low: 1 };
20
+ const VERIFY_LENSES = ['quote-support', 'contradiction-search', 'recency-source-fit'];
21
+ const UNSOURCED_ABORT_RATIO = 0.10; // §8 guardrail: >10% unsourced fails the run
22
+
23
+ // `args` normally arrives as the object passed to Workflow(); tolerate a
24
+ // JSON-string-encoded form too (some dispatch paths stringify it).
25
+ let _args = args;
26
+ if (typeof _args === 'string') { try { _args = JSON.parse(_args); } catch { _args = {}; } }
27
+
28
+ // After the tool moved into `@stravica-ai/digital-operator-pa` (2026-07-08), sessions
29
+ // live in the operator repo (`<repoRoot>/.cache/research/sessions/`) but the
30
+ // briefs and this workflow ship inside the installed package under
31
+ // `node_modules/`. One `toolRoot` no longer maps to both, so the skill now
32
+ // gets all four paths from `research paths --json` and passes them in
33
+ // explicitly. The legacy `toolRoot` arg still works — if it's the only thing
34
+ // given, we derive the same three paths from it (v1 shape).
35
+ const { sessionId, sessionsDir: sessionsDirArg, briefsDir: briefsDirArg, schemaDir: schemaDirArg, cli: cliArg, toolRoot } = _args || {};
36
+ if (!sessionId) throw new Error('research-normal: args.sessionId is required');
37
+ if (!sessionsDirArg && !toolRoot) {
38
+ throw new Error('research-normal: args.sessionsDir (or legacy args.toolRoot) is required — get it from `research paths --json`');
39
+ }
40
+ if (!briefsDirArg && !toolRoot) {
41
+ throw new Error('research-normal: args.briefsDir (or legacy args.toolRoot) is required — get it from `research paths --json`');
42
+ }
43
+
44
+ const sessionsDir = sessionsDirArg || `${toolRoot}/sessions`;
45
+ const briefsDir = briefsDirArg || `${toolRoot}/briefs`;
46
+ const schemaDir = schemaDirArg || `${toolRoot}/src/schema`;
47
+ const cli = cliArg || `node ${toolRoot}/bin/research`;
48
+ const sessionDir = `${sessionsDir}/${sessionId}`;
49
+
50
+ // Build an agent prompt that points the worker at its on-disk brief + concrete values.
51
+ // The workflow has no fs; the agent (Read/Bash/Write/WebSearch) is what touches disk.
52
+ function brief(role, values) {
53
+ const vals = Object.entries(values)
54
+ .map(([k, v]) => `- ${k}: ${typeof v === 'string' ? v : JSON.stringify(v)}`)
55
+ .join('\n');
56
+ return [
57
+ `You are the **${role}** in the deep-research \`research-normal.js\` pipeline.`,
58
+ `Read and follow the brief at \`${briefsDir}/${role}.md\` in full.`,
59
+ `The brief's {{placeholder}} values for this dispatch are:`,
60
+ vals,
61
+ `Return ONLY the structured object the brief's §6 (Output format) specifies — nothing else.`,
62
+ ].join('\n\n');
63
+ }
64
+
65
+ // Reject circular / self-referential / malformed plans (§8: workflow-code validator).
66
+ function validatePlanShape(subQuestions) {
67
+ const errs = [];
68
+ if (!Array.isArray(subQuestions) || subQuestions.length < 2 || subQuestions.length > 4) {
69
+ errs.push(`expected 2–4 sub-questions, got ${Array.isArray(subQuestions) ? subQuestions.length : 'none'}`);
70
+ return errs;
71
+ }
72
+ const ids = new Set();
73
+ for (const sq of subQuestions) {
74
+ if (!sq || typeof sq.id !== 'string') { errs.push('a sub-question is missing a string id'); continue; }
75
+ if (ids.has(sq.id)) errs.push(`duplicate sub-question id "${sq.id}"`);
76
+ ids.add(sq.id);
77
+ }
78
+ for (const sq of subQuestions) {
79
+ const deps = Array.isArray(sq.dependsOn) ? sq.dependsOn : [];
80
+ for (const d of deps) {
81
+ if (d === sq.id) errs.push(`sub-question "${sq.id}" depends on itself`);
82
+ if (!ids.has(d)) errs.push(`sub-question "${sq.id}" depends on unknown "${d}"`);
83
+ }
84
+ }
85
+ // cycle detection (DFS)
86
+ const byId = new Map(subQuestions.map((s) => [s.id, Array.isArray(s.dependsOn) ? s.dependsOn : []]));
87
+ const state = new Map(); // 0=visiting,1=done
88
+ const visit = (id, stack) => {
89
+ if (state.get(id) === 1) return;
90
+ if (stack.has(id)) { errs.push(`dependency cycle through "${id}"`); return; }
91
+ stack.add(id);
92
+ for (const d of (byId.get(id) || [])) if (byId.has(d)) visit(d, stack);
93
+ stack.delete(id);
94
+ state.set(id, 1);
95
+ };
96
+ for (const id of byId.keys()) visit(id, new Set());
97
+ return errs;
98
+ }
99
+
100
+ // Kahn topological order over the dependent sub-questions (deps already validated).
101
+ function topoOrder(subQuestions) {
102
+ const byId = new Map(subQuestions.map((s) => [s.id, s]));
103
+ const order = [];
104
+ const done = new Set();
105
+ let guard = 0;
106
+ while (order.length < subQuestions.length && guard++ < subQuestions.length + 1) {
107
+ for (const sq of subQuestions) {
108
+ if (done.has(sq.id)) continue;
109
+ const deps = (sq.dependsOn || []).filter((d) => byId.has(d));
110
+ if (deps.every((d) => done.has(d))) { order.push(sq); done.add(sq.id); }
111
+ }
112
+ }
113
+ return order;
114
+ }
115
+
116
+ // ── JSON schemas for structured agent returns ──────────────────────────────────
117
+
118
+ const PLAN_SCHEMA = {
119
+ type: 'object', additionalProperties: false,
120
+ required: ['question', 'outputMode', 'gatePolicy', 'subQuestions', 'wrotePlan'],
121
+ properties: {
122
+ question: { type: 'string' },
123
+ intent: { type: 'string' },
124
+ outputMode: { enum: ['shortAnswer', 'article', 'structured'] },
125
+ gatePolicy: { enum: ['operatorPresent', 'background', 'worker'] },
126
+ wrotePlan: { type: 'boolean' },
127
+ subQuestions: {
128
+ type: 'array', minItems: 2, maxItems: 4,
129
+ items: {
130
+ type: 'object', additionalProperties: false,
131
+ required: ['id', 'text', 'kind', 'dependsOn'],
132
+ properties: {
133
+ id: { type: 'string' },
134
+ text: { type: 'string' },
135
+ kind: { enum: ['independent', 'dependent'] },
136
+ dependsOn: { type: 'array', items: { type: 'string' } },
137
+ },
138
+ },
139
+ },
140
+ },
141
+ };
142
+
143
+ const SEARCH_SCHEMA = {
144
+ type: 'object', additionalProperties: false,
145
+ required: ['subQuestionId', 'sources', 'toolCallsUsed'],
146
+ properties: {
147
+ subQuestionId: { type: 'string' },
148
+ sources: {
149
+ type: 'array',
150
+ items: {
151
+ type: 'object', additionalProperties: false,
152
+ required: ['sourceId', 'url', 'title', 'kind', 'sourceQuality'],
153
+ properties: {
154
+ sourceId: { type: 'string' },
155
+ url: { type: 'string' },
156
+ title: { type: 'string' },
157
+ kind: { enum: ['web', 'playwright', 'kb', 'codebase', 'prior-session'] },
158
+ sourceQuality: { enum: ['primary', 'secondary', 'low'] },
159
+ snippet: { type: 'string' },
160
+ },
161
+ },
162
+ },
163
+ toolCallsUsed: { type: 'integer' },
164
+ gatedDropped: {
165
+ type: 'array',
166
+ items: {
167
+ type: 'object', additionalProperties: false,
168
+ required: ['url', 'gateType', 'whatIsNeeded'],
169
+ properties: { url: { type: 'string' }, gateType: { type: 'string' }, whatIsNeeded: { type: 'string' } },
170
+ },
171
+ },
172
+ notes: { type: 'string' },
173
+ },
174
+ };
175
+
176
+ const EXTRACT_SCHEMA = {
177
+ type: 'object', additionalProperties: false,
178
+ required: ['subQuestionId', 'claims', 'unsourcedClaims'],
179
+ properties: {
180
+ subQuestionId: { type: 'string' },
181
+ claims: {
182
+ type: 'array',
183
+ items: {
184
+ type: 'object', additionalProperties: false,
185
+ required: ['claimId', 'text', 'quote', 'sourceId', 'importance', 'sourceQuality'],
186
+ properties: {
187
+ claimId: { type: 'string' },
188
+ text: { type: 'string' },
189
+ quote: { type: 'string' },
190
+ sourceId: { type: 'string' },
191
+ importance: { enum: ['central', 'supporting', 'incidental'] },
192
+ sourceQuality: { enum: ['primary', 'secondary', 'low'] },
193
+ },
194
+ },
195
+ },
196
+ unsourcedClaims: { type: 'integer' },
197
+ notes: { type: 'string' },
198
+ },
199
+ };
200
+
201
+ const VERIFY_SCHEMA = {
202
+ type: 'object', additionalProperties: false,
203
+ required: ['outcome', 'note'],
204
+ properties: {
205
+ outcome: { enum: ['confirmed', 'refuted', 'unverified-infra'] },
206
+ note: { type: 'string' },
207
+ },
208
+ };
209
+
210
+ const DEEPEN_SCHEMA = {
211
+ type: 'object', additionalProperties: false,
212
+ required: ['sufficient', 'newSubQuestions'],
213
+ properties: {
214
+ sufficient: { type: 'boolean' },
215
+ gaps: { type: 'array', items: { type: 'string' } },
216
+ newSubQuestions: {
217
+ type: 'array', maxItems: 4,
218
+ items: {
219
+ type: 'object', additionalProperties: false,
220
+ required: ['id', 'text'],
221
+ properties: { id: { type: 'string' }, text: { type: 'string' } },
222
+ },
223
+ },
224
+ },
225
+ };
226
+
227
+ const SYNTH_SCHEMA = {
228
+ type: 'object', additionalProperties: false,
229
+ required: ['subQuestionId', 'written', 'path', 'confidence', 'claimCount', 'centralConfirmed', 'centralRefuted', 'unsourcedCount'],
230
+ properties: {
231
+ subQuestionId: { type: 'string' },
232
+ written: { type: 'boolean' },
233
+ path: { type: 'string' },
234
+ confidence: { enum: ['low', 'medium', 'high'] },
235
+ claimCount: { type: 'integer' },
236
+ centralConfirmed: { type: 'integer' },
237
+ centralRefuted: { type: 'integer' },
238
+ unsourcedCount: { type: 'integer' },
239
+ refutedTransparency: { type: 'string' },
240
+ },
241
+ };
242
+
243
+ const FINALISE_SCHEMA = {
244
+ type: 'object', additionalProperties: false,
245
+ required: ['wroteSources', 'wroteAnswer', 'roundTripOk', 'markedDone'],
246
+ properties: {
247
+ wroteSources: { type: 'boolean' },
248
+ wroteAnswer: { type: 'boolean' },
249
+ roundTripOk: { type: 'boolean' },
250
+ markedDone: { type: 'boolean' },
251
+ gatesAppended: { type: 'integer' }, // count of gatedDropped records persisted to gates.json (w-018)
252
+ note: { type: 'string' },
253
+ },
254
+ };
255
+
256
+ // ── agent wrappers (model/effort per build-spec §4.1 — explicit, never inherited) ──
257
+
258
+ function searchAgent(sq, siblings, priorContext) {
259
+ return agent(brief('searcher', {
260
+ sessionId, sessionQuestion: baseQuestion, intent: baseIntent, gatePolicy: basePolicy,
261
+ subQuestionId: sq.id, subQuestionText: sq.text,
262
+ siblingSubQuestions: siblings.filter((s) => s.id !== sq.id).map((s) => `${s.id}: ${s.text}`).join(' | ') || 'none',
263
+ sourceIdPrefix: sq.id.replace(/[^a-zA-Z0-9]/g, ''), toolCallCap: 12, sessionDir,
264
+ dependencyContext: priorContext || 'none (independent sub-question)',
265
+ }), { model: 'sonnet', effort: 'medium', phase: 'Search', label: `search:${sq.id}`, schema: SEARCH_SCHEMA });
266
+ }
267
+
268
+ function extractAgent(sq, searchRes, priorContext) {
269
+ const sources = (searchRes && searchRes.sources) || [];
270
+ return agent(brief('extractor', {
271
+ sessionId, sessionQuestion: baseQuestion,
272
+ subQuestionId: sq.id, subQuestionText: sq.text,
273
+ sources: sources.map((s) => ({ sourceId: s.sourceId, url: s.url, title: s.title, sourceQuality: s.sourceQuality })),
274
+ sessionDir, dependencyContext: priorContext || 'none',
275
+ }), { model: 'sonnet', effort: 'medium', phase: 'Extract', label: `extract:${sq.id}`, schema: EXTRACT_SCHEMA });
276
+ }
277
+
278
+ function verifyAgent(claim, lens) {
279
+ return agent(brief('verifier', {
280
+ sessionId, claimText: claim.text, quote: claim.quote, sourceUrl: claim.url || claim.sourceId,
281
+ subQuestionText: claim.sqText, lens,
282
+ }), { model: 'sonnet', effort: 'high', phase: 'Verify', label: `verify:${claim.claimId}:${lens}`, schema: VERIFY_SCHEMA });
283
+ }
284
+
285
+ function synthAgent(sq, buckets) {
286
+ return agent(brief('synthesizer', {
287
+ sessionId, subQuestionId: sq.id, subQuestionText: sq.text, outputMode: baseOutputMode,
288
+ findingFilename: `${sq.id}.json`, sessionDir, schemaDir, cli,
289
+ verifiedClaims: buckets.verified, supportingClaims: buckets.supporting, refutedClaims: buckets.refuted,
290
+ }), { model: 'opus', effort: 'high', phase: 'Synthesize', label: `synth:${sq.id}`, schema: SYNTH_SCHEMA });
291
+ }
292
+
293
+ // ── run ─────────────────────────────────────────────────────────────────────
294
+
295
+ // -- Plan --
296
+ phase('Plan');
297
+ let baseQuestion = '', baseIntent = 'none', basePolicy = 'worker', baseOutputMode = 'shortAnswer';
298
+ let subQuestions;
299
+ {
300
+ let plan = await agent([
301
+ `You are the **planner** for a mode-\`normal\` research run. Lead-tier reasoning.`,
302
+ `1. Read \`${sessionDir}/manifest.json\` and \`${sessionDir}/plan.json\`.`,
303
+ `2. Decompose the manifest's \`question\` (honouring \`intent\`) into **2–4 sub-questions**.`,
304
+ ` Classify each \`independent\` or \`dependent\`; a dependent one lists its \`dependsOn\` ids.`,
305
+ ` Prefer independent facets (they parallelise). Use ids \`sq-1\`,\`sq-2\`,… (these become finding filenames).`,
306
+ ` 44% of multi-agent failures start at planning — make the facets genuinely distinct and jointly cover the question.`,
307
+ `3. WRITE the filled plan back to \`${sessionDir}/plan.json\`: keep the existing \`deepeningRounds\` and`,
308
+ ` \`verificationTier\`, replace \`subQuestions\` with your list (each {id,text,kind,dependsOn}). No extra fields.`,
309
+ `4. Round-trip: run \`${cli} show ${sessionId} --json\` and confirm exit 0 (it validates plan.json).`,
310
+ `Return the structured object: question, intent, outputMode, gatePolicy (echo from the manifest),`,
311
+ `subQuestions, and wrotePlan=true once the round-trip is green.`,
312
+ ].join('\n'), { model: 'opus', effort: 'high', phase: 'Plan', label: 'plan', schema: PLAN_SCHEMA });
313
+
314
+ baseQuestion = plan.question;
315
+ baseIntent = plan.intent || 'none';
316
+ basePolicy = plan.gatePolicy;
317
+ baseOutputMode = plan.outputMode;
318
+ subQuestions = plan.subQuestions;
319
+
320
+ let errs = validatePlanShape(subQuestions);
321
+ if (errs.length) {
322
+ log(`plan rejected (${errs.join('; ')}) — re-planning once`);
323
+ plan = await agent([
324
+ `Your previous plan was rejected by the workflow validator: ${errs.join('; ')}.`,
325
+ `Re-read \`${sessionDir}/manifest.json\`, produce a corrected 2–4 sub-question decomposition with no`,
326
+ `self-references, no cycles, and only valid dependsOn ids, rewrite \`${sessionDir}/plan.json\`, round-trip`,
327
+ `\`${cli} show ${sessionId} --json\`, and return the structured object.`,
328
+ ].join('\n'), { model: 'opus', effort: 'high', phase: 'Plan', label: 'plan-retry', schema: PLAN_SCHEMA });
329
+ subQuestions = plan.subQuestions;
330
+ errs = validatePlanShape(subQuestions);
331
+ if (errs.length) throw new Error(`research-normal: planner produced an invalid plan twice (${errs.join('; ')}) — escalating`);
332
+ }
333
+ log(`plan: ${subQuestions.length} sub-questions — ${subQuestions.map((s) => s.id).join(', ')}`);
334
+ }
335
+
336
+ // -- Search + Extract --
337
+ // Independent facets flow search→extract with no cross-facet barrier (pipeline).
338
+ // Dependent facets are chained AFTER their dependsOn extract completes (the DAG that
339
+ // pipeline() cannot express — documented fallback, §8).
340
+ phase('Search');
341
+ const independent = subQuestions.filter((sq) => sq.kind !== 'dependent' || (sq.dependsOn || []).length === 0);
342
+ const dependents = subQuestions.filter((sq) => sq.kind === 'dependent' && (sq.dependsOn || []).length > 0);
343
+
344
+ const sqResults = new Map(); // sq.id -> { sq, search, extract }
345
+
346
+ const indepPairs = await pipeline(
347
+ independent,
348
+ (sq) => searchAgent(sq, subQuestions),
349
+ (searchRes, sq) => extractAgent(sq, searchRes).then((extract) => ({ sq, search: searchRes, extract })),
350
+ );
351
+ for (const r of indepPairs.filter(Boolean)) sqResults.set(r.sq.id, r);
352
+
353
+ for (const sq of topoOrder(dependents)) {
354
+ const priorContext = (sq.dependsOn || [])
355
+ .map((d) => sqResults.get(d))
356
+ .filter(Boolean)
357
+ .map((r) => `${r.sq.id}: ${(r.extract.claims || []).map((c) => c.text).slice(0, 3).join('; ')}`)
358
+ .join(' || ') || 'dependencies produced no claims';
359
+ const searchRes = await searchAgent(sq, subQuestions, priorContext);
360
+ const extract = await extractAgent(sq, searchRes, priorContext);
361
+ sqResults.set(sq.id, { sq, search: searchRes, extract });
362
+ }
363
+
364
+ // -- Rank + budget cap (deterministic code — §4.1) --
365
+ // Flatten claims, tag with their sq + the url of their cited source, rank by
366
+ // importance×sourceQuality, keep the top CLAIM_BUDGET; the rest are budgetDropped.
367
+ const urlBySourceId = new Map();
368
+ for (const { search } of sqResults.values()) {
369
+ for (const s of (search.sources || [])) urlBySourceId.set(s.sourceId, s.url);
370
+ }
371
+ let allClaims = [];
372
+ for (const { sq, extract } of sqResults.values()) {
373
+ for (const c of (extract.claims || [])) {
374
+ allClaims.push({ ...c, sqId: sq.id, sqText: sq.text, url: urlBySourceId.get(c.sourceId) || null });
375
+ }
376
+ }
377
+ allClaims.sort((a, b) =>
378
+ (IMPORTANCE_W[b.importance] * QUALITY_W[b.sourceQuality]) - (IMPORTANCE_W[a.importance] * QUALITY_W[a.sourceQuality]));
379
+ const extractedCount = allClaims.length;
380
+ const retained = allClaims.slice(0, CLAIM_BUDGET);
381
+ const budgetDropped = extractedCount - retained.length;
382
+ log(`extracted ${extractedCount} claims; retained ${retained.length} (budgetDropped ${budgetDropped})`);
383
+
384
+ // -- Verify (3-vote adversarial on central claims only — §4 normal row) --
385
+ phase('Verify');
386
+ const centralClaims = retained.filter((c) => c.importance === 'central');
387
+ const verifyResults = await parallel(centralClaims.map((claim) => () =>
388
+ parallel(VERIFY_LENSES.map((lens) => () => verifyAgent(claim, lens)))
389
+ .then((votes) => ({ claim, votes: votes.filter(Boolean) }))));
390
+
391
+ let confirmedN = 0, refutedN = 0, infraN = 0, infraCollapsedClaims = 0;
392
+ const verdictByClaimId = new Map();
393
+ for (const r of verifyResults.filter(Boolean)) {
394
+ const outcomes = r.votes.map((v) => v.outcome);
395
+ const refutes = outcomes.filter((o) => o === 'refuted').length;
396
+ const infra = outcomes.filter((o) => o === 'unverified-infra').length;
397
+ confirmedN += outcomes.filter((o) => o === 'confirmed').length;
398
+ refutedN += refutes; infraN += infra;
399
+ if (infra >= 2) infraCollapsedClaims++;
400
+ // ≥2 refutes → dropped; unverified-infra does NOT count toward refutation (§5).
401
+ const verdict = refutes >= 2 ? 'refuted' : 'confirmed';
402
+ verdictByClaimId.set(r.claim.claimId + '@' + r.claim.sqId, { verdict, votes: r.votes });
403
+ }
404
+ // §8: whole-run verify collapse → escalate rather than ship unverified-as-confirmed.
405
+ if (centralClaims.length > 0 && infraCollapsedClaims / centralClaims.length > 0.5) {
406
+ throw new Error(`research-normal: verify tier collapsed — ${infraCollapsedClaims}/${centralClaims.length} central claims were unverified-infra (>50%). Escalating; not shipping unverified findings.`);
407
+ }
408
+ log(`verify: central=${centralClaims.length} votes(confirmed=${confirmedN} refuted=${refutedN} infra=${infraN})`);
409
+
410
+ // -- Deepen (1 gap-driven round; reflection stop when sufficient — §4 normal cap=1) --
411
+ phase('Deepen');
412
+ const refutedCentral = centralClaims.filter((c) => (verdictByClaimId.get(c.claimId + '@' + c.sqId) || {}).verdict === 'refuted');
413
+ const deepen = await agent([
414
+ `You are the **deepener** (lead tier). One gap round is allowed for mode normal.`,
415
+ `Current sub-questions: ${JSON.stringify(subQuestions.map((s) => ({ id: s.id, text: s.text })))}.`,
416
+ `Confirmed central claims: ${confirmedN}. Refuted central claims (need care, not silence): ${JSON.stringify(refutedCentral.map((c) => c.text))}.`,
417
+ `Supporting claims retained: ${retained.filter((c) => c.importance !== 'central').length}.`,
418
+ `Decide whether the evidence sufficiently answers the question. If a real gap remains (a facet unaddressed,`,
419
+ `a refuted-but-central point needing a replacement source), propose AT MOST 4 new sub-questions (ids sq-d1,sq-d2,…).`,
420
+ `If the evidence is sufficient, set sufficient=true and newSubQuestions=[] (reflection stop — do not invent busywork).`,
421
+ ].join('\n'), { model: 'opus', effort: 'high', phase: 'Deepen', label: 'deepen', schema: DEEPEN_SCHEMA });
422
+
423
+ let deepenAdded = 0;
424
+ if (!deepen.sufficient && (deepen.newSubQuestions || []).length) {
425
+ const newSqs = deepen.newSubQuestions.slice(0, 4).map((s) => ({ ...s, kind: 'independent', dependsOn: [] }));
426
+ deepenAdded = newSqs.length;
427
+ log(`deepen: gap round — ${deepenAdded} new sub-question(s)`);
428
+ const deepPairs = await pipeline(
429
+ newSqs,
430
+ (sq) => searchAgent(sq, subQuestions),
431
+ (searchRes, sq) => extractAgent(sq, searchRes).then((extract) => ({ sq, search: searchRes, extract })),
432
+ );
433
+ for (const r of deepPairs.filter(Boolean)) {
434
+ sqResults.set(r.sq.id, r);
435
+ for (const s of (r.search.sources || [])) urlBySourceId.set(s.sourceId, s.url);
436
+ for (const c of (r.extract.claims || [])) {
437
+ const claim = { ...c, sqId: r.sq.id, sqText: r.sq.text, url: urlBySourceId.get(c.sourceId) || null };
438
+ retained.push(claim); // deepen claims join the retained set (round-capped, so no re-cap loop)
439
+ if (claim.importance === 'central') {
440
+ const votes = (await parallel(VERIFY_LENSES.map((lens) => () => verifyAgent(claim, lens)))).filter(Boolean);
441
+ const refutes = votes.filter((v) => v.outcome === 'refuted').length;
442
+ verdictByClaimId.set(claim.claimId + '@' + claim.sqId, { verdict: refutes >= 2 ? 'refuted' : 'confirmed', votes });
443
+ }
444
+ }
445
+ }
446
+ subQuestions = subQuestions.concat(newSqs);
447
+ } else {
448
+ log('deepen: reflection stop — evidence sufficient, no gap round');
449
+ }
450
+
451
+ // -- Synthesize (per-SQ finding files, then the session answer — §4.1 lead tier) --
452
+ phase('Synthesize');
453
+ function bucketsFor(sqId) {
454
+ const mine = retained.filter((c) => c.sqId === sqId);
455
+ const verified = [], refuted = [], supporting = [];
456
+ for (const c of mine) {
457
+ if (c.importance === 'central') {
458
+ const v = verdictByClaimId.get(c.claimId + '@' + c.sqId);
459
+ const entry = { claimId: c.claimId, text: c.text, quote: c.quote, sourceId: c.sourceId, votes: (v && v.votes) || [] };
460
+ (v && v.verdict === 'refuted' ? refuted : verified).push(entry);
461
+ } else {
462
+ supporting.push({ claimId: c.claimId, text: c.text, quote: c.quote, sourceId: c.sourceId, importance: c.importance });
463
+ }
464
+ }
465
+ return { verified, refuted, supporting };
466
+ }
467
+ const synthReceipts = (await parallel(
468
+ subQuestions
469
+ .filter((sq) => retained.some((c) => c.sqId === sq.id))
470
+ .map((sq) => () => synthAgent(sq, bucketsFor(sq.id))),
471
+ )).filter(Boolean);
472
+
473
+ // Unsourced ratio guard (§8): >10% unsourced → abort (do not mark done). mark-done is
474
+ // the only status verb shipped, so "abort" = leave the session unfinalised + report.
475
+ const totalClaimCount = synthReceipts.reduce((n, r) => n + (r.claimCount || 0), 0);
476
+ const totalUnsourced = synthReceipts.reduce((n, r) => n + (r.unsourcedCount || 0), 0);
477
+ const unsourcedRatio = totalClaimCount ? totalUnsourced / totalClaimCount : 0;
478
+
479
+ // Aggregate sources.json deterministically (dedup = code, §4.1). Dedup by sourceId
480
+ // (prefix-unique per searcher, so collisions are impossible) rather than by url:
481
+ // that guarantees every id a finding references is present, which url-dedup could
482
+ // break (the finding validator does not cross-check, but referential integrity is
483
+ // cheap to keep). Searchers already de-dup within their own sub-question.
484
+ const seenId = new Set();
485
+ const sourceItems = [];
486
+ for (const { search } of sqResults.values()) {
487
+ for (const s of (search.sources || [])) {
488
+ if (seenId.has(s.sourceId)) continue;
489
+ seenId.add(s.sourceId);
490
+ sourceItems.push({ id: s.sourceId, kind: s.kind, url: s.url, title: s.title });
491
+ }
492
+ }
493
+ const sourcesJson = JSON.stringify({ items: sourceItems }, null, 2);
494
+
495
+ // Aggregate every searcher's gatedDropped[] into gate records for persistence
496
+ // (w-018). The workflow has no fs, so the finaliser agent shells the record set to
497
+ // `research gates --append`; the append happens here in Finalise, well after all
498
+ // searchers have returned, so there is no write race with the search phase. Each
499
+ // record carries its sub-question id so the swept queue stays traceable.
500
+ const gateRecords = [];
501
+ for (const { sq, search } of sqResults.values()) {
502
+ for (const g of (search.gatedDropped || [])) {
503
+ if (!g || !g.url || !g.gateType || !g.whatIsNeeded) continue;
504
+ gateRecords.push({ url: g.url, gateType: g.gateType, whatIsNeeded: g.whatIsNeeded, subQuestionId: sq.id });
505
+ }
506
+ }
507
+
508
+ const runStats = {
509
+ extracted: extractedCount, central: centralClaims.length,
510
+ confirmed: confirmedN, refuted: refutedN, unverifiedInfra: infraN,
511
+ budgetDropped, deepenAdded, unsourcedCount: totalUnsourced,
512
+ unsourcedRatio: Number(unsourcedRatio.toFixed(3)),
513
+ gatedDropped: gateRecords.length,
514
+ };
515
+
516
+ if (unsourcedRatio > UNSOURCED_ABORT_RATIO) {
517
+ log(`ABORT: unsourced ratio ${(unsourcedRatio * 100).toFixed(1)}% > 10% (§8 guardrail) — not marking done`);
518
+ return { sessionId, status: 'aborted', reason: 'unsourced ratio exceeded 10%', runStats, subQuestions: subQuestions.map((s) => s.id) };
519
+ }
520
+
521
+ // -- Finalise (persist gate records, write sources.json + the session answer, round-trip, mark done) --
522
+ phase('Finalise');
523
+ // The gate-persistence step is present only when searchers actually dropped gated
524
+ // sources; otherwise the finaliser is told to skip it. A non-zero append exit is
525
+ // reported but not fatal (a missed gate log is degraded, not a failed run).
526
+ const gatesStep = gateRecords.length
527
+ ? [
528
+ `1. Persist gate records. ${gateRecords.length} background-posture gate(s) were dropped by searchers`,
529
+ ` during retrieval and must be logged to \`gates.json\` so a later operator-present session can sweep`,
530
+ ` them. Append them by piping this EXACT JSON array to \`research gates --append\` (use a heredoc so`,
531
+ ` the JSON reaches stdin verbatim):`,
532
+ '```bash',
533
+ `cat <<'GATES_EOF' | ${cli} gates --append ${sessionId}`,
534
+ JSON.stringify(gateRecords),
535
+ `GATES_EOF`,
536
+ '```',
537
+ ` Expect exit 0 and an "appended N gate(s)" line; set \`gatesAppended\` to N in your receipt. If it exits`,
538
+ ` NON-ZERO, put the error in \`note\`, set \`gatesAppended\` to 0, and CONTINUE — a missed gate log is`,
539
+ ` degraded, not fatal. Do NOT abort the finalise over it.`,
540
+ ].join('\n')
541
+ : `1. No gated sources were dropped this run — SKIP gate persistence entirely and set \`gatesAppended\` to 0.`;
542
+
543
+ const finalise = await agent([
544
+ `You are the **finaliser** for session \`${sessionId}\` (output mode: \`${baseOutputMode}\`).`,
545
+ `Do these in order:`,
546
+ gatesStep,
547
+ `2. Write this EXACT JSON to \`${sessionDir}/sources.json\` (already URL-deduped by the workflow — write verbatim):`,
548
+ '```json', sourcesJson, '```',
549
+ `3. Read every \`${sessionDir}/findings/*.json\` and compose the session answer, writing it to \`${sessionDir}/answer.md\`:`,
550
+ ` - \`shortAnswer\`: a terse 2–4 sentence answer.`,
551
+ ` - \`article\`: prose with inline \`[source-id]\` citations.`,
552
+ ` - \`structured\`: a JSON code block with named sections.`,
553
+ ` For output mode \`${baseOutputMode}\`, use that form. ALWAYS include a "## Refuted / unverified claims" section`,
554
+ ` listing any refuted central claims for transparency (write "none" if there were none), and a "## Run stats" footer`,
555
+ ` with: ${JSON.stringify(runStats)}.`,
556
+ `4. Round-trip: run \`${cli} show ${sessionId} --json\` — it must exit 0 (validates plan, findings, sources, gates).`,
557
+ `5. Only if the round-trip is green, run \`${cli} mark done ${sessionId}\` (expect exit 0; sets status=done).`,
558
+ `Return the structured receipt (including \`gatesAppended\`).`,
559
+ ].join('\n'), { model: 'sonnet', effort: 'medium', phase: 'Finalise', label: 'finalise', schema: FINALISE_SCHEMA });
560
+
561
+ const status = finalise.markedDone ? 'done' : 'in-progress';
562
+ log(`finalise: sources=${finalise.wroteSources} answer=${finalise.wroteAnswer} roundTrip=${finalise.roundTripOk} markedDone=${finalise.markedDone} gatesAppended=${finalise.gatesAppended ?? 0}/${gateRecords.length}`);
563
+
564
+ return {
565
+ sessionId,
566
+ status,
567
+ question: baseQuestion,
568
+ outputMode: baseOutputMode,
569
+ subQuestions: subQuestions.map((s) => ({ id: s.id, text: s.text, kind: s.kind })),
570
+ findings: synthReceipts,
571
+ runStats,
572
+ answerPath: `sessions/${sessionId}/answer.md`,
573
+ };