gsd-pi 2.65.0-dev.6cc5110 → 2.65.0-dev.800ece0
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/resources/extensions/gsd/auto/finalize-timeout.js +2 -0
- package/dist/resources/extensions/gsd/auto/loop.js +2 -2
- package/dist/resources/extensions/gsd/auto/phases.js +48 -5
- package/dist/resources/extensions/gsd/auto/types.js +2 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +2 -1
- package/dist/resources/extensions/gsd/auto-start.js +134 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -2
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +3 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +31 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +3 -2
- package/dist/resources/extensions/gsd/files.js +17 -0
- package/dist/resources/extensions/gsd/gsd-db.js +36 -2
- package/dist/resources/extensions/gsd/index.js +1 -1
- package/dist/resources/extensions/gsd/notification-overlay.js +1 -1
- package/dist/resources/extensions/gsd/notification-widget.js +2 -1
- package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +1 -1
- package/dist/resources/extensions/gsd/pre-execution-checks.js +16 -2
- package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/queue.md +2 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -2
- package/dist/resources/extensions/gsd/state.js +3 -6
- package/dist/resources/extensions/gsd/workflow-events.js +1 -0
- package/dist/resources/extensions/gsd/workflow-projections.js +3 -2
- package/dist/resources/extensions/subagent/agents.js +19 -5
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +20 -20
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +20 -20
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +3 -1
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/src/tui.ts +3 -1
- package/src/resources/extensions/gsd/auto/finalize-timeout.ts +3 -0
- package/src/resources/extensions/gsd/auto/loop.ts +2 -2
- package/src/resources/extensions/gsd/auto/phases.ts +68 -3
- package/src/resources/extensions/gsd/auto/types.ts +5 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +2 -1
- package/src/resources/extensions/gsd/auto-start.ts +143 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -2
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +3 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +36 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +3 -2
- package/src/resources/extensions/gsd/files.ts +19 -0
- package/src/resources/extensions/gsd/gsd-db.ts +33 -2
- package/src/resources/extensions/gsd/index.ts +1 -0
- package/src/resources/extensions/gsd/notification-overlay.ts +1 -1
- package/src/resources/extensions/gsd/notification-widget.ts +2 -1
- package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +1 -1
- package/src/resources/extensions/gsd/pre-execution-checks.ts +19 -2
- package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/queue.md +2 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -2
- package/src/resources/extensions/gsd/state.ts +3 -6
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +11 -10
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +189 -0
- package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/subagent-agent-discovery.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/wave5-consistency-regressions.test.ts +165 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +127 -2
- package/src/resources/extensions/gsd/workflow-events.ts +5 -3
- package/src/resources/extensions/gsd/workflow-projections.ts +3 -2
- package/src/resources/extensions/subagent/agents.ts +30 -6
- /package/dist/web/standalone/.next/static/{iueakR5x5bQbax2sGz8Yr → E0hBt4ifuG7QBbhUR5-6U}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{iueakR5x5bQbax2sGz8Yr → E0hBt4ifuG7QBbhUR5-6U}/_ssgManifest.js +0 -0
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import test from 'node:test';
|
|
13
13
|
import assert from 'node:assert/strict';
|
|
14
14
|
import {
|
|
15
|
+
isDepthConfirmationAnswer,
|
|
15
16
|
shouldBlockContextWrite,
|
|
16
17
|
isDepthVerified,
|
|
17
18
|
isQueuePhaseActive,
|
|
@@ -117,9 +118,9 @@ test('write-gate: regex does not match slice context files (S01-CONTEXT.md)', ()
|
|
|
117
118
|
assert.strictEqual(result.block, false, 'S01-CONTEXT.md should not be blocked');
|
|
118
119
|
});
|
|
119
120
|
|
|
120
|
-
// ─── Scenario 7: Error message contains actionable instruction ──
|
|
121
|
+
// ─── Scenario 7: Error message contains actionable instruction and anti-bypass language ──
|
|
121
122
|
|
|
122
|
-
test('write-gate: blocked reason contains depth_verification keyword', () => {
|
|
123
|
+
test('write-gate: blocked reason contains depth_verification keyword and anti-bypass language', () => {
|
|
123
124
|
const result = shouldBlockContextWrite(
|
|
124
125
|
'write',
|
|
125
126
|
'.gsd/milestones/M999/M999-CONTEXT.md',
|
|
@@ -129,6 +130,8 @@ test('write-gate: blocked reason contains depth_verification keyword', () => {
|
|
|
129
130
|
assert.strictEqual(result.block, true);
|
|
130
131
|
assert.ok(result.reason!.includes('depth_verification'), 'reason should mention depth_verification question id');
|
|
131
132
|
assert.ok(result.reason!.includes('ask_user_questions'), 'reason should mention ask_user_questions tool');
|
|
133
|
+
assert.ok(result.reason!.includes('MUST NOT'), 'reason should include anti-bypass language');
|
|
134
|
+
assert.ok(result.reason!.includes('(Recommended)'), 'reason should specify the required confirmation option');
|
|
132
135
|
});
|
|
133
136
|
|
|
134
137
|
// ─── Scenario 8: Queue mode blocks CONTEXT.md write without depth verification ──
|
|
@@ -191,3 +194,125 @@ test('write-gate: markDepthVerified unblocks queue-mode writes when milestoneId
|
|
|
191
194
|
|
|
192
195
|
clearDiscussionFlowState();
|
|
193
196
|
});
|
|
197
|
+
|
|
198
|
+
// ─── Standard options fixture used across depth confirmation tests ──
|
|
199
|
+
|
|
200
|
+
const STANDARD_OPTIONS = [
|
|
201
|
+
{ label: 'Yes, you got it (Recommended)' },
|
|
202
|
+
{ label: 'Not quite — let me clarify' },
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
// ─── Scenario 11: accepts first option (confirmation) with structural validation ──
|
|
206
|
+
|
|
207
|
+
test('write-gate: isDepthConfirmationAnswer accepts first option with options present', () => {
|
|
208
|
+
assert.strictEqual(
|
|
209
|
+
isDepthConfirmationAnswer('Yes, you got it (Recommended)', STANDARD_OPTIONS),
|
|
210
|
+
true,
|
|
211
|
+
'should accept exact match of first option label',
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// ─── Scenario 12: rejects second option (decline) ──
|
|
216
|
+
|
|
217
|
+
test('write-gate: isDepthConfirmationAnswer rejects decline option', () => {
|
|
218
|
+
assert.strictEqual(
|
|
219
|
+
isDepthConfirmationAnswer('Not quite — let me clarify', STANDARD_OPTIONS),
|
|
220
|
+
false,
|
|
221
|
+
'should reject the clarification option',
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// ─── Scenario 13: rejects "None of the above" ──
|
|
226
|
+
|
|
227
|
+
test('write-gate: isDepthConfirmationAnswer rejects None of the above', () => {
|
|
228
|
+
assert.strictEqual(
|
|
229
|
+
isDepthConfirmationAnswer('None of the above', STANDARD_OPTIONS),
|
|
230
|
+
false,
|
|
231
|
+
'should reject None of the above',
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ─── Scenario 14: rejects garbage/empty input ──
|
|
236
|
+
|
|
237
|
+
test('write-gate: isDepthConfirmationAnswer rejects garbage and edge cases', () => {
|
|
238
|
+
assert.strictEqual(isDepthConfirmationAnswer('discord', STANDARD_OPTIONS), false, 'garbage string');
|
|
239
|
+
assert.strictEqual(isDepthConfirmationAnswer('', STANDARD_OPTIONS), false, 'empty string');
|
|
240
|
+
assert.strictEqual(isDepthConfirmationAnswer(undefined, STANDARD_OPTIONS), false, 'undefined');
|
|
241
|
+
assert.strictEqual(isDepthConfirmationAnswer(null, STANDARD_OPTIONS), false, 'null');
|
|
242
|
+
assert.strictEqual(isDepthConfirmationAnswer(42, STANDARD_OPTIONS), false, 'number');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// ─── Scenario 15: handles array-wrapped selection ──
|
|
246
|
+
|
|
247
|
+
test('write-gate: isDepthConfirmationAnswer handles array-wrapped selected value', () => {
|
|
248
|
+
assert.strictEqual(
|
|
249
|
+
isDepthConfirmationAnswer(['Yes, you got it (Recommended)'], STANDARD_OPTIONS),
|
|
250
|
+
true,
|
|
251
|
+
'should accept array-wrapped confirmation',
|
|
252
|
+
);
|
|
253
|
+
assert.strictEqual(
|
|
254
|
+
isDepthConfirmationAnswer(['Not quite — let me clarify'], STANDARD_OPTIONS),
|
|
255
|
+
false,
|
|
256
|
+
'should reject array-wrapped decline',
|
|
257
|
+
);
|
|
258
|
+
assert.strictEqual(
|
|
259
|
+
isDepthConfirmationAnswer([], STANDARD_OPTIONS),
|
|
260
|
+
false,
|
|
261
|
+
'should reject empty array',
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ─── Scenario 16: rejects free-form "Other" text that contains "(Recommended)" ──
|
|
266
|
+
|
|
267
|
+
test('write-gate: isDepthConfirmationAnswer rejects free-form text containing Recommended', () => {
|
|
268
|
+
assert.strictEqual(
|
|
269
|
+
isDepthConfirmationAnswer('I think this is fine (Recommended)', STANDARD_OPTIONS),
|
|
270
|
+
false,
|
|
271
|
+
'free-form text with (Recommended) substring must not unlock gate',
|
|
272
|
+
);
|
|
273
|
+
assert.strictEqual(
|
|
274
|
+
isDepthConfirmationAnswer('(Recommended)', STANDARD_OPTIONS),
|
|
275
|
+
false,
|
|
276
|
+
'bare (Recommended) string must not unlock gate',
|
|
277
|
+
);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// ─── Scenario 17: works with changed label text (decoupled from specific copy) ──
|
|
281
|
+
|
|
282
|
+
test('write-gate: isDepthConfirmationAnswer works with different label text', () => {
|
|
283
|
+
const customOptions = [
|
|
284
|
+
{ label: 'Looks good, proceed' },
|
|
285
|
+
{ label: 'Needs more discussion' },
|
|
286
|
+
];
|
|
287
|
+
assert.strictEqual(
|
|
288
|
+
isDepthConfirmationAnswer('Looks good, proceed', customOptions),
|
|
289
|
+
true,
|
|
290
|
+
'should accept first option regardless of label text',
|
|
291
|
+
);
|
|
292
|
+
assert.strictEqual(
|
|
293
|
+
isDepthConfirmationAnswer('Needs more discussion', customOptions),
|
|
294
|
+
false,
|
|
295
|
+
'should reject second option',
|
|
296
|
+
);
|
|
297
|
+
// Old label should NOT work with new options
|
|
298
|
+
assert.strictEqual(
|
|
299
|
+
isDepthConfirmationAnswer('Yes, you got it (Recommended)', customOptions),
|
|
300
|
+
false,
|
|
301
|
+
'old label text should not match new options',
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// ─── Scenario 18: fallback when options not available ──
|
|
306
|
+
|
|
307
|
+
test('write-gate: isDepthConfirmationAnswer falls back to (Recommended) match without options', () => {
|
|
308
|
+
assert.strictEqual(
|
|
309
|
+
isDepthConfirmationAnswer('Yes, you got it (Recommended)'),
|
|
310
|
+
true,
|
|
311
|
+
'should accept via fallback when no options provided',
|
|
312
|
+
);
|
|
313
|
+
assert.strictEqual(
|
|
314
|
+
isDepthConfirmationAnswer('Not quite — let me clarify'),
|
|
315
|
+
false,
|
|
316
|
+
'should reject non-Recommended via fallback',
|
|
317
|
+
);
|
|
318
|
+
});
|
|
@@ -19,10 +19,11 @@ export function getSessionId(): string {
|
|
|
19
19
|
// ─── Event Types ─────────────────────────────────────────────────────────
|
|
20
20
|
|
|
21
21
|
export interface WorkflowEvent {
|
|
22
|
-
|
|
22
|
+
v?: number; // schema version — omitted in v1 (legacy), 2 for current format
|
|
23
|
+
cmd: string; // e.g. "complete-task" (canonical: hyphens; legacy: underscores — both accepted by replay)
|
|
23
24
|
params: Record<string, unknown>;
|
|
24
|
-
ts: string;
|
|
25
|
-
hash: string;
|
|
25
|
+
ts: string; // ISO 8601
|
|
26
|
+
hash: string; // content hash (hex, 16 chars)
|
|
26
27
|
actor: "agent" | "system";
|
|
27
28
|
actor_name?: string; // e.g. "executor-agent-01" — caller-provided identity
|
|
28
29
|
trigger_reason?: string; // e.g. "plan-phase complete" — caller-provided causation
|
|
@@ -46,6 +47,7 @@ export function appendEvent(
|
|
|
46
47
|
.slice(0, 16);
|
|
47
48
|
|
|
48
49
|
const fullEvent: WorkflowEvent = {
|
|
50
|
+
v: 2,
|
|
49
51
|
...event,
|
|
50
52
|
hash,
|
|
51
53
|
session_id: ENGINE_SESSION_ID,
|
|
@@ -16,6 +16,7 @@ import { atomicWriteSync } from "./atomic-write.js";
|
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { mkdirSync, existsSync } from "node:fs";
|
|
18
18
|
import { logWarning } from "./workflow-logger.js";
|
|
19
|
+
import { isClosedStatus } from "./status-guards.js";
|
|
19
20
|
import { deriveState } from "./state.js";
|
|
20
21
|
import type { GSDState } from "./types.js";
|
|
21
22
|
|
|
@@ -55,7 +56,7 @@ export function renderPlanContent(sliceRow: SliceRow, taskRows: TaskRow[]): stri
|
|
|
55
56
|
lines.push("## Tasks");
|
|
56
57
|
|
|
57
58
|
for (const task of taskRows) {
|
|
58
|
-
const checkbox = task.status
|
|
59
|
+
const checkbox = isClosedStatus(task.status) ? "[x]" : "[ ]";
|
|
59
60
|
lines.push(`- ${checkbox} **${task.id}: ${task.title}** \u2014 ${task.description}`);
|
|
60
61
|
|
|
61
62
|
// Estimate subline (always present if non-empty)
|
|
@@ -125,7 +126,7 @@ export function renderRoadmapContent(milestoneRow: MilestoneRow, sliceRows: Slic
|
|
|
125
126
|
lines.push("|----|-------|------|---------|------|------------|");
|
|
126
127
|
|
|
127
128
|
for (const slice of sliceRows) {
|
|
128
|
-
const done = slice.status
|
|
129
|
+
const done = isClosedStatus(slice.status) ? "\u2705" : "\u2B1C";
|
|
129
130
|
|
|
130
131
|
// depends is already parsed to string[] by rowToSlice
|
|
131
132
|
let depends = "\u2014";
|
|
@@ -25,6 +25,33 @@ export interface AgentDiscoveryResult {
|
|
|
25
25
|
projectAgentsDir: string | null;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
interface AgentFrontmatter extends Record<string, unknown> {
|
|
29
|
+
name?: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
tools?: string | string[];
|
|
32
|
+
model?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseAgentTools(value: string | string[] | undefined): string[] | undefined {
|
|
36
|
+
if (typeof value === "string") {
|
|
37
|
+
const tools = value
|
|
38
|
+
.split(",")
|
|
39
|
+
.map((tool) => tool.trim())
|
|
40
|
+
.filter(Boolean);
|
|
41
|
+
return tools.length > 0 ? tools : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (Array.isArray(value)) {
|
|
45
|
+
const tools = value
|
|
46
|
+
.flatMap((tool) => typeof tool === "string" ? tool.split(",") : [])
|
|
47
|
+
.map((tool) => tool.trim())
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
return tools.length > 0 ? tools : undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
28
55
|
function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig[] {
|
|
29
56
|
const agents: AgentConfig[] = [];
|
|
30
57
|
|
|
@@ -51,16 +78,13 @@ function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig
|
|
|
51
78
|
continue;
|
|
52
79
|
}
|
|
53
80
|
|
|
54
|
-
const { frontmatter, body } = parseFrontmatter<
|
|
81
|
+
const { frontmatter, body } = parseFrontmatter<AgentFrontmatter>(content);
|
|
55
82
|
|
|
56
|
-
if (
|
|
83
|
+
if (typeof frontmatter.name !== "string" || typeof frontmatter.description !== "string") {
|
|
57
84
|
continue;
|
|
58
85
|
}
|
|
59
86
|
|
|
60
|
-
const tools = frontmatter.tools
|
|
61
|
-
?.split(",")
|
|
62
|
-
.map((t: string) => t.trim())
|
|
63
|
-
.filter(Boolean);
|
|
87
|
+
const tools = parseAgentTools(frontmatter.tools);
|
|
64
88
|
|
|
65
89
|
agents.push({
|
|
66
90
|
name: frontmatter.name,
|
|
File without changes
|
|
File without changes
|