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,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* definition-loader.ts — Parse and validate V1 YAML workflow definitions.
|
|
3
|
+
*
|
|
4
|
+
* Loads definition YAML files from `.gsd/workflow-defs/`, validates the
|
|
5
|
+
* V1 schema shape, and returns typed TypeScript objects. Pure functions
|
|
6
|
+
* with no engine or runtime dependencies — just `yaml` and `node:fs`.
|
|
7
|
+
*
|
|
8
|
+
* YAML uses snake_case (`depends_on`, `context_from`) per project convention (P005).
|
|
9
|
+
* TypeScript uses camelCase (`dependsOn`, `contextFrom`).
|
|
10
|
+
*
|
|
11
|
+
* Observability: All validation errors are collected into a string[] — callers
|
|
12
|
+
* can log, surface in dashboards, or return to agents for self-repair.
|
|
13
|
+
* substituteParams errors include the offending key name for traceability.
|
|
14
|
+
*/
|
|
15
|
+
import { parse } from "yaml";
|
|
16
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
// ─── Validation ──────────────────────────────────────────────────────────
|
|
19
|
+
/**
|
|
20
|
+
* Validate a parsed (but untyped) YAML object against the V1 workflow schema.
|
|
21
|
+
*
|
|
22
|
+
* Collects all errors (does not short-circuit) so a single call reveals
|
|
23
|
+
* every problem with the definition.
|
|
24
|
+
*
|
|
25
|
+
* Unknown fields are silently accepted for forward compatibility with
|
|
26
|
+
* S05/S06 features (`context_from`, `verify`, `iterate`).
|
|
27
|
+
*/
|
|
28
|
+
export function validateDefinition(parsed) {
|
|
29
|
+
const errors = [];
|
|
30
|
+
if (parsed == null || typeof parsed !== "object") {
|
|
31
|
+
return { valid: false, errors: ["Definition must be a non-null object"] };
|
|
32
|
+
}
|
|
33
|
+
const def = parsed;
|
|
34
|
+
// version: must be 1 (number)
|
|
35
|
+
if (def.version === undefined || def.version === null) {
|
|
36
|
+
errors.push("Missing required field: version");
|
|
37
|
+
}
|
|
38
|
+
else if (def.version !== 1) {
|
|
39
|
+
errors.push(`Unsupported version: ${def.version} (expected 1)`);
|
|
40
|
+
}
|
|
41
|
+
// name: must be a non-empty string
|
|
42
|
+
if (typeof def.name !== "string" || def.name.trim() === "") {
|
|
43
|
+
errors.push("Missing or empty required field: name");
|
|
44
|
+
}
|
|
45
|
+
// steps: must be a non-empty array
|
|
46
|
+
if (!Array.isArray(def.steps)) {
|
|
47
|
+
errors.push("Missing required field: steps (must be an array)");
|
|
48
|
+
}
|
|
49
|
+
else if (def.steps.length === 0) {
|
|
50
|
+
errors.push("steps must contain at least one step");
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Track whether all steps have valid IDs — graph-level checks only run when true
|
|
54
|
+
let allStepIdsValid = true;
|
|
55
|
+
for (let i = 0; i < def.steps.length; i++) {
|
|
56
|
+
const step = def.steps[i];
|
|
57
|
+
if (step == null || typeof step !== "object") {
|
|
58
|
+
errors.push(`Step at index ${i} is not an object`);
|
|
59
|
+
allStepIdsValid = false;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
// Required step fields
|
|
63
|
+
if (typeof step.id !== "string" || step.id.trim() === "") {
|
|
64
|
+
errors.push(`Step at index ${i} missing required field: id`);
|
|
65
|
+
allStepIdsValid = false;
|
|
66
|
+
}
|
|
67
|
+
if (typeof step.name !== "string" || step.name.trim() === "") {
|
|
68
|
+
errors.push(`Step at index ${i} missing required field: name`);
|
|
69
|
+
}
|
|
70
|
+
if (typeof step.prompt !== "string" || step.prompt.trim() === "") {
|
|
71
|
+
errors.push(`Step at index ${i} missing required field: prompt`);
|
|
72
|
+
}
|
|
73
|
+
// produces: path traversal guard
|
|
74
|
+
if (Array.isArray(step.produces)) {
|
|
75
|
+
for (const p of step.produces) {
|
|
76
|
+
if (typeof p === "string" && p.includes("..")) {
|
|
77
|
+
errors.push(`Step "${step.id}" produces path contains disallowed '..': ${p}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// iterate: optional, but if present must conform to IterateConfig shape
|
|
82
|
+
if (step.iterate !== undefined) {
|
|
83
|
+
const it = step.iterate;
|
|
84
|
+
const sid = typeof step.id === "string" ? step.id : `index ${i}`;
|
|
85
|
+
if (it == null || typeof it !== "object" || Array.isArray(it)) {
|
|
86
|
+
errors.push(`Step "${sid}" iterate must be an object with "source" and "pattern" fields`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const itObj = it;
|
|
90
|
+
if (typeof itObj.source !== "string" || itObj.source.trim() === "") {
|
|
91
|
+
errors.push(`Step "${sid}" iterate.source must be a non-empty string`);
|
|
92
|
+
}
|
|
93
|
+
else if (itObj.source.includes("..")) {
|
|
94
|
+
errors.push(`Step "${sid}" iterate.source contains disallowed '..' path traversal`);
|
|
95
|
+
}
|
|
96
|
+
if (typeof itObj.pattern !== "string" || itObj.pattern.trim() === "") {
|
|
97
|
+
errors.push(`Step "${sid}" iterate.pattern must be a non-empty string`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const pat = itObj.pattern;
|
|
101
|
+
let regexValid = true;
|
|
102
|
+
try {
|
|
103
|
+
new RegExp(pat);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
regexValid = false;
|
|
107
|
+
errors.push(`Step "${sid}" iterate.pattern is not a valid regex: ${pat}`);
|
|
108
|
+
}
|
|
109
|
+
if (regexValid && !/\((?!\?)/.test(pat)) {
|
|
110
|
+
errors.push(`Step "${sid}" iterate.pattern must contain at least one capture group`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// verify: optional, but if present must conform to VerifyPolicy shape
|
|
116
|
+
if (step.verify !== undefined) {
|
|
117
|
+
const v = step.verify;
|
|
118
|
+
const sid = typeof step.id === "string" ? step.id : `index ${i}`;
|
|
119
|
+
if (v == null || typeof v !== "object" || Array.isArray(v)) {
|
|
120
|
+
errors.push(`Step "${sid}" verify must be an object with a "policy" field`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const vObj = v;
|
|
124
|
+
const VALID_POLICIES = ["content-heuristic", "shell-command", "prompt-verify", "human-review"];
|
|
125
|
+
if (typeof vObj.policy !== "string" || !VALID_POLICIES.includes(vObj.policy)) {
|
|
126
|
+
errors.push(`Step "${sid}" verify.policy must be one of: ${VALID_POLICIES.join(", ")}`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Policy-specific required field checks
|
|
130
|
+
if (vObj.policy === "shell-command") {
|
|
131
|
+
if (typeof vObj.command !== "string" || vObj.command.trim() === "") {
|
|
132
|
+
errors.push(`Step "${sid}" verify policy "shell-command" requires a non-empty "command" field`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (vObj.policy === "prompt-verify") {
|
|
136
|
+
if (typeof vObj.prompt !== "string" || vObj.prompt.trim() === "") {
|
|
137
|
+
errors.push(`Step "${sid}" verify policy "prompt-verify" requires a non-empty "prompt" field`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// ─── Graph-level validations (only when all step IDs are valid) ────
|
|
145
|
+
if (allStepIdsValid) {
|
|
146
|
+
const steps = def.steps;
|
|
147
|
+
// 1. Duplicate step ID check
|
|
148
|
+
const idCounts = new Map();
|
|
149
|
+
for (const step of steps) {
|
|
150
|
+
const id = step.id;
|
|
151
|
+
idCounts.set(id, (idCounts.get(id) ?? 0) + 1);
|
|
152
|
+
}
|
|
153
|
+
for (const [id, count] of idCounts) {
|
|
154
|
+
if (count > 1) {
|
|
155
|
+
errors.push(`Duplicate step id: ${id}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Build valid ID set for remaining checks
|
|
159
|
+
const validIds = new Set(steps.map((s) => s.id));
|
|
160
|
+
// 2. Dangling dependency check + 3. Self-referencing dependency check
|
|
161
|
+
for (const step of steps) {
|
|
162
|
+
const sid = step.id;
|
|
163
|
+
const deps = Array.isArray(step.requires)
|
|
164
|
+
? step.requires
|
|
165
|
+
: Array.isArray(step.depends_on)
|
|
166
|
+
? step.depends_on
|
|
167
|
+
: [];
|
|
168
|
+
for (const depId of deps) {
|
|
169
|
+
if (depId === sid) {
|
|
170
|
+
errors.push(`Step '${sid}' depends on itself`);
|
|
171
|
+
}
|
|
172
|
+
else if (!validIds.has(depId)) {
|
|
173
|
+
errors.push(`Step '${sid}' requires unknown step '${depId}'`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// 4. Cycle detection (DFS) — only when no duplicate IDs
|
|
178
|
+
if (![...idCounts.values()].some((c) => c > 1)) {
|
|
179
|
+
// Build adjacency list: step → its dependencies
|
|
180
|
+
const adj = new Map();
|
|
181
|
+
for (const step of steps) {
|
|
182
|
+
const sid = step.id;
|
|
183
|
+
const deps = Array.isArray(step.requires)
|
|
184
|
+
? step.requires
|
|
185
|
+
: Array.isArray(step.depends_on)
|
|
186
|
+
? step.depends_on
|
|
187
|
+
: [];
|
|
188
|
+
adj.set(sid, deps.filter((d) => validIds.has(d) && d !== sid));
|
|
189
|
+
}
|
|
190
|
+
const WHITE = 0, GRAY = 1, BLACK = 2;
|
|
191
|
+
const color = new Map();
|
|
192
|
+
for (const id of validIds)
|
|
193
|
+
color.set(id, WHITE);
|
|
194
|
+
const parent = new Map();
|
|
195
|
+
function dfs(node) {
|
|
196
|
+
color.set(node, GRAY);
|
|
197
|
+
for (const dep of adj.get(node) ?? []) {
|
|
198
|
+
if (color.get(dep) === GRAY) {
|
|
199
|
+
// Back edge found — reconstruct cycle path
|
|
200
|
+
const cycle = [dep, node];
|
|
201
|
+
let cur = node;
|
|
202
|
+
while (parent.has(cur) && parent.get(cur) !== null && parent.get(cur) !== dep) {
|
|
203
|
+
cur = parent.get(cur);
|
|
204
|
+
cycle.push(cur);
|
|
205
|
+
}
|
|
206
|
+
cycle.push(dep);
|
|
207
|
+
cycle.reverse();
|
|
208
|
+
return cycle;
|
|
209
|
+
}
|
|
210
|
+
if (color.get(dep) === WHITE) {
|
|
211
|
+
parent.set(dep, node);
|
|
212
|
+
const result = dfs(dep);
|
|
213
|
+
if (result)
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
color.set(node, BLACK);
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
for (const id of validIds) {
|
|
221
|
+
if (color.get(id) === WHITE) {
|
|
222
|
+
parent.set(id, null);
|
|
223
|
+
const cycle = dfs(id);
|
|
224
|
+
if (cycle) {
|
|
225
|
+
errors.push(`Cycle detected: ${cycle.join(" → ")}`);
|
|
226
|
+
break; // One cycle error is enough
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return { valid: errors.length === 0, errors };
|
|
234
|
+
}
|
|
235
|
+
// ─── Loading ─────────────────────────────────────────────────────────────
|
|
236
|
+
/**
|
|
237
|
+
* Load and validate a YAML workflow definition from the filesystem.
|
|
238
|
+
*
|
|
239
|
+
* Reads `<defsDir>/<name>.yaml`, parses YAML, validates the V1 schema,
|
|
240
|
+
* and converts snake_case YAML keys to camelCase TypeScript types.
|
|
241
|
+
*
|
|
242
|
+
* @param defsDir — directory containing definition YAML files
|
|
243
|
+
* @param name — definition filename without extension
|
|
244
|
+
* @returns Parsed and validated WorkflowDefinition
|
|
245
|
+
* @throws Error if file is missing, YAML is malformed, or schema is invalid
|
|
246
|
+
*/
|
|
247
|
+
export function loadDefinition(defsDir, name) {
|
|
248
|
+
const filePath = join(defsDir, `${name}.yaml`);
|
|
249
|
+
if (!existsSync(filePath)) {
|
|
250
|
+
throw new Error(`Definition file not found: ${filePath}`);
|
|
251
|
+
}
|
|
252
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
253
|
+
let parsed;
|
|
254
|
+
try {
|
|
255
|
+
parsed = parse(raw);
|
|
256
|
+
}
|
|
257
|
+
catch (e) {
|
|
258
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
259
|
+
throw new Error(`Failed to parse YAML in ${filePath}: ${msg}`);
|
|
260
|
+
}
|
|
261
|
+
const { valid, errors } = validateDefinition(parsed);
|
|
262
|
+
if (!valid) {
|
|
263
|
+
throw new Error(`Invalid workflow definition in ${filePath}:\n - ${errors.join("\n - ")}`);
|
|
264
|
+
}
|
|
265
|
+
// Convert snake_case YAML → camelCase TypeScript
|
|
266
|
+
const yamlDef = parsed;
|
|
267
|
+
const yamlSteps = yamlDef.steps;
|
|
268
|
+
return {
|
|
269
|
+
version: yamlDef.version,
|
|
270
|
+
name: yamlDef.name,
|
|
271
|
+
description: typeof yamlDef.description === "string" ? yamlDef.description : undefined,
|
|
272
|
+
params: yamlDef.params != null && typeof yamlDef.params === "object"
|
|
273
|
+
? Object.fromEntries(Object.entries(yamlDef.params).map(([k, v]) => [k, String(v)]))
|
|
274
|
+
: undefined,
|
|
275
|
+
steps: yamlSteps.map((s) => ({
|
|
276
|
+
id: s.id,
|
|
277
|
+
name: s.name,
|
|
278
|
+
prompt: s.prompt,
|
|
279
|
+
requires: Array.isArray(s.requires)
|
|
280
|
+
? s.requires
|
|
281
|
+
: Array.isArray(s.depends_on)
|
|
282
|
+
? s.depends_on
|
|
283
|
+
: [],
|
|
284
|
+
produces: Array.isArray(s.produces) ? s.produces : [],
|
|
285
|
+
contextFrom: Array.isArray(s.context_from) ? s.context_from : undefined,
|
|
286
|
+
verify: s.verify,
|
|
287
|
+
iterate: (s.iterate != null && typeof s.iterate === "object")
|
|
288
|
+
? s.iterate
|
|
289
|
+
: undefined,
|
|
290
|
+
})),
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
// ─── Parameter Substitution ──────────────────────────────────────────────
|
|
294
|
+
/** Regex matching `{{key}}` placeholders — captures the key name. */
|
|
295
|
+
const PARAM_PATTERN = /\{\{(\w+)\}\}/g;
|
|
296
|
+
/**
|
|
297
|
+
* Replace `{{key}}` placeholders in a single prompt string.
|
|
298
|
+
*
|
|
299
|
+
* Exported for use by the engine on iteration-instance prompts that live
|
|
300
|
+
* in GRAPH.yaml (outside the definition's step list).
|
|
301
|
+
*
|
|
302
|
+
* @throws Error if any merged param value contains `..` (path-traversal guard)
|
|
303
|
+
*/
|
|
304
|
+
export function substitutePromptString(prompt, merged) {
|
|
305
|
+
return prompt.replace(PARAM_PATTERN, (match, key) => {
|
|
306
|
+
const value = merged[key];
|
|
307
|
+
return value !== undefined ? value : match;
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Replace `{{key}}` placeholders in all step prompts with param values.
|
|
312
|
+
*
|
|
313
|
+
* Merge order: `definition.params` (defaults) ← `overrides` (CLI wins).
|
|
314
|
+
* Returns a **new** WorkflowDefinition — the input is never mutated.
|
|
315
|
+
*
|
|
316
|
+
* @throws Error if any param value contains `..` (path-traversal guard)
|
|
317
|
+
* @throws Error if any `{{key}}` remains unresolved after substitution
|
|
318
|
+
*/
|
|
319
|
+
export function substituteParams(definition, overrides) {
|
|
320
|
+
const merged = {
|
|
321
|
+
...(definition.params ?? {}),
|
|
322
|
+
...(overrides ?? {}),
|
|
323
|
+
};
|
|
324
|
+
// Path-traversal guard: reject any value containing ".."
|
|
325
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
326
|
+
if (value.includes("..")) {
|
|
327
|
+
throw new Error(`Parameter "${key}" contains disallowed '..' (path traversal): ${value}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Substitute in each step prompt
|
|
331
|
+
const substitutedSteps = definition.steps.map((step) => ({
|
|
332
|
+
...step,
|
|
333
|
+
prompt: substitutePromptString(step.prompt, merged),
|
|
334
|
+
}));
|
|
335
|
+
// Check for unresolved placeholders
|
|
336
|
+
const unresolved = new Set();
|
|
337
|
+
for (const step of substitutedSteps) {
|
|
338
|
+
let m;
|
|
339
|
+
const re = new RegExp(PARAM_PATTERN.source, "g");
|
|
340
|
+
while ((m = re.exec(step.prompt)) !== null) {
|
|
341
|
+
unresolved.add(m[1]);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (unresolved.size > 0) {
|
|
345
|
+
const keys = [...unresolved].sort().join(", ");
|
|
346
|
+
throw new Error(`Unresolved parameter(s) in step prompts: ${keys}`);
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
...definition,
|
|
350
|
+
steps: substitutedSteps,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
@@ -11,7 +11,7 @@ import { homedir } from "node:os";
|
|
|
11
11
|
import { gsdRoot } from "./paths.js";
|
|
12
12
|
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
13
13
|
// ─── Project File Markers ───────────────────────────────────────────────────────
|
|
14
|
-
const PROJECT_FILES = [
|
|
14
|
+
export const PROJECT_FILES = [
|
|
15
15
|
"package.json",
|
|
16
16
|
"Cargo.toml",
|
|
17
17
|
"go.mod",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dev-execution-policy.ts — DevExecutionPolicy implementation.
|
|
3
|
+
*
|
|
4
|
+
* Stub policy for the dev engine. All methods return safe defaults.
|
|
5
|
+
* Real verification/closeout continues running through phases.ts via LoopDeps.
|
|
6
|
+
* Wiring this policy into the loop is S04's responsibility.
|
|
7
|
+
*/
|
|
8
|
+
export class DevExecutionPolicy {
|
|
9
|
+
async prepareWorkspace(_basePath, _milestoneId) {
|
|
10
|
+
// no-op — workspace preparation handled by existing GSD logic
|
|
11
|
+
}
|
|
12
|
+
async selectModel(_unitType, _unitId, _context) {
|
|
13
|
+
return null; // use default model selection
|
|
14
|
+
}
|
|
15
|
+
async verify(_unitType, _unitId, _context) {
|
|
16
|
+
return "continue";
|
|
17
|
+
}
|
|
18
|
+
async recover(_unitType, _unitId, _context) {
|
|
19
|
+
return { outcome: "retry" };
|
|
20
|
+
}
|
|
21
|
+
async closeout(_unitType, _unitId, _context) {
|
|
22
|
+
return { committed: false, artifacts: [] };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dev-workflow-engine.ts — DevWorkflowEngine implementation.
|
|
3
|
+
*
|
|
4
|
+
* Implements WorkflowEngine by delegating to existing GSD state derivation
|
|
5
|
+
* and dispatch logic. This is the "dev" engine — it wraps the current GSD
|
|
6
|
+
* auto-mode behavior behind the engine-polymorphic interface.
|
|
7
|
+
*/
|
|
8
|
+
import { deriveState } from "./state.js";
|
|
9
|
+
import { resolveDispatch } from "./auto-dispatch.js";
|
|
10
|
+
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
11
|
+
// ─── Bridge: DispatchAction → EngineDispatchAction ────────────────────────
|
|
12
|
+
/**
|
|
13
|
+
* Map a GSD-specific DispatchAction (which carries `matchedRule`, `unitType`,
|
|
14
|
+
* etc.) to the engine-generic EngineDispatchAction discriminated union.
|
|
15
|
+
*
|
|
16
|
+
* Exported for unit testing.
|
|
17
|
+
*/
|
|
18
|
+
export function bridgeDispatchAction(da) {
|
|
19
|
+
switch (da.action) {
|
|
20
|
+
case "dispatch":
|
|
21
|
+
return {
|
|
22
|
+
action: "dispatch",
|
|
23
|
+
step: {
|
|
24
|
+
unitType: da.unitType,
|
|
25
|
+
unitId: da.unitId,
|
|
26
|
+
prompt: da.prompt,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
case "stop":
|
|
30
|
+
return {
|
|
31
|
+
action: "stop",
|
|
32
|
+
reason: da.reason,
|
|
33
|
+
level: da.level,
|
|
34
|
+
};
|
|
35
|
+
case "skip":
|
|
36
|
+
return { action: "skip" };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// ─── DevWorkflowEngine ───────────────────────────────────────────────────
|
|
40
|
+
export class DevWorkflowEngine {
|
|
41
|
+
engineId = "dev";
|
|
42
|
+
async deriveState(basePath) {
|
|
43
|
+
const gsd = await deriveState(basePath);
|
|
44
|
+
return {
|
|
45
|
+
phase: gsd.phase,
|
|
46
|
+
currentMilestoneId: gsd.activeMilestone?.id ?? null,
|
|
47
|
+
activeSliceId: gsd.activeSlice?.id ?? null,
|
|
48
|
+
activeTaskId: gsd.activeTask?.id ?? null,
|
|
49
|
+
isComplete: gsd.phase === "complete",
|
|
50
|
+
raw: gsd,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async resolveDispatch(state, context) {
|
|
54
|
+
const gsd = state.raw;
|
|
55
|
+
const mid = gsd.activeMilestone?.id ?? "";
|
|
56
|
+
const midTitle = gsd.activeMilestone?.title ?? "";
|
|
57
|
+
const loaded = loadEffectiveGSDPreferences();
|
|
58
|
+
const prefs = loaded?.preferences ?? undefined;
|
|
59
|
+
const dispatchCtx = {
|
|
60
|
+
basePath: context.basePath,
|
|
61
|
+
mid,
|
|
62
|
+
midTitle,
|
|
63
|
+
state: gsd,
|
|
64
|
+
prefs,
|
|
65
|
+
};
|
|
66
|
+
const result = await resolveDispatch(dispatchCtx);
|
|
67
|
+
return bridgeDispatchAction(result);
|
|
68
|
+
}
|
|
69
|
+
async reconcile(state, _completedStep) {
|
|
70
|
+
return {
|
|
71
|
+
outcome: state.isComplete ? "milestone-complete" : "continue",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
getDisplayMetadata(state) {
|
|
75
|
+
return {
|
|
76
|
+
engineLabel: "GSD Dev",
|
|
77
|
+
currentPhase: state.phase,
|
|
78
|
+
progressSummary: `${state.currentMilestoneId ?? "no milestone"} / ${state.activeSliceId ?? "—"} / ${state.activeTaskId ?? "—"}`,
|
|
79
|
+
stepCount: null,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -745,6 +745,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
745
745
|
}
|
|
746
746
|
catch { /* non-fatal */ }
|
|
747
747
|
let allTasksDone = plan.tasks.length > 0;
|
|
748
|
+
let taskUncheckedByDoctor = false;
|
|
748
749
|
for (const task of plan.tasks) {
|
|
749
750
|
const taskUnitId = `${unitId}/${task.id}`;
|
|
750
751
|
const summaryPath = resolveTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY");
|
|
@@ -762,6 +763,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
762
763
|
dryRunCanFix("task_done_missing_summary", `uncheck ${task.id} in plan for ${taskUnitId}`);
|
|
763
764
|
if (shouldFix("task_done_missing_summary")) {
|
|
764
765
|
await markTaskUndoneInPlan(basePath, milestoneId, slice.id, task.id, fixesApplied);
|
|
766
|
+
taskUncheckedByDoctor = true;
|
|
765
767
|
}
|
|
766
768
|
}
|
|
767
769
|
if (!task.done && hasSummary) {
|
|
@@ -822,6 +824,14 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
822
824
|
}
|
|
823
825
|
allTasksDone = allTasksDone && task.done;
|
|
824
826
|
}
|
|
827
|
+
// ── #1850: cascade slice uncheck when task_done_missing_summary fires ──
|
|
828
|
+
// When doctor unchecks tasks inside a done slice, the slice must also be
|
|
829
|
+
// unchecked so the state machine re-enters the executing phase. Without
|
|
830
|
+
// this, state.ts skips done slices and the unchecked tasks never run,
|
|
831
|
+
// causing doctor to fire again on every start (infinite loop).
|
|
832
|
+
if (taskUncheckedByDoctor && slice.done) {
|
|
833
|
+
await markSliceUndoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
834
|
+
}
|
|
825
835
|
// Blocker-without-replan detection
|
|
826
836
|
const replanPath = resolveSliceFile(basePath, milestoneId, slice.id, "REPLAN");
|
|
827
837
|
if (!replanPath) {
|
|
@@ -898,7 +908,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
898
908
|
fixable: true,
|
|
899
909
|
});
|
|
900
910
|
dryRunCanFix("all_tasks_done_roadmap_not_checked", `mark ${slice.id} done in roadmap`);
|
|
901
|
-
if (shouldFix("all_tasks_done_roadmap_not_checked") && (hasSliceSummary ||
|
|
911
|
+
if (shouldFix("all_tasks_done_roadmap_not_checked") && (hasSliceSummary || existsSync(join(slicePath, `${slice.id}-SUMMARY.md`)))) {
|
|
902
912
|
await markSliceDoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
903
913
|
}
|
|
904
914
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine-resolver.ts — Route sessions to engine/policy pairs.
|
|
3
|
+
*
|
|
4
|
+
* Routes `null` and `"dev"` engine IDs to the DevWorkflowEngine/DevExecutionPolicy
|
|
5
|
+
* pair. Any other non-null engine ID is treated as a custom workflow engine that
|
|
6
|
+
* reads its state from an `activeRunDir`. Respects `GSD_ENGINE_BYPASS=1` kill
|
|
7
|
+
* switch to skip the engine layer entirely.
|
|
8
|
+
*/
|
|
9
|
+
import { DevWorkflowEngine } from "./dev-workflow-engine.js";
|
|
10
|
+
import { DevExecutionPolicy } from "./dev-execution-policy.js";
|
|
11
|
+
import { CustomWorkflowEngine } from "./custom-workflow-engine.js";
|
|
12
|
+
import { CustomExecutionPolicy } from "./custom-execution-policy.js";
|
|
13
|
+
/**
|
|
14
|
+
* Resolve an engine/policy pair for the given session.
|
|
15
|
+
*
|
|
16
|
+
* - `null` or `"dev"` → DevWorkflowEngine + DevExecutionPolicy
|
|
17
|
+
* - any other non-null ID → CustomWorkflowEngine(activeRunDir) + CustomExecutionPolicy()
|
|
18
|
+
* (requires activeRunDir to be a non-empty string)
|
|
19
|
+
*
|
|
20
|
+
* Note: `GSD_ENGINE_BYPASS=1` is checked in autoLoop before calling this function.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveEngine(session) {
|
|
23
|
+
const { activeEngineId, activeRunDir } = session;
|
|
24
|
+
if (activeEngineId === null || activeEngineId === "dev") {
|
|
25
|
+
return {
|
|
26
|
+
engine: new DevWorkflowEngine(),
|
|
27
|
+
policy: new DevExecutionPolicy(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Any non-null, non-"dev" engine ID is a custom workflow engine.
|
|
31
|
+
// activeRunDir is required — the engine reads GRAPH.yaml from it.
|
|
32
|
+
if (!activeRunDir || typeof activeRunDir !== "string") {
|
|
33
|
+
throw new Error(`Custom engine "${activeEngineId}" requires activeRunDir to be a non-empty string, ` +
|
|
34
|
+
`got: ${JSON.stringify(activeRunDir)}`);
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
engine: new CustomWorkflowEngine(activeRunDir),
|
|
38
|
+
policy: new CustomExecutionPolicy(activeRunDir),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine-types.ts — Engine-polymorphic type contracts.
|
|
3
|
+
*
|
|
4
|
+
* LEAF NODE: This file must have ZERO imports from any GSD module.
|
|
5
|
+
* Only `node:` imports are permitted. All engine/policy interfaces
|
|
6
|
+
* depend on these types; nothing here depends on GSD internals.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -4,8 +4,18 @@ export function registerExitCommand(pi, deps = {}) {
|
|
|
4
4
|
description: "Exit GSD gracefully",
|
|
5
5
|
handler: async (_args, ctx) => {
|
|
6
6
|
// Stop auto-mode first so locks and activity state are cleaned up before shutdown.
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// Wrapped in try/catch: if gsd-pi was updated on disk mid-session, the dynamic
|
|
8
|
+
// import may resolve a new auto-worktree.js whose static imports reference
|
|
9
|
+
// exports absent from the process-cached native-git-bridge.js (ESM cache is
|
|
10
|
+
// immutable). The user's work is already saved — this is cleanup only.
|
|
11
|
+
try {
|
|
12
|
+
const stopAuto = deps.stopAuto ?? (await importExtensionModule(import.meta.url, "./auto.js")).stopAuto;
|
|
13
|
+
await stopAuto(ctx, pi, "Graceful exit");
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
17
|
+
ctx.ui?.notify?.(`Auto-mode cleanup skipped (module version mismatch): ${msg}`, "warning");
|
|
18
|
+
}
|
|
9
19
|
ctx.shutdown();
|
|
10
20
|
},
|
|
11
21
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Generate shareable reports of milestone work in JSON or markdown format.
|
|
3
3
|
import { writeFileSync, mkdirSync } from "node:fs";
|
|
4
4
|
import { join, basename } from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { execFile } from "node:child_process";
|
|
6
6
|
import { getLedger, getProjectTotals, aggregateByPhase, aggregateBySlice, aggregateByModel, formatCost, formatTokenCount, loadLedgerFromDisk, } from "./metrics.js";
|
|
7
7
|
import { gsdRoot } from "./paths.js";
|
|
8
8
|
import { formatDuration, fileLink } from "../shared/format-utils.js";
|
|
@@ -13,18 +13,14 @@ import { getErrorMessage } from "./error-utils.js";
|
|
|
13
13
|
* Non-blocking, non-fatal — failures are silently ignored.
|
|
14
14
|
*/
|
|
15
15
|
export function openInBrowser(filePath) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
?
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// Non-fatal — if the browser can't be opened, the file path is still shown
|
|
25
|
-
if (err)
|
|
26
|
-
void err;
|
|
27
|
-
});
|
|
16
|
+
if (process.platform === "win32") {
|
|
17
|
+
// PowerShell's Start-Process handles paths with '&' and spaces safely.
|
|
18
|
+
execFile("powershell", ["-c", `Start-Process '${filePath.replace(/'/g, "''")}'`], () => { });
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
const cmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
22
|
+
execFile(cmd, [filePath], () => { });
|
|
23
|
+
}
|
|
28
24
|
}
|
|
29
25
|
/**
|
|
30
26
|
* Write an export file directly, without requiring an ExtensionCommandContext.
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"provides": {
|
|
9
9
|
"tools": [
|
|
10
10
|
"bash", "write", "read", "edit",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
11
|
+
"gsd_decision_save", "gsd_summary_save",
|
|
12
|
+
"gsd_requirement_update", "gsd_milestone_generate_id"
|
|
13
13
|
],
|
|
14
14
|
"commands": ["gsd", "kill", "worktree", "exit"],
|
|
15
15
|
"hooks": ["session_start"],
|