gsd-pi 2.24.0 → 2.26.0
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 +13 -3
- package/dist/headless.js +24 -4
- package/dist/models-resolver.d.ts +0 -11
- package/dist/models-resolver.js +0 -15
- package/dist/resource-loader.d.ts +0 -1
- package/dist/resource-loader.js +0 -9
- package/dist/resources/GSD-WORKFLOW.md +12 -9
- package/dist/resources/extensions/async-jobs/index.ts +9 -1
- package/dist/resources/extensions/bg-shell/index.ts +3 -2
- package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
- package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
- package/dist/resources/extensions/gsd/activity-log.ts +5 -3
- package/dist/resources/extensions/gsd/auto-prompts.ts +14 -0
- package/dist/resources/extensions/gsd/auto-recovery.ts +7 -4
- package/dist/resources/extensions/gsd/auto-worktree.ts +132 -3
- package/dist/resources/extensions/gsd/auto.ts +265 -48
- package/dist/resources/extensions/gsd/cache.ts +3 -1
- package/dist/resources/extensions/gsd/doctor-proactive.ts +7 -6
- package/dist/resources/extensions/gsd/doctor.ts +26 -1
- package/dist/resources/extensions/gsd/files.ts +13 -2
- package/dist/resources/extensions/gsd/git-service.ts +74 -14
- package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +54 -22
- package/dist/resources/extensions/gsd/index.ts +62 -8
- package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/dist/resources/extensions/gsd/memory-store.ts +441 -0
- package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
- package/dist/resources/extensions/gsd/migrate/writer.ts +39 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
- package/dist/resources/extensions/gsd/preferences.ts +2 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
- package/dist/resources/extensions/gsd/prompts/discuss.md +5 -5
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +3 -3
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.ts +45 -1
- package/dist/resources/extensions/gsd/state.ts +17 -6
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
- package/dist/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
- package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
- package/dist/resources/extensions/gsd/types.ts +2 -0
- package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/dist/resources/extensions/gsd/worktree.ts +9 -2
- package/dist/resources/extensions/search-the-web/native-search.ts +19 -5
- package/dist/resources/extensions/shared/path-display.ts +19 -0
- package/package.json +1 -6
- package/packages/pi-agent-core/dist/agent-loop.js +2 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +2 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +64 -0
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/mistral.js +3 -0
- package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +23 -1
- 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/src/providers/anthropic.ts +65 -1
- package/packages/pi-ai/src/providers/mistral.ts +3 -0
- package/packages/pi-ai/src/types.ts +19 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +12 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +7 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +8 -3
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +2 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +5 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +41 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +301 -62
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +5 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +135 -30
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts +8 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.js +60 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/clipboard-image.js +32 -6
- package/packages/pi-coding-agent/dist/utils/clipboard-image.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/path-display.d.ts +34 -0
- package/packages/pi-coding-agent/dist/utils/path-display.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/path-display.js +36 -0
- package/packages/pi-coding-agent/dist/utils/path-display.js.map +1 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +36 -0
- package/packages/pi-coding-agent/src/core/keybindings.ts +1 -1
- package/packages/pi-coding-agent/src/core/lsp/client.ts +11 -1
- package/packages/pi-coding-agent/src/core/lsp/index.ts +7 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +2 -1
- package/packages/pi-coding-agent/src/index.ts +15 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +347 -62
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +124 -4
- package/packages/pi-coding-agent/src/tests/path-display.test.ts +85 -0
- package/packages/pi-coding-agent/src/utils/clipboard-image.ts +33 -6
- package/packages/pi-coding-agent/src/utils/path-display.ts +36 -0
- package/src/resources/GSD-WORKFLOW.md +12 -9
- package/src/resources/extensions/async-jobs/index.ts +9 -1
- package/src/resources/extensions/bg-shell/index.ts +3 -2
- package/src/resources/extensions/bg-shell/overlay.ts +18 -17
- package/src/resources/extensions/get-secrets-from-user.ts +5 -23
- package/src/resources/extensions/gsd/activity-log.ts +5 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +14 -0
- package/src/resources/extensions/gsd/auto-recovery.ts +7 -4
- package/src/resources/extensions/gsd/auto-worktree.ts +132 -3
- package/src/resources/extensions/gsd/auto.ts +265 -48
- package/src/resources/extensions/gsd/cache.ts +3 -1
- package/src/resources/extensions/gsd/doctor-proactive.ts +7 -6
- package/src/resources/extensions/gsd/doctor.ts +26 -1
- package/src/resources/extensions/gsd/files.ts +13 -2
- package/src/resources/extensions/gsd/git-service.ts +74 -14
- package/src/resources/extensions/gsd/gsd-db.ts +78 -1
- package/src/resources/extensions/gsd/guided-flow.ts +54 -22
- package/src/resources/extensions/gsd/index.ts +62 -8
- package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/src/resources/extensions/gsd/memory-store.ts +441 -0
- package/src/resources/extensions/gsd/migrate/command.ts +2 -2
- package/src/resources/extensions/gsd/migrate/writer.ts +39 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
- package/src/resources/extensions/gsd/preferences.ts +2 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
- package/src/resources/extensions/gsd/prompts/discuss.md +5 -5
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +3 -3
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +45 -1
- package/src/resources/extensions/gsd/state.ts +17 -6
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
- package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/src/resources/extensions/gsd/triage-ui.ts +1 -1
- package/src/resources/extensions/gsd/types.ts +2 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/src/resources/extensions/gsd/worktree.ts +9 -2
- package/src/resources/extensions/search-the-web/native-search.ts +19 -5
- package/src/resources/extensions/shared/path-display.ts +19 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
// GSD Memory Store — CRUD, ranked queries, maintenance, and prompt formatting
|
|
2
|
+
//
|
|
3
|
+
// Storage layer for auto-learned project memories. Follows context-store.ts patterns.
|
|
4
|
+
// All functions degrade gracefully: return empty results when DB unavailable, never throw.
|
|
5
|
+
|
|
6
|
+
import { isDbAvailable, _getAdapter, transaction } from './gsd-db.js';
|
|
7
|
+
|
|
8
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
export interface Memory {
|
|
11
|
+
seq: number;
|
|
12
|
+
id: string;
|
|
13
|
+
category: string;
|
|
14
|
+
content: string;
|
|
15
|
+
confidence: number;
|
|
16
|
+
source_unit_type: string | null;
|
|
17
|
+
source_unit_id: string | null;
|
|
18
|
+
created_at: string;
|
|
19
|
+
updated_at: string;
|
|
20
|
+
superseded_by: string | null;
|
|
21
|
+
hit_count: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type MemoryActionCreate = {
|
|
25
|
+
action: 'CREATE';
|
|
26
|
+
category: string;
|
|
27
|
+
content: string;
|
|
28
|
+
confidence?: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type MemoryActionUpdate = {
|
|
32
|
+
action: 'UPDATE';
|
|
33
|
+
id: string;
|
|
34
|
+
content: string;
|
|
35
|
+
confidence?: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type MemoryActionReinforce = {
|
|
39
|
+
action: 'REINFORCE';
|
|
40
|
+
id: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type MemoryActionSupersede = {
|
|
44
|
+
action: 'SUPERSEDE';
|
|
45
|
+
id: string;
|
|
46
|
+
superseded_by: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type MemoryAction =
|
|
50
|
+
| MemoryActionCreate
|
|
51
|
+
| MemoryActionUpdate
|
|
52
|
+
| MemoryActionReinforce
|
|
53
|
+
| MemoryActionSupersede;
|
|
54
|
+
|
|
55
|
+
// ─── Category Display Order ─────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
const CATEGORY_PRIORITY: Record<string, number> = {
|
|
58
|
+
gotcha: 0,
|
|
59
|
+
convention: 1,
|
|
60
|
+
architecture: 2,
|
|
61
|
+
pattern: 3,
|
|
62
|
+
environment: 4,
|
|
63
|
+
preference: 5,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// ─── Row Mapping ────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
function rowToMemory(row: Record<string, unknown>): Memory {
|
|
69
|
+
return {
|
|
70
|
+
seq: row['seq'] as number,
|
|
71
|
+
id: row['id'] as string,
|
|
72
|
+
category: row['category'] as string,
|
|
73
|
+
content: row['content'] as string,
|
|
74
|
+
confidence: row['confidence'] as number,
|
|
75
|
+
source_unit_type: (row['source_unit_type'] as string) ?? null,
|
|
76
|
+
source_unit_id: (row['source_unit_id'] as string) ?? null,
|
|
77
|
+
created_at: row['created_at'] as string,
|
|
78
|
+
updated_at: row['updated_at'] as string,
|
|
79
|
+
superseded_by: (row['superseded_by'] as string) ?? null,
|
|
80
|
+
hit_count: row['hit_count'] as number,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ─── Query Functions ────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get all memories where superseded_by IS NULL.
|
|
88
|
+
* Returns [] if DB is not available. Never throws.
|
|
89
|
+
*/
|
|
90
|
+
export function getActiveMemories(): Memory[] {
|
|
91
|
+
if (!isDbAvailable()) return [];
|
|
92
|
+
const adapter = _getAdapter();
|
|
93
|
+
if (!adapter) return [];
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const rows = adapter.prepare('SELECT * FROM memories WHERE superseded_by IS NULL').all();
|
|
97
|
+
return rows.map(rowToMemory);
|
|
98
|
+
} catch {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get active memories ordered by ranking score: confidence * (1 + hit_count * 0.1).
|
|
105
|
+
* Higher-scored memories are more relevant and frequently confirmed.
|
|
106
|
+
*/
|
|
107
|
+
export function getActiveMemoriesRanked(limit = 30): Memory[] {
|
|
108
|
+
if (!isDbAvailable()) return [];
|
|
109
|
+
const adapter = _getAdapter();
|
|
110
|
+
if (!adapter) return [];
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const rows = adapter.prepare(
|
|
114
|
+
`SELECT * FROM memories
|
|
115
|
+
WHERE superseded_by IS NULL
|
|
116
|
+
ORDER BY (confidence * (1.0 + hit_count * 0.1)) DESC
|
|
117
|
+
LIMIT :limit`,
|
|
118
|
+
).all({ ':limit': limit });
|
|
119
|
+
return rows.map(rowToMemory);
|
|
120
|
+
} catch {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Generate the next memory ID: MEM + zero-padded 3-digit from MAX(seq).
|
|
127
|
+
* Returns MEM001 if no memories exist.
|
|
128
|
+
*/
|
|
129
|
+
export function nextMemoryId(): string {
|
|
130
|
+
if (!isDbAvailable()) return 'MEM001';
|
|
131
|
+
const adapter = _getAdapter();
|
|
132
|
+
if (!adapter) return 'MEM001';
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const row = adapter
|
|
136
|
+
.prepare('SELECT MAX(seq) as max_seq FROM memories')
|
|
137
|
+
.get();
|
|
138
|
+
const maxSeq = row ? (row['max_seq'] as number | null) : null;
|
|
139
|
+
if (maxSeq == null || isNaN(maxSeq)) return 'MEM001';
|
|
140
|
+
const next = maxSeq + 1;
|
|
141
|
+
return `MEM${String(next).padStart(3, '0')}`;
|
|
142
|
+
} catch {
|
|
143
|
+
return 'MEM001';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ─── Mutation Functions ─────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Insert a new memory with auto-assigned ID.
|
|
151
|
+
* Returns the assigned ID, or null on failure.
|
|
152
|
+
*/
|
|
153
|
+
export function createMemory(fields: {
|
|
154
|
+
category: string;
|
|
155
|
+
content: string;
|
|
156
|
+
confidence?: number;
|
|
157
|
+
source_unit_type?: string;
|
|
158
|
+
source_unit_id?: string;
|
|
159
|
+
}): string | null {
|
|
160
|
+
if (!isDbAvailable()) return null;
|
|
161
|
+
const adapter = _getAdapter();
|
|
162
|
+
if (!adapter) return null;
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const id = nextMemoryId();
|
|
166
|
+
const now = new Date().toISOString();
|
|
167
|
+
adapter.prepare(
|
|
168
|
+
`INSERT INTO memories (id, category, content, confidence, source_unit_type, source_unit_id, created_at, updated_at)
|
|
169
|
+
VALUES (:id, :category, :content, :confidence, :source_unit_type, :source_unit_id, :created_at, :updated_at)`,
|
|
170
|
+
).run({
|
|
171
|
+
':id': id,
|
|
172
|
+
':category': fields.category,
|
|
173
|
+
':content': fields.content,
|
|
174
|
+
':confidence': fields.confidence ?? 0.8,
|
|
175
|
+
':source_unit_type': fields.source_unit_type ?? null,
|
|
176
|
+
':source_unit_id': fields.source_unit_id ?? null,
|
|
177
|
+
':created_at': now,
|
|
178
|
+
':updated_at': now,
|
|
179
|
+
});
|
|
180
|
+
return id;
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Update a memory's content and optionally its confidence.
|
|
188
|
+
*/
|
|
189
|
+
export function updateMemoryContent(id: string, content: string, confidence?: number): boolean {
|
|
190
|
+
if (!isDbAvailable()) return false;
|
|
191
|
+
const adapter = _getAdapter();
|
|
192
|
+
if (!adapter) return false;
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const now = new Date().toISOString();
|
|
196
|
+
if (confidence != null) {
|
|
197
|
+
adapter.prepare(
|
|
198
|
+
'UPDATE memories SET content = :content, confidence = :confidence, updated_at = :updated_at WHERE id = :id',
|
|
199
|
+
).run({ ':content': content, ':confidence': confidence, ':updated_at': now, ':id': id });
|
|
200
|
+
} else {
|
|
201
|
+
adapter.prepare(
|
|
202
|
+
'UPDATE memories SET content = :content, updated_at = :updated_at WHERE id = :id',
|
|
203
|
+
).run({ ':content': content, ':updated_at': now, ':id': id });
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
} catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Reinforce a memory: increment hit_count, update timestamp.
|
|
213
|
+
*/
|
|
214
|
+
export function reinforceMemory(id: string): boolean {
|
|
215
|
+
if (!isDbAvailable()) return false;
|
|
216
|
+
const adapter = _getAdapter();
|
|
217
|
+
if (!adapter) return false;
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
adapter.prepare(
|
|
221
|
+
'UPDATE memories SET hit_count = hit_count + 1, updated_at = :updated_at WHERE id = :id',
|
|
222
|
+
).run({ ':updated_at': new Date().toISOString(), ':id': id });
|
|
223
|
+
return true;
|
|
224
|
+
} catch {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Mark a memory as superseded by another.
|
|
231
|
+
*/
|
|
232
|
+
export function supersedeMemory(oldId: string, newId: string): boolean {
|
|
233
|
+
if (!isDbAvailable()) return false;
|
|
234
|
+
const adapter = _getAdapter();
|
|
235
|
+
if (!adapter) return false;
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
adapter.prepare(
|
|
239
|
+
'UPDATE memories SET superseded_by = :new_id, updated_at = :updated_at WHERE id = :old_id',
|
|
240
|
+
).run({ ':new_id': newId, ':updated_at': new Date().toISOString(), ':old_id': oldId });
|
|
241
|
+
return true;
|
|
242
|
+
} catch {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ─── Processed Unit Tracking ────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check if a unit has already been processed for memory extraction.
|
|
251
|
+
*/
|
|
252
|
+
export function isUnitProcessed(unitKey: string): boolean {
|
|
253
|
+
if (!isDbAvailable()) return false;
|
|
254
|
+
const adapter = _getAdapter();
|
|
255
|
+
if (!adapter) return false;
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const row = adapter.prepare(
|
|
259
|
+
'SELECT 1 FROM memory_processed_units WHERE unit_key = :key',
|
|
260
|
+
).get({ ':key': unitKey });
|
|
261
|
+
return row != null;
|
|
262
|
+
} catch {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Record that a unit has been processed for memory extraction.
|
|
269
|
+
*/
|
|
270
|
+
export function markUnitProcessed(unitKey: string, activityFile: string): boolean {
|
|
271
|
+
if (!isDbAvailable()) return false;
|
|
272
|
+
const adapter = _getAdapter();
|
|
273
|
+
if (!adapter) return false;
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
adapter.prepare(
|
|
277
|
+
`INSERT OR IGNORE INTO memory_processed_units (unit_key, activity_file, processed_at)
|
|
278
|
+
VALUES (:key, :file, :at)`,
|
|
279
|
+
).run({ ':key': unitKey, ':file': activityFile, ':at': new Date().toISOString() });
|
|
280
|
+
return true;
|
|
281
|
+
} catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ─── Maintenance ────────────────────────────────────────────────────────────
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Reduce confidence for memories not updated within the last N processed units.
|
|
290
|
+
* "Stale" = updated_at is older than the Nth most recent processed_at.
|
|
291
|
+
*/
|
|
292
|
+
export function decayStaleMemories(thresholdUnits = 20): void {
|
|
293
|
+
if (!isDbAvailable()) return;
|
|
294
|
+
const adapter = _getAdapter();
|
|
295
|
+
if (!adapter) return;
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
// Find the timestamp of the Nth most recent processed unit
|
|
299
|
+
const row = adapter.prepare(
|
|
300
|
+
`SELECT processed_at FROM memory_processed_units
|
|
301
|
+
ORDER BY processed_at DESC
|
|
302
|
+
LIMIT 1 OFFSET :offset`,
|
|
303
|
+
).get({ ':offset': thresholdUnits - 1 });
|
|
304
|
+
|
|
305
|
+
if (!row) return; // not enough processed units yet
|
|
306
|
+
|
|
307
|
+
const cutoff = row['processed_at'] as string;
|
|
308
|
+
adapter.prepare(
|
|
309
|
+
`UPDATE memories
|
|
310
|
+
SET confidence = MAX(0.1, confidence - 0.1), updated_at = :now
|
|
311
|
+
WHERE superseded_by IS NULL AND updated_at < :cutoff AND confidence > 0.1`,
|
|
312
|
+
).run({ ':now': new Date().toISOString(), ':cutoff': cutoff });
|
|
313
|
+
} catch {
|
|
314
|
+
// non-fatal
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Supersede lowest-ranked memories when count exceeds cap.
|
|
320
|
+
*/
|
|
321
|
+
export function enforceMemoryCap(max = 50): void {
|
|
322
|
+
if (!isDbAvailable()) return;
|
|
323
|
+
const adapter = _getAdapter();
|
|
324
|
+
if (!adapter) return;
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const countRow = adapter.prepare(
|
|
328
|
+
'SELECT count(*) as cnt FROM memories WHERE superseded_by IS NULL',
|
|
329
|
+
).get();
|
|
330
|
+
const count = (countRow?.['cnt'] as number) ?? 0;
|
|
331
|
+
if (count <= max) return;
|
|
332
|
+
|
|
333
|
+
const excess = count - max;
|
|
334
|
+
// Find the IDs of the lowest-ranked active memories
|
|
335
|
+
const rows = adapter.prepare(
|
|
336
|
+
`SELECT id FROM memories
|
|
337
|
+
WHERE superseded_by IS NULL
|
|
338
|
+
ORDER BY (confidence * (1.0 + hit_count * 0.1)) ASC
|
|
339
|
+
LIMIT :limit`,
|
|
340
|
+
).all({ ':limit': excess });
|
|
341
|
+
|
|
342
|
+
const now = new Date().toISOString();
|
|
343
|
+
for (const row of rows) {
|
|
344
|
+
adapter.prepare(
|
|
345
|
+
'UPDATE memories SET superseded_by = :reason, updated_at = :now WHERE id = :id',
|
|
346
|
+
).run({ ':reason': 'CAP_EXCEEDED', ':now': now, ':id': row['id'] as string });
|
|
347
|
+
}
|
|
348
|
+
} catch {
|
|
349
|
+
// non-fatal
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ─── Action Application ─────────────────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Process an array of memory actions in a transaction.
|
|
357
|
+
* Calls enforceMemoryCap at the end.
|
|
358
|
+
*/
|
|
359
|
+
export function applyMemoryActions(
|
|
360
|
+
actions: MemoryAction[],
|
|
361
|
+
unitType?: string,
|
|
362
|
+
unitId?: string,
|
|
363
|
+
): void {
|
|
364
|
+
if (!isDbAvailable() || actions.length === 0) return;
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
transaction(() => {
|
|
368
|
+
for (const action of actions) {
|
|
369
|
+
switch (action.action) {
|
|
370
|
+
case 'CREATE':
|
|
371
|
+
createMemory({
|
|
372
|
+
category: action.category,
|
|
373
|
+
content: action.content,
|
|
374
|
+
confidence: action.confidence,
|
|
375
|
+
source_unit_type: unitType,
|
|
376
|
+
source_unit_id: unitId,
|
|
377
|
+
});
|
|
378
|
+
break;
|
|
379
|
+
case 'UPDATE':
|
|
380
|
+
updateMemoryContent(action.id, action.content, action.confidence);
|
|
381
|
+
break;
|
|
382
|
+
case 'REINFORCE':
|
|
383
|
+
reinforceMemory(action.id);
|
|
384
|
+
break;
|
|
385
|
+
case 'SUPERSEDE':
|
|
386
|
+
supersedeMemory(action.id, action.superseded_by);
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
enforceMemoryCap();
|
|
391
|
+
});
|
|
392
|
+
} catch {
|
|
393
|
+
// non-fatal — transaction will have rolled back
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ─── Prompt Formatting ──────────────────────────────────────────────────────
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Format memories as categorized markdown for system prompt injection.
|
|
401
|
+
* Truncates to token budget (~4 chars per token).
|
|
402
|
+
*/
|
|
403
|
+
export function formatMemoriesForPrompt(memories: Memory[], tokenBudget = 2000): string {
|
|
404
|
+
if (memories.length === 0) return '';
|
|
405
|
+
|
|
406
|
+
const charBudget = tokenBudget * 4;
|
|
407
|
+
const header = '## Project Memory (auto-learned)\n';
|
|
408
|
+
let output = header;
|
|
409
|
+
let remaining = charBudget - header.length;
|
|
410
|
+
|
|
411
|
+
// Group by category
|
|
412
|
+
const grouped = new Map<string, Memory[]>();
|
|
413
|
+
for (const m of memories) {
|
|
414
|
+
const list = grouped.get(m.category) ?? [];
|
|
415
|
+
list.push(m);
|
|
416
|
+
grouped.set(m.category, list);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Sort categories by priority
|
|
420
|
+
const sortedCategories = [...grouped.keys()].sort(
|
|
421
|
+
(a, b) => (CATEGORY_PRIORITY[a] ?? 99) - (CATEGORY_PRIORITY[b] ?? 99),
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
for (const category of sortedCategories) {
|
|
425
|
+
const items = grouped.get(category)!;
|
|
426
|
+
const catHeader = `\n### ${category.charAt(0).toUpperCase() + category.slice(1)}\n`;
|
|
427
|
+
|
|
428
|
+
if (remaining < catHeader.length + 10) break;
|
|
429
|
+
output += catHeader;
|
|
430
|
+
remaining -= catHeader.length;
|
|
431
|
+
|
|
432
|
+
for (const item of items) {
|
|
433
|
+
const bullet = `- ${item.content}\n`;
|
|
434
|
+
if (remaining < bullet.length) break;
|
|
435
|
+
output += bullet;
|
|
436
|
+
remaining -= bullet.length;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return output.trimEnd();
|
|
441
|
+
}
|
|
@@ -151,7 +151,7 @@ export async function handleMigrate(
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
// ── Confirmation via showNextAction ────────────────────────────────────────
|
|
154
|
-
const choice = await showNextAction(ctx
|
|
154
|
+
const choice = await showNextAction(ctx, {
|
|
155
155
|
title: "Migration preview",
|
|
156
156
|
summary: lines,
|
|
157
157
|
actions: [
|
|
@@ -187,7 +187,7 @@ export async function handleMigrate(
|
|
|
187
187
|
);
|
|
188
188
|
|
|
189
189
|
// ── Post-write review offer ────────────────────────────────────────────────
|
|
190
|
-
const reviewChoice = await showNextAction(ctx
|
|
190
|
+
const reviewChoice = await showNextAction(ctx, {
|
|
191
191
|
title: "Migration written",
|
|
192
192
|
summary: [
|
|
193
193
|
`${result.paths.length} files written to .gsd/`,
|
|
@@ -483,6 +483,45 @@ export async function writeGSDDirectory(
|
|
|
483
483
|
counts.research++;
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
+
// For fully-completed milestones (all slices done), write a pass-through
|
|
487
|
+
// validation file so deriveState() doesn't enter validating-milestone
|
|
488
|
+
// phase for historical milestones that predate the validation gate (#819).
|
|
489
|
+
const allSlicesDone = milestone.slices.length > 0 && milestone.slices.every(s => s.done);
|
|
490
|
+
if (allSlicesDone) {
|
|
491
|
+
const validationPath = join(mDir, `${milestone.id}-VALIDATION.md`);
|
|
492
|
+
const validationContent = [
|
|
493
|
+
`---`,
|
|
494
|
+
`verdict: pass`,
|
|
495
|
+
`migrated: true`,
|
|
496
|
+
`---`,
|
|
497
|
+
``,
|
|
498
|
+
`# ${milestone.id} Validation`,
|
|
499
|
+
``,
|
|
500
|
+
`Migrated milestone — all slices were completed in the original project.`,
|
|
501
|
+
``,
|
|
502
|
+
].join('\n');
|
|
503
|
+
await saveFile(validationPath, validationContent);
|
|
504
|
+
paths.push(validationPath);
|
|
505
|
+
counts.other++;
|
|
506
|
+
|
|
507
|
+
// Also write a milestone summary if one doesn't exist
|
|
508
|
+
const summaryPath = join(mDir, `${milestone.id}-SUMMARY.md`);
|
|
509
|
+
const summaryContent = [
|
|
510
|
+
`---`,
|
|
511
|
+
`status: done`,
|
|
512
|
+
`migrated: true`,
|
|
513
|
+
`---`,
|
|
514
|
+
``,
|
|
515
|
+
`# ${milestone.id}: ${milestone.title}`,
|
|
516
|
+
``,
|
|
517
|
+
`Migrated from .planning — ${milestone.slices.length} slices completed.`,
|
|
518
|
+
``,
|
|
519
|
+
].join('\n');
|
|
520
|
+
await saveFile(summaryPath, summaryContent);
|
|
521
|
+
paths.push(summaryPath);
|
|
522
|
+
counts.other++;
|
|
523
|
+
}
|
|
524
|
+
|
|
486
525
|
// Slices
|
|
487
526
|
for (const slice of milestone.slices) {
|
|
488
527
|
const sDir = join(mDir, 'slices', slice.id);
|
|
@@ -124,6 +124,12 @@ export async function startParallel(
|
|
|
124
124
|
const toStart = milestoneIds.slice(0, config.max_workers);
|
|
125
125
|
|
|
126
126
|
for (const mid of toStart) {
|
|
127
|
+
// Check budget ceiling before each spawn
|
|
128
|
+
if (isBudgetExceeded()) {
|
|
129
|
+
errors.push({ mid, error: `Budget ceiling ($${config.budget_ceiling}) reached — skipping` });
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
127
133
|
try {
|
|
128
134
|
// Create the worktree (without chdir — coordinator stays in project root)
|
|
129
135
|
let wtPath: string;
|
|
@@ -233,7 +239,7 @@ export function spawnWorker(
|
|
|
233
239
|
|
|
234
240
|
let child: ChildProcess;
|
|
235
241
|
try {
|
|
236
|
-
child = spawn(process.execPath, [binPath, "--print", "/gsd auto"], {
|
|
242
|
+
child = spawn(process.execPath, [binPath, "--mode", "json", "--print", "/gsd auto"], {
|
|
237
243
|
cwd: worker.worktreePath,
|
|
238
244
|
env: {
|
|
239
245
|
...process.env,
|
|
@@ -267,6 +273,28 @@ export function spawnWorker(
|
|
|
267
273
|
return false;
|
|
268
274
|
}
|
|
269
275
|
|
|
276
|
+
// ── NDJSON stdout monitoring ────────────────────────────────────────
|
|
277
|
+
// Workers run with --mode json, emitting one JSON event per line.
|
|
278
|
+
// We parse message_end events to extract cost/token usage, keeping
|
|
279
|
+
// the coordinator's cost tracking in sync with actual API spend.
|
|
280
|
+
if (child.stdout) {
|
|
281
|
+
let stdoutBuffer = "";
|
|
282
|
+
child.stdout.on("data", (data: Buffer) => {
|
|
283
|
+
stdoutBuffer += data.toString();
|
|
284
|
+
const lines = stdoutBuffer.split("\n");
|
|
285
|
+
stdoutBuffer = lines.pop() || "";
|
|
286
|
+
for (const line of lines) {
|
|
287
|
+
processWorkerLine(basePath, milestoneId, line);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
// Flush remaining buffer on close
|
|
291
|
+
child.stdout.on("close", () => {
|
|
292
|
+
if (stdoutBuffer.trim()) {
|
|
293
|
+
processWorkerLine(basePath, milestoneId, stdoutBuffer);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
270
298
|
// Update session status with real PID
|
|
271
299
|
writeSessionStatus(basePath, {
|
|
272
300
|
milestoneId,
|
|
@@ -343,6 +371,90 @@ function resolveGsdBin(): string | null {
|
|
|
343
371
|
return null;
|
|
344
372
|
}
|
|
345
373
|
|
|
374
|
+
// ─── NDJSON Processing ──────────────────────────────────────────────────────
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Process a single NDJSON line from a worker's stdout.
|
|
378
|
+
* Extracts cost and token usage from message_end events and updates
|
|
379
|
+
* the worker's tracking state + session status file.
|
|
380
|
+
*/
|
|
381
|
+
function processWorkerLine(basePath: string, milestoneId: string, line: string): void {
|
|
382
|
+
if (!line.trim() || !state) return;
|
|
383
|
+
|
|
384
|
+
let event: Record<string, unknown>;
|
|
385
|
+
try {
|
|
386
|
+
event = JSON.parse(line);
|
|
387
|
+
} catch {
|
|
388
|
+
return; // Not valid JSON — skip (stderr leakage, debug output, etc.)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const type = String(event.type ?? "");
|
|
392
|
+
|
|
393
|
+
// message_end carries usage data with cost
|
|
394
|
+
if (type === "message_end" && event.message) {
|
|
395
|
+
const msg = event.message as Record<string, unknown>;
|
|
396
|
+
const usage = msg.usage as Record<string, unknown> | undefined;
|
|
397
|
+
|
|
398
|
+
if (usage) {
|
|
399
|
+
const cost = (usage.cost as Record<string, unknown>)?.total;
|
|
400
|
+
if (typeof cost === "number") {
|
|
401
|
+
const worker = state.workers.get(milestoneId);
|
|
402
|
+
if (worker) {
|
|
403
|
+
worker.cost += cost;
|
|
404
|
+
// Update aggregate
|
|
405
|
+
state.totalCost = 0;
|
|
406
|
+
for (const w of state.workers.values()) {
|
|
407
|
+
state.totalCost += w.cost;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Track completed units (each message_end from assistant = progress)
|
|
414
|
+
if (msg.role === "assistant") {
|
|
415
|
+
const worker = state.workers.get(milestoneId);
|
|
416
|
+
if (worker) {
|
|
417
|
+
worker.completedUnits++;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Update session status file so dashboard sees live cost
|
|
422
|
+
const worker = state.workers.get(milestoneId);
|
|
423
|
+
if (worker) {
|
|
424
|
+
writeSessionStatus(basePath, {
|
|
425
|
+
milestoneId,
|
|
426
|
+
pid: worker.pid,
|
|
427
|
+
state: worker.state,
|
|
428
|
+
currentUnit: null,
|
|
429
|
+
completedUnits: worker.completedUnits,
|
|
430
|
+
cost: worker.cost,
|
|
431
|
+
lastHeartbeat: Date.now(),
|
|
432
|
+
startedAt: worker.startedAt,
|
|
433
|
+
worktreePath: worker.worktreePath,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// tool_execution_start can track current unit
|
|
439
|
+
if (type === "extension_ui_request" && event.method === "notify") {
|
|
440
|
+
// GSD auto-mode sends notifications about current unit
|
|
441
|
+
const worker = state.workers.get(milestoneId);
|
|
442
|
+
if (worker) {
|
|
443
|
+
writeSessionStatus(basePath, {
|
|
444
|
+
milestoneId,
|
|
445
|
+
pid: worker.pid,
|
|
446
|
+
state: worker.state,
|
|
447
|
+
currentUnit: null,
|
|
448
|
+
completedUnits: worker.completedUnits,
|
|
449
|
+
cost: worker.cost,
|
|
450
|
+
lastHeartbeat: Date.now(),
|
|
451
|
+
startedAt: worker.startedAt,
|
|
452
|
+
worktreePath: worker.worktreePath,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
346
458
|
// ─── Stop ──────────────────────────────────────────────────────────────────
|
|
347
459
|
|
|
348
460
|
/**
|
|
@@ -366,10 +478,16 @@ export async function stopParallel(
|
|
|
366
478
|
// Send stop signal via file-based IPC (worker checks on next dispatch)
|
|
367
479
|
sendSignal(basePath, mid, "stop");
|
|
368
480
|
|
|
369
|
-
//
|
|
370
|
-
|
|
481
|
+
// Send SIGTERM to the process for immediate response.
|
|
482
|
+
// Use process handle when available, fall back to PID-based kill
|
|
483
|
+
// (handles are null after coordinator restart / deserialization).
|
|
484
|
+
if (worker.pid > 0) {
|
|
371
485
|
try {
|
|
372
|
-
worker.process
|
|
486
|
+
if (worker.process) {
|
|
487
|
+
worker.process.kill("SIGTERM");
|
|
488
|
+
} else {
|
|
489
|
+
process.kill(worker.pid, "SIGTERM");
|
|
490
|
+
}
|
|
373
491
|
} catch { /* process may already be dead */ }
|
|
374
492
|
}
|
|
375
493
|
|
|
@@ -916,8 +916,9 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
916
916
|
if (p.skip_reassess !== undefined) validatedPhases.skip_reassess = !!p.skip_reassess;
|
|
917
917
|
if (p.skip_slice_research !== undefined) validatedPhases.skip_slice_research = !!p.skip_slice_research;
|
|
918
918
|
if (p.skip_milestone_validation !== undefined) validatedPhases.skip_milestone_validation = !!p.skip_milestone_validation;
|
|
919
|
+
if ((p as any).require_slice_discussion !== undefined) (validatedPhases as any).require_slice_discussion = !!(p as any).require_slice_discussion;
|
|
919
920
|
// Warn on unknown phase keys
|
|
920
|
-
const knownPhaseKeys = new Set(["skip_research", "skip_reassess", "skip_slice_research", "skip_milestone_validation"]);
|
|
921
|
+
const knownPhaseKeys = new Set(["skip_research", "skip_reassess", "skip_slice_research", "skip_milestone_validation", "require_slice_discussion"]);
|
|
921
922
|
for (const key of Object.keys(p)) {
|
|
922
923
|
if (!knownPhaseKeys.has(key)) {
|
|
923
924
|
warnings.push(`unknown phases key "${key}" — ignored`);
|
|
@@ -28,7 +28,7 @@ Then:
|
|
|
28
28
|
7. Write `{{sliceUatPath}}` — a concrete UAT script with real test cases derived from the slice plan and task summaries. Include preconditions, numbered steps with expected outcomes, and edge cases. This must NOT be a placeholder or generic template — tailor every test case to what this slice actually built.
|
|
29
29
|
8. Review task summaries for `key_decisions`. Append any significant decisions to `.gsd/DECISIONS.md` if missing.
|
|
30
30
|
9. Mark {{sliceId}} done in `{{roadmapPath}}` (change `[ ]` to `[x]`)
|
|
31
|
-
10. Do not
|
|
31
|
+
10. Do not run git commands — the system commits your changes and handles any merge after this unit succeeds.
|
|
32
32
|
11. Update `.gsd/PROJECT.md` if it exists — refresh current state if needed.
|
|
33
33
|
12. Update `.gsd/STATE.md`
|
|
34
34
|
|