gsd-pi 2.41.0 → 2.42.0-dev.eedc83f
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 +92 -29
- package/dist/cli-web-branch.d.ts +6 -0
- package/dist/cli-web-branch.js +17 -0
- package/dist/cli.js +15 -1
- package/dist/onboarding.js +2 -1
- package/dist/resource-loader.js +39 -6
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +52 -4
- 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-prompts.js +1 -1
- 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 +25 -3
- 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 +20 -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-checks.js +31 -1
- package/dist/resources/extensions/gsd/doctor-providers.js +10 -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 +94 -3
- package/dist/resources/extensions/gsd/git-constants.js +1 -0
- package/dist/resources/extensions/gsd/git-service.js +73 -3
- 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 +3 -2
- package/dist/resources/extensions/gsd/post-unit-hooks.js +24 -412
- package/dist/resources/extensions/gsd/preferences-types.js +2 -0
- package/dist/resources/extensions/gsd/preferences.js +60 -8
- 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/forensics.md +12 -5
- package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
- package/dist/resources/extensions/gsd/repo-identity.js +92 -7
- 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 +147 -0
- package/dist/resources/extensions/gsd/session-lock.js +2 -2
- 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 +21 -4
- package/dist/resources/extensions/mcp-client/index.js +2 -1
- package/dist/resources/extensions/search-the-web/tool-search.js +3 -3
- 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 +13 -13
- package/dist/web/standalone/.next/build-manifest.json +4 -4
- 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/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +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/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- 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 +3 -3
- 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/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.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/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.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/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.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/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.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/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- 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 +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
- package/dist/web/standalone/.next/server/chunks/229.js +3 -3
- package/dist/web/standalone/.next/server/chunks/471.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/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +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/app/_not-found/page-f2a7482d42a5614b.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js +1 -0
- package/dist/web/standalone/.next/static/chunks/app/page-b9367c5ae13b99c6.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{main-app-2f2ee7b85712c2bd.js → main-app-fdab67f7802d7832.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-9afaaebf6042a1d7.js → webpack-fa307370fcf9fb2c.js} +1 -1
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/dist/web-mode.d.ts +4 -0
- package/dist/web-mode.js +69 -11
- package/package.json +1 -1
- package/packages/native/src/__tests__/text.test.mjs +33 -0
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +6 -0
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/agent.test.ts +53 -0
- package/packages/pi-agent-core/src/agent.ts +3 -0
- package/packages/pi-agent-core/src/types.ts +6 -0
- package/packages/pi-agent-core/tsconfig.json +1 -1
- package/packages/pi-ai/dist/models.d.ts +5 -3
- package/packages/pi-ai/dist/models.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +801 -1468
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +1135 -1588
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +60 -2
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/scripts/generate-models.ts +1543 -0
- package/packages/pi-ai/src/models.generated.ts +1140 -1593
- package/packages/pi-ai/src/models.ts +7 -4
- package/packages/pi-ai/src/utils/oauth/github-copilot.ts +74 -2
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +8 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +29 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +60 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- 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/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +23 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +63 -11
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +20 -6
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -5
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +9 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.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 +34 -10
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +7 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +68 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -2
- package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +4 -2
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +18 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +29 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +3 -0
- package/packages/pi-coding-agent/src/core/package-manager.ts +99 -58
- package/packages/pi-coding-agent/src/core/resource-loader.ts +24 -6
- package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/extension-editor.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +10 -6
- 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 +36 -11
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/async-bash-timeout.test.ts +122 -0
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +40 -4
- 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-prompts.ts +1 -1
- 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 +25 -4
- 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 +20 -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-checks.ts +32 -1
- package/src/resources/extensions/gsd/doctor-providers.ts +13 -0
- package/src/resources/extensions/gsd/doctor-types.ts +1 -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 +101 -3
- package/src/resources/extensions/gsd/git-constants.ts +1 -0
- package/src/resources/extensions/gsd/git-service.ts +76 -6
- 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 +3 -2
- package/src/resources/extensions/gsd/post-unit-hooks.ts +24 -462
- package/src/resources/extensions/gsd/preferences-types.ts +6 -0
- package/src/resources/extensions/gsd/preferences.ts +63 -6
- 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/forensics.md +12 -5
- package/src/resources/extensions/gsd/prompts/queue.md +1 -1
- package/src/resources/extensions/gsd/repo-identity.ts +95 -7
- 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 +184 -0
- package/src/resources/extensions/gsd/session-lock.ts +2 -2
- 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/activity-log.test.ts +31 -69
- 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/forensics-dedup.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +49 -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 +341 -0
- package/src/resources/extensions/gsd/tests/manifest-status.test.ts +73 -82
- 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 +127 -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/symlink-numbered-variants.test.ts +151 -0
- 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/verification-gate.test.ts +156 -263
- 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 +135 -0
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +203 -106
- 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 +32 -11
- package/src/resources/extensions/mcp-client/index.ts +5 -1
- package/src/resources/extensions/search-the-web/tool-search.ts +3 -3
- 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/chunks/app/_not-found/page-e07acdb7dd069836.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/layout-745c6ed5fea5fb06.js +0 -1
- package/dist/web/standalone/.next/static/chunks/app/page-801b53eff6e83579.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-e6255954dccfcf0a.js +0 -1
- /package/dist/web/standalone/.next/static/{Ute3pMouVczQyT15qrBBO → JUBX5FUR73jiViQU5a-Cx}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{Ute3pMouVczQyT15qrBBO → JUBX5FUR73jiViQU5a-Cx}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
// GSD Extension — Unified Rule Registry
|
|
2
|
+
//
|
|
3
|
+
// Holds all dispatch rules and hooks as a flat list of UnifiedRule objects.
|
|
4
|
+
// Provides evaluation methods for each phase (dispatch, post-unit, pre-dispatch)
|
|
5
|
+
// and encapsulates mutable hook state as instance fields.
|
|
6
|
+
//
|
|
7
|
+
// A module-level singleton accessor allows existing code to migrate incrementally.
|
|
8
|
+
|
|
9
|
+
import type { UnifiedRule, RulePhase } from "./rule-types.js";
|
|
10
|
+
import type { DispatchAction, DispatchContext, DispatchRule } from "./auto-dispatch.js";
|
|
11
|
+
import type {
|
|
12
|
+
PostUnitHookConfig,
|
|
13
|
+
PreDispatchHookConfig,
|
|
14
|
+
HookDispatchResult,
|
|
15
|
+
PreDispatchResult,
|
|
16
|
+
HookExecutionState,
|
|
17
|
+
PersistedHookState,
|
|
18
|
+
HookStatusEntry,
|
|
19
|
+
} from "./types.js";
|
|
20
|
+
import { resolvePostUnitHooks, resolvePreDispatchHooks } from "./preferences.js";
|
|
21
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
|
|
24
|
+
// ─── Artifact Path Resolution ──────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
export function resolveHookArtifactPath(basePath: string, unitId: string, artifactName: string): string {
|
|
27
|
+
const parts = unitId.split("/");
|
|
28
|
+
if (parts.length === 3) {
|
|
29
|
+
const [mid, sid, tid] = parts;
|
|
30
|
+
return join(basePath, ".gsd", "milestones", mid, "slices", sid, "tasks", `${tid}-${artifactName}`);
|
|
31
|
+
}
|
|
32
|
+
if (parts.length === 2) {
|
|
33
|
+
const [mid, sid] = parts;
|
|
34
|
+
return join(basePath, ".gsd", "milestones", mid, "slices", sid, artifactName);
|
|
35
|
+
}
|
|
36
|
+
return join(basePath, ".gsd", "milestones", parts[0], artifactName);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Dispatch Rule Conversion ──────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Convert an array of DispatchRule objects to UnifiedRule[] format.
|
|
43
|
+
* Preserves exact array order — dispatch is order-dependent (first-match-wins).
|
|
44
|
+
*/
|
|
45
|
+
export function convertDispatchRules(rules: DispatchRule[]): UnifiedRule[] {
|
|
46
|
+
return rules.map((rule) => ({
|
|
47
|
+
name: rule.name,
|
|
48
|
+
when: "dispatch" as const,
|
|
49
|
+
evaluation: "first-match" as const,
|
|
50
|
+
where: rule.match,
|
|
51
|
+
then: (result: any) => result,
|
|
52
|
+
description: `Dispatch rule: ${rule.name}`,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── RuleRegistry ─────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
const HOOK_STATE_FILE = "hook-state.json";
|
|
59
|
+
|
|
60
|
+
export class RuleRegistry {
|
|
61
|
+
/** Static dispatch rules provided at construction time. */
|
|
62
|
+
private readonly dispatchRules: UnifiedRule[];
|
|
63
|
+
|
|
64
|
+
// ── Mutable hook state (encapsulated, not module-level) ──────────────
|
|
65
|
+
|
|
66
|
+
activeHook: HookExecutionState | null = null;
|
|
67
|
+
hookQueue: Array<{
|
|
68
|
+
config: PostUnitHookConfig;
|
|
69
|
+
triggerUnitType: string;
|
|
70
|
+
triggerUnitId: string;
|
|
71
|
+
}> = [];
|
|
72
|
+
cycleCounts: Map<string, number> = new Map();
|
|
73
|
+
retryPending: boolean = false;
|
|
74
|
+
retryTrigger: { unitType: string; unitId: string; retryArtifact: string } | null = null;
|
|
75
|
+
|
|
76
|
+
constructor(dispatchRules: UnifiedRule[]) {
|
|
77
|
+
this.dispatchRules = dispatchRules;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Core query ───────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Returns all rules: static dispatch rules + dynamically loaded hook rules.
|
|
84
|
+
* Hook rules are loaded fresh from preferences on each call (not cached).
|
|
85
|
+
*/
|
|
86
|
+
listRules(): UnifiedRule[] {
|
|
87
|
+
const rules: UnifiedRule[] = [...this.dispatchRules];
|
|
88
|
+
|
|
89
|
+
// Convert post-unit hooks to unified rules
|
|
90
|
+
const postHooks = resolvePostUnitHooks();
|
|
91
|
+
for (const hook of postHooks) {
|
|
92
|
+
rules.push({
|
|
93
|
+
name: hook.name,
|
|
94
|
+
when: "post-unit",
|
|
95
|
+
evaluation: "all-matching",
|
|
96
|
+
where: (unitType: string) => hook.after.includes(unitType),
|
|
97
|
+
then: () => hook,
|
|
98
|
+
description: `Post-unit hook: fires after ${hook.after.join(", ")}`,
|
|
99
|
+
lifecycle: {
|
|
100
|
+
artifact: hook.artifact,
|
|
101
|
+
retry_on: hook.retry_on,
|
|
102
|
+
max_cycles: hook.max_cycles,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Convert pre-dispatch hooks to unified rules
|
|
108
|
+
const preHooks = resolvePreDispatchHooks();
|
|
109
|
+
for (const hook of preHooks) {
|
|
110
|
+
rules.push({
|
|
111
|
+
name: hook.name,
|
|
112
|
+
when: "pre-dispatch",
|
|
113
|
+
evaluation: "all-matching",
|
|
114
|
+
where: (unitType: string) => hook.before.includes(unitType),
|
|
115
|
+
then: () => hook,
|
|
116
|
+
description: `Pre-dispatch hook: fires before ${hook.before.join(", ")}`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return rules;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Dispatch evaluation (async, first-match-wins) ───────────────────
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Iterate dispatch rules in order. First match wins.
|
|
127
|
+
* Returns stop action if no rule matches (unhandled phase).
|
|
128
|
+
*/
|
|
129
|
+
async evaluateDispatch(ctx: DispatchContext): Promise<DispatchAction> {
|
|
130
|
+
for (const rule of this.dispatchRules) {
|
|
131
|
+
const result = await rule.where(ctx);
|
|
132
|
+
if (result) {
|
|
133
|
+
if (result.action !== "skip") result.matchedRule = rule.name;
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
action: "stop",
|
|
139
|
+
reason: `Unhandled phase "${ctx.state.phase}" — run /gsd doctor to diagnose.`,
|
|
140
|
+
level: "info",
|
|
141
|
+
matchedRule: "<no-match>",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── Post-unit hook evaluation (sync, all-matching with lifecycle) ────
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Replicate exact semantics of checkPostUnitHooks from post-unit-hooks.ts:
|
|
149
|
+
* hook-on-hook prevention, idempotency, cycle limits, retry_on, dequeue.
|
|
150
|
+
*/
|
|
151
|
+
evaluatePostUnit(
|
|
152
|
+
completedUnitType: string,
|
|
153
|
+
completedUnitId: string,
|
|
154
|
+
basePath: string,
|
|
155
|
+
): HookDispatchResult | null {
|
|
156
|
+
// If we just completed a hook unit, handle its result
|
|
157
|
+
if (this.activeHook) {
|
|
158
|
+
return this._handleHookCompletion(basePath);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Don't trigger hooks for other hook units (prevent hook-on-hook chains)
|
|
162
|
+
// Don't trigger hooks for triage units or quick-task units
|
|
163
|
+
if (
|
|
164
|
+
completedUnitType.startsWith("hook/") ||
|
|
165
|
+
completedUnitType === "triage-captures" ||
|
|
166
|
+
completedUnitType === "quick-task"
|
|
167
|
+
) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Check if any hooks are configured for this unit type
|
|
172
|
+
const hooks = resolvePostUnitHooks().filter(h =>
|
|
173
|
+
h.after.includes(completedUnitType),
|
|
174
|
+
);
|
|
175
|
+
if (hooks.length === 0) return null;
|
|
176
|
+
|
|
177
|
+
// Build hook queue for this trigger
|
|
178
|
+
this.hookQueue = hooks.map(config => ({
|
|
179
|
+
config,
|
|
180
|
+
triggerUnitType: completedUnitType,
|
|
181
|
+
triggerUnitId: completedUnitId,
|
|
182
|
+
}));
|
|
183
|
+
|
|
184
|
+
return this._dequeueNextHook(basePath);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private _dequeueNextHook(basePath: string): HookDispatchResult | null {
|
|
188
|
+
while (this.hookQueue.length > 0) {
|
|
189
|
+
const entry = this.hookQueue.shift()!;
|
|
190
|
+
const { config, triggerUnitType, triggerUnitId } = entry;
|
|
191
|
+
|
|
192
|
+
// Check idempotency — if artifact already exists, skip
|
|
193
|
+
if (config.artifact) {
|
|
194
|
+
const artifactPath = resolveHookArtifactPath(basePath, triggerUnitId, config.artifact);
|
|
195
|
+
if (existsSync(artifactPath)) continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check cycle limit
|
|
199
|
+
const cycleKey = `${config.name}/${triggerUnitType}/${triggerUnitId}`;
|
|
200
|
+
const currentCycle = (this.cycleCounts.get(cycleKey) ?? 0) + 1;
|
|
201
|
+
const maxCycles = config.max_cycles ?? 1;
|
|
202
|
+
if (currentCycle > maxCycles) continue;
|
|
203
|
+
|
|
204
|
+
this.cycleCounts.set(cycleKey, currentCycle);
|
|
205
|
+
|
|
206
|
+
this.activeHook = {
|
|
207
|
+
hookName: config.name,
|
|
208
|
+
triggerUnitType,
|
|
209
|
+
triggerUnitId,
|
|
210
|
+
cycle: currentCycle,
|
|
211
|
+
pendingRetry: false,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Build prompt with variable substitution
|
|
215
|
+
const [mid, sid, tid] = triggerUnitId.split("/");
|
|
216
|
+
let prompt = config.prompt
|
|
217
|
+
.replace(/\{milestoneId\}/g, mid ?? "")
|
|
218
|
+
.replace(/\{sliceId\}/g, sid ?? "")
|
|
219
|
+
.replace(/\{taskId\}/g, tid ?? "");
|
|
220
|
+
|
|
221
|
+
// Inject browser safety instruction
|
|
222
|
+
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.";
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
hookName: config.name,
|
|
226
|
+
prompt,
|
|
227
|
+
model: config.model,
|
|
228
|
+
unitType: `hook/${config.name}`,
|
|
229
|
+
unitId: triggerUnitId,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// No more hooks — clear active state
|
|
234
|
+
this.activeHook = null;
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private _handleHookCompletion(basePath: string): HookDispatchResult | null {
|
|
239
|
+
const hook = this.activeHook!;
|
|
240
|
+
const hooks = resolvePostUnitHooks();
|
|
241
|
+
const config = hooks.find(h => h.name === hook.hookName);
|
|
242
|
+
|
|
243
|
+
// Check if retry was requested via retry_on artifact
|
|
244
|
+
if (config?.retry_on) {
|
|
245
|
+
const retryArtifactPath = resolveHookArtifactPath(basePath, hook.triggerUnitId, config.retry_on);
|
|
246
|
+
if (existsSync(retryArtifactPath)) {
|
|
247
|
+
const cycleKey = `${config.name}/${hook.triggerUnitType}/${hook.triggerUnitId}`;
|
|
248
|
+
const currentCycle = this.cycleCounts.get(cycleKey) ?? 1;
|
|
249
|
+
const maxCycles = config.max_cycles ?? 1;
|
|
250
|
+
|
|
251
|
+
if (currentCycle < maxCycles) {
|
|
252
|
+
this.activeHook = null;
|
|
253
|
+
this.hookQueue = [];
|
|
254
|
+
this.retryPending = true;
|
|
255
|
+
this.retryTrigger = {
|
|
256
|
+
unitType: hook.triggerUnitType,
|
|
257
|
+
unitId: hook.triggerUnitId,
|
|
258
|
+
retryArtifact: config.retry_on,
|
|
259
|
+
};
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Hook completed normally — try next hook in queue
|
|
266
|
+
this.activeHook = null;
|
|
267
|
+
return this._dequeueNextHook(basePath);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ── Pre-dispatch hook evaluation (sync, all-matching with compose) ──
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Replicate exact semantics of runPreDispatchHooks from post-unit-hooks.ts:
|
|
274
|
+
* modify/skip/replace compose semantics.
|
|
275
|
+
*/
|
|
276
|
+
evaluatePreDispatch(
|
|
277
|
+
unitType: string,
|
|
278
|
+
unitId: string,
|
|
279
|
+
prompt: string,
|
|
280
|
+
basePath: string,
|
|
281
|
+
): PreDispatchResult {
|
|
282
|
+
// Don't intercept hook units
|
|
283
|
+
if (unitType.startsWith("hook/")) {
|
|
284
|
+
return { action: "proceed", prompt, firedHooks: [] };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const hooks = resolvePreDispatchHooks().filter(h =>
|
|
288
|
+
h.before.includes(unitType),
|
|
289
|
+
);
|
|
290
|
+
if (hooks.length === 0) {
|
|
291
|
+
return { action: "proceed", prompt, firedHooks: [] };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const [mid, sid, tid] = unitId.split("/");
|
|
295
|
+
const substitute = (text: string): string =>
|
|
296
|
+
text
|
|
297
|
+
.replace(/\{milestoneId\}/g, mid ?? "")
|
|
298
|
+
.replace(/\{sliceId\}/g, sid ?? "")
|
|
299
|
+
.replace(/\{taskId\}/g, tid ?? "");
|
|
300
|
+
|
|
301
|
+
const firedHooks: string[] = [];
|
|
302
|
+
let currentPrompt = prompt;
|
|
303
|
+
|
|
304
|
+
for (const hook of hooks) {
|
|
305
|
+
if (hook.action === "skip") {
|
|
306
|
+
if (hook.skip_if) {
|
|
307
|
+
const conditionPath = resolveHookArtifactPath(basePath, unitId, hook.skip_if);
|
|
308
|
+
if (!existsSync(conditionPath)) continue;
|
|
309
|
+
}
|
|
310
|
+
firedHooks.push(hook.name);
|
|
311
|
+
return { action: "skip", firedHooks };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (hook.action === "replace") {
|
|
315
|
+
firedHooks.push(hook.name);
|
|
316
|
+
return {
|
|
317
|
+
action: "replace",
|
|
318
|
+
prompt: substitute(hook.prompt ?? ""),
|
|
319
|
+
unitType: hook.unit_type,
|
|
320
|
+
model: hook.model,
|
|
321
|
+
firedHooks,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (hook.action === "modify") {
|
|
326
|
+
firedHooks.push(hook.name);
|
|
327
|
+
if (hook.prepend) {
|
|
328
|
+
currentPrompt = `${substitute(hook.prepend)}\n\n${currentPrompt}`;
|
|
329
|
+
}
|
|
330
|
+
if (hook.append) {
|
|
331
|
+
currentPrompt = `${currentPrompt}\n\n${substitute(hook.append)}`;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
action: "proceed",
|
|
338
|
+
prompt: currentPrompt,
|
|
339
|
+
model: hooks.find(h => h.action === "modify" && h.model)?.model,
|
|
340
|
+
firedHooks,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ── State accessors ─────────────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
getActiveHook(): HookExecutionState | null {
|
|
347
|
+
return this.activeHook;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
isRetryPending(): boolean {
|
|
351
|
+
return this.retryPending;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Returns the trigger unit info for a pending retry, or null.
|
|
356
|
+
* Clears the retry state after reading.
|
|
357
|
+
*/
|
|
358
|
+
consumeRetryTrigger(): { unitType: string; unitId: string; retryArtifact: string } | null {
|
|
359
|
+
if (!this.retryPending || !this.retryTrigger) return null;
|
|
360
|
+
const trigger = { ...this.retryTrigger };
|
|
361
|
+
this.retryPending = false;
|
|
362
|
+
this.retryTrigger = null;
|
|
363
|
+
return trigger;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Clear all mutable state (activeHook, hookQueue, cycleCounts, retryPending, retryTrigger). */
|
|
367
|
+
resetState(): void {
|
|
368
|
+
this.activeHook = null;
|
|
369
|
+
this.hookQueue = [];
|
|
370
|
+
this.cycleCounts.clear();
|
|
371
|
+
this.retryPending = false;
|
|
372
|
+
this.retryTrigger = null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ── Persistence ─────────────────────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
private _hookStatePath(basePath: string): string {
|
|
378
|
+
return join(basePath, ".gsd", HOOK_STATE_FILE);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Persist current hook cycle counts to disk. */
|
|
382
|
+
persistState(basePath: string): void {
|
|
383
|
+
const state: PersistedHookState = {
|
|
384
|
+
cycleCounts: Object.fromEntries(this.cycleCounts),
|
|
385
|
+
savedAt: new Date().toISOString(),
|
|
386
|
+
};
|
|
387
|
+
try {
|
|
388
|
+
const dir = join(basePath, ".gsd");
|
|
389
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
390
|
+
writeFileSync(this._hookStatePath(basePath), JSON.stringify(state, null, 2), "utf-8");
|
|
391
|
+
} catch {
|
|
392
|
+
// Non-fatal — state is recreatable from artifacts
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Restore hook cycle counts from disk after a crash/restart. */
|
|
397
|
+
restoreState(basePath: string): void {
|
|
398
|
+
try {
|
|
399
|
+
const filePath = this._hookStatePath(basePath);
|
|
400
|
+
if (!existsSync(filePath)) return;
|
|
401
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
402
|
+
const state: PersistedHookState = JSON.parse(raw);
|
|
403
|
+
if (state.cycleCounts && typeof state.cycleCounts === "object") {
|
|
404
|
+
this.cycleCounts.clear();
|
|
405
|
+
for (const [key, value] of Object.entries(state.cycleCounts)) {
|
|
406
|
+
if (typeof value === "number") {
|
|
407
|
+
this.cycleCounts.set(key, value);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
} catch {
|
|
412
|
+
// Non-fatal — fresh state is fine
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Clear persisted hook state file from disk. */
|
|
417
|
+
clearPersistedState(basePath: string): void {
|
|
418
|
+
try {
|
|
419
|
+
const filePath = this._hookStatePath(basePath);
|
|
420
|
+
if (existsSync(filePath)) {
|
|
421
|
+
writeFileSync(
|
|
422
|
+
filePath,
|
|
423
|
+
JSON.stringify({ cycleCounts: {}, savedAt: new Date().toISOString() }, null, 2),
|
|
424
|
+
"utf-8",
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
} catch {
|
|
428
|
+
// Non-fatal
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ── Hook status reporting ───────────────────────────────────────────
|
|
433
|
+
|
|
434
|
+
/** Get status of all configured hooks for display. */
|
|
435
|
+
getHookStatus(): HookStatusEntry[] {
|
|
436
|
+
const entries: HookStatusEntry[] = [];
|
|
437
|
+
|
|
438
|
+
const postHooks = resolvePostUnitHooks();
|
|
439
|
+
for (const hook of postHooks) {
|
|
440
|
+
const activeCycles: Record<string, number> = {};
|
|
441
|
+
for (const [key, count] of this.cycleCounts) {
|
|
442
|
+
if (key.startsWith(`${hook.name}/`)) {
|
|
443
|
+
activeCycles[key] = count;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
entries.push({
|
|
447
|
+
name: hook.name,
|
|
448
|
+
type: "post",
|
|
449
|
+
enabled: hook.enabled !== false,
|
|
450
|
+
targets: hook.after,
|
|
451
|
+
activeCycles,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const preHooks = resolvePreDispatchHooks();
|
|
456
|
+
for (const hook of preHooks) {
|
|
457
|
+
entries.push({
|
|
458
|
+
name: hook.name,
|
|
459
|
+
type: "pre",
|
|
460
|
+
enabled: hook.enabled !== false,
|
|
461
|
+
targets: hook.before,
|
|
462
|
+
activeCycles: {},
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return entries;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Manually trigger a specific hook for a unit.
|
|
471
|
+
* Bypasses normal flow — forces hook to run even if artifact exists.
|
|
472
|
+
*/
|
|
473
|
+
triggerHookManually(
|
|
474
|
+
hookName: string,
|
|
475
|
+
unitType: string,
|
|
476
|
+
unitId: string,
|
|
477
|
+
basePath: string,
|
|
478
|
+
): HookDispatchResult | null {
|
|
479
|
+
const hook = resolvePostUnitHooks().find(h => h.name === hookName);
|
|
480
|
+
if (!hook) {
|
|
481
|
+
console.error(`[triggerHookManually] Hook "${hookName}" not found in post_unit_hooks`);
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (!hook.prompt || typeof hook.prompt !== "string" || hook.prompt.trim().length === 0) {
|
|
486
|
+
console.error(`[triggerHookManually] Hook "${hookName}" has empty prompt`);
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
this.activeHook = {
|
|
491
|
+
hookName: hook.name,
|
|
492
|
+
triggerUnitType: unitType,
|
|
493
|
+
triggerUnitId: unitId,
|
|
494
|
+
cycle: 1,
|
|
495
|
+
pendingRetry: false,
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
this.hookQueue = [{
|
|
499
|
+
config: hook,
|
|
500
|
+
triggerUnitType: unitType,
|
|
501
|
+
triggerUnitId: unitId,
|
|
502
|
+
}];
|
|
503
|
+
|
|
504
|
+
const cycleKey = `${hook.name}/${unitType}/${unitId}`;
|
|
505
|
+
const currentCycle = (this.cycleCounts.get(cycleKey) ?? 0) + 1;
|
|
506
|
+
this.cycleCounts.set(cycleKey, currentCycle);
|
|
507
|
+
this.activeHook.cycle = currentCycle;
|
|
508
|
+
|
|
509
|
+
const [mid, sid, tid] = unitId.split("/");
|
|
510
|
+
const prompt = hook.prompt
|
|
511
|
+
.replace(/\{milestoneId\}/g, mid ?? "")
|
|
512
|
+
.replace(/\{sliceId\}/g, sid ?? "")
|
|
513
|
+
.replace(/\{taskId\}/g, tid ?? "");
|
|
514
|
+
|
|
515
|
+
return {
|
|
516
|
+
hookName: hook.name,
|
|
517
|
+
prompt,
|
|
518
|
+
model: hook.model,
|
|
519
|
+
unitType: `hook/${hook.name}`,
|
|
520
|
+
unitId,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/** Format hook status for terminal display. */
|
|
525
|
+
formatHookStatus(): string {
|
|
526
|
+
const entries = this.getHookStatus();
|
|
527
|
+
if (entries.length === 0) {
|
|
528
|
+
return "No hooks configured. Add post_unit_hooks or pre_dispatch_hooks to .gsd/preferences.md";
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const lines: string[] = ["Configured Hooks:", ""];
|
|
532
|
+
|
|
533
|
+
const postHooks = entries.filter(e => e.type === "post");
|
|
534
|
+
const preHooks = entries.filter(e => e.type === "pre");
|
|
535
|
+
|
|
536
|
+
if (postHooks.length > 0) {
|
|
537
|
+
lines.push("Post-Unit Hooks (run after unit completes):");
|
|
538
|
+
for (const hook of postHooks) {
|
|
539
|
+
const status = hook.enabled ? "enabled" : "disabled";
|
|
540
|
+
const cycles = Object.keys(hook.activeCycles).length;
|
|
541
|
+
const cycleInfo = cycles > 0 ? ` (${cycles} active cycle${cycles === 1 ? "" : "s"})` : "";
|
|
542
|
+
lines.push(` ${hook.name} [${status}] → after: ${hook.targets.join(", ")}${cycleInfo}`);
|
|
543
|
+
}
|
|
544
|
+
lines.push("");
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (preHooks.length > 0) {
|
|
548
|
+
lines.push("Pre-Dispatch Hooks (run before unit dispatches):");
|
|
549
|
+
for (const hook of preHooks) {
|
|
550
|
+
const status = hook.enabled ? "enabled" : "disabled";
|
|
551
|
+
lines.push(` ${hook.name} [${status}] → before: ${hook.targets.join(", ")}`);
|
|
552
|
+
}
|
|
553
|
+
lines.push("");
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return lines.join("\n");
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ─── Module-level Singleton ─────────────────────────────────────────────────
|
|
561
|
+
|
|
562
|
+
let _registry: RuleRegistry | null = null;
|
|
563
|
+
|
|
564
|
+
/** Get the singleton registry. Throws if not initialized. */
|
|
565
|
+
export function getRegistry(): RuleRegistry {
|
|
566
|
+
if (!_registry) {
|
|
567
|
+
throw new Error("RuleRegistry not initialized — call initRegistry() or setRegistry() first.");
|
|
568
|
+
}
|
|
569
|
+
return _registry;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** Set the singleton registry instance. */
|
|
573
|
+
export function setRegistry(r: RuleRegistry): void {
|
|
574
|
+
_registry = r;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** Create and set the singleton registry with the given dispatch rules. */
|
|
578
|
+
export function initRegistry(dispatchRules: UnifiedRule[]): RuleRegistry {
|
|
579
|
+
const registry = new RuleRegistry(dispatchRules);
|
|
580
|
+
setRegistry(registry);
|
|
581
|
+
return registry;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Get the singleton registry, lazily creating one with empty dispatch rules
|
|
586
|
+
* if not yet initialized. This ensures facade functions work even when
|
|
587
|
+
* the full registry hasn't been set up (e.g. during testing).
|
|
588
|
+
*/
|
|
589
|
+
export function getOrCreateRegistry(): RuleRegistry {
|
|
590
|
+
if (!_registry) {
|
|
591
|
+
_registry = new RuleRegistry([]);
|
|
592
|
+
}
|
|
593
|
+
return _registry;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** Reset the singleton (for testing). */
|
|
597
|
+
export function resetRegistry(): void {
|
|
598
|
+
_registry = null;
|
|
599
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// GSD Extension — Unified Rule Type Definitions
|
|
2
|
+
//
|
|
3
|
+
// Every dispatch rule and hook is expressed as a `UnifiedRule` with a
|
|
4
|
+
// consistent when/where/then shape. This file defines the type system;
|
|
5
|
+
// the `RuleRegistry` class in rule-registry.ts holds instances at runtime.
|
|
6
|
+
|
|
7
|
+
import type { DispatchAction, DispatchContext } from "./auto-dispatch.js";
|
|
8
|
+
import type {
|
|
9
|
+
PostUnitHookConfig,
|
|
10
|
+
PreDispatchHookConfig,
|
|
11
|
+
HookDispatchResult,
|
|
12
|
+
PreDispatchResult,
|
|
13
|
+
HookExecutionState,
|
|
14
|
+
HookStatusEntry,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
|
|
17
|
+
// ─── Phase & Evaluation Strategy ────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/** Which phase/event a rule responds to. */
|
|
20
|
+
export type RulePhase = "dispatch" | "post-unit" | "pre-dispatch";
|
|
21
|
+
|
|
22
|
+
/** How a rule is evaluated relative to peers in the same phase. */
|
|
23
|
+
export type RuleEvaluation = "first-match" | "all-matching";
|
|
24
|
+
|
|
25
|
+
// ─── Lifecycle Metadata (hooks only) ────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/** Optional lifecycle metadata attached to hook-derived rules. */
|
|
28
|
+
export interface RuleLifecycle {
|
|
29
|
+
/** Expected output file name (relative to unit dir). Used for idempotency. */
|
|
30
|
+
artifact?: string;
|
|
31
|
+
/** If this file is produced instead of artifact, re-run the trigger unit. */
|
|
32
|
+
retry_on?: string;
|
|
33
|
+
/** Max times this hook can fire for the same trigger unit. */
|
|
34
|
+
max_cycles?: number;
|
|
35
|
+
/** Idempotency key pattern for this hook. */
|
|
36
|
+
idempotency_key?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─── Unified Rule ───────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A single entry in the rule registry. Dispatch rules, post-unit hooks,
|
|
43
|
+
* and pre-dispatch hooks all share this shape.
|
|
44
|
+
*/
|
|
45
|
+
export interface UnifiedRule {
|
|
46
|
+
/** Stable human-readable identifier (existing names preserved per D005). */
|
|
47
|
+
name: string;
|
|
48
|
+
/** Which phase/event this rule responds to. */
|
|
49
|
+
when: RulePhase;
|
|
50
|
+
/** How this rule is evaluated relative to peers. */
|
|
51
|
+
evaluation: RuleEvaluation;
|
|
52
|
+
/**
|
|
53
|
+
* Predicate/match function.
|
|
54
|
+
* - Dispatch rules: async, receives DispatchContext, returns DispatchAction | null.
|
|
55
|
+
* - Post-unit hooks: sync, receives (unitType, unitId, basePath).
|
|
56
|
+
* - Pre-dispatch hooks: sync, receives (unitType, unitId, prompt, basePath).
|
|
57
|
+
*/
|
|
58
|
+
where: (...args: any[]) => Promise<any> | any;
|
|
59
|
+
/**
|
|
60
|
+
* Action builder. May be merged with `where` for dispatch rules where
|
|
61
|
+
* the match function returns the action directly.
|
|
62
|
+
*/
|
|
63
|
+
then: (...args: any[]) => any;
|
|
64
|
+
/** Optional human-readable summary for LLM inspection. */
|
|
65
|
+
description?: string;
|
|
66
|
+
/** Optional hook lifecycle metadata. */
|
|
67
|
+
lifecycle?: RuleLifecycle;
|
|
68
|
+
}
|