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
|
@@ -1,436 +1,48 @@
|
|
|
1
|
-
// GSD Extension — Hook Engine
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/** Queue of hooks remaining for the current trigger unit. */
|
|
11
|
-
let hookQueue = [];
|
|
12
|
-
/** Cycle counts per hook+trigger, keyed as "hookName/triggerUnitType/triggerUnitId". */
|
|
13
|
-
const cycleCounts = new Map();
|
|
14
|
-
/** Set when a hook completes with retry_on artifact present — signals caller to re-run trigger. */
|
|
15
|
-
let retryPending = false;
|
|
16
|
-
/** Stores the trigger unit info for pending retries so caller knows what to re-run. */
|
|
17
|
-
let retryTrigger = null;
|
|
18
|
-
// ─── Public API ────────────────────────────────────────────────────────────
|
|
19
|
-
/**
|
|
20
|
-
* Called after a unit completes. Returns the next hook unit to dispatch,
|
|
21
|
-
* or null if no hooks apply (normal dispatch should proceed).
|
|
22
|
-
*
|
|
23
|
-
* Call flow:
|
|
24
|
-
* 1. A core unit (e.g. execute-task) completes → handleAgentEnd calls this
|
|
25
|
-
* 2. If hooks match, returns first hook to dispatch. Caller sends the prompt.
|
|
26
|
-
* 3. Hook unit completes → handleAgentEnd calls this again (activeHook is set)
|
|
27
|
-
* 4. Checks retry_on / next hook / done → returns next action or null
|
|
28
|
-
*/
|
|
1
|
+
// GSD Extension — Hook Engine Facade
|
|
2
|
+
//
|
|
3
|
+
// Thin facade over RuleRegistry. All mutable state and logic lives in the
|
|
4
|
+
// registry instance; these exported functions delegate through getOrCreateRegistry()
|
|
5
|
+
// so existing call-sites and tests work without modification.
|
|
6
|
+
import { getOrCreateRegistry } from "./rule-registry.js";
|
|
7
|
+
// Re-export resolveHookArtifactPath so existing importers still work.
|
|
8
|
+
export { resolveHookArtifactPath } from "./rule-registry.js";
|
|
9
|
+
// ─── Post-Unit Hooks ───────────────────────────────────────────────────────
|
|
29
10
|
export function checkPostUnitHooks(completedUnitType, completedUnitId, basePath) {
|
|
30
|
-
|
|
31
|
-
if (activeHook) {
|
|
32
|
-
return handleHookCompletion(basePath);
|
|
33
|
-
}
|
|
34
|
-
// Don't trigger hooks for other hook units (prevent hook-on-hook chains)
|
|
35
|
-
// Don't trigger hooks for triage units (prevent hook-on-triage chains)
|
|
36
|
-
// Don't trigger hooks for quick-task units (lightweight one-offs from captures)
|
|
37
|
-
if (completedUnitType.startsWith("hook/") || completedUnitType === "triage-captures" || completedUnitType === "quick-task")
|
|
38
|
-
return null;
|
|
39
|
-
// Check if any hooks are configured for this unit type
|
|
40
|
-
const hooks = resolvePostUnitHooks().filter(h => h.after.includes(completedUnitType));
|
|
41
|
-
if (hooks.length === 0)
|
|
42
|
-
return null;
|
|
43
|
-
// Build hook queue for this trigger
|
|
44
|
-
hookQueue = hooks.map(config => ({
|
|
45
|
-
config,
|
|
46
|
-
triggerUnitType: completedUnitType,
|
|
47
|
-
triggerUnitId: completedUnitId,
|
|
48
|
-
}));
|
|
49
|
-
return dequeueNextHook(basePath);
|
|
11
|
+
return getOrCreateRegistry().evaluatePostUnit(completedUnitType, completedUnitId, basePath);
|
|
50
12
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Returns whether a hook is currently active (for progress display).
|
|
53
|
-
*/
|
|
54
13
|
export function getActiveHook() {
|
|
55
|
-
return
|
|
14
|
+
return getOrCreateRegistry().getActiveHook();
|
|
56
15
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Returns true if a retry of the trigger unit was requested by a hook.
|
|
59
|
-
* Caller should re-dispatch the original trigger unit, then hooks will
|
|
60
|
-
* fire again on its next completion.
|
|
61
|
-
*/
|
|
62
16
|
export function isRetryPending() {
|
|
63
|
-
return
|
|
17
|
+
return getOrCreateRegistry().isRetryPending();
|
|
64
18
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Returns the trigger unit info for a pending retry, or null.
|
|
67
|
-
* Clears the retry state after reading.
|
|
68
|
-
*/
|
|
69
19
|
export function consumeRetryTrigger() {
|
|
70
|
-
|
|
71
|
-
return null;
|
|
72
|
-
const trigger = { ...retryTrigger };
|
|
73
|
-
retryPending = false;
|
|
74
|
-
retryTrigger = null;
|
|
75
|
-
return trigger;
|
|
20
|
+
return getOrCreateRegistry().consumeRetryTrigger();
|
|
76
21
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Reset all hook state. Called on auto-mode start/stop.
|
|
79
|
-
*/
|
|
80
22
|
export function resetHookState() {
|
|
81
|
-
|
|
82
|
-
hookQueue = [];
|
|
83
|
-
cycleCounts.clear();
|
|
84
|
-
retryPending = false;
|
|
85
|
-
retryTrigger = null;
|
|
23
|
+
getOrCreateRegistry().resetState();
|
|
86
24
|
}
|
|
87
|
-
// ───
|
|
88
|
-
function dequeueNextHook(basePath) {
|
|
89
|
-
while (hookQueue.length > 0) {
|
|
90
|
-
const entry = hookQueue.shift();
|
|
91
|
-
const { config, triggerUnitType, triggerUnitId } = entry;
|
|
92
|
-
// Check idempotency — if artifact already exists, skip this hook
|
|
93
|
-
if (config.artifact) {
|
|
94
|
-
const artifactPath = resolveHookArtifactPath(basePath, triggerUnitId, config.artifact);
|
|
95
|
-
if (existsSync(artifactPath))
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
// Check cycle limit
|
|
99
|
-
const cycleKey = `${config.name}/${triggerUnitType}/${triggerUnitId}`;
|
|
100
|
-
const currentCycle = (cycleCounts.get(cycleKey) ?? 0) + 1;
|
|
101
|
-
const maxCycles = config.max_cycles ?? 1;
|
|
102
|
-
if (currentCycle > maxCycles)
|
|
103
|
-
continue;
|
|
104
|
-
cycleCounts.set(cycleKey, currentCycle);
|
|
105
|
-
activeHook = {
|
|
106
|
-
hookName: config.name,
|
|
107
|
-
triggerUnitType,
|
|
108
|
-
triggerUnitId,
|
|
109
|
-
cycle: currentCycle,
|
|
110
|
-
pendingRetry: false,
|
|
111
|
-
};
|
|
112
|
-
// Build the prompt with variable substitution
|
|
113
|
-
const [mid, sid, tid] = triggerUnitId.split("/");
|
|
114
|
-
let prompt = config.prompt
|
|
115
|
-
.replace(/\{milestoneId\}/g, mid ?? "")
|
|
116
|
-
.replace(/\{sliceId\}/g, sid ?? "")
|
|
117
|
-
.replace(/\{taskId\}/g, tid ?? "");
|
|
118
|
-
// Inject browser safety instruction for hooks that may use browser tools (#1345).
|
|
119
|
-
// Vite HMR and other persistent connections prevent networkidle from resolving.
|
|
120
|
-
prompt += "\n\n**Browser tool safety:** Do NOT use `browser_wait_for` with `condition: \"network_idle\"` — it hangs indefinitely when dev servers keep persistent connections (Vite HMR, WebSocket). Use `selector_visible`, `text_visible`, or `delay` instead.";
|
|
121
|
-
return {
|
|
122
|
-
hookName: config.name,
|
|
123
|
-
prompt,
|
|
124
|
-
model: config.model,
|
|
125
|
-
unitType: `hook/${config.name}`,
|
|
126
|
-
unitId: triggerUnitId,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
// No more hooks — clear active state and return null for normal dispatch
|
|
130
|
-
activeHook = null;
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
function handleHookCompletion(basePath) {
|
|
134
|
-
const hook = activeHook;
|
|
135
|
-
const hooks = resolvePostUnitHooks();
|
|
136
|
-
const config = hooks.find(h => h.name === hook.hookName);
|
|
137
|
-
// Check if retry was requested via retry_on artifact
|
|
138
|
-
if (config?.retry_on) {
|
|
139
|
-
const retryArtifactPath = resolveHookArtifactPath(basePath, hook.triggerUnitId, config.retry_on);
|
|
140
|
-
if (existsSync(retryArtifactPath)) {
|
|
141
|
-
// Check cycle limit before allowing retry
|
|
142
|
-
const cycleKey = `${config.name}/${hook.triggerUnitType}/${hook.triggerUnitId}`;
|
|
143
|
-
const currentCycle = cycleCounts.get(cycleKey) ?? 1;
|
|
144
|
-
const maxCycles = config.max_cycles ?? 1;
|
|
145
|
-
if (currentCycle < maxCycles) {
|
|
146
|
-
// Signal retry — caller will re-dispatch the trigger unit
|
|
147
|
-
activeHook = null;
|
|
148
|
-
hookQueue = [];
|
|
149
|
-
retryPending = true;
|
|
150
|
-
retryTrigger = { unitType: hook.triggerUnitType, unitId: hook.triggerUnitId, retryArtifact: config.retry_on };
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
// Max cycles reached — fall through to normal completion
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
// Hook completed normally — try next hook in queue
|
|
157
|
-
activeHook = null;
|
|
158
|
-
return dequeueNextHook(basePath);
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* Resolve the path where a hook artifact is expected to be written.
|
|
162
|
-
* Uses the trigger unit's directory context:
|
|
163
|
-
* - Task-level (M001/S01/T01): .gsd/milestones/M001/slices/S01/tasks/T01-{artifact}
|
|
164
|
-
* - Slice-level (M001/S01): .gsd/milestones/M001/slices/S01/{artifact}
|
|
165
|
-
* - Milestone-level (M001): .gsd/milestones/M001/{artifact}
|
|
166
|
-
*/
|
|
167
|
-
export function resolveHookArtifactPath(basePath, unitId, artifactName) {
|
|
168
|
-
const parts = unitId.split("/");
|
|
169
|
-
if (parts.length === 3) {
|
|
170
|
-
const [mid, sid, tid] = parts;
|
|
171
|
-
return join(basePath, ".gsd", "milestones", mid, "slices", sid, "tasks", `${tid}-${artifactName}`);
|
|
172
|
-
}
|
|
173
|
-
if (parts.length === 2) {
|
|
174
|
-
const [mid, sid] = parts;
|
|
175
|
-
return join(basePath, ".gsd", "milestones", mid, "slices", sid, artifactName);
|
|
176
|
-
}
|
|
177
|
-
return join(basePath, ".gsd", "milestones", parts[0], artifactName);
|
|
178
|
-
}
|
|
179
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
180
|
-
// Phase 2: Pre-Dispatch Hooks
|
|
181
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
182
|
-
/**
|
|
183
|
-
* Run pre-dispatch hooks for a unit about to be dispatched.
|
|
184
|
-
* Returns a result indicating whether the unit should proceed (with optional
|
|
185
|
-
* prompt modifications), be skipped, or be replaced entirely.
|
|
186
|
-
*
|
|
187
|
-
* Multiple hooks can fire for the same unit type. They compose:
|
|
188
|
-
* - "modify" hooks stack (all prepend/append applied in order)
|
|
189
|
-
* - "skip" short-circuits (first matching skip wins)
|
|
190
|
-
* - "replace" short-circuits (first matching replace wins)
|
|
191
|
-
* - Skip/replace hooks take precedence over modify hooks
|
|
192
|
-
*/
|
|
25
|
+
// ─── Pre-Dispatch Hooks ────────────────────────────────────────────────────
|
|
193
26
|
export function runPreDispatchHooks(unitType, unitId, prompt, basePath) {
|
|
194
|
-
|
|
195
|
-
if (unitType.startsWith("hook/")) {
|
|
196
|
-
return { action: "proceed", prompt, firedHooks: [] };
|
|
197
|
-
}
|
|
198
|
-
const hooks = resolvePreDispatchHooks().filter(h => h.before.includes(unitType));
|
|
199
|
-
if (hooks.length === 0) {
|
|
200
|
-
return { action: "proceed", prompt, firedHooks: [] };
|
|
201
|
-
}
|
|
202
|
-
const [mid, sid, tid] = unitId.split("/");
|
|
203
|
-
const substitute = (text) => text
|
|
204
|
-
.replace(/\{milestoneId\}/g, mid ?? "")
|
|
205
|
-
.replace(/\{sliceId\}/g, sid ?? "")
|
|
206
|
-
.replace(/\{taskId\}/g, tid ?? "");
|
|
207
|
-
const firedHooks = [];
|
|
208
|
-
let currentPrompt = prompt;
|
|
209
|
-
for (const hook of hooks) {
|
|
210
|
-
if (hook.action === "skip") {
|
|
211
|
-
// Check optional skip condition
|
|
212
|
-
if (hook.skip_if) {
|
|
213
|
-
const conditionPath = resolveHookArtifactPath(basePath, unitId, hook.skip_if);
|
|
214
|
-
if (!existsSync(conditionPath))
|
|
215
|
-
continue; // Condition not met, don't skip
|
|
216
|
-
}
|
|
217
|
-
firedHooks.push(hook.name);
|
|
218
|
-
return { action: "skip", firedHooks };
|
|
219
|
-
}
|
|
220
|
-
if (hook.action === "replace") {
|
|
221
|
-
firedHooks.push(hook.name);
|
|
222
|
-
return {
|
|
223
|
-
action: "replace",
|
|
224
|
-
prompt: substitute(hook.prompt ?? ""),
|
|
225
|
-
unitType: hook.unit_type,
|
|
226
|
-
model: hook.model,
|
|
227
|
-
firedHooks,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
if (hook.action === "modify") {
|
|
231
|
-
firedHooks.push(hook.name);
|
|
232
|
-
if (hook.prepend) {
|
|
233
|
-
currentPrompt = `${substitute(hook.prepend)}\n\n${currentPrompt}`;
|
|
234
|
-
}
|
|
235
|
-
if (hook.append) {
|
|
236
|
-
currentPrompt = `${currentPrompt}\n\n${substitute(hook.append)}`;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return {
|
|
241
|
-
action: "proceed",
|
|
242
|
-
prompt: currentPrompt,
|
|
243
|
-
model: hooks.find(h => h.action === "modify" && h.model)?.model,
|
|
244
|
-
firedHooks,
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
248
|
-
// Phase 3: Hook State Persistence
|
|
249
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
250
|
-
const HOOK_STATE_FILE = "hook-state.json";
|
|
251
|
-
function hookStatePath(basePath) {
|
|
252
|
-
return join(basePath, ".gsd", HOOK_STATE_FILE);
|
|
27
|
+
return getOrCreateRegistry().evaluatePreDispatch(unitType, unitId, prompt, basePath);
|
|
253
28
|
}
|
|
254
|
-
|
|
255
|
-
* Persist current hook cycle counts to disk so they survive crashes/restarts.
|
|
256
|
-
* Called after each hook dispatch and on auto-mode pause.
|
|
257
|
-
*/
|
|
29
|
+
// ─── State Persistence ─────────────────────────────────────────────────────
|
|
258
30
|
export function persistHookState(basePath) {
|
|
259
|
-
|
|
260
|
-
cycleCounts: Object.fromEntries(cycleCounts),
|
|
261
|
-
savedAt: new Date().toISOString(),
|
|
262
|
-
};
|
|
263
|
-
try {
|
|
264
|
-
const dir = join(basePath, ".gsd");
|
|
265
|
-
if (!existsSync(dir))
|
|
266
|
-
mkdirSync(dir, { recursive: true });
|
|
267
|
-
writeFileSync(hookStatePath(basePath), JSON.stringify(state, null, 2), "utf-8");
|
|
268
|
-
}
|
|
269
|
-
catch {
|
|
270
|
-
// Non-fatal — state is recreatable from artifacts
|
|
271
|
-
}
|
|
31
|
+
getOrCreateRegistry().persistState(basePath);
|
|
272
32
|
}
|
|
273
|
-
/**
|
|
274
|
-
* Restore hook cycle counts from disk after a crash/restart.
|
|
275
|
-
* Called during auto-mode resume.
|
|
276
|
-
*/
|
|
277
33
|
export function restoreHookState(basePath) {
|
|
278
|
-
|
|
279
|
-
const filePath = hookStatePath(basePath);
|
|
280
|
-
if (!existsSync(filePath))
|
|
281
|
-
return;
|
|
282
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
283
|
-
const state = JSON.parse(raw);
|
|
284
|
-
if (state.cycleCounts && typeof state.cycleCounts === "object") {
|
|
285
|
-
cycleCounts.clear();
|
|
286
|
-
for (const [key, value] of Object.entries(state.cycleCounts)) {
|
|
287
|
-
if (typeof value === "number") {
|
|
288
|
-
cycleCounts.set(key, value);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
// Non-fatal — fresh state is fine
|
|
295
|
-
}
|
|
34
|
+
getOrCreateRegistry().restoreState(basePath);
|
|
296
35
|
}
|
|
297
|
-
/**
|
|
298
|
-
* Clear persisted hook state file from disk.
|
|
299
|
-
* Called on clean auto-mode stop.
|
|
300
|
-
*/
|
|
301
36
|
export function clearPersistedHookState(basePath) {
|
|
302
|
-
|
|
303
|
-
const filePath = hookStatePath(basePath);
|
|
304
|
-
if (existsSync(filePath)) {
|
|
305
|
-
writeFileSync(filePath, JSON.stringify({ cycleCounts: {}, savedAt: new Date().toISOString() }, null, 2), "utf-8");
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
// Non-fatal
|
|
310
|
-
}
|
|
37
|
+
getOrCreateRegistry().clearPersistedState(basePath);
|
|
311
38
|
}
|
|
312
|
-
//
|
|
313
|
-
// Phase 3: Hook Status Reporting
|
|
314
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
315
|
-
/**
|
|
316
|
-
* Get status of all configured hooks for display by /gsd hooks.
|
|
317
|
-
*/
|
|
39
|
+
// ─── Status & Manual Trigger ───────────────────────────────────────────────
|
|
318
40
|
export function getHookStatus() {
|
|
319
|
-
|
|
320
|
-
// Post-unit hooks
|
|
321
|
-
const postHooks = resolvePostUnitHooks();
|
|
322
|
-
for (const hook of postHooks) {
|
|
323
|
-
const activeCycles = {};
|
|
324
|
-
for (const [key, count] of cycleCounts) {
|
|
325
|
-
if (key.startsWith(`${hook.name}/`)) {
|
|
326
|
-
activeCycles[key] = count;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
entries.push({
|
|
330
|
-
name: hook.name,
|
|
331
|
-
type: "post",
|
|
332
|
-
enabled: hook.enabled !== false,
|
|
333
|
-
targets: hook.after,
|
|
334
|
-
activeCycles,
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
// Pre-dispatch hooks
|
|
338
|
-
const preHooks = resolvePreDispatchHooks();
|
|
339
|
-
for (const hook of preHooks) {
|
|
340
|
-
entries.push({
|
|
341
|
-
name: hook.name,
|
|
342
|
-
type: "pre",
|
|
343
|
-
enabled: hook.enabled !== false,
|
|
344
|
-
targets: hook.before,
|
|
345
|
-
activeCycles: {},
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
return entries;
|
|
41
|
+
return getOrCreateRegistry().getHookStatus();
|
|
349
42
|
}
|
|
350
|
-
/**
|
|
351
|
-
* Manually trigger a specific hook for a unit.
|
|
352
|
-
* This bypasses the normal flow and forces the hook to run even if its artifact exists.
|
|
353
|
-
*
|
|
354
|
-
* @param hookName - The name of the hook to trigger (e.g., "code-review")
|
|
355
|
-
* @param unitType - The type of unit that triggered the hook (e.g., "execute-task")
|
|
356
|
-
* @param unitId - The unit ID (e.g., "M001/S01/T01")
|
|
357
|
-
* @param basePath - The project base path
|
|
358
|
-
* @returns The hook dispatch result or null if hook not found
|
|
359
|
-
*/
|
|
360
43
|
export function triggerHookManually(hookName, unitType, unitId, basePath) {
|
|
361
|
-
|
|
362
|
-
const hook = resolvePostUnitHooks().find(h => h.name === hookName);
|
|
363
|
-
if (!hook) {
|
|
364
|
-
console.error(`[triggerHookManually] Hook "${hookName}" not found in post_unit_hooks`);
|
|
365
|
-
return null;
|
|
366
|
-
}
|
|
367
|
-
if (!hook.prompt || typeof hook.prompt !== 'string' || hook.prompt.trim().length === 0) {
|
|
368
|
-
console.error(`[triggerHookManually] Hook "${hookName}" has empty prompt`);
|
|
369
|
-
return null;
|
|
370
|
-
}
|
|
371
|
-
// Reset any active hook state to allow manual triggering
|
|
372
|
-
activeHook = {
|
|
373
|
-
hookName: hook.name,
|
|
374
|
-
triggerUnitType: unitType,
|
|
375
|
-
triggerUnitId: unitId,
|
|
376
|
-
cycle: 1,
|
|
377
|
-
pendingRetry: false,
|
|
378
|
-
};
|
|
379
|
-
// Build the hook queue with just this hook
|
|
380
|
-
hookQueue = [{
|
|
381
|
-
config: hook,
|
|
382
|
-
triggerUnitType: unitType,
|
|
383
|
-
triggerUnitId: unitId,
|
|
384
|
-
}];
|
|
385
|
-
// Set the cycle count for this specific hook+trigger
|
|
386
|
-
const cycleKey = `${hook.name}/${unitType}/${unitId}`;
|
|
387
|
-
const currentCycle = (cycleCounts.get(cycleKey) ?? 0) + 1;
|
|
388
|
-
cycleCounts.set(cycleKey, currentCycle);
|
|
389
|
-
// Update active hook with the cycle count
|
|
390
|
-
activeHook.cycle = currentCycle;
|
|
391
|
-
// Build the prompt with variable substitution
|
|
392
|
-
const [mid, sid, tid] = unitId.split("/");
|
|
393
|
-
const prompt = hook.prompt
|
|
394
|
-
.replace(/\{milestoneId\}/g, mid ?? "")
|
|
395
|
-
.replace(/\{sliceId\}/g, sid ?? "")
|
|
396
|
-
.replace(/\{taskId\}/g, tid ?? "");
|
|
397
|
-
console.log(`[triggerHookManually] Built prompt for ${hookName}, length: ${prompt.length}`);
|
|
398
|
-
return {
|
|
399
|
-
hookName: hook.name,
|
|
400
|
-
prompt,
|
|
401
|
-
model: hook.model,
|
|
402
|
-
unitType: `hook/${hook.name}`,
|
|
403
|
-
unitId,
|
|
404
|
-
};
|
|
44
|
+
return getOrCreateRegistry().triggerHookManually(hookName, unitType, unitId, basePath);
|
|
405
45
|
}
|
|
406
|
-
/**
|
|
407
|
-
* Format hook status for terminal display.
|
|
408
|
-
*/
|
|
409
46
|
export function formatHookStatus() {
|
|
410
|
-
|
|
411
|
-
if (entries.length === 0) {
|
|
412
|
-
return "No hooks configured. Add post_unit_hooks or pre_dispatch_hooks to .gsd/preferences.md";
|
|
413
|
-
}
|
|
414
|
-
const lines = ["Configured Hooks:", ""];
|
|
415
|
-
const postHooks = entries.filter(e => e.type === "post");
|
|
416
|
-
const preHooks = entries.filter(e => e.type === "pre");
|
|
417
|
-
if (postHooks.length > 0) {
|
|
418
|
-
lines.push("Post-Unit Hooks (run after unit completes):");
|
|
419
|
-
for (const hook of postHooks) {
|
|
420
|
-
const status = hook.enabled ? "enabled" : "disabled";
|
|
421
|
-
const cycles = Object.keys(hook.activeCycles).length;
|
|
422
|
-
const cycleInfo = cycles > 0 ? ` (${cycles} active cycle${cycles === 1 ? "" : "s"})` : "";
|
|
423
|
-
lines.push(` ${hook.name} [${status}] → after: ${hook.targets.join(", ")}${cycleInfo}`);
|
|
424
|
-
}
|
|
425
|
-
lines.push("");
|
|
426
|
-
}
|
|
427
|
-
if (preHooks.length > 0) {
|
|
428
|
-
lines.push("Pre-Dispatch Hooks (run before unit dispatches):");
|
|
429
|
-
for (const hook of preHooks) {
|
|
430
|
-
const status = hook.enabled ? "enabled" : "disabled";
|
|
431
|
-
lines.push(` ${hook.name} [${status}] → before: ${hook.targets.join(", ")}`);
|
|
432
|
-
}
|
|
433
|
-
lines.push("");
|
|
434
|
-
}
|
|
435
|
-
return lines.join("\n");
|
|
47
|
+
return getOrCreateRegistry().formatHookStatus();
|
|
436
48
|
}
|
|
@@ -214,6 +214,7 @@ function mergePreferences(base, override) {
|
|
|
214
214
|
github: (base.github || override.github)
|
|
215
215
|
? { ...(base.github ?? {}), ...(override.github ?? {}) }
|
|
216
216
|
: undefined,
|
|
217
|
+
service_tier: override.service_tier ?? base.service_tier,
|
|
217
218
|
};
|
|
218
219
|
}
|
|
219
220
|
function mergeStringLists(base, override) {
|
|
@@ -16,11 +16,34 @@
|
|
|
16
16
|
* mid-session — especially for late-loading templates like complete-milestone
|
|
17
17
|
* that aren't read until the end of a long auto-mode run.
|
|
18
18
|
*/
|
|
19
|
-
import { readFileSync, readdirSync } from "node:fs";
|
|
19
|
+
import { readFileSync, readdirSync, existsSync } from "node:fs";
|
|
20
20
|
import { GSDError, GSD_PARSE_ERROR } from "./errors.js";
|
|
21
21
|
import { join, dirname } from "node:path";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
|
-
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the GSD extension directory.
|
|
26
|
+
*
|
|
27
|
+
* `import.meta.url` resolves to whichever copy of this module is executing.
|
|
28
|
+
* On Windows (npm global install via MSYS2 / Git Bash) this can resolve to
|
|
29
|
+
* the npm-global `AppData/Roaming/npm/…` path, which does NOT contain the
|
|
30
|
+
* prompts/ and templates/ subtrees that initResources() copies to
|
|
31
|
+
* `~/.gsd/agent/extensions/gsd/`. Detect the mismatch and fall back to
|
|
32
|
+
* the user-local agent directory.
|
|
33
|
+
*/
|
|
34
|
+
function resolveExtensionDir() {
|
|
35
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
if (existsSync(join(moduleDir, "prompts")))
|
|
37
|
+
return moduleDir;
|
|
38
|
+
// Fallback: user-local agent directory
|
|
39
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
40
|
+
const agentGsdDir = join(gsdHome, "agent", "extensions", "gsd");
|
|
41
|
+
if (existsSync(join(agentGsdDir, "prompts")))
|
|
42
|
+
return agentGsdDir;
|
|
43
|
+
// Last resort: return the module dir (warmCache will silently handle the miss)
|
|
44
|
+
return moduleDir;
|
|
45
|
+
}
|
|
46
|
+
const __extensionDir = resolveExtensionDir();
|
|
24
47
|
const promptsDir = join(__extensionDir, "prompts");
|
|
25
48
|
const templatesDir = join(__extensionDir, "templates");
|
|
26
49
|
// Cache all templates eagerly at module load — a running session uses the
|
|
@@ -43,7 +66,11 @@ function warmCache() {
|
|
|
43
66
|
}
|
|
44
67
|
}
|
|
45
68
|
catch {
|
|
46
|
-
// prompts/ may not exist in test environments — lazy loading still works
|
|
69
|
+
// prompts/ may not exist in test environments — lazy loading still works.
|
|
70
|
+
// Emit a diagnostic when running outside tests so wrong-path bugs are visible.
|
|
71
|
+
if (!process.env.VITEST && !process.env.NODE_TEST) {
|
|
72
|
+
process.stderr.write(`[gsd:prompt-loader] warmCache: prompts dir not found: ${promptsDir}\n`);
|
|
73
|
+
}
|
|
47
74
|
}
|
|
48
75
|
try {
|
|
49
76
|
for (const file of readdirSync(templatesDir)) {
|
|
@@ -56,7 +83,10 @@ function warmCache() {
|
|
|
56
83
|
}
|
|
57
84
|
}
|
|
58
85
|
catch {
|
|
59
|
-
// templates/ may not exist in test environments — lazy loading still works
|
|
86
|
+
// templates/ may not exist in test environments — lazy loading still works.
|
|
87
|
+
if (!process.env.VITEST && !process.env.NODE_TEST) {
|
|
88
|
+
process.stderr.write(`[gsd:prompt-loader] warmCache: templates dir not found: ${templatesDir}\n`);
|
|
89
|
+
}
|
|
60
90
|
}
|
|
61
91
|
}
|
|
62
92
|
// Snapshot all templates at module load time
|
|
@@ -17,16 +17,17 @@ All relevant context has been preloaded below — the roadmap, all slice summari
|
|
|
17
17
|
Then:
|
|
18
18
|
1. Use the **Milestone Summary** output template from the inlined context above
|
|
19
19
|
2. {{skillActivation}}
|
|
20
|
-
3. Verify
|
|
21
|
-
4. Verify the milestone
|
|
22
|
-
5.
|
|
23
|
-
6.
|
|
24
|
-
7.
|
|
25
|
-
8. Update `.gsd/
|
|
26
|
-
9.
|
|
27
|
-
10.
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
3. **Verify code changes exist.** Run `git diff --stat HEAD $(git merge-base HEAD main) -- ':!.gsd/'` (or the equivalent for the integration branch). If no non-`.gsd/` files appear in the diff, the milestone produced only planning artifacts and no actual code. In that case, do NOT mark the milestone as passing verification — document the gap clearly in the summary and state that implementation is missing.
|
|
21
|
+
4. Verify each **success criterion** from the milestone definition in `{{roadmapPath}}`. For each criterion, confirm it was met with specific evidence from slice summaries, test results, or observable behavior. List any criterion that was NOT met.
|
|
22
|
+
5. Verify the milestone's **definition of done** — all slices are `[x]`, all slice summaries exist, and any cross-slice integration points work correctly.
|
|
23
|
+
6. Validate **requirement status transitions**. For each requirement that changed status during this milestone, confirm the transition is supported by evidence. Requirements can move between Active, Validated, Deferred, Blocked, or Out of Scope — but only with proof.
|
|
24
|
+
7. Write `{{milestoneSummaryPath}}` using the milestone-summary template. Fill all frontmatter fields and narrative sections. The `requirement_outcomes` field must list every requirement that changed status with `from_status`, `to_status`, and `proof`.
|
|
25
|
+
8. Update `.gsd/REQUIREMENTS.md` if any requirement status transitions were validated in step 5.
|
|
26
|
+
9. Update `.gsd/PROJECT.md` to reflect milestone completion and current project state.
|
|
27
|
+
10. Review all slice summaries for cross-cutting lessons, patterns, or gotchas that emerged during this milestone. Append any non-obvious, reusable insights to `.gsd/KNOWLEDGE.md`.
|
|
28
|
+
11. Do not commit manually — the system auto-commits your changes after this unit completes.
|
|
29
|
+
|
|
30
|
+
**Important:** Do NOT skip the code change verification, success criteria, or definition of done verification (steps 3-5). The milestone summary must reflect actual verified outcomes, not assumed success. If any criterion was not met or no code changes exist, document it clearly in the summary and do not mark the milestone as passing verification.
|
|
30
31
|
|
|
31
32
|
**File system safety:** When scanning milestone directories for evidence, use `ls` or `find` to list directory contents first — never pass a directory path (e.g. `tasks/`, `slices/`) directly to the `read` tool. The `read` tool only accepts file paths, not directories.
|
|
32
33
|
|
|
@@ -56,7 +56,7 @@ Use these templates exactly:
|
|
|
56
56
|
9. Say exactly: "Milestone {{milestoneId}} ready."
|
|
57
57
|
|
|
58
58
|
**For multi-milestone**, write in this order:
|
|
59
|
-
1. For each milestone, call `
|
|
59
|
+
1. For each milestone, call `gsd_milestone_generate_id` to get its ID — never invent milestone IDs manually. Then `mkdir -p .gsd/milestones/<ID>/slices` for each.
|
|
60
60
|
2. Write `.gsd/PROJECT.md` — full vision across ALL milestones (using Project template)
|
|
61
61
|
3. Write `.gsd/REQUIREMENTS.md` — full capability contract (using Requirements template)
|
|
62
62
|
4. Seed `.gsd/DECISIONS.md` (using Decisions template)
|
|
@@ -82,5 +82,5 @@ Use these templates exactly:
|
|
|
82
82
|
- **Investigate before writing** — always scout the codebase first
|
|
83
83
|
- **Use depends_on frontmatter** for multi-milestone sequences (the state machine reads this field to determine execution order)
|
|
84
84
|
- **Anti-reduction rule** — if the spec describes a big vision, plan the big vision. Do not ask "what's the minimum viable version?" or reduce scope. Phase complex/risky work into later milestones — do not cut it.
|
|
85
|
-
- **Naming convention** — always use `
|
|
85
|
+
- **Naming convention** — always use `gsd_milestone_generate_id` to get milestone IDs. Directories use bare IDs (e.g. `M001/` or `M001-r5jzab/`), files use ID-SUFFIX format (e.g. `M001-CONTEXT.md` or `M001-r5jzab-CONTEXT.md`). Never invent milestone IDs manually.
|
|
86
86
|
- **End with "Milestone {{milestoneId}} ready."** — this triggers auto-start detection
|
|
@@ -214,7 +214,7 @@ Once the user confirms the milestone split:
|
|
|
214
214
|
|
|
215
215
|
#### Phase 1: Shared artifacts
|
|
216
216
|
|
|
217
|
-
1. For each milestone, call `
|
|
217
|
+
1. For each milestone, call `gsd_milestone_generate_id` to get its ID — never invent milestone IDs manually. Then `mkdir -p .gsd/milestones/<ID>/slices`.
|
|
218
218
|
2. Write `.gsd/PROJECT.md` — use the **Project** output template below.
|
|
219
219
|
3. Write `.gsd/REQUIREMENTS.md` — use the **Requirements** output template below. Capture Active, Deferred, Out of Scope, and any already Validated requirements. Later milestones may have provisional ownership where slice plans do not exist yet.
|
|
220
220
|
4. Seed `.gsd/DECISIONS.md` — use the **Decisions** output template below.
|
|
@@ -107,7 +107,7 @@ The user confirms or corrects before you write. One depth verification per miles
|
|
|
107
107
|
|
|
108
108
|
Once the user is satisfied, in a single pass for **each** new milestone:
|
|
109
109
|
|
|
110
|
-
1. Call `
|
|
110
|
+
1. Call `gsd_milestone_generate_id` to get the milestone ID — never invent milestone IDs manually. Then `mkdir -p .gsd/milestones/<ID>/slices`.
|
|
111
111
|
2. Write `.gsd/milestones/<ID>/<ID>-CONTEXT.md` — use the **Context** output template below. Capture intent, scope, risks, constraints, integration points, and relevant requirements. Mark the status as "Queued — pending auto-mode execution." **If this milestone depends on other milestones, add YAML frontmatter with `depends_on`:**
|
|
112
112
|
```yaml
|
|
113
113
|
---
|