guardian-framework 0.1.78 → 0.1.80
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 -1
- package/dist/exports.js +36 -1
- package/package.json +1 -1
- package/templates/pi/architecture/modules/module-template.md +12 -0
- package/templates/pi/extensions/architect-lib/generators.ts +266 -90
- package/templates/pi/extensions/architect-lib/tdd-generator.ts +15 -4
- package/templates/pi/extensions/architect-lib/types.ts +1 -0
- package/templates/pi/extensions/architect.ts +7 -7
- package/templates/pi/extensions/domain-explorer.ts +7 -6
- package/templates/pi/extensions/tsconfig.json +15 -0
- package/templates/pi/preflight_report.json +1 -1
- package/templates/pi/scripts/ci/check_architecture_conformance.sh +6 -6
- package/templates/pi/scripts/validate-architecture-readiness.sh +3 -3
- package/templates/pi/scripts/validate-ci.sh +2 -0
package/dist/cli.js
CHANGED
|
@@ -3197,7 +3197,7 @@ __export(exports_package, {
|
|
|
3197
3197
|
bin: () => bin,
|
|
3198
3198
|
author: () => author
|
|
3199
3199
|
});
|
|
3200
|
-
var name = "guardian-framework", version = "0.1.
|
|
3200
|
+
var name = "guardian-framework", version = "0.1.80", 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;
|
|
3201
3201
|
var init_package = __esm(() => {
|
|
3202
3202
|
exports = {
|
|
3203
3203
|
".": {
|
|
@@ -12438,6 +12438,23 @@ ${bc.description}
|
|
|
12438
12438
|
|
|
12439
12439
|
`;
|
|
12440
12440
|
content += `${depsSection}
|
|
12441
|
+
`;
|
|
12442
|
+
content += `## Acceptance Criteria
|
|
12443
|
+
|
|
12444
|
+
`;
|
|
12445
|
+
content += `Each component in this module must satisfy its specific acceptance criteria.
|
|
12446
|
+
`;
|
|
12447
|
+
content += `Fill these out for each component before implementation begins.
|
|
12448
|
+
`;
|
|
12449
|
+
content += `
|
|
12450
|
+
`;
|
|
12451
|
+
content += `| # | Component | Criterion | Verify In |
|
|
12452
|
+
`;
|
|
12453
|
+
content += `|---|-----------|-----------|-----------|
|
|
12454
|
+
`;
|
|
12455
|
+
content += `| 1 | [Component] | [Specific, testable criterion] | [unit / integration / manual] |
|
|
12456
|
+
`;
|
|
12457
|
+
content += `
|
|
12441
12458
|
`;
|
|
12442
12459
|
content += `## Key Files
|
|
12443
12460
|
|
|
@@ -12641,6 +12658,24 @@ ${bc.description}
|
|
|
12641
12658
|
content += `Each layer MUST NOT leak into adjacent layers.
|
|
12642
12659
|
`;
|
|
12643
12660
|
content += `
|
|
12661
|
+
`;
|
|
12662
|
+
content += `## Acceptance Criteria
|
|
12663
|
+
`;
|
|
12664
|
+
content += `
|
|
12665
|
+
`;
|
|
12666
|
+
content += `Each component in this module must satisfy its specific acceptance criteria.
|
|
12667
|
+
`;
|
|
12668
|
+
content += `Fill these out for each component before implementation begins.
|
|
12669
|
+
`;
|
|
12670
|
+
content += `
|
|
12671
|
+
`;
|
|
12672
|
+
content += `| # | Component | Criterion | Verify In |
|
|
12673
|
+
`;
|
|
12674
|
+
content += `|---|-----------|-----------|-----------|
|
|
12675
|
+
`;
|
|
12676
|
+
content += `| 1 | [Component] | [Specific, testable criterion] | [unit / integration / manual] |
|
|
12677
|
+
`;
|
|
12678
|
+
content += `
|
|
12644
12679
|
`;
|
|
12645
12680
|
content += `## ADRs
|
|
12646
12681
|
`;
|
package/dist/exports.js
CHANGED
|
@@ -1982,7 +1982,7 @@ __export(exports_package, {
|
|
|
1982
1982
|
bin: () => bin,
|
|
1983
1983
|
author: () => author
|
|
1984
1984
|
});
|
|
1985
|
-
var name = "guardian-framework", version = "0.1.
|
|
1985
|
+
var name = "guardian-framework", version = "0.1.80", 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;
|
|
1986
1986
|
var init_package = __esm(() => {
|
|
1987
1987
|
exports = {
|
|
1988
1988
|
".": {
|
|
@@ -12762,6 +12762,23 @@ ${bc.description}
|
|
|
12762
12762
|
|
|
12763
12763
|
`;
|
|
12764
12764
|
content += `${depsSection}
|
|
12765
|
+
`;
|
|
12766
|
+
content += `## Acceptance Criteria
|
|
12767
|
+
|
|
12768
|
+
`;
|
|
12769
|
+
content += `Each component in this module must satisfy its specific acceptance criteria.
|
|
12770
|
+
`;
|
|
12771
|
+
content += `Fill these out for each component before implementation begins.
|
|
12772
|
+
`;
|
|
12773
|
+
content += `
|
|
12774
|
+
`;
|
|
12775
|
+
content += `| # | Component | Criterion | Verify In |
|
|
12776
|
+
`;
|
|
12777
|
+
content += `|---|-----------|-----------|-----------|
|
|
12778
|
+
`;
|
|
12779
|
+
content += `| 1 | [Component] | [Specific, testable criterion] | [unit / integration / manual] |
|
|
12780
|
+
`;
|
|
12781
|
+
content += `
|
|
12765
12782
|
`;
|
|
12766
12783
|
content += `## Key Files
|
|
12767
12784
|
|
|
@@ -12965,6 +12982,24 @@ ${bc.description}
|
|
|
12965
12982
|
content += `Each layer MUST NOT leak into adjacent layers.
|
|
12966
12983
|
`;
|
|
12967
12984
|
content += `
|
|
12985
|
+
`;
|
|
12986
|
+
content += `## Acceptance Criteria
|
|
12987
|
+
`;
|
|
12988
|
+
content += `
|
|
12989
|
+
`;
|
|
12990
|
+
content += `Each component in this module must satisfy its specific acceptance criteria.
|
|
12991
|
+
`;
|
|
12992
|
+
content += `Fill these out for each component before implementation begins.
|
|
12993
|
+
`;
|
|
12994
|
+
content += `
|
|
12995
|
+
`;
|
|
12996
|
+
content += `| # | Component | Criterion | Verify In |
|
|
12997
|
+
`;
|
|
12998
|
+
content += `|---|-----------|-----------|-----------|
|
|
12999
|
+
`;
|
|
13000
|
+
content += `| 1 | [Component] | [Specific, testable criterion] | [unit / integration / manual] |
|
|
13001
|
+
`;
|
|
13002
|
+
content += `
|
|
12968
13003
|
`;
|
|
12969
13004
|
content += `## ADRs
|
|
12970
13005
|
`;
|
package/package.json
CHANGED
|
@@ -121,6 +121,18 @@ Response → commit or rollback → UI update
|
|
|
121
121
|
|
|
122
122
|
---
|
|
123
123
|
|
|
124
|
+
## Acceptance Criteria
|
|
125
|
+
|
|
126
|
+
Each component in this module has specific acceptance criteria that must be verified
|
|
127
|
+
before the implementation issue can be closed. These ACs flow into the generated issues.
|
|
128
|
+
|
|
129
|
+
| # | Component | Criterion | Verify In |
|
|
130
|
+
|---|-----------|-----------|-----------|
|
|
131
|
+
| 1 | [ComponentName] | [Specific, testable criterion for this component] | [unit test / integration test / manual] |
|
|
132
|
+
| 2 | [ComponentName] | [Another criterion] | [verification method] |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
124
136
|
## Dependencies
|
|
125
137
|
|
|
126
138
|
### Depends On
|
|
@@ -1,64 +1,245 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import type { ArchitectureSlice, ModuleComponent } from "./types.ts";
|
|
2
4
|
|
|
5
|
+
// ── Module Doc Readers ───────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
/** Read the raw module architecture doc, or empty string if missing. */
|
|
8
|
+
function readModuleDoc(cwd: string, moduleId: string): string {
|
|
9
|
+
try {
|
|
10
|
+
return readFileSync(
|
|
11
|
+
join(cwd, ".pi", "architecture", "modules", `${moduleId}.md`),
|
|
12
|
+
"utf-8",
|
|
13
|
+
);
|
|
14
|
+
} catch {
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Extract a named section's body (between ## heading and next ## / --- / EOF). */
|
|
20
|
+
function extractSection(doc: string, heading: string): string {
|
|
21
|
+
const re = new RegExp(`## ${heading}\n([\\s\\S]*?)(?=\n##|\n---|$)`);
|
|
22
|
+
const m = re.exec(doc);
|
|
23
|
+
return m ? m[1].trim() : "";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface AcRow { num: string; component?: string; criterion: string; verifyIn: string; }
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse every AC row from ## Acceptance Criteria.
|
|
30
|
+
* Supports:
|
|
31
|
+
* 4-column: `| # | Component | Criterion | Verify In |`
|
|
32
|
+
* 3-column: `| # | Criterion | Verify In |`
|
|
33
|
+
* Returns rows with component populated when the 4-col format is detected.
|
|
34
|
+
*/
|
|
35
|
+
function parseAcTable(doc: string): AcRow[] {
|
|
36
|
+
const section = extractSection(doc, "Acceptance Criteria");
|
|
37
|
+
if (!section) return [];
|
|
38
|
+
const rows = section.match(/^\| [\d✅☐][^|]*\|[^|]+\|[^|]+\|[^|]*\|$/gm) ?? [];
|
|
39
|
+
return rows.map((row) => {
|
|
40
|
+
const parts = row.split("|").map((p) => p.trim()).filter(Boolean);
|
|
41
|
+
// 4-col: [num, component, criterion, verifyIn]
|
|
42
|
+
// 3-col: [num, criterion, verifyIn]
|
|
43
|
+
const isFourCol = parts.length >= 4 && parts[1] !== "Criterion";
|
|
44
|
+
if (isFourCol) {
|
|
45
|
+
return { num: parts[0] ?? "", component: parts[1], criterion: parts[2] ?? "", verifyIn: parts[3] ?? "" };
|
|
46
|
+
}
|
|
47
|
+
return { num: parts[0] ?? "", criterion: parts[1] ?? "", verifyIn: parts[2] ?? "" };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Parse numbered steps from ## Implementation Sequence. */
|
|
52
|
+
function parseImplSequence(doc: string): string[] {
|
|
53
|
+
const section = extractSection(doc, "Implementation Sequence");
|
|
54
|
+
if (!section) return [];
|
|
55
|
+
return (section.match(/^\d+\. .+$/gm) ?? []).map((s) => s.trim());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Filter ACs that belong to a specific component.
|
|
60
|
+
*
|
|
61
|
+
* When the AC table has a Component column (4-col format), ACs are matched
|
|
62
|
+
* by component name. Falls back to keyword matching on criterion text when
|
|
63
|
+
* no component column is present.
|
|
64
|
+
*/
|
|
65
|
+
function primaryAcs(acs: AcRow[], componentName: string): AcRow[] {
|
|
66
|
+
if (acs.length === 0) return [];
|
|
67
|
+
|
|
68
|
+
// Prefer explicit component column match
|
|
69
|
+
const byComponent = acs.filter((ac) => ac.component && ac.component === componentName);
|
|
70
|
+
if (byComponent.length > 0) return byComponent;
|
|
71
|
+
|
|
72
|
+
// Fallback: keyword match on criterion text
|
|
73
|
+
const lower = componentName.toLowerCase();
|
|
74
|
+
const isDomainModel =
|
|
75
|
+
(lower.includes("entity") || lower.includes("aggregate") ||
|
|
76
|
+
lower.includes("snapshot") || lower.includes("enum")) &&
|
|
77
|
+
!lower.includes("repository");
|
|
78
|
+
|
|
79
|
+
const matched = acs.filter((ac) =>
|
|
80
|
+
ac.criterion.toLowerCase().includes(lower.replace(/[^a-z0-9]/g, "")),
|
|
81
|
+
);
|
|
82
|
+
if (matched.length > 0) return matched;
|
|
83
|
+
|
|
84
|
+
if (isDomainModel) {
|
|
85
|
+
const domainKeywords = ["entity", "aggregate", "enum", "value object", "jpa", "migration", "flyway", "snapshot", "status"];
|
|
86
|
+
const filtered = acs.filter((ac) =>
|
|
87
|
+
domainKeywords.some((kw) => ac.criterion.toLowerCase().includes(kw)),
|
|
88
|
+
);
|
|
89
|
+
if (filtered.length > 0) return filtered;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Last resort: evenly split ACs among all components (assuming n components, take 1/n-th)
|
|
93
|
+
return acs.slice(0, Math.max(1, Math.ceil(acs.length / 4)));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Format AcRow[] as a markdown table body (no header). Includes Component column if any row has one. */
|
|
97
|
+
function acRowsToMarkdown(rows: AcRow[]): string {
|
|
98
|
+
const hasComponent = rows.some((r) => r.component);
|
|
99
|
+
if (hasComponent) {
|
|
100
|
+
return rows.map((r) => `| ${r.num} | ${r.component} | ${r.criterion} | ${r.verifyIn} |`).join("\n");
|
|
101
|
+
}
|
|
102
|
+
return rows.map((r) => `| ${r.num} | ${r.criterion} | ${r.verifyIn} |`).join("\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Build a human-readable title for the component issue. */
|
|
106
|
+
function buildIssueTitle(comp: ModuleComponent, moduleId: string): string {
|
|
107
|
+
return `Implement ${comp.name} — ${moduleId}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Derive `in_scope` lines from primary ACs. Falls back to generic if none. */
|
|
111
|
+
function buildInScope(comp: ModuleComponent, primaryAcRows: AcRow[], implSteps: string[], moduleId: string): string[] {
|
|
112
|
+
if (primaryAcRows.length > 0) {
|
|
113
|
+
return primaryAcRows.slice(0, 6).map((r) => r.criterion.replace(/^✅\s*/, ""));
|
|
114
|
+
}
|
|
115
|
+
if (implSteps.length > 0) {
|
|
116
|
+
return implSteps.slice(0, 4).map((s) => s.replace(/^\d+\.\s*/, ""));
|
|
117
|
+
}
|
|
118
|
+
return [
|
|
119
|
+
`Implement ${comp.name} for the ${moduleId} module`,
|
|
120
|
+
"Write unit tests for all public methods",
|
|
121
|
+
"Add integration tests with upstream/downstream components",
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Detect whether project at `cwd` is a Java project. */
|
|
126
|
+
function isJavaProject(cwd?: string): boolean {
|
|
127
|
+
if (!cwd) return false;
|
|
128
|
+
try {
|
|
129
|
+
return existsSync(join(cwd, "pom.xml")) ||
|
|
130
|
+
existsSync(join(cwd, "build.gradle")) ||
|
|
131
|
+
existsSync(join(cwd, "build.gradle.kts"));
|
|
132
|
+
} catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
3
137
|
// ── Issue Generation ──
|
|
4
138
|
|
|
5
139
|
export function generateIssueMarkdown(
|
|
6
|
-
|
|
140
|
+
comp: ModuleComponent,
|
|
7
141
|
slice: ArchitectureSlice,
|
|
8
142
|
issueIndex: number,
|
|
9
|
-
|
|
143
|
+
_totalIssues: number,
|
|
10
144
|
tdd?: boolean,
|
|
145
|
+
cwd?: string,
|
|
11
146
|
): string {
|
|
12
147
|
const moduleId = slice.module.replace(/^module-/, "");
|
|
13
|
-
const componentName = component.name.toLowerCase().replace(/\s+/g, "-");
|
|
14
148
|
const issueId = `ISSUE-${moduleId.toUpperCase()}-${issueIndex + 1}`;
|
|
15
149
|
|
|
150
|
+
// ── Read module doc for specifics ──
|
|
151
|
+
const doc = cwd ? readModuleDoc(cwd, slice.module) : "";
|
|
152
|
+
const allAcs = parseAcTable(doc);
|
|
153
|
+
const implSteps = parseImplSequence(doc);
|
|
154
|
+
const compAcs = primaryAcs(allAcs, comp.name);
|
|
155
|
+
|
|
156
|
+
const title = buildIssueTitle(comp, moduleId);
|
|
157
|
+
const inScope = buildInScope(comp, compAcs, implSteps, moduleId);
|
|
158
|
+
|
|
159
|
+
// Language-aware test paths
|
|
160
|
+
const isJava = isJavaProject(cwd);
|
|
161
|
+
const testBaseDir = isJava ? "src/test/java" : "tests/unit";
|
|
162
|
+
const testRunnerHint = isJava ? "mvn test" : "bun test";
|
|
163
|
+
|
|
16
164
|
const testLine = tdd
|
|
17
|
-
? ` - "update:
|
|
18
|
-
: ` - "create:
|
|
165
|
+
? ` - "update: ${testBaseDir}/ (failing tests already generated — make them pass)"`
|
|
166
|
+
: ` - "create: ${testBaseDir}/"`;
|
|
167
|
+
|
|
168
|
+
// YAML front-matter ACs
|
|
169
|
+
const yamlAcs = compAcs.length > 0
|
|
170
|
+
? compAcs.map((r) => {
|
|
171
|
+
const c = r.criterion.replace(/^✅\s*/, "").replaceAll("\"", "'");
|
|
172
|
+
return ` - "${c}"`;
|
|
173
|
+
}).join("\n")
|
|
174
|
+
: ` - "CI pipeline passes (validate-ci.sh)"
|
|
175
|
+
- "All unit tests pass"
|
|
176
|
+
- "Architecture compliance (validate-architecture.sh)"
|
|
177
|
+
- "Canonical references valid (validate-canonical.sh)"`;
|
|
178
|
+
|
|
179
|
+
// Markdown body: all ACs
|
|
180
|
+
const hasCompCol = allAcs.some((a) => a.component);
|
|
181
|
+
const allAcHeader = hasCompCol
|
|
182
|
+
? `| # | Component | Criterion | Verify In |\n|---|-----------|-----------|-----------|`
|
|
183
|
+
: `| # | Criterion | Verify In |\n|---|-----------|-----------|`;
|
|
184
|
+
const allAcMarkdown = allAcs.length > 0
|
|
185
|
+
? `${allAcHeader}\n${acRowsToMarkdown(allAcs)}`
|
|
186
|
+
: `| # | Criterion | Validator |\n|---|-----------|-----------|\n| 1 | CI pipeline passes | \`validate-ci.sh\` |\n| 2 | All unit tests pass | \`validate-tests.sh\` |\n| 3 | Integration tests pass | \`validate-integration.sh\` |\n| 4 | Architecture compliance | \`validate-architecture.sh\` |\n| 5 | Canonical references valid | \`validate-canonical.sh\` |`;
|
|
187
|
+
|
|
188
|
+
// Implementation steps from module doc
|
|
189
|
+
const stepsMarkdown = implSteps.length > 0
|
|
190
|
+
? implSteps.map((s) => `- ${s}`).join("\n")
|
|
191
|
+
: `- Read .pi/architecture/modules/${slice.module}.md\n- Implement entities and interfaces\n- Implement infrastructure (adapter, mapper, repository)\n- Implement use case\n- Write unit + integration tests\n- Run validators\n- Create MR`;
|
|
192
|
+
|
|
193
|
+
const tddSteps = tdd
|
|
194
|
+
? [
|
|
195
|
+
"1. Read canonical architecture references",
|
|
196
|
+
`2. Run the pre-generated failing tests: \`cd ${testBaseDir} && ${testRunnerHint}\``,
|
|
197
|
+
"3. Verify tests FAIL (Red phase)",
|
|
198
|
+
"4. Implement domain entities and interfaces",
|
|
199
|
+
"5. Implement application service/handler",
|
|
200
|
+
"6. Add infrastructure connections",
|
|
201
|
+
"7. Run tests again — they should PASS (Green phase)",
|
|
202
|
+
"8. Refactor if needed (Refactor phase)",
|
|
203
|
+
"9. Write integration tests",
|
|
204
|
+
"10. Run all validators",
|
|
205
|
+
"11. Create MR",
|
|
206
|
+
].join("\n")
|
|
207
|
+
: [
|
|
208
|
+
"1. Read canonical architecture references",
|
|
209
|
+
"2. Create domain entities and interfaces",
|
|
210
|
+
"3. Implement application service/handler",
|
|
211
|
+
"4. Add infrastructure connections",
|
|
212
|
+
"5. Write unit tests (≥ 90% coverage)",
|
|
213
|
+
"6. Write integration tests",
|
|
214
|
+
"7. Run all validators",
|
|
215
|
+
"8. Create MR",
|
|
216
|
+
].join("\n");
|
|
19
217
|
|
|
20
218
|
return `---
|
|
21
219
|
guardian_issue:
|
|
22
220
|
id: "${issueId}"
|
|
23
221
|
epic: "TBD"
|
|
24
|
-
component: "${
|
|
222
|
+
component: "${comp.name}"
|
|
25
223
|
module: "${slice.module}"
|
|
26
224
|
status: planned
|
|
27
225
|
priority: high
|
|
28
226
|
dependencies:
|
|
29
|
-
${
|
|
227
|
+
${comp.dependencies.map((d) => ` - "${d}"`).join("\n")}
|
|
30
228
|
|
|
31
229
|
in_scope:
|
|
32
|
-
|
|
33
|
-
- Write unit tests for all public interfaces
|
|
34
|
-
- Add integration tests with upstream/downstream components
|
|
35
|
-
- Create API documentation
|
|
230
|
+
${inScope.map((s) => { const sc = s.replaceAll("\"", "'"); return ` - "${sc}"`; }).join("\n")}
|
|
36
231
|
|
|
37
232
|
out_of_scope:
|
|
38
|
-
- Changes to upstream components (${
|
|
233
|
+
- Changes to upstream components (${comp.dependencies.join(", ") || "none"})
|
|
39
234
|
- UI/frontend changes
|
|
40
235
|
- Deployment pipeline configuration
|
|
41
236
|
|
|
42
|
-
affected_layers:
|
|
43
|
-
domain:
|
|
44
|
-
- New domain models for ${componentName}
|
|
45
|
-
application:
|
|
46
|
-
- New service/handler for ${componentName}
|
|
47
|
-
infrastructure:
|
|
48
|
-
- New database tables or external service connections
|
|
49
|
-
api:
|
|
50
|
-
- New endpoints or event handlers
|
|
51
|
-
|
|
52
237
|
canonical_references:
|
|
53
|
-
- module: ".pi/architecture/modules/${slice.module}.md
|
|
238
|
+
- module: ".pi/architecture/modules/${slice.module}.md"
|
|
239
|
+
- acceptance_criteria: ".pi/architecture/modules/${slice.module}.md#acceptance-criteria"
|
|
54
240
|
|
|
55
241
|
acceptance_criteria:
|
|
56
|
-
|
|
57
|
-
- "All unit tests pass with ≥ 90% coverage"
|
|
58
|
-
- "Integration tests pass with upstream/downstream components"
|
|
59
|
-
- "validate-security.sh passes"
|
|
60
|
-
- "validate-architecture.sh passes"
|
|
61
|
-
- "validate-canonical.sh passes"
|
|
242
|
+
${yamlAcs}
|
|
62
243
|
|
|
63
244
|
validators:
|
|
64
245
|
- ci
|
|
@@ -68,7 +249,9 @@ ${component.dependencies.map((d) => ` - "${d}"`).join("\n")}
|
|
|
68
249
|
- canonical
|
|
69
250
|
|
|
70
251
|
implementation_notes: |
|
|
71
|
-
|
|
252
|
+
Read .pi/architecture/modules/${slice.module}.md BEFORE implementing.
|
|
253
|
+
All Acceptance Criteria in that file must be satisfied before this issue is closed.
|
|
254
|
+
Component focus: ${comp.name}.
|
|
72
255
|
|
|
73
256
|
file_changes:
|
|
74
257
|
- "modify: src/${moduleId}/domain/"
|
|
@@ -76,85 +259,60 @@ ${component.dependencies.map((d) => ` - "${d}"`).join("\n")}
|
|
|
76
259
|
- "modify: src/${moduleId}/infrastructure/"
|
|
77
260
|
- "modify: src/${moduleId}/interfaces/"
|
|
78
261
|
- ${testLine}
|
|
79
|
-
- "create: tests/integration/${moduleId}/${componentName}/"
|
|
80
262
|
---
|
|
81
263
|
|
|
82
|
-
# ${issueId}: ${
|
|
264
|
+
# ${issueId}: ${title}
|
|
83
265
|
|
|
84
266
|
## Intent
|
|
85
267
|
|
|
86
|
-
|
|
268
|
+
Implement **${comp.name}** for the \`${slice.module}\` module.
|
|
269
|
+
|
|
270
|
+
> ⚠️ **Read before implementing:** \`.pi/architecture/modules/${slice.module}.md\`
|
|
271
|
+
> Every item in the **Acceptance Criteria** section of that file must be satisfied
|
|
272
|
+
> before this issue is closed — including adapter, mapper, and WireMock items.
|
|
87
273
|
|
|
88
274
|
## Architecture Context
|
|
89
275
|
|
|
90
276
|
- **Module:** ${slice.module}
|
|
91
|
-
- **Component:** ${
|
|
92
|
-
- **Status:** ${
|
|
93
|
-
- **Dependencies:** ${
|
|
94
|
-
|
|
95
|
-
## Dependencies
|
|
96
|
-
|
|
97
|
-
\`\`\`
|
|
98
|
-
${component.dependencies.map((d) => ` └── ${d}`).join("\n") || " └── (root component — no dependencies)"}
|
|
99
|
-
\`\`\`
|
|
100
|
-
|
|
101
|
-
## In Scope
|
|
102
|
-
|
|
103
|
-
- Implement ${component.name} for the ${slice.module} module
|
|
104
|
-
- Write unit tests for all public interfaces
|
|
105
|
-
- Add integration tests with upstream/downstream components
|
|
106
|
-
- Create API documentation
|
|
107
|
-
|
|
108
|
-
## Out of Scope
|
|
277
|
+
- **Component:** ${comp.name}
|
|
278
|
+
- **Status:** ${comp.status}
|
|
279
|
+
- **Dependencies:** ${comp.dependencies.length > 0 ? comp.dependencies.join(", ") : "none"}
|
|
109
280
|
|
|
110
|
-
|
|
111
|
-
- UI/frontend changes
|
|
112
|
-
- Deployment pipeline configuration
|
|
281
|
+
## In Scope (this component)
|
|
113
282
|
|
|
114
|
-
|
|
283
|
+
${inScope.map((s) => `- ${s}`).join("\n")}
|
|
115
284
|
|
|
116
|
-
|
|
117
|
-
- New domain models for ${componentName}
|
|
285
|
+
## Acceptance Criteria (this issue)
|
|
118
286
|
|
|
119
|
-
|
|
120
|
-
- New service/handler for ${componentName}
|
|
287
|
+
These acceptance criteria must be satisfied before this issue can be closed:
|
|
121
288
|
|
|
122
|
-
|
|
123
|
-
|
|
289
|
+
| # | Criterion | Verify In |
|
|
290
|
+
|---|-----------|-----------|
|
|
291
|
+
${compAcs.length > 0 ? compAcs.map((r) => `| ${r.num} | ${r.criterion} | ${r.verifyIn} |`).join("\n") : `| 1 | CI pipeline passes | \`validate-ci.sh\` |\n| 2 | All unit tests pass | \`validate-tests.sh\` |\n| 3 | Architecture compliance | \`validate-architecture.sh\` |\n| 4 | Canonical references valid | \`validate-canonical.sh\` |`}
|
|
124
292
|
|
|
125
|
-
|
|
126
|
-
- New endpoints or event handlers
|
|
293
|
+
## Full Module Acceptance Criteria
|
|
127
294
|
|
|
128
|
-
|
|
295
|
+
> All items below must pass before the **epic** is closed.
|
|
296
|
+
> Items may be split across multiple issues — verify your component's items before creating the MR.
|
|
129
297
|
|
|
130
|
-
|
|
298
|
+
${allAcMarkdown}
|
|
131
299
|
|
|
132
|
-
##
|
|
300
|
+
## Implementation Sequence (from module doc)
|
|
133
301
|
|
|
134
|
-
|
|
135
|
-
|---|-----------|-----------|
|
|
136
|
-
| 1 | CI pipeline passes | \`validate-ci.sh\` |
|
|
137
|
-
| 2 | All unit tests pass with ≥ 90% coverage | \`validate-tests.sh\` |
|
|
138
|
-
| 3 | Integration tests pass | \`validate-integration.sh\` |
|
|
139
|
-
| 4 | Security checks pass | \`validate-security.sh\` |
|
|
140
|
-
| 5 | Architecture compliance | \`validate-architecture.sh\` |
|
|
141
|
-
| 6 | Canonical references valid | \`validate-canonical.sh\` |
|
|
302
|
+
${stepsMarkdown}
|
|
142
303
|
|
|
143
304
|
## Implementation
|
|
144
305
|
|
|
145
|
-
> **Agent:**
|
|
146
|
-
>
|
|
306
|
+
> **Agent instructions:**
|
|
307
|
+
> 1. Open \`.pi/architecture/modules/${slice.module}.md\` — read the full Acceptance Criteria table
|
|
308
|
+
> 2. Identify which rows are your responsibility for **${comp.name}**
|
|
309
|
+
> 3. Implement all of them — entity, JPA entity, migration, adapter (all endpoints), mapper, use case
|
|
310
|
+
> 4. Verify each AC row is satisfied in \`src/\` before marking done
|
|
311
|
+
> 5. Run validators and create MR
|
|
147
312
|
|
|
148
313
|
### Steps
|
|
149
314
|
|
|
150
|
-
|
|
151
|
-
2. Create domain entities and interfaces
|
|
152
|
-
3. Implement application service/handler
|
|
153
|
-
4. Add infrastructure connections
|
|
154
|
-
5. Write unit tests (≥ 90% coverage)
|
|
155
|
-
6. Write integration tests
|
|
156
|
-
7. Run all validators
|
|
157
|
-
8. Create MR
|
|
315
|
+
${tddSteps}
|
|
158
316
|
`;
|
|
159
317
|
}
|
|
160
318
|
// ── Contract Freeze Generator ──
|
|
@@ -162,9 +320,13 @@ ${component.dependencies.map((d) => ` └── ${d}`).join("\n") || " └─
|
|
|
162
320
|
export function generateContractFreezeMarkdown(
|
|
163
321
|
slice: ArchitectureSlice,
|
|
164
322
|
epicName: string,
|
|
165
|
-
|
|
323
|
+
_codegenSkill?: string,
|
|
324
|
+
cwd?: string,
|
|
166
325
|
): string {
|
|
167
326
|
const moduleId = slice.module.replace(/^module-/, "");
|
|
327
|
+
const doc = cwd ? readModuleDoc(cwd, slice.module) : "";
|
|
328
|
+
const implSteps = parseImplSequence(doc);
|
|
329
|
+
const allAcs = parseAcTable(doc);
|
|
168
330
|
|
|
169
331
|
return `---
|
|
170
332
|
guardian_issue:
|
|
@@ -200,8 +362,8 @@ guardian_issue:
|
|
|
200
362
|
- module: ".pi/architecture/modules/${slice.module}.md"
|
|
201
363
|
|
|
202
364
|
acceptance_criteria:
|
|
203
|
-
- "All component interfaces defined as
|
|
204
|
-
- "DTO schemas documented"
|
|
365
|
+
- "All component interfaces defined as stubs (TODO bodies)"
|
|
366
|
+
- "DTO schemas documented with field names and types"
|
|
205
367
|
- "API contracts frozen and reviewed"
|
|
206
368
|
- "Implementation PRs reference these contracts"
|
|
207
369
|
|
|
@@ -213,7 +375,7 @@ guardian_issue:
|
|
|
213
375
|
Define the contract before any implementation. Every implementation issue
|
|
214
376
|
depends on this contract being frozen first. The contract should include:
|
|
215
377
|
interfaces, types, DTOs, event schemas, API paths, error formats.
|
|
216
|
-
${
|
|
378
|
+
${_codegenSkill ? `Follow: .pi/skills/agents/${_codegenSkill}.md` : ""}
|
|
217
379
|
|
|
218
380
|
file_changes:
|
|
219
381
|
- "create: src/${moduleId}/domain/"
|
|
@@ -256,11 +418,25 @@ ${slice.nextLogicalSlice.map((c: { name: string }) => `- ${c.name}`).join("\n")}
|
|
|
256
418
|
|
|
257
419
|
| # | Criterion | How to Verify |
|
|
258
420
|
|---|-----------|---------------|
|
|
259
|
-
| 1 | All component interfaces defined | Check src/<
|
|
421
|
+
| 1 | All component interfaces defined as stubs (TODO bodies) | Check src/<module>/domain/ and application/ |
|
|
260
422
|
| 2 | Contracts reviewed and frozen | PR approval |
|
|
261
|
-
| 3 | DTO schemas documented | OpenAPI /
|
|
423
|
+
| 3 | DTO schemas documented with field names and types | OpenAPI / record types |
|
|
262
424
|
| 4 | Implementation depends on contracts | No implementation without interface |
|
|
263
425
|
|
|
426
|
+
${allAcs.length > 0
|
|
427
|
+
? (() => {
|
|
428
|
+
const hasComp = allAcs.some((a) => a.component);
|
|
429
|
+
const header = hasComp
|
|
430
|
+
? `| # | Component | Criterion | Verify In |\n|---|-----------|-----------|-----------|`
|
|
431
|
+
: `| # | Criterion | Verify In |\n|---|-----------|-----------|`;
|
|
432
|
+
return `## Full Module Acceptance Criteria (for reference)\n\n> These are the complete ACs for the module. The contract freeze must define the interfaces\n> so every row below can be implemented in subsequent issues.\n\n${header}\n${acRowsToMarkdown(allAcs)}`;
|
|
433
|
+
})()
|
|
434
|
+
: ""}
|
|
435
|
+
|
|
436
|
+
${implSteps.length > 0
|
|
437
|
+
? `## Full Implementation Sequence (for reference)\n\n${implSteps.map((s) => '- ' + s).join("\n")}`
|
|
438
|
+
: ""}
|
|
439
|
+
|
|
264
440
|
## Implementation
|
|
265
441
|
|
|
266
442
|
> **Agent:** Create interface-only files. No implementation. Use Clean Architecture layers:
|
|
@@ -48,7 +48,6 @@ export function generateEpicTestFiles(options: TddGenerateOptions): string[] {
|
|
|
48
48
|
moduleId,
|
|
49
49
|
cwd,
|
|
50
50
|
language,
|
|
51
|
-
testBaseDir = "tests/unit",
|
|
52
51
|
moduleName,
|
|
53
52
|
} = options;
|
|
54
53
|
|
|
@@ -56,11 +55,23 @@ export function generateEpicTestFiles(options: TddGenerateOptions): string[] {
|
|
|
56
55
|
return [];
|
|
57
56
|
}
|
|
58
57
|
|
|
58
|
+
// ── Language-specific base dir & path strategy ──
|
|
59
|
+
let testBaseDir = options.testBaseDir;
|
|
60
|
+
let modulePath: string;
|
|
61
|
+
|
|
62
|
+
if (language === "java") {
|
|
63
|
+
testBaseDir = testBaseDir ?? "src/test/java";
|
|
64
|
+
modulePath = (moduleName || moduleId).replace(/[^a-zA-Z0-9\/]/g, "-");
|
|
65
|
+
} else {
|
|
66
|
+
testBaseDir = testBaseDir ?? "tests/unit";
|
|
67
|
+
modulePath = moduleName || moduleId;
|
|
68
|
+
}
|
|
69
|
+
|
|
59
70
|
const generated: string[] = [];
|
|
60
|
-
const modulePath = moduleName || moduleId;
|
|
61
71
|
|
|
62
72
|
for (const comp of components) {
|
|
63
|
-
const componentId = comp.name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
73
|
+
const componentId = comp.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
74
|
+
|
|
64
75
|
const testDir = join(cwd, testBaseDir, modulePath, componentId);
|
|
65
76
|
mkdirSync(testDir, { recursive: true });
|
|
66
77
|
|
|
@@ -125,7 +136,7 @@ function generateTestContent(
|
|
|
125
136
|
function generateTypeScript(
|
|
126
137
|
component: ModuleComponent,
|
|
127
138
|
moduleId: string,
|
|
128
|
-
|
|
139
|
+
_componentId: string,
|
|
129
140
|
): string {
|
|
130
141
|
const lines: string[] = [
|
|
131
142
|
`// Generated by Guardian TDD`,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
|
-
import type { ArchitectureSlice, EpicState, ExtensionAPI, ExtensionContext, ModuleComponent, RoadmapPhase
|
|
9
|
+
import type { ArchitectureSlice, EpicState, ExtensionAPI, ExtensionContext, ModuleComponent, RoadmapPhase } from "./architect-lib/types.ts";
|
|
10
10
|
import {
|
|
11
11
|
ARCH_MODULES_DIR,
|
|
12
12
|
commandExists,
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
loadRoadmapState,
|
|
23
23
|
parseModuleFile,
|
|
24
24
|
parseRoadmap,
|
|
25
|
-
readGroupId,
|
|
26
25
|
readRepository,
|
|
27
26
|
readLanguage,
|
|
28
27
|
readRepoTool,
|
|
@@ -187,8 +186,8 @@ class EpicManager {
|
|
|
187
186
|
+ "\n 3. Or set repoTool: \"local\" in guardian-manifest.json for local-only mode"
|
|
188
187
|
+ "\n",
|
|
189
188
|
);
|
|
190
|
-
|
|
191
|
-
return;
|
|
189
|
+
ctx.ui.notify("Epic aborted — remote setup failed. Set repoTool: \"local\" to run without a remote forge.", "error");
|
|
190
|
+
return null as unknown as EpicState;
|
|
192
191
|
}
|
|
193
192
|
|
|
194
193
|
const issues: { id: string; title: string; status: string; remoteIssueId?: string | null }[] = [];
|
|
@@ -246,7 +245,7 @@ class EpicManager {
|
|
|
246
245
|
status: "planned",
|
|
247
246
|
remoteIssueId: null as string | null,
|
|
248
247
|
};
|
|
249
|
-
const freezeMarkdown = generateContractFreezeMarkdown(slice, name);
|
|
248
|
+
const freezeMarkdown = generateContractFreezeMarkdown(slice, name, undefined, this.cwd);
|
|
250
249
|
writeFileSync(join(issuesDir, `${freezeId}.md`), freezeMarkdown);
|
|
251
250
|
if (hasRemote && remoteRepo) {
|
|
252
251
|
const result = createRemoteIssue(this.cwd, freezeEntry.title, join(issuesDir, `${freezeId}.md`), "epic,contract", remoteRepo);
|
|
@@ -260,14 +259,15 @@ class EpicManager {
|
|
|
260
259
|
// 2. Implementation issues
|
|
261
260
|
for (let i = 0; i < slice.nextLogicalSlice.length; i++) {
|
|
262
261
|
const comp = slice.nextLogicalSlice[i];
|
|
262
|
+
const moduleId = slice.module.replace(/^module-/, "");
|
|
263
263
|
const id = `issue-${comp.name.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
|
|
264
264
|
const entry = {
|
|
265
265
|
id,
|
|
266
|
-
title:
|
|
266
|
+
title: `Implement ${comp.name} — ${moduleId}`,
|
|
267
267
|
status: "planned" as string,
|
|
268
268
|
remoteIssueId: null as string | null,
|
|
269
269
|
};
|
|
270
|
-
const md = generateIssueMarkdown(comp, slice, i, slice.nextLogicalSlice.length, tdd);
|
|
270
|
+
const md = generateIssueMarkdown(comp, slice, i, slice.nextLogicalSlice.length, tdd, this.cwd);
|
|
271
271
|
writeFileSync(join(issuesDir, `${id}.md`), md);
|
|
272
272
|
if (hasRemote && remoteRepo) {
|
|
273
273
|
const result = createRemoteIssue(this.cwd, entry.title, join(issuesDir, `${id}.md`), "epic,implementation", remoteRepo);
|
|
@@ -1034,12 +1034,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
1034
1034
|
"",
|
|
1035
1035
|
"1. **## Related ADRs** — Links to ADR files with descriptions of relevance",
|
|
1036
1036
|
"2. **## Diagrams** — Embed the 4 mermaid diagrams from Step 2",
|
|
1037
|
-
"3. **##
|
|
1038
|
-
"
|
|
1039
|
-
"
|
|
1040
|
-
"
|
|
1041
|
-
"
|
|
1042
|
-
"
|
|
1037
|
+
"3. **## Acceptance Criteria** — Add a table row for each component: | # | Component | Criterion | Verify In | with specific, testable acceptance criteria",
|
|
1038
|
+
"5. **## Components** — Expand each aggregate with " + lang + " stubs, methods, invariants keep `status:` and `depends:` markers",
|
|
1039
|
+
"6. **Domain Events** — Table: Event, Description, Trigger, Payload, Published By",
|
|
1040
|
+
"7. **API Endpoints** — Table: Method, Path, Handler, Input, Output, Auth",
|
|
1041
|
+
"8. **Ubiquitous Language** — Terms for this context from `.pi/domain/ubiquitous-language.md`",
|
|
1042
|
+
"9. **Dependencies** — Depends On + Used By",
|
|
1043
|
+
"10. **Implementation Sequence** — Ordered build list",
|
|
1043
1044
|
"",
|
|
1044
1045
|
"### STEP 4: Generate Implementation Roadmap",
|
|
1045
1046
|
"",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"types": ["bun-types"],
|
|
4
|
+
"strict": true,
|
|
5
|
+
"target": "ES2022",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"allowImportingTsExtensions": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["**/*.ts"]
|
|
15
|
+
}
|
|
@@ -35,21 +35,21 @@ declare -a FAILURES=()
|
|
|
35
35
|
|
|
36
36
|
log_pass() {
|
|
37
37
|
echo -e " ${GREEN}✓ PASS${NC} $1"
|
|
38
|
-
((PASS_COUNT
|
|
39
|
-
((TOTAL_COUNT
|
|
38
|
+
PASS_COUNT=$((PASS_COUNT + 1))
|
|
39
|
+
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
log_fail() {
|
|
43
43
|
echo -e " ${RED}✗ FAIL${NC} $1 — $2"
|
|
44
|
-
((FAIL_COUNT
|
|
45
|
-
((TOTAL_COUNT
|
|
44
|
+
FAIL_COUNT=$((FAIL_COUNT + 1))
|
|
45
|
+
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
|
46
46
|
FAILURES+=("$1: $2")
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
log_skip() {
|
|
50
50
|
echo -e " ${YELLOW}⊘ SKIP${NC} $1 — $2"
|
|
51
|
-
((SKIP_COUNT
|
|
52
|
-
((TOTAL_COUNT
|
|
51
|
+
SKIP_COUNT=$((SKIP_COUNT + 1))
|
|
52
|
+
TOTAL_COUNT=$((TOTAL_COUNT + 1))
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
# ── Language Detection ──
|
|
@@ -23,9 +23,9 @@ NC='\033[0m'
|
|
|
23
23
|
PASS=0
|
|
24
24
|
FAIL=0
|
|
25
25
|
|
|
26
|
-
log_pass() { echo -e " ${GREEN}✓ PASS${NC} $1"; ((PASS
|
|
27
|
-
log_fail() { echo -e " ${RED}✗ FAIL${NC} $1 — $2"; ((FAIL
|
|
28
|
-
log_skip() { echo -e " ${YELLOW}⊘ SKIP${NC} $1 — $2"; ((PASS
|
|
26
|
+
log_pass() { echo -e " ${GREEN}✓ PASS${NC} $1"; PASS=$((PASS + 1)); }
|
|
27
|
+
log_fail() { echo -e " ${RED}✗ FAIL${NC} $1 — $2"; FAIL=$((FAIL + 1)); }
|
|
28
|
+
log_skip() { echo -e " ${YELLOW}⊘ SKIP${NC} $1 — $2"; PASS=$((PASS + 1)); }
|
|
29
29
|
|
|
30
30
|
echo "═══ Architecture Readiness Checks ═══"
|
|
31
31
|
echo ""
|