dsh-pstack 0.1.0 → 0.2.0-beta.3
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.
- package/LICENSE +2 -3
- package/README.md +174 -148
- package/README.zh.md +185 -0
- package/assets/logo.png +0 -0
- package/cordis.patch.yml +103 -14
- package/dist/host-contract-CUPSpeNk.d.mts +22 -0
- package/dist/index.d.mts +87 -14
- package/dist/index.mjs +370 -143
- package/dist/models.d.mts +44 -0
- package/dist/models.mjs +201 -0
- package/dist/paths-BT77khwm.mjs +48 -0
- package/dist/{LICENSE → pstack/LICENSE} +1 -2
- package/dist/pstack/README.md +291 -0
- package/{assets → dist/pstack}/agents/comment-sicko.md +1 -1
- package/dist/pstack/agents/poteto-agent.md +9 -0
- package/{assets → dist/pstack}/docs/guide/01-setup.md +11 -9
- package/dist/{assets → pstack}/docs/guide/02-poteto-mode.md +2 -2
- package/{assets → dist/pstack}/docs/guide/04-design.md +4 -4
- package/dist/{assets → pstack}/docs/guide/05-build-and-clean.md +3 -3
- package/dist/{assets → pstack}/docs/guide/06-verify-and-ship.md +11 -1
- package/{assets → dist/pstack}/docs/guide/07-overnight.md +10 -10
- package/dist/{assets → pstack}/docs/guide/09-make-it-yours.md +4 -4
- package/{assets → dist/pstack}/docs/guide/10-recipes-and-pitfalls.md +6 -4
- package/dist/pstack/docs/guide/images/design.jpg +0 -0
- package/dist/pstack/docs/guide/images/overnight.jpg +0 -0
- package/dist/pstack/docs/guide/images/recipes.jpg +0 -0
- package/dist/pstack/docs/guide/images/router.jpg +0 -0
- package/dist/pstack/docs/guide/images/understanding.jpg +0 -0
- package/dist/pstack/docs/guide/images/verification.jpg +0 -0
- package/dist/pstack/docs/harness/dsh.md +98 -0
- package/{assets → dist/pstack}/skills/architect/SKILL.md +5 -5
- package/dist/pstack/skills/arena/SKILL.md +50 -0
- package/{assets → dist/pstack}/skills/automate-me/SKILL.md +14 -13
- package/dist/{assets → pstack}/skills/benchmark-checklist/SKILL.md +1 -3
- package/dist/{assets → pstack}/skills/blast-radius/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/bro/SKILL.md +0 -2
- package/dist/{assets → pstack}/skills/correct/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/create-formal-verification-skill/SKILL.md +5 -3
- package/{assets → dist/pstack}/skills/create-verification-skill/SKILL.md +6 -6
- package/{assets → dist/pstack}/skills/figure-it-out/SKILL.md +4 -4
- package/dist/{assets → pstack}/skills/how/SKILL.md +4 -4
- package/{assets → dist/pstack}/skills/how/references/explainer-prompt.md +1 -1
- package/dist/pstack/skills/in-your-own-words/SKILL.md +7 -0
- package/dist/pstack/skills/interrogate/SKILL.md +33 -0
- package/{assets → dist/pstack}/skills/ketchup/SKILL.md +1 -3
- package/dist/pstack/skills/ketchup-video/SKILL.md +29 -0
- package/{assets → dist/pstack}/skills/maintain-formal-verification-skill/SKILL.md +2 -2
- package/dist/{assets → pstack}/skills/maintain-verification-skill/SKILL.md +3 -3
- package/dist/pstack/skills/make-bot-ui/SKILL.md +117 -0
- package/{assets → dist/pstack}/skills/no-comments/SKILL.md +4 -4
- package/{assets → dist/pstack}/skills/poteto-help/SKILL.md +21 -17
- package/dist/{assets → pstack}/skills/poteto-help/references/prompting.md +1 -1
- package/dist/{assets → pstack}/skills/poteto-help/references/recipes.md +3 -3
- package/{assets → dist/pstack}/skills/poteto-mode/SKILL.md +20 -21
- package/dist/{assets → pstack}/skills/poteto-mode/playbooks/authoring-a-skill.md +1 -1
- package/dist/{assets → pstack}/skills/poteto-mode/playbooks/autonomous-run.md +2 -2
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/autopilot-full.md +7 -7
- package/dist/pstack/skills/poteto-mode/playbooks/autopilot-stack.md +8 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/babysit.md +3 -3
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/bug-fix.md +3 -3
- package/dist/{assets → pstack}/skills/poteto-mode/playbooks/eval.md +1 -1
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/feature.md +1 -1
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/hillclimb.md +1 -1
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/multi-phase-plan.md +13 -13
- package/dist/{assets → pstack}/skills/poteto-mode/playbooks/opening-a-pr.md +3 -3
- package/dist/pstack/skills/poteto-mode/playbooks/orchestrate.md +25 -0
- package/dist/{assets → pstack}/skills/poteto-mode/playbooks/perf-issue.md +2 -2
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/prototype.md +1 -1
- package/dist/{assets → pstack}/skills/poteto-mode/playbooks/refactoring.md +2 -2
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/runtime-forensics.md +1 -1
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/session-pickup.md +1 -1
- package/dist/{assets → pstack}/skills/poteto-mode/playbooks/shipping.md +2 -2
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/visual-parity.md +1 -1
- package/dist/{assets → pstack}/skills/poteto-mode/playbooks/worktree-cleanup.md +2 -2
- package/{assets → dist/pstack}/skills/poteto-mode/scripts/check-plan.mjs +83 -70
- package/dist/{assets/skills/poteto-mode/scripts/check-plan.test.ts → pstack/skills/poteto-mode/scripts/check-plan.test.mjs} +53 -21
- package/dist/pstack/skills/poteto-mode/scripts/expect.mjs +254 -0
- package/dist/pstack/skills/poteto-mode/scripts/support.mjs +93 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.mjs +123 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +217 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.mjs +119 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.mjs +626 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.test.mjs +521 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.mjs +657 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +523 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/render.mjs +106 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/types.mjs +9 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +5 -0
- package/dist/pstack/skills/poteto-mode/scripts/worktree-audit.mjs +668 -0
- package/dist/pstack/skills/poteto-mode/scripts/worktree-audit.test.mjs +134 -0
- package/{assets → dist/pstack}/skills/principle-attack-the-premise/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/principle-boundary-discipline/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/principle-build-the-lever/SKILL.md +0 -3
- package/dist/pstack/skills/principle-encode-lessons-in-structure/SKILL.md +11 -0
- package/dist/pstack/skills/principle-exhaust-the-design-space/SKILL.md +9 -0
- package/{assets → dist/pstack}/skills/principle-experience-first/SKILL.md +1 -3
- package/dist/{assets → pstack}/skills/principle-explain-the-number/SKILL.md +3 -4
- package/dist/{assets → pstack}/skills/principle-fix-root-causes/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/principle-foundational-thinking/SKILL.md +0 -2
- package/dist/{assets → pstack}/skills/principle-guard-the-context-window/SKILL.md +0 -2
- package/dist/{assets → pstack}/skills/principle-laziness-protocol/SKILL.md +1 -3
- package/dist/{assets → pstack}/skills/principle-make-operations-idempotent/SKILL.md +1 -3
- package/dist/{assets → pstack}/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/principle-minimize-reader-load/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/principle-model-the-domain/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/principle-never-block-on-the-human/SKILL.md +1 -3
- package/dist/{assets → pstack}/skills/principle-outcome-oriented-execution/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/principle-prove-it-works/SKILL.md +3 -3
- package/dist/{assets → pstack}/skills/principle-redesign-from-first-principles/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/principle-separate-before-serializing-shared-state/SKILL.md +1 -3
- package/dist/{assets → pstack}/skills/principle-sequence-verifiable-units/SKILL.md +1 -3
- package/dist/{assets → pstack}/skills/principle-subtract-before-you-add/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/principle-test-behavior-not-implementation/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/principle-type-system-discipline/SKILL.md +1 -3
- package/dist/{assets → pstack}/skills/recall/SKILL.md +4 -4
- package/dist/pstack/skills/reflect/SKILL.md +71 -0
- package/{assets → dist/pstack}/skills/reflect/references/divergent-reviewer.md +1 -1
- package/{assets → dist/pstack}/skills/reflect/references/judgment-reviewer.md +1 -1
- package/{assets → dist/pstack}/skills/reflect/references/synthesizer.md +3 -3
- package/dist/{assets → pstack}/skills/reflect/references/tooling-reviewer.md +1 -1
- package/dist/pstack/skills/setup-pstack/SKILL.md +80 -0
- package/{assets → dist/pstack}/skills/sharpen/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/show-me-your-work/SKILL.md +3 -3
- package/dist/pstack/skills/show-me-your-work/scripts/log.mjs +106 -0
- package/dist/pstack/skills/show-me-your-work/scripts/log.test.mjs +56 -0
- package/dist/pstack/skills/statechart/SKILL.md +82 -0
- package/dist/pstack/skills/statechart/references/render.md +93 -0
- package/dist/pstack/skills/statechart/scripts/backends/cfg.mjs +31 -0
- package/dist/pstack/skills/statechart/scripts/backends/mermaid.mjs +186 -0
- package/dist/pstack/skills/statechart/scripts/backends/plain-text.mjs +62 -0
- package/dist/pstack/skills/statechart/scripts/backends/report.mjs +84 -0
- package/dist/pstack/skills/statechart/scripts/frontends/config.mjs +268 -0
- package/dist/pstack/skills/statechart/scripts/frontends/dot.mjs +67 -0
- package/dist/pstack/skills/statechart/scripts/frontends/marks.mjs +94 -0
- package/dist/pstack/skills/statechart/scripts/frontends/messages.mjs +48 -0
- package/dist/pstack/skills/statechart/scripts/frontends/spec.mjs +101 -0
- package/dist/pstack/skills/statechart/scripts/frontends/trace.mjs +39 -0
- package/dist/pstack/skills/statechart/scripts/frontends/value.mjs +180 -0
- package/dist/pstack/skills/statechart/scripts/frontends/verdict.mjs +40 -0
- package/dist/pstack/skills/statechart/scripts/frontends/view.mjs +99 -0
- package/dist/pstack/skills/statechart/scripts/ir.mjs +277 -0
- package/dist/pstack/skills/statechart/scripts/passes/annotate.mjs +92 -0
- package/dist/pstack/skills/statechart/scripts/passes/label.mjs +210 -0
- package/dist/pstack/skills/statechart/scripts/passes/lift.mjs +84 -0
- package/dist/pstack/skills/statechart/scripts/passes/loop-check.mjs +47 -0
- package/dist/pstack/skills/statechart/scripts/passes/paths.mjs +72 -0
- package/dist/pstack/skills/statechart/scripts/passes/reach.mjs +135 -0
- package/dist/pstack/skills/statechart/scripts/passes/region-check.mjs +82 -0
- package/dist/pstack/skills/statechart/scripts/passes/regions.mjs +268 -0
- package/dist/pstack/skills/statechart/scripts/pipeline.mjs +72 -0
- package/dist/pstack/skills/statechart/scripts/run.mjs +440 -0
- package/dist/pstack/skills/statechart/scripts/statechart.mjs +155 -0
- package/dist/pstack/skills/statechart/scripts/statechart.test.mjs +198 -0
- package/dist/pstack/skills/statechart/scripts/worker.mjs +31 -0
- package/dist/pstack/skills/swarm/SKILL.md +43 -0
- package/{assets → dist/pstack}/skills/tdd/SKILL.md +1 -3
- package/{assets → dist/pstack}/skills/teach/SKILL.md +3 -3
- package/{assets → dist/pstack}/skills/technical-writing/SKILL.md +2 -2
- package/{assets → dist/pstack}/skills/typescript-best-practices/SKILL.md +1 -2
- package/{assets → dist/pstack}/skills/unslop/SKILL.md +0 -2
- package/dist/{assets → pstack}/skills/why/SKILL.md +9 -9
- package/dist/script.d.mts +91 -0
- package/dist/script.mjs +485 -0
- package/dist/skill.d.mts +38 -0
- package/dist/skill.mjs +183 -0
- package/dist/skills/authored/decision-audit/SKILL.md +99 -0
- package/dist/skills/authored/decision-audit/SKILL.zh.md +99 -0
- package/dist/skills/authored/decision-audit/references/recover-commit.md +17 -0
- package/dist/skills/authored/decision-audit/references/recover-commit.zh.md +17 -0
- package/dist/skills/authored/decision-write/SKILL.md +131 -0
- package/dist/skills/authored/decision-write/SKILL.zh.md +131 -0
- package/dist/skills/authored/decision-write/references/land.md +48 -0
- package/dist/skills/authored/decision-write/references/land.zh.md +48 -0
- package/dist/skills/authored/setup-project-docs/SKILL.md +58 -0
- package/dist/skills/authored/setup-project-docs/SKILL.zh.md +58 -0
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.md +11 -0
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.zh.md +11 -0
- package/dist/skills/vendored/retro/SKILL.md +44 -0
- package/dist/skills/vendored/retro/agents/openai.yaml +5 -0
- package/dist/skills/vendored/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/dist/skills/vendored/writing-for-agents/SKILL.md +81 -0
- package/dist/skills/vendored/writing-for-agents/agents/openai.yaml +3 -0
- package/dist/subagent.d.mts +175 -106
- package/dist/subagent.mjs +1012 -344
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +61 -50
- package/assets/agents/poteto-agent.md +0 -8
- package/assets/docs/guide/02-poteto-mode.md +0 -139
- package/assets/docs/guide/05-build-and-clean.md +0 -83
- package/assets/docs/guide/06-verify-and-ship.md +0 -152
- package/assets/docs/guide/09-make-it-yours.md +0 -100
- package/assets/docs/harness/dsh.md +0 -65
- package/assets/docs/pstack-models.example.md +0 -23
- package/assets/skills/arena/SKILL.md +0 -50
- package/assets/skills/benchmark-checklist/SKILL.md +0 -41
- package/assets/skills/blast-radius/SKILL.md +0 -52
- package/assets/skills/correct/SKILL.md +0 -34
- package/assets/skills/how/SKILL.md +0 -38
- package/assets/skills/in-your-own-words/SKILL.md +0 -9
- package/assets/skills/interrogate/SKILL.md +0 -69
- package/assets/skills/interrogate/references/code-quality-review.md +0 -47
- package/assets/skills/interrogate/references/lead-judgment.md +0 -39
- package/assets/skills/interrogate/references/reviewer-prompt.md +0 -62
- package/assets/skills/interrogate/references/rubric.md +0 -77
- package/assets/skills/maintain-verification-skill/SKILL.md +0 -41
- package/assets/skills/poteto-help/references/prompting.md +0 -51
- package/assets/skills/poteto-help/references/recipes.md +0 -51
- package/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +0 -12
- package/assets/skills/poteto-mode/playbooks/autonomous-run.md +0 -13
- package/assets/skills/poteto-mode/playbooks/autopilot-stack.md +0 -12
- package/assets/skills/poteto-mode/playbooks/eval.md +0 -25
- package/assets/skills/poteto-mode/playbooks/opening-a-pr.md +0 -34
- package/assets/skills/poteto-mode/playbooks/orchestrate.md +0 -112
- package/assets/skills/poteto-mode/playbooks/perf-issue.md +0 -25
- package/assets/skills/poteto-mode/playbooks/refactoring.md +0 -16
- package/assets/skills/poteto-mode/playbooks/shipping.md +0 -12
- package/assets/skills/poteto-mode/playbooks/worktree-cleanup.md +0 -12
- package/assets/skills/poteto-mode/scripts/bootstrap.ts +0 -62
- package/assets/skills/poteto-mode/scripts/bun.lock +0 -67
- package/assets/skills/poteto-mode/scripts/check-plan.test.ts +0 -96
- package/assets/skills/poteto-mode/scripts/orch/orch.test.ts +0 -761
- package/assets/skills/poteto-mode/scripts/orch/orch.ts +0 -590
- package/assets/skills/poteto-mode/scripts/orch/store.ts +0 -1677
- package/assets/skills/poteto-mode/scripts/package.json +0 -16
- package/assets/skills/poteto-mode/scripts/watch-pr/cli.test.ts +0 -224
- package/assets/skills/poteto-mode/scripts/watch-pr/cli.ts +0 -223
- package/assets/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +0 -129
- package/assets/skills/poteto-mode/scripts/watch-pr/github.test.ts +0 -436
- package/assets/skills/poteto-mode/scripts/watch-pr/github.ts +0 -718
- package/assets/skills/poteto-mode/scripts/watch-pr/policy.test.ts +0 -420
- package/assets/skills/poteto-mode/scripts/watch-pr/policy.ts +0 -836
- package/assets/skills/poteto-mode/scripts/watch-pr/render.ts +0 -169
- package/assets/skills/poteto-mode/scripts/watch-pr/tsconfig.json +0 -13
- package/assets/skills/poteto-mode/scripts/watch-pr/types.compile.ts +0 -93
- package/assets/skills/poteto-mode/scripts/watch-pr/types.ts +0 -413
- package/assets/skills/poteto-mode/scripts/watch-pr/watch-pr +0 -6
- package/assets/skills/poteto-mode/scripts/worktree-audit.sh +0 -127
- package/assets/skills/principle-encode-lessons-in-structure/SKILL.md +0 -13
- package/assets/skills/principle-exhaust-the-design-space/SKILL.md +0 -11
- package/assets/skills/principle-explain-the-number/SKILL.md +0 -19
- package/assets/skills/principle-fix-root-causes/SKILL.md +0 -23
- package/assets/skills/principle-guard-the-context-window/SKILL.md +0 -14
- package/assets/skills/principle-laziness-protocol/SKILL.md +0 -12
- package/assets/skills/principle-make-operations-idempotent/SKILL.md +0 -24
- package/assets/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +0 -16
- package/assets/skills/principle-outcome-oriented-execution/SKILL.md +0 -23
- package/assets/skills/principle-redesign-from-first-principles/SKILL.md +0 -18
- package/assets/skills/principle-sequence-verifiable-units/SKILL.md +0 -19
- package/assets/skills/principle-subtract-before-you-add/SKILL.md +0 -20
- package/assets/skills/recall/SKILL.md +0 -37
- package/assets/skills/reflect/SKILL.md +0 -70
- package/assets/skills/reflect/references/tooling-reviewer.md +0 -48
- package/assets/skills/setup-pstack/SKILL.md +0 -76
- package/assets/skills/show-me-your-work/scripts/log.sh +0 -42
- package/assets/skills/swarm/SKILL.md +0 -41
- package/assets/skills/why/SKILL.md +0 -100
- package/dist/README.md +0 -159
- package/dist/assets/agents/comment-sicko.md +0 -32
- package/dist/assets/agents/poteto-agent.md +0 -8
- package/dist/assets/docs/guide/01-setup.md +0 -64
- package/dist/assets/docs/guide/03-understand.md +0 -79
- package/dist/assets/docs/guide/04-design.md +0 -133
- package/dist/assets/docs/guide/07-overnight.md +0 -122
- package/dist/assets/docs/guide/08-principles.md +0 -72
- package/dist/assets/docs/guide/10-recipes-and-pitfalls.md +0 -180
- package/dist/assets/docs/guide/README.md +0 -38
- package/dist/assets/docs/harness/dsh.md +0 -65
- package/dist/assets/docs/pstack-models.example.md +0 -23
- package/dist/assets/skills/architect/SKILL.md +0 -81
- package/dist/assets/skills/architect/references/design-red-flags.md +0 -57
- package/dist/assets/skills/architect/references/rationale-template.md +0 -35
- package/dist/assets/skills/architect/references/runner-prompt.md +0 -20
- package/dist/assets/skills/arena/SKILL.md +0 -50
- package/dist/assets/skills/automate-me/SKILL.md +0 -105
- package/dist/assets/skills/bro/SKILL.md +0 -9
- package/dist/assets/skills/create-formal-verification-skill/SKILL.md +0 -43
- package/dist/assets/skills/create-verification-skill/SKILL.md +0 -46
- package/dist/assets/skills/create-verification-skill/references/feature-map-example/README.md +0 -47
- package/dist/assets/skills/create-verification-skill/references/feature-map-example/create-note.md +0 -39
- package/dist/assets/skills/create-verification-skill/references/feature-map-example/search.md +0 -45
- package/dist/assets/skills/figure-it-out/SKILL.md +0 -54
- package/dist/assets/skills/how/references/explainer-prompt.md +0 -55
- package/dist/assets/skills/how/references/explorer-prompt.md +0 -35
- package/dist/assets/skills/in-your-own-words/SKILL.md +0 -9
- package/dist/assets/skills/interrogate/SKILL.md +0 -69
- package/dist/assets/skills/interrogate/references/code-quality-review.md +0 -47
- package/dist/assets/skills/interrogate/references/lead-judgment.md +0 -39
- package/dist/assets/skills/interrogate/references/reviewer-prompt.md +0 -62
- package/dist/assets/skills/interrogate/references/rubric.md +0 -77
- package/dist/assets/skills/ketchup/SKILL.md +0 -45
- package/dist/assets/skills/maintain-formal-verification-skill/SKILL.md +0 -60
- package/dist/assets/skills/no-comments/SKILL.md +0 -26
- package/dist/assets/skills/poteto-help/SKILL.md +0 -161
- package/dist/assets/skills/poteto-mode/SKILL.md +0 -153
- package/dist/assets/skills/poteto-mode/playbooks/autopilot-full.md +0 -25
- package/dist/assets/skills/poteto-mode/playbooks/autopilot-stack.md +0 -12
- package/dist/assets/skills/poteto-mode/playbooks/babysit.md +0 -23
- package/dist/assets/skills/poteto-mode/playbooks/bug-fix.md +0 -15
- package/dist/assets/skills/poteto-mode/playbooks/feature.md +0 -21
- package/dist/assets/skills/poteto-mode/playbooks/hillclimb.md +0 -21
- package/dist/assets/skills/poteto-mode/playbooks/investigation.md +0 -14
- package/dist/assets/skills/poteto-mode/playbooks/multi-phase-plan.md +0 -155
- package/dist/assets/skills/poteto-mode/playbooks/orchestrate.md +0 -112
- package/dist/assets/skills/poteto-mode/playbooks/pause-safely.md +0 -10
- package/dist/assets/skills/poteto-mode/playbooks/prototype.md +0 -14
- package/dist/assets/skills/poteto-mode/playbooks/runtime-forensics.md +0 -11
- package/dist/assets/skills/poteto-mode/playbooks/session-pickup.md +0 -11
- package/dist/assets/skills/poteto-mode/playbooks/trace-forensics.md +0 -14
- package/dist/assets/skills/poteto-mode/playbooks/visual-parity.md +0 -11
- package/dist/assets/skills/poteto-mode/references/bugbot-triage.md +0 -142
- package/dist/assets/skills/poteto-mode/scripts/bootstrap.ts +0 -62
- package/dist/assets/skills/poteto-mode/scripts/bun.lock +0 -67
- package/dist/assets/skills/poteto-mode/scripts/check-plan.mjs +0 -205
- package/dist/assets/skills/poteto-mode/scripts/orch/orch.test.ts +0 -761
- package/dist/assets/skills/poteto-mode/scripts/orch/orch.ts +0 -590
- package/dist/assets/skills/poteto-mode/scripts/orch/store.ts +0 -1677
- package/dist/assets/skills/poteto-mode/scripts/package.json +0 -16
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/cli.test.ts +0 -224
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/cli.ts +0 -223
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +0 -129
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/github.test.ts +0 -436
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/github.ts +0 -718
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/policy.test.ts +0 -420
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/policy.ts +0 -836
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/render.ts +0 -169
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/tsconfig.json +0 -13
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/types.compile.ts +0 -93
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/types.ts +0 -413
- package/dist/assets/skills/poteto-mode/scripts/watch-pr/watch-pr +0 -6
- package/dist/assets/skills/poteto-mode/scripts/worktree-audit.sh +0 -127
- package/dist/assets/skills/principle-attack-the-premise/SKILL.md +0 -23
- package/dist/assets/skills/principle-boundary-discipline/SKILL.md +0 -36
- package/dist/assets/skills/principle-build-the-lever/SKILL.md +0 -26
- package/dist/assets/skills/principle-encode-lessons-in-structure/SKILL.md +0 -13
- package/dist/assets/skills/principle-exhaust-the-design-space/SKILL.md +0 -11
- package/dist/assets/skills/principle-experience-first/SKILL.md +0 -21
- package/dist/assets/skills/principle-foundational-thinking/SKILL.md +0 -23
- package/dist/assets/skills/principle-minimize-reader-load/SKILL.md +0 -20
- package/dist/assets/skills/principle-model-the-domain/SKILL.md +0 -26
- package/dist/assets/skills/principle-never-block-on-the-human/SKILL.md +0 -17
- package/dist/assets/skills/principle-prove-it-works/SKILL.md +0 -24
- package/dist/assets/skills/principle-separate-before-serializing-shared-state/SKILL.md +0 -18
- package/dist/assets/skills/principle-test-behavior-not-implementation/SKILL.md +0 -22
- package/dist/assets/skills/principle-type-system-discipline/SKILL.md +0 -15
- package/dist/assets/skills/reflect/SKILL.md +0 -70
- package/dist/assets/skills/reflect/references/divergent-reviewer.md +0 -38
- package/dist/assets/skills/reflect/references/judgment-reviewer.md +0 -37
- package/dist/assets/skills/reflect/references/synthesizer.md +0 -56
- package/dist/assets/skills/setup-pstack/SKILL.md +0 -76
- package/dist/assets/skills/sharpen/SKILL.md +0 -18
- package/dist/assets/skills/show-me-your-work/SKILL.md +0 -84
- package/dist/assets/skills/show-me-your-work/references/decision-log-template.tsv +0 -1
- package/dist/assets/skills/show-me-your-work/scripts/log.sh +0 -42
- package/dist/assets/skills/swarm/SKILL.md +0 -41
- package/dist/assets/skills/tdd/SKILL.md +0 -44
- package/dist/assets/skills/teach/SKILL.md +0 -23
- package/dist/assets/skills/technical-writing/SKILL.md +0 -108
- package/dist/assets/skills/typescript-best-practices/SKILL.md +0 -32
- package/dist/assets/skills/typescript-best-practices/references/patterns.md +0 -328
- package/dist/assets/skills/unslop/SKILL.md +0 -64
- package/dist/assets/skills/why/references/epistemics.md +0 -71
- package/dist/assets/skills/why/references/investigator-prompt.md +0 -84
- package/dist/assets/skills/why/references/source-playbook.md +0 -17
- package/dist/assets/skills/why/references/sources/code-archaeology.md +0 -51
- package/dist/assets/skills/why/references/sources/databricks.md +0 -70
- package/dist/assets/skills/why/references/sources/datadog.md +0 -97
- package/dist/assets/skills/why/references/sources/incident-postmortem.md +0 -5
- package/dist/assets/skills/why/references/sources/linear.md +0 -48
- package/dist/assets/skills/why/references/sources/notion.md +0 -55
- package/dist/assets/skills/why/references/sources/sentry.md +0 -66
- package/dist/assets/skills/why/references/sources/slack.md +0 -17
- package/dist/assets/skills/why/references/synthesizer-prompt.md +0 -129
- package/dist/cordis.patch.yml +0 -20
- package/dist/index.mjs.map +0 -1
- package/dist/paths-Bbai4CaT.mjs +0 -16
- package/dist/paths-Bbai4CaT.mjs.map +0 -1
- package/dist/subagent.mjs.map +0 -1
- /package/{assets → dist/pstack}/docs/guide/03-understand.md +0 -0
- /package/{assets → dist/pstack}/docs/guide/08-principles.md +0 -0
- /package/{assets → dist/pstack}/docs/guide/README.md +0 -0
- /package/{assets → dist/pstack}/skills/architect/references/design-red-flags.md +0 -0
- /package/{assets → dist/pstack}/skills/architect/references/rationale-template.md +0 -0
- /package/{assets → dist/pstack}/skills/architect/references/runner-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/README.md +0 -0
- /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/create-note.md +0 -0
- /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/search.md +0 -0
- /package/{assets → dist/pstack}/skills/how/references/explorer-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/poteto-mode/playbooks/investigation.md +0 -0
- /package/{assets → dist/pstack}/skills/poteto-mode/playbooks/pause-safely.md +0 -0
- /package/{assets → dist/pstack}/skills/poteto-mode/playbooks/trace-forensics.md +0 -0
- /package/{assets → dist/pstack}/skills/poteto-mode/references/bugbot-triage.md +0 -0
- /package/{assets → dist/pstack}/skills/show-me-your-work/references/decision-log-template.tsv +0 -0
- /package/{assets → dist/pstack}/skills/typescript-best-practices/references/patterns.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/epistemics.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/investigator-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/source-playbook.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/code-archaeology.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/databricks.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/datadog.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/incident-postmortem.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/linear.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/notion.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/sentry.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/slack.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/synthesizer-prompt.md +0 -0
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
import { lstatSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, resolve, sep } from "node:path";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
// runtime/skills/poteto-mode/scripts/worktree-audit.ts
|
|
6
|
+
/**
|
|
7
|
+
* Read-only worktree prune audit.
|
|
8
|
+
*
|
|
9
|
+
* A Node replacement for the upstream `scripts/worktree-audit.sh`. The shell
|
|
10
|
+
* script's job was to classify every git worktree by size, age, merge state,
|
|
11
|
+
* uncommitted work, remote/PR state, and the newest supplied session, then print
|
|
12
|
+
* one TSV table sorted by size with a suggested bucket. That job is unchanged; what
|
|
13
|
+
* changed is the toolchain. This file runs under Node alone, needs no `bash`, no
|
|
14
|
+
* `jq`, and no `awk`, and reads the caller's session JSON with `JSON.parse`.
|
|
15
|
+
*
|
|
16
|
+
* Behaviour kept deliberately identical, because the playbook and the tests name
|
|
17
|
+
* the columns:
|
|
18
|
+
*
|
|
19
|
+
* - The header is `# base: <ref>` then one TSV header line, written once.
|
|
20
|
+
* - Columns are SIZE, AGE, MERGED, DIRTY, REMOTE, PR, LAST_CHAT, BUCKET,
|
|
21
|
+
* WORKTREE, in that order.
|
|
22
|
+
* - The main worktree is never a candidate. Only `git worktree list` provides
|
|
23
|
+
* paths; nothing is hand-typed.
|
|
24
|
+
* - `MERGED` is `YES` only when the worktree HEAD is an ancestor of the base.
|
|
25
|
+
* Squash merges are not ancestors, so the PR state is the real signal there.
|
|
26
|
+
* - `DIRTY` counts tracked edits as `wip:<n>` and untracked-only as `scratch:<n>`,
|
|
27
|
+
* with `clean` for neither.
|
|
28
|
+
* - `REMOTE` is `pushed`, `ahead<n>`, `detached`, or `no-remote`.
|
|
29
|
+
* - `PR` is `#<number>/<state>` from the forge, or `-`.
|
|
30
|
+
* - `LAST_CHAT` is the UTC date of the newest supplied session in the worktree, or `-`.
|
|
31
|
+
* - `BUCKET` is `hold-wip`, `hold-open-pr`, `unknown-usage`, `verify-recent-chat`,
|
|
32
|
+
* `safe`, or `review`. An unknown usage signal never reaches `safe`.
|
|
33
|
+
* - Rows are sorted by SIZE descending, then kept in the order they were produced.
|
|
34
|
+
* - The session store is never scanned. `--sessions` is the only usage input, and
|
|
35
|
+
* without it the LAST_CHAT column is `-` and the bucket is `unknown-usage`.
|
|
36
|
+
*/
|
|
37
|
+
/** The shape `--sessions` takes, repeated in every message about it. */
|
|
38
|
+
const SESSIONS_SHAPE = "a JSON array of {\"sessionId\",\"cwd\",\"lastEventTime\"} objects";
|
|
39
|
+
const HEADER = [
|
|
40
|
+
"SIZE",
|
|
41
|
+
"AGE",
|
|
42
|
+
"MERGED",
|
|
43
|
+
"DIRTY",
|
|
44
|
+
"REMOTE",
|
|
45
|
+
"PR",
|
|
46
|
+
"LAST_CHAT",
|
|
47
|
+
"BUCKET",
|
|
48
|
+
"WORKTREE"
|
|
49
|
+
];
|
|
50
|
+
/** The bucket for a worktree whose usage signal is missing. */
|
|
51
|
+
const UNKNOWN_USAGE_BUCKET = "unknown-usage";
|
|
52
|
+
/** Raised for a usage problem; the caller prints the message and exits nonzero. */
|
|
53
|
+
var AuditError = class extends Error {
|
|
54
|
+
exitCode;
|
|
55
|
+
constructor(message, exitCode = 1) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.name = "AuditError";
|
|
58
|
+
this.exitCode = exitCode;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/** How many days a worktree may be untouched and still count as recent. */
|
|
62
|
+
const RECENT_DAYS = 4;
|
|
63
|
+
/**
|
|
64
|
+
* Run git, returning stdout, or `undefined` when it fails.
|
|
65
|
+
*
|
|
66
|
+
* The shell script wrote `2>/dev/null` on most calls and branched on the exit
|
|
67
|
+
* status, so a failure is a normal value here rather than an exception.
|
|
68
|
+
*/
|
|
69
|
+
function git(repo, args) {
|
|
70
|
+
try {
|
|
71
|
+
return execFileSync("git", [
|
|
72
|
+
"-C",
|
|
73
|
+
repo,
|
|
74
|
+
...args
|
|
75
|
+
], {
|
|
76
|
+
encoding: "utf8",
|
|
77
|
+
stdio: [
|
|
78
|
+
"ignore",
|
|
79
|
+
"pipe",
|
|
80
|
+
"ignore"
|
|
81
|
+
],
|
|
82
|
+
maxBuffer: 33554432
|
|
83
|
+
});
|
|
84
|
+
} catch {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Run git without `-C`, for commands that must see the real repository root. */
|
|
89
|
+
function gitHere(cwd, args) {
|
|
90
|
+
try {
|
|
91
|
+
return execFileSync("git", args, {
|
|
92
|
+
cwd,
|
|
93
|
+
encoding: "utf8",
|
|
94
|
+
stdio: [
|
|
95
|
+
"ignore",
|
|
96
|
+
"pipe",
|
|
97
|
+
"ignore"
|
|
98
|
+
],
|
|
99
|
+
maxBuffer: 33554432
|
|
100
|
+
});
|
|
101
|
+
} catch {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** `git worktree list --porcelain`, parsed into worktree paths in order. */
|
|
106
|
+
function worktreePaths(repo) {
|
|
107
|
+
const out = git(repo, [
|
|
108
|
+
"worktree",
|
|
109
|
+
"list",
|
|
110
|
+
"--porcelain"
|
|
111
|
+
]);
|
|
112
|
+
if (out === void 0) return [];
|
|
113
|
+
return out.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => line.slice(9));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Human size, matching `du -h`.
|
|
117
|
+
*
|
|
118
|
+
* GNU `du -h` divides by 1024, promotes the unit when the value reaches 1024, and
|
|
119
|
+
* prints one decimal below ten and a rounded integer at or above ten. Verified
|
|
120
|
+
* against `du` on this host: 8.0K for 4096 bytes, 12K for 8192, 1.1M for 1 MiB,
|
|
121
|
+
* 513M for 512 MiB. Reproducing the format keeps the column comparable with the
|
|
122
|
+
* numbers the playbook quotes and removes the dependency on `du` and `awk`.
|
|
123
|
+
*/
|
|
124
|
+
function formatSize(bytes) {
|
|
125
|
+
const units = [
|
|
126
|
+
"B",
|
|
127
|
+
"K",
|
|
128
|
+
"M",
|
|
129
|
+
"G",
|
|
130
|
+
"T",
|
|
131
|
+
"P"
|
|
132
|
+
];
|
|
133
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
134
|
+
let value = bytes / 1024;
|
|
135
|
+
let unit = 1;
|
|
136
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
137
|
+
value /= 1024;
|
|
138
|
+
unit += 1;
|
|
139
|
+
}
|
|
140
|
+
const suffix = units[unit];
|
|
141
|
+
return `${value < 10 ? value.toFixed(1) : String(Math.round(value))}${suffix}`;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Directory size in bytes as `du -s` counts it: allocated blocks, directories
|
|
145
|
+
* included.
|
|
146
|
+
*
|
|
147
|
+
* Three details make this match `du`, and each was checked against it on this host:
|
|
148
|
+
*
|
|
149
|
+
* - `stat.blocks` is in 512-byte units, not bytes.
|
|
150
|
+
* - A directory consumes blocks of its own, so its `blocks` counts too. Skipping
|
|
151
|
+
* them reads one 4 KiB block low per directory.
|
|
152
|
+
* - `.git` here is a file in a linked worktree, not a directory, so it contributes
|
|
153
|
+
* its own block and nothing is traversed through it.
|
|
154
|
+
*
|
|
155
|
+
* That block accounting is the point of the column: the playbook quotes the space
|
|
156
|
+
* `du` reports before deleting. A symlink contributes its own block and is never
|
|
157
|
+
* traversed, matching `du` without `-L`.
|
|
158
|
+
*/
|
|
159
|
+
function directorySize(root) {
|
|
160
|
+
let total = 0;
|
|
161
|
+
const pending = [root];
|
|
162
|
+
while (pending.length > 0) {
|
|
163
|
+
const current = pending.pop();
|
|
164
|
+
let details;
|
|
165
|
+
try {
|
|
166
|
+
details = lstatSync(current, { throwIfNoEntry: false });
|
|
167
|
+
} catch {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (details === void 0) continue;
|
|
171
|
+
total += details.blocks === void 0 ? details.size : details.blocks * 512;
|
|
172
|
+
if (details.isSymbolicLink() || !details.isDirectory()) continue;
|
|
173
|
+
let names;
|
|
174
|
+
try {
|
|
175
|
+
names = readdirSync(current);
|
|
176
|
+
} catch {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
for (const name of names) pending.push(resolve(current, name));
|
|
180
|
+
}
|
|
181
|
+
return total;
|
|
182
|
+
}
|
|
183
|
+
/** `SIZE` as `du -sh` would print it, or `-` when the directory is gone. */
|
|
184
|
+
function sizeColumn(worktree) {
|
|
185
|
+
try {
|
|
186
|
+
if (!statSync(worktree).isDirectory()) return "-";
|
|
187
|
+
} catch {
|
|
188
|
+
return "-";
|
|
189
|
+
}
|
|
190
|
+
return formatSize(directorySize(worktree));
|
|
191
|
+
}
|
|
192
|
+
/** `AGE` as `<n>d`, or `?` when the HEAD time is unavailable. */
|
|
193
|
+
function ageColumn(headTimestamp, now) {
|
|
194
|
+
if (!(headTimestamp > 0)) return "?";
|
|
195
|
+
return `${Math.floor((now - headTimestamp) / 86400)}d`;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* `DIRTY` for one worktree.
|
|
199
|
+
*
|
|
200
|
+
* `git status --porcelain` marks untracked entries with `??`. Anything else is
|
|
201
|
+
* tracked work, which is what a human has to look at before deleting.
|
|
202
|
+
*/
|
|
203
|
+
function dirtyColumn(porcelain) {
|
|
204
|
+
const lines = porcelain.split("\n").filter((line) => line !== "");
|
|
205
|
+
if (lines.length === 0) return "clean";
|
|
206
|
+
const tracked = lines.filter((line) => !line.startsWith("??"));
|
|
207
|
+
if (tracked.length > 0) return `wip:${tracked.length}`;
|
|
208
|
+
return `scratch:${lines.filter((line) => line.startsWith("??")).length}`;
|
|
209
|
+
}
|
|
210
|
+
/** `MERGED` for one worktree, from whether HEAD is an ancestor of the base. */
|
|
211
|
+
function mergedColumn(isAncestor) {
|
|
212
|
+
return isAncestor ? "YES" : "no";
|
|
213
|
+
}
|
|
214
|
+
/** `REMOTE` for one worktree. */
|
|
215
|
+
function remoteColumn(input) {
|
|
216
|
+
if (input.branch === void 0 || input.branch === "") return "detached";
|
|
217
|
+
if (!input.hasRemote) return "no-remote";
|
|
218
|
+
if (input.remoteSha !== void 0 && input.head !== void 0 && input.remoteSha === input.head) return "pushed";
|
|
219
|
+
return `ahead${input.aheadCount ?? 0}`;
|
|
220
|
+
}
|
|
221
|
+
/** `PR` for one branch, matching the first entry the forge reported for it. */
|
|
222
|
+
function prColumn(branch, prs) {
|
|
223
|
+
if (branch === void 0 || branch === "") return "-";
|
|
224
|
+
const hit = prs.find((pr) => pr.head === branch);
|
|
225
|
+
return hit === void 0 ? "-" : `#${hit.number}/${hit.state}`;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Whether a session's recorded cwd is this worktree or a directory below it.
|
|
229
|
+
*
|
|
230
|
+
* Both sides go through `resolve` before comparing. `git worktree list` returns
|
|
231
|
+
* platform separators while a session's `cwd` may have been written by a different
|
|
232
|
+
* source, so on Windows `C:\repo\wt` and `C:/repo/wt` name the same checkout but are
|
|
233
|
+
* different strings. The old comparison hardcoded `/`, so it matched nothing there:
|
|
234
|
+
* every `LAST_CHAT` was `-` and every bucket silently degraded to `unknown-usage`.
|
|
235
|
+
*
|
|
236
|
+
* `resolve` also normalises trailing separators and `..` segments, so neither can
|
|
237
|
+
* make one path look like an ancestor of the other by accident.
|
|
238
|
+
*/
|
|
239
|
+
function sameOrInside(cwd, worktree) {
|
|
240
|
+
const child = resolve(cwd);
|
|
241
|
+
const parent = resolve(worktree);
|
|
242
|
+
return child === parent || child.startsWith(`${parent}${sep}`);
|
|
243
|
+
}
|
|
244
|
+
/** The newest supplied session whose cwd is this worktree or below it. */
|
|
245
|
+
function lastSession(worktree, sessions) {
|
|
246
|
+
if (sessions === void 0 || sessions.length === 0) return void 0;
|
|
247
|
+
const matches = sessions.filter((session) => sameOrInside(session.cwd ?? "", worktree));
|
|
248
|
+
if (matches.length === 0) return void 0;
|
|
249
|
+
return matches.toSorted((left, right) => (left.lastEventTime ?? 0) - (right.lastEventTime ?? 0))[matches.length - 1];
|
|
250
|
+
}
|
|
251
|
+
/** `LAST_CHAT` for one worktree: the UTC date of its newest supplied session. */
|
|
252
|
+
function lastChatColumn(session) {
|
|
253
|
+
const time = session?.lastEventTime ?? 0;
|
|
254
|
+
if (!(time > 0)) return "-";
|
|
255
|
+
return (/* @__PURE__ */ new Date(time * 1e3)).toISOString().slice(0, 10);
|
|
256
|
+
}
|
|
257
|
+
/** Whether a session counts as recent for the bucket decision. */
|
|
258
|
+
function isRecent(session, now) {
|
|
259
|
+
const time = session?.lastEventTime ?? 0;
|
|
260
|
+
if (!(time > 0)) return false;
|
|
261
|
+
return Math.floor((now - time) / 86400) <= 4;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* The suggested bucket.
|
|
265
|
+
*
|
|
266
|
+
* Uncommitted tracked work outranks everything, then an open PR, then the usage
|
|
267
|
+
* signal. An unknown usage signal never reaches `safe`: without the caller's
|
|
268
|
+
* session input the bucket stays `unknown-usage`, and the playbook asks the user.
|
|
269
|
+
*/
|
|
270
|
+
function bucketFor(input) {
|
|
271
|
+
if (input.dirty.startsWith("wip:")) return "hold-wip";
|
|
272
|
+
if (input.pr.endsWith("/open") || input.pr.endsWith("/draft")) return "hold-open-pr";
|
|
273
|
+
if (!input.usageKnown) return UNKNOWN_USAGE_BUCKET;
|
|
274
|
+
if (input.usedRecently) return "verify-recent-chat";
|
|
275
|
+
if (input.merged === "YES" || input.pr !== "-") return "safe";
|
|
276
|
+
return "review";
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Read this user's PRs from whichever forge CLI can answer.
|
|
280
|
+
*
|
|
281
|
+
* `origin` reports `status`, `gh` reports `state`; both are normalized to a
|
|
282
|
+
* lowercase `state` here. An unreachable forge is an empty list, not an error: the
|
|
283
|
+
* PR column is one signal among several and a first pass may run offline.
|
|
284
|
+
*/
|
|
285
|
+
function readForgePrs(cwd, run = execFileSync) {
|
|
286
|
+
const origin = tryJson(run, cwd, "origin", [
|
|
287
|
+
"pr",
|
|
288
|
+
"list",
|
|
289
|
+
"--author",
|
|
290
|
+
"@me",
|
|
291
|
+
"--state",
|
|
292
|
+
"all",
|
|
293
|
+
"--limit",
|
|
294
|
+
"1000",
|
|
295
|
+
"--json",
|
|
296
|
+
"number,status,headRef"
|
|
297
|
+
]);
|
|
298
|
+
if (origin !== void 0) return normalizeOrigin(origin);
|
|
299
|
+
const gh = tryJson(run, cwd, "gh", [
|
|
300
|
+
"pr",
|
|
301
|
+
"list",
|
|
302
|
+
"--author",
|
|
303
|
+
"@me",
|
|
304
|
+
"--state",
|
|
305
|
+
"all",
|
|
306
|
+
"--limit",
|
|
307
|
+
"1000",
|
|
308
|
+
"--json",
|
|
309
|
+
"number,state,headRefName"
|
|
310
|
+
]);
|
|
311
|
+
if (gh !== void 0) return normalizeGh(gh);
|
|
312
|
+
return [];
|
|
313
|
+
}
|
|
314
|
+
function tryJson(run, cwd, command, args) {
|
|
315
|
+
try {
|
|
316
|
+
const out = run(command, args, {
|
|
317
|
+
cwd,
|
|
318
|
+
encoding: "utf8",
|
|
319
|
+
stdio: [
|
|
320
|
+
"ignore",
|
|
321
|
+
"pipe",
|
|
322
|
+
"ignore"
|
|
323
|
+
],
|
|
324
|
+
maxBuffer: 33554432
|
|
325
|
+
});
|
|
326
|
+
return JSON.parse(out);
|
|
327
|
+
} catch {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/** `[{number, status, headRef}]` from the `origin` CLI. */
|
|
332
|
+
function normalizeOrigin(raw) {
|
|
333
|
+
if (!Array.isArray(raw)) return [];
|
|
334
|
+
const prs = [];
|
|
335
|
+
for (const entry of raw) {
|
|
336
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
337
|
+
const record = entry;
|
|
338
|
+
const number = record["number"];
|
|
339
|
+
const head = record["headRef"];
|
|
340
|
+
if (typeof number !== "number" || typeof head !== "string") continue;
|
|
341
|
+
prs.push({
|
|
342
|
+
number,
|
|
343
|
+
state: String(record["status"] ?? ""),
|
|
344
|
+
head
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return prs;
|
|
348
|
+
}
|
|
349
|
+
/** `[{number, state, headRefName}]` from the `gh` CLI, with a lowercase state. */
|
|
350
|
+
function normalizeGh(raw) {
|
|
351
|
+
if (!Array.isArray(raw)) return [];
|
|
352
|
+
const prs = [];
|
|
353
|
+
for (const entry of raw) {
|
|
354
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
355
|
+
const record = entry;
|
|
356
|
+
const number = record["number"];
|
|
357
|
+
const head = record["headRefName"];
|
|
358
|
+
if (typeof number !== "number" || typeof head !== "string") continue;
|
|
359
|
+
prs.push({
|
|
360
|
+
number,
|
|
361
|
+
state: String(record["state"] ?? "").toLowerCase(),
|
|
362
|
+
head
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
return prs;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Pick the base ref when the caller gave no `-b`.
|
|
369
|
+
*
|
|
370
|
+
* `origin/HEAD`, then `origin/main`, then `origin/master`, matching the shell
|
|
371
|
+
* script. An empty result means no base exists and the caller must pass one.
|
|
372
|
+
*/
|
|
373
|
+
function resolveBase(repo) {
|
|
374
|
+
const candidates = [];
|
|
375
|
+
const head = git(repo, [
|
|
376
|
+
"symbolic-ref",
|
|
377
|
+
"--short",
|
|
378
|
+
"refs/remotes/origin/HEAD"
|
|
379
|
+
]);
|
|
380
|
+
if (head !== void 0) candidates.push(head.trim());
|
|
381
|
+
candidates.push("origin/main", "origin/master");
|
|
382
|
+
return candidates.find((candidate) => candidate !== "" && git(repo, [
|
|
383
|
+
"rev-parse",
|
|
384
|
+
"--verify",
|
|
385
|
+
"--quiet",
|
|
386
|
+
`${candidate}^{commit}`
|
|
387
|
+
]) !== void 0);
|
|
388
|
+
}
|
|
389
|
+
/** Build one row. Every column is a pure function so each has a unit test. */
|
|
390
|
+
function buildRow(worktree, context) {
|
|
391
|
+
const head = git(worktree, ["rev-parse", "HEAD"])?.trim();
|
|
392
|
+
const headTimestamp = Number(git(worktree, [
|
|
393
|
+
"log",
|
|
394
|
+
"-1",
|
|
395
|
+
"--format=%ct",
|
|
396
|
+
"HEAD"
|
|
397
|
+
])?.trim() ?? 0);
|
|
398
|
+
const isAncestor = head !== void 0 && head !== "" && head !== "0" ? git(worktree, [
|
|
399
|
+
"merge-base",
|
|
400
|
+
"--is-ancestor",
|
|
401
|
+
head,
|
|
402
|
+
context.base
|
|
403
|
+
]) !== void 0 : false;
|
|
404
|
+
const dirty = dirtyColumn(git(worktree, ["status", "--porcelain"]) ?? "");
|
|
405
|
+
const branchText = git(worktree, [
|
|
406
|
+
"symbolic-ref",
|
|
407
|
+
"--quiet",
|
|
408
|
+
"--short",
|
|
409
|
+
"HEAD"
|
|
410
|
+
]);
|
|
411
|
+
const branch = branchText === void 0 ? "" : branchText.trim();
|
|
412
|
+
const hasRemote = branch !== "" && git(worktree, [
|
|
413
|
+
"show-ref",
|
|
414
|
+
"--verify",
|
|
415
|
+
"--quiet",
|
|
416
|
+
`refs/remotes/origin/${branch}`
|
|
417
|
+
]) !== void 0;
|
|
418
|
+
const remoteSha = hasRemote ? git(worktree, ["rev-parse", `origin/${branch}`])?.trim() : void 0;
|
|
419
|
+
const aheadText = hasRemote ? git(worktree, [
|
|
420
|
+
"rev-list",
|
|
421
|
+
"--count",
|
|
422
|
+
`origin/${branch}..HEAD`
|
|
423
|
+
]) : void 0;
|
|
424
|
+
const remote = remoteColumn({
|
|
425
|
+
branch: branch === "" ? void 0 : branch,
|
|
426
|
+
hasRemote,
|
|
427
|
+
remoteSha,
|
|
428
|
+
head: head?.trim(),
|
|
429
|
+
aheadCount: aheadText === void 0 ? void 0 : Number(aheadText.trim())
|
|
430
|
+
});
|
|
431
|
+
const pr = prColumn(branch === "" ? void 0 : branch, context.prs);
|
|
432
|
+
const session = lastSession(worktree, context.sessions);
|
|
433
|
+
const usageKnown = session !== void 0;
|
|
434
|
+
const sessionsProvided = context.sessions !== void 0;
|
|
435
|
+
const bucket = bucketFor({
|
|
436
|
+
dirty,
|
|
437
|
+
pr,
|
|
438
|
+
merged: mergedColumn(isAncestor),
|
|
439
|
+
usageKnown,
|
|
440
|
+
usedRecently: isRecent(session, context.now)
|
|
441
|
+
});
|
|
442
|
+
return {
|
|
443
|
+
size: sizeColumn(worktree),
|
|
444
|
+
age: ageColumn(headTimestamp, context.now),
|
|
445
|
+
merged: mergedColumn(isAncestor),
|
|
446
|
+
dirty,
|
|
447
|
+
remote,
|
|
448
|
+
pr,
|
|
449
|
+
lastChat: lastChatColumn(session),
|
|
450
|
+
bucket,
|
|
451
|
+
note: usageNote({
|
|
452
|
+
usageKnown,
|
|
453
|
+
sessionsProvided
|
|
454
|
+
}),
|
|
455
|
+
worktree
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Why a row's usage signal is what it is.
|
|
460
|
+
*
|
|
461
|
+
* Both "nobody supplied a session list" and "a list was supplied but it never
|
|
462
|
+
* mentioned this worktree" produce the `unknown-usage` bucket, and the caller has
|
|
463
|
+
* to fix a different thing in each case: build the list, or widen it. Collapsing
|
|
464
|
+
* them would lose that distinction, which is the only diagnostic the row carries.
|
|
465
|
+
*/
|
|
466
|
+
function usageNote(input) {
|
|
467
|
+
if (input.usageKnown) return void 0;
|
|
468
|
+
return input.sessionsProvided ? "no session in the supplied list touched this worktree" : "no session list supplied";
|
|
469
|
+
}
|
|
470
|
+
/** Sort rows by SIZE descending, preserving the original order on a tie. */
|
|
471
|
+
function sortRows(rows) {
|
|
472
|
+
const sizeOf = (row) => {
|
|
473
|
+
const match = /^([\d.]+)([BKMGTP]?)$/.exec(row.size);
|
|
474
|
+
if (match === null) return 0;
|
|
475
|
+
return Number(match[1]) * 1024 ** ({
|
|
476
|
+
"": 0,
|
|
477
|
+
B: 0,
|
|
478
|
+
K: 1,
|
|
479
|
+
M: 2,
|
|
480
|
+
G: 3,
|
|
481
|
+
T: 4,
|
|
482
|
+
P: 5
|
|
483
|
+
}[match[2]] ?? 0);
|
|
484
|
+
};
|
|
485
|
+
return rows.map((row, index) => ({
|
|
486
|
+
row,
|
|
487
|
+
index
|
|
488
|
+
})).sort((left, right) => sizeOf(right.row) - sizeOf(left.row) || left.index - right.index).map((entry) => entry.row);
|
|
489
|
+
}
|
|
490
|
+
/** Render the whole table, header included. */
|
|
491
|
+
function renderTable(rows, base) {
|
|
492
|
+
const lines = [`# base: ${base}`, HEADER.join(" ")];
|
|
493
|
+
for (const row of rows) lines.push([
|
|
494
|
+
row.size,
|
|
495
|
+
row.age,
|
|
496
|
+
row.merged,
|
|
497
|
+
row.dirty,
|
|
498
|
+
row.remote,
|
|
499
|
+
row.pr,
|
|
500
|
+
row.lastChat,
|
|
501
|
+
row.bucket,
|
|
502
|
+
row.worktree
|
|
503
|
+
].join(" "));
|
|
504
|
+
const notes = rows.filter((row) => row.note !== void 0);
|
|
505
|
+
if (notes.length > 0) {
|
|
506
|
+
lines.push("# usage signal unknown for:");
|
|
507
|
+
for (const row of notes) lines.push(`# ${row.worktree}: ${row.note}`);
|
|
508
|
+
}
|
|
509
|
+
return `${lines.join("\n")}\n`;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Read and validate the `--sessions` file.
|
|
513
|
+
*
|
|
514
|
+
* The shell version required a JSON array via `jq -e 'type == "array"'` and pinned
|
|
515
|
+
* the path before changing directory. Both hold here: the path is resolved before
|
|
516
|
+
* any `git -C`, and a non-array is an error rather than an empty list, because a
|
|
517
|
+
* silently ignored usage signal would quietly turn every bucket into
|
|
518
|
+
* `unknown-usage`.
|
|
519
|
+
*
|
|
520
|
+
* Every element is checked, not filtered. The array is a projection the caller
|
|
521
|
+
* builds from DSH session search results, so a malformed entry is a real mistake; the
|
|
522
|
+
* previous `filter` dropped such entries and let the row fall through to
|
|
523
|
+
* `unknown-usage`, which is exactly the silent data loss the paragraph above says
|
|
524
|
+
* this function refuses to do.
|
|
525
|
+
*/
|
|
526
|
+
function readSessionsFile(path) {
|
|
527
|
+
const absolute = isAbsolute(path) ? path : resolve(process.cwd(), path);
|
|
528
|
+
let text;
|
|
529
|
+
try {
|
|
530
|
+
text = readFileSync(absolute, "utf8");
|
|
531
|
+
} catch {
|
|
532
|
+
throw new AuditError(`cannot read sessions file: ${path}`);
|
|
533
|
+
}
|
|
534
|
+
let parsed;
|
|
535
|
+
try {
|
|
536
|
+
parsed = JSON.parse(text);
|
|
537
|
+
} catch {
|
|
538
|
+
throw new AuditError(`sessions file must be ${SESSIONS_SHAPE}: ${path}`);
|
|
539
|
+
}
|
|
540
|
+
if (!Array.isArray(parsed)) throw new AuditError(`sessions file must be ${SESSIONS_SHAPE}: ${path}. Build it from DSH session search results; a single result is not an array.`);
|
|
541
|
+
return parsed.map((entry, index) => {
|
|
542
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) throw new AuditError(`sessions[${index}] must be an object, got ${JSON.stringify(entry)?.slice(0, 60) ?? "undefined"}`);
|
|
543
|
+
const { sessionId, cwd, lastEventTime } = entry;
|
|
544
|
+
if (typeof sessionId !== "string" || sessionId === "") throw new AuditError(`sessions[${index}].sessionId must be a non-empty string`);
|
|
545
|
+
if (cwd !== void 0 && cwd !== null && typeof cwd !== "string") throw new AuditError(`sessions[${index}].cwd must be a string or null`);
|
|
546
|
+
if (lastEventTime !== void 0 && typeof lastEventTime !== "number") throw new AuditError(`sessions[${index}].lastEventTime must be a number`);
|
|
547
|
+
return {
|
|
548
|
+
sessionId,
|
|
549
|
+
...cwd === void 0 ? {} : { cwd },
|
|
550
|
+
...lastEventTime === void 0 ? {} : { lastEventTime }
|
|
551
|
+
};
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
/** Usage text, kept close to the shell script's help. */
|
|
555
|
+
const USAGE = [
|
|
556
|
+
"Usage: worktree-audit.mjs [-b|--base <ref>] [-s|--sessions <json>] [repo-path]",
|
|
557
|
+
" repo-path defaults to the current repo.",
|
|
558
|
+
" -b sets the ref the MERGED column checks against (e.g. origin/integration).",
|
|
559
|
+
" Without it the base is origin/HEAD, then origin/main, then origin/master.",
|
|
560
|
+
" -s takes a JSON array you build from DSH session search results, of",
|
|
561
|
+
" {\"sessionId\",\"cwd\",\"lastEventTime\"}. This script never reads the session",
|
|
562
|
+
" store, so without it LAST_CHAT is \"-\" and the bucket is unknown-usage.",
|
|
563
|
+
""
|
|
564
|
+
].join("\n");
|
|
565
|
+
/**
|
|
566
|
+
* Parse the command line.
|
|
567
|
+
*
|
|
568
|
+
* An unknown option and a missing argument value both exit 2, as the shell script
|
|
569
|
+
* did; an unreadable sessions file and a missing base exit 1.
|
|
570
|
+
*/
|
|
571
|
+
function parseArgs(argv) {
|
|
572
|
+
let base;
|
|
573
|
+
let sessions;
|
|
574
|
+
const positional = [];
|
|
575
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
576
|
+
const argument = argv[index];
|
|
577
|
+
if (argument === "-h" || argument === "--help") return { help: true };
|
|
578
|
+
if (argument === "-b" || argument === "--base") {
|
|
579
|
+
const value = argv[index + 1];
|
|
580
|
+
if (value === void 0) throw new AuditError(`${argument} needs a ref`, 2);
|
|
581
|
+
base = value;
|
|
582
|
+
index += 1;
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
if (argument === "-s" || argument === "--sessions") {
|
|
586
|
+
const value = argv[index + 1];
|
|
587
|
+
if (value === void 0) throw new AuditError(`${argument} needs a JSON file`, 2);
|
|
588
|
+
sessions = value;
|
|
589
|
+
index += 1;
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (argument.startsWith("-")) throw new AuditError(`unknown option: ${argument}`, 2);
|
|
593
|
+
positional.push(argument);
|
|
594
|
+
}
|
|
595
|
+
if (positional.length > 1) throw new AuditError(`expected at most one repo path, received ${positional.length}`, 2);
|
|
596
|
+
return {
|
|
597
|
+
...base === void 0 ? {} : { base },
|
|
598
|
+
...sessions === void 0 ? {} : { sessions },
|
|
599
|
+
...positional[0] === void 0 ? {} : { repo: positional[0] },
|
|
600
|
+
help: false
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
/** Run the audit. Returns the process exit code. */
|
|
604
|
+
function main(argv, io = {
|
|
605
|
+
stdout: (value) => process.stdout.write(value),
|
|
606
|
+
stderr: (value) => process.stderr.write(value)
|
|
607
|
+
}, options = {}) {
|
|
608
|
+
try {
|
|
609
|
+
const args = parseArgs(argv);
|
|
610
|
+
if (args.help) {
|
|
611
|
+
io.stdout(USAGE);
|
|
612
|
+
return 0;
|
|
613
|
+
}
|
|
614
|
+
const sessions = args.sessions === void 0 ? void 0 : readSessionsFile(args.sessions);
|
|
615
|
+
const cwd = options.cwd ?? process.cwd();
|
|
616
|
+
const repo = args.repo ?? gitHere(cwd, ["rev-parse", "--show-toplevel"])?.trim() ?? (() => {
|
|
617
|
+
throw new AuditError("not in a git repo; pass a repo path");
|
|
618
|
+
})();
|
|
619
|
+
if (git(repo, ["rev-parse", "--git-dir"]) === void 0) throw new AuditError(`not a git repo: ${repo}`);
|
|
620
|
+
const paths = worktreePaths(repo);
|
|
621
|
+
const mainWorktree = paths[0];
|
|
622
|
+
if (mainWorktree === void 0) throw new AuditError(`not a git repo: ${repo}`);
|
|
623
|
+
let base = args.base;
|
|
624
|
+
if (base === void 0) {
|
|
625
|
+
base = resolveBase(repo);
|
|
626
|
+
if (base === void 0) throw new AuditError("no base ref (origin/HEAD, origin/main, origin/master all missing); pass -b <ref>");
|
|
627
|
+
io.stderr(`warn: no -b given; MERGED checks against ${base}\n`);
|
|
628
|
+
}
|
|
629
|
+
if (base.startsWith("origin/")) {
|
|
630
|
+
if (git(repo, [
|
|
631
|
+
"fetch",
|
|
632
|
+
"origin",
|
|
633
|
+
`+refs/heads/${base.slice(7)}:refs/remotes/${base}`,
|
|
634
|
+
"--quiet"
|
|
635
|
+
]) === void 0) io.stderr(`warn: could not fetch ${base}; merged column may be stale\n`);
|
|
636
|
+
}
|
|
637
|
+
if (git(repo, [
|
|
638
|
+
"rev-parse",
|
|
639
|
+
"--verify",
|
|
640
|
+
"--quiet",
|
|
641
|
+
`${base}^{commit}`
|
|
642
|
+
]) === void 0) throw new AuditError(`base ref not found: ${base}`);
|
|
643
|
+
const prs = readForgePrs(repo);
|
|
644
|
+
const now = options.now ?? Math.floor(Date.now() / 1e3);
|
|
645
|
+
const context = {
|
|
646
|
+
base,
|
|
647
|
+
repo,
|
|
648
|
+
mainWorktree,
|
|
649
|
+
prs,
|
|
650
|
+
sessions,
|
|
651
|
+
now
|
|
652
|
+
};
|
|
653
|
+
const rows = paths.filter((path) => path !== mainWorktree).map((path) => buildRow(path, context));
|
|
654
|
+
io.stdout(renderTable(sortRows(rows), base));
|
|
655
|
+
return 0;
|
|
656
|
+
} catch (error) {
|
|
657
|
+
if (error instanceof AuditError) {
|
|
658
|
+
io.stderr(`error: ${error.message}\n`);
|
|
659
|
+
return error.exitCode;
|
|
660
|
+
}
|
|
661
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
662
|
+
io.stderr(`error: ${message}\n`);
|
|
663
|
+
return 1;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
const entryPoint = process.argv[1];
|
|
667
|
+
if (entryPoint !== void 0 && resolve(entryPoint) === fileURLToPath(import.meta.url)) process.exitCode = main(process.argv.slice(2));
|
|
668
|
+
export { AuditError, HEADER, RECENT_DAYS, UNKNOWN_USAGE_BUCKET, USAGE, ageColumn, bucketFor, buildRow, dirtyColumn, formatSize, isRecent, lastChatColumn, lastSession, main, mergedColumn, normalizeGh, normalizeOrigin, parseArgs, prColumn, readForgePrs, readSessionsFile, remoteColumn, renderTable, resolveBase, sameOrInside, sizeColumn, sortRows, usageNote, worktreePaths };
|