ai-spec-dev 0.37.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +381 -1796
- package/RELEASE_LOG.md +231 -0
- package/cli/commands/create.ts +9 -1176
- package/cli/commands/dashboard.ts +1 -1
- package/cli/pipeline/helpers.ts +34 -0
- package/cli/pipeline/multi-repo.ts +483 -0
- package/cli/pipeline/single-repo.ts +755 -0
- package/cli/utils.ts +2 -0
- package/core/code-generator.ts +52 -341
- package/core/codegen/helpers.ts +219 -0
- package/core/codegen/topo-sort.ts +98 -0
- package/core/constitution-consolidator.ts +2 -2
- package/core/dsl-coverage-checker.ts +298 -0
- package/core/dsl-extractor.ts +19 -46
- package/core/dsl-feedback.ts +1 -1
- package/core/dsl-validator.ts +74 -0
- package/core/error-feedback.ts +95 -11
- package/core/frontend-context-loader.ts +27 -5
- package/core/knowledge-memory.ts +52 -0
- package/core/mock/fixtures.ts +89 -0
- package/core/mock/proxy.ts +380 -0
- package/core/mock-server-generator.ts +12 -460
- package/core/requirement-decomposer.ts +4 -28
- package/core/reviewer.ts +1 -1
- package/core/safe-json.ts +76 -0
- package/core/spec-updater.ts +5 -21
- package/core/token-budget.ts +124 -0
- package/core/vcr.ts +20 -1
- package/dist/cli/index.js +4110 -3534
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +4237 -3661
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +18 -16
- package/dist/index.d.ts +18 -16
- package/dist/index.js +310 -182
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +308 -180
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/purpose.md +173 -33
- package/tests/auto-consolidation.test.ts +109 -0
- package/tests/combined-generator.test.ts +81 -0
- package/tests/constitution-consolidator.test.ts +161 -0
- package/tests/constitution-generator.test.ts +94 -0
- package/tests/contract-bridge.test.ts +201 -0
- package/tests/design-dialogue.test.ts +108 -0
- package/tests/dsl-coverage-checker.test.ts +230 -0
- package/tests/dsl-feedback.test.ts +45 -0
- package/tests/dsl-validator-xref.test.ts +99 -0
- package/tests/error-feedback-repair.test.ts +319 -0
- package/tests/error-feedback-validation.test.ts +91 -0
- package/tests/frontend-context-loader.test.ts +609 -0
- package/tests/global-constitution.test.ts +110 -0
- package/tests/key-store.test.ts +73 -0
- package/tests/knowledge-memory.test.ts +327 -0
- package/tests/project-index.test.ts +206 -0
- package/tests/prompt-hasher.test.ts +19 -0
- package/tests/requirement-decomposer.test.ts +171 -0
- package/tests/reviewer.test.ts +4 -1
- package/tests/run-logger.test.ts +289 -0
- package/tests/run-snapshot.test.ts +113 -0
- package/tests/safe-json.test.ts +63 -0
- package/tests/spec-updater.test.ts +161 -0
- package/tests/test-generator.test.ts +146 -0
- package/tests/token-budget.test.ts +124 -0
- package/tests/vcr-hash.test.ts +101 -0
- package/tests/workspace-loader.test.ts +277 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* token-budget.ts — Lightweight token estimation and priority-based context assembly.
|
|
3
|
+
*
|
|
4
|
+
* Prevents silent context window overflow by estimating tokens and
|
|
5
|
+
* trimming lower-priority sections when the budget is exceeded.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
|
|
10
|
+
// ─── Token Estimation ────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
/** CJK character range. */
|
|
13
|
+
const CJK_RANGE = /[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef]/g;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Estimate token count for a string.
|
|
17
|
+
* CJK characters ≈ 1 token each; English/code ≈ 1 token per 4 characters.
|
|
18
|
+
* This is deliberately conservative (over-estimates slightly) to avoid
|
|
19
|
+
* exceeding the actual context window.
|
|
20
|
+
*/
|
|
21
|
+
export function estimateTokens(text: string): number {
|
|
22
|
+
if (!text) return 0;
|
|
23
|
+
const cjkCount = (text.match(CJK_RANGE) ?? []).length;
|
|
24
|
+
const nonCjkLength = text.length - cjkCount;
|
|
25
|
+
return Math.ceil(cjkCount + nonCjkLength / 4);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── Budget Assembly ────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A named section of context with a priority level.
|
|
32
|
+
* Lower priority number = higher importance (trimmed last).
|
|
33
|
+
*/
|
|
34
|
+
export interface BudgetSection {
|
|
35
|
+
/** Section name for logging. */
|
|
36
|
+
name: string;
|
|
37
|
+
/** The actual text content. */
|
|
38
|
+
content: string;
|
|
39
|
+
/** Priority: 1 = highest (never trim), 5 = lowest (trim first). */
|
|
40
|
+
priority: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface BudgetResult {
|
|
44
|
+
/** The assembled prompt text (all included sections concatenated). */
|
|
45
|
+
assembledPrompt: string;
|
|
46
|
+
/** Total estimated tokens in the assembled prompt. */
|
|
47
|
+
totalTokens: number;
|
|
48
|
+
/** Names of sections that were trimmed or dropped. */
|
|
49
|
+
trimmedSections: string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Assemble context sections within a token budget.
|
|
54
|
+
*
|
|
55
|
+
* Sections are added in priority order (P1 first). When the budget is
|
|
56
|
+
* exceeded, lower-priority sections are truncated or dropped entirely.
|
|
57
|
+
*
|
|
58
|
+
* @param sections - Context sections to assemble.
|
|
59
|
+
* @param maxTokens - Maximum token budget.
|
|
60
|
+
*/
|
|
61
|
+
export function assembleSections(
|
|
62
|
+
sections: BudgetSection[],
|
|
63
|
+
maxTokens: number
|
|
64
|
+
): BudgetResult {
|
|
65
|
+
// Sort by priority (ascending = most important first)
|
|
66
|
+
const sorted = [...sections].sort((a, b) => a.priority - b.priority);
|
|
67
|
+
|
|
68
|
+
const included: string[] = [];
|
|
69
|
+
const trimmedSections: string[] = [];
|
|
70
|
+
let usedTokens = 0;
|
|
71
|
+
|
|
72
|
+
for (const section of sorted) {
|
|
73
|
+
if (!section.content) continue;
|
|
74
|
+
|
|
75
|
+
const sectionTokens = estimateTokens(section.content);
|
|
76
|
+
|
|
77
|
+
if (usedTokens + sectionTokens <= maxTokens) {
|
|
78
|
+
// Fits entirely
|
|
79
|
+
included.push(section.content);
|
|
80
|
+
usedTokens += sectionTokens;
|
|
81
|
+
} else {
|
|
82
|
+
const remainingBudget = maxTokens - usedTokens;
|
|
83
|
+
if (remainingBudget <= 100) {
|
|
84
|
+
// Not enough room even for a truncated version
|
|
85
|
+
trimmedSections.push(section.name);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Truncate to fit remaining budget (approximate: 4 chars per token for safety)
|
|
90
|
+
const charBudget = Math.floor(remainingBudget * 3);
|
|
91
|
+
const truncated = section.content.slice(0, charBudget);
|
|
92
|
+
included.push(truncated + `\n\n... [${section.name} truncated — context budget reached]`);
|
|
93
|
+
trimmedSections.push(section.name);
|
|
94
|
+
usedTokens = maxTokens; // budget exhausted
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const assembledPrompt = included.join("\n\n");
|
|
99
|
+
|
|
100
|
+
if (trimmedSections.length > 0) {
|
|
101
|
+
console.log(
|
|
102
|
+
chalk.yellow(
|
|
103
|
+
` ⚠ Token budget: ${usedTokens}/${maxTokens} tokens. Trimmed: ${trimmedSections.join(", ")}`
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { assembledPrompt, totalTokens: usedTokens, trimmedSections };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─── Default Budgets ─────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
/** Default context token budgets per provider. */
|
|
114
|
+
export const DEFAULT_TOKEN_BUDGETS: Record<string, number> = {
|
|
115
|
+
gemini: 900_000,
|
|
116
|
+
claude: 180_000,
|
|
117
|
+
openai: 120_000,
|
|
118
|
+
deepseek: 60_000,
|
|
119
|
+
default: 100_000,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export function getDefaultBudget(providerName: string): number {
|
|
123
|
+
return DEFAULT_TOKEN_BUDGETS[providerName] ?? DEFAULT_TOKEN_BUDGETS.default;
|
|
124
|
+
}
|
package/core/vcr.ts
CHANGED
|
@@ -141,13 +141,14 @@ export class VcrRecordingProvider implements AIProvider {
|
|
|
141
141
|
*/
|
|
142
142
|
export class VcrReplayProvider implements AIProvider {
|
|
143
143
|
private index = 0;
|
|
144
|
+
private _mismatches: Array<{ index: number; expected: string; actual: string }> = [];
|
|
144
145
|
|
|
145
146
|
constructor(private readonly recording: VcrRecording) {}
|
|
146
147
|
|
|
147
148
|
get providerName() { return "vcr-replay"; }
|
|
148
149
|
get modelName() { return this.recording.runId; }
|
|
149
150
|
|
|
150
|
-
async generate(
|
|
151
|
+
async generate(prompt: string, systemInstruction?: string): Promise<string> {
|
|
151
152
|
const entry = this.recording.entries[this.index++];
|
|
152
153
|
if (!entry) {
|
|
153
154
|
throw new Error(
|
|
@@ -155,11 +156,29 @@ export class VcrReplayProvider implements AIProvider {
|
|
|
155
156
|
`responses have been consumed. The pipeline made more AI calls than the recording has.`
|
|
156
157
|
);
|
|
157
158
|
}
|
|
159
|
+
|
|
160
|
+
// Validate prompt hash to detect pipeline drift
|
|
161
|
+
const actualHash = createHash("sha256")
|
|
162
|
+
.update(prompt + "\x00" + (systemInstruction ?? ""))
|
|
163
|
+
.digest("hex")
|
|
164
|
+
.slice(0, 8);
|
|
165
|
+
if (actualHash !== entry.callHash) {
|
|
166
|
+
this._mismatches.push({
|
|
167
|
+
index: entry.index,
|
|
168
|
+
expected: entry.callHash,
|
|
169
|
+
actual: actualHash,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
158
173
|
return entry.response;
|
|
159
174
|
}
|
|
160
175
|
|
|
161
176
|
get remaining() { return this.recording.entries.length - this.index; }
|
|
162
177
|
get consumed() { return this.index; }
|
|
178
|
+
|
|
179
|
+
/** Returns prompt hash mismatches detected during replay. */
|
|
180
|
+
get mismatches() { return this._mismatches; }
|
|
181
|
+
get hasMismatches() { return this._mismatches.length > 0; }
|
|
163
182
|
}
|
|
164
183
|
|
|
165
184
|
// ─── Loader helpers ───────────────────────────────────────────────────────────
|