@tekyzinc/gsd-t 2.74.13 → 3.10.10

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 (69) hide show
  1. package/CHANGELOG.md +165 -0
  2. package/README.md +117 -1
  3. package/bin/advisor-integration.js +93 -0
  4. package/bin/check-headless-sessions.js +140 -0
  5. package/bin/context-meter-config.cjs +101 -0
  6. package/bin/context-meter-config.test.cjs +101 -0
  7. package/bin/gsd-t-unattended-platform.js +381 -0
  8. package/bin/gsd-t-unattended-safety.js +766 -0
  9. package/bin/gsd-t-unattended.js +1259 -0
  10. package/bin/gsd-t.js +723 -19
  11. package/bin/handoff-lock.js +249 -0
  12. package/bin/headless-auto-spawn.js +328 -0
  13. package/bin/model-selector.js +224 -0
  14. package/bin/runway-estimator.js +242 -0
  15. package/bin/token-budget.js +96 -89
  16. package/bin/token-optimizer.js +471 -0
  17. package/bin/token-telemetry.js +246 -0
  18. package/commands/gsd-t-audit.md +3 -3
  19. package/commands/gsd-t-backlog-list.md +38 -0
  20. package/commands/gsd-t-brainstorm.md +3 -3
  21. package/commands/gsd-t-complete-milestone.md +24 -0
  22. package/commands/gsd-t-debug.md +124 -7
  23. package/commands/gsd-t-discuss.md +10 -3
  24. package/commands/gsd-t-doc-ripple.md +32 -4
  25. package/commands/gsd-t-execute.md +107 -52
  26. package/commands/gsd-t-help.md +22 -0
  27. package/commands/gsd-t-integrate.md +67 -4
  28. package/commands/gsd-t-optimization-apply.md +91 -0
  29. package/commands/gsd-t-optimization-reject.md +94 -0
  30. package/commands/gsd-t-partition.md +7 -0
  31. package/commands/gsd-t-pause.md +3 -0
  32. package/commands/gsd-t-plan.md +10 -3
  33. package/commands/gsd-t-prd.md +3 -3
  34. package/commands/gsd-t-quick.md +71 -9
  35. package/commands/gsd-t-reflect.md +3 -7
  36. package/commands/gsd-t-resume.md +86 -1
  37. package/commands/gsd-t-status.md +31 -0
  38. package/commands/gsd-t-test-sync.md +7 -0
  39. package/commands/gsd-t-unattended-stop.md +83 -0
  40. package/commands/gsd-t-unattended-watch.md +290 -0
  41. package/commands/gsd-t-unattended.md +414 -0
  42. package/commands/gsd-t-verify.md +12 -5
  43. package/commands/gsd-t-visualize.md +3 -7
  44. package/commands/gsd-t-wave.md +82 -18
  45. package/docs/GSD-T-README.md +69 -0
  46. package/docs/architecture.md +176 -4
  47. package/docs/infrastructure.md +221 -0
  48. package/docs/methodology.md +44 -0
  49. package/docs/prd-harness-evolution.md +51 -37
  50. package/docs/requirements.md +95 -0
  51. package/docs/unattended-windows-caveats.md +245 -0
  52. package/package.json +2 -2
  53. package/scripts/context-meter/count-tokens-client.js +221 -0
  54. package/scripts/context-meter/count-tokens-client.test.js +308 -0
  55. package/scripts/context-meter/test-injector.js +55 -0
  56. package/scripts/context-meter/threshold.js +88 -0
  57. package/scripts/context-meter/threshold.test.js +255 -0
  58. package/scripts/context-meter/transcript-parser.js +252 -0
  59. package/scripts/context-meter/transcript-parser.test.js +320 -0
  60. package/scripts/gsd-t-context-meter.e2e.test.js +415 -0
  61. package/scripts/gsd-t-context-meter.js +350 -0
  62. package/scripts/gsd-t-context-meter.test.js +417 -0
  63. package/scripts/gsd-t-heartbeat.js +2 -2
  64. package/scripts/gsd-t-statusline.js +23 -8
  65. package/templates/CLAUDE-global.md +17 -1
  66. package/templates/CLAUDE-project.md +26 -6
  67. package/templates/context-meter-config.json +10 -0
  68. package/templates/prompts/README.md +1 -1
  69. package/bin/task-counter.cjs +0 -161
@@ -1,10 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * GSD-T Token Budget — Session-level token tracking and graduated degradation
4
+ * GSD-T Token Budget — Session-level token tracking (three-band model)
5
5
  *
6
- * Reads .gsd-t/token-log.md for historical averages, tracks session usage,
7
- * and returns model override recommendations at degradation thresholds.
6
+ * Reads .gsd-t/.context-meter-state.json (M34) for real context-window
7
+ * readings, tracks session usage, and returns a three-band status signal
8
+ * (normal / warn / stop) that callers use to decide whether to proceed,
9
+ * log a warning, or halt cleanly.
10
+ *
11
+ * v3.0.0 (M35 — clean break from v2.0.0):
12
+ * - The `downgrade` and `conserve` bands were REMOVED. Silent model
13
+ * degradation and silent phase-skipping are anti-features — they
14
+ * violate GSD-T's "quality is non-negotiable" principle.
15
+ * - `getDegradationActions()` now returns `{band, pct, message}` instead
16
+ * of `{threshold, actions, modelOverrides}`. No `modelOverride`, no
17
+ * `skipPhases`, no `checkpoint` side-channel.
18
+ * - `warn` threshold tightened from 60% → 70%. `stop` tightened from
19
+ * 95% → 85% — keeps us clear of the runtime's native ~95% compact.
8
20
  *
9
21
  * Zero external dependencies (Node.js built-ins only).
10
22
  */
@@ -28,11 +40,16 @@ const BASE_ESTIMATES = {
28
40
  default: 6000,
29
41
  };
30
42
 
43
+ // v3.0.0 three-band thresholds. Lower-bound inclusive.
44
+ // pct < 70 → normal
45
+ // 70 ≤ pct < 85 → warn (informational — log, proceed)
46
+ // pct ≥ 85 → stop (halt cleanly, hand off to runway estimator)
47
+ const WARN_THRESHOLD_PCT = 70;
48
+ const STOP_THRESHOLD_PCT = 85;
49
+
31
50
  const THRESHOLDS = {
32
- warn: 60,
33
- downgrade: 70,
34
- conserve: 85,
35
- stop: 95,
51
+ warn: WARN_THRESHOLD_PCT,
52
+ stop: STOP_THRESHOLD_PCT,
36
53
  };
37
54
 
38
55
  // ── Exports ──────────────────────────────────────────────────────────────────
@@ -74,50 +91,57 @@ function estimateCost(model, taskType, options) {
74
91
 
75
92
  // ── getSessionStatus ─────────────────────────────────────────────────────────
76
93
 
94
+ const STATE_FILE_REL = path.join(".gsd-t", ".context-meter-state.json");
95
+ const STATE_STALE_MS = 5 * 60 * 1000;
96
+
77
97
  /**
78
98
  * @param {string} [projectDir]
79
99
  * @returns {{ consumed: number, estimated_remaining: number, pct: number, threshold: string }}
80
100
  *
81
- * v2.74.12: previously this read process.env.CLAUDE_CONTEXT_TOKENS_USED /
82
- * CLAUDE_CONTEXT_TOKENS_MAX, which Claude Code does not export so consumed
83
- * was always 0 and threshold was always 'normal'. The graduated-degradation
84
- * machinery downstream was inert. Now we synthesise a percent from the real
85
- * task counter at .gsd-t/.task-counter, mapping 0..limit linearly to
86
- * 0..100%. This keeps the API stable so commands that ask for thresholds
87
- * (downgrade/conserve/stop) get a real signal.
101
+ * v2.0.0 (M34): reads `.gsd-t/.context-meter-state.json` produced by the
102
+ * Context Meter PostToolUse hook. When that file is fresh (timestamp within
103
+ * the last 5 minutes), real `input_tokens` drive the response. Otherwise we
104
+ * fall back to a historical heuristic from `.gsd-t/token-log.md`, preserving
105
+ * graceful degradation for projects without the hook installed.
88
106
  */
89
107
  function getSessionStatus(projectDir) {
90
108
  const dir = projectDir || process.cwd();
91
- const counter = readTaskCounter(dir);
92
- const limit = counter.limit > 0 ? counter.limit : 5;
93
- const consumed = counter.count;
94
- const pct = Math.min(100, Math.round((consumed / limit) * 100 * 10) / 10);
95
- const threshold = resolveThreshold(pct);
96
- const estimated_remaining = Math.max(0, limit - consumed);
97
- return { consumed, estimated_remaining, pct, threshold };
109
+ const real = readContextMeterState(dir);
110
+ if (real) {
111
+ const consumed = real.inputTokens;
112
+ const window = real.modelWindowSize > 0 ? real.modelWindowSize : 200000;
113
+ const estimated_remaining = Math.max(0, window - consumed);
114
+ const pct = Math.round(real.pct * 10) / 10;
115
+ const threshold = resolveThreshold(pct);
116
+ return { consumed, estimated_remaining, pct, threshold };
117
+ }
118
+ return getSessionStatusHeuristic(dir);
98
119
  }
99
120
 
100
- function readTaskCounter(dir) {
121
+ function readContextMeterState(dir) {
101
122
  try {
102
- const fp = path.join(dir, ".gsd-t", ".task-counter");
123
+ const fp = path.join(dir, STATE_FILE_REL);
103
124
  const raw = fs.readFileSync(fp, "utf8");
104
125
  const s = JSON.parse(raw);
105
- let limit = 5;
106
- try {
107
- const cfgRaw = fs.readFileSync(path.join(dir, ".gsd-t", "task-counter-config.json"), "utf8");
108
- const cfg = JSON.parse(cfgRaw);
109
- if (cfg && typeof cfg.limit === "number" && cfg.limit > 0) limit = cfg.limit;
110
- } catch (_) {}
111
- if (process.env.GSD_T_TASK_LIMIT) {
112
- const n = parseInt(process.env.GSD_T_TASK_LIMIT, 10);
113
- if (!isNaN(n) && n > 0) limit = n;
114
- }
115
- return { count: typeof s.count === "number" ? s.count : 0, limit };
126
+ if (!s || typeof s.inputTokens !== "number" || typeof s.pct !== "number") return null;
127
+ if (!s.timestamp) return null;
128
+ const age = Date.now() - Date.parse(s.timestamp);
129
+ if (isNaN(age) || age > STATE_STALE_MS || age < 0) return null;
130
+ return s;
116
131
  } catch (_) {
117
- return { count: 0, limit: 5 };
132
+ return null;
118
133
  }
119
134
  }
120
135
 
136
+ function getSessionStatusHeuristic(dir) {
137
+ const window = 200000;
138
+ const consumed = readSessionConsumed(dir);
139
+ const estimated_remaining = Math.max(0, window - consumed);
140
+ const pct = window > 0 ? Math.round((consumed / window) * 100 * 10) / 10 : 0;
141
+ const threshold = resolveThreshold(pct);
142
+ return { consumed, estimated_remaining, pct, threshold };
143
+ }
144
+
121
145
  // ── recordUsage ──────────────────────────────────────────────────────────────
122
146
 
123
147
  /**
@@ -136,15 +160,20 @@ function recordUsage(usage) {
136
160
  fs.appendFileSync(fp, line);
137
161
  }
138
162
 
139
- // ── getDegradationActions ─────────────────────────────────────────────────────
163
+ // ── getDegradationActions (v3.0.0 — three-band) ─────────────────────────────
140
164
 
141
165
  /**
166
+ * v3.0.0 three-band response. The name is preserved for caller-identification
167
+ * convenience; the return shape is a CLEAN BREAK from v2.0.0 — no
168
+ * `modelOverrides`, no `actions` list, no `skipPhases`, no `checkpoint`
169
+ * side-channel. Callers that relied on those fields MUST be updated.
170
+ *
142
171
  * @param {string} [projectDir]
143
- * @returns {{ threshold: string, actions: string[], modelOverrides: object }}
172
+ * @returns {{ band: 'normal'|'warn'|'stop', pct: number, message: string }}
144
173
  */
145
174
  function getDegradationActions(projectDir) {
146
- const { threshold } = getSessionStatus(projectDir);
147
- return buildDegradationResponse(threshold);
175
+ const { threshold, pct } = getSessionStatus(projectDir);
176
+ return buildBandResponse(threshold, pct);
148
177
  }
149
178
 
150
179
  // ── estimateMilestoneCost ─────────────────────────────────────────────────────
@@ -156,69 +185,47 @@ function getDegradationActions(projectDir) {
156
185
  */
157
186
  function estimateMilestoneCost(remainingTasks, projectDir) {
158
187
  const status = getSessionStatus(projectDir);
159
- const limit = status.consumed + status.estimated_remaining || 5;
188
+ const window = status.consumed + status.estimated_remaining || 200000;
160
189
  const estimatedTokens = remainingTasks.reduce((sum, t) => {
161
190
  return sum + estimateCost(t.model, t.taskType, { complexity: t.complexity, projectDir });
162
191
  }, 0);
163
- // Estimate task-equivalents needed for the remaining work: cost-weighted
164
- // approximation against historical avg, capped at the configured task limit.
165
- const taskEquivalents = remainingTasks.length;
166
- const estimatedPct = limit > 0 ? Math.min(100, Math.round((taskEquivalents / limit) * 100 * 10) / 10) : 0;
167
- const feasible = taskEquivalents <= status.estimated_remaining;
192
+ const estimatedPct = window > 0 ? Math.min(100, Math.round((estimatedTokens / window) * 100 * 10) / 10) : 0;
193
+ const feasible = estimatedTokens <= status.estimated_remaining;
168
194
  return { estimatedTokens, estimatedPct, feasible };
169
195
  }
170
196
 
171
- // ── Internal: threshold resolution ───────────────────────────────────────────
197
+ // ── Internal: threshold resolution (v3.0.0 — three-band) ─────────────────────
172
198
 
173
199
  function resolveThreshold(pct) {
200
+ if (!Number.isFinite(pct)) return "normal";
174
201
  if (pct >= THRESHOLDS.stop) return "stop";
175
- if (pct >= THRESHOLDS.conserve) return "conserve";
176
- if (pct >= THRESHOLDS.downgrade) return "downgrade";
177
202
  if (pct >= THRESHOLDS.warn) return "warn";
178
203
  return "normal";
179
204
  }
180
205
 
181
- function buildDegradationResponse(threshold) {
182
- const responses = {
183
- normal: {
184
- threshold: "normal",
185
- actions: [],
186
- modelOverrides: {},
187
- },
188
- warn: {
189
- threshold: "warn",
190
- actions: ["Display budget alert", "Reduce iteration budgets to minimum (2)"],
191
- modelOverrides: {},
192
- },
193
- downgrade: {
194
- threshold: "downgrade",
195
- actions: ["Downgrade non-critical Sonnet to Haiku", "Skip exploratory testing", "Disable shadow-mode audit"],
196
- modelOverrides: {
197
- "sonnet:qa": "sonnet",
198
- "sonnet:execute": "haiku",
199
- "sonnet:doc-ripple": "skip",
200
- "opus:red-team": "sonnet",
201
- "haiku:*": "haiku",
202
- },
203
- },
204
- conserve: {
205
- threshold: "conserve",
206
- actions: ["Pause doc-ripple", "Pause design brief generation", "Checkpoint all progress"],
207
- modelOverrides: {
208
- "sonnet:qa": "sonnet",
209
- "sonnet:execute": "haiku",
210
- "sonnet:doc-ripple": "skip",
211
- "opus:red-team": "sonnet",
212
- "haiku:*": "haiku",
213
- },
214
- },
215
- stop: {
216
- threshold: "stop",
217
- actions: ["Hard stop", "Save all progress", "Display resume instruction"],
218
- modelOverrides: {},
219
- },
220
- };
221
- return responses[threshold] || responses.normal;
206
+ function buildBandResponse(band, pct) {
207
+ const safePct = Number.isFinite(pct) ? pct : 0;
208
+ switch (band) {
209
+ case "warn":
210
+ return {
211
+ band: "warn",
212
+ pct: safePct,
213
+ message: `Context ${safePct.toFixed(1)}% — warn band (≥${WARN_THRESHOLD_PCT}%). Informational only; proceed.`,
214
+ };
215
+ case "stop":
216
+ return {
217
+ band: "stop",
218
+ pct: safePct,
219
+ message: `Context ${safePct.toFixed(1)}% — stop band (≥${STOP_THRESHOLD_PCT}%). Halt cleanly; hand off to runway estimator / headless auto-spawn.`,
220
+ };
221
+ case "normal":
222
+ default:
223
+ return {
224
+ band: "normal",
225
+ pct: safePct,
226
+ message: `Context ${safePct.toFixed(1)}% — normal band. Proceed.`,
227
+ };
228
+ }
222
229
  }
223
230
 
224
231
  // ── Internal: token-log parsing ───────────────────────────────────────────────