gsd-pi 2.41.0-dev.0acbce9 → 2.41.0-dev.5a170d0
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/README.md +1 -1
- package/dist/cli-web-branch.d.ts +6 -0
- package/dist/cli-web-branch.js +17 -0
- package/dist/onboarding.js +2 -1
- package/dist/resources/extensions/gsd/auto/loop.js +89 -1
- package/dist/resources/extensions/gsd/auto/phases.js +28 -10
- package/dist/resources/extensions/gsd/auto/session.js +6 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +8 -2
- package/dist/resources/extensions/gsd/auto-dispatch.js +19 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +7 -0
- package/dist/resources/extensions/gsd/auto-recovery.js +12 -4
- package/dist/resources/extensions/gsd/auto-start.js +8 -3
- package/dist/resources/extensions/gsd/auto-worktree.js +147 -13
- package/dist/resources/extensions/gsd/auto.js +64 -2
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +199 -164
- package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +62 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +16 -0
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +7 -2
- package/dist/resources/extensions/gsd/commands/catalog.js +40 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +146 -0
- package/dist/resources/extensions/gsd/context-injector.js +74 -0
- package/dist/resources/extensions/gsd/context-store.js +4 -3
- package/dist/resources/extensions/gsd/custom-execution-policy.js +47 -0
- package/dist/resources/extensions/gsd/custom-verification.js +145 -0
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +164 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -0
- package/dist/resources/extensions/gsd/db-writer.js +5 -2
- package/dist/resources/extensions/gsd/definition-loader.js +352 -0
- package/dist/resources/extensions/gsd/detection.js +1 -1
- package/dist/resources/extensions/gsd/dev-execution-policy.js +24 -0
- package/dist/resources/extensions/gsd/dev-workflow-engine.js +82 -0
- package/dist/resources/extensions/gsd/doctor.js +11 -1
- package/dist/resources/extensions/gsd/engine-resolver.js +40 -0
- package/dist/resources/extensions/gsd/engine-types.js +8 -0
- package/dist/resources/extensions/gsd/execution-policy.js +8 -0
- package/dist/resources/extensions/gsd/exit-command.js +12 -2
- package/dist/resources/extensions/gsd/export.js +9 -13
- package/dist/resources/extensions/gsd/extension-manifest.json +2 -2
- package/dist/resources/extensions/gsd/files.js +28 -11
- package/dist/resources/extensions/gsd/forensics.js +10 -3
- package/dist/resources/extensions/gsd/git-service.js +5 -1
- package/dist/resources/extensions/gsd/graph.js +225 -0
- package/dist/resources/extensions/gsd/gsd-db.js +25 -8
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +7 -3
- package/dist/resources/extensions/gsd/journal.js +85 -0
- package/dist/resources/extensions/gsd/md-importer.js +5 -0
- package/dist/resources/extensions/gsd/milestone-ids.js +1 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +2 -2
- package/dist/resources/extensions/gsd/post-unit-hooks.js +24 -412
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences.js +1 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +34 -4
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
- package/dist/resources/extensions/gsd/prompts/discuss.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
- package/dist/resources/extensions/gsd/repo-identity.js +46 -2
- package/dist/resources/extensions/gsd/rule-registry.js +489 -0
- package/dist/resources/extensions/gsd/rule-types.js +6 -0
- package/dist/resources/extensions/gsd/run-manager.js +134 -0
- package/dist/resources/extensions/gsd/service-tier.js +138 -0
- package/dist/resources/extensions/gsd/structured-data-formatter.js +2 -1
- package/dist/resources/extensions/gsd/templates/decisions.md +2 -2
- package/dist/resources/extensions/gsd/workflow-engine.js +7 -0
- package/dist/resources/extensions/gsd/workflow-templates.js +13 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +20 -6
- package/dist/resources/extensions/gsd/worktree-resolver.js +19 -2
- package/dist/resources/extensions/subagent/index.js +7 -3
- package/dist/resources/extensions/voice/index.js +4 -4
- package/dist/resources/skills/create-workflow/SKILL.md +103 -0
- package/dist/resources/skills/create-workflow/references/feature-patterns.md +128 -0
- package/dist/resources/skills/create-workflow/references/verification-policies.md +76 -0
- package/dist/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
- package/dist/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
- package/dist/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
- package/dist/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
- package/dist/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
- package/dist/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
- package/dist/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
- package/dist/web/standalone/.next/server/chunks/229.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/4024.c195dc1fdd2adbea.js +9 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-9afaaebf6042a1d7.js → webpack-fa307370fcf9fb2c.js} +1 -1
- package/dist/web-mode.d.ts +2 -0
- package/dist/web-mode.js +29 -7
- package/package.json +1 -1
- package/packages/native/src/__tests__/text.test.mjs +33 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +3 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +10 -7
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +4 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +11 -7
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +5 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +5 -1
- package/src/resources/extensions/gsd/auto/loop.ts +101 -1
- package/src/resources/extensions/gsd/auto/phases.ts +30 -10
- package/src/resources/extensions/gsd/auto/session.ts +6 -0
- package/src/resources/extensions/gsd/auto/types.ts +4 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +9 -2
- package/src/resources/extensions/gsd/auto-dispatch.ts +25 -5
- package/src/resources/extensions/gsd/auto-post-unit.ts +8 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +12 -4
- package/src/resources/extensions/gsd/auto-start.ts +8 -3
- package/src/resources/extensions/gsd/auto-worktree.ts +162 -18
- package/src/resources/extensions/gsd/auto.ts +71 -2
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +209 -162
- package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +62 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -0
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +9 -2
- package/src/resources/extensions/gsd/commands/catalog.ts +40 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +164 -0
- package/src/resources/extensions/gsd/context-injector.ts +100 -0
- package/src/resources/extensions/gsd/context-store.ts +4 -3
- package/src/resources/extensions/gsd/custom-execution-policy.ts +73 -0
- package/src/resources/extensions/gsd/custom-verification.ts +180 -0
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +216 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -0
- package/src/resources/extensions/gsd/db-writer.ts +6 -2
- package/src/resources/extensions/gsd/definition-loader.ts +462 -0
- package/src/resources/extensions/gsd/detection.ts +1 -1
- package/src/resources/extensions/gsd/dev-execution-policy.ts +51 -0
- package/src/resources/extensions/gsd/dev-workflow-engine.ts +110 -0
- package/src/resources/extensions/gsd/doctor.ts +12 -1
- package/src/resources/extensions/gsd/engine-resolver.ts +57 -0
- package/src/resources/extensions/gsd/engine-types.ts +71 -0
- package/src/resources/extensions/gsd/execution-policy.ts +43 -0
- package/src/resources/extensions/gsd/exit-command.ts +14 -2
- package/src/resources/extensions/gsd/export.ts +8 -15
- package/src/resources/extensions/gsd/extension-manifest.json +2 -2
- package/src/resources/extensions/gsd/files.ts +29 -12
- package/src/resources/extensions/gsd/forensics.ts +9 -3
- package/src/resources/extensions/gsd/git-service.ts +5 -4
- package/src/resources/extensions/gsd/graph.ts +312 -0
- package/src/resources/extensions/gsd/gsd-db.ts +37 -8
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +7 -3
- package/src/resources/extensions/gsd/journal.ts +134 -0
- package/src/resources/extensions/gsd/md-importer.ts +6 -0
- package/src/resources/extensions/gsd/milestone-ids.ts +1 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +2 -2
- package/src/resources/extensions/gsd/post-unit-hooks.ts +24 -462
- package/src/resources/extensions/gsd/preferences-types.ts +3 -0
- package/src/resources/extensions/gsd/preferences.ts +1 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +35 -4
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +11 -10
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
- package/src/resources/extensions/gsd/prompts/discuss.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +1 -1
- package/src/resources/extensions/gsd/repo-identity.ts +47 -2
- package/src/resources/extensions/gsd/rule-registry.ts +599 -0
- package/src/resources/extensions/gsd/rule-types.ts +68 -0
- package/src/resources/extensions/gsd/run-manager.ts +180 -0
- package/src/resources/extensions/gsd/service-tier.ts +171 -0
- package/src/resources/extensions/gsd/structured-data-formatter.ts +3 -1
- package/src/resources/extensions/gsd/templates/decisions.md +2 -2
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +103 -120
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +85 -0
- package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +202 -0
- package/src/resources/extensions/gsd/tests/bundled-workflow-defs.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/captures.test.ts +12 -1
- package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +283 -0
- package/src/resources/extensions/gsd/tests/context-injector.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/context-store.test.ts +10 -5
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +20 -20
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +540 -0
- package/src/resources/extensions/gsd/tests/custom-verification.test.ts +382 -0
- package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +339 -0
- package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/definition-loader.test.ts +778 -0
- package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +318 -0
- package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +15 -10
- package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +5 -4
- package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/doctor-task-done-missing-summary-slice-loop.test.ts +174 -0
- package/src/resources/extensions/gsd/tests/e2e-workflow-pipeline-integration.test.ts +476 -0
- package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +271 -0
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/graph-operations.test.ts +599 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +7 -7
- package/src/resources/extensions/gsd/tests/iterate-engine-integration.test.ts +429 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +513 -0
- package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +147 -0
- package/src/resources/extensions/gsd/tests/journal.test.ts +386 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/milestone-id-reservation.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/parsers.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +47 -25
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +3 -1
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +61 -1
- package/src/resources/extensions/gsd/tests/routing-history.test.ts +11 -22
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +413 -0
- package/src/resources/extensions/gsd/tests/run-manager.test.ts +229 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/tool-naming.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +178 -0
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +195 -105
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +78 -3
- package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +74 -0
- package/src/resources/extensions/gsd/types.ts +3 -0
- package/src/resources/extensions/gsd/workflow-engine.ts +38 -0
- package/src/resources/extensions/gsd/workflow-templates.ts +12 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +21 -6
- package/src/resources/extensions/gsd/worktree-resolver.ts +30 -9
- package/src/resources/extensions/subagent/index.ts +7 -3
- package/src/resources/extensions/voice/index.ts +4 -4
- package/src/resources/skills/create-workflow/SKILL.md +103 -0
- package/src/resources/skills/create-workflow/references/feature-patterns.md +128 -0
- package/src/resources/skills/create-workflow/references/verification-policies.md +76 -0
- package/src/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
- package/src/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
- package/src/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
- package/src/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
- package/src/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
- package/src/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
- package/src/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
- package/dist/web/standalone/.next/static/chunks/4024.279c423e4661ece1.js +0 -9
- /package/dist/web/standalone/.next/static/{SwbKZ7JPNFlEmU4f8pKEv → K7GYOOPvQWX6TKYEKhODM}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{SwbKZ7JPNFlEmU4f8pKEv → K7GYOOPvQWX6TKYEKhODM}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import test, { describe } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
supportsServiceTier,
|
|
6
|
+
formatServiceTierStatus,
|
|
7
|
+
resolveServiceTierIcon,
|
|
8
|
+
type ServiceTierSetting,
|
|
9
|
+
} from "../service-tier.ts";
|
|
10
|
+
|
|
11
|
+
// ─── supportsServiceTier ─────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
describe("supportsServiceTier", () => {
|
|
14
|
+
test("returns true for gpt-5.4", () => {
|
|
15
|
+
assert.equal(supportsServiceTier("gpt-5.4"), true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("returns true for gpt-5.4-pro", () => {
|
|
19
|
+
assert.equal(supportsServiceTier("gpt-5.4-pro"), true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("returns true for gpt-5.4-mini", () => {
|
|
23
|
+
assert.equal(supportsServiceTier("gpt-5.4-mini"), true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("returns true for openai/gpt-5.4 (provider-prefixed)", () => {
|
|
27
|
+
assert.equal(supportsServiceTier("openai/gpt-5.4"), true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("returns false for claude-opus-4-6", () => {
|
|
31
|
+
assert.equal(supportsServiceTier("claude-opus-4-6"), false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("returns false for gemini-2.5-pro", () => {
|
|
35
|
+
assert.equal(supportsServiceTier("gemini-2.5-pro"), false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("returns false for gpt-4o", () => {
|
|
39
|
+
assert.equal(supportsServiceTier("gpt-4o"), false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("returns false for empty string", () => {
|
|
43
|
+
assert.equal(supportsServiceTier(""), false);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ─── formatServiceTierStatus ─────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
describe("formatServiceTierStatus", () => {
|
|
50
|
+
test("shows disabled when service_tier is undefined", () => {
|
|
51
|
+
const output = formatServiceTierStatus(undefined);
|
|
52
|
+
assert.ok(output.includes("disabled"), `Expected 'disabled' in: ${output}`);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("shows priority when set to priority", () => {
|
|
56
|
+
const output = formatServiceTierStatus("priority");
|
|
57
|
+
assert.ok(output.includes("priority"), `Expected 'priority' in: ${output}`);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("shows flex when set to flex", () => {
|
|
61
|
+
const output = formatServiceTierStatus("flex");
|
|
62
|
+
assert.ok(output.includes("flex"), `Expected 'flex' in: ${output}`);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ─── resolveServiceTierIcon ──────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
describe("resolveServiceTierIcon", () => {
|
|
69
|
+
test("returns lightning bolt for priority tier on supported model", () => {
|
|
70
|
+
const icon = resolveServiceTierIcon("priority", "gpt-5.4");
|
|
71
|
+
assert.equal(icon, "⚡");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("returns money icon for flex tier on supported model", () => {
|
|
75
|
+
const icon = resolveServiceTierIcon("flex", "gpt-5.4");
|
|
76
|
+
assert.equal(icon, "💰");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("returns empty string when tier is set but model does not support it", () => {
|
|
80
|
+
const icon = resolveServiceTierIcon("priority", "claude-opus-4-6");
|
|
81
|
+
assert.equal(icon, "");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("returns empty string when tier is undefined", () => {
|
|
85
|
+
const icon = resolveServiceTierIcon(undefined, "gpt-5.4");
|
|
86
|
+
assert.equal(icon, "");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("returns empty string when both tier and model are unsupported", () => {
|
|
90
|
+
const icon = resolveServiceTierIcon(undefined, "claude-opus-4-6");
|
|
91
|
+
assert.equal(icon, "");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("returns empty string when model is empty", () => {
|
|
95
|
+
const icon = resolveServiceTierIcon("priority", "");
|
|
96
|
+
assert.equal(icon, "");
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Tests the pure functions — no file I/O, no extension context.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { describe, it
|
|
6
|
+
import { describe, it } from "node:test";
|
|
7
7
|
import assert from "node:assert/strict";
|
|
8
8
|
import type { UnitMetrics } from "../metrics.js";
|
|
9
9
|
|
|
@@ -72,7 +72,7 @@ describe("skill-health", () => {
|
|
|
72
72
|
|
|
73
73
|
// With no metrics file, should return empty
|
|
74
74
|
const result = computeStaleAvoidList("/nonexistent/path", ["some-skill"]);
|
|
75
|
-
assert.
|
|
75
|
+
assert.deepEqual(result, []);
|
|
76
76
|
});
|
|
77
77
|
});
|
|
78
78
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for #1855: Stalled tool detection crashes with
|
|
3
|
+
* "The path argument must be of type string. Received undefined"
|
|
4
|
+
*
|
|
5
|
+
* When a tool stalls in-flight for 10+ minutes, the idle watchdog fires
|
|
6
|
+
* recoverTimedOutUnit(). In auto/phases.ts, buildRecoveryContext was
|
|
7
|
+
* returning an empty object `{}`, so basePath was undefined. The recovery
|
|
8
|
+
* code passed undefined to readUnitRuntimeRecord → runtimePath → join(),
|
|
9
|
+
* which throws a TypeError. The session is permanently frozen because the
|
|
10
|
+
* error propagates into the idle watchdog catch handler but the unit
|
|
11
|
+
* promise is never resolved.
|
|
12
|
+
*
|
|
13
|
+
* This test calls recoverTimedOutUnit with an empty RecoveryContext (the
|
|
14
|
+
* bug) and verifies it crashes, then calls it with a valid RecoveryContext
|
|
15
|
+
* (the fix) and verifies it does not crash.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { recoverTimedOutUnit, type RecoveryContext } from "../auto-timeout-recovery.ts";
|
|
22
|
+
import { createTestContext } from './test-helpers.ts';
|
|
23
|
+
|
|
24
|
+
const { assertTrue, report } = createTestContext();
|
|
25
|
+
|
|
26
|
+
// Minimal mock for ExtensionContext — only the fields recoverTimedOutUnit touches.
|
|
27
|
+
function makeMockCtx() {
|
|
28
|
+
return {
|
|
29
|
+
ui: {
|
|
30
|
+
notify: () => {},
|
|
31
|
+
},
|
|
32
|
+
} as any;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Minimal mock for ExtensionAPI — only sendMessage is called during recovery.
|
|
36
|
+
function makeMockPi() {
|
|
37
|
+
return {
|
|
38
|
+
sendMessage: () => {},
|
|
39
|
+
} as any;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ═══ #1855: empty RecoveryContext (basePath undefined) crashes ════════════════
|
|
43
|
+
|
|
44
|
+
{
|
|
45
|
+
console.log("\n=== #1855: recoverTimedOutUnit crashes when basePath is undefined ===");
|
|
46
|
+
const ctx = makeMockCtx();
|
|
47
|
+
const pi = makeMockPi();
|
|
48
|
+
|
|
49
|
+
// Simulate the bug: buildRecoveryContext returns {} (empty object).
|
|
50
|
+
// basePath is undefined, which causes join(undefined, ".gsd") to throw.
|
|
51
|
+
const emptyRctx = {} as RecoveryContext;
|
|
52
|
+
|
|
53
|
+
let crashed = false;
|
|
54
|
+
try {
|
|
55
|
+
await recoverTimedOutUnit(ctx, pi, "execute-task", "M001/S01/T01", "idle", emptyRctx);
|
|
56
|
+
} catch (err: any) {
|
|
57
|
+
crashed = true;
|
|
58
|
+
assertTrue(
|
|
59
|
+
err.message.includes("path") || err.message.includes("string") || err.code === "ERR_INVALID_ARG_TYPE",
|
|
60
|
+
`should crash with path/type error, got: ${err.message}`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
assertTrue(crashed, "should crash when basePath is undefined (reproduces #1855)");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ═══ #1855: valid RecoveryContext does not crash ═════════════════════════════
|
|
67
|
+
|
|
68
|
+
{
|
|
69
|
+
console.log("\n=== #1855: recoverTimedOutUnit succeeds with valid RecoveryContext ===");
|
|
70
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-stalled-tool-test-"));
|
|
71
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
|
|
72
|
+
mkdirSync(join(base, ".gsd", "runtime", "units"), { recursive: true });
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const ctx = makeMockCtx();
|
|
76
|
+
const pi = makeMockPi();
|
|
77
|
+
|
|
78
|
+
const validRctx: RecoveryContext = {
|
|
79
|
+
basePath: base,
|
|
80
|
+
verbose: false,
|
|
81
|
+
currentUnitStartedAt: Date.now(),
|
|
82
|
+
unitRecoveryCount: new Map(),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
let crashed = false;
|
|
86
|
+
let result: string | undefined;
|
|
87
|
+
try {
|
|
88
|
+
result = await recoverTimedOutUnit(ctx, pi, "execute-task", "M001/S01/T01", "idle", validRctx);
|
|
89
|
+
} catch (err: any) {
|
|
90
|
+
crashed = true;
|
|
91
|
+
console.error(` Unexpected crash: ${err.message}`);
|
|
92
|
+
}
|
|
93
|
+
assertTrue(!crashed, "should not crash with valid basePath");
|
|
94
|
+
// With no runtime record on disk and recoveryAttempts=0, the function
|
|
95
|
+
// should attempt steering recovery (sendMessage) and return "recovered".
|
|
96
|
+
assertTrue(result === "recovered", `should return 'recovered', got '${result}'`);
|
|
97
|
+
} finally {
|
|
98
|
+
rmSync(base, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
report();
|
|
@@ -86,16 +86,17 @@ describe("structured-data-formatter: formatDecisionCompact", () => {
|
|
|
86
86
|
const result = formatDecisionCompact(sampleDecision);
|
|
87
87
|
assert.equal(
|
|
88
88
|
result,
|
|
89
|
-
"D001 | M001/S01 | architecture | Use SQLite for storage | WAL mode, single-writer | Built-in, no external deps | yes",
|
|
89
|
+
"D001 | M001/S01 | architecture | Use SQLite for storage | WAL mode, single-writer | Built-in, no external deps | yes | agent",
|
|
90
90
|
);
|
|
91
91
|
});
|
|
92
92
|
|
|
93
93
|
it("includes all fields in the correct order", () => {
|
|
94
94
|
const result = formatDecisionCompact(sampleDecision);
|
|
95
95
|
const parts = result.split(" | ");
|
|
96
|
-
assert.equal(parts.length,
|
|
96
|
+
assert.equal(parts.length, 8);
|
|
97
97
|
assert.equal(parts[0], "D001");
|
|
98
98
|
assert.equal(parts[6], "yes");
|
|
99
|
+
assert.equal(parts[7], "agent");
|
|
99
100
|
});
|
|
100
101
|
});
|
|
101
102
|
|
|
@@ -107,7 +108,7 @@ describe("structured-data-formatter: formatDecisionsCompact", () => {
|
|
|
107
108
|
it("includes Fields header line", () => {
|
|
108
109
|
const result = formatDecisionsCompact([sampleDecision]);
|
|
109
110
|
assert.ok(result.startsWith("# Decisions (compact)"));
|
|
110
|
-
assert.ok(result.includes("Fields: id | when | scope | decision | choice | rationale | revisable"));
|
|
111
|
+
assert.ok(result.includes("Fields: id | when | scope | decision | choice | rationale | revisable | made_by"));
|
|
111
112
|
});
|
|
112
113
|
|
|
113
114
|
it("formats multiple decisions on separate lines", () => {
|
|
@@ -118,6 +118,51 @@ console.log('\n── Loop guard: arg order is normalized ──');
|
|
|
118
118
|
assertEq(getToolCallLoopCount(), 2, 'Should detect as same call regardless of key order');
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
122
|
+
// Nested/array arguments produce distinct hashes
|
|
123
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
124
|
+
|
|
125
|
+
console.log('\n── Loop guard: nested args are not stripped ──');
|
|
126
|
+
|
|
127
|
+
{
|
|
128
|
+
resetToolCallLoopGuard();
|
|
129
|
+
|
|
130
|
+
// Simulate ask_user_questions-style calls with different nested content
|
|
131
|
+
for (let i = 1; i <= 5; i++) {
|
|
132
|
+
const result = checkToolCallLoop('ask_user_questions', {
|
|
133
|
+
questions: [{ id: `q${i}`, question: `Question ${i}?` }],
|
|
134
|
+
});
|
|
135
|
+
assertTrue(result.block === false, `Nested call ${i} with unique content should be allowed`);
|
|
136
|
+
assertEq(getToolCallLoopCount(), 1, `Each unique nested call should reset count to 1`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Truly identical nested calls should still be detected
|
|
140
|
+
resetToolCallLoopGuard();
|
|
141
|
+
for (let i = 1; i <= 4; i++) {
|
|
142
|
+
checkToolCallLoop('ask_user_questions', {
|
|
143
|
+
questions: [{ id: 'same', question: 'Same?' }],
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
const blocked = checkToolCallLoop('ask_user_questions', {
|
|
147
|
+
questions: [{ id: 'same', question: 'Same?' }],
|
|
148
|
+
});
|
|
149
|
+
assertTrue(blocked.block === true, 'Identical nested calls should still be blocked');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
153
|
+
// Nested object key order is normalized
|
|
154
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
155
|
+
|
|
156
|
+
console.log('\n── Loop guard: nested key order is normalized ──');
|
|
157
|
+
|
|
158
|
+
{
|
|
159
|
+
resetToolCallLoopGuard();
|
|
160
|
+
|
|
161
|
+
checkToolCallLoop('tool', { outer: { b: 2, a: 1 } });
|
|
162
|
+
const result = checkToolCallLoop('tool', { outer: { a: 1, b: 2 } });
|
|
163
|
+
assertEq(getToolCallLoopCount(), 2, 'Same nested args in different key order should match');
|
|
164
|
+
}
|
|
165
|
+
|
|
121
166
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
122
167
|
|
|
123
168
|
report();
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// tool-naming — Verifies canonical + alias tool registration for GSD DB tools.
|
|
2
|
+
//
|
|
3
|
+
// Each of the 4 DB tools must register under its canonical gsd_concept_action name
|
|
4
|
+
// AND under the old gsd_action_concept name as a backward-compatible alias.
|
|
5
|
+
// The alias must share the exact same execute function reference as the canonical tool.
|
|
6
|
+
|
|
7
|
+
import { createTestContext } from './test-helpers.ts';
|
|
8
|
+
import { registerDbTools } from '../bootstrap/db-tools.ts';
|
|
9
|
+
|
|
10
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
11
|
+
|
|
12
|
+
// ─── Mock PI ──────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
function makeMockPi() {
|
|
15
|
+
const tools: any[] = [];
|
|
16
|
+
return {
|
|
17
|
+
registerTool: (tool: any) => tools.push(tool),
|
|
18
|
+
tools,
|
|
19
|
+
} as any;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ─── Rename map ───────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const RENAME_MAP: Array<{ canonical: string; alias: string }> = [
|
|
25
|
+
{ canonical: "gsd_decision_save", alias: "gsd_save_decision" },
|
|
26
|
+
{ canonical: "gsd_requirement_update", alias: "gsd_update_requirement" },
|
|
27
|
+
{ canonical: "gsd_summary_save", alias: "gsd_save_summary" },
|
|
28
|
+
{ canonical: "gsd_milestone_generate_id", alias: "gsd_generate_milestone_id" },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
// ─── Registration count ──────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
console.log('\n── Tool naming: registration count ──');
|
|
34
|
+
|
|
35
|
+
const pi = makeMockPi();
|
|
36
|
+
registerDbTools(pi);
|
|
37
|
+
|
|
38
|
+
assertEq(pi.tools.length, 8, 'Should register exactly 8 tools (4 canonical + 4 aliases)');
|
|
39
|
+
|
|
40
|
+
// ─── Both names exist for each pair ──────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
console.log('\n── Tool naming: canonical and alias names exist ──');
|
|
43
|
+
|
|
44
|
+
for (const { canonical, alias } of RENAME_MAP) {
|
|
45
|
+
const canonicalTool = pi.tools.find((t: any) => t.name === canonical);
|
|
46
|
+
const aliasTool = pi.tools.find((t: any) => t.name === alias);
|
|
47
|
+
|
|
48
|
+
assertTrue(canonicalTool !== undefined, `Canonical tool "${canonical}" should be registered`);
|
|
49
|
+
assertTrue(aliasTool !== undefined, `Alias tool "${alias}" should be registered`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── Execute function identity ───────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
console.log('\n── Tool naming: execute function identity (===) ──');
|
|
55
|
+
|
|
56
|
+
for (const { canonical, alias } of RENAME_MAP) {
|
|
57
|
+
const canonicalTool = pi.tools.find((t: any) => t.name === canonical);
|
|
58
|
+
const aliasTool = pi.tools.find((t: any) => t.name === alias);
|
|
59
|
+
|
|
60
|
+
if (canonicalTool && aliasTool) {
|
|
61
|
+
assertTrue(
|
|
62
|
+
canonicalTool.execute === aliasTool.execute,
|
|
63
|
+
`"${canonical}" and "${alias}" should share the same execute function reference`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── Alias descriptions include "(alias for ...)" ───────────────────────────
|
|
69
|
+
|
|
70
|
+
console.log('\n── Tool naming: alias descriptions ──');
|
|
71
|
+
|
|
72
|
+
for (const { canonical, alias } of RENAME_MAP) {
|
|
73
|
+
const aliasTool = pi.tools.find((t: any) => t.name === alias);
|
|
74
|
+
|
|
75
|
+
if (aliasTool) {
|
|
76
|
+
assertTrue(
|
|
77
|
+
aliasTool.description.includes(`alias for ${canonical}`),
|
|
78
|
+
`Alias "${alias}" description should include "alias for ${canonical}"`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── Canonical tools have proper promptGuidelines ────────────────────────────
|
|
84
|
+
|
|
85
|
+
console.log('\n── Tool naming: canonical promptGuidelines use canonical name ──');
|
|
86
|
+
|
|
87
|
+
for (const { canonical } of RENAME_MAP) {
|
|
88
|
+
const canonicalTool = pi.tools.find((t: any) => t.name === canonical);
|
|
89
|
+
|
|
90
|
+
if (canonicalTool) {
|
|
91
|
+
const guidelinesText = canonicalTool.promptGuidelines.join(' ');
|
|
92
|
+
assertTrue(
|
|
93
|
+
guidelinesText.includes(canonical),
|
|
94
|
+
`Canonical tool "${canonical}" promptGuidelines should reference its own name`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── Alias promptGuidelines direct to canonical ──────────────────────────────
|
|
100
|
+
|
|
101
|
+
console.log('\n── Tool naming: alias promptGuidelines redirect to canonical ──');
|
|
102
|
+
|
|
103
|
+
for (const { canonical, alias } of RENAME_MAP) {
|
|
104
|
+
const aliasTool = pi.tools.find((t: any) => t.name === alias);
|
|
105
|
+
|
|
106
|
+
if (aliasTool) {
|
|
107
|
+
const guidelinesText = aliasTool.promptGuidelines.join(' ');
|
|
108
|
+
assertTrue(
|
|
109
|
+
guidelinesText.includes(`Alias for ${canonical}`),
|
|
110
|
+
`Alias "${alias}" promptGuidelines should say "Alias for ${canonical}"`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
116
|
+
|
|
117
|
+
report();
|
|
@@ -15,6 +15,7 @@ import { fileURLToPath } from "node:url";
|
|
|
15
15
|
|
|
16
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
const hooksPath = join(__dirname, "..", "post-unit-hooks.ts");
|
|
18
|
+
const registryPath = join(__dirname, "..", "rule-registry.ts");
|
|
18
19
|
const autoPromptsPath = join(__dirname, "..", "auto-prompts.ts");
|
|
19
20
|
|
|
20
21
|
// After decomposition, triage/dispatch logic lives in auto-post-unit.ts
|
|
@@ -25,7 +26,11 @@ const autoSrc = [
|
|
|
25
26
|
postUnitSrc,
|
|
26
27
|
readFileSync(join(__dirname, "..", "auto-start.ts"), "utf-8"),
|
|
27
28
|
].join("\n");
|
|
28
|
-
|
|
29
|
+
// Hook exclusion logic lives in the rule-registry (facade delegates there)
|
|
30
|
+
const hooksSrc = [
|
|
31
|
+
readFileSync(hooksPath, "utf-8"),
|
|
32
|
+
readFileSync(registryPath, "utf-8"),
|
|
33
|
+
].join("\n");
|
|
29
34
|
const autoPromptsSrc = (() => { try { return readFileSync(autoPromptsPath, "utf-8"); } catch { return autoSrc; } })();
|
|
30
35
|
|
|
31
36
|
// ─── Hook exclusion ──────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* windows-path-normalization.test.ts — Verify Windows backslash paths are
|
|
3
|
+
* normalised to forward slashes before embedding in bash command strings.
|
|
4
|
+
*
|
|
5
|
+
* Regression test for #1436: on Windows, `cd C:\Users\user\project` in bash
|
|
6
|
+
* strips backslashes (escape characters), producing `C:Usersuserproject`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
10
|
+
|
|
11
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
12
|
+
|
|
13
|
+
// ─── shellEscape + path normalization ──────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
// Replicate the shellEscape helper from cmux/index.ts
|
|
16
|
+
function shellEscape(value: string): string {
|
|
17
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// The bashPath pattern used in subagent/index.ts
|
|
21
|
+
function bashPath(p: string): string {
|
|
22
|
+
return shellEscape(p.replaceAll("\\", "/"));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log("\n=== Windows backslash path normalization (#1436) ===");
|
|
26
|
+
|
|
27
|
+
// Backslash paths are converted to forward slashes
|
|
28
|
+
assertEq(
|
|
29
|
+
bashPath("C:\\Users\\user\\project"),
|
|
30
|
+
"'C:/Users/user/project'",
|
|
31
|
+
"backslash path normalised to forward slashes in shell-escaped string",
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Unix paths pass through unchanged
|
|
35
|
+
assertEq(
|
|
36
|
+
bashPath("/home/user/project"),
|
|
37
|
+
"'/home/user/project'",
|
|
38
|
+
"Unix path unchanged",
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Mixed separators are normalised
|
|
42
|
+
assertEq(
|
|
43
|
+
bashPath("C:\\Users/user\\project/src"),
|
|
44
|
+
"'C:/Users/user/project/src'",
|
|
45
|
+
"mixed separators normalised",
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Paths with single quotes are still properly escaped
|
|
49
|
+
assertEq(
|
|
50
|
+
bashPath("C:\\Users\\o'brien\\project"),
|
|
51
|
+
"'C:/Users/o'\\''brien/project'",
|
|
52
|
+
"single quote in path is escaped after normalisation",
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// UNC paths
|
|
56
|
+
assertEq(
|
|
57
|
+
bashPath("\\\\server\\share\\dir"),
|
|
58
|
+
"'//server/share/dir'",
|
|
59
|
+
"UNC path normalised",
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Empty string
|
|
63
|
+
assertEq(
|
|
64
|
+
bashPath(""),
|
|
65
|
+
"''",
|
|
66
|
+
"empty string handled",
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// ─── cd command construction ───────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
console.log("\n=== cd command construction with normalised paths ===");
|
|
72
|
+
|
|
73
|
+
const windowsCwd = "C:\\Users\\user\\project\\.gsd\\worktrees\\M001";
|
|
74
|
+
const cdCommand = `cd ${bashPath(windowsCwd)}`;
|
|
75
|
+
assertEq(
|
|
76
|
+
cdCommand,
|
|
77
|
+
"cd 'C:/Users/user/project/.gsd/worktrees/M001'",
|
|
78
|
+
"cd command uses forward slashes for Windows worktree path",
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Verify the mangled form from #1436 is NOT produced
|
|
82
|
+
assertTrue(
|
|
83
|
+
!cdCommand.includes("C:Users"),
|
|
84
|
+
"mangled path C:Usersuserproject must not appear",
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// ─── Worktree teardown orphan detection ────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
console.log("\n=== teardown orphan warning path formatting ===");
|
|
90
|
+
|
|
91
|
+
const windowsWtDir = "C:\\Users\\user\\project\\.gsd\\worktrees\\M001";
|
|
92
|
+
const helpCommand = `rm -rf "${windowsWtDir.replaceAll("\\", "/")}"`;
|
|
93
|
+
assertEq(
|
|
94
|
+
helpCommand,
|
|
95
|
+
'rm -rf "C:/Users/user/project/.gsd/worktrees/M001"',
|
|
96
|
+
"orphan cleanup help command uses forward slashes",
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
report();
|
|
@@ -47,6 +47,7 @@ function seedMainDb(dbPath: string): void {
|
|
|
47
47
|
choice: 'node:sqlite',
|
|
48
48
|
rationale: 'Built-in',
|
|
49
49
|
revisable: 'yes',
|
|
50
|
+
made_by: 'agent',
|
|
50
51
|
superseded_by: null,
|
|
51
52
|
});
|
|
52
53
|
insertRequirement({
|
|
@@ -182,6 +183,7 @@ console.log('\n=== worktree-db: reconcileWorktreeDb ===');
|
|
|
182
183
|
choice: 'WAL',
|
|
183
184
|
rationale: 'Performance',
|
|
184
185
|
revisable: 'yes',
|
|
186
|
+
made_by: 'agent',
|
|
185
187
|
superseded_by: null,
|
|
186
188
|
});
|
|
187
189
|
closeDatabase();
|
|
@@ -357,6 +359,7 @@ console.log('\n=== worktree-db: reconcileWorktreeDb ===');
|
|
|
357
359
|
choice: 'yes',
|
|
358
360
|
rationale: 'Robustness',
|
|
359
361
|
revisable: 'no',
|
|
362
|
+
made_by: 'agent',
|
|
360
363
|
superseded_by: null,
|
|
361
364
|
});
|
|
362
365
|
closeDatabase();
|
|
@@ -395,6 +398,7 @@ console.log('\n=== worktree-db: reconcileWorktreeDb ===');
|
|
|
395
398
|
choice: 'works',
|
|
396
399
|
rationale: 'Verify DETACH cleanup',
|
|
397
400
|
revisable: 'no',
|
|
401
|
+
made_by: 'agent',
|
|
398
402
|
superseded_by: null,
|
|
399
403
|
});
|
|
400
404
|
|