gsd-pi 2.37.1 → 2.38.0-dev.4d4d14a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/app-paths.js +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/extension-registry.js +2 -2
- package/dist/onboarding.js +1 -0
- package/dist/remote-questions-config.js +2 -2
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/env-utils.js +29 -0
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/github-sync/cli.js +284 -0
- package/dist/resources/extensions/github-sync/index.js +73 -0
- package/dist/resources/extensions/github-sync/mapping.js +67 -0
- package/dist/resources/extensions/github-sync/sync.js +424 -0
- package/dist/resources/extensions/github-sync/templates.js +118 -0
- package/dist/resources/extensions/github-sync/types.js +7 -0
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +74 -9
- package/dist/resources/extensions/gsd/auto-loop.js +149 -170
- package/dist/resources/extensions/gsd/auto-post-unit.js +105 -68
- package/dist/resources/extensions/gsd/auto-prompts.js +98 -33
- package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
- package/dist/resources/extensions/gsd/auto-start.js +13 -2
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +22 -2
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +62 -12
- package/dist/resources/extensions/gsd/doctor.js +184 -11
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +43 -2
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-service.js +8 -1
- package/dist/resources/extensions/gsd/index.js +24 -20
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/observability-validator.js +24 -0
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +3 -2
- package/dist/resources/extensions/gsd/preferences-validation.js +101 -11
- package/dist/resources/extensions/gsd/preferences.js +8 -5
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
- package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
- package/dist/resources/extensions/gsd/repo-identity.js +21 -4
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/remote-questions/status.js +2 -1
- package/dist/resources/extensions/remote-questions/store.js +2 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +2 -1
- package/packages/pi-ai/dist/env-api-keys.js +13 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +172 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +172 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +47 -764
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
- package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
- package/packages/pi-ai/package.json +1 -0
- package/packages/pi-ai/src/env-api-keys.ts +14 -0
- package/packages/pi-ai/src/models.generated.ts +172 -0
- package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
- package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
- package/packages/pi-ai/src/providers/anthropic.ts +76 -868
- package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
- package/packages/pi-ai/src/types.ts +2 -0
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/env-utils.ts +31 -0
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/github-sync/cli.ts +364 -0
- package/src/resources/extensions/github-sync/index.ts +93 -0
- package/src/resources/extensions/github-sync/mapping.ts +81 -0
- package/src/resources/extensions/github-sync/sync.ts +556 -0
- package/src/resources/extensions/github-sync/templates.ts +183 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
- package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
- package/src/resources/extensions/github-sync/types.ts +47 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +99 -8
- package/src/resources/extensions/gsd/auto-loop.ts +207 -252
- package/src/resources/extensions/gsd/auto-post-unit.ts +82 -39
- package/src/resources/extensions/gsd/auto-prompts.ts +132 -36
- package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
- package/src/resources/extensions/gsd/auto-start.ts +18 -2
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +24 -2
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +64 -10
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +177 -13
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +47 -2
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +13 -1
- package/src/resources/extensions/gsd/index.ts +24 -17
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/observability-validator.ts +27 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +9 -5
- package/src/resources/extensions/gsd/preferences-validation.ts +92 -11
- package/src/resources/extensions/gsd/preferences.ts +8 -5
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
- package/src/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
- package/src/resources/extensions/gsd/repo-identity.ts +23 -4
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +16 -37
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +191 -3
- package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +43 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/remote-questions/status.ts +3 -1
- package/src/resources/extensions/remote-questions/store.ts +3 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/subagent/index.ts +12 -3
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Reactive Task Execution — Parallel Dispatch
|
|
2
|
+
|
|
3
|
+
**Working directory:** `{{workingDirectory}}`
|
|
4
|
+
**Milestone:** {{milestoneId}} — {{milestoneTitle}}
|
|
5
|
+
**Slice:** {{sliceId}} — {{sliceTitle}}
|
|
6
|
+
|
|
7
|
+
## Mission
|
|
8
|
+
|
|
9
|
+
You are executing **multiple tasks in parallel** for this slice. The task graph below shows which tasks are ready for simultaneous execution based on their input/output dependencies.
|
|
10
|
+
|
|
11
|
+
**Critical rule:** Use the `subagent` tool in **parallel mode** to dispatch all ready tasks simultaneously. Each subagent gets a full `execute-task` prompt and is responsible for its own implementation, verification, task summary, and checkbox updates. The parent batch agent orchestrates, verifies, and records failures only when a dispatched task failed before it could leave its own summary behind.
|
|
12
|
+
|
|
13
|
+
## Task Dependency Graph
|
|
14
|
+
|
|
15
|
+
{{graphContext}}
|
|
16
|
+
|
|
17
|
+
## Ready Tasks for Parallel Dispatch
|
|
18
|
+
|
|
19
|
+
{{readyTaskCount}} tasks are ready for parallel execution:
|
|
20
|
+
|
|
21
|
+
{{readyTaskList}}
|
|
22
|
+
|
|
23
|
+
## Execution Protocol
|
|
24
|
+
|
|
25
|
+
1. **Dispatch all ready tasks** using `subagent` in parallel mode. Each subagent prompt is provided below.
|
|
26
|
+
2. **Wait for all subagents** to complete.
|
|
27
|
+
3. **Verify each dispatched task's outputs** — check that expected files were created/modified, that verification commands pass where applicable, and that each task wrote its own `T##-SUMMARY.md`.
|
|
28
|
+
4. **Do not rewrite successful task summaries or duplicate checkbox edits.** Treat a subagent-written summary as authoritative for that task.
|
|
29
|
+
5. **If a failed task produced no summary, write a recovery summary for that task** with `blocker_discovered: true`, clear failure details, and leave the task unchecked so replan/retry has an authoritative record.
|
|
30
|
+
6. **Preserve successful sibling tasks exactly as they landed.** Do not roll back good work because another parallel task failed.
|
|
31
|
+
7. **Do NOT create a batch commit.** The surrounding unit lifecycle owns commits; this parent batch agent should not invent a second commit layer.
|
|
32
|
+
8. **Report the batch outcome** — which tasks succeeded, which failed, and any output collisions or dependency surprises.
|
|
33
|
+
|
|
34
|
+
If any subagent fails:
|
|
35
|
+
- Keep successful task summaries and checkbox updates as-is
|
|
36
|
+
- Write a failure summary only when the failed task did not leave one behind
|
|
37
|
+
- Do not silently discard or overwrite another task's outputs
|
|
38
|
+
- The orchestrator will handle re-dispatch or replanning on the next iteration
|
|
39
|
+
|
|
40
|
+
## Subagent Prompts
|
|
41
|
+
|
|
42
|
+
{{subagentPrompts}}
|
|
43
|
+
|
|
44
|
+
{{inlinedTemplates}}
|
|
@@ -18,32 +18,47 @@ If a `GSD Skill Preferences` block is present in system context, use it to decid
|
|
|
18
18
|
|
|
19
19
|
**UAT file:** `{{uatPath}}`
|
|
20
20
|
**Result file to write:** `{{uatResultPath}}`
|
|
21
|
+
**Detected UAT mode:** `{{uatType}}`
|
|
21
22
|
|
|
22
|
-
You are the
|
|
23
|
+
You are the UAT runner. Execute every check defined in `{{uatPath}}` as deeply as this mode truthfully allows. Do not collapse live or subjective checks into cheap artifact checks just to get a PASS.
|
|
24
|
+
|
|
25
|
+
### Automation rules by mode
|
|
26
|
+
|
|
27
|
+
- `artifact-driven` — verify with shell commands, scripts, file reads, and artifact structure checks.
|
|
28
|
+
- `live-runtime` — exercise the real runtime path. Start or connect to the app/service if needed, use browser/runtime/network checks, and verify observable behavior.
|
|
29
|
+
- `mixed` — run all automatable artifact-driven and live-runtime checks. Separate any remaining human-only checks explicitly.
|
|
30
|
+
- `human-experience` — automate setup, preconditions, screenshots, logs, and objective checks, but do **not** invent subjective PASS results. Mark taste-based, experiential, or purely human-judgment checks as `NEEDS-HUMAN` and use an overall verdict of `PARTIAL` unless every required check was objective and passed.
|
|
31
|
+
|
|
32
|
+
### Evidence tools
|
|
33
|
+
|
|
34
|
+
Choose the lightest tool that proves the check honestly:
|
|
23
35
|
|
|
24
36
|
- Run shell commands with `bash`
|
|
25
37
|
- Run `grep` / `rg` checks against files
|
|
26
|
-
- Run `node` / script invocations
|
|
38
|
+
- Run `node` / other script invocations
|
|
27
39
|
- Read files and verify their contents
|
|
28
40
|
- Check that expected artifacts exist and have correct structure
|
|
41
|
+
- For live/runtime/UI checks, exercise the real flow in the browser when applicable and inspect runtime/network/console state
|
|
42
|
+
- When a check cannot be honestly automated, gather the best objective evidence you can and mark it `NEEDS-HUMAN`
|
|
29
43
|
|
|
30
44
|
For each check, record:
|
|
31
45
|
- The check description (from the UAT file)
|
|
46
|
+
- The evidence mode used: `artifact`, `runtime`, or `human-follow-up`
|
|
32
47
|
- The command or action taken
|
|
33
48
|
- The actual result observed
|
|
34
|
-
- PASS or
|
|
49
|
+
- `PASS`, `FAIL`, or `NEEDS-HUMAN`
|
|
35
50
|
|
|
36
51
|
After running all checks, compute the **overall verdict**:
|
|
37
|
-
- `PASS` — all checks passed
|
|
52
|
+
- `PASS` — all required checks passed and no human-only checks remain
|
|
38
53
|
- `FAIL` — one or more checks failed
|
|
39
|
-
- `PARTIAL` — some checks passed,
|
|
54
|
+
- `PARTIAL` — some checks passed, but one or more checks were skipped, inconclusive, or still require human judgment
|
|
40
55
|
|
|
41
56
|
Write `{{uatResultPath}}` with:
|
|
42
57
|
|
|
43
58
|
```markdown
|
|
44
59
|
---
|
|
45
60
|
sliceId: {{sliceId}}
|
|
46
|
-
uatType:
|
|
61
|
+
uatType: {{uatType}}
|
|
47
62
|
verdict: PASS | FAIL | PARTIAL
|
|
48
63
|
date: <ISO 8601 timestamp>
|
|
49
64
|
---
|
|
@@ -52,9 +67,9 @@ date: <ISO 8601 timestamp>
|
|
|
52
67
|
|
|
53
68
|
## Checks
|
|
54
69
|
|
|
55
|
-
| Check | Result | Notes |
|
|
56
|
-
|
|
57
|
-
| <check description> | PASS / FAIL | <observed output or reason> |
|
|
70
|
+
| Check | Mode | Result | Notes |
|
|
71
|
+
|-------|------|--------|-------|
|
|
72
|
+
| <check description> | artifact / runtime / human-follow-up | PASS / FAIL / NEEDS-HUMAN | <observed output, evidence, or reason> |
|
|
58
73
|
|
|
59
74
|
## Overall Verdict
|
|
60
75
|
|
|
@@ -62,7 +77,7 @@ date: <ISO 8601 timestamp>
|
|
|
62
77
|
|
|
63
78
|
## Notes
|
|
64
79
|
|
|
65
|
-
<any additional context, errors encountered, or follow-up
|
|
80
|
+
<any additional context, errors encountered, screenshots/logs gathered, or manual follow-up still required>
|
|
66
81
|
```
|
|
67
82
|
|
|
68
83
|
---
|
|
@@ -14,7 +14,7 @@ You are executing a **{{templateName}}** workflow (template: `{{templateId}}`).
|
|
|
14
14
|
|
|
15
15
|
## Workflow Definition
|
|
16
16
|
|
|
17
|
-
Follow the workflow defined below. Execute each phase in order, completing one before moving to the next.
|
|
17
|
+
Follow the workflow defined below. Execute each phase in order, completing one before moving to the next. For low and medium complexity workflows, keep moving by default — pause only at true decision gates (user must choose between materially different directions, outward-facing actions need approval, or the workflow explicitly requires a human checkpoint). For high complexity workflows, confirm at phase transitions unless the workflow explicitly marks a gate as skip-safe.
|
|
18
18
|
|
|
19
19
|
{{workflowContent}}
|
|
20
20
|
|
|
@@ -24,5 +24,5 @@ Follow the workflow defined below. Execute each phase in order, completing one b
|
|
|
24
24
|
2. **Artifact discipline.** If an artifact directory is specified, write all planning/summary documents there.
|
|
25
25
|
3. **Atomic commits.** Commit working code after each meaningful change. Use conventional commit format: `<type>(<scope>): <description>`.
|
|
26
26
|
4. **Verify before shipping.** Run the project's test suite and build before marking the workflow complete.
|
|
27
|
-
5. **
|
|
27
|
+
5. **Decision gates, not ceremony.** After each phase, summarize what changed. For low/medium complexity, ask for confirmation only when the next phase depends on a real user choice or external approval. For high complexity, confirm before proceeding to each new phase.
|
|
28
28
|
6. **Stay focused.** This is a {{complexity}}-complexity workflow. Match your ceremony level to the task — don't over-engineer or under-deliver.
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive Task Graph — derives dependency edges from task plan IO signatures.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions that build a DAG from task IO intersections and resolve
|
|
5
|
+
* which tasks are currently ready for parallel dispatch. Used by the
|
|
6
|
+
* reactive-execute dispatch path (ADR-004).
|
|
7
|
+
*
|
|
8
|
+
* Graph derivation and resolution functions are pure (no filesystem access).
|
|
9
|
+
* The `loadSliceTaskIO` loader at the bottom is the only async/IO function.
|
|
10
|
+
*/
|
|
11
|
+
import { loadFile, parsePlan, parseTaskPlanIO } from "./files.js";
|
|
12
|
+
import { resolveTasksDir, resolveTaskFiles } from "./paths.js";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
|
|
15
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
16
|
+
// ─── Graph Construction ───────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Build a dependency graph from task IO signatures.
|
|
19
|
+
*
|
|
20
|
+
* A task T_b depends on T_a when any of T_b's inputFiles appear in T_a's
|
|
21
|
+
* outputFiles. Self-references are excluded.
|
|
22
|
+
*
|
|
23
|
+
* Tasks are returned in the same order as the input array.
|
|
24
|
+
*/
|
|
25
|
+
export function deriveTaskGraph(tasks) {
|
|
26
|
+
// Build output → producer lookup
|
|
27
|
+
const outputToProducer = new Map();
|
|
28
|
+
for (const task of tasks) {
|
|
29
|
+
for (const outFile of task.outputFiles) {
|
|
30
|
+
const existing = outputToProducer.get(outFile);
|
|
31
|
+
if (existing) {
|
|
32
|
+
existing.push(task.id);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
outputToProducer.set(outFile, [task.id]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return tasks.map((task) => {
|
|
40
|
+
const deps = new Set();
|
|
41
|
+
for (const inFile of task.inputFiles) {
|
|
42
|
+
const producers = outputToProducer.get(inFile);
|
|
43
|
+
if (producers) {
|
|
44
|
+
for (const pid of producers) {
|
|
45
|
+
if (pid !== task.id)
|
|
46
|
+
deps.add(pid);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
...task,
|
|
52
|
+
dependsOn: [...deps].sort(),
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// ─── Ready Set Resolution ─────────────────────────────────────────────────
|
|
57
|
+
/**
|
|
58
|
+
* Return task IDs whose dependencies are all in `completed`.
|
|
59
|
+
* Excludes tasks that are already done or in-flight.
|
|
60
|
+
*/
|
|
61
|
+
export function getReadyTasks(graph, completed, inFlight) {
|
|
62
|
+
return graph
|
|
63
|
+
.filter((node) => {
|
|
64
|
+
if (node.done || completed.has(node.id) || inFlight.has(node.id))
|
|
65
|
+
return false;
|
|
66
|
+
return node.dependsOn.every((dep) => completed.has(dep));
|
|
67
|
+
})
|
|
68
|
+
.map((node) => node.id);
|
|
69
|
+
}
|
|
70
|
+
// ─── Conflict-Free Subset Selection ──────────────────────────────────────
|
|
71
|
+
/**
|
|
72
|
+
* Greedy selection of non-conflicting tasks up to `maxParallel`.
|
|
73
|
+
*
|
|
74
|
+
* Two tasks conflict if they share any outputFile. We also exclude tasks
|
|
75
|
+
* whose outputs overlap with `inFlightOutputs` (files being written by
|
|
76
|
+
* tasks currently in progress).
|
|
77
|
+
*/
|
|
78
|
+
export function chooseNonConflictingSubset(readyIds, graph, maxParallel, inFlightOutputs) {
|
|
79
|
+
const nodeMap = new Map(graph.map((n) => [n.id, n]));
|
|
80
|
+
const claimed = new Set(inFlightOutputs);
|
|
81
|
+
const selected = [];
|
|
82
|
+
for (const id of readyIds) {
|
|
83
|
+
if (selected.length >= maxParallel)
|
|
84
|
+
break;
|
|
85
|
+
const node = nodeMap.get(id);
|
|
86
|
+
if (!node)
|
|
87
|
+
continue;
|
|
88
|
+
// Check for output overlap with already-selected or in-flight
|
|
89
|
+
const conflicts = node.outputFiles.some((f) => claimed.has(f));
|
|
90
|
+
if (conflicts)
|
|
91
|
+
continue;
|
|
92
|
+
// Claim this task's outputs
|
|
93
|
+
for (const f of node.outputFiles)
|
|
94
|
+
claimed.add(f);
|
|
95
|
+
selected.push(id);
|
|
96
|
+
}
|
|
97
|
+
return selected;
|
|
98
|
+
}
|
|
99
|
+
// ─── Graph Quality Checks ─────────────────────────────────────────────────
|
|
100
|
+
/**
|
|
101
|
+
* Returns true if any incomplete task has 0 inputFiles AND 0 outputFiles.
|
|
102
|
+
*
|
|
103
|
+
* An ambiguous graph means IO annotations are too sparse to derive reliable
|
|
104
|
+
* edges — the dispatcher should fall back to sequential execution.
|
|
105
|
+
*/
|
|
106
|
+
export function isGraphAmbiguous(graph) {
|
|
107
|
+
return graph.some((node) => !node.done &&
|
|
108
|
+
node.inputFiles.length === 0 &&
|
|
109
|
+
node.outputFiles.length === 0);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Detect deadlock: no tasks are ready and none are in-flight, yet incomplete
|
|
113
|
+
* tasks remain. This indicates a circular dependency or impossible state.
|
|
114
|
+
*/
|
|
115
|
+
export function detectDeadlock(graph, completed, inFlight) {
|
|
116
|
+
const incomplete = graph.filter((n) => !n.done && !completed.has(n.id) && !inFlight.has(n.id));
|
|
117
|
+
if (incomplete.length === 0)
|
|
118
|
+
return false; // all done
|
|
119
|
+
if (inFlight.size > 0)
|
|
120
|
+
return false; // something is running, wait for it
|
|
121
|
+
// Nothing in flight, but incomplete tasks remain — check if any are ready
|
|
122
|
+
const ready = getReadyTasks(graph, completed, inFlight);
|
|
123
|
+
return ready.length === 0;
|
|
124
|
+
}
|
|
125
|
+
// ─── Graph Metrics ────────────────────────────────────────────────────────
|
|
126
|
+
/** Compute summary metrics for logging. */
|
|
127
|
+
export function graphMetrics(graph) {
|
|
128
|
+
const completed = new Set(graph.filter((n) => n.done).map((n) => n.id));
|
|
129
|
+
const ready = getReadyTasks(graph, completed, new Set());
|
|
130
|
+
const edgeCount = graph.reduce((sum, n) => sum + n.dependsOn.length, 0);
|
|
131
|
+
return {
|
|
132
|
+
taskCount: graph.length,
|
|
133
|
+
edgeCount,
|
|
134
|
+
readySetSize: ready.length,
|
|
135
|
+
ambiguous: isGraphAmbiguous(graph),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// ─── IO Loader (async, filesystem) ────────────────────────────────────────
|
|
139
|
+
/**
|
|
140
|
+
* Load TaskIO for all tasks in a slice by reading the slice plan (for done
|
|
141
|
+
* status and task IDs) and individual task plan files (for IO sections).
|
|
142
|
+
*
|
|
143
|
+
* Returns [] when the slice plan or tasks directory doesn't exist.
|
|
144
|
+
*/
|
|
145
|
+
export async function loadSliceTaskIO(basePath, mid, sid) {
|
|
146
|
+
const { resolveSliceFile } = await import("./paths.js");
|
|
147
|
+
const slicePlanPath = resolveSliceFile(basePath, mid, sid, "PLAN");
|
|
148
|
+
const planContent = slicePlanPath ? await loadFile(slicePlanPath) : null;
|
|
149
|
+
if (!planContent)
|
|
150
|
+
return [];
|
|
151
|
+
const plan = parsePlan(planContent);
|
|
152
|
+
const tDir = resolveTasksDir(basePath, mid, sid);
|
|
153
|
+
if (!tDir)
|
|
154
|
+
return [];
|
|
155
|
+
const results = [];
|
|
156
|
+
for (const taskEntry of plan.tasks) {
|
|
157
|
+
const planFiles = resolveTaskFiles(tDir, "PLAN");
|
|
158
|
+
const taskFileName = planFiles.find((f) => f.toUpperCase().startsWith(taskEntry.id.toUpperCase() + "-"));
|
|
159
|
+
if (!taskFileName) {
|
|
160
|
+
// Task plan file missing — include with empty IO (will trigger ambiguous)
|
|
161
|
+
results.push({
|
|
162
|
+
id: taskEntry.id,
|
|
163
|
+
title: taskEntry.title,
|
|
164
|
+
inputFiles: [],
|
|
165
|
+
outputFiles: [],
|
|
166
|
+
done: taskEntry.done,
|
|
167
|
+
});
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const taskContent = await loadFile(join(tDir, taskFileName));
|
|
171
|
+
if (!taskContent) {
|
|
172
|
+
results.push({
|
|
173
|
+
id: taskEntry.id,
|
|
174
|
+
title: taskEntry.title,
|
|
175
|
+
inputFiles: [],
|
|
176
|
+
outputFiles: [],
|
|
177
|
+
done: taskEntry.done,
|
|
178
|
+
});
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const io = parseTaskPlanIO(taskContent);
|
|
182
|
+
results.push({
|
|
183
|
+
id: taskEntry.id,
|
|
184
|
+
title: taskEntry.title,
|
|
185
|
+
inputFiles: io.inputFiles,
|
|
186
|
+
outputFiles: io.outputFiles,
|
|
187
|
+
done: taskEntry.done,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return results;
|
|
191
|
+
}
|
|
192
|
+
// ─── State Persistence ────────────────────────────────────────────────────
|
|
193
|
+
function reactiveStatePath(basePath, mid, sid) {
|
|
194
|
+
return join(basePath, ".gsd", "runtime", `${mid}-${sid}-reactive.json`);
|
|
195
|
+
}
|
|
196
|
+
function isReactiveState(data) {
|
|
197
|
+
if (!data || typeof data !== "object")
|
|
198
|
+
return false;
|
|
199
|
+
const d = data;
|
|
200
|
+
return typeof d.sliceId === "string" && Array.isArray(d.completed) && Array.isArray(d.dispatched);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Load persisted reactive execution state for a slice.
|
|
204
|
+
* Returns null when no state file exists or the file is invalid.
|
|
205
|
+
*/
|
|
206
|
+
export function loadReactiveState(basePath, mid, sid) {
|
|
207
|
+
return loadJsonFileOrNull(reactiveStatePath(basePath, mid, sid), isReactiveState);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Save reactive execution state to disk.
|
|
211
|
+
*/
|
|
212
|
+
export function saveReactiveState(basePath, mid, sid, state) {
|
|
213
|
+
saveJsonFile(reactiveStatePath(basePath, mid, sid), state);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Remove the reactive state file when a slice completes.
|
|
217
|
+
*/
|
|
218
|
+
export function clearReactiveState(basePath, mid, sid) {
|
|
219
|
+
const path = reactiveStatePath(basePath, mid, sid);
|
|
220
|
+
try {
|
|
221
|
+
if (existsSync(path))
|
|
222
|
+
unlinkSync(path);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// Non-fatal
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -10,6 +10,7 @@ import { execFileSync } from "node:child_process";
|
|
|
10
10
|
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, rmSync, symlinkSync } from "node:fs";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
12
|
import { join, resolve } from "node:path";
|
|
13
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
13
14
|
// ─── Repo Identity ──────────────────────────────────────────────────────────
|
|
14
15
|
/**
|
|
15
16
|
* Get the git remote URL for "origin", or "" if no remote is configured.
|
|
@@ -84,14 +85,30 @@ function resolveGitRoot(basePath) {
|
|
|
84
85
|
return resolve(basePath);
|
|
85
86
|
}
|
|
86
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Validate a GSD_PROJECT_ID value.
|
|
90
|
+
*
|
|
91
|
+
* Must contain only alphanumeric characters, hyphens, and underscores.
|
|
92
|
+
* Call this once at startup so the user gets immediate feedback on bad values.
|
|
93
|
+
*/
|
|
94
|
+
export function validateProjectId(id) {
|
|
95
|
+
return /^[a-zA-Z0-9_-]+$/.test(id);
|
|
96
|
+
}
|
|
87
97
|
/**
|
|
88
98
|
* Compute a stable identity for a repository.
|
|
89
99
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
100
|
+
* If `GSD_PROJECT_ID` is set, returns it directly (validation is expected
|
|
101
|
+
* to have already happened at startup via `validateProjectId`).
|
|
102
|
+
*
|
|
103
|
+
* Otherwise returns SHA-256 of `${remoteUrl}\n${resolvedRoot}`, truncated
|
|
104
|
+
* to 12 hex chars. Deterministic: same repo always produces the same hash
|
|
105
|
+
* regardless of which worktree the caller is inside.
|
|
93
106
|
*/
|
|
94
107
|
export function repoIdentity(basePath) {
|
|
108
|
+
const projectId = process.env.GSD_PROJECT_ID;
|
|
109
|
+
if (projectId) {
|
|
110
|
+
return projectId;
|
|
111
|
+
}
|
|
95
112
|
const remoteUrl = getRemoteUrl(basePath);
|
|
96
113
|
const root = resolveGitRoot(basePath);
|
|
97
114
|
const input = `${remoteUrl}\n${root}`;
|
|
@@ -105,7 +122,7 @@ export function repoIdentity(basePath) {
|
|
|
105
122
|
* otherwise `~/.gsd/projects/<hash>`.
|
|
106
123
|
*/
|
|
107
124
|
export function externalGsdRoot(basePath) {
|
|
108
|
-
const base = process.env.GSD_STATE_DIR ||
|
|
125
|
+
const base = process.env.GSD_STATE_DIR || gsdHome;
|
|
109
126
|
return join(base, "projects", repoIdentity(basePath));
|
|
110
127
|
}
|
|
111
128
|
// ─── Symlink Management ─────────────────────────────────────────────────────
|
|
@@ -9,6 +9,7 @@ import { loadJsonFileOrNull } from "./json-persistence.js";
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { resolveProjectRoot } from "./worktree.js";
|
|
12
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
12
13
|
// ─── Resource Staleness ───────────────────────────────────────────────────
|
|
13
14
|
/**
|
|
14
15
|
* Read the resource version (semver) from the managed-resources manifest.
|
|
@@ -19,7 +20,7 @@ function isManifestWithVersion(data) {
|
|
|
19
20
|
return data !== null && typeof data === "object" && "gsdVersion" in data && typeof data.gsdVersion === "string";
|
|
20
21
|
}
|
|
21
22
|
export function readResourceVersion() {
|
|
22
|
-
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(
|
|
23
|
+
const agentDir = process.env.GSD_CODING_AGENT_DIR || join(gsdHome, "agent");
|
|
23
24
|
const manifestPath = join(agentDir, "managed-resources.json");
|
|
24
25
|
const manifest = loadJsonFileOrNull(manifestPath, isManifestWithVersion);
|
|
25
26
|
return manifest?.gsdVersion ?? null;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Pure TypeScript, zero Pi dependencies.
|
|
4
4
|
import { parseRoadmap, parsePlan, parseSummary, loadFile, parseRequirementCounts, parseContextDependsOn, } from './files.js';
|
|
5
5
|
import { resolveMilestoneFile, resolveSlicePath, resolveSliceFile, resolveTaskFile, resolveTasksDir, resolveGsdRootFile, gsdRoot, } from './paths.js';
|
|
6
|
-
import { findMilestoneIds } from './
|
|
6
|
+
import { findMilestoneIds } from './milestone-ids.js';
|
|
7
7
|
import { nativeBatchParseGsdFiles } from './native-parser-bridge.js';
|
|
8
8
|
import { join, resolve } from 'path';
|
|
9
9
|
import { existsSync, readdirSync } from 'node:fs';
|
|
@@ -42,11 +42,19 @@ estimated_files: {{estimatedFiles}}
|
|
|
42
42
|
|
|
43
43
|
## Inputs
|
|
44
44
|
|
|
45
|
+
<!-- Every input MUST be a backtick-wrapped file path. These paths are machine-parsed to
|
|
46
|
+
derive task dependencies — vague descriptions without paths break dependency detection.
|
|
47
|
+
For the first task in a slice with no prior task outputs, list the existing source files
|
|
48
|
+
this task reads or modifies. -->
|
|
49
|
+
|
|
45
50
|
- `{{filePath}}` — {{whatThisTaskNeedsFromPriorWork}}
|
|
46
|
-
- {{priorTaskSummaryInsight}}
|
|
47
51
|
|
|
48
52
|
## Expected Output
|
|
49
53
|
|
|
50
|
-
<!--
|
|
54
|
+
<!-- Every output MUST be a backtick-wrapped file path — the specific files this task creates
|
|
55
|
+
or modifies. These paths are machine-parsed to derive task dependencies.
|
|
56
|
+
This task should produce a real increment toward making the slice goal/demo true. A full
|
|
57
|
+
slice plan should not be able to mark every task complete while the claimed slice behavior
|
|
58
|
+
still does not work at the stated proof level. -->
|
|
51
59
|
|
|
52
|
-
- `{{filePath}}` — {{
|
|
60
|
+
- `{{filePath}}` — {{whatThisTaskCreatesOrModifies}}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
3
3
|
import { deriveState } from './state.js';
|
|
4
4
|
import { parseRoadmap, parsePlan, parseSummary, loadFile } from './files.js';
|
|
5
|
-
import { findMilestoneIds } from './
|
|
5
|
+
import { findMilestoneIds } from './milestone-ids.js';
|
|
6
6
|
import { resolveMilestoneFile, resolveSliceFile, resolveGsdRootFile } from './paths.js';
|
|
7
7
|
import { getLedger, getProjectTotals, aggregateByPhase, aggregateBySlice, aggregateByModel, aggregateByTier, formatTierSavings, loadLedgerFromDisk, } from './metrics.js';
|
|
8
8
|
import { loadAllCaptures, countPendingCaptures } from './captures.js';
|
|
@@ -57,42 +57,61 @@ export function captureIntegrationBranch(basePath, milestoneId, options) {
|
|
|
57
57
|
writeIntegrationBranch(basePath, milestoneId, current, options);
|
|
58
58
|
}
|
|
59
59
|
// ─── Pure Utility Functions (unchanged) ────────────────────────────────────
|
|
60
|
+
/**
|
|
61
|
+
* Find the worktrees segment in a path, supporting both direct
|
|
62
|
+
* (`/.gsd/worktrees/`) and symlink-resolved (`/.gsd/projects/<hash>/worktrees/`)
|
|
63
|
+
* layouts. When `.gsd` is a symlink to `~/.gsd/projects/<hash>`, resolved
|
|
64
|
+
* paths contain the intermediate `projects/<hash>/` segment that the old
|
|
65
|
+
* single-marker check missed.
|
|
66
|
+
*/
|
|
67
|
+
function findWorktreeSegment(normalizedPath) {
|
|
68
|
+
// Direct layout: /.gsd/worktrees/<name>
|
|
69
|
+
const directMarker = "/.gsd/worktrees/";
|
|
70
|
+
const idx = normalizedPath.indexOf(directMarker);
|
|
71
|
+
if (idx !== -1) {
|
|
72
|
+
return { gsdIdx: idx, afterWorktrees: idx + directMarker.length };
|
|
73
|
+
}
|
|
74
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/<name>
|
|
75
|
+
const symlinkRe = /\/\.gsd\/projects\/[a-f0-9]+\/worktrees\//;
|
|
76
|
+
const match = normalizedPath.match(symlinkRe);
|
|
77
|
+
if (match && match.index !== undefined) {
|
|
78
|
+
return { gsdIdx: match.index, afterWorktrees: match.index + match[0].length };
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
60
82
|
/**
|
|
61
83
|
* Detect the active worktree name from the current working directory.
|
|
62
84
|
* Returns null if not inside a GSD worktree (.gsd/worktrees/<name>/).
|
|
63
85
|
*/
|
|
64
86
|
export function detectWorktreeName(basePath) {
|
|
65
87
|
const normalizedPath = basePath.replaceAll("\\", "/");
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
if (idx === -1)
|
|
88
|
+
const seg = findWorktreeSegment(normalizedPath);
|
|
89
|
+
if (!seg)
|
|
69
90
|
return null;
|
|
70
|
-
const afterMarker = normalizedPath.slice(
|
|
91
|
+
const afterMarker = normalizedPath.slice(seg.afterWorktrees);
|
|
71
92
|
const name = afterMarker.split("/")[0];
|
|
72
93
|
return name || null;
|
|
73
94
|
}
|
|
74
95
|
/**
|
|
75
96
|
* Resolve the project root from a path that may be inside a worktree.
|
|
76
|
-
* If the path contains
|
|
77
|
-
*
|
|
97
|
+
* If the path contains a worktrees segment, returns the portion before
|
|
98
|
+
* `/.gsd/`. Otherwise returns the input unchanged.
|
|
78
99
|
*
|
|
79
100
|
* Use this in commands that call `process.cwd()` to ensure they always
|
|
80
101
|
* operate against the real project root, not a worktree subdirectory.
|
|
81
102
|
*/
|
|
82
103
|
export function resolveProjectRoot(basePath) {
|
|
83
104
|
const normalizedPath = basePath.replaceAll("\\", "/");
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
if (idx === -1)
|
|
105
|
+
const seg = findWorktreeSegment(normalizedPath);
|
|
106
|
+
if (!seg)
|
|
87
107
|
return basePath;
|
|
88
|
-
// Return the original path up to the
|
|
89
|
-
// Account for potential OS-specific separators
|
|
108
|
+
// Return the original path up to the /.gsd/ boundary
|
|
90
109
|
const sep = basePath.includes("\\") ? "\\" : "/";
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
return basePath.slice(0,
|
|
95
|
-
return basePath.slice(0,
|
|
110
|
+
const gsdMarker = `${sep}.gsd${sep}`;
|
|
111
|
+
const gsdIdx = basePath.indexOf(gsdMarker);
|
|
112
|
+
if (gsdIdx !== -1)
|
|
113
|
+
return basePath.slice(0, gsdIdx);
|
|
114
|
+
return basePath.slice(0, seg.gsdIdx);
|
|
96
115
|
}
|
|
97
116
|
/**
|
|
98
117
|
* Get the slice branch name, namespaced by worktree when inside one.
|
|
@@ -5,8 +5,9 @@ import { existsSync, readdirSync } from "node:fs";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { readPromptRecord } from "./store.js";
|
|
8
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
8
9
|
export function getLatestPromptSummary() {
|
|
9
|
-
const runtimeDir = join(
|
|
10
|
+
const runtimeDir = join(gsdHome, "runtime", "remote-questions");
|
|
10
11
|
if (!existsSync(runtimeDir))
|
|
11
12
|
return null;
|
|
12
13
|
const files = readdirSync(runtimeDir).filter((f) => f.endsWith(".json"));
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
7
8
|
function runtimeDir() {
|
|
8
|
-
return join(
|
|
9
|
+
return join(gsdHome, "runtime", "remote-questions");
|
|
9
10
|
}
|
|
10
11
|
function recordPath(id) {
|
|
11
12
|
return join(runtimeDir(), `${id}.json`);
|
|
@@ -15,7 +15,8 @@ import { resolveSearchProviderFromPreferences } from '../gsd/preferences.js';
|
|
|
15
15
|
// Compute authFilePath locally instead of importing from app-paths.ts,
|
|
16
16
|
// because extensions are copied to ~/.gsd/agent/extensions/ at runtime
|
|
17
17
|
// where the relative import '../../../app-paths.ts' doesn't resolve.
|
|
18
|
-
const
|
|
18
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), '.gsd');
|
|
19
|
+
const authFilePath = join(gsdHome, 'agent', 'auth.json');
|
|
19
20
|
const VALID_PREFERENCES = new Set(['tavily', 'brave', 'ollama', 'auto']);
|
|
20
21
|
const PREFERENCE_KEY = 'search_provider';
|
|
21
22
|
/** Returns the Tavily API key from the environment, or empty string if not set. */
|
|
@@ -360,7 +360,7 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
362
|
}
|
|
363
|
-
async function runSingleAgentInCmuxSplit(cmuxClient,
|
|
363
|
+
async function runSingleAgentInCmuxSplit(cmuxClient, directionOrSurfaceId, defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails) {
|
|
364
364
|
const agent = agents.find((a) => a.name === agentName);
|
|
365
365
|
if (!agent) {
|
|
366
366
|
return runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails);
|
|
@@ -397,7 +397,12 @@ async function runSingleAgentInCmuxSplit(cmuxClient, direction, defaultCwd, agen
|
|
|
397
397
|
const stdoutPath = path.join(tmpOutputDir, "stdout.jsonl");
|
|
398
398
|
const stderrPath = path.join(tmpOutputDir, "stderr.log");
|
|
399
399
|
const exitPath = path.join(tmpOutputDir, "exit.code");
|
|
400
|
-
|
|
400
|
+
// Accept either a pre-created surface ID or a direction to create a new split
|
|
401
|
+
const isDirection = directionOrSurfaceId === "right" || directionOrSurfaceId === "down"
|
|
402
|
+
|| directionOrSurfaceId === "left" || directionOrSurfaceId === "up";
|
|
403
|
+
const cmuxSurfaceId = isDirection
|
|
404
|
+
? await cmuxClient.createSplit(directionOrSurfaceId)
|
|
405
|
+
: directionOrSurfaceId;
|
|
401
406
|
if (!cmuxSurfaceId) {
|
|
402
407
|
return runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails);
|
|
403
408
|
}
|
|
@@ -656,10 +661,14 @@ export default function (pi) {
|
|
|
656
661
|
const MAX_RETRIES = 1; // Retry failed tasks once
|
|
657
662
|
const batchId = crypto.randomUUID();
|
|
658
663
|
const batchSize = params.tasks.length;
|
|
664
|
+
// Pre-create a grid layout for cmux splits so agents get a clean tiled arrangement
|
|
665
|
+
const gridSurfaces = cmuxSplitsEnabled
|
|
666
|
+
? await cmuxClient.createGridLayout(Math.min(batchSize, MAX_CONCURRENCY))
|
|
667
|
+
: [];
|
|
659
668
|
const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
|
|
660
669
|
const workerId = registerWorker(t.agent, t.task, index, batchSize, batchId);
|
|
661
670
|
const runTask = () => cmuxSplitsEnabled
|
|
662
|
-
? runSingleAgentInCmuxSplit(cmuxClient, index % 2 === 0 ? "right" : "down", ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, signal, (partial) => {
|
|
671
|
+
? runSingleAgentInCmuxSplit(cmuxClient, gridSurfaces[index] ?? (index % 2 === 0 ? "right" : "down"), ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, signal, (partial) => {
|
|
663
672
|
if (partial.details?.results[0]) {
|
|
664
673
|
allResults[index] = partial.details.results[0];
|
|
665
674
|
emitParallelUpdate();
|
|
@@ -17,8 +17,9 @@ const execFile = promisify(execFileCb);
|
|
|
17
17
|
function encodeCwd(cwd) {
|
|
18
18
|
return cwd.replace(/\//g, "--");
|
|
19
19
|
}
|
|
20
|
+
const gsdHome = process.env.GSD_HOME || path.join(os.homedir(), ".gsd");
|
|
20
21
|
function getIsolationBaseDir(cwd, taskId) {
|
|
21
|
-
return path.join(
|
|
22
|
+
return path.join(gsdHome, "wt", encodeCwd(cwd), taskId);
|
|
22
23
|
}
|
|
23
24
|
// Track active isolation dirs for cleanup on exit
|
|
24
25
|
const activeIsolations = new Set();
|