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,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* worktree-health-dispatch.test.ts — Regression tests for the worktree health
|
|
3
|
+
* check in auto/phases.ts (#1833, #1843).
|
|
4
|
+
*
|
|
5
|
+
* Verifies that the pre-dispatch health check recognises non-JS project types
|
|
6
|
+
* (Rust, Go, Python, etc.) via the shared PROJECT_FILES list from detection.ts,
|
|
7
|
+
* rather than hard-coding package.json / src/ only.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import test from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { execSync } from "node:child_process";
|
|
16
|
+
|
|
17
|
+
import { PROJECT_FILES } from "../detection.js";
|
|
18
|
+
|
|
19
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/** Create a minimal git repo and return its path. */
|
|
22
|
+
function createGitRepo(): string {
|
|
23
|
+
const dir = mkdtempSync(join(tmpdir(), "wt-dispatch-test-"));
|
|
24
|
+
// All execSync calls use hardcoded strings only — no user input, no injection risk.
|
|
25
|
+
execSync("git init", { cwd: dir, stdio: "ignore" });
|
|
26
|
+
execSync("git config user.email test@test.com", { cwd: dir, stdio: "ignore" });
|
|
27
|
+
execSync("git config user.name Test", { cwd: dir, stdio: "ignore" });
|
|
28
|
+
writeFileSync(join(dir, "README.md"), "# test\n");
|
|
29
|
+
execSync("git add . && git commit -m init", { cwd: dir, stdio: "ignore" });
|
|
30
|
+
return dir;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Simulate the health check logic from auto/phases.ts.
|
|
35
|
+
*
|
|
36
|
+
* Returns true when the directory would PASS the health check (dispatch
|
|
37
|
+
* proceeds), false when it would FAIL (dispatch blocked).
|
|
38
|
+
*
|
|
39
|
+
* This mirrors the fixed logic: .git must exist, AND at least one
|
|
40
|
+
* PROJECT_FILES entry or a src/ directory must exist.
|
|
41
|
+
*/
|
|
42
|
+
function wouldPassHealthCheck(basePath: string, existsSyncFn: (p: string) => boolean): boolean {
|
|
43
|
+
const hasGit = existsSyncFn(join(basePath, ".git"));
|
|
44
|
+
if (!hasGit) return false;
|
|
45
|
+
|
|
46
|
+
for (const file of PROJECT_FILES) {
|
|
47
|
+
if (existsSyncFn(join(basePath, file))) return true;
|
|
48
|
+
}
|
|
49
|
+
if (existsSyncFn(join(basePath, "src"))) return true;
|
|
50
|
+
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
import { existsSync } from "node:fs";
|
|
55
|
+
|
|
56
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
test("PROJECT_FILES is exported and contains expected multi-ecosystem entries", () => {
|
|
59
|
+
assert.ok(Array.isArray(PROJECT_FILES), "PROJECT_FILES is an array");
|
|
60
|
+
assert.ok(PROJECT_FILES.length >= 17, `expected >= 17 entries, got ${PROJECT_FILES.length}`);
|
|
61
|
+
// Spot-check key ecosystems
|
|
62
|
+
assert.ok(PROJECT_FILES.includes("Cargo.toml"), "includes Rust marker");
|
|
63
|
+
assert.ok(PROJECT_FILES.includes("go.mod"), "includes Go marker");
|
|
64
|
+
assert.ok(PROJECT_FILES.includes("pyproject.toml"), "includes Python marker");
|
|
65
|
+
assert.ok(PROJECT_FILES.includes("package.json"), "includes JS marker");
|
|
66
|
+
assert.ok(PROJECT_FILES.includes("pom.xml"), "includes Java marker");
|
|
67
|
+
assert.ok(PROJECT_FILES.includes("Package.swift"), "includes Swift marker");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("health check passes for Rust project (Cargo.toml, no package.json)", () => {
|
|
71
|
+
const dir = createGitRepo();
|
|
72
|
+
try {
|
|
73
|
+
writeFileSync(join(dir, "Cargo.toml"), "[package]\nname = \"test\"\n");
|
|
74
|
+
mkdirSync(join(dir, "crates"), { recursive: true });
|
|
75
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "Rust project should pass health check");
|
|
76
|
+
} finally {
|
|
77
|
+
rmSync(dir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("health check passes for Go project (go.mod, no package.json)", () => {
|
|
82
|
+
const dir = createGitRepo();
|
|
83
|
+
try {
|
|
84
|
+
writeFileSync(join(dir, "go.mod"), "module example.com/test\n\ngo 1.21\n");
|
|
85
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "Go project should pass health check");
|
|
86
|
+
} finally {
|
|
87
|
+
rmSync(dir, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("health check passes for Python project (pyproject.toml, no package.json)", () => {
|
|
92
|
+
const dir = createGitRepo();
|
|
93
|
+
try {
|
|
94
|
+
writeFileSync(join(dir, "pyproject.toml"), "[project]\nname = \"test\"\n");
|
|
95
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "Python project should pass health check");
|
|
96
|
+
} finally {
|
|
97
|
+
rmSync(dir, { recursive: true, force: true });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("health check passes for Java project (pom.xml, no package.json)", () => {
|
|
102
|
+
const dir = createGitRepo();
|
|
103
|
+
try {
|
|
104
|
+
writeFileSync(join(dir, "pom.xml"), "<project></project>\n");
|
|
105
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "Java project should pass health check");
|
|
106
|
+
} finally {
|
|
107
|
+
rmSync(dir, { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("health check passes for Swift project (Package.swift, no package.json)", () => {
|
|
112
|
+
const dir = createGitRepo();
|
|
113
|
+
try {
|
|
114
|
+
writeFileSync(join(dir, "Package.swift"), "// swift-tools-version:5.7\n");
|
|
115
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "Swift project should pass health check");
|
|
116
|
+
} finally {
|
|
117
|
+
rmSync(dir, { recursive: true, force: true });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("health check passes for C/C++ project (CMakeLists.txt, no package.json)", () => {
|
|
122
|
+
const dir = createGitRepo();
|
|
123
|
+
try {
|
|
124
|
+
writeFileSync(join(dir, "CMakeLists.txt"), "cmake_minimum_required(VERSION 3.20)\n");
|
|
125
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "C/C++ project should pass health check");
|
|
126
|
+
} finally {
|
|
127
|
+
rmSync(dir, { recursive: true, force: true });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("health check passes for Elixir project (mix.exs, no package.json)", () => {
|
|
132
|
+
const dir = createGitRepo();
|
|
133
|
+
try {
|
|
134
|
+
writeFileSync(join(dir, "mix.exs"), "defmodule Test.MixProject do\nend\n");
|
|
135
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "Elixir project should pass health check");
|
|
136
|
+
} finally {
|
|
137
|
+
rmSync(dir, { recursive: true, force: true });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("health check passes for JS project (package.json, backward compat)", () => {
|
|
142
|
+
const dir = createGitRepo();
|
|
143
|
+
try {
|
|
144
|
+
writeFileSync(join(dir, "package.json"), '{"name":"test"}\n');
|
|
145
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "JS project should pass health check");
|
|
146
|
+
} finally {
|
|
147
|
+
rmSync(dir, { recursive: true, force: true });
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("health check passes for src/-only project (backward compat)", () => {
|
|
152
|
+
const dir = createGitRepo();
|
|
153
|
+
try {
|
|
154
|
+
mkdirSync(join(dir, "src"), { recursive: true });
|
|
155
|
+
assert.ok(wouldPassHealthCheck(dir, existsSync), "src/-only project should pass health check");
|
|
156
|
+
} finally {
|
|
157
|
+
rmSync(dir, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("health check fails for directory with no .git", () => {
|
|
162
|
+
const dir = mkdtempSync(join(tmpdir(), "wt-dispatch-test-nogit-"));
|
|
163
|
+
try {
|
|
164
|
+
writeFileSync(join(dir, "Cargo.toml"), "[package]\nname = \"test\"\n");
|
|
165
|
+
assert.ok(!wouldPassHealthCheck(dir, existsSync), "no-git directory should fail health check");
|
|
166
|
+
} finally {
|
|
167
|
+
rmSync(dir, { recursive: true, force: true });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("health check fails for empty git repo with no project files", () => {
|
|
172
|
+
const dir = createGitRepo();
|
|
173
|
+
try {
|
|
174
|
+
assert.ok(!wouldPassHealthCheck(dir, existsSync), "empty git repo should fail health check");
|
|
175
|
+
} finally {
|
|
176
|
+
rmSync(dir, { recursive: true, force: true });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync } from "node:fs";
|
|
2
4
|
import { join } from "node:path";
|
|
3
5
|
import { tmpdir } from "node:os";
|
|
4
6
|
import { execSync } from "node:child_process";
|
|
@@ -13,82 +15,42 @@ import {
|
|
|
13
15
|
worktreeBranchName,
|
|
14
16
|
worktreePath,
|
|
15
17
|
} from "../worktree-manager.ts";
|
|
16
|
-
import { createTestContext } from './test-helpers.ts';
|
|
17
18
|
|
|
18
|
-
const { assertEq, assertTrue, report } = createTestContext();
|
|
19
19
|
function run(command: string, cwd: string): string {
|
|
20
20
|
return execSync(command, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
const base = mkdtempSync(join(tmpdir(), "gsd-
|
|
25
|
-
run("git init -b main", base);
|
|
26
|
-
run('git config user.name "
|
|
27
|
-
run('git config user.email "
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
async function main(): Promise<void> {
|
|
41
|
-
console.log("\n=== worktreeBranchName ===");
|
|
42
|
-
assertEq(worktreeBranchName("feature-x"), "worktree/feature-x", "branch name format");
|
|
43
|
-
|
|
44
|
-
console.log("\n=== createWorktree ===");
|
|
45
|
-
const info = createWorktree(base, "feature-x");
|
|
46
|
-
assertTrue(info.name === "feature-x", "name matches");
|
|
47
|
-
assertTrue(info.branch === "worktree/feature-x", "branch matches");
|
|
48
|
-
assertTrue(info.exists, "worktree exists");
|
|
49
|
-
assertTrue(existsSync(info.path), "worktree path exists on disk");
|
|
50
|
-
assertTrue(existsSync(join(info.path, "README.md")), "README.md copied to worktree");
|
|
51
|
-
assertTrue(existsSync(join(info.path, ".gsd", "milestones", "M001", "M001-ROADMAP.md")), ".gsd files copied");
|
|
52
|
-
|
|
53
|
-
// Branch was created
|
|
54
|
-
const branches = run("git branch", base);
|
|
55
|
-
assertTrue(branches.includes("worktree/feature-x"), "branch was created");
|
|
56
|
-
|
|
57
|
-
console.log("\n=== createWorktree — duplicate ===");
|
|
58
|
-
let duplicateError = "";
|
|
59
|
-
try {
|
|
60
|
-
createWorktree(base, "feature-x");
|
|
61
|
-
} catch (e) {
|
|
62
|
-
duplicateError = (e as Error).message;
|
|
63
|
-
}
|
|
64
|
-
assertTrue(duplicateError.includes("already exists"), "duplicate creation fails");
|
|
23
|
+
function makeBaseRepo(): string {
|
|
24
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-wt-test-"));
|
|
25
|
+
run("git init -b main", base);
|
|
26
|
+
run('git config user.name "Test User"', base);
|
|
27
|
+
run('git config user.email "test@example.com"', base);
|
|
28
|
+
mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
|
|
29
|
+
writeFileSync(join(base, "README.md"), "# Test Project\n", "utf-8");
|
|
30
|
+
writeFileSync(
|
|
31
|
+
join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
|
|
32
|
+
"# M001: Demo\n\n## Slices\n- [ ] **S01: First** `risk:low` `depends:[]`\n > After this: it works\n",
|
|
33
|
+
"utf-8",
|
|
34
|
+
);
|
|
35
|
+
run("git add .", base);
|
|
36
|
+
run('git commit -m "chore: init"', base);
|
|
37
|
+
return base;
|
|
38
|
+
}
|
|
65
39
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
console.log("\n=== listWorktrees ===");
|
|
76
|
-
const list = listWorktrees(base);
|
|
77
|
-
assertEq(list.length, 1, "one worktree listed");
|
|
78
|
-
assertEq(list[0]!.name, "feature-x", "correct name");
|
|
79
|
-
assertEq(list[0]!.branch, "worktree/feature-x", "correct branch");
|
|
80
|
-
assertTrue(list[0]!.exists, "exists flag is true");
|
|
81
|
-
|
|
82
|
-
console.log("\n=== make changes in worktree ===");
|
|
83
|
-
const wtPath = worktreePath(base, "feature-x");
|
|
84
|
-
// Add a new GSD artifact in the worktree
|
|
40
|
+
function makeRepoWithWorktree(worktreeName: string): { base: string; wtPath: string } {
|
|
41
|
+
const base = makeBaseRepo();
|
|
42
|
+
createWorktree(base, worktreeName);
|
|
43
|
+
return { base, wtPath: worktreePath(base, worktreeName) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function makeRepoWithChanges(worktreeName: string): { base: string; wtPath: string } {
|
|
47
|
+
const { base, wtPath } = makeRepoWithWorktree(worktreeName);
|
|
85
48
|
mkdirSync(join(wtPath, ".gsd", "milestones", "M002"), { recursive: true });
|
|
86
49
|
writeFileSync(
|
|
87
50
|
join(wtPath, ".gsd", "milestones", "M002", "M002-ROADMAP.md"),
|
|
88
51
|
"# M002: New Feature\n\n## Slices\n- [ ] **S01: Setup** `risk:low` `depends:[]`\n > After this: new feature ready\n",
|
|
89
52
|
"utf-8",
|
|
90
53
|
);
|
|
91
|
-
// Modify an existing artifact
|
|
92
54
|
writeFileSync(
|
|
93
55
|
join(wtPath, ".gsd", "milestones", "M001", "M001-ROADMAP.md"),
|
|
94
56
|
"# M001: Demo (updated)\n\n## Slices\n- [x] **S01: First** `risk:low` `depends:[]`\n > Done\n",
|
|
@@ -96,46 +58,174 @@ async function main(): Promise<void> {
|
|
|
96
58
|
);
|
|
97
59
|
run("git add .", wtPath);
|
|
98
60
|
run('git commit -m "feat: add M002 and update M001"', wtPath);
|
|
99
|
-
|
|
100
|
-
console.log("\n=== diffWorktreeGSD ===");
|
|
101
|
-
const diff = diffWorktreeGSD(base, "feature-x");
|
|
102
|
-
assertTrue(diff.added.length > 0, "has added files");
|
|
103
|
-
assertTrue(diff.added.some(f => f.includes("M002")), "M002 roadmap is in added");
|
|
104
|
-
assertTrue(diff.modified.length > 0, "has modified files");
|
|
105
|
-
assertTrue(diff.modified.some(f => f.includes("M001")), "M001 roadmap is in modified");
|
|
106
|
-
assertEq(diff.removed.length, 0, "no removed files");
|
|
107
|
-
|
|
108
|
-
console.log("\n=== getWorktreeGSDDiff ===");
|
|
109
|
-
const fullDiff = getWorktreeGSDDiff(base, "feature-x");
|
|
110
|
-
assertTrue(fullDiff.includes("M002"), "full diff mentions M002");
|
|
111
|
-
assertTrue(fullDiff.includes("updated"), "full diff mentions update");
|
|
112
|
-
|
|
113
|
-
console.log("\n=== getWorktreeLog ===");
|
|
114
|
-
const log = getWorktreeLog(base, "feature-x");
|
|
115
|
-
assertTrue(log.includes("add M002"), "log shows commit message");
|
|
116
|
-
|
|
117
|
-
console.log("\n=== removeWorktree ===");
|
|
118
|
-
removeWorktree(base, "feature-x", { deleteBranch: true });
|
|
119
|
-
assertTrue(!existsSync(wtPath), "worktree directory removed");
|
|
120
|
-
const branchesAfter = run("git branch", base);
|
|
121
|
-
assertTrue(!branchesAfter.includes("worktree/feature-x"), "branch deleted");
|
|
122
|
-
|
|
123
|
-
console.log("\n=== listWorktrees after removal ===");
|
|
124
|
-
const listAfter = listWorktrees(base);
|
|
125
|
-
assertEq(listAfter.length, 0, "no worktrees after removal");
|
|
126
|
-
|
|
127
|
-
console.log("\n=== removeWorktree — already gone ===");
|
|
128
|
-
// Should not throw
|
|
129
|
-
removeWorktree(base, "feature-x", { deleteBranch: true });
|
|
130
|
-
assertTrue(true, "removeWorktree on missing worktree does not throw");
|
|
131
|
-
|
|
132
|
-
// Cleanup
|
|
133
|
-
rmSync(base, { recursive: true, force: true });
|
|
134
|
-
|
|
135
|
-
report();
|
|
61
|
+
return { base, wtPath };
|
|
136
62
|
}
|
|
137
63
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
64
|
+
// ─── worktreeBranchName ───────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
test("worktreeBranchName formats branch name", () => {
|
|
67
|
+
assert.strictEqual(
|
|
68
|
+
worktreeBranchName("feature-x"),
|
|
69
|
+
"worktree/feature-x",
|
|
70
|
+
"should prefix with worktree/",
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// ─── createWorktree ───────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
test("createWorktree creates worktree with correct metadata", () => {
|
|
77
|
+
const base = makeBaseRepo();
|
|
78
|
+
try {
|
|
79
|
+
const info = createWorktree(base, "feature-x");
|
|
80
|
+
assert.strictEqual(info.name, "feature-x", "name should match");
|
|
81
|
+
assert.strictEqual(info.branch, "worktree/feature-x", "branch should be prefixed");
|
|
82
|
+
assert.ok(info.exists, "exists flag should be true");
|
|
83
|
+
assert.ok(existsSync(info.path), "worktree path should exist on disk");
|
|
84
|
+
assert.ok(existsSync(join(info.path, "README.md")), "README.md should be in worktree");
|
|
85
|
+
assert.ok(
|
|
86
|
+
existsSync(join(info.path, ".gsd", "milestones", "M001", "M001-ROADMAP.md")),
|
|
87
|
+
".gsd files should be in worktree",
|
|
88
|
+
);
|
|
89
|
+
const branches = run("git branch", base);
|
|
90
|
+
assert.ok(branches.includes("worktree/feature-x"), "branch should be created in base repo");
|
|
91
|
+
} finally {
|
|
92
|
+
rmSync(base, { recursive: true, force: true });
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("createWorktree rejects duplicate name", () => {
|
|
97
|
+
const { base } = makeRepoWithWorktree("feature-x");
|
|
98
|
+
try {
|
|
99
|
+
assert.throws(
|
|
100
|
+
() => createWorktree(base, "feature-x"),
|
|
101
|
+
(err: Error) => {
|
|
102
|
+
assert.ok(
|
|
103
|
+
err.message.includes("already exists"),
|
|
104
|
+
`expected "already exists" in error, got: ${err.message}`,
|
|
105
|
+
);
|
|
106
|
+
return true;
|
|
107
|
+
},
|
|
108
|
+
"should throw on duplicate worktree name",
|
|
109
|
+
);
|
|
110
|
+
} finally {
|
|
111
|
+
rmSync(base, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("createWorktree rejects invalid name", () => {
|
|
116
|
+
const base = makeBaseRepo();
|
|
117
|
+
try {
|
|
118
|
+
assert.throws(
|
|
119
|
+
() => createWorktree(base, "bad name!"),
|
|
120
|
+
(err: Error) => {
|
|
121
|
+
assert.ok(
|
|
122
|
+
err.message.includes("Invalid worktree name"),
|
|
123
|
+
`expected "Invalid worktree name" in error, got: ${err.message}`,
|
|
124
|
+
);
|
|
125
|
+
return true;
|
|
126
|
+
},
|
|
127
|
+
"should throw on invalid worktree name",
|
|
128
|
+
);
|
|
129
|
+
} finally {
|
|
130
|
+
rmSync(base, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ─── listWorktrees ────────────────────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
test("listWorktrees returns active worktrees", () => {
|
|
137
|
+
const { base } = makeRepoWithWorktree("feature-x");
|
|
138
|
+
try {
|
|
139
|
+
const list = listWorktrees(base);
|
|
140
|
+
assert.strictEqual(list.length, 1, "should list exactly one worktree");
|
|
141
|
+
assert.strictEqual(list[0]!.name, "feature-x", "name should match");
|
|
142
|
+
assert.strictEqual(list[0]!.branch, "worktree/feature-x", "branch should match");
|
|
143
|
+
assert.ok(list[0]!.exists, "exists flag should be true");
|
|
144
|
+
} finally {
|
|
145
|
+
rmSync(base, { recursive: true, force: true });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("listWorktrees returns empty after removal", () => {
|
|
150
|
+
const { base } = makeRepoWithWorktree("feature-x");
|
|
151
|
+
try {
|
|
152
|
+
removeWorktree(base, "feature-x");
|
|
153
|
+
const list = listWorktrees(base);
|
|
154
|
+
assert.strictEqual(list.length, 0, "should have no worktrees after removal");
|
|
155
|
+
} finally {
|
|
156
|
+
rmSync(base, { recursive: true, force: true });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ─── diffWorktreeGSD ─────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
test("diffWorktreeGSD detects added and modified GSD files", () => {
|
|
163
|
+
const { base } = makeRepoWithChanges("feature-x");
|
|
164
|
+
try {
|
|
165
|
+
const diff = diffWorktreeGSD(base, "feature-x");
|
|
166
|
+
assert.ok(diff.added.length > 0, "should have added files");
|
|
167
|
+
assert.ok(
|
|
168
|
+
diff.added.some((f) => f.includes("M002")),
|
|
169
|
+
"M002 roadmap should be in added files",
|
|
170
|
+
);
|
|
171
|
+
assert.ok(diff.modified.length > 0, "should have modified files");
|
|
172
|
+
assert.ok(
|
|
173
|
+
diff.modified.some((f) => f.includes("M001")),
|
|
174
|
+
"M001 roadmap should be in modified files",
|
|
175
|
+
);
|
|
176
|
+
assert.strictEqual(diff.removed.length, 0, "should have no removed files");
|
|
177
|
+
} finally {
|
|
178
|
+
rmSync(base, { recursive: true, force: true });
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// ─── getWorktreeGSDDiff ───────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
test("getWorktreeGSDDiff returns patch content", () => {
|
|
185
|
+
const { base } = makeRepoWithChanges("feature-x");
|
|
186
|
+
try {
|
|
187
|
+
const fullDiff = getWorktreeGSDDiff(base, "feature-x");
|
|
188
|
+
assert.ok(fullDiff.includes("M002"), "diff should mention M002");
|
|
189
|
+
assert.ok(fullDiff.includes("updated"), "diff should mention the update");
|
|
190
|
+
} finally {
|
|
191
|
+
rmSync(base, { recursive: true, force: true });
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// ─── getWorktreeLog ───────────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
test("getWorktreeLog shows commits", () => {
|
|
198
|
+
const { base } = makeRepoWithChanges("feature-x");
|
|
199
|
+
try {
|
|
200
|
+
const log = getWorktreeLog(base, "feature-x");
|
|
201
|
+
assert.ok(log.includes("add M002"), "log should include the commit message");
|
|
202
|
+
} finally {
|
|
203
|
+
rmSync(base, { recursive: true, force: true });
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// ─── removeWorktree ───────────────────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
test("removeWorktree removes directory and branch", () => {
|
|
210
|
+
const { base, wtPath } = makeRepoWithWorktree("feature-x");
|
|
211
|
+
try {
|
|
212
|
+
removeWorktree(base, "feature-x", { deleteBranch: true });
|
|
213
|
+
assert.ok(!existsSync(wtPath), "worktree directory should be gone");
|
|
214
|
+
const branches = run("git branch", base);
|
|
215
|
+
assert.ok(!branches.includes("worktree/feature-x"), "branch should be deleted");
|
|
216
|
+
} finally {
|
|
217
|
+
rmSync(base, { recursive: true, force: true });
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("removeWorktree on missing worktree does not throw", () => {
|
|
222
|
+
const base = makeBaseRepo();
|
|
223
|
+
try {
|
|
224
|
+
assert.doesNotThrow(
|
|
225
|
+
() => removeWorktree(base, "nonexistent"),
|
|
226
|
+
"should not throw when worktree does not exist",
|
|
227
|
+
);
|
|
228
|
+
} finally {
|
|
229
|
+
rmSync(base, { recursive: true, force: true });
|
|
230
|
+
}
|
|
141
231
|
});
|
|
@@ -52,7 +52,7 @@ function makeDeps(
|
|
|
52
52
|
fn: "mergeMilestoneToMain",
|
|
53
53
|
args: [basePath, milestoneId, roadmapContent],
|
|
54
54
|
});
|
|
55
|
-
return { pushed: false };
|
|
55
|
+
return { pushed: false, codeFilesChanged: true };
|
|
56
56
|
},
|
|
57
57
|
syncWorktreeStateBack: (
|
|
58
58
|
mainBasePath: string,
|
|
@@ -424,7 +424,7 @@ test("mergeAndExit in worktree mode shows pushed status", () => {
|
|
|
424
424
|
const deps = makeDeps({
|
|
425
425
|
isInAutoWorktree: () => true,
|
|
426
426
|
getIsolationMode: () => "worktree",
|
|
427
|
-
mergeMilestoneToMain: () => ({ pushed: true }),
|
|
427
|
+
mergeMilestoneToMain: () => ({ pushed: true, codeFilesChanged: true }),
|
|
428
428
|
});
|
|
429
429
|
const ctx = makeNotifyCtx();
|
|
430
430
|
const resolver = new WorktreeResolver(s, deps);
|
|
@@ -659,6 +659,81 @@ test("mergeAndExit in none mode is a no-op", () => {
|
|
|
659
659
|
assert.equal(ctx.messages.length, 0);
|
|
660
660
|
});
|
|
661
661
|
|
|
662
|
+
// ─── #1906 — metadata-only merge warning ────────────────────────────────────
|
|
663
|
+
|
|
664
|
+
test("mergeAndExit warns when merge contains no code changes (#1906)", () => {
|
|
665
|
+
const s = makeSession({
|
|
666
|
+
basePath: "/project/.gsd/worktrees/M001",
|
|
667
|
+
originalBasePath: "/project",
|
|
668
|
+
});
|
|
669
|
+
const deps = makeDeps({
|
|
670
|
+
isInAutoWorktree: () => true,
|
|
671
|
+
getIsolationMode: () => "worktree",
|
|
672
|
+
mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: false }),
|
|
673
|
+
});
|
|
674
|
+
const ctx = makeNotifyCtx();
|
|
675
|
+
const resolver = new WorktreeResolver(s, deps);
|
|
676
|
+
|
|
677
|
+
resolver.mergeAndExit("M001", ctx);
|
|
678
|
+
|
|
679
|
+
assert.ok(
|
|
680
|
+
ctx.messages.some((m) => m.msg.includes("NO code changes") && m.level === "warning"),
|
|
681
|
+
"must emit warning when only .gsd/ metadata was merged",
|
|
682
|
+
);
|
|
683
|
+
assert.ok(
|
|
684
|
+
!ctx.messages.some((m) => m.msg.includes("merged to main") && m.level === "info"),
|
|
685
|
+
"must NOT emit success-style info notification for metadata-only merge",
|
|
686
|
+
);
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
test("mergeAndExit emits info when merge contains code changes (#1906)", () => {
|
|
690
|
+
const s = makeSession({
|
|
691
|
+
basePath: "/project/.gsd/worktrees/M001",
|
|
692
|
+
originalBasePath: "/project",
|
|
693
|
+
});
|
|
694
|
+
const deps = makeDeps({
|
|
695
|
+
isInAutoWorktree: () => true,
|
|
696
|
+
getIsolationMode: () => "worktree",
|
|
697
|
+
mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: true }),
|
|
698
|
+
});
|
|
699
|
+
const ctx = makeNotifyCtx();
|
|
700
|
+
const resolver = new WorktreeResolver(s, deps);
|
|
701
|
+
|
|
702
|
+
resolver.mergeAndExit("M001", ctx);
|
|
703
|
+
|
|
704
|
+
assert.ok(
|
|
705
|
+
ctx.messages.some((m) => m.msg.includes("merged to main") && m.level === "info"),
|
|
706
|
+
"must emit info notification when code files were merged",
|
|
707
|
+
);
|
|
708
|
+
assert.ok(
|
|
709
|
+
!ctx.messages.some((m) => m.msg.includes("NO code changes")),
|
|
710
|
+
"must NOT emit metadata-only warning when code files were merged",
|
|
711
|
+
);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
test("mergeAndExit branch mode warns when merge contains no code changes (#1906)", () => {
|
|
715
|
+
const s = makeSession({
|
|
716
|
+
basePath: "/project",
|
|
717
|
+
originalBasePath: "/project",
|
|
718
|
+
});
|
|
719
|
+
const deps = makeDeps({
|
|
720
|
+
isInAutoWorktree: () => false,
|
|
721
|
+
getIsolationMode: () => "branch",
|
|
722
|
+
getCurrentBranch: () => "milestone/M001",
|
|
723
|
+
autoWorktreeBranch: () => "milestone/M001",
|
|
724
|
+
mergeMilestoneToMain: () => ({ pushed: false, codeFilesChanged: false }),
|
|
725
|
+
});
|
|
726
|
+
const ctx = makeNotifyCtx();
|
|
727
|
+
const resolver = new WorktreeResolver(s, deps);
|
|
728
|
+
|
|
729
|
+
resolver.mergeAndExit("M001", ctx);
|
|
730
|
+
|
|
731
|
+
assert.ok(
|
|
732
|
+
ctx.messages.some((m) => m.msg.includes("NO code changes") && m.level === "warning"),
|
|
733
|
+
"branch mode must emit warning when only .gsd/ metadata was merged",
|
|
734
|
+
);
|
|
735
|
+
});
|
|
736
|
+
|
|
662
737
|
// ─── mergeAndEnterNext Tests ─────────────────────────────────────────────────
|
|
663
738
|
|
|
664
739
|
test("mergeAndEnterNext calls mergeAndExit then enterMilestone", () => {
|
|
@@ -677,7 +752,7 @@ test("mergeAndEnterNext calls mergeAndExit then enterMilestone", () => {
|
|
|
677
752
|
_roadmap: string,
|
|
678
753
|
) => {
|
|
679
754
|
callOrder.push(`merge:${milestoneId}`);
|
|
680
|
-
return { pushed: false };
|
|
755
|
+
return { pushed: false, codeFilesChanged: true };
|
|
681
756
|
},
|
|
682
757
|
getAutoWorktreePath: () => null,
|
|
683
758
|
createAutoWorktree: (basePath: string, milestoneId: string) => {
|