gsd-pi 2.28.0-dev.e19bf89 → 2.29.0-dev.23d50d0
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 +24 -17
- package/dist/cli.js +15 -9
- package/dist/headless.js +4 -0
- package/dist/resource-loader.js +80 -8
- package/dist/resources/extensions/bg-shell/process-manager.ts +13 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +217 -65
- package/dist/resources/extensions/gsd/auto-dispatch.ts +2 -2
- package/dist/resources/extensions/gsd/auto-post-unit.ts +53 -6
- package/dist/resources/extensions/gsd/auto-prompts.ts +27 -14
- package/dist/resources/extensions/gsd/auto-recovery.ts +33 -23
- package/dist/resources/extensions/gsd/auto-start.ts +25 -10
- package/dist/resources/extensions/gsd/auto-verification.ts +41 -7
- package/dist/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
- package/dist/resources/extensions/gsd/auto-worktree.ts +9 -0
- package/dist/resources/extensions/gsd/auto.ts +67 -22
- package/dist/resources/extensions/gsd/commands-handlers.ts +3 -11
- package/dist/resources/extensions/gsd/commands-logs.ts +536 -0
- package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
- package/dist/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
- package/dist/resources/extensions/gsd/commands.ts +75 -29
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +2 -1
- package/dist/resources/extensions/gsd/doctor-types.ts +13 -0
- package/dist/resources/extensions/gsd/doctor.ts +2 -6
- package/dist/resources/extensions/gsd/export.ts +28 -2
- package/dist/resources/extensions/gsd/gsd-db.ts +19 -0
- package/dist/resources/extensions/gsd/index.ts +2 -1
- package/dist/resources/extensions/gsd/json-persistence.ts +67 -0
- package/dist/resources/extensions/gsd/mechanical-completion.ts +430 -0
- package/dist/resources/extensions/gsd/metrics.ts +17 -31
- package/dist/resources/extensions/gsd/paths.ts +17 -8
- package/dist/resources/extensions/gsd/preferences-models.ts +7 -1
- package/dist/resources/extensions/gsd/preferences-validation.ts +2 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +26 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -1
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +28 -0
- package/dist/resources/extensions/gsd/queue-order.ts +10 -11
- package/dist/resources/extensions/gsd/routing-history.ts +13 -17
- package/dist/resources/extensions/gsd/session-lock.ts +284 -0
- package/dist/resources/extensions/gsd/session-status-io.ts +23 -41
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
- package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/mechanical-completion.test.ts +356 -0
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
- package/dist/resources/extensions/gsd/tests/token-profile.test.ts +14 -16
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
- package/dist/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
- package/dist/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
- package/dist/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
- package/dist/resources/extensions/gsd/types.ts +3 -0
- package/dist/resources/extensions/gsd/unit-runtime.ts +16 -13
- package/dist/resources/extensions/gsd/verification-evidence.ts +2 -0
- package/dist/resources/extensions/gsd/verification-gate.ts +13 -2
- package/dist/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
- package/dist/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
- package/dist/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
- package/dist/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
- package/dist/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
- package/dist/resources/extensions/gsd/workflow-templates/registry.json +85 -0
- package/dist/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
- package/dist/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
- package/dist/resources/extensions/gsd/workflow-templates/spike.md +69 -0
- package/dist/resources/extensions/gsd/workflow-templates.ts +241 -0
- package/dist/resources/extensions/mcp-client/index.ts +459 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +9 -20
- package/dist/resources/extensions/remote-questions/http-client.ts +76 -0
- package/dist/resources/extensions/remote-questions/notify.ts +1 -2
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +11 -18
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
- package/dist/resources/extensions/remote-questions/types.ts +3 -0
- package/dist/resources/extensions/shared/mod.ts +3 -0
- package/dist/resources/skills/create-gsd-extension/SKILL.md +87 -0
- package/dist/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
- package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
- package/dist/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
- package/dist/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
- package/dist/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
- package/dist/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
- package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
- package/dist/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
- package/dist/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
- package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
- package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
- package/dist/resources/skills/create-gsd-extension/references/state-management.md +70 -0
- package/dist/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
- package/dist/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
- package/dist/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
- package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
- package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
- package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
- package/dist/resources/skills/create-skill/SKILL.md +184 -0
- package/dist/resources/skills/create-skill/references/api-security.md +226 -0
- package/dist/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
- package/dist/resources/skills/create-skill/references/common-patterns.md +595 -0
- package/dist/resources/skills/create-skill/references/core-principles.md +437 -0
- package/dist/resources/skills/create-skill/references/executable-code.md +175 -0
- package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
- package/dist/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
- package/dist/resources/skills/create-skill/references/recommended-structure.md +168 -0
- package/dist/resources/skills/create-skill/references/skill-structure.md +372 -0
- package/dist/resources/skills/create-skill/references/use-xml-tags.md +466 -0
- package/dist/resources/skills/create-skill/references/using-scripts.md +113 -0
- package/dist/resources/skills/create-skill/references/using-templates.md +112 -0
- package/dist/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
- package/dist/resources/skills/create-skill/templates/router-skill.md +73 -0
- package/dist/resources/skills/create-skill/templates/simple-skill.md +33 -0
- package/dist/resources/skills/create-skill/workflows/add-reference.md +96 -0
- package/dist/resources/skills/create-skill/workflows/add-script.md +93 -0
- package/dist/resources/skills/create-skill/workflows/add-template.md +74 -0
- package/dist/resources/skills/create-skill/workflows/add-workflow.md +120 -0
- package/dist/resources/skills/create-skill/workflows/audit-skill.md +148 -0
- package/dist/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
- package/dist/resources/skills/create-skill/workflows/get-guidance.md +121 -0
- package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
- package/dist/resources/skills/create-skill/workflows/verify-skill.md +204 -0
- package/package.json +6 -3
- package/packages/native/dist/native.d.ts +2 -0
- package/packages/native/dist/native.js +19 -5
- package/packages/native/src/native.ts +23 -9
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +13 -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 +3 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.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 +10 -0
- package/packages/pi-coding-agent/dist/core/system-prompt.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 -1
- 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/scripts/copy-assets.cjs +39 -8
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +13 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +3 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +11 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +4 -1
- package/packages/pi-tui/dist/autocomplete.d.ts +3 -0
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +14 -0
- package/packages/pi-tui/dist/autocomplete.js.map +1 -1
- package/packages/pi-tui/src/autocomplete.ts +19 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/bg-shell/process-manager.ts +13 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +217 -65
- package/src/resources/extensions/gsd/auto-dispatch.ts +2 -2
- package/src/resources/extensions/gsd/auto-post-unit.ts +53 -6
- package/src/resources/extensions/gsd/auto-prompts.ts +27 -14
- package/src/resources/extensions/gsd/auto-recovery.ts +33 -23
- package/src/resources/extensions/gsd/auto-start.ts +25 -10
- package/src/resources/extensions/gsd/auto-verification.ts +41 -7
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +21 -6
- package/src/resources/extensions/gsd/auto-worktree.ts +9 -0
- package/src/resources/extensions/gsd/auto.ts +67 -22
- package/src/resources/extensions/gsd/commands-handlers.ts +3 -11
- package/src/resources/extensions/gsd/commands-logs.ts +536 -0
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +90 -47
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
- package/src/resources/extensions/gsd/commands.ts +75 -29
- package/src/resources/extensions/gsd/dashboard-overlay.ts +2 -1
- package/src/resources/extensions/gsd/doctor-types.ts +13 -0
- package/src/resources/extensions/gsd/doctor.ts +2 -6
- package/src/resources/extensions/gsd/export.ts +28 -2
- package/src/resources/extensions/gsd/gsd-db.ts +19 -0
- package/src/resources/extensions/gsd/index.ts +2 -1
- package/src/resources/extensions/gsd/json-persistence.ts +67 -0
- package/src/resources/extensions/gsd/mechanical-completion.ts +430 -0
- package/src/resources/extensions/gsd/metrics.ts +17 -31
- package/src/resources/extensions/gsd/paths.ts +17 -8
- package/src/resources/extensions/gsd/preferences-models.ts +7 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +2 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +26 -2
- package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -1
- package/src/resources/extensions/gsd/prompts/workflow-start.md +28 -0
- package/src/resources/extensions/gsd/queue-order.ts +10 -11
- package/src/resources/extensions/gsd/routing-history.ts +13 -17
- package/src/resources/extensions/gsd/session-lock.ts +284 -0
- package/src/resources/extensions/gsd/session-status-io.ts +23 -41
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/commands-logs.test.ts +241 -0
- package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +356 -0
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/session-lock.test.ts +315 -0
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +14 -16
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +26 -24
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +136 -7
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
- package/src/resources/extensions/gsd/types.ts +3 -0
- package/src/resources/extensions/gsd/unit-runtime.ts +16 -13
- package/src/resources/extensions/gsd/verification-evidence.ts +2 -0
- package/src/resources/extensions/gsd/verification-gate.ts +13 -2
- package/src/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
- package/src/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
- package/src/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
- package/src/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
- package/src/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
- package/src/resources/extensions/gsd/workflow-templates/registry.json +85 -0
- package/src/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
- package/src/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
- package/src/resources/extensions/gsd/workflow-templates/spike.md +69 -0
- package/src/resources/extensions/gsd/workflow-templates.ts +241 -0
- package/src/resources/extensions/mcp-client/index.ts +459 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +9 -20
- package/src/resources/extensions/remote-questions/http-client.ts +76 -0
- package/src/resources/extensions/remote-questions/notify.ts +1 -2
- package/src/resources/extensions/remote-questions/slack-adapter.ts +11 -18
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +8 -20
- package/src/resources/extensions/remote-questions/types.ts +3 -0
- package/src/resources/extensions/shared/mod.ts +3 -0
- package/src/resources/skills/create-gsd-extension/SKILL.md +87 -0
- package/src/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
- package/src/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
- package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
- package/src/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
- package/src/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
- package/src/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
- package/src/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
- package/src/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
- package/src/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
- package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
- package/src/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
- package/src/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
- package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
- package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
- package/src/resources/skills/create-gsd-extension/references/state-management.md +70 -0
- package/src/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
- package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
- package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
- package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
- package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
- package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
- package/src/resources/skills/create-skill/SKILL.md +184 -0
- package/src/resources/skills/create-skill/references/api-security.md +226 -0
- package/src/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
- package/src/resources/skills/create-skill/references/common-patterns.md +595 -0
- package/src/resources/skills/create-skill/references/core-principles.md +437 -0
- package/src/resources/skills/create-skill/references/executable-code.md +175 -0
- package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
- package/src/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
- package/src/resources/skills/create-skill/references/recommended-structure.md +168 -0
- package/src/resources/skills/create-skill/references/skill-structure.md +372 -0
- package/src/resources/skills/create-skill/references/use-xml-tags.md +466 -0
- package/src/resources/skills/create-skill/references/using-scripts.md +113 -0
- package/src/resources/skills/create-skill/references/using-templates.md +112 -0
- package/src/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
- package/src/resources/skills/create-skill/templates/router-skill.md +73 -0
- package/src/resources/skills/create-skill/templates/simple-skill.md +33 -0
- package/src/resources/skills/create-skill/workflows/add-reference.md +96 -0
- package/src/resources/skills/create-skill/workflows/add-script.md +93 -0
- package/src/resources/skills/create-skill/workflows/add-template.md +74 -0
- package/src/resources/skills/create-skill/workflows/add-workflow.md +120 -0
- package/src/resources/skills/create-skill/workflows/audit-skill.md +148 -0
- package/src/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
- package/src/resources/skills/create-skill/workflows/get-guidance.md +121 -0
- package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
- package/src/resources/skills/create-skill/workflows/verify-skill.md +204 -0
- package/dist/resources/extensions/gsd/preferences-hooks.ts +0 -10
- package/dist/resources/extensions/mcporter/index.ts +0 -525
- package/dist/resources/extensions/shared/progress-widget.ts +0 -282
- package/dist/resources/extensions/shared/thinking-widget.ts +0 -107
- package/src/resources/extensions/gsd/preferences-hooks.ts +0 -10
- package/src/resources/extensions/mcporter/index.ts +0 -525
- package/src/resources/extensions/shared/progress-widget.ts +0 -282
- package/src/resources/extensions/shared/thinking-widget.ts +0 -107
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Workflow Template: {{templateName}}
|
|
2
|
+
|
|
3
|
+
You are executing a **{{templateName}}** workflow (template: `{{templateId}}`).
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
- **Description:** {{description}}
|
|
8
|
+
- **Issue reference:** {{issueRef}}
|
|
9
|
+
- **Date:** {{date}}
|
|
10
|
+
- **Branch:** {{branch}}
|
|
11
|
+
- **Artifact directory:** {{artifactDir}}
|
|
12
|
+
- **Phases:** {{phases}}
|
|
13
|
+
- **Complexity:** {{complexity}}
|
|
14
|
+
|
|
15
|
+
## Workflow Definition
|
|
16
|
+
|
|
17
|
+
Follow the workflow defined below. Execute each phase in order, completing one before moving to the next. At each phase gate, confirm with the user before proceeding.
|
|
18
|
+
|
|
19
|
+
{{workflowContent}}
|
|
20
|
+
|
|
21
|
+
## Execution Rules
|
|
22
|
+
|
|
23
|
+
1. **Follow the phases in order.** Do not skip phases unless the workflow explicitly allows it.
|
|
24
|
+
2. **Artifact discipline.** If an artifact directory is specified, write all planning/summary documents there.
|
|
25
|
+
3. **Atomic commits.** Commit working code after each meaningful change. Use conventional commit format: `<type>(<scope>): <description>`.
|
|
26
|
+
4. **Verify before shipping.** Run the project's test suite and build before marking the workflow complete.
|
|
27
|
+
5. **Gate between phases.** After each phase, summarize what was done and ask the user to confirm before moving to the next phase.
|
|
28
|
+
6. **Stay focused.** This is a {{complexity}}-complexity workflow. Match your ceremony level to the task — don't over-engineer or under-deliver.
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* survives branch switches and is shared across sessions.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
13
12
|
import { join } from "node:path";
|
|
14
13
|
import { gsdRoot } from "./paths.js";
|
|
15
14
|
import { milestoneIdSort } from "./milestone-ids.js";
|
|
15
|
+
import { loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
|
|
16
16
|
|
|
17
17
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
18
18
|
|
|
@@ -45,6 +45,12 @@ function queueOrderPath(basePath: string): string {
|
|
|
45
45
|
return join(gsdRoot(basePath), "QUEUE-ORDER.json");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// ─── Type Guards ─────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
function isQueueOrderFile(data: unknown): data is QueueOrderFile {
|
|
51
|
+
return data !== null && typeof data === "object" && "order" in data! && Array.isArray((data as QueueOrderFile).order);
|
|
52
|
+
}
|
|
53
|
+
|
|
48
54
|
// ─── Read / Write ────────────────────────────────────────────────────────────
|
|
49
55
|
|
|
50
56
|
/**
|
|
@@ -52,15 +58,8 @@ function queueOrderPath(basePath: string): string {
|
|
|
52
58
|
* the file is corrupt/unreadable.
|
|
53
59
|
*/
|
|
54
60
|
export function loadQueueOrder(basePath: string): string[] | null {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
const data: QueueOrderFile = JSON.parse(readFileSync(p, "utf-8"));
|
|
59
|
-
if (!Array.isArray(data.order)) return null;
|
|
60
|
-
return data.order;
|
|
61
|
-
} catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
61
|
+
const data = loadJsonFileOrNull(queueOrderPath(basePath), isQueueOrderFile);
|
|
62
|
+
return data?.order ?? null;
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
/**
|
|
@@ -71,7 +70,7 @@ export function saveQueueOrder(basePath: string, order: string[]): void {
|
|
|
71
70
|
order,
|
|
72
71
|
updatedAt: new Date().toISOString(),
|
|
73
72
|
};
|
|
74
|
-
|
|
73
|
+
saveJsonFile(queueOrderPath(basePath), data);
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
// ─── Sorting ─────────────────────────────────────────────────────────────────
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
// Tracks success/failure per tier per unit-type pattern to improve
|
|
3
3
|
// classification accuracy over time.
|
|
4
4
|
|
|
5
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
6
5
|
import { join } from "node:path";
|
|
7
6
|
import { gsdRoot } from "./paths.js";
|
|
8
7
|
import type { ComplexityTier } from "./types.js";
|
|
8
|
+
import { loadJsonFile, saveJsonFile } from "./json-persistence.js";
|
|
9
9
|
|
|
10
10
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
11
11
|
|
|
@@ -267,24 +267,20 @@ function historyPath(base: string): string {
|
|
|
267
267
|
return join(gsdRoot(base), HISTORY_FILE);
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
function isRoutingHistoryData(data: unknown): data is RoutingHistoryData {
|
|
271
|
+
return (
|
|
272
|
+
typeof data === "object" &&
|
|
273
|
+
data !== null &&
|
|
274
|
+
(data as RoutingHistoryData).version === 1 &&
|
|
275
|
+
typeof (data as RoutingHistoryData).patterns === "object" &&
|
|
276
|
+
(data as RoutingHistoryData).patterns !== null
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
270
280
|
function loadHistory(base: string): RoutingHistoryData {
|
|
271
|
-
|
|
272
|
-
const raw = readFileSync(historyPath(base), "utf-8");
|
|
273
|
-
const parsed = JSON.parse(raw);
|
|
274
|
-
if (parsed.version === 1 && parsed.patterns) {
|
|
275
|
-
return parsed as RoutingHistoryData;
|
|
276
|
-
}
|
|
277
|
-
} catch {
|
|
278
|
-
// File doesn't exist or is corrupt — start fresh
|
|
279
|
-
}
|
|
280
|
-
return createEmptyHistory();
|
|
281
|
+
return loadJsonFile(historyPath(base), isRoutingHistoryData, createEmptyHistory);
|
|
281
282
|
}
|
|
282
283
|
|
|
283
284
|
function saveHistory(base: string, data: RoutingHistoryData): void {
|
|
284
|
-
|
|
285
|
-
mkdirSync(gsdRoot(base), { recursive: true });
|
|
286
|
-
writeFileSync(historyPath(base), JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
287
|
-
} catch {
|
|
288
|
-
// Non-fatal — don't let history failures break auto-mode
|
|
289
|
-
}
|
|
285
|
+
saveJsonFile(historyPath(base), data);
|
|
290
286
|
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Session Lock — OS-level exclusive locking for auto-mode sessions.
|
|
3
|
+
*
|
|
4
|
+
* Prevents multiple GSD processes from running auto-mode concurrently on
|
|
5
|
+
* the same project. Uses proper-lockfile for OS-level file locking (flock/
|
|
6
|
+
* lockfile) which eliminates the TOCTOU race condition that existed with
|
|
7
|
+
* the old advisory JSON lock approach.
|
|
8
|
+
*
|
|
9
|
+
* The lock file (.gsd/auto.lock) contains JSON metadata (PID, start time,
|
|
10
|
+
* unit info) for diagnostics, but the actual exclusion is enforced by the
|
|
11
|
+
* OS-level lock held via proper-lockfile.
|
|
12
|
+
*
|
|
13
|
+
* Lifecycle:
|
|
14
|
+
* acquireSessionLock() — called at the START of bootstrapAutoSession
|
|
15
|
+
* validateSessionLock() — called periodically during dispatch to detect takeover
|
|
16
|
+
* releaseSessionLock() — called on clean stop/pause
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { createRequire } from "node:module";
|
|
20
|
+
import { existsSync, readFileSync, mkdirSync, unlinkSync } from "node:fs";
|
|
21
|
+
import { join, dirname } from "node:path";
|
|
22
|
+
import { gsdRoot } from "./paths.js";
|
|
23
|
+
import { atomicWriteSync } from "./atomic-write.js";
|
|
24
|
+
|
|
25
|
+
const _require = createRequire(import.meta.url);
|
|
26
|
+
|
|
27
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
export interface SessionLockData {
|
|
30
|
+
pid: number;
|
|
31
|
+
startedAt: string;
|
|
32
|
+
unitType: string;
|
|
33
|
+
unitId: string;
|
|
34
|
+
unitStartedAt: string;
|
|
35
|
+
completedUnits: number;
|
|
36
|
+
sessionFile?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type SessionLockResult =
|
|
40
|
+
| { acquired: true }
|
|
41
|
+
| { acquired: false; reason: string; existingPid?: number };
|
|
42
|
+
|
|
43
|
+
// ─── Module State ───────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/** Release function from proper-lockfile — calling it releases the OS lock. */
|
|
46
|
+
let _releaseFunction: (() => void) | null = null;
|
|
47
|
+
|
|
48
|
+
/** The path we currently hold a lock on. */
|
|
49
|
+
let _lockedPath: string | null = null;
|
|
50
|
+
|
|
51
|
+
/** Our PID at lock acquisition time. */
|
|
52
|
+
let _lockPid: number = 0;
|
|
53
|
+
|
|
54
|
+
const LOCK_FILE = "auto.lock";
|
|
55
|
+
|
|
56
|
+
function lockPath(basePath: string): string {
|
|
57
|
+
return join(gsdRoot(basePath), LOCK_FILE);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Attempt to acquire an exclusive session lock for the given project.
|
|
64
|
+
*
|
|
65
|
+
* This uses proper-lockfile for OS-level file locking. If another process
|
|
66
|
+
* already holds the lock, this returns { acquired: false } with details.
|
|
67
|
+
*
|
|
68
|
+
* The lock file also contains JSON metadata about the session for
|
|
69
|
+
* diagnostic purposes (PID, unit info, etc.).
|
|
70
|
+
*/
|
|
71
|
+
export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
72
|
+
const lp = lockPath(basePath);
|
|
73
|
+
|
|
74
|
+
// Ensure the directory exists
|
|
75
|
+
mkdirSync(dirname(lp), { recursive: true });
|
|
76
|
+
|
|
77
|
+
// Write our lock data first (the content is informational; the OS lock is the real guard)
|
|
78
|
+
const lockData: SessionLockData = {
|
|
79
|
+
pid: process.pid,
|
|
80
|
+
startedAt: new Date().toISOString(),
|
|
81
|
+
unitType: "starting",
|
|
82
|
+
unitId: "bootstrap",
|
|
83
|
+
unitStartedAt: new Date().toISOString(),
|
|
84
|
+
completedUnits: 0,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
let lockfile: typeof import("proper-lockfile");
|
|
88
|
+
try {
|
|
89
|
+
lockfile = _require("proper-lockfile") as typeof import("proper-lockfile");
|
|
90
|
+
} catch {
|
|
91
|
+
// proper-lockfile not available — fall back to PID-based check
|
|
92
|
+
return acquireFallbackLock(basePath, lp, lockData);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Try to acquire an exclusive OS-level lock on the lock file.
|
|
97
|
+
// We lock the directory (gsdRoot) since proper-lockfile works best
|
|
98
|
+
// on directories, and the lock file itself may not exist yet.
|
|
99
|
+
const gsdDir = gsdRoot(basePath);
|
|
100
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
101
|
+
|
|
102
|
+
const release = lockfile.lockSync(gsdDir, {
|
|
103
|
+
realpath: false,
|
|
104
|
+
stale: 300_000, // 5 minutes — consider lock stale if holder hasn't updated
|
|
105
|
+
update: 10_000, // Update lock mtime every 10s to prove liveness
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
_releaseFunction = release;
|
|
109
|
+
_lockedPath = basePath;
|
|
110
|
+
_lockPid = process.pid;
|
|
111
|
+
|
|
112
|
+
// Write the informational lock data
|
|
113
|
+
atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
|
|
114
|
+
|
|
115
|
+
return { acquired: true };
|
|
116
|
+
} catch (err) {
|
|
117
|
+
// Lock is held by another process
|
|
118
|
+
const existingData = readExistingLockData(lp);
|
|
119
|
+
const existingPid = existingData?.pid;
|
|
120
|
+
const reason = existingPid
|
|
121
|
+
? `Another auto-mode session (PID ${existingPid}) is already running on this project.`
|
|
122
|
+
: `Another auto-mode session is already running on this project.`;
|
|
123
|
+
|
|
124
|
+
return { acquired: false, reason, existingPid };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Fallback lock acquisition when proper-lockfile is not available.
|
|
130
|
+
* Uses PID-based liveness checking (the old approach, but with the lock
|
|
131
|
+
* written BEFORE initialization rather than after).
|
|
132
|
+
*/
|
|
133
|
+
function acquireFallbackLock(
|
|
134
|
+
basePath: string,
|
|
135
|
+
lp: string,
|
|
136
|
+
lockData: SessionLockData,
|
|
137
|
+
): SessionLockResult {
|
|
138
|
+
// Check if an existing lock is held by a live process
|
|
139
|
+
const existing = readExistingLockData(lp);
|
|
140
|
+
if (existing && existing.pid !== process.pid) {
|
|
141
|
+
if (isPidAlive(existing.pid)) {
|
|
142
|
+
return {
|
|
143
|
+
acquired: false,
|
|
144
|
+
reason: `Another auto-mode session (PID ${existing.pid}) is already running on this project.`,
|
|
145
|
+
existingPid: existing.pid,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// Stale lock from dead process — we can take over
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Write our lock data
|
|
152
|
+
atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
|
|
153
|
+
_lockedPath = basePath;
|
|
154
|
+
_lockPid = process.pid;
|
|
155
|
+
|
|
156
|
+
return { acquired: true };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Update the lock file metadata (called on each unit dispatch).
|
|
161
|
+
* Does NOT re-acquire the OS lock — just updates the JSON content.
|
|
162
|
+
*/
|
|
163
|
+
export function updateSessionLock(
|
|
164
|
+
basePath: string,
|
|
165
|
+
unitType: string,
|
|
166
|
+
unitId: string,
|
|
167
|
+
completedUnits: number,
|
|
168
|
+
sessionFile?: string,
|
|
169
|
+
): void {
|
|
170
|
+
if (_lockedPath !== basePath && _lockedPath !== null) return;
|
|
171
|
+
|
|
172
|
+
const lp = lockPath(basePath);
|
|
173
|
+
try {
|
|
174
|
+
const data: SessionLockData = {
|
|
175
|
+
pid: process.pid,
|
|
176
|
+
startedAt: new Date().toISOString(),
|
|
177
|
+
unitType,
|
|
178
|
+
unitId,
|
|
179
|
+
unitStartedAt: new Date().toISOString(),
|
|
180
|
+
completedUnits,
|
|
181
|
+
sessionFile,
|
|
182
|
+
};
|
|
183
|
+
atomicWriteSync(lp, JSON.stringify(data, null, 2));
|
|
184
|
+
} catch {
|
|
185
|
+
// Non-fatal: lock update failure
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Validate that we still own the session lock.
|
|
191
|
+
*
|
|
192
|
+
* Returns true if we still hold the lock, false if another process
|
|
193
|
+
* has taken over (indicating we should gracefully stop).
|
|
194
|
+
*
|
|
195
|
+
* This is called periodically during the dispatch loop.
|
|
196
|
+
*/
|
|
197
|
+
export function validateSessionLock(basePath: string): boolean {
|
|
198
|
+
// If we have an OS-level lock, we're still the owner
|
|
199
|
+
if (_releaseFunction && _lockedPath === basePath) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Fallback: check the lock file PID
|
|
204
|
+
const lp = lockPath(basePath);
|
|
205
|
+
const existing = readExistingLockData(lp);
|
|
206
|
+
if (!existing) {
|
|
207
|
+
// Lock file was deleted — we lost ownership
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return existing.pid === process.pid;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Release the session lock. Called on clean stop/pause.
|
|
216
|
+
*/
|
|
217
|
+
export function releaseSessionLock(basePath: string): void {
|
|
218
|
+
// Release the OS-level lock
|
|
219
|
+
if (_releaseFunction) {
|
|
220
|
+
try {
|
|
221
|
+
_releaseFunction();
|
|
222
|
+
} catch {
|
|
223
|
+
// Lock may already be released
|
|
224
|
+
}
|
|
225
|
+
_releaseFunction = null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Remove the lock file
|
|
229
|
+
const lp = lockPath(basePath);
|
|
230
|
+
try {
|
|
231
|
+
if (existsSync(lp)) unlinkSync(lp);
|
|
232
|
+
} catch {
|
|
233
|
+
// Non-fatal
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
_lockedPath = null;
|
|
237
|
+
_lockPid = 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Check if a session lock exists and return its data (for crash recovery).
|
|
242
|
+
* Does NOT acquire the lock.
|
|
243
|
+
*/
|
|
244
|
+
export function readSessionLockData(basePath: string): SessionLockData | null {
|
|
245
|
+
return readExistingLockData(lockPath(basePath));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Check if the process that wrote the lock is still alive.
|
|
250
|
+
*/
|
|
251
|
+
export function isSessionLockProcessAlive(data: SessionLockData): boolean {
|
|
252
|
+
return isPidAlive(data.pid);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Returns true if we currently hold a session lock for the given path.
|
|
257
|
+
*/
|
|
258
|
+
export function isSessionLockHeld(basePath: string): boolean {
|
|
259
|
+
return _lockedPath === basePath && _lockPid === process.pid;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ─── Internal Helpers ───────────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
function readExistingLockData(lp: string): SessionLockData | null {
|
|
265
|
+
try {
|
|
266
|
+
if (!existsSync(lp)) return null;
|
|
267
|
+
const raw = readFileSync(lp, "utf-8");
|
|
268
|
+
return JSON.parse(raw) as SessionLockData;
|
|
269
|
+
} catch {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function isPidAlive(pid: number): boolean {
|
|
275
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
276
|
+
if (pid === process.pid) return false;
|
|
277
|
+
try {
|
|
278
|
+
process.kill(pid, 0);
|
|
279
|
+
return true;
|
|
280
|
+
} catch (err) {
|
|
281
|
+
if ((err as NodeJS.ErrnoException).code === "EPERM") return true;
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -11,9 +11,6 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
|
-
writeFileSync,
|
|
15
|
-
readFileSync,
|
|
16
|
-
renameSync,
|
|
17
14
|
unlinkSync,
|
|
18
15
|
readdirSync,
|
|
19
16
|
mkdirSync,
|
|
@@ -21,6 +18,7 @@ import {
|
|
|
21
18
|
} from "node:fs";
|
|
22
19
|
import { join } from "node:path";
|
|
23
20
|
import { gsdRoot } from "./paths.js";
|
|
21
|
+
import { loadJsonFileOrNull, writeJsonFileAtomic } from "./json-persistence.js";
|
|
24
22
|
|
|
25
23
|
// ─── Types ─────────────────────────────────────────────────────────────────
|
|
26
24
|
|
|
@@ -49,9 +47,16 @@ export interface SignalMessage {
|
|
|
49
47
|
const PARALLEL_DIR = "parallel";
|
|
50
48
|
const STATUS_SUFFIX = ".status.json";
|
|
51
49
|
const SIGNAL_SUFFIX = ".signal.json";
|
|
52
|
-
const TMP_SUFFIX = ".tmp";
|
|
53
50
|
const DEFAULT_STALE_TIMEOUT_MS = 30_000;
|
|
54
51
|
|
|
52
|
+
function isSessionStatus(data: unknown): data is SessionStatus {
|
|
53
|
+
return data !== null && typeof data === "object" && "milestoneId" in data && "pid" in data;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isSignalMessage(data: unknown): data is SignalMessage {
|
|
57
|
+
return data !== null && typeof data === "object" && "signal" in data && "sentAt" in data;
|
|
58
|
+
}
|
|
59
|
+
|
|
55
60
|
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
56
61
|
|
|
57
62
|
function parallelDir(basePath: string): string {
|
|
@@ -86,25 +91,13 @@ function isPidAlive(pid: number): boolean {
|
|
|
86
91
|
|
|
87
92
|
/** Write session status atomically (write to .tmp, then rename). */
|
|
88
93
|
export function writeSessionStatus(basePath: string, status: SessionStatus): void {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const dest = statusPath(basePath, status.milestoneId);
|
|
92
|
-
const tmp = dest + TMP_SUFFIX;
|
|
93
|
-
writeFileSync(tmp, JSON.stringify(status, null, 2), "utf-8");
|
|
94
|
-
renameSync(tmp, dest);
|
|
95
|
-
} catch { /* non-fatal */ }
|
|
94
|
+
ensureParallelDir(basePath);
|
|
95
|
+
writeJsonFileAtomic(statusPath(basePath, status.milestoneId), status);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/** Read a specific milestone's session status. */
|
|
99
99
|
export function readSessionStatus(basePath: string, milestoneId: string): SessionStatus | null {
|
|
100
|
-
|
|
101
|
-
const p = statusPath(basePath, milestoneId);
|
|
102
|
-
if (!existsSync(p)) return null;
|
|
103
|
-
const raw = readFileSync(p, "utf-8");
|
|
104
|
-
return JSON.parse(raw) as SessionStatus;
|
|
105
|
-
} catch {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
100
|
+
return loadJsonFileOrNull(statusPath(basePath, milestoneId), isSessionStatus);
|
|
108
101
|
}
|
|
109
102
|
|
|
110
103
|
/** Read all session status files from .gsd/parallel/. */
|
|
@@ -114,13 +107,10 @@ export function readAllSessionStatuses(basePath: string): SessionStatus[] {
|
|
|
114
107
|
|
|
115
108
|
const results: SessionStatus[] = [];
|
|
116
109
|
try {
|
|
117
|
-
const
|
|
118
|
-
for (const entry of entries) {
|
|
110
|
+
for (const entry of readdirSync(dir)) {
|
|
119
111
|
if (!entry.endsWith(STATUS_SUFFIX)) continue;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
results.push(JSON.parse(raw) as SessionStatus);
|
|
123
|
-
} catch { /* skip corrupt files */ }
|
|
112
|
+
const status = loadJsonFileOrNull(join(dir, entry), isSessionStatus);
|
|
113
|
+
if (status) results.push(status);
|
|
124
114
|
}
|
|
125
115
|
} catch { /* non-fatal */ }
|
|
126
116
|
return results;
|
|
@@ -138,27 +128,19 @@ export function removeSessionStatus(basePath: string, milestoneId: string): void
|
|
|
138
128
|
|
|
139
129
|
/** Write a signal file for a worker to consume. */
|
|
140
130
|
export function sendSignal(basePath: string, milestoneId: string, signal: SessionSignal): void {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const tmp = dest + TMP_SUFFIX;
|
|
145
|
-
const msg: SignalMessage = { signal, sentAt: Date.now(), from: "coordinator" };
|
|
146
|
-
writeFileSync(tmp, JSON.stringify(msg, null, 2), "utf-8");
|
|
147
|
-
renameSync(tmp, dest);
|
|
148
|
-
} catch { /* non-fatal */ }
|
|
131
|
+
ensureParallelDir(basePath);
|
|
132
|
+
const msg: SignalMessage = { signal, sentAt: Date.now(), from: "coordinator" };
|
|
133
|
+
writeJsonFileAtomic(signalPath(basePath, milestoneId), msg);
|
|
149
134
|
}
|
|
150
135
|
|
|
151
136
|
/** Read and delete a signal file (atomic consume). Returns null if no signal pending. */
|
|
152
137
|
export function consumeSignal(basePath: string, milestoneId: string): SignalMessage | null {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
unlinkSync(p);
|
|
158
|
-
return JSON.parse(raw) as SignalMessage;
|
|
159
|
-
} catch {
|
|
160
|
-
return null;
|
|
138
|
+
const p = signalPath(basePath, milestoneId);
|
|
139
|
+
const msg = loadJsonFileOrNull(p, isSignalMessage);
|
|
140
|
+
if (msg) {
|
|
141
|
+
try { unlinkSync(p); } catch { /* non-fatal */ }
|
|
161
142
|
}
|
|
143
|
+
return msg;
|
|
162
144
|
}
|
|
163
145
|
|
|
164
146
|
// ─── Stale Detection ───────────────────────────────────────────────────────
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
getBudgetAlertLevel,
|
|
6
6
|
getBudgetEnforcementAction,
|
|
7
7
|
getNewBudgetAlertLevel,
|
|
8
|
-
} from "../auto.js";
|
|
8
|
+
} from "../auto-budget.js";
|
|
9
9
|
|
|
10
10
|
test("getBudgetAlertLevel returns the expected threshold bucket", () => {
|
|
11
11
|
assert.equal(getBudgetAlertLevel(0.10), 0);
|
|
@@ -17,8 +17,8 @@ import { tmpdir } from "node:os";
|
|
|
17
17
|
import {
|
|
18
18
|
_getUnitConsecutiveSkips,
|
|
19
19
|
_resetUnitConsecutiveSkips,
|
|
20
|
-
MAX_CONSECUTIVE_SKIPS,
|
|
21
20
|
} from "../auto.ts";
|
|
21
|
+
import { MAX_CONSECUTIVE_SKIPS } from "../auto/session.ts";
|
|
22
22
|
import { persistCompletedKey, removePersistedKey, loadPersistedKeys } from "../auto-recovery.ts";
|
|
23
23
|
import { createTestContext } from "./test-helpers.ts";
|
|
24
24
|
|