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.
Files changed (225) hide show
  1. package/README.md +39 -0
  2. package/dist/onboarding.js +2 -2
  3. package/dist/remote-questions-config.d.ts +10 -0
  4. package/dist/remote-questions-config.js +36 -0
  5. package/dist/resources/extensions/gsd/activity-log.ts +37 -7
  6. package/dist/resources/extensions/gsd/auto-dashboard.ts +4 -0
  7. package/dist/resources/extensions/gsd/auto-dispatch.ts +9 -3
  8. package/dist/resources/extensions/gsd/auto-prompts.ts +91 -42
  9. package/dist/resources/extensions/gsd/auto-recovery.ts +7 -2
  10. package/dist/resources/extensions/gsd/auto-worktree.ts +33 -4
  11. package/dist/resources/extensions/gsd/auto.ts +177 -25
  12. package/dist/resources/extensions/gsd/commands.ts +264 -23
  13. package/dist/resources/extensions/gsd/complexity.ts +236 -0
  14. package/dist/resources/extensions/gsd/dispatch-guard.ts +7 -19
  15. package/dist/resources/extensions/gsd/docs/preferences-reference.md +202 -2
  16. package/dist/resources/extensions/gsd/files.ts +129 -3
  17. package/dist/resources/extensions/gsd/git-service.ts +19 -8
  18. package/dist/resources/extensions/gsd/gitignore.ts +41 -2
  19. package/dist/resources/extensions/gsd/guided-flow.ts +247 -10
  20. package/dist/resources/extensions/gsd/index.ts +47 -3
  21. package/dist/resources/extensions/gsd/metrics.ts +44 -0
  22. package/dist/resources/extensions/gsd/native-git-bridge.ts +5 -0
  23. package/dist/resources/extensions/gsd/native-parser-bridge.ts +5 -0
  24. package/dist/resources/extensions/gsd/paths.ts +9 -0
  25. package/dist/resources/extensions/gsd/preferences.ts +181 -2
  26. package/dist/resources/extensions/gsd/prompts/execute-task.md +6 -5
  27. package/dist/resources/extensions/gsd/prompts/system.md +2 -0
  28. package/dist/resources/extensions/gsd/queue-order.ts +231 -0
  29. package/dist/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
  30. package/dist/resources/extensions/gsd/routing-history.ts +290 -0
  31. package/dist/resources/extensions/gsd/state.ts +15 -3
  32. package/dist/resources/extensions/gsd/templates/knowledge.md +19 -0
  33. package/dist/resources/extensions/gsd/templates/preferences.md +14 -0
  34. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +50 -0
  35. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
  36. package/dist/resources/extensions/gsd/tests/budget-prediction.test.ts +220 -0
  37. package/dist/resources/extensions/gsd/tests/complexity-routing.test.ts +294 -0
  38. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +180 -0
  39. package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
  40. package/dist/resources/extensions/gsd/tests/git-service.test.ts +132 -0
  41. package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
  42. package/dist/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
  43. package/dist/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
  44. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +28 -0
  45. package/dist/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
  46. package/dist/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
  47. package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
  48. package/dist/resources/extensions/gsd/tests/routing-history.test.ts +87 -0
  49. package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
  50. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +130 -0
  51. package/dist/resources/extensions/gsd/tests/token-profile.test.ts +263 -0
  52. package/dist/resources/extensions/gsd/types.ts +28 -0
  53. package/dist/resources/extensions/gsd/worktree-manager.ts +8 -5
  54. package/dist/resources/extensions/gsd/worktree.ts +24 -2
  55. package/dist/resources/extensions/shared/next-action-ui.ts +16 -1
  56. package/package.json +1 -1
  57. package/packages/pi-ai/dist/models.generated.d.ts +493 -13
  58. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  59. package/packages/pi-ai/dist/models.generated.js +422 -62
  60. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  61. package/packages/pi-ai/dist/providers/google-shared.d.ts +12 -0
  62. package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
  63. package/packages/pi-ai/dist/providers/google-shared.js +9 -22
  64. package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
  65. package/packages/pi-ai/dist/providers/google-shared.test.d.ts +2 -0
  66. package/packages/pi-ai/dist/providers/google-shared.test.d.ts.map +1 -0
  67. package/packages/pi-ai/dist/providers/google-shared.test.js +125 -0
  68. package/packages/pi-ai/dist/providers/google-shared.test.js.map +1 -0
  69. package/packages/pi-ai/src/models.generated.ts +422 -62
  70. package/packages/pi-ai/src/providers/google-shared.test.ts +137 -0
  71. package/packages/pi-ai/src/providers/google-shared.ts +10 -19
  72. package/packages/pi-coding-agent/dist/cli/args.d.ts +5 -0
  73. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  74. package/packages/pi-coding-agent/dist/cli/args.js +21 -0
  75. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  76. package/packages/pi-coding-agent/dist/cli/list-models.d.ts +14 -3
  77. package/packages/pi-coding-agent/dist/cli/list-models.d.ts.map +1 -1
  78. package/packages/pi-coding-agent/dist/cli/list-models.js +52 -17
  79. package/packages/pi-coding-agent/dist/cli/list-models.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts +27 -0
  81. package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -0
  82. package/packages/pi-coding-agent/dist/core/discovery-cache.js +79 -0
  83. package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -0
  84. package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts +2 -0
  85. package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +140 -0
  87. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -0
  88. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +35 -0
  89. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -0
  90. package/packages/pi-coding-agent/dist/core/model-discovery.js +162 -0
  91. package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -0
  92. package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts +2 -0
  93. package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts.map +1 -0
  94. package/packages/pi-coding-agent/dist/core/model-discovery.test.js +100 -0
  95. package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -0
  96. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts +2 -0
  97. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts.map +1 -0
  98. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +113 -0
  99. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -0
  100. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +26 -0
  101. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  102. package/packages/pi-coding-agent/dist/core/model-registry.js +98 -0
  103. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts +62 -0
  105. package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts.map +1 -0
  106. package/packages/pi-coding-agent/dist/core/models-json-writer.js +145 -0
  107. package/packages/pi-coding-agent/dist/core/models-json-writer.js.map +1 -0
  108. package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts +2 -0
  109. package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts.map +1 -0
  110. package/packages/pi-coding-agent/dist/core/models-json-writer.test.js +118 -0
  111. package/packages/pi-coding-agent/dist/core/models-json-writer.test.js.map +1 -0
  112. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -0
  113. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
  115. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  118. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +7 -7
  120. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +209 -13
  122. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.d.ts +2 -0
  124. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.d.ts.map +1 -0
  125. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +67 -0
  126. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -0
  127. package/packages/pi-coding-agent/dist/index.d.ts +5 -1
  128. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/index.js +4 -1
  130. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/main.js +17 -2
  133. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts +1 -0
  135. package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/components/index.js +1 -0
  137. package/packages/pi-coding-agent/dist/modes/interactive/components/index.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +1 -1
  139. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +25 -0
  141. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -0
  142. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +121 -0
  143. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -0
  144. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
  145. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  146. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +32 -0
  147. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -0
  150. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  151. package/packages/pi-coding-agent/src/cli/args.ts +21 -0
  152. package/packages/pi-coding-agent/src/cli/list-models.ts +70 -17
  153. package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +170 -0
  154. package/packages/pi-coding-agent/src/core/discovery-cache.ts +97 -0
  155. package/packages/pi-coding-agent/src/core/model-discovery.test.ts +125 -0
  156. package/packages/pi-coding-agent/src/core/model-discovery.ts +231 -0
  157. package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +135 -0
  158. package/packages/pi-coding-agent/src/core/model-registry.ts +107 -0
  159. package/packages/pi-coding-agent/src/core/models-json-writer.test.ts +145 -0
  160. package/packages/pi-coding-agent/src/core/models-json-writer.ts +188 -0
  161. package/packages/pi-coding-agent/src/core/settings-manager.ts +21 -0
  162. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  163. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +85 -0
  164. package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +245 -17
  165. package/packages/pi-coding-agent/src/index.ts +5 -0
  166. package/packages/pi-coding-agent/src/main.ts +19 -2
  167. package/packages/pi-coding-agent/src/modes/interactive/components/index.ts +1 -0
  168. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +1 -1
  169. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +163 -0
  170. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +37 -0
  171. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +13 -0
  172. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  173. package/pkg/dist/modes/interactive/theme/theme.js +10 -0
  174. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  175. package/src/resources/extensions/gsd/activity-log.ts +37 -7
  176. package/src/resources/extensions/gsd/auto-dashboard.ts +4 -0
  177. package/src/resources/extensions/gsd/auto-dispatch.ts +9 -3
  178. package/src/resources/extensions/gsd/auto-prompts.ts +91 -42
  179. package/src/resources/extensions/gsd/auto-recovery.ts +7 -2
  180. package/src/resources/extensions/gsd/auto-worktree.ts +33 -4
  181. package/src/resources/extensions/gsd/auto.ts +177 -25
  182. package/src/resources/extensions/gsd/commands.ts +264 -23
  183. package/src/resources/extensions/gsd/complexity.ts +236 -0
  184. package/src/resources/extensions/gsd/dispatch-guard.ts +7 -19
  185. package/src/resources/extensions/gsd/docs/preferences-reference.md +202 -2
  186. package/src/resources/extensions/gsd/files.ts +129 -3
  187. package/src/resources/extensions/gsd/git-service.ts +19 -8
  188. package/src/resources/extensions/gsd/gitignore.ts +41 -2
  189. package/src/resources/extensions/gsd/guided-flow.ts +247 -10
  190. package/src/resources/extensions/gsd/index.ts +47 -3
  191. package/src/resources/extensions/gsd/metrics.ts +44 -0
  192. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -0
  193. package/src/resources/extensions/gsd/native-parser-bridge.ts +5 -0
  194. package/src/resources/extensions/gsd/paths.ts +9 -0
  195. package/src/resources/extensions/gsd/preferences.ts +181 -2
  196. package/src/resources/extensions/gsd/prompts/execute-task.md +6 -5
  197. package/src/resources/extensions/gsd/prompts/system.md +2 -0
  198. package/src/resources/extensions/gsd/queue-order.ts +231 -0
  199. package/src/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
  200. package/src/resources/extensions/gsd/routing-history.ts +290 -0
  201. package/src/resources/extensions/gsd/state.ts +15 -3
  202. package/src/resources/extensions/gsd/templates/knowledge.md +19 -0
  203. package/src/resources/extensions/gsd/templates/preferences.md +14 -0
  204. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +50 -0
  205. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
  206. package/src/resources/extensions/gsd/tests/budget-prediction.test.ts +220 -0
  207. package/src/resources/extensions/gsd/tests/complexity-routing.test.ts +294 -0
  208. package/src/resources/extensions/gsd/tests/context-compression.test.ts +180 -0
  209. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
  210. package/src/resources/extensions/gsd/tests/git-service.test.ts +132 -0
  211. package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
  212. package/src/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
  213. package/src/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
  214. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +28 -0
  215. package/src/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
  216. package/src/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
  217. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
  218. package/src/resources/extensions/gsd/tests/routing-history.test.ts +87 -0
  219. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
  220. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +130 -0
  221. package/src/resources/extensions/gsd/tests/token-profile.test.ts +263 -0
  222. package/src/resources/extensions/gsd/types.ts +28 -0
  223. package/src/resources/extensions/gsd/worktree-manager.ts +8 -5
  224. package/src/resources/extensions/gsd/worktree.ts +24 -2
  225. 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
- activeMilestone = { id: mid, title: mid };
228
- activeMilestoneFound = true;
229
- registry.push({ id: mid, title: mid, status: 'active' });
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
+ });