guardian-framework 0.1.62 → 0.1.64
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/dist/cli.js +8 -3
- package/dist/exports.js +1 -1
- package/package.json +1 -1
- package/templates/pi/extensions/architect-lib/forge-adapter.ts +18 -0
- package/templates/pi/extensions/architect-lib/helpers.ts +2 -0
- package/templates/pi/extensions/architect-lib/tdd-generator.ts +354 -0
- package/templates/pi/extensions/architect-lib/types.ts +1 -0
- package/templates/pi/extensions/architect.ts +36 -4
- package/templates/pi/preflight_report.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3178,7 +3178,7 @@ __export(exports_package, {
|
|
|
3178
3178
|
bin: () => bin,
|
|
3179
3179
|
author: () => author
|
|
3180
3180
|
});
|
|
3181
|
-
var name = "guardian-framework", version = "0.1.
|
|
3181
|
+
var name = "guardian-framework", version = "0.1.64", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
|
|
3182
3182
|
var init_package = __esm(() => {
|
|
3183
3183
|
exports = {
|
|
3184
3184
|
".": {
|
|
@@ -15085,8 +15085,13 @@ async function runVerify(targetDir = process.cwd()) {
|
|
|
15085
15085
|
|
|
15086
15086
|
// src/index.ts
|
|
15087
15087
|
init_manifest();
|
|
15088
|
-
|
|
15089
|
-
|
|
15088
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
15089
|
+
import { join as join20 } from "path";
|
|
15090
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
15091
|
+
var __dirname3 = fileURLToPath3(new URL(".", import.meta.url));
|
|
15092
|
+
var PKG = JSON.parse(readFileSync16(join20(__dirname3, "..", "package.json"), "utf-8"));
|
|
15093
|
+
var VERSION = PKG.version;
|
|
15094
|
+
var PKG_NAME = PKG.name;
|
|
15090
15095
|
var _newerVersion = null;
|
|
15091
15096
|
function checkForUpdateSync() {
|
|
15092
15097
|
try {
|
package/dist/exports.js
CHANGED
|
@@ -1963,7 +1963,7 @@ __export(exports_package, {
|
|
|
1963
1963
|
bin: () => bin,
|
|
1964
1964
|
author: () => author
|
|
1965
1965
|
});
|
|
1966
|
-
var name = "guardian-framework", version = "0.1.
|
|
1966
|
+
var name = "guardian-framework", version = "0.1.64", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
|
|
1967
1967
|
var init_package = __esm(() => {
|
|
1968
1968
|
exports = {
|
|
1969
1969
|
".": {
|
package/package.json
CHANGED
|
@@ -54,6 +54,24 @@ export function readRepository(cwd: string): string | null {
|
|
|
54
54
|
return null;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Read project language from guardian-manifest.json (defaults to "typescript")
|
|
59
|
+
*/
|
|
60
|
+
export function readLanguage(cwd: string): string {
|
|
61
|
+
try {
|
|
62
|
+
const manifestPath = join(cwd, "guardian-manifest.json");
|
|
63
|
+
if (existsSync(manifestPath)) {
|
|
64
|
+
const raw = readFileSync(manifestPath, "utf-8");
|
|
65
|
+
const manifest = JSON.parse(raw) as { language?: string; templateContext?: { language?: string } };
|
|
66
|
+
if (manifest.language) return manifest.language;
|
|
67
|
+
if (manifest.templateContext?.language) return manifest.templateContext.language;
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// fall through to default
|
|
71
|
+
}
|
|
72
|
+
return "typescript";
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
/**
|
|
58
76
|
* Get forge base URL. For GitLab, detects self-hosted instances via glab config.
|
|
59
77
|
*/
|
|
@@ -5,6 +5,7 @@ import type { ExtensionContext, EpicState, ModuleComponent, ArchitectureSlice, R
|
|
|
5
5
|
import {
|
|
6
6
|
readRepoTool as adapterReadRepoTool,
|
|
7
7
|
readRepository as adapterReadRepository,
|
|
8
|
+
readLanguage as adapterReadLanguage,
|
|
8
9
|
getGitBaseUrl as adapterGetGitBaseUrl,
|
|
9
10
|
commandExists as adapterCommandExists,
|
|
10
11
|
runScript as adapterRunScript,
|
|
@@ -30,6 +31,7 @@ export function log(ctx: ExtensionContext, message: string, level = "info") {
|
|
|
30
31
|
export const runScript = adapterRunScript;
|
|
31
32
|
export const readRepoTool = adapterReadRepoTool;
|
|
32
33
|
export const readRepository = adapterReadRepository;
|
|
34
|
+
export const readLanguage = adapterReadLanguage;
|
|
33
35
|
export const getGitBaseUrl = adapterGetGitBaseUrl;
|
|
34
36
|
export const commandExists = adapterCommandExists;
|
|
35
37
|
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TDD Test Generator for Guardian Architect Extension
|
|
3
|
+
*
|
|
4
|
+
* Deterministic test file generation from architecture module components.
|
|
5
|
+
* No LLM involvement — tests are generated from structured component data.
|
|
6
|
+
*
|
|
7
|
+
* When --tdd is enabled on an epic via /architect --epic "Name" --tdd,
|
|
8
|
+
* this generator creates failing test files before implementation issues
|
|
9
|
+
* reach the agent. The agent makes them green via Red→Green→Refactor.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import type { ModuleComponent } from "./types.ts";
|
|
15
|
+
|
|
16
|
+
// ── Supported Languages ──
|
|
17
|
+
|
|
18
|
+
const TDD_LANGUAGES = new Set(["typescript", "rust", "python", "go", "java"]);
|
|
19
|
+
|
|
20
|
+
export function isTddSupported(language: string): boolean {
|
|
21
|
+
return TDD_LANGUAGES.has(language);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ── Main Generator ──
|
|
25
|
+
|
|
26
|
+
export interface TddGenerateOptions {
|
|
27
|
+
/** Array of components from the epic's slice */
|
|
28
|
+
components: ModuleComponent[];
|
|
29
|
+
/** Module identifier (e.g., "audit-ingestion") */
|
|
30
|
+
moduleId: string;
|
|
31
|
+
/** Project root directory */
|
|
32
|
+
cwd: string;
|
|
33
|
+
/** Language from manifest (default: "typescript") */
|
|
34
|
+
language: string;
|
|
35
|
+
/** Base test directory (default: "tests/unit") */
|
|
36
|
+
testBaseDir?: string;
|
|
37
|
+
/** Module name for path construction */
|
|
38
|
+
moduleName?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate failing test files for all components in an epic slice.
|
|
43
|
+
* Returns list of generated file paths.
|
|
44
|
+
*/
|
|
45
|
+
export function generateEpicTestFiles(options: TddGenerateOptions): string[] {
|
|
46
|
+
const {
|
|
47
|
+
components,
|
|
48
|
+
moduleId,
|
|
49
|
+
cwd,
|
|
50
|
+
language,
|
|
51
|
+
testBaseDir = "tests/unit",
|
|
52
|
+
moduleName,
|
|
53
|
+
} = options;
|
|
54
|
+
|
|
55
|
+
if (!isTddSupported(language)) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const generated: string[] = [];
|
|
60
|
+
const modulePath = moduleName || moduleId;
|
|
61
|
+
|
|
62
|
+
for (const comp of components) {
|
|
63
|
+
const componentId = comp.name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
64
|
+
const testDir = join(cwd, testBaseDir, modulePath, componentId);
|
|
65
|
+
mkdirSync(testDir, { recursive: true });
|
|
66
|
+
|
|
67
|
+
const fileName = getTestFileName(componentId, language);
|
|
68
|
+
const filePath = join(testDir, fileName);
|
|
69
|
+
|
|
70
|
+
// Skip if file already exists (don't overwrite user edits)
|
|
71
|
+
if (existsSync(filePath)) continue;
|
|
72
|
+
|
|
73
|
+
const content = generateTestContent(comp, language, moduleId, componentId);
|
|
74
|
+
writeFileSync(filePath, content, "utf-8");
|
|
75
|
+
generated.push(filePath);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return generated;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── Test File Name ──
|
|
82
|
+
|
|
83
|
+
function getTestFileName(componentId: string, language: string): string {
|
|
84
|
+
switch (language) {
|
|
85
|
+
case "typescript":
|
|
86
|
+
return `${componentId}.test.ts`;
|
|
87
|
+
case "rust":
|
|
88
|
+
return `${componentId}_test.rs`;
|
|
89
|
+
case "python":
|
|
90
|
+
return `test_${componentId}.py`;
|
|
91
|
+
case "go":
|
|
92
|
+
return `${componentId}_test.go`;
|
|
93
|
+
case "java":
|
|
94
|
+
return `${toPascalCase(componentId)}Test.java`;
|
|
95
|
+
default:
|
|
96
|
+
return `${componentId}.test.ts`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Test Content Generation ──
|
|
101
|
+
|
|
102
|
+
function generateTestContent(
|
|
103
|
+
component: ModuleComponent,
|
|
104
|
+
language: string,
|
|
105
|
+
moduleId: string,
|
|
106
|
+
componentId: string,
|
|
107
|
+
): string {
|
|
108
|
+
switch (language) {
|
|
109
|
+
case "rust":
|
|
110
|
+
return generateRust(component, moduleId, componentId);
|
|
111
|
+
case "python":
|
|
112
|
+
return generatePython(component, moduleId, componentId);
|
|
113
|
+
case "go":
|
|
114
|
+
return generateGo(component, moduleId, componentId);
|
|
115
|
+
case "java":
|
|
116
|
+
return generateJava(component, moduleId, componentId);
|
|
117
|
+
case "typescript":
|
|
118
|
+
default:
|
|
119
|
+
return generateTypeScript(component, moduleId, componentId);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── TypeScript Test Generator ──
|
|
124
|
+
|
|
125
|
+
function generateTypeScript(
|
|
126
|
+
component: ModuleComponent,
|
|
127
|
+
moduleId: string,
|
|
128
|
+
componentId: string,
|
|
129
|
+
): string {
|
|
130
|
+
const lines: string[] = [
|
|
131
|
+
`// Generated by Guardian TDD`,
|
|
132
|
+
`// From: ${component.name} (${moduleId} module)`,
|
|
133
|
+
`// These tests will FAIL until ${component.name} is implemented.`,
|
|
134
|
+
``,
|
|
135
|
+
`import { describe, it, expect } from "bun:test";`,
|
|
136
|
+
``,
|
|
137
|
+
`describe("${component.name}", () => {`,
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
// Instantiation test
|
|
141
|
+
lines.push(...indent([
|
|
142
|
+
`it("should be defined", () => {`,
|
|
143
|
+
` expect(false).toBe(true);`,
|
|
144
|
+
` // TODO: uncomment when ${component.name} is implemented`,
|
|
145
|
+
` // const instance = new ${component.name.replace(/[^a-zA-Z0-9]/g, "")}();`,
|
|
146
|
+
` // expect(instance).toBeDefined();`,
|
|
147
|
+
`});`,
|
|
148
|
+
], 1));
|
|
149
|
+
|
|
150
|
+
// Dependency interaction tests
|
|
151
|
+
for (const dep of component.dependencies) {
|
|
152
|
+
lines.push(...indent([
|
|
153
|
+
`it("should interact with ${dep}", () => {`,
|
|
154
|
+
` expect(false).toBe(true);`,
|
|
155
|
+
` // TODO: implement dependency test for ${dep}`,
|
|
156
|
+
` // const mock = { /* mock ${dep} interface */ };`,
|
|
157
|
+
` // const result = new ${component.name.replace(/[^a-zA-Z0-9]/g, "")}(mock);`,
|
|
158
|
+
` // expect(result).toBeDefined();`,
|
|
159
|
+
`});`,
|
|
160
|
+
], 1));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
lines.push(`});`);
|
|
164
|
+
lines.push(``);
|
|
165
|
+
return lines.join("\n");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Rust Test Generator ──
|
|
169
|
+
|
|
170
|
+
function generateRust(
|
|
171
|
+
component: ModuleComponent,
|
|
172
|
+
moduleId: string,
|
|
173
|
+
componentId: string,
|
|
174
|
+
): string {
|
|
175
|
+
const lines: string[] = [
|
|
176
|
+
`// Generated by Guardian TDD`,
|
|
177
|
+
`// From: ${component.name} (${moduleId} module)`,
|
|
178
|
+
`// These tests will FAIL until ${component.name} is implemented.`,
|
|
179
|
+
``,
|
|
180
|
+
`#[cfg(test)]`,
|
|
181
|
+
`mod tests {`,
|
|
182
|
+
` use super::*;`,
|
|
183
|
+
``,
|
|
184
|
+
` #[test]`,
|
|
185
|
+
` fn test_${componentId}_is_defined() {`,
|
|
186
|
+
` // TODO: uncomment when ${component.name} is implemented`,
|
|
187
|
+
` // let _instance = ${toPascalCase(componentId)}::new();`,
|
|
188
|
+
` assert!(false, "Test not yet implemented");`,
|
|
189
|
+
` }`,
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
for (const dep of component.dependencies) {
|
|
193
|
+
lines.push(...[
|
|
194
|
+
``,
|
|
195
|
+
` #[test]`,
|
|
196
|
+
` fn test_${componentId}_interacts_with_${dep.replace(/[^a-z0-9]/gi, "_").toLowerCase()}() {`,
|
|
197
|
+
` // TODO: implement dependency test for ${dep}`,
|
|
198
|
+
` // let mock = Mock${dep.replace(/[^a-zA-Z0-9]/g, "")}::new();`,
|
|
199
|
+
` // let result = ${toPascalCase(componentId)}::new(mock);`,
|
|
200
|
+
` assert!(false, "Dependency test not yet implemented");`,
|
|
201
|
+
` }`,
|
|
202
|
+
]);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
lines.push(`}`);
|
|
206
|
+
lines.push(``);
|
|
207
|
+
return lines.join("\n");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── Python Test Generator ──
|
|
211
|
+
|
|
212
|
+
function generatePython(
|
|
213
|
+
component: ModuleComponent,
|
|
214
|
+
moduleId: string,
|
|
215
|
+
componentId: string,
|
|
216
|
+
): string {
|
|
217
|
+
const clsName = toPascalCase(componentId);
|
|
218
|
+
const lines: string[] = [
|
|
219
|
+
`# Generated by Guardian TDD`,
|
|
220
|
+
`# From: ${component.name} (${moduleId} module)`,
|
|
221
|
+
`# These tests will FAIL until ${component.name} is implemented.`,
|
|
222
|
+
``,
|
|
223
|
+
`import pytest`,
|
|
224
|
+
``,
|
|
225
|
+
``,
|
|
226
|
+
`class Test${clsName}:`,
|
|
227
|
+
` """Tests for ${component.name}."""`,
|
|
228
|
+
``,
|
|
229
|
+
` def test_${componentId}_is_defined(self):`,
|
|
230
|
+
` """${component.name} should be instantiable."""`,
|
|
231
|
+
` assert False, "TODO: uncomment when ${component.name} is implemented"`,
|
|
232
|
+
` # instance = ${clsName}()`,
|
|
233
|
+
` # assert instance is not None`,
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
for (const dep of component.dependencies) {
|
|
237
|
+
lines.push(...[
|
|
238
|
+
``,
|
|
239
|
+
` def test_${componentId}_interacts_with_${dep.replace(/[^a-z0-9]/gi, "_").toLowerCase()}(self):`,
|
|
240
|
+
` """${component.name} should interact with ${dep}."""`,
|
|
241
|
+
` assert False, "TODO: implement dependency test for ${dep}"`,
|
|
242
|
+
` # mock = MagicMock(spec=${dep.replace(/[^a-zA-Z0-9]/g, "")})`,
|
|
243
|
+
` # instance = ${clsName}(mock)`,
|
|
244
|
+
` # result = instance.execute()`,
|
|
245
|
+
` # assert result is not None`,
|
|
246
|
+
]);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
lines.push(``);
|
|
250
|
+
return lines.join("\n");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ── Go Test Generator ──
|
|
254
|
+
|
|
255
|
+
function generateGo(
|
|
256
|
+
component: ModuleComponent,
|
|
257
|
+
moduleId: string,
|
|
258
|
+
componentId: string,
|
|
259
|
+
): string {
|
|
260
|
+
const lines: string[] = [
|
|
261
|
+
`// Generated by Guardian TDD`,
|
|
262
|
+
`// From: ${component.name} (${moduleId} module)`,
|
|
263
|
+
`// These tests will FAIL until ${component.name} is implemented.`,
|
|
264
|
+
``,
|
|
265
|
+
`package ${moduleId.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()}`,
|
|
266
|
+
``,
|
|
267
|
+
`import "testing"`,
|
|
268
|
+
``,
|
|
269
|
+
`func Test${toPascalCase(componentId)}_IsDefined(t *testing.T) {`,
|
|
270
|
+
` // TODO: uncomment when ${component.name} is implemented`,
|
|
271
|
+
` // instance := New${toPascalCase(componentId)}()`,
|
|
272
|
+
` // if instance == nil {`,
|
|
273
|
+
` // t.Fail()`,
|
|
274
|
+
` // }`,
|
|
275
|
+
` t.Fatal("Test not yet implemented")`,
|
|
276
|
+
`}`,
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
for (const dep of component.dependencies) {
|
|
280
|
+
lines.push(...[
|
|
281
|
+
``,
|
|
282
|
+
`func Test${toPascalCase(componentId)}_InteractsWith_${capitalize(dep.replace(/[^a-zA-Z0-9]/g, ""))}(t *testing.T) {`,
|
|
283
|
+
` // TODO: implement dependency test for ${dep}`,
|
|
284
|
+
` // mock := new(Mock${capitalize(dep.replace(/[^a-zA-Z0-9]/g, ""))})`,
|
|
285
|
+
` // instance := New${toPascalCase(componentId)}(mock)`,
|
|
286
|
+
` // result := instance.Execute()`,
|
|
287
|
+
` t.Fatal("Dependency test not yet implemented")`,
|
|
288
|
+
`}`,
|
|
289
|
+
]);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
lines.push(``);
|
|
293
|
+
return lines.join("\n");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ── Java Test Generator ──
|
|
297
|
+
|
|
298
|
+
function generateJava(
|
|
299
|
+
component: ModuleComponent,
|
|
300
|
+
moduleId: string,
|
|
301
|
+
componentId: string,
|
|
302
|
+
): string {
|
|
303
|
+
const clsName = toPascalCase(componentId);
|
|
304
|
+
const lines: string[] = [
|
|
305
|
+
`// Generated by Guardian TDD`,
|
|
306
|
+
`// From: ${component.name} (${moduleId} module)`,
|
|
307
|
+
`// These tests will FAIL until ${component.name} is implemented.`,
|
|
308
|
+
``,
|
|
309
|
+
`import org.junit.jupiter.api.Test;`,
|
|
310
|
+
`import static org.junit.jupiter.api.Assertions.*;`,
|
|
311
|
+
``,
|
|
312
|
+
`class ${clsName}Test {`,
|
|
313
|
+
``,
|
|
314
|
+
` @Test`,
|
|
315
|
+
` void test${clsName}_IsDefined() {`,
|
|
316
|
+
` // TODO: uncomment when ${component.name} is implemented`,
|
|
317
|
+
` // ${clsName} instance = new ${clsName}();`,
|
|
318
|
+
` // assertNotNull(instance);`,
|
|
319
|
+
` fail("Test not yet implemented");`,
|
|
320
|
+
` }`,
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
for (const dep of component.dependencies) {
|
|
324
|
+
lines.push(...[
|
|
325
|
+
``,
|
|
326
|
+
` @Test`,
|
|
327
|
+
` void test${clsName}_InteractsWith_${capitalize(dep.replace(/[^a-zA-Z0-9]/g, ""))}() {`,
|
|
328
|
+
` // TODO: implement dependency test for ${dep}`,
|
|
329
|
+
` // ${capitalize(dep.replace(/[^a-zA-Z0-9]/g, ""))} mock = mock(${capitalize(dep.replace(/[^a-zA-Z0-9]/g, ""))}.class);`,
|
|
330
|
+
` // ${clsName} instance = new ${clsName}(mock);`,
|
|
331
|
+
` fail("Dependency test not yet implemented");`,
|
|
332
|
+
` }`,
|
|
333
|
+
]);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
lines.push(`}`);
|
|
337
|
+
lines.push(``);
|
|
338
|
+
return lines.join("\n");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// ── Utility ──
|
|
342
|
+
|
|
343
|
+
function capitalize(s: string): string {
|
|
344
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function toPascalCase(s: string): string {
|
|
348
|
+
return s.split("-").map(capitalize).join("");
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function indent(lines: string[], level: number): string[] {
|
|
352
|
+
const pad = " ".repeat(level);
|
|
353
|
+
return lines.map((l) => `${pad}${l}`);
|
|
354
|
+
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
parseRoadmap,
|
|
25
25
|
readGroupId,
|
|
26
26
|
readRepository,
|
|
27
|
+
readLanguage,
|
|
27
28
|
readRepoTool,
|
|
28
29
|
runScript,
|
|
29
30
|
saveRoadmapState,
|
|
@@ -34,6 +35,10 @@ import {
|
|
|
34
35
|
generateIssueMarkdown,
|
|
35
36
|
generateProofingMarkdown,
|
|
36
37
|
} from "./architect-lib/generators";
|
|
38
|
+
import {
|
|
39
|
+
generateEpicTestFiles,
|
|
40
|
+
isTddSupported,
|
|
41
|
+
} from "./architect-lib/tdd-generator";
|
|
37
42
|
|
|
38
43
|
// ── Helpers ──
|
|
39
44
|
|
|
@@ -94,6 +99,7 @@ function formatEpicStatus(state: EpicState | null): string {
|
|
|
94
99
|
`Progress: ${done}/${total} components`,
|
|
95
100
|
`Issues: ${(state.issues || []).length}`,
|
|
96
101
|
`Pipeline: ${state.status}`,
|
|
102
|
+
state.tdd ? `TDD: enabled` : `TDD: disabled`,
|
|
97
103
|
].join("\n");
|
|
98
104
|
}
|
|
99
105
|
|
|
@@ -114,6 +120,7 @@ class EpicManager {
|
|
|
114
120
|
ctx: ExtensionContext,
|
|
115
121
|
name: string,
|
|
116
122
|
trackingIssueId?: string,
|
|
123
|
+
tdd = false,
|
|
117
124
|
): Promise<EpicState> {
|
|
118
125
|
const moduleFiles = discoverModules(this.cwd);
|
|
119
126
|
if (moduleFiles.length === 0) {
|
|
@@ -293,6 +300,20 @@ class EpicManager {
|
|
|
293
300
|
}
|
|
294
301
|
issues.push(readinessEntry);
|
|
295
302
|
|
|
303
|
+
// ── TDD test generation ──
|
|
304
|
+
let tddTestFiles: string[] = [];
|
|
305
|
+
if (tdd) {
|
|
306
|
+
const language = readLanguage(this.cwd);
|
|
307
|
+
if (isTddSupported(language)) {
|
|
308
|
+
tddTestFiles = generateEpicTestFiles({
|
|
309
|
+
components: slice.nextLogicalSlice,
|
|
310
|
+
moduleId: slice.module,
|
|
311
|
+
cwd: this.cwd,
|
|
312
|
+
language,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
296
317
|
const state: EpicState = {
|
|
297
318
|
name,
|
|
298
319
|
trackingIssueId: effectiveTrackingId,
|
|
@@ -302,6 +323,7 @@ class EpicManager {
|
|
|
302
323
|
status: "planning",
|
|
303
324
|
currentIssueIndex: 0,
|
|
304
325
|
createdAt: new Date().toISOString(),
|
|
326
|
+
tdd,
|
|
305
327
|
};
|
|
306
328
|
|
|
307
329
|
this.state = state;
|
|
@@ -339,7 +361,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
339
361
|
const tokens = raw ? parseArgs(raw) : [];
|
|
340
362
|
if (tokens.length === 0) {
|
|
341
363
|
ctx.ui.notify(
|
|
342
|
-
"Usage: /architect [--epic Name] [--tracking-issue N] | --roadmap | --phase \"Phase 1\" | --phase-status | --phase-done <N> | --phase-module-done <N> \"Module\" | status | next-epic | abort",
|
|
364
|
+
"Usage: /architect [--epic Name] [--tracking-issue N] [--tdd] | --roadmap | --phase \"Phase 1\" | --phase-status | --phase-done <N> | --phase-module-done <N> \"Module\" | status | next-epic | abort",
|
|
343
365
|
"info",
|
|
344
366
|
);
|
|
345
367
|
return;
|
|
@@ -701,19 +723,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
701
723
|
|
|
702
724
|
const epicName = findFlag(tokens, "--epic");
|
|
703
725
|
const trackingIssueId = findFlag(tokens, "--tracking-issue");
|
|
726
|
+
const tddEnabled = tokens.includes("--tdd");
|
|
704
727
|
|
|
705
728
|
if (!epicName) {
|
|
706
|
-
ctx.ui.notify('Usage: /architect --epic "Epic Name" [--tracking-issue N]', "error");
|
|
729
|
+
ctx.ui.notify('Usage: /architect --epic "Epic Name" [--tracking-issue N] [--tdd]', "error");
|
|
707
730
|
return;
|
|
708
731
|
}
|
|
709
732
|
|
|
710
733
|
try {
|
|
711
734
|
if (!epicName || epicName.trim() === "") {
|
|
712
|
-
ctx.ui.notify('Usage: /architect --epic "Epic Name"', "error");
|
|
735
|
+
ctx.ui.notify('Usage: /architect --epic "Epic Name" [--tracking-issue N] [--tdd]', "error");
|
|
713
736
|
return;
|
|
714
737
|
}
|
|
715
738
|
|
|
716
|
-
const state = await manager.startEpic(ctx, epicName, trackingIssueId);
|
|
739
|
+
const state = await manager.startEpic(ctx, epicName, trackingIssueId, tddEnabled);
|
|
717
740
|
|
|
718
741
|
if (!state || !state.slices || state.slices.length === 0) {
|
|
719
742
|
ctx.ui.notify("Failed to discover architecture components. Check .pi/architecture/modules/.", "error");
|
|
@@ -811,6 +834,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
811
834
|
"3. Run `pipeline_run_acceptance` to validate your work",
|
|
812
835
|
"4. Call `pipeline_advance` to move to the next step",
|
|
813
836
|
"5. Pipeline auto-advances through: implement → validate → create-mr → merge",
|
|
837
|
+
tddEnabled ? [
|
|
838
|
+
"",
|
|
839
|
+
"**TDD Mode: ON**",
|
|
840
|
+
"- Failing test files were generated from architecture contracts before issues were created.",
|
|
841
|
+
"- Start each component by examining its test file in the `tests/unit/` directory.",
|
|
842
|
+
"- Run tests first to see them fail, then implement to make them pass (Red→Green→Refactor).",
|
|
843
|
+
"- Tests are living artifacts — evolve them as the component grows.",
|
|
844
|
+
"- Reference `.pi/skills/tdd-practice.md` for TDD guidance.",
|
|
845
|
+
].join("\n") : "",
|
|
814
846
|
"",
|
|
815
847
|
"---",
|
|
816
848
|
"",
|