gsd-pi 2.16.0 → 2.18.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 +39 -0
- package/dist/onboarding.js +2 -2
- package/dist/remote-questions-config.d.ts +10 -0
- package/dist/remote-questions-config.js +36 -0
- package/dist/resources/extensions/gsd/activity-log.ts +37 -7
- package/dist/resources/extensions/gsd/auto-dashboard.ts +4 -0
- package/dist/resources/extensions/gsd/auto-dispatch.ts +9 -3
- package/dist/resources/extensions/gsd/auto-prompts.ts +91 -42
- package/dist/resources/extensions/gsd/auto-recovery.ts +7 -2
- package/dist/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/dist/resources/extensions/gsd/auto.ts +177 -25
- package/dist/resources/extensions/gsd/commands.ts +264 -23
- package/dist/resources/extensions/gsd/complexity.ts +236 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +202 -2
- package/dist/resources/extensions/gsd/files.ts +129 -3
- package/dist/resources/extensions/gsd/git-service.ts +19 -8
- package/dist/resources/extensions/gsd/gitignore.ts +41 -2
- package/dist/resources/extensions/gsd/guided-flow.ts +247 -10
- package/dist/resources/extensions/gsd/index.ts +47 -3
- package/dist/resources/extensions/gsd/metrics.ts +44 -0
- package/dist/resources/extensions/gsd/native-git-bridge.ts +5 -0
- package/dist/resources/extensions/gsd/native-parser-bridge.ts +5 -0
- package/dist/resources/extensions/gsd/paths.ts +9 -0
- package/dist/resources/extensions/gsd/preferences.ts +181 -2
- package/dist/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/dist/resources/extensions/gsd/prompts/system.md +2 -0
- package/dist/resources/extensions/gsd/queue-order.ts +231 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
- package/dist/resources/extensions/gsd/routing-history.ts +290 -0
- package/dist/resources/extensions/gsd/state.ts +15 -3
- package/dist/resources/extensions/gsd/templates/knowledge.md +19 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +14 -0
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +50 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/dist/resources/extensions/gsd/tests/budget-prediction.test.ts +220 -0
- package/dist/resources/extensions/gsd/tests/complexity-routing.test.ts +294 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +180 -0
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +132 -0
- package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
- package/dist/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
- package/dist/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +28 -0
- package/dist/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
- package/dist/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
- package/dist/resources/extensions/gsd/tests/routing-history.test.ts +87 -0
- package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +130 -0
- package/dist/resources/extensions/gsd/tests/token-profile.test.ts +263 -0
- package/dist/resources/extensions/gsd/types.ts +28 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/dist/resources/extensions/gsd/worktree.ts +24 -2
- package/dist/resources/extensions/shared/next-action-ui.ts +16 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +493 -13
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +422 -62
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.d.ts +12 -0
- package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.js +9 -22
- package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/google-shared.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/google-shared.test.js +125 -0
- package/packages/pi-ai/dist/providers/google-shared.test.js.map +1 -0
- package/packages/pi-ai/src/models.generated.ts +422 -62
- package/packages/pi-ai/src/providers/google-shared.test.ts +137 -0
- package/packages/pi-ai/src/providers/google-shared.ts +10 -19
- package/packages/pi-coding-agent/dist/cli/args.d.ts +5 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +21 -0
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/cli/list-models.d.ts +14 -3
- package/packages/pi-coding-agent/dist/cli/list-models.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/list-models.js +52 -17
- package/packages/pi-coding-agent/dist/cli/list-models.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts +27 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.js +79 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +140 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.js +162 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js +100 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +113 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +26 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +98 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts +62 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.js +145 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.js +118 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -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 +11 -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/tools/edit-diff.d.ts +7 -7
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +209 -13
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +67 -0
- package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts +5 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +4 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +17 -2
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +25 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +121 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -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 +32 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -0
- package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +21 -0
- package/packages/pi-coding-agent/src/cli/list-models.ts +70 -17
- package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +170 -0
- package/packages/pi-coding-agent/src/core/discovery-cache.ts +97 -0
- package/packages/pi-coding-agent/src/core/model-discovery.test.ts +125 -0
- package/packages/pi-coding-agent/src/core/model-discovery.ts +231 -0
- package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +135 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +107 -0
- package/packages/pi-coding-agent/src/core/models-json-writer.test.ts +145 -0
- package/packages/pi-coding-agent/src/core/models-json-writer.ts +188 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +21 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +85 -0
- package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +245 -17
- package/packages/pi-coding-agent/src/index.ts +5 -0
- package/packages/pi-coding-agent/src/main.ts +19 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +163 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +37 -0
- package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +13 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/pkg/dist/modes/interactive/theme/theme.js +10 -0
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
- package/src/resources/extensions/gsd/activity-log.ts +37 -7
- package/src/resources/extensions/gsd/auto-dashboard.ts +4 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +9 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +91 -42
- package/src/resources/extensions/gsd/auto-recovery.ts +7 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/src/resources/extensions/gsd/auto.ts +177 -25
- package/src/resources/extensions/gsd/commands.ts +264 -23
- package/src/resources/extensions/gsd/complexity.ts +236 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/src/resources/extensions/gsd/docs/preferences-reference.md +202 -2
- package/src/resources/extensions/gsd/files.ts +129 -3
- package/src/resources/extensions/gsd/git-service.ts +19 -8
- package/src/resources/extensions/gsd/gitignore.ts +41 -2
- package/src/resources/extensions/gsd/guided-flow.ts +247 -10
- package/src/resources/extensions/gsd/index.ts +47 -3
- package/src/resources/extensions/gsd/metrics.ts +44 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +5 -0
- package/src/resources/extensions/gsd/native-parser-bridge.ts +5 -0
- package/src/resources/extensions/gsd/paths.ts +9 -0
- package/src/resources/extensions/gsd/preferences.ts +181 -2
- package/src/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/src/resources/extensions/gsd/prompts/system.md +2 -0
- package/src/resources/extensions/gsd/queue-order.ts +231 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
- package/src/resources/extensions/gsd/routing-history.ts +290 -0
- package/src/resources/extensions/gsd/state.ts +15 -3
- package/src/resources/extensions/gsd/templates/knowledge.md +19 -0
- package/src/resources/extensions/gsd/templates/preferences.md +14 -0
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/budget-prediction.test.ts +220 -0
- package/src/resources/extensions/gsd/tests/complexity-routing.test.ts +294 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +132 -0
- package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
- package/src/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
- package/src/resources/extensions/gsd/tests/routing-history.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +130 -0
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +263 -0
- package/src/resources/extensions/gsd/types.ts +28 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/src/resources/extensions/gsd/worktree.ts +24 -2
- package/src/resources/extensions/shared/next-action-ui.ts +16 -1
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Queue Reorder UI
|
|
3
|
+
*
|
|
4
|
+
* Interactive TUI overlay for reordering pending milestones.
|
|
5
|
+
* ↑/↓ navigates cursor. Space grabs/releases item for moving.
|
|
6
|
+
* While grabbed, ↑/↓ swaps the item with its neighbor.
|
|
7
|
+
* Enter confirms all changes. Esc cancels.
|
|
8
|
+
* Conflicting depends_on entries are auto-removed on confirm.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ExtensionContext } from "@gsd/pi-coding-agent";
|
|
12
|
+
import { type Theme } from "@gsd/pi-coding-agent";
|
|
13
|
+
import { Key, matchesKey, truncateToWidth, type TUI } from "@gsd/pi-tui";
|
|
14
|
+
import { makeUI, GLYPH } from "../shared/ui.js";
|
|
15
|
+
import { validateQueueOrder, type DependencyValidation } from "./queue-order.js";
|
|
16
|
+
|
|
17
|
+
export interface ReorderItem {
|
|
18
|
+
id: string;
|
|
19
|
+
title: string;
|
|
20
|
+
dependsOn?: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ReorderResult {
|
|
24
|
+
order: string[];
|
|
25
|
+
/** depends_on entries to remove from CONTEXT.md files */
|
|
26
|
+
depsToRemove: Array<{ milestone: string; dep: string }>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Show the queue reorder overlay.
|
|
31
|
+
* Returns the new order + deps to remove, or null if cancelled.
|
|
32
|
+
*/
|
|
33
|
+
export async function showQueueReorder(
|
|
34
|
+
ctx: ExtensionContext,
|
|
35
|
+
completed: ReorderItem[],
|
|
36
|
+
pending: ReorderItem[],
|
|
37
|
+
): Promise<ReorderResult | null> {
|
|
38
|
+
if (!ctx.hasUI) return null;
|
|
39
|
+
if (pending.length < 2) return null;
|
|
40
|
+
|
|
41
|
+
return ctx.ui.custom<ReorderResult | null>((tui: TUI, theme: Theme, _kb, done) => {
|
|
42
|
+
const items = [...pending];
|
|
43
|
+
let cursor = 0;
|
|
44
|
+
let grabbed = false;
|
|
45
|
+
let cachedLines: string[] | undefined;
|
|
46
|
+
let validation: DependencyValidation;
|
|
47
|
+
|
|
48
|
+
// Mutable deps map — tracks removals during this session
|
|
49
|
+
const liveDeps = new Map<string, string[]>();
|
|
50
|
+
for (const item of [...completed, ...pending]) {
|
|
51
|
+
if (item.dependsOn && item.dependsOn.length > 0) {
|
|
52
|
+
liveDeps.set(item.id, [...item.dependsOn]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const removedDeps: Array<{ milestone: string; dep: string }> = [];
|
|
57
|
+
const completedIds = new Set(completed.map(c => c.id));
|
|
58
|
+
|
|
59
|
+
function revalidate() {
|
|
60
|
+
validation = validateQueueOrder(items.map(i => i.id), liveDeps, completedIds);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
revalidate();
|
|
64
|
+
|
|
65
|
+
function refresh() {
|
|
66
|
+
cachedLines = undefined;
|
|
67
|
+
tui.requestRender();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function swapItems(fromIdx: number, toIdx: number) {
|
|
71
|
+
if (toIdx < 0 || toIdx >= items.length) return;
|
|
72
|
+
const [item] = items.splice(fromIdx, 1);
|
|
73
|
+
items.splice(toIdx, 0, item);
|
|
74
|
+
cursor = toIdx;
|
|
75
|
+
revalidate();
|
|
76
|
+
refresh();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function removeDep(milestone: string, dep: string) {
|
|
80
|
+
const deps = liveDeps.get(milestone);
|
|
81
|
+
if (!deps) return;
|
|
82
|
+
const idx = deps.indexOf(dep);
|
|
83
|
+
if (idx >= 0) {
|
|
84
|
+
deps.splice(idx, 1);
|
|
85
|
+
if (deps.length === 0) liveDeps.delete(milestone);
|
|
86
|
+
removedDeps.push({ milestone, dep });
|
|
87
|
+
const item = items.find(i => i.id === milestone);
|
|
88
|
+
if (item?.dependsOn) {
|
|
89
|
+
item.dependsOn = item.dependsOn.filter(d => d !== dep);
|
|
90
|
+
}
|
|
91
|
+
revalidate();
|
|
92
|
+
refresh();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function handleInput(data: string) {
|
|
97
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
|
|
98
|
+
done(null);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Confirm — auto-resolve would_block violations
|
|
103
|
+
if (matchesKey(data, Key.enter)) {
|
|
104
|
+
const wouldBlock = validation.violations.filter(v => v.type === 'would_block');
|
|
105
|
+
for (const v of wouldBlock) {
|
|
106
|
+
removeDep(v.milestone, v.dependsOn);
|
|
107
|
+
}
|
|
108
|
+
done({ order: items.map(i => i.id), depsToRemove: removedDeps });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Space — toggle grab mode
|
|
113
|
+
if (data === " ") {
|
|
114
|
+
grabbed = !grabbed;
|
|
115
|
+
refresh();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ↑/↓ — move grabbed item OR navigate cursor
|
|
120
|
+
if (matchesKey(data, Key.up)) {
|
|
121
|
+
if (grabbed) {
|
|
122
|
+
swapItems(cursor, cursor - 1);
|
|
123
|
+
} else {
|
|
124
|
+
cursor = Math.max(0, cursor - 1);
|
|
125
|
+
refresh();
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (matchesKey(data, Key.down)) {
|
|
130
|
+
if (grabbed) {
|
|
131
|
+
swapItems(cursor, cursor + 1);
|
|
132
|
+
} else {
|
|
133
|
+
cursor = Math.min(items.length - 1, cursor + 1);
|
|
134
|
+
refresh();
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 'd' — manually remove a dep on the cursor item
|
|
140
|
+
if (data === "d" || data === "D") {
|
|
141
|
+
const item = items[cursor];
|
|
142
|
+
const deps = liveDeps.get(item.id);
|
|
143
|
+
if (deps) {
|
|
144
|
+
const activeDep = deps.find(d => !completedIds.has(d));
|
|
145
|
+
if (activeDep) removeDep(item.id, activeDep);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function render(width: number): string[] {
|
|
152
|
+
if (cachedLines) return cachedLines;
|
|
153
|
+
|
|
154
|
+
const ui = makeUI(theme, width);
|
|
155
|
+
const lines: string[] = [];
|
|
156
|
+
const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
|
|
157
|
+
const add = (s: string) => truncateToWidth(s, width);
|
|
158
|
+
|
|
159
|
+
const headerText = grabbed ? " Queue Reorder — Moving Item" : " Queue Reorder";
|
|
160
|
+
push(ui.bar(), ui.blank(), ui.header(headerText), ui.blank());
|
|
161
|
+
|
|
162
|
+
// Completed milestones (dimmed)
|
|
163
|
+
if (completed.length > 0) {
|
|
164
|
+
lines.push(add(theme.fg("dim", " Completed:")));
|
|
165
|
+
for (const m of completed) {
|
|
166
|
+
const label = m.title && m.title !== m.id ? `${m.id} ${m.title}` : m.id;
|
|
167
|
+
lines.push(add(` ${theme.fg("dim", `${GLYPH.statusDone} ${label}`)}`));
|
|
168
|
+
}
|
|
169
|
+
push(ui.blank());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Pending milestones
|
|
173
|
+
const queueLabel = grabbed ? " Queue (space to release, ↑/↓ to move):" : " Queue (space to grab, ↑/↓ to navigate):";
|
|
174
|
+
lines.push(add(theme.fg("text", queueLabel)));
|
|
175
|
+
|
|
176
|
+
const violatedPairs = new Set(
|
|
177
|
+
validation.violations.filter(v => v.type === 'would_block').map(v => `${v.milestone}:${v.dependsOn}`),
|
|
178
|
+
);
|
|
179
|
+
const redundantPairs = new Set(
|
|
180
|
+
validation.redundant.map(r => `${r.milestone}:${r.dependsOn}`),
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
for (let i = 0; i < items.length; i++) {
|
|
184
|
+
const item = items[i];
|
|
185
|
+
const isCursor = i === cursor;
|
|
186
|
+
const num = i + 1;
|
|
187
|
+
const label = item.title && item.title !== item.id ? `${item.id} ${item.title}` : item.id;
|
|
188
|
+
|
|
189
|
+
if (isCursor && grabbed) {
|
|
190
|
+
lines.push(add(` ${theme.fg("warning", `▸▸ ${num}. ${label}`)}`));
|
|
191
|
+
} else if (isCursor) {
|
|
192
|
+
lines.push(add(` ${theme.fg("accent", `${GLYPH.cursor} ${num}. ${label}`)}`));
|
|
193
|
+
} else {
|
|
194
|
+
lines.push(add(` ${theme.fg("text", `${num}. ${label}`)}`));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// depends_on annotations
|
|
198
|
+
const deps = liveDeps.get(item.id) ?? [];
|
|
199
|
+
for (const dep of deps) {
|
|
200
|
+
if (completedIds.has(dep)) continue;
|
|
201
|
+
const pairKey = `${item.id}:${dep}`;
|
|
202
|
+
if (violatedPairs.has(pairKey)) {
|
|
203
|
+
lines.push(add(` ${theme.fg("warning", `${GLYPH.statusWarning} depends_on: ${dep} — auto-removed on confirm`)}`));
|
|
204
|
+
} else if (redundantPairs.has(pairKey)) {
|
|
205
|
+
lines.push(add(` ${theme.fg("dim", `↳ depends_on: ${dep} (redundant)`)}`));
|
|
206
|
+
} else {
|
|
207
|
+
lines.push(add(` ${theme.fg("dim", `↳ depends_on: ${dep}`)}`));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Missing deps
|
|
212
|
+
for (const v of validation.violations.filter(v => v.milestone === item.id && v.type === 'missing_dep')) {
|
|
213
|
+
lines.push(add(` ${theme.fg("error", `${GLYPH.statusWarning} depends_on: ${v.dependsOn} (does not exist)`)}`));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Removed deps feedback
|
|
218
|
+
if (removedDeps.length > 0) {
|
|
219
|
+
push(ui.blank());
|
|
220
|
+
for (const r of removedDeps) {
|
|
221
|
+
lines.push(add(` ${theme.fg("success", `${GLYPH.statusDone} Removed: ${r.milestone} depends_on ${r.dep}`)}`));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Circular warning
|
|
226
|
+
const circ = validation.violations.find(v => v.type === 'circular');
|
|
227
|
+
if (circ) {
|
|
228
|
+
push(ui.blank());
|
|
229
|
+
lines.push(add(` ${theme.fg("error", `${GLYPH.statusWarning} ${circ.message}`)}`));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
push(ui.blank());
|
|
233
|
+
|
|
234
|
+
// Hints — context-sensitive based on grab state
|
|
235
|
+
const hints: string[] = [];
|
|
236
|
+
if (grabbed) {
|
|
237
|
+
hints.push("↑/↓ move item", "space release");
|
|
238
|
+
} else {
|
|
239
|
+
hints.push("↑/↓ navigate", "space grab");
|
|
240
|
+
}
|
|
241
|
+
const hasDeps = liveDeps.get(items[cursor]?.id)?.some(d => !completedIds.has(d));
|
|
242
|
+
if (hasDeps) hints.push("d del dep");
|
|
243
|
+
|
|
244
|
+
const wouldBlockCount = validation.violations.filter(v => v.type === 'would_block').length;
|
|
245
|
+
if (wouldBlockCount > 0) {
|
|
246
|
+
hints.push(`enter (fixes ${wouldBlockCount} dep)`);
|
|
247
|
+
} else {
|
|
248
|
+
hints.push("enter ok");
|
|
249
|
+
}
|
|
250
|
+
hints.push("esc");
|
|
251
|
+
|
|
252
|
+
push(ui.hints(hints), ui.bar());
|
|
253
|
+
|
|
254
|
+
cachedLines = lines;
|
|
255
|
+
return lines;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { render, invalidate: () => { cachedLines = undefined; }, handleInput };
|
|
259
|
+
}, {
|
|
260
|
+
overlay: true,
|
|
261
|
+
overlayOptions: { width: "70%", minWidth: 50, maxHeight: "80%", anchor: "center" },
|
|
262
|
+
});
|
|
263
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// GSD Extension — Routing History (Adaptive Learning)
|
|
2
|
+
// Tracks success/failure per tier per unit-type pattern to improve
|
|
3
|
+
// classification accuracy over time.
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { gsdRoot } from "./paths.js";
|
|
8
|
+
import type { ComplexityTier } from "./types.js";
|
|
9
|
+
|
|
10
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export interface TierOutcome {
|
|
13
|
+
success: number;
|
|
14
|
+
fail: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PatternHistory {
|
|
18
|
+
light: TierOutcome;
|
|
19
|
+
standard: TierOutcome;
|
|
20
|
+
heavy: TierOutcome;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface RoutingHistoryData {
|
|
24
|
+
version: 1;
|
|
25
|
+
/** Keyed by pattern string, e.g. "execute-task:docs" or "complete-slice" */
|
|
26
|
+
patterns: Record<string, PatternHistory>;
|
|
27
|
+
/** User feedback entries (from /gsd:rate-unit) */
|
|
28
|
+
feedback: FeedbackEntry[];
|
|
29
|
+
/** Last updated timestamp */
|
|
30
|
+
updatedAt: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface FeedbackEntry {
|
|
34
|
+
unitType: string;
|
|
35
|
+
unitId: string;
|
|
36
|
+
tier: ComplexityTier;
|
|
37
|
+
rating: "over" | "under" | "ok";
|
|
38
|
+
timestamp: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
const HISTORY_FILE = "routing-history.json";
|
|
44
|
+
const ROLLING_WINDOW = 50; // only consider last N entries per pattern
|
|
45
|
+
const FAILURE_THRESHOLD = 0.20; // >20% failure rate triggers tier bump
|
|
46
|
+
const FEEDBACK_WEIGHT = 2; // feedback signals count 2x vs automatic
|
|
47
|
+
|
|
48
|
+
// ─── In-Memory State ─────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
let history: RoutingHistoryData | null = null;
|
|
51
|
+
let historyBasePath = "";
|
|
52
|
+
|
|
53
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Initialize routing history for a project.
|
|
57
|
+
*/
|
|
58
|
+
export function initRoutingHistory(base: string): void {
|
|
59
|
+
historyBasePath = base;
|
|
60
|
+
history = loadHistory(base);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Reset routing history state.
|
|
65
|
+
*/
|
|
66
|
+
export function resetRoutingHistory(): void {
|
|
67
|
+
history = null;
|
|
68
|
+
historyBasePath = "";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Record the outcome of a unit dispatch.
|
|
73
|
+
*
|
|
74
|
+
* @param unitType The unit type (e.g. "execute-task")
|
|
75
|
+
* @param tier The tier that was used
|
|
76
|
+
* @param success Whether the unit completed successfully
|
|
77
|
+
* @param tags Optional tags from task metadata (e.g. ["docs", "test"])
|
|
78
|
+
*/
|
|
79
|
+
export function recordOutcome(
|
|
80
|
+
unitType: string,
|
|
81
|
+
tier: ComplexityTier,
|
|
82
|
+
success: boolean,
|
|
83
|
+
tags?: string[],
|
|
84
|
+
): void {
|
|
85
|
+
if (!history) return;
|
|
86
|
+
|
|
87
|
+
// Record for the base unit type
|
|
88
|
+
const basePattern = unitType;
|
|
89
|
+
ensurePattern(basePattern);
|
|
90
|
+
const outcome = history.patterns[basePattern][tier];
|
|
91
|
+
if (success) outcome.success++;
|
|
92
|
+
else outcome.fail++;
|
|
93
|
+
|
|
94
|
+
// Record for tag-specific patterns (e.g. "execute-task:docs")
|
|
95
|
+
if (tags && tags.length > 0) {
|
|
96
|
+
for (const tag of tags) {
|
|
97
|
+
const tagPattern = `${unitType}:${tag}`;
|
|
98
|
+
ensurePattern(tagPattern);
|
|
99
|
+
const tagOutcome = history.patterns[tagPattern][tier];
|
|
100
|
+
if (success) tagOutcome.success++;
|
|
101
|
+
else tagOutcome.fail++;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Apply rolling window — cap total entries per tier per pattern
|
|
106
|
+
for (const pattern of Object.keys(history.patterns)) {
|
|
107
|
+
const p = history.patterns[pattern];
|
|
108
|
+
for (const t of ["light", "standard", "heavy"] as const) {
|
|
109
|
+
const total = p[t].success + p[t].fail;
|
|
110
|
+
if (total > ROLLING_WINDOW) {
|
|
111
|
+
const scale = ROLLING_WINDOW / total;
|
|
112
|
+
p[t].success = Math.round(p[t].success * scale);
|
|
113
|
+
p[t].fail = Math.round(p[t].fail * scale);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
history.updatedAt = new Date().toISOString();
|
|
119
|
+
saveHistory(historyBasePath, history);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Record user feedback for the last completed unit.
|
|
124
|
+
*/
|
|
125
|
+
export function recordFeedback(
|
|
126
|
+
unitType: string,
|
|
127
|
+
unitId: string,
|
|
128
|
+
tier: ComplexityTier,
|
|
129
|
+
rating: "over" | "under" | "ok",
|
|
130
|
+
): void {
|
|
131
|
+
if (!history) return;
|
|
132
|
+
|
|
133
|
+
history.feedback.push({
|
|
134
|
+
unitType,
|
|
135
|
+
unitId,
|
|
136
|
+
tier,
|
|
137
|
+
rating,
|
|
138
|
+
timestamp: new Date().toISOString(),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Cap feedback array at 200 entries
|
|
142
|
+
if (history.feedback.length > 200) {
|
|
143
|
+
history.feedback = history.feedback.slice(-200);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Apply feedback as weighted outcome
|
|
147
|
+
const pattern = unitType;
|
|
148
|
+
ensurePattern(pattern);
|
|
149
|
+
|
|
150
|
+
if (rating === "over") {
|
|
151
|
+
// User says this could have used a simpler model → record as success at current tier
|
|
152
|
+
// and also as success at one tier lower (encourages more downgrading)
|
|
153
|
+
const lower = tierBelow(tier);
|
|
154
|
+
if (lower) {
|
|
155
|
+
const outcomes = history.patterns[pattern][lower];
|
|
156
|
+
outcomes.success += FEEDBACK_WEIGHT;
|
|
157
|
+
}
|
|
158
|
+
} else if (rating === "under") {
|
|
159
|
+
// User says this needed a better model → record as failure at current tier
|
|
160
|
+
const outcomes = history.patterns[pattern][tier];
|
|
161
|
+
outcomes.fail += FEEDBACK_WEIGHT;
|
|
162
|
+
}
|
|
163
|
+
// "ok" = no adjustment needed
|
|
164
|
+
|
|
165
|
+
history.updatedAt = new Date().toISOString();
|
|
166
|
+
saveHistory(historyBasePath, history);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get the recommended tier adjustment for a given pattern.
|
|
171
|
+
* Returns the tier to bump to if the failure rate exceeds threshold,
|
|
172
|
+
* or null if no adjustment is needed.
|
|
173
|
+
*/
|
|
174
|
+
export function getAdaptiveTierAdjustment(
|
|
175
|
+
unitType: string,
|
|
176
|
+
currentTier: ComplexityTier,
|
|
177
|
+
tags?: string[],
|
|
178
|
+
): ComplexityTier | null {
|
|
179
|
+
if (!history) return null;
|
|
180
|
+
|
|
181
|
+
// Check tag-specific patterns first (more specific)
|
|
182
|
+
if (tags && tags.length > 0) {
|
|
183
|
+
for (const tag of tags) {
|
|
184
|
+
const tagPattern = `${unitType}:${tag}`;
|
|
185
|
+
const adjustment = checkPatternFailureRate(tagPattern, currentTier);
|
|
186
|
+
if (adjustment) return adjustment;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Fall back to base pattern
|
|
191
|
+
return checkPatternFailureRate(unitType, currentTier);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Clear all routing history (user-triggered reset).
|
|
196
|
+
*/
|
|
197
|
+
export function clearRoutingHistory(base: string): void {
|
|
198
|
+
history = createEmptyHistory();
|
|
199
|
+
saveHistory(base, history);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get current history data (for display/debugging).
|
|
204
|
+
*/
|
|
205
|
+
export function getRoutingHistory(): RoutingHistoryData | null {
|
|
206
|
+
return history;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ─── Internal ────────────────────────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
function checkPatternFailureRate(
|
|
212
|
+
pattern: string,
|
|
213
|
+
tier: ComplexityTier,
|
|
214
|
+
): ComplexityTier | null {
|
|
215
|
+
if (!history?.patterns[pattern]) return null;
|
|
216
|
+
|
|
217
|
+
const outcomes = history.patterns[pattern][tier];
|
|
218
|
+
const total = outcomes.success + outcomes.fail;
|
|
219
|
+
if (total < 3) return null; // Not enough data
|
|
220
|
+
|
|
221
|
+
const failureRate = outcomes.fail / total;
|
|
222
|
+
if (failureRate > FAILURE_THRESHOLD) {
|
|
223
|
+
// Bump to next tier
|
|
224
|
+
return tierAbove(tier);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function tierAbove(tier: ComplexityTier): ComplexityTier | null {
|
|
231
|
+
switch (tier) {
|
|
232
|
+
case "light": return "standard";
|
|
233
|
+
case "standard": return "heavy";
|
|
234
|
+
case "heavy": return null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function tierBelow(tier: ComplexityTier): ComplexityTier | null {
|
|
239
|
+
switch (tier) {
|
|
240
|
+
case "light": return null;
|
|
241
|
+
case "standard": return "light";
|
|
242
|
+
case "heavy": return "standard";
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function ensurePattern(pattern: string): void {
|
|
247
|
+
if (!history) return;
|
|
248
|
+
if (!history.patterns[pattern]) {
|
|
249
|
+
history.patterns[pattern] = {
|
|
250
|
+
light: { success: 0, fail: 0 },
|
|
251
|
+
standard: { success: 0, fail: 0 },
|
|
252
|
+
heavy: { success: 0, fail: 0 },
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function createEmptyHistory(): RoutingHistoryData {
|
|
258
|
+
return {
|
|
259
|
+
version: 1,
|
|
260
|
+
patterns: {},
|
|
261
|
+
feedback: [],
|
|
262
|
+
updatedAt: new Date().toISOString(),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function historyPath(base: string): string {
|
|
267
|
+
return join(gsdRoot(base), HISTORY_FILE);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function loadHistory(base: string): RoutingHistoryData {
|
|
271
|
+
try {
|
|
272
|
+
const raw = readFileSync(historyPath(base), "utf-8");
|
|
273
|
+
const parsed = JSON.parse(raw);
|
|
274
|
+
if (parsed.version === 1 && parsed.patterns) {
|
|
275
|
+
return parsed as RoutingHistoryData;
|
|
276
|
+
}
|
|
277
|
+
} catch {
|
|
278
|
+
// File doesn't exist or is corrupt — start fresh
|
|
279
|
+
}
|
|
280
|
+
return createEmptyHistory();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function saveHistory(base: string, data: RoutingHistoryData): void {
|
|
284
|
+
try {
|
|
285
|
+
mkdirSync(gsdRoot(base), { recursive: true });
|
|
286
|
+
writeFileSync(historyPath(base), JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
287
|
+
} catch {
|
|
288
|
+
// Non-fatal — don't let history failures break auto-mode
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -224,9 +224,21 @@ async function _deriveStateImpl(basePath: string): Promise<GSDState> {
|
|
|
224
224
|
const draftFile = resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT");
|
|
225
225
|
if (draftFile) activeMilestoneHasDraft = true;
|
|
226
226
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
|
|
228
|
+
// Check milestone-level dependencies before promoting to active.
|
|
229
|
+
// Without this, a queued milestone with depends_on in its CONTEXT
|
|
230
|
+
// frontmatter would be promoted to active even when its deps are unmet
|
|
231
|
+
// (the dep check only existed in the has-roadmap path previously).
|
|
232
|
+
const contextContent = contextFile ? await cachedLoadFile(contextFile) : null;
|
|
233
|
+
const deps = parseContextDependsOn(contextContent);
|
|
234
|
+
const depsUnmet = deps.some(dep => !completeMilestoneIds.has(dep));
|
|
235
|
+
if (depsUnmet) {
|
|
236
|
+
registry.push({ id: mid, title: mid, status: 'pending', dependsOn: deps });
|
|
237
|
+
} else {
|
|
238
|
+
activeMilestone = { id: mid, title: mid };
|
|
239
|
+
activeMilestoneFound = true;
|
|
240
|
+
registry.push({ id: mid, title: mid, status: 'active', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
|
|
241
|
+
}
|
|
230
242
|
} else {
|
|
231
243
|
registry.push({ id: mid, title: mid, status: 'pending' });
|
|
232
244
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Project Knowledge
|
|
2
|
+
|
|
3
|
+
Append-only register of project-specific rules, patterns, and lessons learned.
|
|
4
|
+
Agents read this before every unit. Add entries when you discover something worth remembering.
|
|
5
|
+
|
|
6
|
+
## Rules
|
|
7
|
+
|
|
8
|
+
| # | Scope | Rule | Why | Added |
|
|
9
|
+
|---|-------|------|-----|-------|
|
|
10
|
+
|
|
11
|
+
## Patterns
|
|
12
|
+
|
|
13
|
+
| # | Pattern | Where | Notes |
|
|
14
|
+
|---|---------|-------|-------|
|
|
15
|
+
|
|
16
|
+
## Lessons Learned
|
|
17
|
+
|
|
18
|
+
| # | What Happened | Root Cause | Fix | Scope |
|
|
19
|
+
|---|--------------|------------|-----|-------|
|
|
@@ -15,7 +15,21 @@ git:
|
|
|
15
15
|
snapshots:
|
|
16
16
|
pre_merge_check:
|
|
17
17
|
commit_type:
|
|
18
|
+
main_branch:
|
|
19
|
+
merge_strategy:
|
|
20
|
+
isolation:
|
|
18
21
|
unique_milestone_ids:
|
|
22
|
+
budget_ceiling:
|
|
23
|
+
budget_enforcement:
|
|
24
|
+
context_pause_threshold:
|
|
25
|
+
notifications:
|
|
26
|
+
enabled:
|
|
27
|
+
on_complete:
|
|
28
|
+
on_error:
|
|
29
|
+
on_budget:
|
|
30
|
+
on_milestone:
|
|
31
|
+
on_attention:
|
|
32
|
+
uat_dispatch:
|
|
19
33
|
---
|
|
20
34
|
|
|
21
35
|
# GSD Skill Preferences
|
|
@@ -7,6 +7,7 @@ import { randomUUID } from "node:crypto";
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
resolveExpectedArtifactPath,
|
|
10
|
+
verifyExpectedArtifact,
|
|
10
11
|
diagnoseExpectedArtifact,
|
|
11
12
|
buildLoopRemediationSteps,
|
|
12
13
|
completedKeysPath,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
removePersistedKey,
|
|
15
16
|
loadPersistedKeys,
|
|
16
17
|
} from "../auto-recovery.ts";
|
|
18
|
+
import { parseRoadmap, clearParseCache } from "../files.ts";
|
|
17
19
|
|
|
18
20
|
function makeTmpBase(): string {
|
|
19
21
|
const base = join(tmpdir(), `gsd-test-${randomUUID()}`);
|
|
@@ -270,3 +272,51 @@ test("removePersistedKey is safe when file doesn't exist", () => {
|
|
|
270
272
|
cleanup(base);
|
|
271
273
|
}
|
|
272
274
|
});
|
|
275
|
+
|
|
276
|
+
// ─── verifyExpectedArtifact: parse cache collision regression ─────────────
|
|
277
|
+
|
|
278
|
+
test("verifyExpectedArtifact detects roadmap [x] change despite parse cache", () => {
|
|
279
|
+
// Regression test: cacheKey collision when [ ] → [x] doesn't change
|
|
280
|
+
// file length or first/last 100 chars. Without the fix, parseRoadmap
|
|
281
|
+
// returns stale cached data with done=false even though the file has [x].
|
|
282
|
+
const base = makeTmpBase();
|
|
283
|
+
try {
|
|
284
|
+
// Build a roadmap long enough that the [x] change is outside the first/last 100 chars
|
|
285
|
+
const padding = "A".repeat(200);
|
|
286
|
+
const roadmapBefore = [
|
|
287
|
+
`# M001: Test Milestone ${padding}`,
|
|
288
|
+
"",
|
|
289
|
+
"## Slices",
|
|
290
|
+
"",
|
|
291
|
+
"- [ ] **S01: First slice** `risk:low`",
|
|
292
|
+
"",
|
|
293
|
+
`## Footer ${padding}`,
|
|
294
|
+
].join("\n");
|
|
295
|
+
const roadmapAfter = roadmapBefore.replace("- [ ] **S01:", "- [x] **S01:");
|
|
296
|
+
|
|
297
|
+
// Verify lengths are identical (the key collision condition)
|
|
298
|
+
assert.equal(roadmapBefore.length, roadmapAfter.length);
|
|
299
|
+
|
|
300
|
+
// Populate parse cache with the pre-edit roadmap
|
|
301
|
+
const before = parseRoadmap(roadmapBefore);
|
|
302
|
+
const sliceBefore = before.slices.find(s => s.id === "S01");
|
|
303
|
+
assert.ok(sliceBefore);
|
|
304
|
+
assert.equal(sliceBefore!.done, false);
|
|
305
|
+
|
|
306
|
+
// Now write the post-edit roadmap to disk and create required artifacts
|
|
307
|
+
const roadmapPath = join(base, ".gsd", "milestones", "M001", "M001-ROADMAP.md");
|
|
308
|
+
writeFileSync(roadmapPath, roadmapAfter);
|
|
309
|
+
const summaryPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-SUMMARY.md");
|
|
310
|
+
writeFileSync(summaryPath, "# Summary\nDone.");
|
|
311
|
+
const uatPath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-UAT.md");
|
|
312
|
+
writeFileSync(uatPath, "# UAT\nPassed.");
|
|
313
|
+
|
|
314
|
+
// verifyExpectedArtifact should see the [x] despite the parse cache
|
|
315
|
+
// having the [ ] version. The fix clears the parse cache inside verify.
|
|
316
|
+
const verified = verifyExpectedArtifact("complete-slice", "M001/S01", base);
|
|
317
|
+
assert.equal(verified, true, "verifyExpectedArtifact should return true when roadmap has [x]");
|
|
318
|
+
} finally {
|
|
319
|
+
clearParseCache();
|
|
320
|
+
cleanup(base);
|
|
321
|
+
}
|
|
322
|
+
});
|