gsd-pi 2.17.0 → 2.19.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 (217) 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 +14 -2
  7. package/dist/resources/extensions/gsd/auto-prompts.ts +65 -16
  8. package/dist/resources/extensions/gsd/auto-worktree.ts +33 -4
  9. package/dist/resources/extensions/gsd/auto.ts +399 -29
  10. package/dist/resources/extensions/gsd/captures.ts +384 -0
  11. package/dist/resources/extensions/gsd/commands.ts +382 -23
  12. package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
  13. package/dist/resources/extensions/gsd/dashboard-overlay.ts +10 -0
  14. package/dist/resources/extensions/gsd/dispatch-guard.ts +7 -19
  15. package/dist/resources/extensions/gsd/docs/preferences-reference.md +201 -2
  16. package/dist/resources/extensions/gsd/files.ts +123 -1
  17. package/dist/resources/extensions/gsd/guided-flow.ts +237 -4
  18. package/dist/resources/extensions/gsd/index.ts +47 -3
  19. package/dist/resources/extensions/gsd/metrics.ts +48 -0
  20. package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
  21. package/dist/resources/extensions/gsd/model-router.ts +256 -0
  22. package/dist/resources/extensions/gsd/paths.ts +9 -0
  23. package/dist/resources/extensions/gsd/post-unit-hooks.ts +2 -1
  24. package/dist/resources/extensions/gsd/preferences.ts +132 -1
  25. package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
  26. package/dist/resources/extensions/gsd/prompts/execute-task.md +6 -5
  27. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  28. package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  29. package/dist/resources/extensions/gsd/prompts/system.md +2 -0
  30. package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  31. package/dist/resources/extensions/gsd/queue-order.ts +231 -0
  32. package/dist/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
  33. package/dist/resources/extensions/gsd/state.ts +15 -3
  34. package/dist/resources/extensions/gsd/templates/knowledge.md +19 -0
  35. package/dist/resources/extensions/gsd/templates/preferences.md +14 -0
  36. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
  37. package/dist/resources/extensions/gsd/tests/captures.test.ts +438 -0
  38. package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  39. package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
  40. package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -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/milestone-transition-worktree.test.ts +144 -0
  45. package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  46. package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  47. package/dist/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
  48. package/dist/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
  49. package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
  50. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +227 -1
  51. package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  52. package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
  53. package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  54. package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  55. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +198 -0
  56. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +255 -0
  57. package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
  58. package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
  59. package/dist/resources/extensions/gsd/visualizer-data.ts +154 -0
  60. package/dist/resources/extensions/gsd/visualizer-overlay.ts +193 -0
  61. package/dist/resources/extensions/gsd/visualizer-views.ts +293 -0
  62. package/dist/resources/extensions/gsd/worktree-manager.ts +8 -5
  63. package/dist/resources/extensions/gsd/worktree.ts +22 -0
  64. package/dist/resources/extensions/remote-questions/discord-adapter.ts +33 -0
  65. package/dist/resources/extensions/remote-questions/format.ts +12 -6
  66. package/dist/resources/extensions/remote-questions/manager.ts +8 -0
  67. package/dist/resources/extensions/shared/next-action-ui.ts +16 -1
  68. package/package.json +1 -1
  69. package/packages/pi-coding-agent/dist/cli/args.d.ts +5 -0
  70. package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
  71. package/packages/pi-coding-agent/dist/cli/args.js +21 -0
  72. package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
  73. package/packages/pi-coding-agent/dist/cli/list-models.d.ts +14 -3
  74. package/packages/pi-coding-agent/dist/cli/list-models.d.ts.map +1 -1
  75. package/packages/pi-coding-agent/dist/cli/list-models.js +52 -17
  76. package/packages/pi-coding-agent/dist/cli/list-models.js.map +1 -1
  77. package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts +27 -0
  78. package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -0
  79. package/packages/pi-coding-agent/dist/core/discovery-cache.js +79 -0
  80. package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -0
  81. package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts +2 -0
  82. package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts.map +1 -0
  83. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +140 -0
  84. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -0
  85. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +35 -0
  86. package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -0
  87. package/packages/pi-coding-agent/dist/core/model-discovery.js +162 -0
  88. package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -0
  89. package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts +2 -0
  90. package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts.map +1 -0
  91. package/packages/pi-coding-agent/dist/core/model-discovery.test.js +100 -0
  92. package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -0
  93. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts +2 -0
  94. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts.map +1 -0
  95. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +113 -0
  96. package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -0
  97. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +26 -0
  98. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  99. package/packages/pi-coding-agent/dist/core/model-registry.js +98 -0
  100. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts +62 -0
  102. package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts.map +1 -0
  103. package/packages/pi-coding-agent/dist/core/models-json-writer.js +145 -0
  104. package/packages/pi-coding-agent/dist/core/models-json-writer.js.map +1 -0
  105. package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts +2 -0
  106. package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts.map +1 -0
  107. package/packages/pi-coding-agent/dist/core/models-json-writer.test.js +118 -0
  108. package/packages/pi-coding-agent/dist/core/models-json-writer.test.js.map +1 -0
  109. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -0
  110. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
  112. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  115. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/index.d.ts +5 -1
  117. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  118. package/packages/pi-coding-agent/dist/index.js +4 -1
  119. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/main.js +17 -2
  122. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts +1 -0
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/index.js +1 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/index.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +1 -1
  128. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +25 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +121 -0
  132. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -0
  133. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +32 -0
  136. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  137. package/packages/pi-coding-agent/src/cli/args.ts +21 -0
  138. package/packages/pi-coding-agent/src/cli/list-models.ts +70 -17
  139. package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +170 -0
  140. package/packages/pi-coding-agent/src/core/discovery-cache.ts +97 -0
  141. package/packages/pi-coding-agent/src/core/model-discovery.test.ts +125 -0
  142. package/packages/pi-coding-agent/src/core/model-discovery.ts +231 -0
  143. package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +135 -0
  144. package/packages/pi-coding-agent/src/core/model-registry.ts +107 -0
  145. package/packages/pi-coding-agent/src/core/models-json-writer.test.ts +145 -0
  146. package/packages/pi-coding-agent/src/core/models-json-writer.ts +188 -0
  147. package/packages/pi-coding-agent/src/core/settings-manager.ts +21 -0
  148. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  149. package/packages/pi-coding-agent/src/index.ts +5 -0
  150. package/packages/pi-coding-agent/src/main.ts +19 -2
  151. package/packages/pi-coding-agent/src/modes/interactive/components/index.ts +1 -0
  152. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +1 -1
  153. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +163 -0
  154. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +37 -0
  155. package/src/resources/extensions/gsd/activity-log.ts +37 -7
  156. package/src/resources/extensions/gsd/auto-dashboard.ts +14 -2
  157. package/src/resources/extensions/gsd/auto-prompts.ts +65 -16
  158. package/src/resources/extensions/gsd/auto-worktree.ts +33 -4
  159. package/src/resources/extensions/gsd/auto.ts +399 -29
  160. package/src/resources/extensions/gsd/captures.ts +384 -0
  161. package/src/resources/extensions/gsd/commands.ts +382 -23
  162. package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
  163. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
  164. package/src/resources/extensions/gsd/dispatch-guard.ts +7 -19
  165. package/src/resources/extensions/gsd/docs/preferences-reference.md +201 -2
  166. package/src/resources/extensions/gsd/files.ts +123 -1
  167. package/src/resources/extensions/gsd/guided-flow.ts +237 -4
  168. package/src/resources/extensions/gsd/index.ts +47 -3
  169. package/src/resources/extensions/gsd/metrics.ts +48 -0
  170. package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
  171. package/src/resources/extensions/gsd/model-router.ts +256 -0
  172. package/src/resources/extensions/gsd/paths.ts +9 -0
  173. package/src/resources/extensions/gsd/post-unit-hooks.ts +2 -1
  174. package/src/resources/extensions/gsd/preferences.ts +132 -1
  175. package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
  176. package/src/resources/extensions/gsd/prompts/execute-task.md +6 -5
  177. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
  178. package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
  179. package/src/resources/extensions/gsd/prompts/system.md +2 -0
  180. package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -0
  181. package/src/resources/extensions/gsd/queue-order.ts +231 -0
  182. package/src/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
  183. package/src/resources/extensions/gsd/state.ts +15 -3
  184. package/src/resources/extensions/gsd/templates/knowledge.md +19 -0
  185. package/src/resources/extensions/gsd/templates/preferences.md +14 -0
  186. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
  187. package/src/resources/extensions/gsd/tests/captures.test.ts +438 -0
  188. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
  189. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
  190. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -0
  191. package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
  192. package/src/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
  193. package/src/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
  194. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +144 -0
  195. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
  196. package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -0
  197. package/src/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
  198. package/src/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
  199. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
  200. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +227 -1
  201. package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
  202. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
  203. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
  204. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
  205. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +198 -0
  206. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +255 -0
  207. package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
  208. package/src/resources/extensions/gsd/triage-ui.ts +175 -0
  209. package/src/resources/extensions/gsd/visualizer-data.ts +154 -0
  210. package/src/resources/extensions/gsd/visualizer-overlay.ts +193 -0
  211. package/src/resources/extensions/gsd/visualizer-views.ts +293 -0
  212. package/src/resources/extensions/gsd/worktree-manager.ts +8 -5
  213. package/src/resources/extensions/gsd/worktree.ts +22 -0
  214. package/src/resources/extensions/remote-questions/discord-adapter.ts +33 -0
  215. package/src/resources/extensions/remote-questions/format.ts +12 -6
  216. package/src/resources/extensions/remote-questions/manager.ts +8 -0
  217. package/src/resources/extensions/shared/next-action-ui.ts +16 -1
@@ -0,0 +1,256 @@
1
+ // GSD Extension — Dynamic Model Router
2
+ // Maps complexity tiers to models, enforcing downgrade-only semantics.
3
+ // The user's configured model is always the ceiling.
4
+
5
+ import type { ComplexityTier, ClassificationResult } from "./complexity-classifier.js";
6
+ import { tierOrdinal } from "./complexity-classifier.js";
7
+ import type { ResolvedModelConfig } from "./preferences.js";
8
+
9
+ // ─── Types ───────────────────────────────────────────────────────────────────
10
+
11
+ export interface DynamicRoutingConfig {
12
+ enabled?: boolean;
13
+ tier_models?: {
14
+ light?: string;
15
+ standard?: string;
16
+ heavy?: string;
17
+ };
18
+ escalate_on_failure?: boolean; // default: true
19
+ budget_pressure?: boolean; // default: true
20
+ cross_provider?: boolean; // default: true
21
+ hooks?: boolean; // default: true
22
+ }
23
+
24
+ export interface RoutingDecision {
25
+ /** The model ID to use (may be downgraded from configured) */
26
+ modelId: string;
27
+ /** Fallback chain: [selected_model, ...configured_fallbacks, configured_primary] */
28
+ fallbacks: string[];
29
+ /** The complexity tier that drove this decision */
30
+ tier: ComplexityTier;
31
+ /** True if the model was downgraded from the configured primary */
32
+ wasDowngraded: boolean;
33
+ /** Human-readable reason for this decision */
34
+ reason: string;
35
+ }
36
+
37
+ // ─── Known Model Tiers ───────────────────────────────────────────────────────
38
+ // Maps known model IDs to their capability tier. Used when tier_models is not
39
+ // explicitly configured to pick the best available model for each tier.
40
+
41
+ const MODEL_CAPABILITY_TIER: Record<string, ComplexityTier> = {
42
+ // Light-tier models (cheapest)
43
+ "claude-haiku-4-5": "light",
44
+ "claude-3-5-haiku-latest": "light",
45
+ "claude-3-haiku-20240307": "light",
46
+ "gpt-4o-mini": "light",
47
+ "gemini-2.0-flash": "light",
48
+ "gemini-flash-2.0": "light",
49
+
50
+ // Standard-tier models
51
+ "claude-sonnet-4-6": "standard",
52
+ "claude-sonnet-4-5-20250514": "standard",
53
+ "claude-3-5-sonnet-latest": "standard",
54
+ "gpt-4o": "standard",
55
+ "gemini-2.5-pro": "standard",
56
+ "deepseek-chat": "standard",
57
+
58
+ // Heavy-tier models (most capable)
59
+ "claude-opus-4-6": "heavy",
60
+ "claude-3-opus-latest": "heavy",
61
+ "gpt-4-turbo": "heavy",
62
+ "o1": "heavy",
63
+ "o3": "heavy",
64
+ };
65
+
66
+ // ─── Cost Table (per 1K input tokens, approximate USD) ───────────────────────
67
+ // Used for cross-provider cost comparison when multiple providers offer
68
+ // the same capability tier.
69
+
70
+ const MODEL_COST_PER_1K_INPUT: Record<string, number> = {
71
+ "claude-haiku-4-5": 0.0008,
72
+ "claude-3-5-haiku-latest": 0.0008,
73
+ "claude-sonnet-4-6": 0.003,
74
+ "claude-sonnet-4-5-20250514": 0.003,
75
+ "claude-opus-4-6": 0.015,
76
+ "gpt-4o-mini": 0.00015,
77
+ "gpt-4o": 0.0025,
78
+ "gemini-2.0-flash": 0.0001,
79
+ "gemini-2.5-pro": 0.00125,
80
+ "deepseek-chat": 0.00014,
81
+ };
82
+
83
+ // ─── Public API ──────────────────────────────────────────────────────────────
84
+
85
+ /**
86
+ * Resolve the model to use for a given complexity tier.
87
+ *
88
+ * Downgrade-only: the returned model is always equal to or cheaper than
89
+ * the user's configured primary model. Never upgrades beyond configuration.
90
+ *
91
+ * @param classification The complexity classification result
92
+ * @param phaseConfig The user's configured model for this phase (ceiling)
93
+ * @param routingConfig Dynamic routing configuration
94
+ * @param availableModelIds List of available model IDs (from registry)
95
+ */
96
+ export function resolveModelForComplexity(
97
+ classification: ClassificationResult,
98
+ phaseConfig: ResolvedModelConfig | undefined,
99
+ routingConfig: DynamicRoutingConfig,
100
+ availableModelIds: string[],
101
+ ): RoutingDecision {
102
+ // If no phase config or routing disabled, pass through
103
+ if (!phaseConfig || !routingConfig.enabled) {
104
+ return {
105
+ modelId: phaseConfig?.primary ?? "",
106
+ fallbacks: phaseConfig?.fallbacks ?? [],
107
+ tier: classification.tier,
108
+ wasDowngraded: false,
109
+ reason: "dynamic routing disabled or no phase config",
110
+ };
111
+ }
112
+
113
+ const configuredPrimary = phaseConfig.primary;
114
+ const configuredTier = getModelTier(configuredPrimary);
115
+ const requestedTier = classification.tier;
116
+
117
+ // Downgrade-only: if requested tier >= configured tier, no change
118
+ if (tierOrdinal(requestedTier) >= tierOrdinal(configuredTier)) {
119
+ return {
120
+ modelId: configuredPrimary,
121
+ fallbacks: phaseConfig.fallbacks,
122
+ tier: requestedTier,
123
+ wasDowngraded: false,
124
+ reason: `tier ${requestedTier} >= configured ${configuredTier}`,
125
+ };
126
+ }
127
+
128
+ // Find the best model for the requested tier
129
+ const targetModelId = findModelForTier(
130
+ requestedTier,
131
+ routingConfig,
132
+ availableModelIds,
133
+ routingConfig.cross_provider !== false,
134
+ );
135
+
136
+ if (!targetModelId) {
137
+ // No suitable model found — use configured primary
138
+ return {
139
+ modelId: configuredPrimary,
140
+ fallbacks: phaseConfig.fallbacks,
141
+ tier: requestedTier,
142
+ wasDowngraded: false,
143
+ reason: `no ${requestedTier}-tier model available`,
144
+ };
145
+ }
146
+
147
+ // Build fallback chain: [downgraded_model, ...configured_fallbacks, configured_primary]
148
+ const fallbacks = [
149
+ ...phaseConfig.fallbacks.filter(f => f !== targetModelId),
150
+ configuredPrimary,
151
+ ].filter(f => f !== targetModelId);
152
+
153
+ return {
154
+ modelId: targetModelId,
155
+ fallbacks,
156
+ tier: requestedTier,
157
+ wasDowngraded: true,
158
+ reason: classification.reason,
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Escalate to the next tier after a failure.
164
+ * Returns the new tier, or null if already at heavy (max).
165
+ */
166
+ export function escalateTier(currentTier: ComplexityTier): ComplexityTier | null {
167
+ switch (currentTier) {
168
+ case "light": return "standard";
169
+ case "standard": return "heavy";
170
+ case "heavy": return null;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Get the default routing config (all features enabled).
176
+ */
177
+ export function defaultRoutingConfig(): DynamicRoutingConfig {
178
+ return {
179
+ enabled: false,
180
+ escalate_on_failure: true,
181
+ budget_pressure: true,
182
+ cross_provider: true,
183
+ hooks: true,
184
+ };
185
+ }
186
+
187
+ // ─── Internal ────────────────────────────────────────────────────────────────
188
+
189
+ function getModelTier(modelId: string): ComplexityTier {
190
+ // Strip provider prefix if present
191
+ const bareId = modelId.includes("/") ? modelId.split("/").pop()! : modelId;
192
+
193
+ // Check exact match first
194
+ if (MODEL_CAPABILITY_TIER[bareId]) return MODEL_CAPABILITY_TIER[bareId];
195
+
196
+ // Check if any known model ID is a prefix/suffix match
197
+ for (const [knownId, tier] of Object.entries(MODEL_CAPABILITY_TIER)) {
198
+ if (bareId.includes(knownId) || knownId.includes(bareId)) return tier;
199
+ }
200
+
201
+ // Unknown models are assumed heavy (safest assumption)
202
+ return "heavy";
203
+ }
204
+
205
+ function findModelForTier(
206
+ tier: ComplexityTier,
207
+ config: DynamicRoutingConfig,
208
+ availableModelIds: string[],
209
+ crossProvider: boolean,
210
+ ): string | null {
211
+ // 1. Check explicit tier_models config
212
+ const explicitModel = config.tier_models?.[tier];
213
+ if (explicitModel && availableModelIds.includes(explicitModel)) {
214
+ return explicitModel;
215
+ }
216
+ // Also check with provider prefix stripped
217
+ if (explicitModel) {
218
+ const match = availableModelIds.find(id => {
219
+ const bareAvail = id.includes("/") ? id.split("/").pop()! : id;
220
+ const bareExplicit = explicitModel.includes("/") ? explicitModel.split("/").pop()! : explicitModel;
221
+ return bareAvail === bareExplicit;
222
+ });
223
+ if (match) return match;
224
+ }
225
+
226
+ // 2. Auto-detect: find the cheapest available model in the requested tier
227
+ const candidates = availableModelIds
228
+ .filter(id => {
229
+ const modelTier = getModelTier(id);
230
+ return modelTier === tier;
231
+ })
232
+ .sort((a, b) => {
233
+ if (!crossProvider) return 0;
234
+ const costA = getModelCost(a);
235
+ const costB = getModelCost(b);
236
+ return costA - costB;
237
+ });
238
+
239
+ return candidates[0] ?? null;
240
+ }
241
+
242
+ function getModelCost(modelId: string): number {
243
+ const bareId = modelId.includes("/") ? modelId.split("/").pop()! : modelId;
244
+
245
+ if (MODEL_COST_PER_1K_INPUT[bareId] !== undefined) {
246
+ return MODEL_COST_PER_1K_INPUT[bareId];
247
+ }
248
+
249
+ // Check partial matches
250
+ for (const [knownId, cost] of Object.entries(MODEL_COST_PER_1K_INPUT)) {
251
+ if (bareId.includes(knownId) || knownId.includes(bareId)) return cost;
252
+ }
253
+
254
+ // Unknown cost — assume expensive to avoid routing to unknown cheap models
255
+ return 999;
256
+ }
@@ -15,6 +15,9 @@ import { nativeScanGsdTree, type GsdTreeEntry } from "./native-parser-bridge.js"
15
15
 
16
16
  // ─── Directory Listing Cache ──────────────────────────────────────────────────
17
17
 
18
+ /** Max entries before eviction. Prevents unbounded growth in long sessions (#611). */
19
+ const DIR_CACHE_MAX = 200;
20
+
18
21
  const dirEntryCache = new Map<string, Dirent[]>();
19
22
  const dirListCache = new Map<string, string[]>();
20
23
 
@@ -85,6 +88,7 @@ function cachedReaddirWithTypes(dirPath: string): Dirent[] {
85
88
  d.isSocket = () => false;
86
89
  return d;
87
90
  });
91
+ if (dirEntryCache.size >= DIR_CACHE_MAX) dirEntryCache.clear();
88
92
  dirEntryCache.set(dirPath, dirents);
89
93
  return dirents;
90
94
  }
@@ -92,6 +96,7 @@ function cachedReaddirWithTypes(dirPath: string): Dirent[] {
92
96
  }
93
97
 
94
98
  const entries = readdirSync(dirPath, { withFileTypes: true });
99
+ if (dirEntryCache.size >= DIR_CACHE_MAX) dirEntryCache.clear();
95
100
  dirEntryCache.set(dirPath, entries);
96
101
  return entries;
97
102
  }
@@ -107,6 +112,7 @@ function cachedReaddir(dirPath: string): string[] {
107
112
  const treeEntries = nativeTreeCache.get(key);
108
113
  if (treeEntries) {
109
114
  const names = treeEntries.map(e => e.name);
115
+ if (dirListCache.size >= DIR_CACHE_MAX) dirListCache.clear();
110
116
  dirListCache.set(dirPath, names);
111
117
  return names;
112
118
  }
@@ -114,6 +120,7 @@ function cachedReaddir(dirPath: string): string[] {
114
120
  }
115
121
 
116
122
  const entries = readdirSync(dirPath);
123
+ if (dirListCache.size >= DIR_CACHE_MAX) dirListCache.clear();
117
124
  dirListCache.set(dirPath, entries);
118
125
  return entries;
119
126
  }
@@ -248,6 +255,7 @@ export const GSD_ROOT_FILES = {
248
255
  STATE: "STATE.md",
249
256
  REQUIREMENTS: "REQUIREMENTS.md",
250
257
  OVERRIDES: "OVERRIDES.md",
258
+ KNOWLEDGE: "KNOWLEDGE.md",
251
259
  } as const;
252
260
 
253
261
  export type GSDRootFileKey = keyof typeof GSD_ROOT_FILES;
@@ -259,6 +267,7 @@ const LEGACY_GSD_ROOT_FILES: Record<GSDRootFileKey, string> = {
259
267
  STATE: "state.md",
260
268
  REQUIREMENTS: "requirements.md",
261
269
  OVERRIDES: "overrides.md",
270
+ KNOWLEDGE: "knowledge.md",
262
271
  };
263
272
 
264
273
  export function gsdRoot(basePath: string): string {
@@ -60,7 +60,8 @@ export function checkPostUnitHooks(
60
60
  }
61
61
 
62
62
  // Don't trigger hooks for other hook units (prevent hook-on-hook chains)
63
- if (completedUnitType.startsWith("hook/")) return null;
63
+ // Don't trigger hooks for triage units (prevent hook-on-triage chains)
64
+ if (completedUnitType.startsWith("hook/") || completedUnitType === "triage-captures") return null;
64
65
 
65
66
  // Check if any hooks are configured for this unit type
66
67
  const hooks = resolvePostUnitHooks().filter(h =>
@@ -1,9 +1,11 @@
1
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
1
+ import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { isAbsolute, join } from "node:path";
4
4
  import { getAgentDir } from "@gsd/pi-coding-agent";
5
5
  import type { GitPreferences } from "./git-service.js";
6
6
  import type { PostUnitHookConfig, PreDispatchHookConfig, BudgetEnforcementMode, NotificationPreferences, TokenProfile, InlineLevel, PhaseSkipPreferences } from "./types.js";
7
+ import type { DynamicRoutingConfig } from "./model-router.js";
8
+ import { defaultRoutingConfig } from "./model-router.js";
7
9
  import { VALID_BRANCH_NAME } from "./git-service.js";
8
10
 
9
11
  const GLOBAL_PREFERENCES_PATH = join(homedir(), ".gsd", "preferences.md");
@@ -36,8 +38,10 @@ const KNOWN_PREFERENCE_KEYS = new Set<string>([
36
38
  "git",
37
39
  "post_unit_hooks",
38
40
  "pre_dispatch_hooks",
41
+ "dynamic_routing",
39
42
  "token_profile",
40
43
  "phases",
44
+ "auto_visualize",
41
45
  ]);
42
46
 
43
47
  export interface GSDSkillRule {
@@ -128,8 +132,10 @@ export interface GSDPreferences {
128
132
  git?: GitPreferences;
129
133
  post_unit_hooks?: PostUnitHookConfig[];
130
134
  pre_dispatch_hooks?: PreDispatchHookConfig[];
135
+ dynamic_routing?: DynamicRoutingConfig;
131
136
  token_profile?: TokenProfile;
132
137
  phases?: PhaseSkipPreferences;
138
+ auto_visualize?: boolean;
133
139
  }
134
140
 
135
141
  export interface LoadedGSDPreferences {
@@ -674,6 +680,20 @@ export function resolveModelWithFallbacksForUnit(unitType: string): ResolvedMode
674
680
  };
675
681
  }
676
682
 
683
+ /**
684
+ * Resolve the dynamic routing configuration from effective preferences.
685
+ * Returns the merged config with defaults applied.
686
+ */
687
+ export function resolveDynamicRoutingConfig(): DynamicRoutingConfig {
688
+ const prefs = loadEffectiveGSDPreferences();
689
+ const configured = prefs?.preferences.dynamic_routing;
690
+ if (!configured) return defaultRoutingConfig();
691
+ return {
692
+ ...defaultRoutingConfig(),
693
+ ...configured,
694
+ };
695
+ }
696
+
677
697
  export function resolveAutoSupervisorConfig(): AutoSupervisorConfig {
678
698
  const prefs = loadEffectiveGSDPreferences();
679
699
  const configured = prefs?.preferences.auto_supervisor ?? {};
@@ -780,6 +800,9 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
780
800
  : undefined,
781
801
  post_unit_hooks: mergePostUnitHooks(base.post_unit_hooks, override.post_unit_hooks),
782
802
  pre_dispatch_hooks: mergePreDispatchHooks(base.pre_dispatch_hooks, override.pre_dispatch_hooks),
803
+ dynamic_routing: (base.dynamic_routing || override.dynamic_routing)
804
+ ? { ...(base.dynamic_routing ?? {}), ...(override.dynamic_routing ?? {}) } as DynamicRoutingConfig
805
+ : undefined,
783
806
  token_profile: override.token_profile ?? base.token_profile,
784
807
  phases: (base.phases || override.phases)
785
808
  ? { ...(base.phases ?? {}), ...(override.phases ?? {}) }
@@ -1100,6 +1123,56 @@ export function validatePreferences(preferences: GSDPreferences): {
1100
1123
  }
1101
1124
  }
1102
1125
 
1126
+ // ─── Dynamic Routing ─────────────────────────────────────────────────
1127
+ if (preferences.dynamic_routing !== undefined) {
1128
+ if (typeof preferences.dynamic_routing === "object" && preferences.dynamic_routing !== null) {
1129
+ const dr = preferences.dynamic_routing as unknown as Record<string, unknown>;
1130
+ const validDr: Partial<DynamicRoutingConfig> = {};
1131
+
1132
+ if (dr.enabled !== undefined) {
1133
+ if (typeof dr.enabled === "boolean") validDr.enabled = dr.enabled;
1134
+ else errors.push("dynamic_routing.enabled must be a boolean");
1135
+ }
1136
+ if (dr.escalate_on_failure !== undefined) {
1137
+ if (typeof dr.escalate_on_failure === "boolean") validDr.escalate_on_failure = dr.escalate_on_failure;
1138
+ else errors.push("dynamic_routing.escalate_on_failure must be a boolean");
1139
+ }
1140
+ if (dr.budget_pressure !== undefined) {
1141
+ if (typeof dr.budget_pressure === "boolean") validDr.budget_pressure = dr.budget_pressure;
1142
+ else errors.push("dynamic_routing.budget_pressure must be a boolean");
1143
+ }
1144
+ if (dr.cross_provider !== undefined) {
1145
+ if (typeof dr.cross_provider === "boolean") validDr.cross_provider = dr.cross_provider;
1146
+ else errors.push("dynamic_routing.cross_provider must be a boolean");
1147
+ }
1148
+ if (dr.hooks !== undefined) {
1149
+ if (typeof dr.hooks === "boolean") validDr.hooks = dr.hooks;
1150
+ else errors.push("dynamic_routing.hooks must be a boolean");
1151
+ }
1152
+ if (dr.tier_models !== undefined) {
1153
+ if (typeof dr.tier_models === "object" && dr.tier_models !== null) {
1154
+ const tm = dr.tier_models as Record<string, unknown>;
1155
+ const validTm: Record<string, string> = {};
1156
+ for (const tier of ["light", "standard", "heavy"]) {
1157
+ if (tm[tier] !== undefined) {
1158
+ if (typeof tm[tier] === "string") validTm[tier] = tm[tier] as string;
1159
+ else errors.push(`dynamic_routing.tier_models.${tier} must be a string`);
1160
+ }
1161
+ }
1162
+ if (Object.keys(validTm).length > 0) validDr.tier_models = validTm as DynamicRoutingConfig["tier_models"];
1163
+ } else {
1164
+ errors.push("dynamic_routing.tier_models must be an object");
1165
+ }
1166
+ }
1167
+
1168
+ if (Object.keys(validDr).length > 0) {
1169
+ validated.dynamic_routing = validDr as unknown as DynamicRoutingConfig;
1170
+ }
1171
+ } else {
1172
+ errors.push("dynamic_routing must be an object");
1173
+ }
1174
+ }
1175
+
1103
1176
  // ─── Git Preferences ───────────────────────────────────────────────────
1104
1177
  if (preferences.git && typeof preferences.git === "object") {
1105
1178
  const git: Record<string, unknown> = {};
@@ -1252,3 +1325,61 @@ export function resolvePreDispatchHooks(): PreDispatchHookConfig[] {
1252
1325
  return (prefs?.preferences.pre_dispatch_hooks ?? [])
1253
1326
  .filter(h => h.enabled !== false);
1254
1327
  }
1328
+
1329
+ /**
1330
+ * Validate a model ID string.
1331
+ * Returns true if the ID looks like a valid model identifier.
1332
+ */
1333
+ export function validateModelId(modelId: string): boolean {
1334
+ if (!modelId || typeof modelId !== "string") return false;
1335
+ const trimmed = modelId.trim();
1336
+ if (trimmed.length === 0 || trimmed.length > 256) return false;
1337
+ // Allow alphanumeric, hyphens, underscores, dots, slashes, colons
1338
+ return /^[a-zA-Z0-9\-_./:]+$/.test(trimmed);
1339
+ }
1340
+
1341
+ /**
1342
+ * Update the models section of the global GSD preferences file.
1343
+ * Performs a safe read-modify-write: reads current content, updates the models
1344
+ * YAML block, and writes back. Creates the file if it doesn't exist.
1345
+ */
1346
+ export function updatePreferencesModels(models: GSDModelConfigV2): void {
1347
+ const prefsPath = getGlobalGSDPreferencesPath();
1348
+
1349
+ let content = "";
1350
+ if (existsSync(prefsPath)) {
1351
+ content = readFileSync(prefsPath, "utf-8");
1352
+ }
1353
+
1354
+ // Build the new models block
1355
+ const lines: string[] = ["models:"];
1356
+ for (const [phase, value] of Object.entries(models)) {
1357
+ if (typeof value === "string") {
1358
+ lines.push(` ${phase}: ${value}`);
1359
+ } else if (value && typeof value === "object") {
1360
+ const config = value as GSDPhaseModelConfig;
1361
+ lines.push(` ${phase}:`);
1362
+ lines.push(` model: ${config.model}`);
1363
+ if (config.provider) {
1364
+ lines.push(` provider: ${config.provider}`);
1365
+ }
1366
+ if (config.fallbacks && config.fallbacks.length > 0) {
1367
+ lines.push(` fallbacks:`);
1368
+ for (const fb of config.fallbacks) {
1369
+ lines.push(` - ${fb}`);
1370
+ }
1371
+ }
1372
+ }
1373
+ }
1374
+ const modelsBlock = lines.join("\n");
1375
+
1376
+ // Replace existing models block or append
1377
+ const modelsRegex = /^models:[\s\S]*?(?=\n[a-z_]|\n*$)/m;
1378
+ if (modelsRegex.test(content)) {
1379
+ content = content.replace(modelsRegex, modelsBlock);
1380
+ } else {
1381
+ content = content.trimEnd() + "\n\n" + modelsBlock + "\n";
1382
+ }
1383
+
1384
+ writeFileSync(prefsPath, content, "utf-8");
1385
+ }
@@ -7,15 +7,17 @@
7
7
  * Templates live at prompts/ relative to this module's directory.
8
8
  * They use {{variableName}} syntax for substitution.
9
9
  *
10
- * Templates are cached on first read per session. This prevents a running
11
- * session from being invalidated when another `gsd` launch overwrites
12
- * ~/.gsd/agent/ with newer templates via initResources(). Without caching,
13
- * the in-memory extension code (which knows variable set A) can read a
14
- * newer template from disk (which expects variable set B), causing a
15
- * "template declares {{X}} but no value was provided" crash mid-session.
10
+ * All templates are eagerly loaded into cache at module init via warmCache().
11
+ * This prevents a running session from being invalidated when another `gsd`
12
+ * launch overwrites ~/.gsd/agent/ with newer templates via initResources().
13
+ * Without eager caching, the in-memory extension code (which knows variable
14
+ * set A) can read a newer template from disk (which expects variable set B),
15
+ * causing a "template declares {{X}} but no value was provided" crash
16
+ * mid-session — especially for late-loading templates like complete-milestone
17
+ * that aren't read until the end of a long auto-mode run.
16
18
  */
17
19
 
18
- import { readFileSync } from "node:fs";
20
+ import { readFileSync, readdirSync } from "node:fs";
19
21
  import { join, dirname } from "node:path";
20
22
  import { fileURLToPath } from "node:url";
21
23
 
@@ -23,10 +25,44 @@ const __extensionDir = dirname(fileURLToPath(import.meta.url));
23
25
  const promptsDir = join(__extensionDir, "prompts");
24
26
  const templatesDir = join(__extensionDir, "templates");
25
27
 
26
- // Cache templates on first read — a running session uses the template versions
27
- // that were on disk when it first loaded them, immune to later overwrites.
28
+ // Cache all templates eagerly at module load — a running session uses the
29
+ // template versions that were on disk at startup, immune to later overwrites.
28
30
  const templateCache = new Map<string, string>();
29
31
 
32
+ /**
33
+ * Eagerly read all .md files from prompts/ and templates/ into cache.
34
+ * Called once at module init so that every template is snapshot before
35
+ * a concurrent initResources() can overwrite files on disk.
36
+ */
37
+ function warmCache(): void {
38
+ try {
39
+ for (const file of readdirSync(promptsDir)) {
40
+ if (!file.endsWith(".md")) continue;
41
+ const name = file.slice(0, -3);
42
+ if (!templateCache.has(name)) {
43
+ templateCache.set(name, readFileSync(join(promptsDir, file), "utf-8"));
44
+ }
45
+ }
46
+ } catch {
47
+ // prompts/ may not exist in test environments — lazy loading still works
48
+ }
49
+
50
+ try {
51
+ for (const file of readdirSync(templatesDir)) {
52
+ if (!file.endsWith(".md")) continue;
53
+ const cacheKey = `tpl:${file.slice(0, -3)}`;
54
+ if (!templateCache.has(cacheKey)) {
55
+ templateCache.set(cacheKey, readFileSync(join(templatesDir, file), "utf-8"));
56
+ }
57
+ }
58
+ } catch {
59
+ // templates/ may not exist in test environments — lazy loading still works
60
+ }
61
+ }
62
+
63
+ // Snapshot all templates at module load time
64
+ warmCache();
65
+
30
66
  /**
31
67
  * Load a prompt template and substitute variables.
32
68
  *
@@ -54,11 +54,12 @@ Then:
54
54
  - Don't fix symptoms. Understand *why* something fails before changing code. A test that passes after a change you don't understand is luck, not a fix.
55
55
  11. **Blocker discovery:** If execution reveals that the remaining slice plan is fundamentally invalid — not just a bug or minor deviation, but a plan-invalidating finding like a wrong API, missing capability, or architectural mismatch — set `blocker_discovered: true` in the task summary frontmatter and describe the blocker clearly in the summary narrative. Do NOT set `blocker_discovered: true` for ordinary debugging, minor deviations, or issues that can be fixed within the current task or the remaining plan. This flag triggers an automatic replan of the slice.
56
56
  12. If you made an architectural, pattern, library, or observability decision during this task that downstream work should know about, append it to `.gsd/DECISIONS.md` (use the **Decisions** output template from the inlined templates below if the file doesn't exist yet). Not every task produces decisions — only append when a meaningful choice was made.
57
- 13. Use the **Task Summary** output template from the inlined templates below
58
- 14. Write `{{taskSummaryPath}}`
59
- 15. Mark {{taskId}} done in `{{planPath}}` (change `[ ]` to `[x]`)
60
- 16. Do not commit manually the system auto-commits your changes after this unit completes.
61
- 17. Update `.gsd/STATE.md`
57
+ 13. If you discover a non-obvious rule, recurring gotcha, or useful pattern during execution, append it to `.gsd/KNOWLEDGE.md`. Only add entries that would save future agents from repeating your investigation. Don't add obvious things.
58
+ 14. Use the **Task Summary** output template from the inlined templates below
59
+ 15. Write `{{taskSummaryPath}}`
60
+ 16. Mark {{taskId}} done in `{{planPath}}` (change `[ ]` to `[x]`)
61
+ 17. Do not commit manually — the system auto-commits your changes after this unit completes.
62
+ 18. Update `.gsd/STATE.md`
62
63
 
63
64
  All work stays in your working directory: `{{workingDirectory}}`.
64
65
 
@@ -16,6 +16,12 @@ All relevant context has been preloaded below — the current roadmap, completed
16
16
 
17
17
  {{inlinedContext}}
18
18
 
19
+ ## Deferred Captures
20
+
21
+ The following user thoughts were captured during execution and deferred to future slices during triage. Consider whether any should influence the remaining roadmap:
22
+
23
+ {{deferredCaptures}}
24
+
19
25
  If a `GSD Skill Preferences` block is present in system context, use it to decide which skills to load and follow during reassessment, without relaxing required verification or artifact rules.
20
26
 
21
27
  Then assess whether the remaining roadmap still makes sense given what was just built.
@@ -12,6 +12,14 @@ All relevant context has been preloaded below — the roadmap, current slice pla
12
12
 
13
13
  {{inlinedContext}}
14
14
 
15
+ ## Capture Context
16
+
17
+ The following user-captured thoughts triggered or informed this replan:
18
+
19
+ {{captureContext}}
20
+
21
+ Consider these captures when rewriting the remaining tasks — they represent the user's real-time insights about what needs to change.
22
+
15
23
  ## Hard Constraints
16
24
 
17
25
  - **Do NOT renumber or remove completed tasks.** All `[x]` tasks and their IDs must remain exactly as they are in the plan.
@@ -65,6 +65,7 @@ Titles live inside file content (headings, frontmatter), not in file or director
65
65
  PROJECT.md (living doc - what the project is right now)
66
66
  REQUIREMENTS.md (requirement contract - tracks active/validated/deferred/out-of-scope)
67
67
  DECISIONS.md (append-only register of architectural and pattern decisions)
68
+ KNOWLEDGE.md (append-only register of project-specific rules, patterns, and lessons learned)
68
69
  OVERRIDES.md (user-issued overrides that supersede plan content via /gsd steer)
69
70
  QUEUE.md (append-only log of queued milestones via /gsd queue)
70
71
  STATE.md
@@ -100,6 +101,7 @@ All auto-mode work happens inside a worktree at `.gsd/worktrees/<MID>/`. This is
100
101
  - **PROJECT.md** is a living document describing what the project is right now - current state only, updated at slice completion when stale
101
102
  - **REQUIREMENTS.md** tracks the requirement contract — requirements move between Active, Validated, Deferred, Blocked, and Out of Scope as slices prove or invalidate them. Update at slice completion when evidence supports a status change.
102
103
  - **DECISIONS.md** is an append-only register of architectural and pattern decisions - read it during planning/research, append to it during execution when a meaningful decision is made
104
+ - **KNOWLEDGE.md** is an append-only register of project-specific rules, patterns, and lessons learned. Read it at the start of every unit. Append to it when you discover a recurring issue, a non-obvious pattern, or a rule that future agents should follow.
103
105
  - **CONTEXT.md** files (milestone or slice level) capture the brief — scope, goals, constraints, and key decisions from discussion. When present, they are the authoritative source for what a milestone or slice is trying to achieve. Read them before planning or executing.
104
106
  - **Milestones** are major project phases (M001, M002, ...)
105
107
  - **Slices** are demoable vertical increments (S01, S02, ...) ordered by risk. After each slice completes, the roadmap is reassessed before the next slice begins.