@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,85 @@
1
+ // task-loader.mjs — pure: load + validate a task definition from schedules/.
2
+ // Task path: <repoRoot>/schedules/<id>.json
3
+ // Required fields: id, command
4
+ import { readFile } from 'node:fs/promises';
5
+ import { join } from 'node:path';
6
+
7
+ const REQUIRED_FIELDS = ['id', 'command'];
8
+
9
+ /**
10
+ * Reject task commands that contain shell quoting. The runner spawns
11
+ * commands directly (no shell), so quotes are not honoured — they would
12
+ * silently shred the args (e.g. `bash -c "a && b"` → spawn('bash',
13
+ * ['-c', '"a', '&&', 'b"']), bash exits 2 with "unexpected EOF").
14
+ *
15
+ * Validated at load time so --dry-run also catches the bad shape, and
16
+ * re-asserted by runner.parseCommand as a chokepoint guarantee for
17
+ * programmatic callers.
18
+ *
19
+ * @param {string} command
20
+ * @throws {Error & { exitCode: 2 }} if command is not a non-empty string,
21
+ * or contains a single (') or double (") quote character
22
+ */
23
+ export function assertNoShellQuoting(command) {
24
+ if (typeof command !== 'string') {
25
+ const e = new Error('command must be a string');
26
+ e.exitCode = 2;
27
+ throw e;
28
+ }
29
+ const m = command.match(/['"]/);
30
+ if (m) {
31
+ const e = new Error(
32
+ `command contains shell quoting (${JSON.stringify(m[0])} at index ${m.index}) — ` +
33
+ 'the runner spawns commands directly without a shell, so quotes are not honoured ' +
34
+ 'and would silently shred the args. If you need shell features (variable expansion, ' +
35
+ '&&, pipes, sourcing), wrap the command in a script file and invoke the script, ' +
36
+ 'or move the shell wrapper to the cron entry instead of the task command.'
37
+ );
38
+ e.exitCode = 2;
39
+ throw e;
40
+ }
41
+ }
42
+
43
+ export async function loadTask(repoRoot, taskId) {
44
+ const path = join(repoRoot, 'schedules', `${taskId}.json`);
45
+ let raw;
46
+ try {
47
+ raw = await readFile(path, 'utf8');
48
+ } catch (err) {
49
+ if (err.code === 'ENOENT') {
50
+ const e = new Error(`task "${taskId}" not found in schedules (${path})`);
51
+ e.exitCode = 2;
52
+ throw e;
53
+ }
54
+ const e = new Error(`could not read schedule file for "${taskId}": ${err.message}`);
55
+ e.exitCode = 3;
56
+ throw e;
57
+ }
58
+
59
+ let data;
60
+ try {
61
+ data = JSON.parse(raw);
62
+ } catch (err) {
63
+ const e = new Error(`schedule file for "${taskId}" is not valid JSON: ${err.message}`);
64
+ e.exitCode = 2;
65
+ throw e;
66
+ }
67
+
68
+ for (const field of REQUIRED_FIELDS) {
69
+ if (!data[field]) {
70
+ const e = new Error(`task "${taskId}": missing required field "${field}"`);
71
+ e.exitCode = 2;
72
+ throw e;
73
+ }
74
+ }
75
+
76
+ try {
77
+ assertNoShellQuoting(data.command);
78
+ } catch (err) {
79
+ const e = new Error(`task "${taskId}": ${err.message}`);
80
+ e.exitCode = err.exitCode ?? 2;
81
+ throw e;
82
+ }
83
+
84
+ return data;
85
+ }
@@ -0,0 +1,61 @@
1
+ // lint — voice/format linter subcommand router.
2
+ //
3
+ // Wired as `pa lint <verb>` (verb-second pattern, mirrors `pa gsc`). Today the
4
+ // sole verb is `linkedin`; the router exists so other surfaces (article,
5
+ // newsletter) can land as sibling verbs without reshaping the dispatch.
6
+ //
7
+ // Exit codes are lint-semantic and owned by each command (0 clean / 1 errors /
8
+ // 2 warnings / 3 usage|IO|config) — see docs/lint-linkedin.md. The router
9
+ // itself returns 3 for its own usage errors (no verb, unknown verb).
10
+
11
+ import { run as runLinkedin } from './commands/linkedin.mjs';
12
+
13
+ const HELP = `pa lint — lint operator-authored text against voice rules
14
+
15
+ Usage:
16
+ pa lint linkedin <input-file> [--rules <path>] [--format <text|json>]
17
+ [--severity <error|warning|info>] [--surface <name>]
18
+ Lint a file against the LinkedIn voice-rule corpus and report
19
+ violations in alter-format (swap → / cut → / rename →).
20
+
21
+ pa lint -h, --help
22
+ Show this help.
23
+
24
+ Exit codes (lint-semantic, ESLint-style — see docs/lint-linkedin.md):
25
+ 0 clean | 1 errors found | 2 warnings only | 3 usage / IO / config error
26
+ `;
27
+
28
+ export async function run(argv, overrides = {}) {
29
+ const verb = argv[0];
30
+ const rest = argv.slice(1);
31
+
32
+ if (!verb || verb === '--help' || verb === '-h' || verb === 'help') {
33
+ (overrides.stdout ?? process.stdout).write(HELP);
34
+ // No verb is a usage error (3); explicit help is success (0).
35
+ return verb ? 0 : 3;
36
+ }
37
+
38
+ switch (verb) {
39
+ case 'linkedin':
40
+ return runLinkedin(rest, overrides);
41
+ default:
42
+ (overrides.stderr ?? process.stderr).write(`[pa lint] unknown verb: ${verb}\n`);
43
+ (overrides.stdout ?? process.stdout).write(HELP);
44
+ return 3;
45
+ }
46
+ }
47
+
48
+ // CLI entry: map thrown errors to exit codes (err.exitCode or 3 for the
49
+ // lint family — every non-finding failure here is usage/IO/config).
50
+ export async function main(argv, overrides = {}) {
51
+ try {
52
+ const code = await run(argv, overrides);
53
+ return code ?? 0;
54
+ } catch (err) {
55
+ const code = err && typeof err.exitCode === 'number' ? err.exitCode : 3;
56
+ (overrides.stderr ?? process.stderr).write(
57
+ `pa lint: ${err && err.message ? err.message : err}\n`,
58
+ );
59
+ return code;
60
+ }
61
+ }
@@ -0,0 +1,133 @@
1
+ // linkedin.mjs — `pa lint linkedin <file>` command.
2
+ //
3
+ // Loads the voice-rule corpus, lints the input file, prints violations in
4
+ // alter-format, and returns a lint-semantic exit code.
5
+ //
6
+ // Exit codes for this command (deliberately lint-semantic, ESLint-style —
7
+ // see docs/lint-linkedin.md for why this differs from tooling standard §6):
8
+ // 0 clean (or only info / below floor) | 1 errors found |
9
+ // 2 warnings only | 3 usage / IO / config error
10
+
11
+ import { readFile } from 'node:fs/promises';
12
+ import { existsSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { getRepoRoot } from '../../pa/repo-root.mjs';
15
+ import { loadRules } from '../rules.mjs';
16
+ import { lint, applySeverityFloor, exitCodeFor } from '../linter.mjs';
17
+ import { formatText, formatJson } from '../reporter.mjs';
18
+
19
+ const DEFAULT_RULES_REL = 'operator/knowledge/writing/linkedin-rules.json';
20
+ const FORMATS = new Set(['text', 'json']);
21
+ const SEVERITIES = new Set(['error', 'warning', 'info']);
22
+
23
+ export const USAGE = `pa lint linkedin — lint text against the LinkedIn voice rules
24
+
25
+ Usage:
26
+ pa lint linkedin <input-file> [options]
27
+
28
+ Options:
29
+ --rules <path> Rule corpus JSON. Defaults to
30
+ ${DEFAULT_RULES_REL} relative to the operator repo
31
+ root; required if that file is absent.
32
+ --format <text|json> Output format (default: text).
33
+ --severity <error|warning|info>
34
+ Severity floor — only violations at or above this
35
+ level are reported (default: error).
36
+ --surface <name> Surface context: all | linkedin | article |
37
+ newsletter | reply (default: linkedin). Rules whose
38
+ surface is "all" or includes this value apply.
39
+ -h, --help Show this help.
40
+
41
+ Output (text): one line per violation —
42
+ <file>:<line>:<col> <severity> <ruleId> <matched> → <alter-guidance>
43
+
44
+ Exit codes (lint-semantic):
45
+ 0 clean | 1 errors found | 2 warnings only | 3 usage / IO / config error
46
+ `;
47
+
48
+ function usageError(message) {
49
+ return Object.assign(new Error(message), { exitCode: 3 });
50
+ }
51
+
52
+ // Minimal flag parser: one positional (the input file) plus the long flags
53
+ // above. Unknown flags and missing flag values are usage errors.
54
+ export function parseArgs(argv) {
55
+ const opts = { file: null, rules: null, format: 'text', severity: 'error', surface: 'linkedin' };
56
+ for (let i = 0; i < argv.length; i += 1) {
57
+ const arg = argv[i];
58
+ const takeValue = (name) => {
59
+ const v = argv[i + 1];
60
+ if (v === undefined) throw usageError(`${name} requires a value`);
61
+ i += 1;
62
+ return v;
63
+ };
64
+ switch (arg) {
65
+ case '--rules': opts.rules = takeValue('--rules'); break;
66
+ case '--format': opts.format = takeValue('--format'); break;
67
+ case '--severity': opts.severity = takeValue('--severity'); break;
68
+ case '--surface': opts.surface = takeValue('--surface'); break;
69
+ default:
70
+ if (arg.startsWith('-')) throw usageError(`unknown flag: ${arg}`);
71
+ if (opts.file !== null) throw usageError(`unexpected extra argument: ${arg}`);
72
+ opts.file = arg;
73
+ }
74
+ }
75
+ if (!opts.file) throw usageError('missing <input-file>');
76
+ if (!FORMATS.has(opts.format)) throw usageError(`invalid --format "${opts.format}" (text|json)`);
77
+ if (!SEVERITIES.has(opts.severity)) {
78
+ throw usageError(`invalid --severity "${opts.severity}" (error|warning|info)`);
79
+ }
80
+ return opts;
81
+ }
82
+
83
+ // Resolve the rules path: explicit --rules wins; otherwise the default under
84
+ // the operator repo root, if that file exists. No default and no flag is a
85
+ // usage error with an actionable message.
86
+ async function resolveRulesPath(explicit) {
87
+ if (explicit) {
88
+ if (!existsSync(explicit)) throw usageError(`rules file not found: ${explicit}`);
89
+ return explicit;
90
+ }
91
+ let root;
92
+ try {
93
+ root = await getRepoRoot();
94
+ } catch {
95
+ root = null;
96
+ }
97
+ if (root) {
98
+ const candidate = join(root, DEFAULT_RULES_REL);
99
+ if (existsSync(candidate)) return candidate;
100
+ }
101
+ throw usageError(
102
+ `no --rules given and default corpus not found (expected ${DEFAULT_RULES_REL} under the repo root); pass --rules <path>`,
103
+ );
104
+ }
105
+
106
+ export async function run(argv, overrides = {}) {
107
+ const out = overrides.stdout ?? process.stdout;
108
+ if (argv[0] === '-h' || argv[0] === '--help') {
109
+ out.write(USAGE);
110
+ return 0;
111
+ }
112
+ const opts = parseArgs(argv);
113
+
114
+ const rulesPath = await resolveRulesPath(opts.rules);
115
+ const rules = await loadRules(rulesPath);
116
+
117
+ let text;
118
+ try {
119
+ text = await readFile(opts.file, 'utf8');
120
+ } catch (e) {
121
+ throw usageError(`cannot read input file ${opts.file}: ${e.message}`);
122
+ }
123
+
124
+ const all = lint(text, rules, { surface: opts.surface });
125
+ const reported = applySeverityFloor(all, opts.severity);
126
+
127
+ if (opts.format === 'json') {
128
+ out.write(formatJson(reported, opts.file) + '\n');
129
+ } else if (reported.length > 0) {
130
+ out.write(formatText(reported, opts.file) + '\n');
131
+ }
132
+ return exitCodeFor(reported);
133
+ }
@@ -0,0 +1,90 @@
1
+ // detectors.mjs — the three detector types for the linkedin voice linter.
2
+ //
3
+ // A detector takes the input text and a rule's `detect` block and returns an
4
+ // array of matches: { line, col, matched }. Lines and columns are 1-based so
5
+ // the text reporter can emit clickable `file:line:col` pointers.
6
+ //
7
+ // `literal-phrase` and `regex` scan line-by-line so positions fall out
8
+ // naturally and a regex's negated character classes (e.g. `[^.,;]`) cannot
9
+ // silently swallow newlines. `count` is document-wide: it flags every match
10
+ // beyond `max` so the writer sees each excess occurrence, not just the count.
11
+
12
+ const WORD = /\w/;
13
+
14
+ // Escape a literal string for safe embedding in a RegExp source.
15
+ function escapeRegex(s) {
16
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
17
+ }
18
+
19
+ // Build the RegExp used by a `literal-phrase` detector.
20
+ //
21
+ // Word boundaries are applied conditionally: a leading `\b` only when the
22
+ // phrase starts with a word character, a trailing `\b` only when it ends with
23
+ // one. A blanket `\b...\b` around a phrase like "it's worth noting" is fine,
24
+ // but around punctuation-edged phrases it would never match — conditional
25
+ // anchoring keeps boundaries meaningful without false negatives.
26
+ function buildPhraseRegex(detect) {
27
+ const phrase = detect.phrase ?? '';
28
+ const wordBoundary = detect.wordBoundary !== false; // default true
29
+ const caseSensitive = detect.caseSensitive === true; // default false
30
+ let src = escapeRegex(phrase);
31
+ if (wordBoundary) {
32
+ if (WORD.test(phrase[0] ?? '')) src = '\\b' + src;
33
+ if (WORD.test(phrase[phrase.length - 1] ?? '')) src = src + '\\b';
34
+ }
35
+ return new RegExp(src, caseSensitive ? 'g' : 'gi');
36
+ }
37
+
38
+ function buildRegex(detect) {
39
+ const caseSensitive = detect.caseSensitive === true; // default false
40
+ return new RegExp(detect.pattern, caseSensitive ? 'g' : 'gi');
41
+ }
42
+
43
+ // Scan one line for every non-overlapping match of `re` (which must carry the
44
+ // global flag). Returns [{ col, matched }] with 1-based columns.
45
+ function scanLine(line, re) {
46
+ const out = [];
47
+ re.lastIndex = 0;
48
+ let m;
49
+ while ((m = re.exec(line)) !== null) {
50
+ out.push({ col: m.index + 1, matched: m[0] });
51
+ // Guard against zero-width matches looping forever.
52
+ if (m.index === re.lastIndex) re.lastIndex += 1;
53
+ }
54
+ return out;
55
+ }
56
+
57
+ // Walk the document line-by-line, applying `re` to each line.
58
+ function scanDocument(text, re) {
59
+ const out = [];
60
+ const lines = text.split('\n');
61
+ for (let i = 0; i < lines.length; i += 1) {
62
+ for (const hit of scanLine(lines[i], re)) {
63
+ out.push({ line: i + 1, col: hit.col, matched: hit.matched });
64
+ }
65
+ }
66
+ return out;
67
+ }
68
+
69
+ // Dispatch a rule's detect block against the text.
70
+ export function detect(text, detectBlock) {
71
+ switch (detectBlock.type) {
72
+ case 'literal-phrase':
73
+ return scanDocument(text, buildPhraseRegex(detectBlock));
74
+ case 'regex':
75
+ return scanDocument(text, buildRegex(detectBlock));
76
+ case 'count': {
77
+ const re = buildRegex({ pattern: detectBlock.pattern, caseSensitive: detectBlock.caseSensitive });
78
+ const all = scanDocument(text, re);
79
+ const max = Number.isFinite(detectBlock.max) ? detectBlock.max : 0;
80
+ // Pass if matches <= max. Otherwise flag every occurrence past the
81
+ // ceiling (the (max+1)th onward) so each excess use is actionable.
82
+ return all.length > max ? all.slice(max) : [];
83
+ }
84
+ default:
85
+ // Unreachable when rules are validated upstream, but be defensive.
86
+ throw Object.assign(new Error(`unknown detect type: ${detectBlock.type}`), { exitCode: 3 });
87
+ }
88
+ }
89
+
90
+ export { escapeRegex, buildPhraseRegex, buildRegex, scanLine, scanDocument };
@@ -0,0 +1,69 @@
1
+ // linter.mjs — run a validated rule corpus over an input text.
2
+ //
3
+ // Pure engine: no IO, no process exit. Takes text + rules + options, returns
4
+ // a flat list of violations sorted by position. The CLI layer handles file
5
+ // reading, severity-floor filtering, formatting, and exit codes.
6
+
7
+ import { detect } from './detectors.mjs';
8
+ import { ruleAppliesToSurface } from './rules.mjs';
9
+
10
+ const SEVERITY_RANK = { info: 0, warning: 1, error: 2 };
11
+
12
+ // Render a rule's alter block into a single guidance string in alter-format:
13
+ // swap -> "swap → "use" | "X"" (the candidate replacements, then why)
14
+ // cut -> "cut → <guidance>"
15
+ // rename -> "rename → <guidance>"
16
+ export function formatAlter(alter) {
17
+ if (alter.kind === 'swap') {
18
+ const opts = alter.with.map((w) => JSON.stringify(w)).join(' | ');
19
+ return `swap → ${opts} — ${alter.guidance}`;
20
+ }
21
+ if (alter.kind === 'cut') return `cut → ${alter.guidance}`;
22
+ return `rename → ${alter.guidance}`;
23
+ }
24
+
25
+ // Lint `text` against `rules` for the given surface. Returns every violation
26
+ // (unfiltered by severity); ordering is by line then column then ruleId so
27
+ // output is deterministic.
28
+ export function lint(text, rules, { surface = 'linkedin' } = {}) {
29
+ const violations = [];
30
+ for (const rule of rules) {
31
+ if (!ruleAppliesToSurface(rule, surface)) continue;
32
+ for (const hit of detect(text, rule.detect)) {
33
+ violations.push({
34
+ line: hit.line,
35
+ col: hit.col,
36
+ ruleId: rule.ruleId,
37
+ category: rule.category,
38
+ severity: rule.severity,
39
+ matched: hit.matched,
40
+ alter: formatAlter(rule.alter),
41
+ alterKind: rule.alter.kind,
42
+ rationale: rule.rationale,
43
+ source: rule.source,
44
+ });
45
+ }
46
+ }
47
+ violations.sort((a, b) =>
48
+ a.line - b.line || a.col - b.col || a.ruleId.localeCompare(b.ruleId),
49
+ );
50
+ return violations;
51
+ }
52
+
53
+ // Keep only violations at or above the severity floor.
54
+ export function applySeverityFloor(violations, floor) {
55
+ const min = SEVERITY_RANK[floor] ?? SEVERITY_RANK.error;
56
+ return violations.filter((v) => SEVERITY_RANK[v.severity] >= min);
57
+ }
58
+
59
+ // Exit code from the reported (post-floor) violations, per the lint contract:
60
+ // 1 = at least one error 2 = warnings (no errors) 0 = info-only or clean
61
+ export function exitCodeFor(violations) {
62
+ let worst = -1;
63
+ for (const v of violations) worst = Math.max(worst, SEVERITY_RANK[v.severity]);
64
+ if (worst === SEVERITY_RANK.error) return 1;
65
+ if (worst === SEVERITY_RANK.warning) return 2;
66
+ return 0;
67
+ }
68
+
69
+ export { SEVERITY_RANK };
@@ -0,0 +1,32 @@
1
+ // reporter.mjs — render violations as text or JSON.
2
+ //
3
+ // text: one line per violation —
4
+ // <file>:<line>:<col> <severity> <ruleId> <matched> → <alter-guidance>
5
+ // json: a machine-readable array, each entry
6
+ // { file, line, col, ruleId, severity, matched, alter, rationale, source }
7
+
8
+ export function formatText(violations, file) {
9
+ return violations
10
+ .map((v) =>
11
+ `${file}:${v.line}:${v.col} ${v.severity} ${v.ruleId} ${JSON.stringify(v.matched)} → ${v.alter}`,
12
+ )
13
+ .join('\n');
14
+ }
15
+
16
+ export function toJsonShape(violations, file) {
17
+ return violations.map((v) => ({
18
+ file,
19
+ line: v.line,
20
+ col: v.col,
21
+ ruleId: v.ruleId,
22
+ severity: v.severity,
23
+ matched: v.matched,
24
+ alter: v.alter,
25
+ rationale: v.rationale,
26
+ source: v.source,
27
+ }));
28
+ }
29
+
30
+ export function formatJson(violations, file) {
31
+ return JSON.stringify(toJsonShape(violations, file), null, 2);
32
+ }
@@ -0,0 +1,132 @@
1
+ // rules.mjs — load and validate the linkedin voice-rule corpus.
2
+ //
3
+ // The corpus is a JSON array of rule objects. The schema is the contract
4
+ // shared with the production corpus authored at
5
+ // operator/knowledge/writing/linkedin-rules.json. Validation is strict: a
6
+ // malformed corpus is a configuration error (exit 3), not a lint finding —
7
+ // we refuse to run rather than silently skip broken rules.
8
+
9
+ import { readFile } from 'node:fs/promises';
10
+
11
+ const CATEGORIES = new Set([
12
+ 'punctuation', 'vocabulary', 'phrase', 'reaction-phrase',
13
+ 'filler', 'structural', 'format', 'count',
14
+ ]);
15
+ const SEVERITIES = new Set(['error', 'warning', 'info']);
16
+ const SURFACES = new Set(['all', 'linkedin', 'article', 'newsletter', 'reply']);
17
+ const DETECT_TYPES = new Set(['literal-phrase', 'regex', 'count']);
18
+ const ALTER_KINDS = new Set(['swap', 'cut', 'rename']);
19
+
20
+ function configError(message) {
21
+ return Object.assign(new Error(message), { exitCode: 3 });
22
+ }
23
+
24
+ // Validate one rule, throwing a configError describing the first problem.
25
+ // `where` locates the rule in the corpus for the operator.
26
+ function validateRule(rule, where) {
27
+ if (rule === null || typeof rule !== 'object' || Array.isArray(rule)) {
28
+ throw configError(`${where}: rule must be an object`);
29
+ }
30
+ const id = typeof rule.ruleId === 'string' ? rule.ruleId : null;
31
+ if (!id) throw configError(`${where}: missing or non-string ruleId`);
32
+ const at = `rule "${id}"`;
33
+
34
+ if (!CATEGORIES.has(rule.category)) {
35
+ throw configError(`${at}: invalid category "${rule.category}"`);
36
+ }
37
+ if (!Array.isArray(rule.surface) || rule.surface.length === 0) {
38
+ throw configError(`${at}: surface must be a non-empty array`);
39
+ }
40
+ for (const s of rule.surface) {
41
+ if (!SURFACES.has(s)) throw configError(`${at}: invalid surface "${s}"`);
42
+ }
43
+ if (!SEVERITIES.has(rule.severity)) {
44
+ throw configError(`${at}: invalid severity "${rule.severity}"`);
45
+ }
46
+
47
+ const d = rule.detect;
48
+ if (!d || typeof d !== 'object') throw configError(`${at}: missing detect block`);
49
+ if (!DETECT_TYPES.has(d.type)) throw configError(`${at}: invalid detect.type "${d.type}"`);
50
+ if (d.type === 'literal-phrase') {
51
+ if (typeof d.phrase !== 'string' || d.phrase.length === 0) {
52
+ throw configError(`${at}: literal-phrase detect needs a non-empty phrase`);
53
+ }
54
+ } else if (d.type === 'regex') {
55
+ if (typeof d.pattern !== 'string' || d.pattern.length === 0) {
56
+ throw configError(`${at}: regex detect needs a pattern`);
57
+ }
58
+ assertCompiles(d.pattern, at);
59
+ } else if (d.type === 'count') {
60
+ if (typeof d.pattern !== 'string' || d.pattern.length === 0) {
61
+ throw configError(`${at}: count detect needs a pattern`);
62
+ }
63
+ if (typeof d.max !== 'number' || !Number.isFinite(d.max) || d.max < 0) {
64
+ throw configError(`${at}: count detect needs a non-negative numeric max`);
65
+ }
66
+ assertCompiles(d.pattern, at);
67
+ }
68
+
69
+ const a = rule.alter;
70
+ if (!a || typeof a !== 'object') throw configError(`${at}: missing alter block`);
71
+ if (!ALTER_KINDS.has(a.kind)) throw configError(`${at}: invalid alter.kind "${a.kind}"`);
72
+ if (typeof a.guidance !== 'string' || a.guidance.length === 0) {
73
+ throw configError(`${at}: alter needs guidance`);
74
+ }
75
+ if (a.kind === 'swap') {
76
+ if (!Array.isArray(a.with) || a.with.length === 0 || !a.with.every((w) => typeof w === 'string')) {
77
+ throw configError(`${at}: swap alter needs a non-empty array of string replacements`);
78
+ }
79
+ }
80
+
81
+ if (typeof rule.rationale !== 'string' || rule.rationale.length === 0) {
82
+ throw configError(`${at}: missing rationale`);
83
+ }
84
+ if (typeof rule.source !== 'string' || rule.source.length === 0) {
85
+ throw configError(`${at}: missing source`);
86
+ }
87
+ return id;
88
+ }
89
+
90
+ function assertCompiles(pattern, at) {
91
+ try {
92
+ new RegExp(pattern);
93
+ } catch (e) {
94
+ throw configError(`${at}: detect pattern does not compile: ${e.message}`);
95
+ }
96
+ }
97
+
98
+ // Validate a parsed corpus (array of rules). Returns the rules unchanged.
99
+ export function validateCorpus(rules) {
100
+ if (!Array.isArray(rules)) throw configError('rule corpus must be a JSON array');
101
+ const seen = new Set();
102
+ rules.forEach((rule, i) => {
103
+ const id = validateRule(rule, `corpus[${i}]`);
104
+ if (seen.has(id)) throw configError(`duplicate ruleId "${id}"`);
105
+ seen.add(id);
106
+ });
107
+ return rules;
108
+ }
109
+
110
+ // Read, parse, and validate the corpus at `path`.
111
+ export async function loadRules(path) {
112
+ let raw;
113
+ try {
114
+ raw = await readFile(path, 'utf8');
115
+ } catch (e) {
116
+ throw configError(`cannot read rules file ${path}: ${e.message}`);
117
+ }
118
+ let parsed;
119
+ try {
120
+ parsed = JSON.parse(raw);
121
+ } catch (e) {
122
+ throw configError(`rules file ${path} is not valid JSON: ${e.message}`);
123
+ }
124
+ return validateCorpus(parsed);
125
+ }
126
+
127
+ // Rule applies if it targets the active surface ("all" matches every surface).
128
+ export function ruleAppliesToSurface(rule, surface) {
129
+ return rule.surface.includes('all') || rule.surface.includes(surface);
130
+ }
131
+
132
+ export { CATEGORIES, SEVERITIES, SURFACES, DETECT_TYPES, ALTER_KINDS };