guardian-framework 0.1.63 → 0.1.65
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 +36 -12
- package/dist/exports.js +33 -11
- 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
|
@@ -2930,6 +2930,25 @@ import * as crypto2 from "crypto";
|
|
|
2930
2930
|
import * as fs3 from "fs";
|
|
2931
2931
|
import * as path4 from "path";
|
|
2932
2932
|
import { fileURLToPath } from "url";
|
|
2933
|
+
function loadFrameworkVersion() {
|
|
2934
|
+
if (_cachedVersion)
|
|
2935
|
+
return _cachedVersion;
|
|
2936
|
+
try {
|
|
2937
|
+
const dir = path4.dirname(_filename);
|
|
2938
|
+
const candidates = [
|
|
2939
|
+
path4.resolve(dir, "..", "..", "package.json"),
|
|
2940
|
+
path4.resolve(dir, "..", "package.json")
|
|
2941
|
+
];
|
|
2942
|
+
for (const candidate of candidates) {
|
|
2943
|
+
if (fs3.existsSync(candidate)) {
|
|
2944
|
+
_cachedVersion = JSON.parse(fs3.readFileSync(candidate, "utf-8")).version;
|
|
2945
|
+
return _cachedVersion;
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
} catch {}
|
|
2949
|
+
_cachedVersion = "0.0.0";
|
|
2950
|
+
return _cachedVersion;
|
|
2951
|
+
}
|
|
2933
2952
|
function hashFile(filePath) {
|
|
2934
2953
|
if (!fs3.existsSync(filePath)) {
|
|
2935
2954
|
return "";
|
|
@@ -3099,10 +3118,11 @@ function calculateTokenStats(targetDir, manifest) {
|
|
|
3099
3118
|
lastCalculatedAt: new Date().toISOString()
|
|
3100
3119
|
};
|
|
3101
3120
|
}
|
|
3102
|
-
var _filename, _dirname, MANIFEST_FILE = "guardian-manifest.json", FRAMEWORK_VERSION
|
|
3121
|
+
var _filename, _dirname, MANIFEST_FILE = "guardian-manifest.json", _cachedVersion, FRAMEWORK_VERSION, SCHEMA_VERSION = "0.1";
|
|
3103
3122
|
var init_manifest = __esm(() => {
|
|
3104
3123
|
_filename = fileURLToPath(import.meta.url);
|
|
3105
3124
|
_dirname = path4.dirname(_filename);
|
|
3125
|
+
FRAMEWORK_VERSION = loadFrameworkVersion();
|
|
3106
3126
|
});
|
|
3107
3127
|
|
|
3108
3128
|
// src/lib/retry.ts
|
|
@@ -3129,7 +3149,7 @@ function calculateBackoff(attempt, baseDelayMs, maxBackoffMs) {
|
|
|
3129
3149
|
return Math.min(delay, maxBackoffMs);
|
|
3130
3150
|
}
|
|
3131
3151
|
function sleep(ms) {
|
|
3132
|
-
return new Promise((
|
|
3152
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
3133
3153
|
}
|
|
3134
3154
|
var DEFAULT_OPTIONS;
|
|
3135
3155
|
var init_retry = __esm(() => {
|
|
@@ -3178,7 +3198,7 @@ __export(exports_package, {
|
|
|
3178
3198
|
bin: () => bin,
|
|
3179
3199
|
author: () => author
|
|
3180
3200
|
});
|
|
3181
|
-
var name = "guardian-framework", version = "0.1.
|
|
3201
|
+
var name = "guardian-framework", version = "0.1.65", 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
3202
|
var init_package = __esm(() => {
|
|
3183
3203
|
exports = {
|
|
3184
3204
|
".": {
|
|
@@ -10657,7 +10677,7 @@ import * as child_process from "child_process";
|
|
|
10657
10677
|
import * as fs6 from "fs";
|
|
10658
10678
|
async function executeHook(hookName, script, workspacePath, timeoutMs) {
|
|
10659
10679
|
const startTime = Date.now();
|
|
10660
|
-
return new Promise((
|
|
10680
|
+
return new Promise((resolve5) => {
|
|
10661
10681
|
const proc = child_process.spawn("bash", ["-lc", script], {
|
|
10662
10682
|
cwd: workspacePath,
|
|
10663
10683
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -10698,7 +10718,7 @@ async function executeHook(hookName, script, workspacePath, timeoutMs) {
|
|
|
10698
10718
|
clearTimeout(hardTimer);
|
|
10699
10719
|
if (stallTimer)
|
|
10700
10720
|
clearTimeout(stallTimer);
|
|
10701
|
-
|
|
10721
|
+
resolve5({
|
|
10702
10722
|
success: code === 0,
|
|
10703
10723
|
exitCode: code ?? 1,
|
|
10704
10724
|
stdout: stdout2.trim(),
|
|
@@ -10710,7 +10730,7 @@ async function executeHook(hookName, script, workspacePath, timeoutMs) {
|
|
|
10710
10730
|
clearTimeout(hardTimer);
|
|
10711
10731
|
if (stallTimer)
|
|
10712
10732
|
clearTimeout(stallTimer);
|
|
10713
|
-
|
|
10733
|
+
resolve5({
|
|
10714
10734
|
success: false,
|
|
10715
10735
|
exitCode: 1,
|
|
10716
10736
|
stdout: "",
|
|
@@ -14454,6 +14474,7 @@ init_update();
|
|
|
14454
14474
|
// src/commands/upgrade.ts
|
|
14455
14475
|
init_dist4();
|
|
14456
14476
|
init_manifest();
|
|
14477
|
+
init_update();
|
|
14457
14478
|
async function runUpgrade(targetDir = process.cwd(), options = {}) {
|
|
14458
14479
|
const manifest = readManifest(targetDir);
|
|
14459
14480
|
if (!manifest) {
|
|
@@ -14462,10 +14483,11 @@ async function runUpgrade(targetDir = process.cwd(), options = {}) {
|
|
|
14462
14483
|
}
|
|
14463
14484
|
const currentSchema = manifest.schemaVersion ?? "unknown";
|
|
14464
14485
|
const currentFramework = manifest.frameworkVersion ?? "unknown";
|
|
14465
|
-
|
|
14466
|
-
|
|
14467
|
-
|
|
14468
|
-
|
|
14486
|
+
await runUpdate(targetDir, {
|
|
14487
|
+
dryRun: options.dryRun,
|
|
14488
|
+
force: options.force,
|
|
14489
|
+
regenerate: options.regenerate
|
|
14490
|
+
});
|
|
14469
14491
|
const migrations = [];
|
|
14470
14492
|
if (currentSchema !== SCHEMA_VERSION) {
|
|
14471
14493
|
migrations.push(`Schema: ${currentSchema} \u2192 ${SCHEMA_VERSION}`);
|
|
@@ -14477,7 +14499,7 @@ async function runUpgrade(targetDir = process.cwd(), options = {}) {
|
|
|
14477
14499
|
migrations.push(`Framework: ${currentFramework} \u2192 ${FRAMEWORK_VERSION}`);
|
|
14478
14500
|
}
|
|
14479
14501
|
if (migrations.length === 0) {
|
|
14480
|
-
outro("
|
|
14502
|
+
outro("Project is on the latest version. Template files synced.");
|
|
14481
14503
|
return;
|
|
14482
14504
|
}
|
|
14483
14505
|
if (options.dryRun) {
|
|
@@ -15257,7 +15279,9 @@ async function runCommand(command, args) {
|
|
|
15257
15279
|
break;
|
|
15258
15280
|
case "upgrade":
|
|
15259
15281
|
await runUpgrade(targetDir, {
|
|
15260
|
-
dryRun: args.values.dryRun
|
|
15282
|
+
dryRun: args.values.dryRun,
|
|
15283
|
+
force: args.values.force,
|
|
15284
|
+
regenerate: args.values.regenerate
|
|
15261
15285
|
});
|
|
15262
15286
|
break;
|
|
15263
15287
|
case "uninstall":
|
package/dist/exports.js
CHANGED
|
@@ -1750,6 +1750,25 @@ import * as crypto from "node:crypto";
|
|
|
1750
1750
|
import * as fs from "node:fs";
|
|
1751
1751
|
import * as path from "node:path";
|
|
1752
1752
|
import { fileURLToPath } from "node:url";
|
|
1753
|
+
function loadFrameworkVersion() {
|
|
1754
|
+
if (_cachedVersion)
|
|
1755
|
+
return _cachedVersion;
|
|
1756
|
+
try {
|
|
1757
|
+
const dir = path.dirname(_filename);
|
|
1758
|
+
const candidates = [
|
|
1759
|
+
path.resolve(dir, "..", "..", "package.json"),
|
|
1760
|
+
path.resolve(dir, "..", "package.json")
|
|
1761
|
+
];
|
|
1762
|
+
for (const candidate of candidates) {
|
|
1763
|
+
if (fs.existsSync(candidate)) {
|
|
1764
|
+
_cachedVersion = JSON.parse(fs.readFileSync(candidate, "utf-8")).version;
|
|
1765
|
+
return _cachedVersion;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
} catch {}
|
|
1769
|
+
_cachedVersion = "0.0.0";
|
|
1770
|
+
return _cachedVersion;
|
|
1771
|
+
}
|
|
1753
1772
|
function hashFile(filePath) {
|
|
1754
1773
|
if (!fs.existsSync(filePath)) {
|
|
1755
1774
|
return "";
|
|
@@ -1919,10 +1938,11 @@ function calculateTokenStats(targetDir, manifest) {
|
|
|
1919
1938
|
lastCalculatedAt: new Date().toISOString()
|
|
1920
1939
|
};
|
|
1921
1940
|
}
|
|
1922
|
-
var _filename, _dirname, MANIFEST_FILE = "guardian-manifest.json", FRAMEWORK_VERSION
|
|
1941
|
+
var _filename, _dirname, MANIFEST_FILE = "guardian-manifest.json", _cachedVersion, FRAMEWORK_VERSION, SCHEMA_VERSION = "0.1";
|
|
1923
1942
|
var init_manifest = __esm(() => {
|
|
1924
1943
|
_filename = fileURLToPath(import.meta.url);
|
|
1925
1944
|
_dirname = path.dirname(_filename);
|
|
1945
|
+
FRAMEWORK_VERSION = loadFrameworkVersion();
|
|
1926
1946
|
});
|
|
1927
1947
|
|
|
1928
1948
|
// src/lib/result.ts
|
|
@@ -1963,7 +1983,7 @@ __export(exports_package, {
|
|
|
1963
1983
|
bin: () => bin,
|
|
1964
1984
|
author: () => author
|
|
1965
1985
|
});
|
|
1966
|
-
var name = "guardian-framework", version = "0.1.
|
|
1986
|
+
var name = "guardian-framework", version = "0.1.65", 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
1987
|
var init_package = __esm(() => {
|
|
1968
1988
|
exports = {
|
|
1969
1989
|
".": {
|
|
@@ -2668,7 +2688,7 @@ function calculateBackoff(attempt, baseDelayMs, maxBackoffMs) {
|
|
|
2668
2688
|
return Math.min(delay, maxBackoffMs);
|
|
2669
2689
|
}
|
|
2670
2690
|
function sleep(ms) {
|
|
2671
|
-
return new Promise((
|
|
2691
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
2672
2692
|
}
|
|
2673
2693
|
var DEFAULT_OPTIONS;
|
|
2674
2694
|
var init_retry = __esm(() => {
|
|
@@ -9781,7 +9801,7 @@ import * as child_process from "node:child_process";
|
|
|
9781
9801
|
import * as fs4 from "node:fs";
|
|
9782
9802
|
async function executeHook(hookName, script, workspacePath, timeoutMs) {
|
|
9783
9803
|
const startTime = Date.now();
|
|
9784
|
-
return new Promise((
|
|
9804
|
+
return new Promise((resolve3) => {
|
|
9785
9805
|
const proc = child_process.spawn("bash", ["-lc", script], {
|
|
9786
9806
|
cwd: workspacePath,
|
|
9787
9807
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -9822,7 +9842,7 @@ async function executeHook(hookName, script, workspacePath, timeoutMs) {
|
|
|
9822
9842
|
clearTimeout(hardTimer);
|
|
9823
9843
|
if (stallTimer)
|
|
9824
9844
|
clearTimeout(stallTimer);
|
|
9825
|
-
|
|
9845
|
+
resolve3({
|
|
9826
9846
|
success: code === 0,
|
|
9827
9847
|
exitCode: code ?? 1,
|
|
9828
9848
|
stdout: stdout2.trim(),
|
|
@@ -9834,7 +9854,7 @@ async function executeHook(hookName, script, workspacePath, timeoutMs) {
|
|
|
9834
9854
|
clearTimeout(hardTimer);
|
|
9835
9855
|
if (stallTimer)
|
|
9836
9856
|
clearTimeout(stallTimer);
|
|
9837
|
-
|
|
9857
|
+
resolve3({
|
|
9838
9858
|
success: false,
|
|
9839
9859
|
exitCode: 1,
|
|
9840
9860
|
stdout: "",
|
|
@@ -14122,6 +14142,7 @@ function collectDirectoriesToPrune(files2) {
|
|
|
14122
14142
|
// src/commands/upgrade.ts
|
|
14123
14143
|
init_dist4();
|
|
14124
14144
|
init_manifest();
|
|
14145
|
+
init_update();
|
|
14125
14146
|
async function runUpgrade(targetDir = process.cwd(), options = {}) {
|
|
14126
14147
|
const manifest = readManifest(targetDir);
|
|
14127
14148
|
if (!manifest) {
|
|
@@ -14130,10 +14151,11 @@ async function runUpgrade(targetDir = process.cwd(), options = {}) {
|
|
|
14130
14151
|
}
|
|
14131
14152
|
const currentSchema = manifest.schemaVersion ?? "unknown";
|
|
14132
14153
|
const currentFramework = manifest.frameworkVersion ?? "unknown";
|
|
14133
|
-
|
|
14134
|
-
|
|
14135
|
-
|
|
14136
|
-
|
|
14154
|
+
await runUpdate(targetDir, {
|
|
14155
|
+
dryRun: options.dryRun,
|
|
14156
|
+
force: options.force,
|
|
14157
|
+
regenerate: options.regenerate
|
|
14158
|
+
});
|
|
14137
14159
|
const migrations = [];
|
|
14138
14160
|
if (currentSchema !== SCHEMA_VERSION) {
|
|
14139
14161
|
migrations.push(`Schema: ${currentSchema} → ${SCHEMA_VERSION}`);
|
|
@@ -14145,7 +14167,7 @@ async function runUpgrade(targetDir = process.cwd(), options = {}) {
|
|
|
14145
14167
|
migrations.push(`Framework: ${currentFramework} → ${FRAMEWORK_VERSION}`);
|
|
14146
14168
|
}
|
|
14147
14169
|
if (migrations.length === 0) {
|
|
14148
|
-
outro("
|
|
14170
|
+
outro("Project is on the latest version. Template files synced.");
|
|
14149
14171
|
return;
|
|
14150
14172
|
}
|
|
14151
14173
|
if (options.dryRun) {
|
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
|
"",
|