dsh-compound-engineering 0.0.0-stage → 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 (461) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -2
  3. package/README.zh.md +161 -0
  4. package/cordis.patch.yml +6 -0
  5. package/lib/frontmatter.js +167 -0
  6. package/lib/index.js +46 -0
  7. package/lib/provider.js +201 -0
  8. package/package.json +62 -4
  9. package/skills/CODING_STANDARDS.md +68 -0
  10. package/skills/ce-babysit-pr/SKILL.md +59 -0
  11. package/skills/ce-babysit-pr/references/branch-currency.md +22 -0
  12. package/skills/ce-babysit-pr/references/envelope.md +44 -0
  13. package/skills/ce-babysit-pr/references/pipeline.md +15 -0
  14. package/skills/ce-babysit-pr/references/report.md +20 -0
  15. package/skills/ce-babysit-pr/references/settle.md +37 -0
  16. package/skills/ce-babysit-pr/references/setup.md +33 -0
  17. package/skills/ce-babysit-pr/references/stack-commands.md +39 -0
  18. package/skills/ce-babysit-pr/references/stack.md +43 -0
  19. package/skills/ce-babysit-pr/references/tick.md +102 -0
  20. package/skills/ce-babysit-pr/references/watch-loop.md +222 -0
  21. package/skills/ce-babysit-pr/scripts/pr-snapshot +3656 -0
  22. package/skills/ce-bakeoff/SKILL.md +48 -0
  23. package/skills/ce-bakeoff/references/candidates.md +37 -0
  24. package/skills/ce-bakeoff/references/judging.md +19 -0
  25. package/skills/ce-bakeoff/references/output.md +11 -0
  26. package/skills/ce-bakeoff/references/verification.md +15 -0
  27. package/skills/ce-brainstorm/SKILL.md +69 -0
  28. package/skills/ce-brainstorm/references/agents/slack-researcher.md +127 -0
  29. package/skills/ce-brainstorm/references/approaches.md +57 -0
  30. package/skills/ce-brainstorm/references/bakeoff.md +9 -0
  31. package/skills/ce-brainstorm/references/blindspot-pass.md +70 -0
  32. package/skills/ce-brainstorm/references/brainstorm-sections.md +458 -0
  33. package/skills/ce-brainstorm/references/dialogue.md +89 -0
  34. package/skills/ce-brainstorm/references/handoff.md +187 -0
  35. package/skills/ce-brainstorm/references/html-rendering.md +638 -0
  36. package/skills/ce-brainstorm/references/interaction-rules.md +33 -0
  37. package/skills/ce-brainstorm/references/markdown-rendering.md +242 -0
  38. package/skills/ce-brainstorm/references/model-tiers.md +13 -0
  39. package/skills/ce-brainstorm/references/output-mode.md +26 -0
  40. package/skills/ce-brainstorm/references/phase-0.md +104 -0
  41. package/skills/ce-brainstorm/references/plan-write.md +32 -0
  42. package/skills/ce-brainstorm/references/product-pressure-test.md +42 -0
  43. package/skills/ce-brainstorm/references/reasoning-elevation.md +124 -0
  44. package/skills/ce-brainstorm/references/settled-decisions.md +45 -0
  45. package/skills/ce-brainstorm/references/synthesis-summary.md +149 -0
  46. package/skills/ce-brainstorm/references/universal-brainstorming.md +72 -0
  47. package/skills/ce-brainstorm/references/verdict-routing.md +26 -0
  48. package/skills/ce-brainstorm/references/visual-probes.md +164 -0
  49. package/skills/ce-brainstorm/scripts/elevation-dispatch.sh +288 -0
  50. package/skills/ce-brainstorm/scripts/light-webserver.js +1275 -0
  51. package/skills/ce-brainstorm/scripts/packs-resolve.py +737 -0
  52. package/skills/ce-brainstorm/scripts/peer-job-runner.py +2594 -0
  53. package/skills/ce-code-review/SKILL.md +40 -0
  54. package/skills/ce-code-review/references/action-class-rubric.md +67 -0
  55. package/skills/ce-code-review/references/cross-model-recovery.md +38 -0
  56. package/skills/ce-code-review/references/cross-model-review.md +191 -0
  57. package/skills/ce-code-review/references/depth-paths.md +32 -0
  58. package/skills/ce-code-review/references/diff-scope.md +59 -0
  59. package/skills/ce-code-review/references/dispatch-reviewers.md +135 -0
  60. package/skills/ce-code-review/references/findings-schema.json +125 -0
  61. package/skills/ce-code-review/references/finish-input.md +118 -0
  62. package/skills/ce-code-review/references/finish-review.md +202 -0
  63. package/skills/ce-code-review/references/intent-and-plan.md +73 -0
  64. package/skills/ce-code-review/references/modes-and-output.md +156 -0
  65. package/skills/ce-code-review/references/persona-catalog.md +66 -0
  66. package/skills/ce-code-review/references/personas/adversarial-reviewer.md +111 -0
  67. package/skills/ce-code-review/references/personas/agent-native-reviewer.md +173 -0
  68. package/skills/ce-code-review/references/personas/api-contract-reviewer.md +44 -0
  69. package/skills/ce-code-review/references/personas/correctness-reviewer.md +48 -0
  70. package/skills/ce-code-review/references/personas/data-migration-reviewer.md +111 -0
  71. package/skills/ce-code-review/references/personas/deployment-verification-agent.md +157 -0
  72. package/skills/ce-code-review/references/personas/julik-frontend-races-reviewer.md +45 -0
  73. package/skills/ce-code-review/references/personas/learnings-researcher.md +260 -0
  74. package/skills/ce-code-review/references/personas/maintainability-reviewer.md +85 -0
  75. package/skills/ce-code-review/references/personas/performance-reviewer.md +45 -0
  76. package/skills/ce-code-review/references/personas/previous-comments-reviewer.md +59 -0
  77. package/skills/ce-code-review/references/personas/project-standards-reviewer.md +77 -0
  78. package/skills/ce-code-review/references/personas/reliability-reviewer.md +49 -0
  79. package/skills/ce-code-review/references/personas/security-reviewer.md +50 -0
  80. package/skills/ce-code-review/references/personas/swift-ios-reviewer.md +99 -0
  81. package/skills/ce-code-review/references/personas/testing-reviewer.md +51 -0
  82. package/skills/ce-code-review/references/review-output-template.md +172 -0
  83. package/skills/ce-code-review/references/scope.md +176 -0
  84. package/skills/ce-code-review/references/select-and-route.md +93 -0
  85. package/skills/ce-code-review/references/subagent-template.md +206 -0
  86. package/skills/ce-code-review/references/validator-batch-template.md +67 -0
  87. package/skills/ce-code-review/scripts/cross-model-adversarial-review.sh +1275 -0
  88. package/skills/ce-code-review/scripts/findings-mechanics.py +274 -0
  89. package/skills/ce-code-review/scripts/packs-resolve.py +737 -0
  90. package/skills/ce-code-review/scripts/peer-job-runner.py +2594 -0
  91. package/skills/ce-code-review/scripts/review-scope.py +419 -0
  92. package/skills/ce-code-review/scripts/run-log.py +280 -0
  93. package/skills/ce-commit/SKILL.md +60 -0
  94. package/skills/ce-commit-push-pr/SKILL.md +69 -0
  95. package/skills/ce-commit-push-pr/references/apply-and-handoff.md +79 -0
  96. package/skills/ce-commit-push-pr/references/branch-creation.md +47 -0
  97. package/skills/ce-commit-push-pr/references/commit-and-push.md +26 -0
  98. package/skills/ce-commit-push-pr/references/compose.md +29 -0
  99. package/skills/ce-commit-push-pr/references/context.md +42 -0
  100. package/skills/ce-commit-push-pr/references/gh-stack-cli.md +83 -0
  101. package/skills/ce-commit-push-pr/references/pr-description-writing.md +208 -0
  102. package/skills/ce-commit-push-pr/references/stack-submit.md +81 -0
  103. package/skills/ce-compound/SKILL.md +79 -0
  104. package/skills/ce-compound/assets/resolution-template.md +96 -0
  105. package/skills/ce-compound/references/agents/best-practices-researcher.md +115 -0
  106. package/skills/ce-compound/references/agents/data-integrity-guardian.md +68 -0
  107. package/skills/ce-compound/references/agents/framework-docs-researcher.md +93 -0
  108. package/skills/ce-compound/references/agents/pattern-recognition-specialist.md +55 -0
  109. package/skills/ce-compound/references/agents/performance-oracle.md +108 -0
  110. package/skills/ce-compound/references/agents/security-sentinel.md +91 -0
  111. package/skills/ce-compound/references/agents/session-historian.md +82 -0
  112. package/skills/ce-compound/references/assembly.md +127 -0
  113. package/skills/ce-compound/references/concepts-vocabulary.md +98 -0
  114. package/skills/ce-compound/references/enhancement.md +42 -0
  115. package/skills/ce-compound/references/grounding-validation.md +89 -0
  116. package/skills/ce-compound/references/lightweight.md +56 -0
  117. package/skills/ce-compound/references/modes.md +13 -0
  118. package/skills/ce-compound/references/refresh-and-discoverability.md +96 -0
  119. package/skills/ce-compound/references/report.md +115 -0
  120. package/skills/ce-compound/references/research.md +144 -0
  121. package/skills/ce-compound/references/schema.yaml +236 -0
  122. package/skills/ce-compound/references/session-history.md +73 -0
  123. package/skills/ce-compound/references/yaml-schema.md +130 -0
  124. package/skills/ce-compound/scripts/packs-resolve.py +737 -0
  125. package/skills/ce-compound/scripts/session-history/__pycache__/dsh_session.cpython-314.pyc +0 -0
  126. package/skills/ce-compound/scripts/session-history/discover-sessions.sh +290 -0
  127. package/skills/ce-compound/scripts/session-history/discover_dsh.py +123 -0
  128. package/skills/ce-compound/scripts/session-history/dsh_session.py +225 -0
  129. package/skills/ce-compound/scripts/session-history/extract-errors.py +178 -0
  130. package/skills/ce-compound/scripts/session-history/extract-metadata.py +301 -0
  131. package/skills/ce-compound/scripts/session-history/extract-skeleton.py +346 -0
  132. package/skills/ce-compound/scripts/validate-doc-claims.py +497 -0
  133. package/skills/ce-compound/scripts/validate-frontmatter.py +137 -0
  134. package/skills/ce-compound-refresh/SKILL.md +88 -0
  135. package/skills/ce-compound-refresh/assets/resolution-template.md +96 -0
  136. package/skills/ce-compound-refresh/references/classify.md +42 -0
  137. package/skills/ce-compound-refresh/references/commit.md +7 -0
  138. package/skills/ce-compound-refresh/references/concepts-vocabulary.md +116 -0
  139. package/skills/ce-compound-refresh/references/discoverability.md +15 -0
  140. package/skills/ce-compound-refresh/references/investigate.md +23 -0
  141. package/skills/ce-compound-refresh/references/modes.md +27 -0
  142. package/skills/ce-compound-refresh/references/per-action-flows.md +142 -0
  143. package/skills/ce-compound-refresh/references/report.md +23 -0
  144. package/skills/ce-compound-refresh/references/schema.yaml +236 -0
  145. package/skills/ce-compound-refresh/references/scope.md +18 -0
  146. package/skills/ce-compound-refresh/references/worth-audit.md +35 -0
  147. package/skills/ce-compound-refresh/references/yaml-schema.md +130 -0
  148. package/skills/ce-compound-refresh/scripts/validate-doc-claims.py +497 -0
  149. package/skills/ce-compound-refresh/scripts/validate-frontmatter.py +137 -0
  150. package/skills/ce-debug/SKILL.md +122 -0
  151. package/skills/ce-debug/references/anti-patterns.md +91 -0
  152. package/skills/ce-debug/references/defense-in-depth.md +39 -0
  153. package/skills/ce-debug/references/fix.md +21 -0
  154. package/skills/ce-debug/references/investigate.md +114 -0
  155. package/skills/ce-debug/references/investigation-techniques.md +384 -0
  156. package/skills/ce-debug/references/pipeline-mode.md +78 -0
  157. package/skills/ce-debug/references/post-fix-handoff.md +60 -0
  158. package/skills/ce-debug/references/return-to-caller.md +57 -0
  159. package/skills/ce-doc-review/SKILL.md +75 -0
  160. package/skills/ce-doc-review/references/bulk-preview.md +146 -0
  161. package/skills/ce-doc-review/references/cross-model-review.md +207 -0
  162. package/skills/ce-doc-review/references/decision-primer.md +47 -0
  163. package/skills/ce-doc-review/references/dispatch.md +56 -0
  164. package/skills/ce-doc-review/references/document-intake.md +44 -0
  165. package/skills/ce-doc-review/references/findings-schema.json +85 -0
  166. package/skills/ce-doc-review/references/modes.md +20 -0
  167. package/skills/ce-doc-review/references/open-questions-defer.md +158 -0
  168. package/skills/ce-doc-review/references/persona-selection.md +41 -0
  169. package/skills/ce-doc-review/references/personas/adversarial-document-reviewer.md +109 -0
  170. package/skills/ce-doc-review/references/personas/coherence-reviewer.md +69 -0
  171. package/skills/ce-doc-review/references/personas/design-lens-reviewer.md +48 -0
  172. package/skills/ce-doc-review/references/personas/feasibility-reviewer.md +50 -0
  173. package/skills/ce-doc-review/references/personas/product-lens-reviewer.md +85 -0
  174. package/skills/ce-doc-review/references/personas/scope-guardian-reviewer.md +83 -0
  175. package/skills/ce-doc-review/references/personas/security-lens-reviewer.md +41 -0
  176. package/skills/ce-doc-review/references/personas/whole-doc-reviewer.md +25 -0
  177. package/skills/ce-doc-review/references/rendering-floor.md +175 -0
  178. package/skills/ce-doc-review/references/review-output-template.md +123 -0
  179. package/skills/ce-doc-review/references/subagent-template.md +188 -0
  180. package/skills/ce-doc-review/references/synthesis-and-presentation.md +350 -0
  181. package/skills/ce-doc-review/references/walkthrough.md +347 -0
  182. package/skills/ce-doc-review/scripts/cross-model-doc-review.sh +1270 -0
  183. package/skills/ce-doc-review/scripts/packs-resolve.py +737 -0
  184. package/skills/ce-doc-review/scripts/peer-job-runner.py +2594 -0
  185. package/skills/ce-dogfood/SKILL.md +66 -0
  186. package/skills/ce-dogfood/agents/openai.yaml +2 -0
  187. package/skills/ce-dogfood/references/dogfood-report-template.md +97 -0
  188. package/skills/ce-dogfood/references/phases.md +167 -0
  189. package/skills/ce-dogfood/references/test-matrix-taxonomy.md +62 -0
  190. package/skills/ce-dogfood/scripts/packs-resolve.py +737 -0
  191. package/skills/ce-explain/SKILL.md +81 -0
  192. package/skills/ce-explain/references/agents/behavior-trace-scout.md +19 -0
  193. package/skills/ce-explain/references/agents/work-recap-scout.md +23 -0
  194. package/skills/ce-explain/references/check-in.md +18 -0
  195. package/skills/ce-explain/references/destinations.md +35 -0
  196. package/skills/ce-explain/references/explainer-html.md +29 -0
  197. package/skills/ce-explain/references/explainer-markdown.md +24 -0
  198. package/skills/ce-explain/references/intake.md +50 -0
  199. package/skills/ce-explain/references/orchestration.md +46 -0
  200. package/skills/ce-handoff/SKILL.md +54 -0
  201. package/skills/ce-handoff/references/create.md +88 -0
  202. package/skills/ce-handoff/references/resume.md +46 -0
  203. package/skills/ce-ideate/SKILL.md +75 -0
  204. package/skills/ce-ideate/references/agents/issue-intelligence-analyst.md +166 -0
  205. package/skills/ce-ideate/references/agents/learnings-researcher.md +247 -0
  206. package/skills/ce-ideate/references/agents/slack-researcher.md +127 -0
  207. package/skills/ce-ideate/references/agents/web-researcher.md +121 -0
  208. package/skills/ce-ideate/references/decomposition.md +39 -0
  209. package/skills/ce-ideate/references/divergent-ideation.md +112 -0
  210. package/skills/ce-ideate/references/grounding.md +138 -0
  211. package/skills/ce-ideate/references/html-rendering.md +638 -0
  212. package/skills/ce-ideate/references/ideation-sections.md +191 -0
  213. package/skills/ce-ideate/references/issue-intelligence.md +44 -0
  214. package/skills/ce-ideate/references/markdown-rendering.md +242 -0
  215. package/skills/ce-ideate/references/output-mode.md +34 -0
  216. package/skills/ce-ideate/references/post-ideation-workflow.md +169 -0
  217. package/skills/ce-ideate/references/scope-gates.md +148 -0
  218. package/skills/ce-ideate/references/universal-ideation.md +119 -0
  219. package/skills/ce-ideate/references/user-research-artifacts.md +28 -0
  220. package/skills/ce-ideate/references/web-research-cache.md +56 -0
  221. package/skills/ce-noslop/SKILL.md +45 -0
  222. package/skills/ce-noslop/references/patterns.md +61 -0
  223. package/skills/ce-noslop/references/terminology.md +3 -0
  224. package/skills/ce-optimize/SKILL.md +64 -0
  225. package/skills/ce-optimize/references/agents/learnings-researcher.md +247 -0
  226. package/skills/ce-optimize/references/agents/repo-research-analyst.md +258 -0
  227. package/skills/ce-optimize/references/example-expensive-benchmark-spec.yaml +88 -0
  228. package/skills/ce-optimize/references/example-hard-spec.yaml +64 -0
  229. package/skills/ce-optimize/references/example-judge-spec.yaml +83 -0
  230. package/skills/ce-optimize/references/example-text-target-spec.yaml +80 -0
  231. package/skills/ce-optimize/references/experiment-log-schema.yaml +563 -0
  232. package/skills/ce-optimize/references/experiment-prompt-template.md +143 -0
  233. package/skills/ce-optimize/references/judge-prompt-template.md +117 -0
  234. package/skills/ce-optimize/references/loop.md +286 -0
  235. package/skills/ce-optimize/references/measurement.md +168 -0
  236. package/skills/ce-optimize/references/optimize-spec-schema.yaml +570 -0
  237. package/skills/ce-optimize/references/persistence.md +93 -0
  238. package/skills/ce-optimize/references/spec.md +107 -0
  239. package/skills/ce-optimize/references/text-targets.md +51 -0
  240. package/skills/ce-optimize/references/usage-guide.md +147 -0
  241. package/skills/ce-optimize/references/wrap-up.md +54 -0
  242. package/skills/ce-optimize/scripts/decide.mjs +676 -0
  243. package/skills/ce-optimize/scripts/experiment-worktree.sh +330 -0
  244. package/skills/ce-optimize/scripts/measure.sh +140 -0
  245. package/skills/ce-optimize/scripts/parallel-probe.sh +134 -0
  246. package/skills/ce-plan/SKILL.md +60 -0
  247. package/skills/ce-plan/references/agents/agent-native-planning-strategist.md +66 -0
  248. package/skills/ce-plan/references/agents/architecture-strategist.md +46 -0
  249. package/skills/ce-plan/references/agents/best-practices-researcher.md +118 -0
  250. package/skills/ce-plan/references/agents/data-integrity-guardian.md +68 -0
  251. package/skills/ce-plan/references/agents/framework-docs-researcher.md +97 -0
  252. package/skills/ce-plan/references/agents/git-history-analyzer.md +40 -0
  253. package/skills/ce-plan/references/agents/learnings-researcher.md +268 -0
  254. package/skills/ce-plan/references/agents/pattern-recognition-specialist.md +55 -0
  255. package/skills/ce-plan/references/agents/performance-oracle.md +108 -0
  256. package/skills/ce-plan/references/agents/repo-research-analyst.md +262 -0
  257. package/skills/ce-plan/references/agents/security-sentinel.md +91 -0
  258. package/skills/ce-plan/references/agents/slack-researcher.md +131 -0
  259. package/skills/ce-plan/references/agents/spec-flow-analyzer.md +84 -0
  260. package/skills/ce-plan/references/agents/web-researcher.md +125 -0
  261. package/skills/ce-plan/references/approach-altitude.md +55 -0
  262. package/skills/ce-plan/references/bakeoff.md +9 -0
  263. package/skills/ce-plan/references/deepening-workflow.md +263 -0
  264. package/skills/ce-plan/references/final-review.md +134 -0
  265. package/skills/ce-plan/references/html-rendering.md +638 -0
  266. package/skills/ce-plan/references/intake.md +158 -0
  267. package/skills/ce-plan/references/markdown-rendering.md +242 -0
  268. package/skills/ce-plan/references/output-contracts.md +29 -0
  269. package/skills/ce-plan/references/output-mode.md +45 -0
  270. package/skills/ce-plan/references/plan-handoff.md +116 -0
  271. package/skills/ce-plan/references/plan-sections.md +489 -0
  272. package/skills/ce-plan/references/reasoning-elevation.md +124 -0
  273. package/skills/ce-plan/references/research.md +211 -0
  274. package/skills/ce-plan/references/resume.md +61 -0
  275. package/skills/ce-plan/references/settled-decisions.md +45 -0
  276. package/skills/ce-plan/references/structure.md +207 -0
  277. package/skills/ce-plan/references/synthesis-summary.md +260 -0
  278. package/skills/ce-plan/references/universal-planning.md +170 -0
  279. package/skills/ce-plan/scripts/elevation-dispatch.sh +288 -0
  280. package/skills/ce-plan/scripts/packs-resolve.py +737 -0
  281. package/skills/ce-plan/scripts/peer-job-runner.py +2594 -0
  282. package/skills/ce-polish/SKILL.md +22 -0
  283. package/skills/ce-polish/agents/openai.yaml +2 -0
  284. package/skills/ce-polish/references/dev-server-astro.md +58 -0
  285. package/skills/ce-polish/references/dev-server-detection.md +40 -0
  286. package/skills/ce-polish/references/dev-server-next.md +61 -0
  287. package/skills/ce-polish/references/dev-server-nuxt.md +58 -0
  288. package/skills/ce-polish/references/dev-server-procfile.md +59 -0
  289. package/skills/ce-polish/references/dev-server-rails.md +50 -0
  290. package/skills/ce-polish/references/dev-server-remix.md +58 -0
  291. package/skills/ce-polish/references/dev-server-sveltekit.md +58 -0
  292. package/skills/ce-polish/references/dev-server-vite.md +48 -0
  293. package/skills/ce-polish/references/install-riffrec.md +35 -0
  294. package/skills/ce-polish/references/launch-json-schema.md +177 -0
  295. package/skills/ce-polish/references/live-loop.md +98 -0
  296. package/skills/ce-polish/references/live-remote.md +35 -0
  297. package/skills/ce-polish/references/live-start.md +93 -0
  298. package/skills/ce-polish/references/live-stream-contract.md +253 -0
  299. package/skills/ce-polish/references/run.md +67 -0
  300. package/skills/ce-polish/scripts/detect-project-type.sh +277 -0
  301. package/skills/ce-polish/scripts/detect-riffrec.sh +225 -0
  302. package/skills/ce-polish/scripts/live-endpoint.js +2841 -0
  303. package/skills/ce-polish/scripts/read-launch-json.sh +117 -0
  304. package/skills/ce-polish/scripts/resolve-package-manager.sh +95 -0
  305. package/skills/ce-polish/scripts/resolve-port.sh +307 -0
  306. package/skills/ce-pov/SKILL.md +64 -0
  307. package/skills/ce-pov/references/agents/external-evidence-researcher.md +26 -0
  308. package/skills/ce-pov/references/agents/pov-peer.md +43 -0
  309. package/skills/ce-pov/references/agents/precedent-activity-scout.md +21 -0
  310. package/skills/ce-pov/references/agents/project-grounding-scout.md +32 -0
  311. package/skills/ce-pov/references/boundaries.md +36 -0
  312. package/skills/ce-pov/references/cross-model-panel.md +596 -0
  313. package/skills/ce-pov/references/followup.md +9 -0
  314. package/skills/ce-pov/references/grounding.md +47 -0
  315. package/skills/ce-pov/references/intake.md +61 -0
  316. package/skills/ce-pov/references/invocation.md +47 -0
  317. package/skills/ce-pov/references/method.md +68 -0
  318. package/skills/ce-pov/references/pov-schema.json +56 -0
  319. package/skills/ce-pov/references/report.md +7 -0
  320. package/skills/ce-pov/scripts/cross-model-pov.sh +1110 -0
  321. package/skills/ce-pov/scripts/peer-job-runner.py +2594 -0
  322. package/skills/ce-product-pulse/SKILL.md +81 -0
  323. package/skills/ce-product-pulse/agents/openai.yaml +2 -0
  324. package/skills/ce-product-pulse/references/config.md +20 -0
  325. package/skills/ce-product-pulse/references/interview.md +260 -0
  326. package/skills/ce-product-pulse/references/report-template.md +89 -0
  327. package/skills/ce-product-pulse/references/run.md +46 -0
  328. package/skills/ce-product-pulse/references/setup.md +43 -0
  329. package/skills/ce-promote/SKILL.md +90 -0
  330. package/skills/ce-promote/agents/openai.yaml +2 -0
  331. package/skills/ce-promote/references/spiral-cli.md +149 -0
  332. package/skills/ce-proof/SKILL.md +65 -0
  333. package/skills/ce-proof/references/api.md +176 -0
  334. package/skills/ce-proof/references/workflows.md +124 -0
  335. package/skills/ce-prototype/SKILL.md +59 -0
  336. package/skills/ce-prototype/assets/annotate.css +206 -0
  337. package/skills/ce-prototype/assets/annotate.js +837 -0
  338. package/skills/ce-prototype/references/annotation-loop.md +32 -0
  339. package/skills/ce-prototype/references/build.md +39 -0
  340. package/skills/ce-prototype/references/craft-floor.md +43 -0
  341. package/skills/ce-prototype/references/preview.md +118 -0
  342. package/skills/ce-prototype/references/scoping.md +38 -0
  343. package/skills/ce-prototype/references/write-back.md +37 -0
  344. package/skills/ce-prototype/scripts/light-webserver.js +1275 -0
  345. package/skills/ce-resolve-pr-feedback/SKILL.md +60 -0
  346. package/skills/ce-resolve-pr-feedback/references/agents/pr-comment-resolver.md +59 -0
  347. package/skills/ce-resolve-pr-feedback/references/evaluation-rubric.md +141 -0
  348. package/skills/ce-resolve-pr-feedback/references/full-mode.md +336 -0
  349. package/skills/ce-resolve-pr-feedback/references/pipeline-mode.md +23 -0
  350. package/skills/ce-resolve-pr-feedback/references/resume.md +60 -0
  351. package/skills/ce-resolve-pr-feedback/references/return-to-caller.md +111 -0
  352. package/skills/ce-resolve-pr-feedback/references/targeted-mode.md +48 -0
  353. package/skills/ce-resolve-pr-feedback/scripts/get-pr-comments +176 -0
  354. package/skills/ce-resolve-pr-feedback/scripts/get-thread-for-comment +84 -0
  355. package/skills/ce-resolve-pr-feedback/scripts/pending-feedback.py +287 -0
  356. package/skills/ce-resolve-pr-feedback/scripts/reply-to-pr-thread +57 -0
  357. package/skills/ce-resolve-pr-feedback/scripts/resolve-pr-thread +23 -0
  358. package/skills/ce-retune/SKILL.md +42 -0
  359. package/skills/ce-retune/agents/openai.yaml +2 -0
  360. package/skills/ce-retune/references/baseline-mining.md +122 -0
  361. package/skills/ce-retune/references/corpus-audit.md +128 -0
  362. package/skills/ce-retune/references/cut-passes.md +129 -0
  363. package/skills/ce-retune/references/halt-taxonomy.md +123 -0
  364. package/skills/ce-retune/references/noise-floor.md +131 -0
  365. package/skills/ce-retune/references/workflow-shapes.md +73 -0
  366. package/skills/ce-riffrec-feedback-analysis/SKILL.md +33 -0
  367. package/skills/ce-riffrec-feedback-analysis/references/analyzer.md +18 -0
  368. package/skills/ce-riffrec-feedback-analysis/references/compound-engineering-feedback-format.md +116 -0
  369. package/skills/ce-riffrec-feedback-analysis/references/extensive-analysis.md +98 -0
  370. package/skills/ce-riffrec-feedback-analysis/references/install-riffrec.md +27 -0
  371. package/skills/ce-riffrec-feedback-analysis/references/quick-bug-report.md +38 -0
  372. package/skills/ce-riffrec-feedback-analysis/scripts/analyze_riffrec_zip.py +1306 -0
  373. package/skills/ce-setup/SKILL.md +103 -0
  374. package/skills/ce-setup/agents/openai.yaml +2 -0
  375. package/skills/ce-setup/assets/compounding-directive.md +11 -0
  376. package/skills/ce-setup/assets/noslop-directive.md +7 -0
  377. package/skills/ce-setup/assets/pack-rule-template.md +11 -0
  378. package/skills/ce-setup/references/acpx-cache-warm.md +14 -0
  379. package/skills/ce-setup/references/config-template.yaml +210 -0
  380. package/skills/ce-setup/references/legacy-codex-tool-map.md +15 -0
  381. package/skills/ce-setup/references/pack-scaffold.md +45 -0
  382. package/skills/ce-setup/references/repo-fixes.md +121 -0
  383. package/skills/ce-setup/scripts/check-health +929 -0
  384. package/skills/ce-setup/scripts/packs-resolve.py +737 -0
  385. package/skills/ce-simplify-code/SKILL.md +76 -0
  386. package/skills/ce-simplify-code/references/personas/code-quality-reviewer.md +17 -0
  387. package/skills/ce-simplify-code/references/personas/code-reuse-reviewer.md +7 -0
  388. package/skills/ce-simplify-code/references/personas/comment-reviewer.md +24 -0
  389. package/skills/ce-simplify-code/references/personas/efficiency-reviewer.md +11 -0
  390. package/skills/ce-simplify-code/references/personas/test-value-reviewer.md +23 -0
  391. package/skills/ce-simplify-code/references/reviewer-dispatch.md +11 -0
  392. package/skills/ce-simplify-code/references/standalone-report.md +25 -0
  393. package/skills/ce-strategy/SKILL.md +61 -0
  394. package/skills/ce-strategy/references/grounding.md +22 -0
  395. package/skills/ce-strategy/references/interview.md +179 -0
  396. package/skills/ce-strategy/references/strategy-template.md +94 -0
  397. package/skills/ce-strategy/references/update-run.md +33 -0
  398. package/skills/ce-sweep/SKILL.md +93 -0
  399. package/skills/ce-sweep/agents/openai.yaml +2 -0
  400. package/skills/ce-sweep/references/agents/media-analyzer.md +54 -0
  401. package/skills/ce-sweep/references/interview.md +208 -0
  402. package/skills/ce-sweep/references/model-tiers.md +13 -0
  403. package/skills/ce-sweep/references/plan-template.md +62 -0
  404. package/skills/ce-sweep/references/run.md +126 -0
  405. package/skills/ce-sweep/references/sources/email.md +57 -0
  406. package/skills/ce-sweep/references/sources/github-issues.md +59 -0
  407. package/skills/ce-sweep/references/sources/slack.md +61 -0
  408. package/skills/ce-sweep/references/state-schema.md +157 -0
  409. package/skills/ce-sweep/references/subagent-template.md +60 -0
  410. package/skills/ce-sweep/scripts/analyze_riffrec_zip.py +1306 -0
  411. package/skills/ce-sweep/scripts/sweep-state.py +793 -0
  412. package/skills/ce-test-browser/SKILL.md +50 -0
  413. package/skills/ce-test-browser/references/agent-browser-driver.md +47 -0
  414. package/skills/ce-test-browser/references/pipeline-orchestration.md +45 -0
  415. package/skills/ce-test-browser/references/route-and-report.md +129 -0
  416. package/skills/ce-test-browser/scripts/resolve-port.sh +41 -0
  417. package/skills/ce-test-xcode/SKILL.md +26 -0
  418. package/skills/ce-test-xcode/agents/openai.yaml +2 -0
  419. package/skills/ce-test-xcode/references/setup-and-build.md +32 -0
  420. package/skills/ce-test-xcode/references/test-and-report.md +64 -0
  421. package/skills/ce-work/SKILL.md +62 -0
  422. package/skills/ce-work/references/agents/figma-design-sync.md +27 -0
  423. package/skills/ce-work/references/agents/implementation-worker.md +17 -0
  424. package/skills/ce-work/references/cross-model-execution.md +191 -0
  425. package/skills/ce-work/references/execution-engines.md +64 -0
  426. package/skills/ce-work/references/execution-strategy.md +73 -0
  427. package/skills/ce-work/references/implementation-loop.md +107 -0
  428. package/skills/ce-work/references/implementation-result-schema.json +52 -0
  429. package/skills/ce-work/references/input-triage.md +50 -0
  430. package/skills/ce-work/references/non-code-execution.md +23 -0
  431. package/skills/ce-work/references/return-to-caller.md +33 -0
  432. package/skills/ce-work/references/review-findings-followup.md +82 -0
  433. package/skills/ce-work/references/shipping-workflow.md +89 -0
  434. package/skills/ce-work/references/tracker-defer.md +150 -0
  435. package/skills/ce-work/references/work-intake.md +51 -0
  436. package/skills/ce-work/references/workspace-setup.md +27 -0
  437. package/skills/ce-work/scripts/__pycache__/unit_workspace_jobs.cpython-314.pyc +0 -0
  438. package/skills/ce-work/scripts/__pycache__/unit_workspace_state.cpython-314.pyc +0 -0
  439. package/skills/ce-work/scripts/cross-model-work.sh +1385 -0
  440. package/skills/ce-work/scripts/peer-job-runner.py +2594 -0
  441. package/skills/ce-work/scripts/unit-workspace.py +215 -0
  442. package/skills/ce-work/scripts/unit_workspace_ignored.py +93 -0
  443. package/skills/ce-work/scripts/unit_workspace_integration.py +677 -0
  444. package/skills/ce-work/scripts/unit_workspace_jobs.py +1216 -0
  445. package/skills/ce-work/scripts/unit_workspace_lifecycle.py +981 -0
  446. package/skills/ce-work/scripts/unit_workspace_state.py +1379 -0
  447. package/skills/ce-work/scripts/unit_workspace_transaction.py +612 -0
  448. package/skills/ce-worktree/SKILL.md +54 -0
  449. package/skills/lfg/SKILL.md +59 -0
  450. package/skills/lfg/references/debug-return.md +25 -0
  451. package/skills/lfg/references/intake.md +30 -0
  452. package/skills/lfg/references/next-work-handoff.md +96 -0
  453. package/skills/lfg/references/plan-brief.md +36 -0
  454. package/skills/lfg/references/review-followup.md +78 -0
  455. package/skills/lfg/references/shipping.md +65 -0
  456. package/skills/lfg/references/stage-routing.md +49 -0
  457. package/skills/lfg/references/task-visibility.md +11 -0
  458. package/skills/lfg/references/tracker-defer.md +150 -0
  459. package/skills/lfg/references/work-return.md +27 -0
  460. package/skills/wtf/SKILL.md +33 -0
  461. package/skills/wtf/agents/openai.yaml +2 -0
@@ -0,0 +1,793 @@
1
+ #!/usr/bin/env python3
2
+ """Deterministic state engine for the feedback sweep (ce-sweep).
3
+
4
+ This helper owns ALL reads and writes of the sweep state file. Peer agents
5
+ (source connectors, the analyzer, the orchestrator) never edit the file by
6
+ hand — they go through these subcommands so the schema contract, the id-keyed
7
+ merge, the single-writer lease, and the closed-item evidence rule are enforced
8
+ in exactly one place. See `references/state-schema.md` for the cross-agent
9
+ contract this script implements.
10
+
11
+ Design rules (shared with the repo's other state helpers):
12
+ - Pure Python 3 stdlib. No third-party dependencies.
13
+ - Every OPERATIONAL failure path prints a parseable STATUS WORD on line 1 and
14
+ exits 0 — it never raises a traceback to the caller. Only genuine CLI
15
+ misuse (bad/missing subcommand args) exits non-zero via argparse.
16
+ - Writes are atomic: a temp file in the state dir + os.replace (atomic on
17
+ POSIX), so a concurrent reader never sees a torn file.
18
+ - The script never calls the wall clock for the values it stores EXCEPT the
19
+ lease timestamp (staleness needs "now"). Tests pin it with --now / stamp
20
+ values with --timestamp so behavior is reproducible.
21
+
22
+ STATUS WORDS (line 1 of stdout for every subcommand):
23
+ OK success (an optional JSON payload follows on line 2)
24
+ NO-STATE read: the state file does not exist yet
25
+ CORRUPT the state file exists but does not parse as our schema
26
+ LOCKED lease-acquire: a live lease is held by another writer
27
+ STALE-RECLAIMED lease-acquire: an expired lease was taken over (JSON payload)
28
+ LEASE-LOST a mutating call was made by a writer that does not own the
29
+ lease (or a release of another writer's lease); no write
30
+ REFUSED cursor-advance: unknown past-item, or non-monotonic cursor
31
+ ERROR an unexpected internal error (defensive; never a traceback)
32
+
33
+ The state file is genuine YAML restricted to a small, deliberate subset so a
34
+ hand-written stdlib serializer/parser round-trips it deterministically. See
35
+ `references/state-schema.md` (section "YAML subset") for the exact grammar:
36
+ non-empty dict values become block mappings (any depth); scalars are emitted as
37
+ JSON tokens (strings always double-quoted on one line); lists and empty dicts
38
+ are emitted as inline JSON flow on a single line — itself valid YAML.
39
+ """
40
+ import argparse
41
+ import json
42
+ import os
43
+ import sys
44
+ import tempfile
45
+ from datetime import datetime, timezone
46
+
47
+ try:
48
+ import fcntl # POSIX advisory locks (macOS, Linux — this repo's Unix targets)
49
+ _HAS_FCNTL = True
50
+ except ImportError: # non-POSIX; degrade to unlocked (single-writer by convention)
51
+ _HAS_FCNTL = False
52
+
53
+ SCHEMA_VERSION = 1
54
+
55
+ # The closed lifecycle status enum is documented in references/state-schema.md.
56
+ # Unknown statuses are preserved on write-back, never dropped — nothing here
57
+ # whitelists values.
58
+
59
+ # A `closed` item MUST carry all three evidence fields; `validate` downgrades
60
+ # any closed item missing any of them back to `fix_pending`.
61
+ EVIDENCE_FIELDS = ("fix_ref", "verified_merge_sha", "verified_at")
62
+
63
+ DEFAULT_TTL_MINUTES = 60
64
+
65
+
66
+ # --------------------------------------------------------------------------- #
67
+ # Minimal YAML subset: serializer + parser
68
+ # --------------------------------------------------------------------------- #
69
+ # Grammar (2-space indent, no tabs):
70
+ # - Block mappings: `<key>: <value>` or `<key>:` (nested mapping follows).
71
+ # - Keys: a bare token matching _SAFE_KEY, else a JSON double-quoted string.
72
+ # - Scalars: null / true / false / integers / floats emitted bare; strings
73
+ # always emitted as JSON double-quoted (single line, fully escaped).
74
+ # - Nested containers (dict/list) that are NOT part of the known block
75
+ # structure are emitted as inline JSON flow on one line — valid YAML.
76
+ # The emitter only produces block MAPPINGS (never block sequences); the parser
77
+ # tolerates arbitrary-depth block mappings plus scalar / inline-JSON values.
78
+
79
+ import re
80
+
81
+ _SAFE_KEY = re.compile(r"^[A-Za-z_][A-Za-z0-9_.-]*$")
82
+
83
+ # Preferred key orders for deterministic, human-legible output. Any keys not
84
+ # listed are appended in sorted order so unknown/preserved fields stay stable.
85
+ _DOC_ORDER = ("schema_version", "lease", "sources", "items", "last_run")
86
+ _LEASE_ORDER = ("writer", "timestamp", "ttl_minutes")
87
+ _ITEM_ORDER = (
88
+ "source", "status", "sensitive", "title", "url", "body", "quote",
89
+ "fix_ref", "verified_merge_sha", "verified_at",
90
+ )
91
+ _LAST_RUN_ORDER = ("timestamp", "outcome", "writer", "counts")
92
+
93
+
94
+ def _ordered_keys(d, preferred):
95
+ seen = [k for k in preferred if k in d]
96
+ rest = sorted(k for k in d if k not in preferred)
97
+ return seen + rest
98
+
99
+
100
+ def _emit_key(key):
101
+ key = str(key)
102
+ if _SAFE_KEY.match(key):
103
+ return key
104
+ return json.dumps(key, ensure_ascii=False)
105
+
106
+
107
+ def _emit_scalar(v):
108
+ if v is None:
109
+ return "null"
110
+ if v is True:
111
+ return "true"
112
+ if v is False:
113
+ return "false"
114
+ if isinstance(v, int):
115
+ return str(v)
116
+ if isinstance(v, float):
117
+ return repr(v)
118
+ if isinstance(v, str):
119
+ return json.dumps(v, ensure_ascii=False)
120
+ # dict / list -> inline JSON flow (valid YAML), sorted for determinism.
121
+ return json.dumps(v, ensure_ascii=False, sort_keys=True)
122
+
123
+
124
+ def _emit_mapping(d, indent, preferred):
125
+ """Emit a dict as block YAML. Only nests block-style for child dicts;
126
+ lists and any other container leaf are emitted inline via _emit_scalar."""
127
+ pad = " " * indent
128
+ lines = []
129
+ for key in _ordered_keys(d, preferred):
130
+ val = d[key]
131
+ ks = _emit_key(key)
132
+ if isinstance(val, dict) and val:
133
+ lines.append(f"{pad}{ks}:")
134
+ # Nested item/source/lease/last_run maps: no special preferred order
135
+ # for arbitrary nested dicts; only the top structural maps below get
136
+ # a preferred order (handled by their callers).
137
+ child_pref = _child_preferred(indent, key)
138
+ lines.extend(_emit_mapping(val, indent + 1, child_pref))
139
+ else:
140
+ lines.append(f"{pad}{ks}: {_emit_scalar(val)}")
141
+ return lines
142
+
143
+
144
+ def _child_preferred(parent_indent, key):
145
+ """Preferred child-key order for the known structural containers."""
146
+ if parent_indent == 0:
147
+ if key == "lease":
148
+ return _LEASE_ORDER
149
+ if key == "last_run":
150
+ return _LAST_RUN_ORDER
151
+ return () # sources / items: children are id-keyed, no field order
152
+ # A source entry or item entry: apply the item field order (harmless for
153
+ # source entries, which only carry `cursor`/`sensitive`).
154
+ return _ITEM_ORDER
155
+
156
+
157
+ def emit_document(state):
158
+ lines = _emit_mapping(state, 0, _DOC_ORDER)
159
+ return "\n".join(lines) + "\n"
160
+
161
+
162
+ def _split_key(content):
163
+ """Split a mapping line into (key, rest-after-colon). Raises ValueError on
164
+ a line with no colon so a malformed file surfaces as CORRUPT."""
165
+ if content.startswith('"'):
166
+ key, end = json.JSONDecoder().raw_decode(content)
167
+ rest = content[end:]
168
+ if not rest.startswith(":"):
169
+ raise ValueError("expected ':' after quoted key")
170
+ return key, rest[1:]
171
+ idx = content.find(":")
172
+ if idx == -1:
173
+ raise ValueError("expected ':' in mapping line")
174
+ return content[:idx], content[idx + 1:]
175
+
176
+
177
+ _BLOCK = object() # sentinel: value is a nested block, parse deeper lines
178
+
179
+
180
+ def _parse_value(rest):
181
+ rest = rest.strip()
182
+ if rest == "":
183
+ return _BLOCK
184
+ if rest == "null":
185
+ return None
186
+ if rest == "true":
187
+ return True
188
+ if rest == "false":
189
+ return False
190
+ first = rest[0]
191
+ if first in '"{[':
192
+ return json.loads(rest) # JSON string, object, or array (flow style)
193
+ if first == "-" or first.isdigit():
194
+ try:
195
+ if "." in rest or "e" in rest or "E" in rest:
196
+ return float(rest)
197
+ return int(rest)
198
+ except ValueError:
199
+ pass
200
+ # Emitter never produces bare unquoted strings; a bare token here is a
201
+ # hand-edit — keep it verbatim rather than failing.
202
+ return rest
203
+
204
+
205
+ def _parse_mapping(rows, cursor, indent):
206
+ result = {}
207
+ while cursor["i"] < len(rows):
208
+ cur_indent, content = rows[cursor["i"]]
209
+ if cur_indent < indent:
210
+ break
211
+ if cur_indent > indent:
212
+ # Orphan deeper line with no parent key; skip defensively.
213
+ cursor["i"] += 1
214
+ continue
215
+ cursor["i"] += 1
216
+ key, rest = _split_key(content)
217
+ val = _parse_value(rest)
218
+ if val is _BLOCK:
219
+ if cursor["i"] < len(rows) and rows[cursor["i"]][0] > indent:
220
+ val = _parse_mapping(rows, cursor, rows[cursor["i"]][0])
221
+ else:
222
+ val = {}
223
+ result[key] = val
224
+ return result
225
+
226
+
227
+ def parse_document(text):
228
+ rows = []
229
+ for raw in text.split("\n"):
230
+ stripped = raw.strip()
231
+ if not stripped or stripped.startswith("#"):
232
+ continue
233
+ indent = len(raw) - len(raw.lstrip(" "))
234
+ rows.append((indent, stripped))
235
+ if not rows:
236
+ return {}
237
+ return _parse_mapping(rows, {"i": 0}, rows[0][0])
238
+
239
+
240
+ # --------------------------------------------------------------------------- #
241
+ # State load / save
242
+ # --------------------------------------------------------------------------- #
243
+
244
+ def _item_key(source, item_id):
245
+ """Storage key for an item. The items keyspace is flat, so a bare id can
246
+ collide across sources (two Slack channels emitting the same ts, or a
247
+ source that reuses numeric ids). Namespacing by source keeps each source's
248
+ id space independent, matching the composite keys documented in
249
+ references/state-schema.md."""
250
+ return "{}:{}".format(source, item_id)
251
+
252
+
253
+ def load_state(path):
254
+ """Return (status, data): ('absent', None), ('corrupt', None), or
255
+ ('ok', dict). A file that parses but lacks schema_version is corrupt."""
256
+ try:
257
+ with open(path, encoding="utf-8") as f:
258
+ # A machine-local state file can live under world-shared /tmp, and
259
+ # it is a correctness dependency (lease, cursors, closed status) as
260
+ # well as an injection sink (item bodies re-read into agent
261
+ # context). Reject a file not owned by us so a co-tenant cannot
262
+ # plant a forged lease/cursor or attacker-authored item text. Skip
263
+ # where geteuid is unavailable (non-POSIX), where the threat does
264
+ # not apply.
265
+ geteuid = getattr(os, "geteuid", None)
266
+ if geteuid is not None and os.fstat(f.fileno()).st_uid != geteuid():
267
+ return ("corrupt", None)
268
+ text = f.read()
269
+ except FileNotFoundError:
270
+ return ("absent", None)
271
+ except (OSError, UnicodeDecodeError):
272
+ return ("corrupt", None)
273
+ if not text.strip():
274
+ return ("absent", None)
275
+ try:
276
+ data = parse_document(text)
277
+ except Exception:
278
+ return ("corrupt", None)
279
+ if not isinstance(data, dict) or "schema_version" not in data:
280
+ return ("corrupt", None)
281
+ data.setdefault("sources", {})
282
+ data.setdefault("items", {})
283
+ return ("ok", data)
284
+
285
+
286
+ def new_state():
287
+ return {"schema_version": SCHEMA_VERSION, "sources": {}, "items": {}}
288
+
289
+
290
+ def write_state(path, state):
291
+ """Atomic write of the state file. Returns True on success."""
292
+ state["schema_version"] = SCHEMA_VERSION
293
+ text = emit_document(state)
294
+ d = os.path.dirname(os.path.abspath(path))
295
+ os.makedirs(d, exist_ok=True)
296
+ fd, tmp = tempfile.mkstemp(dir=d, prefix=".tmp-sweep-", suffix=".yml")
297
+ try:
298
+ with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as f:
299
+ f.write(text)
300
+ os.replace(tmp, path)
301
+ except BaseException:
302
+ try:
303
+ os.unlink(tmp)
304
+ except OSError:
305
+ pass
306
+ raise
307
+ return True
308
+
309
+
310
+ # --------------------------------------------------------------------------- #
311
+ # Output + time helpers
312
+ # --------------------------------------------------------------------------- #
313
+
314
+ def emit(status_word, payload=None):
315
+ print(status_word)
316
+ if payload is not None:
317
+ print(json.dumps(payload))
318
+ return 0
319
+
320
+
321
+ def resolve_now(args):
322
+ """The 'current time' for lease staleness + lease re-stamping. Pinned by
323
+ --now in tests; otherwise the real UTC clock."""
324
+ now = getattr(args, "now", None)
325
+ if now:
326
+ return now
327
+ return datetime.now(timezone.utc).isoformat()
328
+
329
+
330
+ def _parse_iso(s):
331
+ if not isinstance(s, str) or not s:
332
+ return None
333
+ try:
334
+ dt = datetime.fromisoformat(s.replace("Z", "+00:00"))
335
+ except ValueError:
336
+ return None
337
+ if dt.tzinfo is None:
338
+ dt = dt.replace(tzinfo=timezone.utc)
339
+ return dt
340
+
341
+
342
+ def current_lease(state):
343
+ """The active lease dict (with a non-empty writer), or None."""
344
+ lease = state.get("lease")
345
+ if isinstance(lease, dict) and lease.get("writer"):
346
+ return lease
347
+ return None
348
+
349
+
350
+ def lease_is_stale(lease, now_iso):
351
+ """True only when we can PROVE the lease is older than its TTL. If either
352
+ timestamp is unparseable we cannot prove staleness -> treat as live (do not
353
+ reclaim). Conservative: never stomp a lease we cannot show is expired."""
354
+ ts = _parse_iso(lease.get("timestamp", ""))
355
+ now = _parse_iso(now_iso)
356
+ if ts is None or now is None:
357
+ return False
358
+ try:
359
+ ttl = int(lease.get("ttl_minutes", DEFAULT_TTL_MINUTES))
360
+ except (TypeError, ValueError):
361
+ ttl = DEFAULT_TTL_MINUTES
362
+ return (now - ts).total_seconds() > ttl * 60
363
+
364
+
365
+ def restamp_lease(state, writer, now_iso):
366
+ """Refresh the owning writer's lease timestamp so a long sweep keeps it
367
+ alive across many writes. Only called after ownership is confirmed."""
368
+ lease = state.get("lease")
369
+ if isinstance(lease, dict) and lease.get("writer") == writer:
370
+ lease["timestamp"] = now_iso
371
+
372
+
373
+ def owns_lease(state, writer):
374
+ lease = current_lease(state)
375
+ return lease is not None and lease.get("writer") == writer
376
+
377
+
378
+ # --------------------------------------------------------------------------- #
379
+ # Subcommands
380
+ # --------------------------------------------------------------------------- #
381
+
382
+ def cmd_read(args):
383
+ st, data = load_state(args.state)
384
+ if st == "absent":
385
+ return emit("NO-STATE")
386
+ if st == "corrupt":
387
+ return emit("CORRUPT")
388
+ return emit("OK", data)
389
+
390
+
391
+ def cmd_validate(args):
392
+ st, data = load_state(args.state)
393
+ if st == "corrupt":
394
+ return emit("CORRUPT")
395
+ if st == "absent":
396
+ return emit("OK", {"downgraded": []})
397
+ downgraded = []
398
+ for item_id, item in data.get("items", {}).items():
399
+ if not isinstance(item, dict):
400
+ continue
401
+ if item.get("status") == "closed" and any(
402
+ not item.get(f) for f in EVIDENCE_FIELDS
403
+ ):
404
+ item["status"] = "fix_pending"
405
+ downgraded.append(item_id)
406
+ if downgraded:
407
+ write_state(args.state, data)
408
+ return emit("OK", {"downgraded": sorted(downgraded)})
409
+
410
+
411
+ def _load_owned_state(args):
412
+ """Load state for a lease-gated mutation. Returns (data, None) when
413
+ args.writer holds the lease, else (None, status_word) for the caller to
414
+ emit. Absent state means no lease to own -> the caller is not the owner."""
415
+ st, data = load_state(args.state)
416
+ if st == "corrupt":
417
+ return None, "CORRUPT"
418
+ if st == "absent" or not owns_lease(data, args.writer):
419
+ return None, "LEASE-LOST"
420
+ return data, None
421
+
422
+
423
+ def _commit_owned(args, data):
424
+ """Shared tail for lease-gated mutations: re-stamp the lease, persist."""
425
+ restamp_lease(data, args.writer, resolve_now(args))
426
+ write_state(args.state, data)
427
+ return emit("OK")
428
+
429
+
430
+ def cmd_upsert_item(args):
431
+ data, err = _load_owned_state(args)
432
+ if err:
433
+ return emit(err)
434
+ try:
435
+ incoming = json.loads(args.json)
436
+ except (ValueError, TypeError):
437
+ return emit("ERROR")
438
+ if not isinstance(incoming, dict):
439
+ return emit("ERROR")
440
+
441
+ items = data.setdefault("items", {})
442
+ key = _item_key(args.source, args.id)
443
+ existing = items.get(key)
444
+ merged = dict(existing) if isinstance(existing, dict) else {}
445
+ # id-keyed merge: only the keys present in the incoming item are replaced;
446
+ # unknown fields already on the item survive untouched.
447
+ merged.update(incoming)
448
+ merged["source"] = args.source
449
+ merged["id"] = args.id
450
+
451
+ source_entry = data.get("sources", {}).get(args.source, {})
452
+ is_sensitive = (
453
+ merged.get("sensitive") is True
454
+ or (isinstance(source_entry, dict) and source_entry.get("sensitive") is True)
455
+ )
456
+ if is_sensitive:
457
+ for f in ("body", "quote"):
458
+ merged.pop(f, None)
459
+
460
+ items[key] = merged
461
+ return _commit_owned(args, data)
462
+
463
+
464
+ def cmd_cursor_get(args):
465
+ st, data = load_state(args.state)
466
+ if st == "corrupt":
467
+ return emit("CORRUPT")
468
+ if st == "absent":
469
+ return emit("OK", {"cursor": None})
470
+ entry = data.get("sources", {}).get(args.source, {})
471
+ cursor = entry.get("cursor") if isinstance(entry, dict) else None
472
+ return emit("OK", {"cursor": cursor})
473
+
474
+
475
+ def cmd_cursor_advance(args):
476
+ data, err = _load_owned_state(args)
477
+ if err:
478
+ return emit(err)
479
+ # The cursor may only advance past an item that actually exists in state,
480
+ # so a resume never skips unrecorded items.
481
+ if _item_key(args.source, args.past_item) not in data.get("items", {}):
482
+ return emit("REFUSED")
483
+ entry = data.setdefault("sources", {}).setdefault(args.source, {})
484
+ current = entry.get("cursor")
485
+ # Monotonic guard. A new cursor sorting strictly before the current one is
486
+ # refused; equal is allowed (idempotent re-advance).
487
+ if current is not None and _cursor_lt(str(args.to), str(current)):
488
+ return emit("REFUSED")
489
+ entry["cursor"] = args.to
490
+ return _commit_owned(args, data)
491
+
492
+
493
+ def _cursor_lt(a, b):
494
+ """True when cursor `a` precedes `b`. Pure-digit cursors compare
495
+ numerically so an unpadded id like '9' correctly precedes '10'; everything
496
+ else (Slack fixed-width ts, ISO timestamps) already sorts correctly as a
497
+ string, so fall back to lexical order."""
498
+ if a.isdigit() and b.isdigit():
499
+ return int(a) < int(b)
500
+ return a < b
501
+
502
+
503
+ def cmd_lease_acquire(args):
504
+ st, data = load_state(args.state)
505
+ if st == "corrupt":
506
+ return emit("CORRUPT")
507
+ if st == "absent":
508
+ data = new_state()
509
+ now = resolve_now(args)
510
+ ttl = args.ttl_minutes if args.ttl_minutes is not None else DEFAULT_TTL_MINUTES
511
+ lease = current_lease(data)
512
+ if lease is None or lease.get("writer") == args.writer:
513
+ # Free, or re-entrant acquire by the same writer: (re)stamp and take it.
514
+ data["lease"] = {"writer": args.writer, "timestamp": now, "ttl_minutes": ttl}
515
+ write_state(args.state, data)
516
+ return emit("OK")
517
+ if lease_is_stale(lease, now):
518
+ prev = {
519
+ "previous_writer": lease.get("writer"),
520
+ "previous_timestamp": lease.get("timestamp"),
521
+ }
522
+ data["lease"] = {"writer": args.writer, "timestamp": now, "ttl_minutes": ttl}
523
+ write_state(args.state, data)
524
+ return emit("STALE-RECLAIMED", prev)
525
+ return emit("LOCKED")
526
+
527
+
528
+ def cmd_lease_release(args):
529
+ st, data = load_state(args.state)
530
+ if st == "corrupt":
531
+ return emit("CORRUPT")
532
+ if st == "absent":
533
+ return emit("OK") # nothing to release
534
+ lease = current_lease(data)
535
+ if lease is None:
536
+ return emit("OK")
537
+ if lease.get("writer") != args.writer:
538
+ return emit("LEASE-LOST") # never release another writer's lease
539
+ data.pop("lease", None)
540
+ write_state(args.state, data)
541
+ return emit("OK")
542
+
543
+
544
+ def cmd_run_record(args):
545
+ # Intentionally lease-agnostic: an `aborted-locked` run could not acquire
546
+ # the lease yet must still record its outcome. In local-commit mode there
547
+ # is a single writer per checkout, so this bookkeeping write is safe.
548
+ st, data = load_state(args.state)
549
+ if st == "corrupt":
550
+ return emit("CORRUPT")
551
+ if st == "absent":
552
+ data = new_state()
553
+ try:
554
+ counts = json.loads(args.counts)
555
+ except (ValueError, TypeError):
556
+ counts = {}
557
+ data["last_run"] = {
558
+ "timestamp": args.timestamp,
559
+ "outcome": args.outcome,
560
+ "writer": args.writer,
561
+ "counts": counts,
562
+ }
563
+ write_state(args.state, data)
564
+ return emit("OK")
565
+
566
+
567
+ def cmd_import_legacy(args):
568
+ """Best-effort import of a Cora-style legacy state file. Liberal on input:
569
+ map what matches the known shapes, skip what doesn't, never fail."""
570
+ st, data = load_state(args.state)
571
+ if st == "corrupt":
572
+ return emit("CORRUPT")
573
+ if st == "absent":
574
+ data = new_state()
575
+
576
+ # Optional map from legacy channel id -> configured source id, so imported
577
+ # cursors land under the id the live connector reads via `cursor-get
578
+ # --source <config-id>`. Without it, a legacy "C42" cursor would be orphaned
579
+ # from a source configured as "slack-alpha" and the first sweep re-ingests
580
+ # everything. Absent or unparseable -> identity (legacy id used verbatim).
581
+ source_map = {}
582
+ if getattr(args, "source_map", None):
583
+ try:
584
+ parsed = json.loads(args.source_map)
585
+ if isinstance(parsed, dict):
586
+ source_map = {str(k): str(v) for k, v in parsed.items()}
587
+ except (ValueError, TypeError):
588
+ source_map = {}
589
+
590
+ legacy = _read_legacy(args.file)
591
+ cursors_imported = 0
592
+ items_imported = 0
593
+ if isinstance(legacy, dict):
594
+ cursors_imported = _import_channels(legacy, data, source_map)
595
+ items_imported = _import_legacy_items(legacy, data)
596
+
597
+ # Persist only when something changed: a fresh state file was seeded, or
598
+ # the import actually brought data in. A no-op import writes nothing.
599
+ if st == "absent" or cursors_imported or items_imported:
600
+ write_state(args.state, data)
601
+ return emit("OK", {
602
+ "cursors_imported": cursors_imported,
603
+ "items_imported": items_imported,
604
+ })
605
+
606
+
607
+ def _read_legacy(path):
608
+ try:
609
+ with open(path, encoding="utf-8") as f:
610
+ raw = f.read()
611
+ except (OSError, UnicodeDecodeError):
612
+ return None
613
+ # Try JSON first (Cora persists JSON); fall back to our YAML subset.
614
+ try:
615
+ return json.loads(raw)
616
+ except ValueError:
617
+ pass
618
+ try:
619
+ return parse_document(raw)
620
+ except Exception:
621
+ return None
622
+
623
+
624
+ def _import_channels(legacy, data, source_map=None):
625
+ channels = legacy.get("channels")
626
+ if not isinstance(channels, dict):
627
+ return 0
628
+ source_map = source_map or {}
629
+ sources = data.setdefault("sources", {})
630
+ count = 0
631
+ for chan_id, chan in channels.items():
632
+ if not isinstance(chan, dict):
633
+ continue
634
+ cursor = chan.get("last_processed_ts") or chan.get("cursor")
635
+ if cursor is None:
636
+ continue
637
+ source_id = source_map.get(str(chan_id), str(chan_id))
638
+ entry = sources.setdefault(source_id, {})
639
+ # Never regress an already-advanced cursor: a re-import against live
640
+ # state must not rewind a source to the legacy value and re-ingest
641
+ # (and re-acknowledge) everything since. Seed only when absent, or when
642
+ # the legacy value is not older than the current one.
643
+ current = entry.get("cursor")
644
+ if current is not None and _cursor_lt(str(cursor), str(current)):
645
+ continue
646
+ entry["cursor"] = cursor
647
+ count += 1
648
+ return count
649
+
650
+
651
+ def _import_legacy_items(legacy, data):
652
+ raw_items = legacy.get("items")
653
+ items = data.setdefault("items", {})
654
+ count = 0
655
+
656
+ def add(item_id, fields):
657
+ if not item_id:
658
+ return 0
659
+ merged = dict(items.get(str(item_id), {}))
660
+ for k in ("status", "source", "channel", "title", "url"):
661
+ if k in fields and fields[k] is not None:
662
+ key = "source" if k == "channel" else k
663
+ merged.setdefault(key, fields[k])
664
+ items[str(item_id)] = merged
665
+ return 1
666
+
667
+ if isinstance(raw_items, dict):
668
+ for item_id, fields in raw_items.items():
669
+ if isinstance(fields, dict):
670
+ count += add(item_id, fields)
671
+ elif isinstance(raw_items, list):
672
+ for entry in raw_items:
673
+ if isinstance(entry, dict):
674
+ count += add(entry.get("id"), entry)
675
+ return count
676
+
677
+
678
+ # --------------------------------------------------------------------------- #
679
+ # CLI wiring
680
+ # --------------------------------------------------------------------------- #
681
+
682
+ def build_parser():
683
+ p = argparse.ArgumentParser(description="ce-sweep deterministic state engine")
684
+ sub = p.add_subparsers(dest="cmd", required=True)
685
+
686
+ def with_state(sp):
687
+ sp.add_argument("--state", required=True)
688
+ return sp
689
+
690
+ with_state(sub.add_parser("read"))
691
+ with_state(sub.add_parser("validate"))
692
+
693
+ up = with_state(sub.add_parser("upsert-item"))
694
+ up.add_argument("--id", required=True)
695
+ up.add_argument("--source", required=True)
696
+ up.add_argument("--json", required=True)
697
+ up.add_argument("--writer", required=True)
698
+ up.add_argument("--now")
699
+
700
+ cg = with_state(sub.add_parser("cursor-get"))
701
+ cg.add_argument("--source", required=True)
702
+
703
+ ca = with_state(sub.add_parser("cursor-advance"))
704
+ ca.add_argument("--source", required=True)
705
+ ca.add_argument("--to", required=True)
706
+ ca.add_argument("--past-item", required=True)
707
+ ca.add_argument("--writer", required=True)
708
+ ca.add_argument("--now")
709
+
710
+ la = with_state(sub.add_parser("lease-acquire"))
711
+ la.add_argument("--writer", required=True)
712
+ la.add_argument("--ttl-minutes", type=int, default=None)
713
+ la.add_argument("--now")
714
+
715
+ lr = with_state(sub.add_parser("lease-release"))
716
+ lr.add_argument("--writer", required=True)
717
+
718
+ rr = with_state(sub.add_parser("run-record"))
719
+ rr.add_argument("--writer", required=True)
720
+ rr.add_argument(
721
+ "--outcome", required=True,
722
+ choices=("completed", "aborted-locked", "partial", "failed"),
723
+ )
724
+ rr.add_argument("--counts", required=True)
725
+ rr.add_argument("--timestamp", required=True)
726
+
727
+ il = with_state(sub.add_parser("import-legacy"))
728
+ il.add_argument("--file", required=True)
729
+ il.add_argument(
730
+ "--source-map",
731
+ help='JSON object mapping legacy channel id -> configured source id, '
732
+ 'e.g. \'{"C42":"slack-alpha"}\'. Absent -> legacy ids used verbatim.',
733
+ )
734
+
735
+ return p
736
+
737
+
738
+ _HANDLERS = {
739
+ "read": cmd_read,
740
+ "validate": cmd_validate,
741
+ "upsert-item": cmd_upsert_item,
742
+ "cursor-get": cmd_cursor_get,
743
+ "cursor-advance": cmd_cursor_advance,
744
+ "lease-acquire": cmd_lease_acquire,
745
+ "lease-release": cmd_lease_release,
746
+ "run-record": cmd_run_record,
747
+ "import-legacy": cmd_import_legacy,
748
+ }
749
+
750
+ # Subcommands that read-modify-write the state file. The lease is a high-level
751
+ # "who owns the sweep" guard, but some writes are deliberately lease-agnostic
752
+ # (run-record for an aborted-locked run, validate, import-legacy). Two
753
+ # concurrent invocations (an overlapping cron and manual sweep) could otherwise
754
+ # interleave load -> mutate -> write and lose an update — e.g. an aborted run's
755
+ # stale-snapshot write clobbering the holder's just-committed upsert. An OS
756
+ # advisory lock held across each mutating RMW makes them mutually exclusive
757
+ # regardless of lease ownership.
758
+ _MUTATING = {
759
+ "validate", "upsert-item", "cursor-advance", "lease-acquire",
760
+ "lease-release", "run-record", "import-legacy",
761
+ }
762
+
763
+
764
+ def _run_locked(handler, args):
765
+ lock_path = str(args.state) + ".lock"
766
+ try:
767
+ lock_fd = open(lock_path, "w", encoding="utf-8")
768
+ except OSError:
769
+ return handler(args) # cannot create a lock file; degrade to unlocked
770
+ try:
771
+ fcntl.flock(lock_fd, fcntl.LOCK_EX)
772
+ return handler(args)
773
+ finally:
774
+ try:
775
+ fcntl.flock(lock_fd, fcntl.LOCK_UN)
776
+ finally:
777
+ lock_fd.close()
778
+
779
+
780
+ def main(argv):
781
+ args = build_parser().parse_args(argv[1:])
782
+ handler = _HANDLERS[args.cmd]
783
+ try:
784
+ if _HAS_FCNTL and args.cmd in _MUTATING:
785
+ return _run_locked(handler, args)
786
+ return handler(args)
787
+ except Exception as exc: # never leak a traceback to the caller
788
+ sys.stderr.write(f"sweep-state: internal error: {exc}\n")
789
+ return emit("ERROR")
790
+
791
+
792
+ if __name__ == "__main__":
793
+ sys.exit(main(sys.argv))