gsd-pi 2.37.1-dev.d3ace49 β 2.38.0-dev.29edcdc
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/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/remote-questions-config.js +2 -2
- package/dist/resource-loader.js +34 -1
- 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 +8 -9
- package/dist/resources/extensions/gsd/auto-loop.js +597 -588
- package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
- package/dist/resources/extensions/gsd/auto-prompts.js +23 -43
- 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-worktree.js +3 -3
- 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 +24 -3
- 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 +27 -11
- package/dist/resources/extensions/gsd/doctor.js +204 -12
- package/dist/resources/extensions/gsd/exit-command.js +2 -1
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +6 -2
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-service.js +15 -12
- package/dist/resources/extensions/gsd/guided-flow.js +82 -32
- package/dist/resources/extensions/gsd/index.js +24 -20
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +37 -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 +1 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +59 -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/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/dist/resources/extensions/gsd/prompts/run-uat.md +27 -10
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- 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/roadmap-mutations.js +24 -0
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/mcp-client/index.js +14 -1
- 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 +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/src/utils/oauth/anthropic.ts +2 -2
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
- package/packages/pi-coding-agent/dist/core/extensions/loader.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/extensions/loader.ts +223 -7
- 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 +7 -9
- package/src/resources/extensions/gsd/auto-loop.ts +484 -546
- package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
- package/src/resources/extensions/gsd/auto-prompts.ts +25 -45
- 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-worktree.ts +3 -3
- 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 +26 -4
- 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 +26 -9
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +199 -14
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +5 -3
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +20 -10
- package/src/resources/extensions/gsd/guided-flow.ts +110 -38
- package/src/resources/extensions/gsd/index.ts +24 -17
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +4 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +51 -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/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/src/resources/extensions/gsd/prompts/run-uat.md +27 -10
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- 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/roadmap-mutations.ts +29 -0
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
- 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 +86 -3
- 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/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 +0 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/mcp-client/index.ts +17 -1
- 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,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown formatters for GitHub issue bodies, PR descriptions,
|
|
3
|
+
* and summary comments.
|
|
4
|
+
*
|
|
5
|
+
* All functions produce GitHub-flavored markdown strings ready
|
|
6
|
+
* for the `gh` CLI body parameters.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// βββ Milestone Issue Body βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
10
|
+
|
|
11
|
+
export interface MilestoneData {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
vision?: string;
|
|
15
|
+
successCriteria?: string[];
|
|
16
|
+
slices?: Array<{ id: string; title: string; taskCount?: number }>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function formatMilestoneIssueBody(data: MilestoneData): string {
|
|
20
|
+
const lines: string[] = [];
|
|
21
|
+
|
|
22
|
+
lines.push(`# ${data.id}: ${data.title}`);
|
|
23
|
+
lines.push("");
|
|
24
|
+
|
|
25
|
+
if (data.vision) {
|
|
26
|
+
lines.push("## Vision");
|
|
27
|
+
lines.push(data.vision);
|
|
28
|
+
lines.push("");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (data.successCriteria?.length) {
|
|
32
|
+
lines.push("## Success Criteria");
|
|
33
|
+
for (const criterion of data.successCriteria) {
|
|
34
|
+
lines.push(`- [ ] ${criterion}`);
|
|
35
|
+
}
|
|
36
|
+
lines.push("");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (data.slices?.length) {
|
|
40
|
+
lines.push("## Slices");
|
|
41
|
+
lines.push("");
|
|
42
|
+
lines.push("| Slice | Title | Tasks |");
|
|
43
|
+
lines.push("|-------|-------|-------|");
|
|
44
|
+
for (const slice of data.slices) {
|
|
45
|
+
lines.push(`| ${slice.id} | ${slice.title} | ${slice.taskCount ?? "β"} |`);
|
|
46
|
+
}
|
|
47
|
+
lines.push("");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
lines.push("---");
|
|
51
|
+
lines.push("*Auto-generated by GSD GitHub Sync*");
|
|
52
|
+
|
|
53
|
+
return lines.join("\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// βββ Slice PR Body ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
57
|
+
|
|
58
|
+
export interface SliceData {
|
|
59
|
+
id: string;
|
|
60
|
+
title: string;
|
|
61
|
+
goal?: string;
|
|
62
|
+
mustHaves?: string[];
|
|
63
|
+
demoCriterion?: string;
|
|
64
|
+
tasks?: Array<{ id: string; title: string; issueNumber?: number }>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function formatSlicePRBody(data: SliceData): string {
|
|
68
|
+
const lines: string[] = [];
|
|
69
|
+
|
|
70
|
+
lines.push(`## ${data.id}: ${data.title}`);
|
|
71
|
+
lines.push("");
|
|
72
|
+
|
|
73
|
+
if (data.goal) {
|
|
74
|
+
lines.push("### Goal");
|
|
75
|
+
lines.push(data.goal);
|
|
76
|
+
lines.push("");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (data.mustHaves?.length) {
|
|
80
|
+
lines.push("### Must-Haves");
|
|
81
|
+
for (const item of data.mustHaves) {
|
|
82
|
+
lines.push(`- ${item}`);
|
|
83
|
+
}
|
|
84
|
+
lines.push("");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (data.demoCriterion) {
|
|
88
|
+
lines.push("### Demo Criterion");
|
|
89
|
+
lines.push(data.demoCriterion);
|
|
90
|
+
lines.push("");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (data.tasks?.length) {
|
|
94
|
+
lines.push("### Tasks");
|
|
95
|
+
for (const task of data.tasks) {
|
|
96
|
+
const ref = task.issueNumber ? ` (#${task.issueNumber})` : "";
|
|
97
|
+
lines.push(`- [ ] ${task.id}: ${task.title}${ref}`);
|
|
98
|
+
}
|
|
99
|
+
lines.push("");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
lines.push("---");
|
|
103
|
+
lines.push("*Auto-generated by GSD GitHub Sync*");
|
|
104
|
+
|
|
105
|
+
return lines.join("\n");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// βββ Task Issue Body ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
109
|
+
|
|
110
|
+
export interface TaskData {
|
|
111
|
+
id: string;
|
|
112
|
+
title: string;
|
|
113
|
+
description?: string;
|
|
114
|
+
files?: string[];
|
|
115
|
+
verifyCriteria?: string[];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function formatTaskIssueBody(data: TaskData): string {
|
|
119
|
+
const lines: string[] = [];
|
|
120
|
+
|
|
121
|
+
lines.push(`## ${data.id}: ${data.title}`);
|
|
122
|
+
lines.push("");
|
|
123
|
+
|
|
124
|
+
if (data.description) {
|
|
125
|
+
lines.push(data.description);
|
|
126
|
+
lines.push("");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (data.files?.length) {
|
|
130
|
+
lines.push("### Files");
|
|
131
|
+
for (const file of data.files) {
|
|
132
|
+
lines.push(`- \`${file}\``);
|
|
133
|
+
}
|
|
134
|
+
lines.push("");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (data.verifyCriteria?.length) {
|
|
138
|
+
lines.push("### Verification");
|
|
139
|
+
for (const criterion of data.verifyCriteria) {
|
|
140
|
+
lines.push(`- [ ] ${criterion}`);
|
|
141
|
+
}
|
|
142
|
+
lines.push("");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return lines.join("\n");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// βββ Summary Comment ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
149
|
+
|
|
150
|
+
export interface SummaryData {
|
|
151
|
+
oneLiner?: string;
|
|
152
|
+
body?: string;
|
|
153
|
+
frontmatter?: Record<string, unknown>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function formatSummaryComment(data: SummaryData): string {
|
|
157
|
+
const lines: string[] = [];
|
|
158
|
+
|
|
159
|
+
if (data.oneLiner) {
|
|
160
|
+
lines.push(`**Summary:** ${data.oneLiner}`);
|
|
161
|
+
lines.push("");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (data.body) {
|
|
165
|
+
lines.push(data.body);
|
|
166
|
+
lines.push("");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (data.frontmatter && Object.keys(data.frontmatter).length > 0) {
|
|
170
|
+
lines.push("<details>");
|
|
171
|
+
lines.push("<summary>Metadata</summary>");
|
|
172
|
+
lines.push("");
|
|
173
|
+
lines.push("```yaml");
|
|
174
|
+
for (const [key, value] of Object.entries(data.frontmatter)) {
|
|
175
|
+
lines.push(`${key}: ${JSON.stringify(value)}`);
|
|
176
|
+
}
|
|
177
|
+
lines.push("```");
|
|
178
|
+
lines.push("");
|
|
179
|
+
lines.push("</details>");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return lines.join("\n");
|
|
183
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import test, { describe, it, beforeEach } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { ghIsAvailable, _resetGhCache } from "../cli.ts";
|
|
4
|
+
|
|
5
|
+
describe("cli", () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
_resetGhCache();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("ghIsAvailable returns boolean", () => {
|
|
11
|
+
const result = ghIsAvailable();
|
|
12
|
+
assert.equal(typeof result, "boolean");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("ghIsAvailable caches result", () => {
|
|
16
|
+
const first = ghIsAvailable();
|
|
17
|
+
const second = ghIsAvailable();
|
|
18
|
+
assert.equal(first, second);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { buildTaskCommitMessage } from "../../gsd/git-service.ts";
|
|
4
|
+
|
|
5
|
+
describe("commit linking", () => {
|
|
6
|
+
it("appends Resolves #N when issueNumber is set", () => {
|
|
7
|
+
const msg = buildTaskCommitMessage({
|
|
8
|
+
taskId: "S01/T02",
|
|
9
|
+
taskTitle: "implement auth",
|
|
10
|
+
issueNumber: 43,
|
|
11
|
+
});
|
|
12
|
+
assert.ok(msg.includes("Resolves #43"), "should include Resolves trailer");
|
|
13
|
+
assert.ok(msg.startsWith("feat(S01/T02):"), "subject line unchanged");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("includes both key files and Resolves #N", () => {
|
|
17
|
+
const msg = buildTaskCommitMessage({
|
|
18
|
+
taskId: "S01/T02",
|
|
19
|
+
taskTitle: "implement auth",
|
|
20
|
+
keyFiles: ["src/auth.ts"],
|
|
21
|
+
issueNumber: 43,
|
|
22
|
+
});
|
|
23
|
+
assert.ok(msg.includes("- src/auth.ts"), "key files present");
|
|
24
|
+
assert.ok(msg.includes("Resolves #43"), "Resolves trailer present");
|
|
25
|
+
// Resolves should come after key files
|
|
26
|
+
const keyFilesIdx = msg.indexOf("- src/auth.ts");
|
|
27
|
+
const resolvesIdx = msg.indexOf("Resolves #43");
|
|
28
|
+
assert.ok(resolvesIdx > keyFilesIdx, "Resolves after key files");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("no Resolves trailer when issueNumber is not set", () => {
|
|
32
|
+
const msg = buildTaskCommitMessage({
|
|
33
|
+
taskId: "S01/T02",
|
|
34
|
+
taskTitle: "implement auth",
|
|
35
|
+
});
|
|
36
|
+
assert.ok(!msg.includes("Resolves"), "no Resolves when no issueNumber");
|
|
37
|
+
assert.ok(!msg.includes("\n"), "no body when no issueNumber or keyFiles");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import {
|
|
7
|
+
loadSyncMapping,
|
|
8
|
+
saveSyncMapping,
|
|
9
|
+
createEmptyMapping,
|
|
10
|
+
getMilestoneRecord,
|
|
11
|
+
getSliceRecord,
|
|
12
|
+
getTaskRecord,
|
|
13
|
+
getTaskIssueNumber,
|
|
14
|
+
setMilestoneRecord,
|
|
15
|
+
setSliceRecord,
|
|
16
|
+
setTaskRecord,
|
|
17
|
+
} from "../mapping.ts";
|
|
18
|
+
import type { SyncMapping, MilestoneSyncRecord, SliceSyncRecord, SyncEntityRecord } from "../types.ts";
|
|
19
|
+
|
|
20
|
+
describe("mapping", () => {
|
|
21
|
+
let tmpDir: string;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
tmpDir = mkdtempSync(join(tmpdir(), "gsd-sync-test-"));
|
|
25
|
+
mkdirSync(join(tmpDir, ".gsd"), { recursive: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("loadSyncMapping returns null when no file exists", () => {
|
|
33
|
+
const result = loadSyncMapping(tmpDir);
|
|
34
|
+
assert.equal(result, null);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("round-trips save/load", () => {
|
|
38
|
+
const mapping = createEmptyMapping("owner/repo");
|
|
39
|
+
saveSyncMapping(tmpDir, mapping);
|
|
40
|
+
const loaded = loadSyncMapping(tmpDir);
|
|
41
|
+
assert.deepEqual(loaded, mapping);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("createEmptyMapping has correct structure", () => {
|
|
45
|
+
const mapping = createEmptyMapping("owner/repo");
|
|
46
|
+
assert.equal(mapping.version, 1);
|
|
47
|
+
assert.equal(mapping.repo, "owner/repo");
|
|
48
|
+
assert.deepEqual(mapping.milestones, {});
|
|
49
|
+
assert.deepEqual(mapping.slices, {});
|
|
50
|
+
assert.deepEqual(mapping.tasks, {});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("milestone record accessors work", () => {
|
|
54
|
+
const mapping = createEmptyMapping("owner/repo");
|
|
55
|
+
assert.equal(getMilestoneRecord(mapping, "M001"), null);
|
|
56
|
+
|
|
57
|
+
const record: MilestoneSyncRecord = {
|
|
58
|
+
issueNumber: 42,
|
|
59
|
+
ghMilestoneNumber: 1,
|
|
60
|
+
lastSyncedAt: "2025-01-01T00:00:00Z",
|
|
61
|
+
state: "open",
|
|
62
|
+
};
|
|
63
|
+
setMilestoneRecord(mapping, "M001", record);
|
|
64
|
+
assert.deepEqual(getMilestoneRecord(mapping, "M001"), record);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("slice record accessors work", () => {
|
|
68
|
+
const mapping = createEmptyMapping("owner/repo");
|
|
69
|
+
assert.equal(getSliceRecord(mapping, "M001", "S01"), null);
|
|
70
|
+
|
|
71
|
+
const record: SliceSyncRecord = {
|
|
72
|
+
issueNumber: 0,
|
|
73
|
+
prNumber: 50,
|
|
74
|
+
branch: "milestone/M001/S01",
|
|
75
|
+
lastSyncedAt: "2025-01-01T00:00:00Z",
|
|
76
|
+
state: "open",
|
|
77
|
+
};
|
|
78
|
+
setSliceRecord(mapping, "M001", "S01", record);
|
|
79
|
+
assert.deepEqual(getSliceRecord(mapping, "M001", "S01"), record);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("task record accessors work", () => {
|
|
83
|
+
const mapping = createEmptyMapping("owner/repo");
|
|
84
|
+
assert.equal(getTaskRecord(mapping, "M001", "S01", "T01"), null);
|
|
85
|
+
assert.equal(getTaskIssueNumber(mapping, "M001", "S01", "T01"), null);
|
|
86
|
+
|
|
87
|
+
const record: SyncEntityRecord = {
|
|
88
|
+
issueNumber: 43,
|
|
89
|
+
lastSyncedAt: "2025-01-01T00:00:00Z",
|
|
90
|
+
state: "open",
|
|
91
|
+
};
|
|
92
|
+
setTaskRecord(mapping, "M001", "S01", "T01", record);
|
|
93
|
+
assert.deepEqual(getTaskRecord(mapping, "M001", "S01", "T01"), record);
|
|
94
|
+
assert.equal(getTaskIssueNumber(mapping, "M001", "S01", "T01"), 43);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("rejects mapping with wrong version", () => {
|
|
98
|
+
const mapping = createEmptyMapping("owner/repo");
|
|
99
|
+
(mapping as any).version = 2;
|
|
100
|
+
saveSyncMapping(tmpDir, mapping);
|
|
101
|
+
const loaded = loadSyncMapping(tmpDir);
|
|
102
|
+
assert.equal(loaded, null);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import {
|
|
4
|
+
formatMilestoneIssueBody,
|
|
5
|
+
formatSlicePRBody,
|
|
6
|
+
formatTaskIssueBody,
|
|
7
|
+
formatSummaryComment,
|
|
8
|
+
} from "../templates.ts";
|
|
9
|
+
|
|
10
|
+
describe("templates", () => {
|
|
11
|
+
describe("formatMilestoneIssueBody", () => {
|
|
12
|
+
it("includes title and vision", () => {
|
|
13
|
+
const body = formatMilestoneIssueBody({
|
|
14
|
+
id: "M001",
|
|
15
|
+
title: "Build Auth",
|
|
16
|
+
vision: "Secure authentication for all users",
|
|
17
|
+
});
|
|
18
|
+
assert.ok(body.includes("M001: Build Auth"));
|
|
19
|
+
assert.ok(body.includes("Secure authentication"));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("renders success criteria as checkboxes", () => {
|
|
23
|
+
const body = formatMilestoneIssueBody({
|
|
24
|
+
id: "M001",
|
|
25
|
+
title: "Auth",
|
|
26
|
+
successCriteria: ["Users can log in", "OAuth works"],
|
|
27
|
+
});
|
|
28
|
+
assert.ok(body.includes("- [ ] Users can log in"));
|
|
29
|
+
assert.ok(body.includes("- [ ] OAuth works"));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("renders slice table", () => {
|
|
33
|
+
const body = formatMilestoneIssueBody({
|
|
34
|
+
id: "M001",
|
|
35
|
+
title: "Auth",
|
|
36
|
+
slices: [
|
|
37
|
+
{ id: "S01", title: "Core types", taskCount: 3 },
|
|
38
|
+
{ id: "S02", title: "OAuth", taskCount: 5 },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
assert.ok(body.includes("| S01 | Core types | 3 |"));
|
|
42
|
+
assert.ok(body.includes("| S02 | OAuth | 5 |"));
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("formatSlicePRBody", () => {
|
|
47
|
+
it("includes goal and must-haves", () => {
|
|
48
|
+
const body = formatSlicePRBody({
|
|
49
|
+
id: "S01",
|
|
50
|
+
title: "Core Auth Types",
|
|
51
|
+
goal: "Define all auth types",
|
|
52
|
+
mustHaves: ["User type", "Session type"],
|
|
53
|
+
});
|
|
54
|
+
assert.ok(body.includes("Define all auth types"));
|
|
55
|
+
assert.ok(body.includes("- User type"));
|
|
56
|
+
assert.ok(body.includes("- Session type"));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("renders task checklist with issue links", () => {
|
|
60
|
+
const body = formatSlicePRBody({
|
|
61
|
+
id: "S01",
|
|
62
|
+
title: "Auth",
|
|
63
|
+
tasks: [
|
|
64
|
+
{ id: "T01", title: "Types", issueNumber: 43 },
|
|
65
|
+
{ id: "T02", title: "Schema" },
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
assert.ok(body.includes("- [ ] T01: Types (#43)"));
|
|
69
|
+
assert.ok(body.includes("- [ ] T02: Schema"));
|
|
70
|
+
assert.ok(!body.includes("T02: Schema (#"));
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("formatTaskIssueBody", () => {
|
|
75
|
+
it("includes files and verification", () => {
|
|
76
|
+
const body = formatTaskIssueBody({
|
|
77
|
+
id: "T01",
|
|
78
|
+
title: "Add types",
|
|
79
|
+
files: ["src/types.ts"],
|
|
80
|
+
verifyCriteria: ["Types compile"],
|
|
81
|
+
});
|
|
82
|
+
assert.ok(body.includes("`src/types.ts`"));
|
|
83
|
+
assert.ok(body.includes("- [ ] Types compile"));
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("formatSummaryComment", () => {
|
|
88
|
+
it("includes one-liner and body", () => {
|
|
89
|
+
const comment = formatSummaryComment({
|
|
90
|
+
oneLiner: "Added retry logic",
|
|
91
|
+
body: "Implemented exponential backoff",
|
|
92
|
+
});
|
|
93
|
+
assert.ok(comment.includes("**Summary:** Added retry logic"));
|
|
94
|
+
assert.ok(comment.includes("Implemented exponential backoff"));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("wraps frontmatter in details block", () => {
|
|
98
|
+
const comment = formatSummaryComment({
|
|
99
|
+
frontmatter: { duration: "45m", key_files: ["a.ts"] },
|
|
100
|
+
});
|
|
101
|
+
assert.ok(comment.includes("<details>"));
|
|
102
|
+
assert.ok(comment.includes("duration:"));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("handles empty data gracefully", () => {
|
|
106
|
+
const comment = formatSummaryComment({});
|
|
107
|
+
assert.equal(typeof comment, "string");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for the GitHub Sync extension.
|
|
3
|
+
*
|
|
4
|
+
* Config shape (stored in GSD preferences under `github` key) and
|
|
5
|
+
* sync mapping records (stored in `.gsd/github-sync.json`).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// βββ Configuration ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
9
|
+
|
|
10
|
+
export interface GitHubSyncConfig {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
/** "owner/repo" β auto-detected from git remote if omitted. */
|
|
13
|
+
repo?: string;
|
|
14
|
+
/** GitHub Projects v2 number (optional). */
|
|
15
|
+
project?: number;
|
|
16
|
+
/** Labels applied to all created issues. */
|
|
17
|
+
labels?: string[];
|
|
18
|
+
/** Append "Resolves #N" to task commits. Default: true. */
|
|
19
|
+
auto_link_commits?: boolean;
|
|
20
|
+
/** Create per-slice draft PRs. Default: true. */
|
|
21
|
+
slice_prs?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// βββ Sync Mapping βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
25
|
+
|
|
26
|
+
export interface SyncEntityRecord {
|
|
27
|
+
issueNumber: number;
|
|
28
|
+
lastSyncedAt: string;
|
|
29
|
+
state: "open" | "closed";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface MilestoneSyncRecord extends SyncEntityRecord {
|
|
33
|
+
ghMilestoneNumber: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SliceSyncRecord extends SyncEntityRecord {
|
|
37
|
+
prNumber: number;
|
|
38
|
+
branch: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SyncMapping {
|
|
42
|
+
version: 1;
|
|
43
|
+
repo: string;
|
|
44
|
+
milestones: Record<string, MilestoneSyncRecord>;
|
|
45
|
+
slices: Record<string, SliceSyncRecord>;
|
|
46
|
+
tasks: Record<string, SyncEntityRecord>;
|
|
47
|
+
}
|
|
@@ -124,6 +124,9 @@ export class AutoSession {
|
|
|
124
124
|
// ββ Sidecar queue βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
125
125
|
sidecarQueue: SidecarItem[] = [];
|
|
126
126
|
|
|
127
|
+
// ββ Dispatch circuit breakers ββββββββββββββββββββββββββββββββββββββ
|
|
128
|
+
rewriteAttemptCount = 0;
|
|
129
|
+
|
|
127
130
|
// ββ Metrics ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
128
131
|
autoStartTime = 0;
|
|
129
132
|
lastPromptCharCount: number | undefined;
|
|
@@ -134,27 +137,8 @@ export class AutoSession {
|
|
|
134
137
|
sigtermHandler: (() => void) | null = null;
|
|
135
138
|
|
|
136
139
|
// ββ Loop promise state ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
* emitted from the previous session's abort during this window must be
|
|
140
|
-
* ignored; they do not belong to the new unit.
|
|
141
|
-
*/
|
|
142
|
-
sessionSwitchInFlight = false;
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* One-shot resolver for the current unit's agent_end promise.
|
|
146
|
-
* Non-null only while a unit is in-flight (between sendMessage and agent_end).
|
|
147
|
-
* Scoped to the session to prevent concurrent session corruption.
|
|
148
|
-
*/
|
|
149
|
-
pendingResolve: ((result: { status: "completed" | "cancelled" | "error"; event?: { messages: unknown[] } }) => void) | null = null;
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Queue for agent_end events that arrive when no pendingResolve exists.
|
|
153
|
-
* This happens when error-recovery sendMessage retries produce agent_end
|
|
154
|
-
* events between loop iterations. The next runUnit drains this queue
|
|
155
|
-
* instead of waiting for a new event.
|
|
156
|
-
*/
|
|
157
|
-
pendingAgentEndQueue: Array<{ messages: unknown[] }> = [];
|
|
140
|
+
// Per-unit resolve function and session-switch guard live at module level
|
|
141
|
+
// in auto-loop.ts (_currentResolve, _sessionSwitchInFlight).
|
|
158
142
|
|
|
159
143
|
// ββ Methods ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
160
144
|
|
|
@@ -228,14 +212,12 @@ export class AutoSession {
|
|
|
228
212
|
this.lastBaselineCharCount = undefined;
|
|
229
213
|
this.pendingQuickTasks = [];
|
|
230
214
|
this.sidecarQueue = [];
|
|
215
|
+
this.rewriteAttemptCount = 0;
|
|
231
216
|
|
|
232
217
|
// Signal handler
|
|
233
218
|
this.sigtermHandler = null;
|
|
234
219
|
|
|
235
|
-
// Loop promise state
|
|
236
|
-
this.sessionSwitchInFlight = false;
|
|
237
|
-
this.pendingResolve = null;
|
|
238
|
-
this.pendingAgentEndQueue = [];
|
|
220
|
+
// Loop promise state lives in auto-loop.ts module scope
|
|
239
221
|
}
|
|
240
222
|
|
|
241
223
|
toJSON(): Record<string, unknown> {
|
|
@@ -62,6 +62,7 @@ export interface DispatchContext {
|
|
|
62
62
|
midTitle: string;
|
|
63
63
|
state: GSDState;
|
|
64
64
|
prefs: GSDPreferences | undefined;
|
|
65
|
+
session?: import("./auto/session.js").AutoSession;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
interface DispatchRule {
|
|
@@ -82,26 +83,23 @@ function missingSliceStop(mid: string, phase: string): DispatchAction {
|
|
|
82
83
|
// βββ Rewrite Circuit Breaker ββββββββββββββββββββββββββββββββββββββββββββββ
|
|
83
84
|
|
|
84
85
|
const MAX_REWRITE_ATTEMPTS = 3;
|
|
85
|
-
let rewriteAttemptCount = 0;
|
|
86
|
-
export function resetRewriteCircuitBreaker(): void {
|
|
87
|
-
rewriteAttemptCount = 0;
|
|
88
|
-
}
|
|
89
86
|
|
|
90
87
|
// βββ Rules ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
91
88
|
|
|
92
89
|
const DISPATCH_RULES: DispatchRule[] = [
|
|
93
90
|
{
|
|
94
91
|
name: "rewrite-docs (override gate)",
|
|
95
|
-
match: async ({ mid, midTitle, state, basePath }) => {
|
|
92
|
+
match: async ({ mid, midTitle, state, basePath, session }) => {
|
|
96
93
|
const pendingOverrides = await loadActiveOverrides(basePath);
|
|
97
94
|
if (pendingOverrides.length === 0) return null;
|
|
98
|
-
|
|
95
|
+
const count = session?.rewriteAttemptCount ?? 0;
|
|
96
|
+
if (count >= MAX_REWRITE_ATTEMPTS) {
|
|
99
97
|
const { resolveAllOverrides } = await import("./files.js");
|
|
100
98
|
await resolveAllOverrides(basePath);
|
|
101
|
-
rewriteAttemptCount = 0;
|
|
99
|
+
if (session) session.rewriteAttemptCount = 0;
|
|
102
100
|
return null;
|
|
103
101
|
}
|
|
104
|
-
rewriteAttemptCount++;
|
|
102
|
+
if (session) session.rewriteAttemptCount++;
|
|
105
103
|
const unitId = state.activeSlice ? `${mid}/${state.activeSlice.id}` : mid;
|
|
106
104
|
return {
|
|
107
105
|
action: "dispatch",
|
|
@@ -157,7 +155,7 @@ const DISPATCH_RULES: DispatchRule[] = [
|
|
|
157
155
|
uatContent ?? "",
|
|
158
156
|
basePath,
|
|
159
157
|
),
|
|
160
|
-
pauseAfterDispatch: uatType !== "artifact-driven",
|
|
158
|
+
pauseAfterDispatch: uatType !== "artifact-driven" && uatType !== "browser-executable" && uatType !== "runtime-executable",
|
|
161
159
|
};
|
|
162
160
|
},
|
|
163
161
|
},
|