dsh-pstack 0.1.0 → 0.2.0-beta.1
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 +1 -1
- package/README.md +92 -117
- package/assets/agents/comment-sicko.md +1 -1
- package/assets/agents/poteto-agent.md +2 -2
- package/assets/docs/guide/01-setup.md +9 -9
- package/assets/docs/guide/02-poteto-mode.md +4 -4
- package/assets/docs/guide/05-build-and-clean.md +3 -3
- package/assets/docs/guide/06-verify-and-ship.md +2 -2
- package/assets/docs/guide/07-overnight.md +10 -12
- package/assets/docs/guide/09-make-it-yours.md +2 -6
- package/assets/docs/guide/10-recipes-and-pitfalls.md +3 -3
- package/assets/docs/guide/README.md +1 -1
- package/assets/docs/guide/images/design.jpg +0 -0
- package/assets/docs/guide/images/overnight.jpg +0 -0
- package/assets/docs/guide/images/recipes.jpg +0 -0
- package/assets/docs/guide/images/router.jpg +0 -0
- package/assets/docs/guide/images/understanding.jpg +0 -0
- package/assets/docs/guide/images/verification.jpg +0 -0
- package/assets/docs/harness/dsh.md +60 -65
- package/assets/docs/pstack-models.example.md +40 -9
- package/assets/skills/architect/SKILL.md +12 -12
- package/assets/skills/arena/SKILL.md +11 -11
- package/assets/skills/automate-me/SKILL.md +15 -14
- package/assets/skills/benchmark-checklist/SKILL.md +0 -2
- package/assets/skills/blast-radius/SKILL.md +5 -5
- package/assets/skills/bro/SKILL.md +0 -2
- package/assets/skills/correct/SKILL.md +0 -2
- package/assets/skills/create-formal-verification-skill/SKILL.md +2 -2
- package/assets/skills/create-verification-skill/SKILL.md +2 -2
- package/assets/skills/figure-it-out/SKILL.md +13 -13
- package/assets/skills/how/SKILL.md +6 -6
- package/assets/skills/how/references/explainer-prompt.md +1 -1
- package/assets/skills/in-your-own-words/SKILL.md +0 -2
- package/assets/skills/interrogate/SKILL.md +9 -7
- package/assets/skills/interrogate/references/rubric.md +1 -1
- package/assets/skills/ketchup/SKILL.md +4 -4
- package/assets/skills/maintain-formal-verification-skill/SKILL.md +2 -2
- package/assets/skills/maintain-verification-skill/SKILL.md +3 -3
- package/assets/skills/no-comments/SKILL.md +7 -7
- package/assets/skills/poteto-help/SKILL.md +32 -31
- package/assets/skills/poteto-help/references/prompting.md +1 -1
- package/assets/skills/poteto-help/references/recipes.md +1 -1
- package/assets/skills/poteto-mode/SKILL.md +17 -19
- package/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +3 -3
- package/assets/skills/poteto-mode/playbooks/autonomous-run.md +6 -4
- package/assets/skills/poteto-mode/playbooks/autopilot-full.md +6 -6
- package/assets/skills/poteto-mode/playbooks/autopilot-stack.md +2 -2
- package/assets/skills/poteto-mode/playbooks/babysit.md +3 -3
- package/assets/skills/poteto-mode/playbooks/bug-fix.md +7 -7
- package/assets/skills/poteto-mode/playbooks/eval.md +3 -3
- package/assets/skills/poteto-mode/playbooks/feature.md +8 -8
- package/assets/skills/poteto-mode/playbooks/hillclimb.md +3 -3
- package/assets/skills/poteto-mode/playbooks/investigation.md +2 -2
- package/assets/skills/poteto-mode/playbooks/multi-phase-plan.md +21 -21
- package/assets/skills/poteto-mode/playbooks/opening-a-pr.md +6 -6
- package/assets/skills/poteto-mode/playbooks/orchestrate.md +13 -13
- package/assets/skills/poteto-mode/playbooks/pause-safely.md +2 -2
- package/assets/skills/poteto-mode/playbooks/perf-issue.md +6 -6
- package/assets/skills/poteto-mode/playbooks/prototype.md +4 -4
- package/assets/skills/poteto-mode/playbooks/refactoring.md +9 -9
- package/assets/skills/poteto-mode/playbooks/runtime-forensics.md +3 -3
- package/assets/skills/poteto-mode/playbooks/session-pickup.md +2 -2
- package/assets/skills/poteto-mode/playbooks/shipping.md +2 -2
- package/assets/skills/poteto-mode/playbooks/trace-forensics.md +2 -2
- package/assets/skills/poteto-mode/playbooks/visual-parity.md +5 -5
- package/assets/skills/poteto-mode/playbooks/worktree-cleanup.md +14 -6
- package/assets/skills/poteto-mode/scripts/check-plan.mjs +85 -70
- package/assets/skills/poteto-mode/scripts/check-plan.test.mjs +343 -0
- package/assets/skills/poteto-mode/scripts/orch/orch.mjs +4282 -0
- package/assets/skills/poteto-mode/scripts/orch/orch.test.mjs +2042 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +5101 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/github.test.mjs +1177 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +1441 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +4601 -0
- package/assets/skills/poteto-mode/scripts/worktree-audit.mjs +675 -0
- package/assets/skills/principle-attack-the-premise/SKILL.md +0 -2
- package/assets/skills/principle-boundary-discipline/SKILL.md +0 -2
- package/assets/skills/principle-build-the-lever/SKILL.md +0 -3
- package/assets/skills/principle-encode-lessons-in-structure/SKILL.md +0 -2
- package/assets/skills/principle-exhaust-the-design-space/SKILL.md +0 -2
- package/assets/skills/principle-experience-first/SKILL.md +0 -2
- package/assets/skills/principle-explain-the-number/SKILL.md +0 -2
- package/assets/skills/principle-fix-root-causes/SKILL.md +0 -2
- package/assets/skills/principle-foundational-thinking/SKILL.md +0 -2
- package/assets/skills/principle-guard-the-context-window/SKILL.md +0 -2
- package/assets/skills/principle-laziness-protocol/SKILL.md +0 -2
- package/assets/skills/principle-make-operations-idempotent/SKILL.md +0 -2
- package/assets/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +0 -2
- package/assets/skills/principle-minimize-reader-load/SKILL.md +0 -2
- package/assets/skills/principle-model-the-domain/SKILL.md +0 -2
- package/assets/skills/principle-never-block-on-the-human/SKILL.md +0 -2
- package/assets/skills/principle-outcome-oriented-execution/SKILL.md +0 -2
- package/assets/skills/principle-prove-it-works/SKILL.md +2 -2
- package/assets/skills/principle-redesign-from-first-principles/SKILL.md +0 -2
- package/assets/skills/principle-separate-before-serializing-shared-state/SKILL.md +0 -2
- package/assets/skills/principle-sequence-verifiable-units/SKILL.md +0 -2
- package/assets/skills/principle-subtract-before-you-add/SKILL.md +0 -2
- package/assets/skills/principle-test-behavior-not-implementation/SKILL.md +0 -2
- package/assets/skills/principle-type-system-discipline/SKILL.md +0 -2
- package/assets/skills/recall/SKILL.md +9 -9
- package/assets/skills/reflect/SKILL.md +20 -21
- package/assets/skills/reflect/references/divergent-reviewer.md +4 -4
- package/assets/skills/reflect/references/judgment-reviewer.md +2 -2
- package/assets/skills/reflect/references/synthesizer.md +6 -6
- package/assets/skills/reflect/references/tooling-reviewer.md +2 -2
- package/assets/skills/setup-pstack/SKILL.md +28 -37
- package/assets/skills/sharpen/SKILL.md +0 -2
- package/assets/skills/show-me-your-work/SKILL.md +9 -9
- package/assets/skills/show-me-your-work/scripts/log.mjs +108 -0
- package/assets/skills/swarm/SKILL.md +8 -6
- package/assets/skills/tdd/SKILL.md +0 -2
- package/assets/skills/teach/SKILL.md +2 -2
- package/assets/skills/technical-writing/SKILL.md +2 -2
- package/assets/skills/typescript-best-practices/SKILL.md +6 -4
- package/assets/skills/unslop/SKILL.md +0 -2
- package/assets/skills/why/SKILL.md +9 -9
- package/cordis.patch.yml +33 -14
- package/dist/frontmatter-BQX3ekwP.mjs +229 -0
- package/dist/frontmatter-BQX3ekwP.mjs.map +1 -0
- package/dist/history.d.mts +429 -0
- package/dist/history.mjs +1129 -0
- package/dist/history.mjs.map +1 -0
- package/dist/host-contract-o47JH_r8.d.mts +527 -0
- package/dist/index.d.mts +273 -11
- package/dist/index.mjs +248 -148
- package/dist/index.mjs.map +1 -1
- package/dist/paths-D_JlVmBk.mjs +42 -0
- package/dist/paths-D_JlVmBk.mjs.map +1 -0
- package/dist/script.d.mts +193 -0
- package/dist/script.mjs +782 -0
- package/dist/script.mjs.map +1 -0
- package/dist/subagent.d.mts +306 -100
- package/dist/subagent.mjs +1309 -343
- package/dist/subagent.mjs.map +1 -1
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +55 -41
- package/static/icon.png +0 -0
- 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/show-me-your-work/scripts/log.sh +0 -42
- package/dist/LICENSE +0 -22
- 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/02-poteto-mode.md +0 -139
- 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/05-build-and-clean.md +0 -83
- package/dist/assets/docs/guide/06-verify-and-ship.md +0 -152
- 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/09-make-it-yours.md +0 -100
- 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/benchmark-checklist/SKILL.md +0 -41
- package/dist/assets/skills/blast-radius/SKILL.md +0 -52
- package/dist/assets/skills/bro/SKILL.md +0 -9
- package/dist/assets/skills/correct/SKILL.md +0 -34
- 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/SKILL.md +0 -38
- 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/maintain-verification-skill/SKILL.md +0 -41
- 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-help/references/prompting.md +0 -51
- package/dist/assets/skills/poteto-help/references/recipes.md +0 -51
- package/dist/assets/skills/poteto-mode/SKILL.md +0 -153
- package/dist/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +0 -12
- package/dist/assets/skills/poteto-mode/playbooks/autonomous-run.md +0 -13
- 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/eval.md +0 -25
- 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/opening-a-pr.md +0 -34
- 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/perf-issue.md +0 -25
- package/dist/assets/skills/poteto-mode/playbooks/prototype.md +0 -14
- package/dist/assets/skills/poteto-mode/playbooks/refactoring.md +0 -16
- 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/shipping.md +0 -12
- 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/playbooks/worktree-cleanup.md +0 -12
- 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/check-plan.test.ts +0 -96
- 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-explain-the-number/SKILL.md +0 -19
- package/dist/assets/skills/principle-fix-root-causes/SKILL.md +0 -23
- package/dist/assets/skills/principle-foundational-thinking/SKILL.md +0 -23
- package/dist/assets/skills/principle-guard-the-context-window/SKILL.md +0 -14
- package/dist/assets/skills/principle-laziness-protocol/SKILL.md +0 -12
- package/dist/assets/skills/principle-make-operations-idempotent/SKILL.md +0 -24
- package/dist/assets/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +0 -16
- 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-outcome-oriented-execution/SKILL.md +0 -23
- package/dist/assets/skills/principle-prove-it-works/SKILL.md +0 -24
- package/dist/assets/skills/principle-redesign-from-first-principles/SKILL.md +0 -18
- package/dist/assets/skills/principle-separate-before-serializing-shared-state/SKILL.md +0 -18
- package/dist/assets/skills/principle-sequence-verifiable-units/SKILL.md +0 -19
- package/dist/assets/skills/principle-subtract-before-you-add/SKILL.md +0 -20
- 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/recall/SKILL.md +0 -37
- 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/reflect/references/tooling-reviewer.md +0 -48
- 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/SKILL.md +0 -100
- 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/paths-Bbai4CaT.mjs +0 -16
- package/dist/paths-Bbai4CaT.mjs.map +0 -1
package/dist/script.mjs
ADDED
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
import { t as ASSETS_ROOT } from "./paths-D_JlVmBk.mjs";
|
|
2
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
//#region scripts/runtime-manifest.ts
|
|
5
|
+
/** The directory holding the authored runtime sources. */
|
|
6
|
+
const AUTHORED_ROOT = "scripts/runtime/";
|
|
7
|
+
/** `TEST_SUPPORT` as entry sources, so a test entry declares what it imports. */
|
|
8
|
+
const TEST_SUPPORT_ROOTS = [{
|
|
9
|
+
kind: "authored",
|
|
10
|
+
path: `${AUTHORED_ROOT}tests/expect.ts`
|
|
11
|
+
}, {
|
|
12
|
+
kind: "authored",
|
|
13
|
+
path: `${AUTHORED_ROOT}tests/support.ts`
|
|
14
|
+
}];
|
|
15
|
+
const upstreamScript = (path) => ({
|
|
16
|
+
kind: "upstream",
|
|
17
|
+
path: `skills/poteto-mode/scripts/${path}`
|
|
18
|
+
});
|
|
19
|
+
/** CLI artifacts, in the order the reviews and the prose name them. */
|
|
20
|
+
const RUNTIME_CLIS = [
|
|
21
|
+
{
|
|
22
|
+
id: "check-plan",
|
|
23
|
+
kind: "cli",
|
|
24
|
+
entry: upstreamScript("check-plan.mjs"),
|
|
25
|
+
out: "check-plan.mjs",
|
|
26
|
+
assetPath: "skills/poteto-mode/scripts/check-plan.mjs",
|
|
27
|
+
executable: true,
|
|
28
|
+
sources: [upstreamScript("check-plan.mjs")],
|
|
29
|
+
summary: "plan-shape checker"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "orch",
|
|
33
|
+
kind: "cli",
|
|
34
|
+
entry: upstreamScript("orch/orch.ts"),
|
|
35
|
+
out: "orch/orch.mjs",
|
|
36
|
+
assetPath: "skills/poteto-mode/scripts/orch/orch.mjs",
|
|
37
|
+
executable: true,
|
|
38
|
+
sources: [upstreamScript("orch/orch.ts"), upstreamScript("orch/store.ts")],
|
|
39
|
+
summary: "orchestration bookkeeping store"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "watch-pr",
|
|
43
|
+
kind: "cli",
|
|
44
|
+
entry: upstreamScript("watch-pr/watch-pr"),
|
|
45
|
+
out: "watch-pr/watch-pr.mjs",
|
|
46
|
+
assetPath: "skills/poteto-mode/scripts/watch-pr/watch-pr.mjs",
|
|
47
|
+
executable: true,
|
|
48
|
+
sources: [
|
|
49
|
+
upstreamScript("watch-pr/watch-pr"),
|
|
50
|
+
upstreamScript("watch-pr/cli.ts"),
|
|
51
|
+
upstreamScript("watch-pr/github.ts"),
|
|
52
|
+
upstreamScript("watch-pr/policy.ts"),
|
|
53
|
+
upstreamScript("watch-pr/render.ts"),
|
|
54
|
+
upstreamScript("watch-pr/types.ts")
|
|
55
|
+
],
|
|
56
|
+
summary: "pull-request watcher"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "worktree-audit",
|
|
60
|
+
kind: "cli",
|
|
61
|
+
entry: {
|
|
62
|
+
kind: "authored",
|
|
63
|
+
path: `${AUTHORED_ROOT}worktree-audit.ts`
|
|
64
|
+
},
|
|
65
|
+
out: "worktree-audit.mjs",
|
|
66
|
+
assetPath: "skills/poteto-mode/scripts/worktree-audit.mjs",
|
|
67
|
+
executable: true,
|
|
68
|
+
sources: [{
|
|
69
|
+
kind: "authored",
|
|
70
|
+
path: `${AUTHORED_ROOT}worktree-audit.ts`
|
|
71
|
+
}],
|
|
72
|
+
summary: "read-only worktree audit"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "log",
|
|
76
|
+
kind: "cli",
|
|
77
|
+
entry: {
|
|
78
|
+
kind: "authored",
|
|
79
|
+
path: `${AUTHORED_ROOT}log.ts`
|
|
80
|
+
},
|
|
81
|
+
out: "log.mjs",
|
|
82
|
+
assetPath: "skills/show-me-your-work/scripts/log.mjs",
|
|
83
|
+
executable: true,
|
|
84
|
+
sources: [{
|
|
85
|
+
kind: "authored",
|
|
86
|
+
path: `${AUTHORED_ROOT}log.ts`
|
|
87
|
+
}],
|
|
88
|
+
summary: "decision-log row appender"
|
|
89
|
+
}
|
|
90
|
+
];
|
|
91
|
+
/** Test bundles, run by `node --test` from a copy with no install step. */
|
|
92
|
+
const RUNTIME_TESTS = [
|
|
93
|
+
{
|
|
94
|
+
id: "check-plan-test",
|
|
95
|
+
kind: "test",
|
|
96
|
+
entry: upstreamScript("check-plan.test.ts"),
|
|
97
|
+
out: "check-plan.test.mjs",
|
|
98
|
+
assetPath: "skills/poteto-mode/scripts/check-plan.test.mjs",
|
|
99
|
+
executable: false,
|
|
100
|
+
sources: [upstreamScript("check-plan.test.ts")],
|
|
101
|
+
summary: "check-plan tests"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "orch-test",
|
|
105
|
+
kind: "test",
|
|
106
|
+
entry: upstreamScript("orch/orch.test.ts"),
|
|
107
|
+
out: "orch/orch.test.mjs",
|
|
108
|
+
assetPath: "skills/poteto-mode/scripts/orch/orch.test.mjs",
|
|
109
|
+
executable: false,
|
|
110
|
+
sources: [
|
|
111
|
+
upstreamScript("orch/orch.test.ts"),
|
|
112
|
+
upstreamScript("orch/store.ts"),
|
|
113
|
+
...TEST_SUPPORT_ROOTS
|
|
114
|
+
],
|
|
115
|
+
summary: "orch tests"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "watch-pr-cli-test",
|
|
119
|
+
kind: "test",
|
|
120
|
+
entry: upstreamScript("watch-pr/cli.test.ts"),
|
|
121
|
+
out: "watch-pr/cli.test.mjs",
|
|
122
|
+
assetPath: "skills/poteto-mode/scripts/watch-pr/cli.test.mjs",
|
|
123
|
+
executable: false,
|
|
124
|
+
sources: [
|
|
125
|
+
upstreamScript("watch-pr/cli.test.ts"),
|
|
126
|
+
upstreamScript("watch-pr/cli.ts"),
|
|
127
|
+
upstreamScript("watch-pr/github.ts"),
|
|
128
|
+
upstreamScript("watch-pr/policy.ts"),
|
|
129
|
+
upstreamScript("watch-pr/render.ts"),
|
|
130
|
+
upstreamScript("watch-pr/types.ts"),
|
|
131
|
+
upstreamScript("watch-pr/fakes.test-helper.ts"),
|
|
132
|
+
...TEST_SUPPORT_ROOTS
|
|
133
|
+
],
|
|
134
|
+
summary: "watch-pr argument and rendering tests"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: "watch-pr-github-test",
|
|
138
|
+
kind: "test",
|
|
139
|
+
entry: upstreamScript("watch-pr/github.test.ts"),
|
|
140
|
+
out: "watch-pr/github.test.mjs",
|
|
141
|
+
assetPath: "skills/poteto-mode/scripts/watch-pr/github.test.mjs",
|
|
142
|
+
executable: false,
|
|
143
|
+
sources: [
|
|
144
|
+
upstreamScript("watch-pr/github.test.ts"),
|
|
145
|
+
upstreamScript("watch-pr/github.ts"),
|
|
146
|
+
upstreamScript("watch-pr/types.ts"),
|
|
147
|
+
upstreamScript("watch-pr/fakes.test-helper.ts"),
|
|
148
|
+
...TEST_SUPPORT_ROOTS
|
|
149
|
+
],
|
|
150
|
+
summary: "watch-pr GitHub reader tests"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: "watch-pr-policy-test",
|
|
154
|
+
kind: "test",
|
|
155
|
+
entry: upstreamScript("watch-pr/policy.test.ts"),
|
|
156
|
+
out: "watch-pr/policy.test.mjs",
|
|
157
|
+
assetPath: "skills/poteto-mode/scripts/watch-pr/policy.test.mjs",
|
|
158
|
+
executable: false,
|
|
159
|
+
sources: [
|
|
160
|
+
upstreamScript("watch-pr/policy.test.ts"),
|
|
161
|
+
upstreamScript("watch-pr/policy.ts"),
|
|
162
|
+
upstreamScript("watch-pr/github.ts"),
|
|
163
|
+
upstreamScript("watch-pr/types.ts"),
|
|
164
|
+
upstreamScript("watch-pr/fakes.test-helper.ts"),
|
|
165
|
+
...TEST_SUPPORT_ROOTS
|
|
166
|
+
],
|
|
167
|
+
summary: "watch-pr policy and queue tests"
|
|
168
|
+
}
|
|
169
|
+
];
|
|
170
|
+
[...RUNTIME_CLIS, ...RUNTIME_TESTS];
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/script.ts
|
|
173
|
+
/**
|
|
174
|
+
* `pstack_script`: run a bundled pstack script through the Host's own runtime.
|
|
175
|
+
*
|
|
176
|
+
* Every shipped CLI is a Node artefact inside this package. A model could always
|
|
177
|
+
* write `node <path> …` into a shell, and that is exactly what breaks in the
|
|
178
|
+
* Desktop build: Electron runs the same CLI, so `node` is not on `PATH` there.
|
|
179
|
+
*
|
|
180
|
+
* So the entry point is a tool, not a command. The tool spawns the artefact with
|
|
181
|
+
* `process.execPath` — whichever binary is already running this Host, Node or
|
|
182
|
+
* Electron — and passes `ELECTRON_RUN_AS_NODE=1` only when it is Electron. That is
|
|
183
|
+
* what makes the npm CLI and the Desktop build behave the same without depending on
|
|
184
|
+
* anything in `PATH`.
|
|
185
|
+
*
|
|
186
|
+
* The tool never accepts a path or a command string. `id` is a closed enum over the
|
|
187
|
+
* shipped artefacts, resolved from this package, so a call cannot reach a script the
|
|
188
|
+
* package does not contain. It is not a general shell: there is no `sh -c`, no glob
|
|
189
|
+
* expansion by a shell, and no way to name another executable.
|
|
190
|
+
*/
|
|
191
|
+
/** Model-facing tool name. */
|
|
192
|
+
const TOOL_NAME = "pstack_script";
|
|
193
|
+
/** The shipped scripts this tool may run, in display order. */
|
|
194
|
+
const SCRIPT_IDS = RUNTIME_CLIS.map((entry) => entry.id);
|
|
195
|
+
/** Default wall-clock limit for a foreground run. */
|
|
196
|
+
const DEFAULT_TIMEOUT_MS = 12e4;
|
|
197
|
+
/** Bounds a caller may set for one foreground run. */
|
|
198
|
+
const MAX_TIMEOUT_MS = 6e5;
|
|
199
|
+
/** Default cap on captured bytes per stream. */
|
|
200
|
+
const DEFAULT_OUTPUT_LIMIT_BYTES = 65536;
|
|
201
|
+
/** Raised for an argument problem, before anything is spawned. */
|
|
202
|
+
var ScriptInputError = class extends Error {
|
|
203
|
+
constructor(message) {
|
|
204
|
+
super(message);
|
|
205
|
+
this.name = "ScriptInputError";
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
/** Raised when a shipped artefact is missing from this install. */
|
|
209
|
+
var ScriptMissingError = class extends Error {
|
|
210
|
+
constructor(message) {
|
|
211
|
+
super(message);
|
|
212
|
+
this.name = "ScriptMissingError";
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
/** Absolute path of one shipped artefact. */
|
|
216
|
+
function scriptPath(id) {
|
|
217
|
+
const entry = RUNTIME_CLIS.find((candidate) => candidate.id === id);
|
|
218
|
+
if (entry === void 0) throw new ScriptInputError(`unknown script id "${String(id)}"`);
|
|
219
|
+
return join(ASSETS_ROOT, ...entry.assetPath.split("/"));
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The environment a child gets.
|
|
223
|
+
*
|
|
224
|
+
* The one rule is the Electron selector. Under Electron, `process.execPath` is the
|
|
225
|
+
* Desktop binary, which starts a browser window unless `ELECTRON_RUN_AS_NODE=1` tells
|
|
226
|
+
* it to behave as Node. Under plain Node the variable is inert — Node ignores it —
|
|
227
|
+
* so setting it unconditionally would also work; it is conditional here so the
|
|
228
|
+
* environment of a normal run stays exactly the caller's, and the one code path that
|
|
229
|
+
* needs it is visible.
|
|
230
|
+
*/
|
|
231
|
+
function scriptEnvironment(base = process.env, versions = process.versions) {
|
|
232
|
+
if (versions.electron === void 0) return { ...base };
|
|
233
|
+
return {
|
|
234
|
+
...base,
|
|
235
|
+
ELECTRON_RUN_AS_NODE: "1"
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/** Validate the raw tool arguments. */
|
|
239
|
+
function parseScriptArgs(raw) {
|
|
240
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new ScriptInputError("arguments must be an object");
|
|
241
|
+
const record = raw;
|
|
242
|
+
for (const key of Object.keys(record)) if (![
|
|
243
|
+
"id",
|
|
244
|
+
"args",
|
|
245
|
+
"cwd",
|
|
246
|
+
"background",
|
|
247
|
+
"timeoutMs",
|
|
248
|
+
"outputLimitBytes"
|
|
249
|
+
].includes(key)) throw new ScriptInputError(`unknown argument "${key}"`);
|
|
250
|
+
const id = record["id"];
|
|
251
|
+
if (typeof id !== "string") throw new ScriptInputError("\"id\" must be a string");
|
|
252
|
+
if (!SCRIPT_IDS.includes(id)) throw new ScriptInputError(`"id" must be one of ${SCRIPT_IDS.join(", ")}; this tool runs bundled scripts only`);
|
|
253
|
+
const rawArgs = record["args"];
|
|
254
|
+
if (rawArgs !== void 0 && !Array.isArray(rawArgs)) throw new ScriptInputError("\"args\" must be an array of strings");
|
|
255
|
+
const args = [];
|
|
256
|
+
for (const value of rawArgs ?? []) {
|
|
257
|
+
if (typeof value !== "string") throw new ScriptInputError("\"args\" must contain only strings");
|
|
258
|
+
args.push(value);
|
|
259
|
+
}
|
|
260
|
+
const cwd = record["cwd"];
|
|
261
|
+
if (cwd !== void 0 && (typeof cwd !== "string" || cwd.trim() === "")) throw new ScriptInputError("\"cwd\" must be a non-empty string when present");
|
|
262
|
+
const background = record["background"] ?? false;
|
|
263
|
+
if (typeof background !== "boolean") throw new ScriptInputError("\"background\" must be a boolean");
|
|
264
|
+
const timeoutMs = record["timeoutMs"];
|
|
265
|
+
if (timeoutMs !== void 0) {
|
|
266
|
+
if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs) || timeoutMs <= 0) throw new ScriptInputError("\"timeoutMs\" must be a positive integer");
|
|
267
|
+
if (timeoutMs > 6e5) throw new ScriptInputError(`"timeoutMs" must be at most ${MAX_TIMEOUT_MS}`);
|
|
268
|
+
}
|
|
269
|
+
const outputLimitBytes = record["outputLimitBytes"];
|
|
270
|
+
if (outputLimitBytes !== void 0) {
|
|
271
|
+
if (typeof outputLimitBytes !== "number" || !Number.isInteger(outputLimitBytes) || outputLimitBytes <= 0) throw new ScriptInputError("\"outputLimitBytes\" must be a positive integer");
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
id,
|
|
275
|
+
args,
|
|
276
|
+
...cwd === void 0 ? {} : { cwd },
|
|
277
|
+
background,
|
|
278
|
+
...timeoutMs === void 0 ? {} : { timeoutMs },
|
|
279
|
+
...outputLimitBytes === void 0 ? {} : { outputLimitBytes }
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Resolve the directory the child runs in.
|
|
284
|
+
*
|
|
285
|
+
* The tool's `cwd` argument is documented as resolving against "this session's
|
|
286
|
+
* directory", so `sessionCwd` — the value `ctx.workingDirectory.ensure()` returns —
|
|
287
|
+
* is the base, not the Host process's working directory.
|
|
288
|
+
*
|
|
289
|
+
* - omitted -> the session directory
|
|
290
|
+
* - relative -> joined onto the session directory
|
|
291
|
+
* - absolute -> used as-is
|
|
292
|
+
*
|
|
293
|
+
* With no Session (a call outside an Agent) there is no session directory to
|
|
294
|
+
* anchor to; the argument is then passed through and `resolve` normalises it
|
|
295
|
+
* against the Host process, which is the only directory that exists.
|
|
296
|
+
*/
|
|
297
|
+
function resolveWorkingDirectory(requested, sessionCwd) {
|
|
298
|
+
if (requested === void 0) return sessionCwd;
|
|
299
|
+
if (isAbsolute(requested)) return resolve(requested);
|
|
300
|
+
return sessionCwd === void 0 ? resolve(requested) : resolve(join(sessionCwd, requested));
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Run one script in the foreground.
|
|
304
|
+
*
|
|
305
|
+
* A nonzero exit, a signal, a timeout, and truncated output are all returned rather
|
|
306
|
+
* than raised: the caller needs to see what the script said before it failed, and an
|
|
307
|
+
* exception would discard the captured output.
|
|
308
|
+
*/
|
|
309
|
+
async function runForeground(args, options = {}) {
|
|
310
|
+
const path = scriptPath(args.id);
|
|
311
|
+
const started = Date.now();
|
|
312
|
+
const timeoutMs = args.timeoutMs ?? 12e4;
|
|
313
|
+
const limit = args.outputLimitBytes ?? 65536;
|
|
314
|
+
return await new Promise((resolve, reject) => {
|
|
315
|
+
const child = spawn(options.execPath ?? process.execPath, [path, ...args.args], {
|
|
316
|
+
cwd: args.cwd ?? options.cwd,
|
|
317
|
+
env: options.environment ?? scriptEnvironment(),
|
|
318
|
+
stdio: [
|
|
319
|
+
"ignore",
|
|
320
|
+
"pipe",
|
|
321
|
+
"pipe"
|
|
322
|
+
],
|
|
323
|
+
windowsHide: true,
|
|
324
|
+
signal: options.signal
|
|
325
|
+
});
|
|
326
|
+
let stdout = "";
|
|
327
|
+
let stderr = "";
|
|
328
|
+
let stdoutTruncated = false;
|
|
329
|
+
let stderrTruncated = false;
|
|
330
|
+
let timedOut = false;
|
|
331
|
+
const collect = (chunk, to) => {
|
|
332
|
+
const text = chunk.toString("utf8");
|
|
333
|
+
if (to === "out") {
|
|
334
|
+
if (stdout.length >= limit) {
|
|
335
|
+
stdoutTruncated = true;
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
stdout += text.slice(0, limit - stdout.length);
|
|
339
|
+
if (stdout.length >= limit) stdoutTruncated = true;
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (stderr.length >= limit) {
|
|
343
|
+
stderrTruncated = true;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
stderr += text.slice(0, limit - stderr.length);
|
|
347
|
+
if (stderr.length >= limit) stderrTruncated = true;
|
|
348
|
+
};
|
|
349
|
+
child.stdout?.on("data", (chunk) => collect(chunk, "out"));
|
|
350
|
+
child.stderr?.on("data", (chunk) => collect(chunk, "err"));
|
|
351
|
+
const timer = setTimeout(() => {
|
|
352
|
+
timedOut = true;
|
|
353
|
+
child.kill("SIGKILL");
|
|
354
|
+
}, timeoutMs);
|
|
355
|
+
child.once("error", (error) => {
|
|
356
|
+
clearTimeout(timer);
|
|
357
|
+
reject(error);
|
|
358
|
+
});
|
|
359
|
+
child.once("close", (code, signal) => {
|
|
360
|
+
clearTimeout(timer);
|
|
361
|
+
resolve({
|
|
362
|
+
kind: "foreground",
|
|
363
|
+
id: args.id,
|
|
364
|
+
exitCode: code ?? (signal === null ? 1 : 1),
|
|
365
|
+
signal,
|
|
366
|
+
timedOut,
|
|
367
|
+
stdout,
|
|
368
|
+
stderr,
|
|
369
|
+
stdoutTruncated,
|
|
370
|
+
stderrTruncated,
|
|
371
|
+
durationMs: Date.now() - started
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Start a script as a background job.
|
|
378
|
+
*
|
|
379
|
+
* `jobs` is required and passing nothing is a refusal with a reason. A job whose
|
|
380
|
+
* output nobody can read would be worse than a failed call: the model would report a
|
|
381
|
+
* task as started and no notice would ever arrive.
|
|
382
|
+
*/
|
|
383
|
+
async function runBackground(args, options) {
|
|
384
|
+
const jobs = options.jobs;
|
|
385
|
+
if (jobs === void 0) throw new ScriptInputError("background: true needs the Host jobs service, and this composition has none. Run the call in the foreground (omit background), or ask the operator to mount a job controller.");
|
|
386
|
+
const path = scriptPath(args.id);
|
|
387
|
+
const execPath = options.execPath ?? process.execPath;
|
|
388
|
+
const environment = options.environment ?? scriptEnvironment();
|
|
389
|
+
const cwd = options.cwd;
|
|
390
|
+
/**
|
|
391
|
+
* Settles when the child either started or failed to start, or when the registry
|
|
392
|
+
* never invoked the producer at all.
|
|
393
|
+
*
|
|
394
|
+
* `run` fills this in, so the caller can await the real startup outcome instead of
|
|
395
|
+
* guessing at one with a timer. `child_process` emits exactly one of `spawn` or
|
|
396
|
+
* `error`:
|
|
397
|
+
*
|
|
398
|
+
* - `spawn` fires once the child has been created, before any stdio or exit event.
|
|
399
|
+
* - `error` fires instead when creation failed (ENOENT, EACCES, EMFILE), and no
|
|
400
|
+
* `spawn` ever follows.
|
|
401
|
+
*
|
|
402
|
+
* So these two events are mutually exclusive and jointly exhaustive for "did it
|
|
403
|
+
* start", which is exactly the question the tool has to answer. Measured on this
|
|
404
|
+
* Node: a missing binary reports ENOENT through `error`, while a real binary —
|
|
405
|
+
* including one that exits immediately or runs slowly — reports `spawn` with no
|
|
406
|
+
* error.
|
|
407
|
+
*
|
|
408
|
+
* `state` exists because the promise must never be able to hang. Two cases would
|
|
409
|
+
* otherwise leave it pending forever:
|
|
410
|
+
*
|
|
411
|
+
* - a registry that registers a job without calling `run` (a stub, or a future
|
|
412
|
+
* controller with a different contract). `dsh-jobs-local` does call it
|
|
413
|
+
* synchronously (`hooks = spec.run(handle)`), but the tool cannot assume which
|
|
414
|
+
* implementation it is talking to — and hanging is far worse than reporting
|
|
415
|
+
* "started", because the model never gets an answer at all.
|
|
416
|
+
* - `run` throwing synchronously, which would skip both event handlers.
|
|
417
|
+
*
|
|
418
|
+
* Both are treated as "started" only in the sense of "do not claim a failure we did
|
|
419
|
+
* not observe": a genuinely failed job still settles as `failed` through `done`, and
|
|
420
|
+
* the Host's job tools report that.
|
|
421
|
+
*/
|
|
422
|
+
let reportStarted;
|
|
423
|
+
const state = {
|
|
424
|
+
settled: false,
|
|
425
|
+
ran: false
|
|
426
|
+
};
|
|
427
|
+
const started = new Promise((settle) => {
|
|
428
|
+
reportStarted = (failure) => {
|
|
429
|
+
if (state.settled) return;
|
|
430
|
+
state.settled = true;
|
|
431
|
+
settle(failure);
|
|
432
|
+
};
|
|
433
|
+
});
|
|
434
|
+
const jobId = jobs.start({
|
|
435
|
+
kind: "bash",
|
|
436
|
+
label: `pstack_script ${args.id}`,
|
|
437
|
+
...options.caller === void 0 ? {} : { owner: options.caller },
|
|
438
|
+
outputLimitBytes: args.outputLimitBytes ?? 65536,
|
|
439
|
+
run: (job) => {
|
|
440
|
+
state.ran = true;
|
|
441
|
+
const settleStarted = reportStarted;
|
|
442
|
+
let child;
|
|
443
|
+
try {
|
|
444
|
+
child = spawn(execPath, [path, ...args.args], {
|
|
445
|
+
cwd,
|
|
446
|
+
env: environment,
|
|
447
|
+
stdio: [
|
|
448
|
+
"ignore",
|
|
449
|
+
"pipe",
|
|
450
|
+
"pipe"
|
|
451
|
+
],
|
|
452
|
+
windowsHide: true
|
|
453
|
+
});
|
|
454
|
+
} catch (error) {
|
|
455
|
+
settleStarted(error.code ?? "spawn-threw");
|
|
456
|
+
throw error;
|
|
457
|
+
}
|
|
458
|
+
child.stdout?.on("data", (chunk) => job.append(chunk.toString("utf8"), { channel: "stdout" }));
|
|
459
|
+
child.stderr?.on("data", (chunk) => job.append(chunk.toString("utf8"), { channel: "stderr" }));
|
|
460
|
+
child.once("spawn", () => settleStarted(null));
|
|
461
|
+
child.once("error", (error) => {
|
|
462
|
+
settleStarted(error.code ?? "spawn-failed");
|
|
463
|
+
});
|
|
464
|
+
return {
|
|
465
|
+
cancel: (reason) => {
|
|
466
|
+
child.kill("SIGTERM");
|
|
467
|
+
if (reason !== void 0) job.updateProgress(`cancelling: ${reason}`);
|
|
468
|
+
},
|
|
469
|
+
done: new Promise((settle) => {
|
|
470
|
+
child.once("error", (error) => {
|
|
471
|
+
settle({
|
|
472
|
+
status: "failed",
|
|
473
|
+
detail: error.code ?? "spawn-failed"
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
child.once("close", (code, signal) => {
|
|
477
|
+
if (signal !== null) settle({
|
|
478
|
+
status: "killed",
|
|
479
|
+
detail: `signal ${signal}`
|
|
480
|
+
});
|
|
481
|
+
else if (code === 0) settle({ status: "completed" });
|
|
482
|
+
else settle({
|
|
483
|
+
status: "failed",
|
|
484
|
+
detail: `exit code ${code ?? "unknown"}`
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
})
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
/**
|
|
492
|
+
* Turn a reported start failure into the tool error.
|
|
493
|
+
*
|
|
494
|
+
* The code only. A foreground run is how the caller sees the actual error, and
|
|
495
|
+
* naming the code keeps host paths out of the model's context.
|
|
496
|
+
*/
|
|
497
|
+
const failStart = (reason) => {
|
|
498
|
+
throw new ScriptMissingError(`background job ${jobId} failed to start (${reason}). Run the same script in the foreground to see its error.`);
|
|
499
|
+
};
|
|
500
|
+
if (!state.ran) {
|
|
501
|
+
const view = jobs.get(jobId, options.caller);
|
|
502
|
+
if (view.status === "failed") failStart("the job controller reported a failed start");
|
|
503
|
+
return {
|
|
504
|
+
kind: "background",
|
|
505
|
+
id: args.id,
|
|
506
|
+
jobId,
|
|
507
|
+
status: view.status
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
const startFailure = await started;
|
|
511
|
+
if (startFailure !== null) failStart(startFailure);
|
|
512
|
+
const view = jobs.get(jobId, options.caller);
|
|
513
|
+
if (view.status === "failed") failStart("the job controller reported a failed start");
|
|
514
|
+
return {
|
|
515
|
+
kind: "background",
|
|
516
|
+
id: args.id,
|
|
517
|
+
jobId,
|
|
518
|
+
status: view.status
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Render one captured stream as a fenced block, byte for byte.
|
|
523
|
+
*
|
|
524
|
+
* `trimEnd` used to run here, which is wrong for two reasons:
|
|
525
|
+
*
|
|
526
|
+
* - A stream of only whitespace passed the `!== ""` test and then rendered as an
|
|
527
|
+
* empty-looking `stdout:` line, so a reader concluded the script printed nothing.
|
|
528
|
+
* - `check-plan`'s contract includes exact line counts and blank-line separators, so
|
|
529
|
+
* a trimmed block is a different document — and the model reads this text as the
|
|
530
|
+
* script's output.
|
|
531
|
+
*
|
|
532
|
+
* A fence keeps every byte visible, including trailing blank lines. The fence is
|
|
533
|
+
* lengthened past the longest backtick run in the content so it can never be closed
|
|
534
|
+
* early by the content itself. Nothing inside is escaped or reinterpreted.
|
|
535
|
+
*/
|
|
536
|
+
function renderCapturedStream(text) {
|
|
537
|
+
if (text === "") return [];
|
|
538
|
+
const longestRun = Math.max(0, ...(text.match(/`+/g) ?? []).map((run) => run.length));
|
|
539
|
+
const fence = "`".repeat(Math.max(3, longestRun + 1));
|
|
540
|
+
return [
|
|
541
|
+
fence,
|
|
542
|
+
text.endsWith("\n") ? text.slice(0, -1) : text,
|
|
543
|
+
fence
|
|
544
|
+
];
|
|
545
|
+
}
|
|
546
|
+
/** Render one outcome for the model. */
|
|
547
|
+
function renderScriptOutcome(outcome) {
|
|
548
|
+
if (outcome.kind === "background") return [`started ${outcome.id} as background job ${outcome.jobId} (status: ${outcome.status}).`, "Read or stop it through the Host's job tools; this tool does not poll."].join("\n");
|
|
549
|
+
const lines = [`${outcome.id} exited with code ${outcome.exitCode}${outcome.signal === null ? "" : ` (signal ${outcome.signal})`}${outcome.timedOut ? ` after the timeout` : ""} in ${outcome.durationMs}ms`];
|
|
550
|
+
if (outcome.stdout !== "") lines.push("", "stdout (exact bytes):", ...renderCapturedStream(outcome.stdout));
|
|
551
|
+
if (outcome.stderr !== "") lines.push("", "stderr (exact bytes):", ...renderCapturedStream(outcome.stderr));
|
|
552
|
+
if (outcome.stdoutTruncated) lines.push("", "stdout was truncated at the output limit.");
|
|
553
|
+
if (outcome.stderrTruncated) lines.push("", "stderr was truncated at the output limit.");
|
|
554
|
+
if (outcome.exitCode !== 0 || outcome.signal !== null || outcome.timedOut) lines.push("", "This run failed. Read the output above; do not report success.");
|
|
555
|
+
return lines.join("\n");
|
|
556
|
+
}
|
|
557
|
+
const TOOL_DESCRIPTION = [
|
|
558
|
+
"Run one of the scripts bundled with pstack, using the Host's own Node runtime.",
|
|
559
|
+
"",
|
|
560
|
+
"Use this instead of a shell for the pstack scripts. It works in both deployments: the npm CLI and",
|
|
561
|
+
"the Desktop app, where `node` is not on PATH because the Desktop process is Electron.",
|
|
562
|
+
"",
|
|
563
|
+
"`id` selects a bundled script by name; only those names are accepted, so this is not a general",
|
|
564
|
+
"shell and takes no path or command string. `args` are passed to the script verbatim.",
|
|
565
|
+
"",
|
|
566
|
+
"The run starts in this session's directory, so a relative path in `args` lands inside the",
|
|
567
|
+
"workspace or worktree the session is using.",
|
|
568
|
+
"",
|
|
569
|
+
"**This tool does not go through command approval, and the Host's sandbox policy does not apply**",
|
|
570
|
+
"to it. It spawns a Node process directly instead of asking `ctx.shell` to run a command, which is",
|
|
571
|
+
"what keeps it working where no `node` is on PATH. The scripts are this package's own audited",
|
|
572
|
+
"artefacts and `id` cannot name anything else, but the consequence is real: unlike a bash call, no",
|
|
573
|
+
"approval prompt is shown and no filesystem policy is enforced on what the script writes. Say so",
|
|
574
|
+
"when a task depends on that difference.",
|
|
575
|
+
"",
|
|
576
|
+
"A nonzero exit, a signal, a timeout, and truncated output all come back in the result with the",
|
|
577
|
+
"captured output, so a failed run is visible rather than silent.",
|
|
578
|
+
"",
|
|
579
|
+
"Set `background: true` for a long run. That needs the Host jobs service; without it the call is",
|
|
580
|
+
"refused with a reason instead of hanging. Background runs return a job id and a settlement notice",
|
|
581
|
+
"arrives later — do not poll."
|
|
582
|
+
].join("\n");
|
|
583
|
+
/** The model-facing JSON Schema for `parameters`. */
|
|
584
|
+
function buildScriptParameters() {
|
|
585
|
+
return {
|
|
586
|
+
type: "object",
|
|
587
|
+
additionalProperties: false,
|
|
588
|
+
properties: {
|
|
589
|
+
id: {
|
|
590
|
+
type: "string",
|
|
591
|
+
enum: [...SCRIPT_IDS],
|
|
592
|
+
description: "Which bundled pstack script to run. Not a path: only these names are accepted."
|
|
593
|
+
},
|
|
594
|
+
args: {
|
|
595
|
+
type: "array",
|
|
596
|
+
items: { type: "string" },
|
|
597
|
+
description: "Arguments passed to the script verbatim, one array entry per argument."
|
|
598
|
+
},
|
|
599
|
+
cwd: {
|
|
600
|
+
type: "string",
|
|
601
|
+
description: "Working directory for the script. Relative paths resolve against this session's directory; an absolute path is used as-is. Omitted runs in this session's directory."
|
|
602
|
+
},
|
|
603
|
+
background: {
|
|
604
|
+
type: "boolean",
|
|
605
|
+
description: "Run as a Host background job and return its id. Requires the jobs service; a composition without it refuses the call."
|
|
606
|
+
},
|
|
607
|
+
timeoutMs: {
|
|
608
|
+
type: "integer",
|
|
609
|
+
description: `Foreground wall-clock limit; 1 or greater, and values below 1 are rejected. Defaults to ${DEFAULT_TIMEOUT_MS}; values above ${MAX_TIMEOUT_MS} are rejected.`
|
|
610
|
+
},
|
|
611
|
+
outputLimitBytes: {
|
|
612
|
+
type: "integer",
|
|
613
|
+
description: `Per-stream capture limit; 1 or greater, and values below 1 are rejected. Defaults to ${DEFAULT_OUTPUT_LIMIT_BYTES}.`
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
required: ["id"]
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* The declared shape of one outcome, exported so tests can validate real results.
|
|
621
|
+
*
|
|
622
|
+
* Split by `kind` instead of one flat object. The flat form could only require
|
|
623
|
+
* `kind` and `id`, so a foreground result that omitted `exitCode` was still
|
|
624
|
+
* "valid" and the schema proved nothing about the fields the model reads.
|
|
625
|
+
*
|
|
626
|
+
* `signal` is nullable because `child_process` reports `null` whenever the child
|
|
627
|
+
* exited on its own — that is every successful run. Declaring it `string` made
|
|
628
|
+
* the registry reject the tool's own normal output: `createSuccessResult()`
|
|
629
|
+
* validates before rendering, so **every** clean exit surfaced to the model as
|
|
630
|
+
* `tool "pstack_script" returned invalid output`.
|
|
631
|
+
*/
|
|
632
|
+
const OUTPUT_SCHEMA = { oneOf: [{
|
|
633
|
+
type: "object",
|
|
634
|
+
additionalProperties: false,
|
|
635
|
+
properties: {
|
|
636
|
+
kind: {
|
|
637
|
+
type: "string",
|
|
638
|
+
const: "foreground"
|
|
639
|
+
},
|
|
640
|
+
id: {
|
|
641
|
+
type: "string",
|
|
642
|
+
description: "Which bundled script ran."
|
|
643
|
+
},
|
|
644
|
+
exitCode: {
|
|
645
|
+
type: "integer",
|
|
646
|
+
description: "The child's exit code."
|
|
647
|
+
},
|
|
648
|
+
signal: {
|
|
649
|
+
oneOf: [{ type: "string" }, { type: "null" }],
|
|
650
|
+
description: "Terminating signal, or null when the script exited on its own."
|
|
651
|
+
},
|
|
652
|
+
timedOut: {
|
|
653
|
+
type: "boolean",
|
|
654
|
+
description: "True when this tool's own timeout killed the run."
|
|
655
|
+
},
|
|
656
|
+
stdout: { type: "string" },
|
|
657
|
+
stderr: { type: "string" },
|
|
658
|
+
stdoutTruncated: { type: "boolean" },
|
|
659
|
+
stderrTruncated: { type: "boolean" },
|
|
660
|
+
durationMs: { type: "integer" }
|
|
661
|
+
},
|
|
662
|
+
required: [
|
|
663
|
+
"kind",
|
|
664
|
+
"id",
|
|
665
|
+
"exitCode",
|
|
666
|
+
"signal",
|
|
667
|
+
"timedOut",
|
|
668
|
+
"stdout",
|
|
669
|
+
"stderr",
|
|
670
|
+
"stdoutTruncated",
|
|
671
|
+
"stderrTruncated",
|
|
672
|
+
"durationMs"
|
|
673
|
+
]
|
|
674
|
+
}, {
|
|
675
|
+
type: "object",
|
|
676
|
+
additionalProperties: false,
|
|
677
|
+
properties: {
|
|
678
|
+
kind: {
|
|
679
|
+
type: "string",
|
|
680
|
+
const: "background"
|
|
681
|
+
},
|
|
682
|
+
id: {
|
|
683
|
+
type: "string",
|
|
684
|
+
description: "Which bundled script started."
|
|
685
|
+
},
|
|
686
|
+
jobId: {
|
|
687
|
+
type: "string",
|
|
688
|
+
description: "The Host job id to read or stop."
|
|
689
|
+
},
|
|
690
|
+
status: {
|
|
691
|
+
type: "string",
|
|
692
|
+
description: "Job status when the call returned."
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
required: [
|
|
696
|
+
"kind",
|
|
697
|
+
"id",
|
|
698
|
+
"jobId",
|
|
699
|
+
"status"
|
|
700
|
+
]
|
|
701
|
+
}] };
|
|
702
|
+
/** Build the tool for one row. */
|
|
703
|
+
function createPstackScriptTool(host) {
|
|
704
|
+
return {
|
|
705
|
+
name: TOOL_NAME,
|
|
706
|
+
description: TOOL_DESCRIPTION,
|
|
707
|
+
parameters: buildScriptParameters(),
|
|
708
|
+
output: {
|
|
709
|
+
schema: OUTPUT_SCHEMA,
|
|
710
|
+
render: (_args, value) => [{
|
|
711
|
+
type: "text",
|
|
712
|
+
text: renderScriptOutcome(value)
|
|
713
|
+
}]
|
|
714
|
+
},
|
|
715
|
+
isConcurrencySafe: () => false,
|
|
716
|
+
/**
|
|
717
|
+
* The Host's last-resort budget for one call.
|
|
718
|
+
*
|
|
719
|
+
* `DEFAULT_TIMEOUT_MS` is the tool's own timer for a foreground run, and it fires
|
|
720
|
+
* first in every normal case: it kills the child and returns a `timedOut` result the
|
|
721
|
+
* model can read. This budget is larger on purpose, so it only ever wins when the
|
|
722
|
+
* tool's own machinery failed to return at all — a hung `await`, a timer that never
|
|
723
|
+
* ran — and the alternative would be a session blocked with no answer.
|
|
724
|
+
*
|
|
725
|
+
* `MAX_TIMEOUT_MS` rather than a new number: it is already the ceiling the tool
|
|
726
|
+
* accepts for a caller-supplied `timeoutMs`, so the Host budget can never be the
|
|
727
|
+
* thing that cuts short a run the caller was allowed to request.
|
|
728
|
+
*
|
|
729
|
+
* It does not bound a background job. The wrapper disposes its timer when `execute`
|
|
730
|
+
* settles, which is milliseconds after `jobs.start` returns.
|
|
731
|
+
*/
|
|
732
|
+
timeoutMs: MAX_TIMEOUT_MS,
|
|
733
|
+
async execute(rawArgs, exec) {
|
|
734
|
+
const args = parseScriptArgs(rawArgs);
|
|
735
|
+
const sessionCwd = exec.agent === void 0 ? void 0 : await host.workingDirectory.ensure(exec.agent, exec.signal);
|
|
736
|
+
const cwd = resolveWorkingDirectory(args.cwd, sessionCwd);
|
|
737
|
+
if (args.background) return await runBackground(args, {
|
|
738
|
+
jobs: host.getJobs?.(),
|
|
739
|
+
...cwd === void 0 ? {} : { cwd },
|
|
740
|
+
...exec.agent === void 0 ? {} : { caller: exec.agent.id }
|
|
741
|
+
});
|
|
742
|
+
return await runForeground(args, {
|
|
743
|
+
signal: exec.signal,
|
|
744
|
+
...cwd === void 0 ? {} : { cwd }
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
/** Cordis row name, used by Loader diagnostics. */
|
|
750
|
+
const name = "pstack-script";
|
|
751
|
+
/**
|
|
752
|
+
* The hard dependencies.
|
|
753
|
+
*
|
|
754
|
+
* `tools` is the registry this row registers into; `workingDirectory` is what
|
|
755
|
+
* turns the tool's `cwd` argument and its omitted case into the Session's
|
|
756
|
+
* directory. Both are always present in a profile that has an Agent at all.
|
|
757
|
+
*
|
|
758
|
+
* `jobs` is deliberately absent: a composition without a job controller still
|
|
759
|
+
* registers the tool, and only `background: true` is refused. Declaring `jobs`
|
|
760
|
+
* here would keep the whole fiber inactive in every profile that has no job
|
|
761
|
+
* controller, which is most of them.
|
|
762
|
+
*/
|
|
763
|
+
const inject = ["tools", "workingDirectory"];
|
|
764
|
+
/**
|
|
765
|
+
* Register `pstack_script` for this row.
|
|
766
|
+
*
|
|
767
|
+
* `inject` is what makes `ctx.tools` readable at all: without it Cordis runs
|
|
768
|
+
* `apply` immediately and the service proxy throws
|
|
769
|
+
* `cannot get property "tools" without inject`, so the row would register nothing
|
|
770
|
+
* in any profile.
|
|
771
|
+
*/
|
|
772
|
+
function apply(ctx) {
|
|
773
|
+
ctx.tools.register(createPstackScriptTool({
|
|
774
|
+
tools: ctx.tools,
|
|
775
|
+
workingDirectory: ctx.workingDirectory,
|
|
776
|
+
getJobs: () => ctx.get?.("jobs")
|
|
777
|
+
}));
|
|
778
|
+
}
|
|
779
|
+
//#endregion
|
|
780
|
+
export { DEFAULT_OUTPUT_LIMIT_BYTES, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, OUTPUT_SCHEMA, SCRIPT_IDS, ScriptInputError, ScriptMissingError, TOOL_NAME, apply, buildScriptParameters, createPstackScriptTool, inject, name, parseScriptArgs, renderCapturedStream, renderScriptOutcome, resolveWorkingDirectory, runBackground, runForeground, scriptEnvironment, scriptPath };
|
|
781
|
+
|
|
782
|
+
//# sourceMappingURL=script.mjs.map
|