guardian-framework 0.1.104 → 0.1.109
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/cli.js +232 -16
- package/dist/exports.js +232 -16
- package/package.json +1 -1
- package/templates/languages/rust-patterns.md +5 -2
- package/templates/pi/context/checklists.md +15 -1
- package/templates/pi/domain/exploration.md +9 -0
- package/templates/pi/domain/ubiquitous-language.md +12 -0
- package/templates/pi/extensions/architect-lib/generators.ts +163 -12
- package/templates/pi/extensions/architect-lib/helpers.ts +49 -0
- package/templates/pi/extensions/architect-lib/tdd-generator.ts +1 -1
- package/templates/pi/extensions/architect-lib/types.ts +44 -2
- package/templates/pi/extensions/architect.ts +105 -41
- package/templates/pi/extensions/ask-user-question.ts +2 -2
- package/templates/pi/extensions/bash-guard.ts +2 -2
- package/templates/pi/extensions/config-reload.ts +12 -5
- package/templates/pi/extensions/coordinator.ts +2 -2
- package/templates/pi/extensions/curator.ts +1 -1
- package/templates/pi/extensions/domain-explorer.ts +50 -3
- package/templates/pi/extensions/filechanges.ts +2 -2
- package/templates/pi/extensions/goal-loop.ts +3 -3
- package/templates/pi/extensions/hooks.ts +1 -1
- package/templates/pi/extensions/pi-env.d.ts +96 -0
- package/templates/pi/extensions/pipeline.ts +1 -1
- package/templates/pi/extensions/project-scaffolder.ts +1 -1
- package/templates/pi/extensions/read-only-mode.ts +5 -1
- package/templates/pi/extensions/tsconfig.json +9 -2
- package/templates/pi/preflight_report.json +1 -1
- package/templates/pi/prompts/ci-blueprint.md +2 -2
- package/templates/pi/scripts/check_model_judgment.sh +28 -0
- package/templates/pi/scripts/ci/check_judgment_contracts.sh +93 -0
- package/templates/pi/scripts/ci/run_preflight.sh +3 -3
- package/templates/pi/scripts/ci/run_stage.sh +1 -1
- package/templates/pi/scripts/ci/stage_lint.sh +1 -1
- package/templates/pi/scripts/coverage.sh +58 -0
- package/templates/pi/scripts/install_coverage_tools.sh +43 -0
- package/templates/pi/scripts/languages/go/check_model_judgment.sh +70 -0
- package/templates/pi/scripts/languages/go/check_runtime_reachability.sh +34 -0
- package/templates/pi/scripts/languages/go/coverage.sh +78 -0
- package/templates/pi/scripts/languages/go/install_coverage_tools.sh +17 -0
- package/templates/pi/scripts/languages/java/check_model_judgment.sh +142 -0
- package/templates/pi/scripts/languages/java/check_runtime_reachability.sh +31 -0
- package/templates/pi/scripts/languages/java/coverage.sh +103 -0
- package/templates/pi/scripts/languages/java/install_coverage_tools.sh +40 -0
- package/templates/pi/scripts/languages/java/validate-architecture.sh +20 -0
- package/templates/pi/scripts/languages/python/check_model_judgment.sh +67 -0
- package/templates/pi/scripts/languages/python/check_runtime_reachability.sh +34 -0
- package/templates/pi/scripts/languages/python/coverage.sh +88 -0
- package/templates/pi/scripts/languages/python/install_coverage_tools.sh +22 -0
- package/templates/pi/scripts/languages/rust/check_model_judgment.sh +61 -0
- package/templates/pi/scripts/languages/rust/check_runtime_reachability.sh +40 -0
- package/templates/pi/scripts/languages/rust/coverage.sh +79 -0
- package/templates/pi/scripts/languages/rust/install_coverage_tools.sh +20 -0
- package/templates/pi/scripts/languages/rust/validate-tests.sh +9 -6
- package/templates/pi/scripts/languages/typescript/check_model_judgment.sh +81 -0
- package/templates/pi/scripts/languages/typescript/check_runtime_reachability.sh +41 -0
- package/templates/pi/scripts/languages/typescript/coverage.sh +154 -0
- package/templates/pi/scripts/languages/typescript/install_coverage_tools.sh +41 -0
- package/templates/pi/scripts/languages/typescript/validate-canonical.sh +6 -2
- package/templates/pi/scripts/languages/typescript/validate-ci.sh +2 -2
- package/templates/pi/scripts/validate-architecture-readiness.sh +29 -0
- package/templates/pi/scripts/validate-exploration-depth.sh +112 -0
- package/templates/pi/scripts/validate-runtime-wiring.sh +35 -0
- package/templates/pi/skills/agents/architecture-validator.md +28 -0
- package/templates/pi/skills/agents/test-validator.md +1 -1
- package/templates/pi/skills/go-enterprise-codegen.md +46 -0
- package/templates/pi/skills/java-spring-enterprise-codegen.md +50 -0
- package/templates/pi/skills/python-enterprise-codegen.md +46 -0
- package/templates/pi/skills/rust-enterprise-codegen.md +46 -0
- package/templates/pi/skills/tdd-practice.md +17 -9
- package/templates/pi/skills/typescript-enterprise-codegen.md +46 -0
|
@@ -34,6 +34,10 @@ import {
|
|
|
34
34
|
generateContractFreezeMarkdown,
|
|
35
35
|
generateIssueMarkdown,
|
|
36
36
|
generateProofingMarkdown,
|
|
37
|
+
generateRuntimeIntegrationMarkdown,
|
|
38
|
+
moduleHasRuntimeIntegration,
|
|
39
|
+
detectLanguage,
|
|
40
|
+
testBaseDirFromLang,
|
|
37
41
|
} from "./architect-lib/generators";
|
|
38
42
|
import {
|
|
39
43
|
generateEpicTestFiles,
|
|
@@ -108,7 +112,7 @@ function formatEpicStatus(state: EpicState | null): string {
|
|
|
108
112
|
class EpicManager {
|
|
109
113
|
private state: EpicState | null;
|
|
110
114
|
|
|
111
|
-
constructor(private cwd: string) {
|
|
115
|
+
constructor(private readonly cwd: string) {
|
|
112
116
|
this.state = loadEpicState(cwd);
|
|
113
117
|
}
|
|
114
118
|
|
|
@@ -116,7 +120,8 @@ class EpicManager {
|
|
|
116
120
|
return this.state;
|
|
117
121
|
}
|
|
118
122
|
|
|
119
|
-
|
|
123
|
+
// NOSONAR: architect orchestrator (deliberate multi-branch dispatcher)
|
|
124
|
+
async startEpic( // NOSONAR: architect orchestrator — deliberate multi-branch dispatcher
|
|
120
125
|
ctx: ExtensionContext,
|
|
121
126
|
name: string,
|
|
122
127
|
trackingIssueId?: string,
|
|
@@ -137,9 +142,33 @@ class EpicManager {
|
|
|
137
142
|
slice = { module: matchedModule.replace(".md", ""), components, nextLogicalSlice: planned };
|
|
138
143
|
}
|
|
139
144
|
}
|
|
140
|
-
// Fallback:
|
|
145
|
+
// Fallback: auto-select ONLY when unambiguous. If the epic name did not
|
|
146
|
+
// match a module doc and several modules still have planned components,
|
|
147
|
+
// error with candidates instead of silently planning the wrong module
|
|
148
|
+
// (previously this silently picked the first match — e.g. stale docs).
|
|
141
149
|
if (!slice) {
|
|
142
|
-
|
|
150
|
+
const candidates: { file: string; planned: number }[] = [];
|
|
151
|
+
for (const f of moduleFiles) {
|
|
152
|
+
const comps = parseModuleFile(join(this.cwd, ARCH_MODULES_DIR, f));
|
|
153
|
+
const planned = comps.filter((c: ModuleComponent) => c.status === "planned");
|
|
154
|
+
if (planned.length > 0) candidates.push({ file: f, planned: planned.length });
|
|
155
|
+
}
|
|
156
|
+
if (candidates.length === 1) {
|
|
157
|
+
const c = candidates[0];
|
|
158
|
+
const comps = parseModuleFile(join(this.cwd, ARCH_MODULES_DIR, c.file));
|
|
159
|
+
slice = {
|
|
160
|
+
module: c.file.replace(".md", ""),
|
|
161
|
+
components: comps,
|
|
162
|
+
nextLogicalSlice: comps.filter((x: ModuleComponent) => x.status === "planned"),
|
|
163
|
+
};
|
|
164
|
+
} else if (candidates.length > 1) {
|
|
165
|
+
const list = candidates
|
|
166
|
+
.map((c) => ` - ${c.file.replace(".md", "")} (${c.planned} planned)`)
|
|
167
|
+
.join("\n");
|
|
168
|
+
throw new Error(
|
|
169
|
+
`Epic name "${name}" did not match any architecture module, and ${candidates.length} modules have planned components:\n${list}\nRename the epic to match a module (e.g. /architect --epic "Module Name") or mark stale module components as implemented.`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
143
172
|
}
|
|
144
173
|
if (!slice) {
|
|
145
174
|
throw new Error("All architecture components are implemented. No next slice found.");
|
|
@@ -279,6 +308,30 @@ class EpicManager {
|
|
|
279
308
|
issues.push(entry);
|
|
280
309
|
}
|
|
281
310
|
|
|
311
|
+
// 2.5 Runtime Integration & Legacy Migration (ADR-009) — mandatory when
|
|
312
|
+
// the module doc documents runtime integration. Placed after the
|
|
313
|
+
// implementation issues and before proofing so "implemented but never
|
|
314
|
+
// wired" cannot be deferred by a scope note.
|
|
315
|
+
if (moduleHasRuntimeIntegration(this.cwd, slice.module)) {
|
|
316
|
+
const integrationId = "issue-runtime-integration";
|
|
317
|
+
const integrationEntry = {
|
|
318
|
+
id: integrationId,
|
|
319
|
+
title: "Runtime Integration & Legacy Migration",
|
|
320
|
+
status: "planned" as string,
|
|
321
|
+
remoteIssueId: null as string | null,
|
|
322
|
+
};
|
|
323
|
+
const integrationMd = generateRuntimeIntegrationMarkdown(slice, name, this.cwd);
|
|
324
|
+
writeFileSync(join(issuesDir, `${integrationId}.md`), integrationMd);
|
|
325
|
+
if (hasRemote && remoteRepo) {
|
|
326
|
+
const result = createRemoteIssue(this.cwd, integrationEntry.title, join(issuesDir, `${integrationId}.md`), "epic,runtime-integration", remoteRepo);
|
|
327
|
+
if (result.success && result.issueNumber) {
|
|
328
|
+
integrationEntry.remoteIssueId = result.issueNumber;
|
|
329
|
+
if (effectiveTrackingId) linkRemoteIssue(this.cwd, result.issueNumber, effectiveTrackingId);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
issues.push(integrationEntry);
|
|
333
|
+
}
|
|
334
|
+
|
|
282
335
|
// 3. Proofing
|
|
283
336
|
const proofingId = "issue-proofing";
|
|
284
337
|
const proofingEntry = {
|
|
@@ -342,7 +395,10 @@ class EpicManager {
|
|
|
342
395
|
`**Components:** ${slice.nextLogicalSlice.length}`,
|
|
343
396
|
"",
|
|
344
397
|
"## Issues",
|
|
345
|
-
...issues.map((i) =>
|
|
398
|
+
...issues.map((i) => {
|
|
399
|
+
const ref = i.remoteIssueId ? ` (#${i.remoteIssueId})` : "";
|
|
400
|
+
return `- ${i.title}${ref}`;
|
|
401
|
+
}),
|
|
346
402
|
"",
|
|
347
403
|
"---",
|
|
348
404
|
"Auto-generated by Guardian Architect",
|
|
@@ -382,21 +438,31 @@ class EpicManager {
|
|
|
382
438
|
|
|
383
439
|
// ── Extension ──
|
|
384
440
|
|
|
385
|
-
export default function (pi: ExtensionAPI) {
|
|
386
|
-
let manager: EpicManager | null = null;
|
|
387
441
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
442
|
+
function randomPL(): string {
|
|
443
|
+
const buf = new Uint8Array(2);
|
|
444
|
+
(globalThis as { crypto?: { getRandomValues?: (a: Uint8Array) => void } }).crypto?.getRandomValues?.(buf);
|
|
445
|
+
const n = ((buf[0] << 8) | buf[1]) % 10000;
|
|
446
|
+
return `PL-${String(n).padStart(4, "0")}`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function findFlag(tokens: string[], prefix: string): string | undefined {
|
|
450
|
+
const eqMatch = tokens.find((a) => a.startsWith(`${prefix}=`));
|
|
451
|
+
if (eqMatch) return eqMatch.split("=").slice(1).join("=");
|
|
452
|
+
const idx = tokens.indexOf(prefix);
|
|
453
|
+
if (idx >= 0 && idx + 1 < tokens.length && !tokens[idx + 1].startsWith("--")) return tokens[idx + 1];
|
|
454
|
+
return undefined;
|
|
394
455
|
}
|
|
395
456
|
|
|
457
|
+
export default function architectExtension(pi: ExtensionAPI) {
|
|
458
|
+
let manager: EpicManager | null = null;
|
|
459
|
+
|
|
460
|
+
|
|
396
461
|
pi.registerCommand("architect", {
|
|
397
462
|
description: "Orchestrate the full architecture-to-implementation process",
|
|
398
|
-
|
|
399
|
-
|
|
463
|
+
// NOSONAR: architect command dispatcher (deliberate multi-branch router)
|
|
464
|
+
handler: async function architectHandler(args: unknown, ctx: ExtensionContext) { // NOSONAR: architect command router — deliberate multi-branch dispatcher
|
|
465
|
+
manager ??= new EpicManager(ctx.cwd);
|
|
400
466
|
const raw = typeof args === "string" ? args : "";
|
|
401
467
|
const tokens = raw ? parseArgs(raw) : [];
|
|
402
468
|
if (tokens.length === 0) {
|
|
@@ -451,7 +517,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
451
517
|
}
|
|
452
518
|
|
|
453
519
|
if (tokens[0] === "--phase" || action === "phase") {
|
|
454
|
-
const phaseName = (tokens.slice(1).join(" ") || findFlag(tokens, "--phase")).replace(/["']/g, "").trim();
|
|
520
|
+
const phaseName = (tokens.slice(1).join(" ") || findFlag(tokens, "--phase") || "").replace(/["']/g, "").trim();
|
|
455
521
|
if (!phaseName) {
|
|
456
522
|
ctx.ui.notify('Usage: /architect --phase "Phase 1"', "error");
|
|
457
523
|
return;
|
|
@@ -465,9 +531,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
465
531
|
|
|
466
532
|
// Find phase by name or index
|
|
467
533
|
let targetPhase: RoadmapPhase | undefined;
|
|
468
|
-
const idxMatch =
|
|
534
|
+
const idxMatch = /Phase\s+(\d+)/i.exec(phaseName);
|
|
469
535
|
if (idxMatch) {
|
|
470
|
-
targetPhase = phases.find((p) => p.index === parseInt(idxMatch[1], 10));
|
|
536
|
+
targetPhase = phases.find((p) => p.index === Number.parseInt(idxMatch[1], 10));
|
|
471
537
|
} else {
|
|
472
538
|
targetPhase = phases.find((p) => p.title.toLowerCase().includes(phaseName.toLowerCase()));
|
|
473
539
|
}
|
|
@@ -482,7 +548,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
482
548
|
if (dep.toLowerCase() === "none") return false;
|
|
483
549
|
const depMatch = dep.match(/Phase\s+(\d+)/i);
|
|
484
550
|
if (!depMatch) return false;
|
|
485
|
-
const depPhase = phases.find((p) => p.index === parseInt(depMatch[1], 10));
|
|
551
|
+
const depPhase = phases.find((p) => p.index === Number.parseInt(depMatch[1], 10));
|
|
486
552
|
return depPhase && depPhase.status !== "done";
|
|
487
553
|
});
|
|
488
554
|
if (unmetDeps.length > 0) {
|
|
@@ -537,7 +603,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
537
603
|
}
|
|
538
604
|
const pipelineFile = join(ctx.cwd, ".pi/.guardian-pipeline-state.json");
|
|
539
605
|
if (!existsSync(pipelineFile) && existingIssues.length > 0) {
|
|
540
|
-
const pipelineId =
|
|
606
|
+
const pipelineId = randomPL();
|
|
541
607
|
const pipelineState = {
|
|
542
608
|
id: pipelineId,
|
|
543
609
|
name: mod.name,
|
|
@@ -579,10 +645,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
579
645
|
const rs = loadRoadmapState(ctx.cwd) || {
|
|
580
646
|
phases: [], currentPhaseIndex: 0, startedAt: "", updatedAt: "",
|
|
581
647
|
};
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
648
|
+
const roadmapPhase = rs.phases.find((p) => p.index === targetPhase.index);
|
|
649
|
+
if (roadmapPhase) {
|
|
650
|
+
const comp = new Set(roadmapPhase.completedModules || []);
|
|
651
|
+
comp.add(mod.name);
|
|
652
|
+
roadmapPhase.completedModules = Array.from(comp);
|
|
586
653
|
}
|
|
587
654
|
rs.updatedAt = new Date().toISOString();
|
|
588
655
|
saveRoadmapState(ctx.cwd, rs);
|
|
@@ -590,13 +657,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
590
657
|
}
|
|
591
658
|
try {
|
|
592
659
|
const state = await manager.startEpic(ctx, mod.name);
|
|
593
|
-
if (!state
|
|
660
|
+
if (!state?.slices?.length) {
|
|
594
661
|
results.push(`⚠️ ${mod.name} — no architecture components found`);
|
|
595
662
|
continue;
|
|
596
663
|
}
|
|
597
664
|
const items = (state.issues || []).map((i: { id: string }) => i.id);
|
|
598
665
|
// Create pipeline state for this epic
|
|
599
|
-
const pipelineId =
|
|
666
|
+
const pipelineId = randomPL();
|
|
600
667
|
const pipelineState = {
|
|
601
668
|
id: pipelineId,
|
|
602
669
|
name: mod.name,
|
|
@@ -670,8 +737,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
670
737
|
}
|
|
671
738
|
|
|
672
739
|
if (tokens[0] === "--phase-module-done" || action === "phase-module-done") {
|
|
673
|
-
const phaseIdx = parseInt(tokens[1] || "", 10);
|
|
674
|
-
if (isNaN(phaseIdx) || !tokens[2]) {
|
|
740
|
+
const phaseIdx = Number.parseInt(tokens[1] || "", 10);
|
|
741
|
+
if (Number.isNaN(phaseIdx) || !tokens[2]) {
|
|
675
742
|
ctx.ui.notify('Usage: /architect --phase-module-done <phase-number> "<module-name>"', "error");
|
|
676
743
|
return;
|
|
677
744
|
}
|
|
@@ -713,8 +780,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
713
780
|
}
|
|
714
781
|
|
|
715
782
|
if (tokens[0] === "--phase-done" || action === "phase-done") {
|
|
716
|
-
const phaseIdx = parseInt(tokens[1] || "", 10);
|
|
717
|
-
if (isNaN(phaseIdx)) {
|
|
783
|
+
const phaseIdx = Number.parseInt(tokens[1] || "", 10);
|
|
784
|
+
if (Number.isNaN(phaseIdx)) {
|
|
718
785
|
ctx.ui.notify('Usage: /architect --phase-done <phase-number>', "error");
|
|
719
786
|
return;
|
|
720
787
|
}
|
|
@@ -778,7 +845,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
778
845
|
|
|
779
846
|
const state = await manager.startEpic(ctx, epicName, trackingIssueId, tddEnabled);
|
|
780
847
|
|
|
781
|
-
if (!state
|
|
848
|
+
if (!state?.slices?.length) {
|
|
782
849
|
ctx.ui.notify("Failed to discover architecture components. Check .pi/architecture/modules/.", "error");
|
|
783
850
|
return;
|
|
784
851
|
}
|
|
@@ -806,7 +873,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
806
873
|
} catch { /* ignore */ }
|
|
807
874
|
|
|
808
875
|
// Write pipeline state directly (ctx.tools not available in command handlers)
|
|
809
|
-
const pipelineId =
|
|
876
|
+
const pipelineId = randomPL();
|
|
810
877
|
const pipelineState = {
|
|
811
878
|
id: pipelineId,
|
|
812
879
|
name: epicName,
|
|
@@ -837,7 +904,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
837
904
|
: "";
|
|
838
905
|
|
|
839
906
|
const firstItem = items[0];
|
|
840
|
-
const issueFilename = `${firstItem}.md`.
|
|
907
|
+
const issueFilename = `${firstItem}.md`.replaceAll("/", "-");
|
|
841
908
|
const issuePath = join(ctx.cwd, ".pi/issues", issueFilename);
|
|
842
909
|
|
|
843
910
|
let issueContent = "";
|
|
@@ -870,7 +937,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
870
937
|
"",
|
|
871
938
|
"**TDD Mode: ON**",
|
|
872
939
|
"- Failing test files were generated from architecture contracts before issues were created.",
|
|
873
|
-
"- Start each component by examining its test file in the `
|
|
940
|
+
"- Start each component by examining its test file in the `" + testBaseDirFromLang(detectLanguage(ctx.cwd)) + "/` directory.",
|
|
874
941
|
"- Run tests first to see them fail, then implement to make them pass (Red→Green→Refactor).",
|
|
875
942
|
"- Tests are living artifacts — evolve them as the component grows.",
|
|
876
943
|
"- Reference `.pi/skills/tdd-practice.md` for TDD guidance.",
|
|
@@ -900,7 +967,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
900
967
|
description: "Show the current epic status and progress.",
|
|
901
968
|
parameters: { type: "object", properties: {} },
|
|
902
969
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
903
|
-
|
|
970
|
+
manager ??= new EpicManager(ctx.cwd);
|
|
904
971
|
const state = manager.getState();
|
|
905
972
|
return { content: [{ type: "text", text: formatEpicStatus(state) }] };
|
|
906
973
|
},
|
|
@@ -920,18 +987,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
920
987
|
for (const file of moduleFiles) {
|
|
921
988
|
const components = parseModuleFile(join(ctx.cwd, ".pi/architecture/modules", file));
|
|
922
989
|
const planned = components.filter((c) => c.status === "planned");
|
|
923
|
-
lines.push(`### ${file.replace(".md", "")}`);
|
|
924
|
-
lines.push(` Components: ${components.length} (${planned.length} planned)`);
|
|
990
|
+
lines.push(`### ${file.replace(".md", "")}`, ` Components: ${components.length} (${planned.length} planned)`);
|
|
925
991
|
if (planned.length > 0) {
|
|
926
|
-
lines.push(" Next slice:");
|
|
927
|
-
for (const c of planned) lines.push(` - ${c.name}`);
|
|
992
|
+
lines.push(" Next slice:", ...planned.map((c: ModuleComponent) => ` - ${c.name}`));
|
|
928
993
|
}
|
|
929
994
|
lines.push("");
|
|
930
995
|
}
|
|
931
996
|
const slice = findNextLogicalSlice(ctx.cwd, moduleFiles);
|
|
932
997
|
if (slice) {
|
|
933
|
-
lines.push(`**Recommended next epic:** ${slice.module}`);
|
|
934
|
-
lines.push(`Components: ${slice.nextLogicalSlice.map((c: ModuleComponent) => c.name).join(", ")}`);
|
|
998
|
+
lines.push(`**Recommended next epic:** ${slice.module}`, `Components: ${slice.nextLogicalSlice.map((c: ModuleComponent) => c.name).join(", ")}`);
|
|
935
999
|
}
|
|
936
1000
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
937
1001
|
},
|
|
@@ -182,7 +182,7 @@ async function askSingleChoice(
|
|
|
182
182
|
{ id: "other", label: otherLabel, value: "__other__", isOther: true },
|
|
183
183
|
];
|
|
184
184
|
|
|
185
|
-
return ctx.ui.custom
|
|
185
|
+
return (ctx.ui as any).custom(
|
|
186
186
|
(tui: any, theme: any, _kb: any, done: (r: AskAnswer | null) => void) => {
|
|
187
187
|
let optionIndex = 0;
|
|
188
188
|
let editMode = false;
|
|
@@ -306,7 +306,7 @@ async function askMultiChoice(
|
|
|
306
306
|
{ id: "submit", label: "Submit", value: "__submit__", isSubmit: true },
|
|
307
307
|
];
|
|
308
308
|
|
|
309
|
-
return ctx.ui.custom
|
|
309
|
+
return (ctx.ui as any).custom(
|
|
310
310
|
(tui: any, theme: any, _kb: any, done: (r: AskAnswer[] | null) => void) => {
|
|
311
311
|
let optionIndex = 0;
|
|
312
312
|
let editMode = false;
|
|
@@ -65,7 +65,7 @@ function tokenizeCommand(cmd: string): { parts: string[]; ops: string[] } {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// Operators: |, ||, &&, ;, >, >>, <
|
|
68
|
-
if (ch === "|" || ch === ";" || ch === "<" || ch === ">") {
|
|
68
|
+
if (ch === "|" || ch === ";" || ch === "<" || ch === ">" || ch === "&") {
|
|
69
69
|
if (current.trim()) parts.push(current.trim());
|
|
70
70
|
current = "";
|
|
71
71
|
|
|
@@ -304,7 +304,7 @@ async function promptRunOrAbort(ctx: any, command: string, risk: Risk): Promise<
|
|
|
304
304
|
{ value: "abort", label: "Abort", description: "Block this command" },
|
|
305
305
|
];
|
|
306
306
|
|
|
307
|
-
const choice = await ctx.ui.custom
|
|
307
|
+
const choice = await (ctx.ui as any).custom(
|
|
308
308
|
(tui, theme, _kb, done) => {
|
|
309
309
|
const container = new Container();
|
|
310
310
|
container.addChild(
|
|
@@ -14,12 +14,12 @@ import * as fs from "node:fs";
|
|
|
14
14
|
import * as path from "node:path";
|
|
15
15
|
|
|
16
16
|
type ExtensionAPI = {
|
|
17
|
-
on(event: string, handler: (event: unknown, ctx:
|
|
17
|
+
on(event: string, handler: (event: unknown, ctx: ReloadCtx) => void | Promise<void>): void;
|
|
18
18
|
registerCommand(
|
|
19
19
|
name: string,
|
|
20
20
|
options: {
|
|
21
21
|
description: string;
|
|
22
|
-
handler(args: string[], ctx:
|
|
22
|
+
handler(args: string[], ctx: ReloadCtx): unknown | Promise<unknown>;
|
|
23
23
|
},
|
|
24
24
|
): void;
|
|
25
25
|
};
|
|
@@ -35,11 +35,18 @@ function parseFrontMatter(text: string): string {
|
|
|
35
35
|
return text.slice(4, end);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
type ReloadUi = {
|
|
39
|
+
notify(message: string, level?: string): void;
|
|
40
|
+
setStatus(key: string, message: string | null): void;
|
|
41
|
+
theme?: { fg(role: string, text: string): string };
|
|
42
|
+
};
|
|
43
|
+
type ReloadCtx = { ui?: ReloadUi };
|
|
44
|
+
|
|
38
45
|
let lastKnownConfig = "";
|
|
39
46
|
let watcher: fs.FSWatcher | undefined;
|
|
40
47
|
let reloadCount = 0;
|
|
41
48
|
|
|
42
|
-
function startWatching(ctx:
|
|
49
|
+
function startWatching(ctx: ReloadCtx): void {
|
|
43
50
|
if (!fs.existsSync(CONFIG_FILE)) return;
|
|
44
51
|
|
|
45
52
|
lastKnownConfig = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
@@ -82,7 +89,7 @@ function stopWatching(): void {
|
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
export default function (pi: ExtensionAPI) {
|
|
85
|
-
pi.on("session_start", async (_event: unknown, ctx:
|
|
92
|
+
pi.on("session_start", async (_event: unknown, ctx: ReloadCtx) => {
|
|
86
93
|
stopWatching();
|
|
87
94
|
startWatching(ctx);
|
|
88
95
|
});
|
|
@@ -93,7 +100,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
93
100
|
|
|
94
101
|
pi.registerCommand("reload-config", {
|
|
95
102
|
description: "Manually reload workflow config from AGENTS.md",
|
|
96
|
-
handler: async (_args: string[], ctx:
|
|
103
|
+
handler: async (_args: string[], ctx: ReloadCtx) => {
|
|
97
104
|
if (!fs.existsSync(CONFIG_FILE)) {
|
|
98
105
|
ctx?.ui?.notify("No .pi/agent/AGENTS.md found.", "warning");
|
|
99
106
|
return;
|
|
@@ -24,7 +24,7 @@ type ExtensionContext = {
|
|
|
24
24
|
type ToolCallEvent = { toolName: string; toolCallId: string; input: { command: string } };
|
|
25
25
|
|
|
26
26
|
type ExtensionAPI = {
|
|
27
|
-
on(event: string, handler: (event: unknown, ctx: ExtensionContext) =>
|
|
27
|
+
on(event: string, handler: (event: unknown, ctx: ExtensionContext) => unknown | Promise<unknown>): void;
|
|
28
28
|
registerTool(options: {
|
|
29
29
|
name: string;
|
|
30
30
|
label: string;
|
|
@@ -34,7 +34,7 @@ type ExtensionAPI = {
|
|
|
34
34
|
toolCallId: string,
|
|
35
35
|
params: Record<string, unknown>,
|
|
36
36
|
signal: AbortSignal,
|
|
37
|
-
onUpdate: (update: { type
|
|
37
|
+
onUpdate: (update: { type?: string; message?: string; content?: unknown }) => void,
|
|
38
38
|
ctx: ExtensionContext,
|
|
39
39
|
): unknown | Promise<unknown>;
|
|
40
40
|
}): void;
|
|
@@ -464,6 +464,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
464
464
|
addCheck("Glossary file", false, "Not found");
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
+
// 4b. ADR-008 modeling-depth rules (deterministic, over raw session JSON)
|
|
468
|
+
const depthRawPath = sessionPath.replace(/\.md$/, "") + ".raw.json";
|
|
469
|
+
const depthScript = path.join(ctx.cwd, ".pi", "scripts", "validate-exploration-depth.sh");
|
|
470
|
+
if (fs.existsSync(depthRawPath) && fs.existsSync(depthScript)) {
|
|
471
|
+
try {
|
|
472
|
+
const depthResult = await shellExec(ctx, "bash " + depthScript + " " + depthRawPath);
|
|
473
|
+
const lines = depthResult.stdout.trim().split("\n").filter((l) => l.trim() !== "");
|
|
474
|
+
const violations = lines.filter((l) => l.includes("[") && l.includes("] error:"));
|
|
475
|
+
addCheck(
|
|
476
|
+
"Modeling depth rules (ADR-008)",
|
|
477
|
+
depthResult.exitCode === 0,
|
|
478
|
+
violations.length > 0 ? violations.join(" | ") : lines.slice(-1)[0] || "",
|
|
479
|
+
);
|
|
480
|
+
} catch (err) {
|
|
481
|
+
addCheck("Modeling depth rules (ADR-008)", false, "Error: " + String(err));
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
484
|
+
addCheck("Modeling depth rules (ADR-008) - skipped", true, "no session raw.json");
|
|
485
|
+
}
|
|
486
|
+
|
|
467
487
|
// 4. Source code drift
|
|
468
488
|
const scriptPath = path.join(ctx.cwd, ".pi", "scripts", "validate-ubiquitous-language.sh");
|
|
469
489
|
if (fs.existsSync(scriptPath)) {
|
|
@@ -988,8 +1008,35 @@ export default function (pi: ExtensionAPI) {
|
|
|
988
1008
|
"- Current ADR-001: `.pi/architecture/decisions/ADR-001-architecture-pattern.md`",
|
|
989
1009
|
"- System diagram: `.pi/architecture/diagrams/system-context.md`",
|
|
990
1010
|
"- Module stubs: `.pi/architecture/modules/` (" + moduleFiles.length + " files)",
|
|
1011
|
+
"- ADR-008 modeling depth: `.pi/architecture/decisions/ADR-008-proportional-modeling-depth.md`",
|
|
1012
|
+
"- Structured session JSON (depth fields): `.pi/domain/exploration/" + sessionId + ".raw.json`",
|
|
991
1013
|
"",
|
|
992
|
-
"### STEP
|
|
1014
|
+
"### STEP 0: ADR-008 Modeling Depth (read first, applies everywhere)",
|
|
1015
|
+
"",
|
|
1016
|
+
"Read the `**Modeling Depth:**` header of EVERY module doc plus the structured",
|
|
1017
|
+
"session JSON (`.raw.json`). Enrich PROPORTIONALLY — never rewrite the depth header,",
|
|
1018
|
+
"never delete an existing `Modeling Depth Note (ADR-008)` section, and never invent",
|
|
1019
|
+
"modeling the exploration did not capture:",
|
|
1020
|
+
"",
|
|
1021
|
+
"- **tactical** (core subdomain): full DDD enrichment — aggregates as consistency",
|
|
1022
|
+
" boundaries with the exploration invariants (`rule` + illegalStatePrevented),",
|
|
1023
|
+
" behavior methods not setters, repository interface owned by the domain layer,",
|
|
1024
|
+
" value objects with entity-vs-VO justification, triaged domain events with payloads,",
|
|
1025
|
+
" and explicit domain / application / infrastructure service classification.",
|
|
1026
|
+
"- **service** (supporting subdomain): enrich to a Service + Persistence Contract.",
|
|
1027
|
+
" Do NOT expand aggregates, value objects, domain events, or API surfaces beyond",
|
|
1028
|
+
" what the exploration captured.",
|
|
1029
|
+
"- **adapter** (generic subdomain): enrich to an Interface + Anti-Corruption Layer —",
|
|
1030
|
+
" the narrow interface the domain consumes, the vendor/OSS choice, the ACL",
|
|
1031
|
+
" translation and its context-map edge. NO domain entities or aggregates.",
|
|
1032
|
+
"",
|
|
1033
|
+
"Language judgment: for Java/Spring modules apply the decision tables in the",
|
|
1034
|
+
"`java-spring-enterprise-codegen` skill section 10 (entity-vs-VO, service classifier,",
|
|
1035
|
+
"event triage, transaction placement — `@Transactional` only in application services).",
|
|
1036
|
+
"Diagrams and section breadth follow each module's depth (full module-level diagram",
|
|
1037
|
+
"set only for tactical; service = data-flow + sequence; adapter = ACL/integration).",
|
|
1038
|
+
"",
|
|
1039
|
+
"### STEP 1: Generate All ADRs",
|
|
993
1040
|
"",
|
|
994
1041
|
"**Do this first, before touching any module docs.** Module enrichment needs ADR references.",
|
|
995
1042
|
"",
|
|
@@ -1035,8 +1082,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1035
1082
|
"1. **## Related ADRs** — Links to ADR files with descriptions of relevance",
|
|
1036
1083
|
"2. **## Diagrams** — Embed the 4 mermaid diagrams from Step 2",
|
|
1037
1084
|
"3. **## Acceptance Criteria** — Add a table row for each component: | # | Component | Criterion | Verify In | with specific, testable acceptance criteria",
|
|
1038
|
-
"5. **## Components** —
|
|
1039
|
-
"6. **Domain Events** — Table: Event, Description, Trigger, Payload, Published By",
|
|
1085
|
+
"5. **## Components** — depth-aware: for tactical modules expand aggregates with " + lang + " stubs, methods, exploration invariants, VO justifications and a domain/-owned repository interface; for service/adapter modules keep only what the Modeling Depth Note permits (no invented aggregates/VOs). Always keep `status:` and `depends:` markers",
|
|
1086
|
+
"6. **Domain Events** (tactical modules only) — Table: Event, Description, Trigger, Payload, Published By; include ADR-008 triage flags (domain expert cares / crosses boundary / audit trail)",
|
|
1040
1087
|
"7. **API Endpoints** — Table: Method, Path, Handler, Input, Output, Auth",
|
|
1041
1088
|
"8. **Ubiquitous Language** — Terms for this context from `.pi/domain/ubiquitous-language.md`",
|
|
1042
1089
|
"9. **Dependencies** — Depends On + Used By",
|
|
@@ -493,7 +493,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
493
493
|
})),
|
|
494
494
|
];
|
|
495
495
|
|
|
496
|
-
const picked = await ctx.ui.custom
|
|
496
|
+
const picked = await (ctx.ui as any).custom(
|
|
497
497
|
(tui, theme, _kb, done) => {
|
|
498
498
|
const container = new Container();
|
|
499
499
|
container.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
|
|
@@ -546,7 +546,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
546
546
|
}
|
|
547
547
|
|
|
548
548
|
const md = `\`\`\`diff\n${t.diff.trimEnd() || "(no diff)"}\n\`\`\``;
|
|
549
|
-
await ctx.ui.custom
|
|
549
|
+
await (ctx.ui as any).custom(
|
|
550
550
|
(tui, theme, _kb, done) => {
|
|
551
551
|
const container = new Container();
|
|
552
552
|
container.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
|
|
@@ -285,9 +285,9 @@ class GoalManager {
|
|
|
285
285
|
|
|
286
286
|
clear(): void {
|
|
287
287
|
if (!this.state) return;
|
|
288
|
-
this.
|
|
289
|
-
saveGoalState(this.cwd, this.
|
|
290
|
-
this.
|
|
288
|
+
this._state.status = "cleared";
|
|
289
|
+
saveGoalState(this.cwd, this._state);
|
|
290
|
+
this._state = null;
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
addSubgoal(textIn: string): string {
|
|
@@ -52,7 +52,7 @@ type ExtensionContext = {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
type ExtensionAPI = {
|
|
55
|
-
on(event: string, handler: (event: unknown, ctx: ExtensionContext) =>
|
|
55
|
+
on(event: string, handler: (event: unknown, ctx: ExtensionContext) => unknown | Promise<unknown>): void;
|
|
56
56
|
registerTool(options: {
|
|
57
57
|
name: string;
|
|
58
58
|
label: string;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Ambient declarations for the pi runtime SDK. The host provides these at
|
|
2
|
+
// runtime with real types; not vendored here. Auto-generated names from
|
|
3
|
+
// actual imports across the extension sources so tsc type-checks cleanly.
|
|
4
|
+
|
|
5
|
+
declare module "@mariozechner/pi-coding-agent" {
|
|
6
|
+
export const DynamicBorder: any;
|
|
7
|
+
export type DynamicBorder = any;
|
|
8
|
+
export const ExtensionAPI: any;
|
|
9
|
+
export type ExtensionAPI = any;
|
|
10
|
+
export const ExtensionCommandContext: any;
|
|
11
|
+
export type ExtensionCommandContext = any;
|
|
12
|
+
export const ExtensionContext: any;
|
|
13
|
+
export type ExtensionContext = any;
|
|
14
|
+
export const createFindTool: any;
|
|
15
|
+
export type createFindTool = any;
|
|
16
|
+
export const createGrepTool: any;
|
|
17
|
+
export type createGrepTool = any;
|
|
18
|
+
export const createLsTool: any;
|
|
19
|
+
export type createLsTool = any;
|
|
20
|
+
export const createReadTool: any;
|
|
21
|
+
export type createReadTool = any;
|
|
22
|
+
export const getMarkdownTheme: any;
|
|
23
|
+
export type getMarkdownTheme = any;
|
|
24
|
+
export const isEditToolResult: any;
|
|
25
|
+
export type isEditToolResult = any;
|
|
26
|
+
export const isToolCallEventType: any;
|
|
27
|
+
export type isToolCallEventType = any;
|
|
28
|
+
export const isWriteToolResult: any;
|
|
29
|
+
export type isWriteToolResult = any;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare module "@mariozechner/pi-tui" {
|
|
33
|
+
export const Container: any;
|
|
34
|
+
export type Container = any;
|
|
35
|
+
export const Editor: any;
|
|
36
|
+
export type Editor = any;
|
|
37
|
+
export const EditorTheme: any;
|
|
38
|
+
export type EditorTheme = any;
|
|
39
|
+
export const Key: any;
|
|
40
|
+
export type Key = any;
|
|
41
|
+
export const Markdown: any;
|
|
42
|
+
export type Markdown = any;
|
|
43
|
+
export const SelectItem: any;
|
|
44
|
+
export type SelectItem = any;
|
|
45
|
+
export const SelectList: any;
|
|
46
|
+
export type SelectList = any;
|
|
47
|
+
export const Text: any;
|
|
48
|
+
export type Text = any;
|
|
49
|
+
export const matchesKey: any;
|
|
50
|
+
export type matchesKey = any;
|
|
51
|
+
export const truncateToWidth: any;
|
|
52
|
+
export type truncateToWidth = any;
|
|
53
|
+
export const wrapTextWithAnsi: any;
|
|
54
|
+
export type wrapTextWithAnsi = any;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
declare module "@earendil-works/pi-coding-agent" {
|
|
58
|
+
export const ExecResult: any;
|
|
59
|
+
export type ExecResult = any;
|
|
60
|
+
export const ExtensionAPI: any;
|
|
61
|
+
export type ExtensionAPI = any;
|
|
62
|
+
export const ExtensionContext: any;
|
|
63
|
+
export type ExtensionContext = any;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
declare module "typebox" {
|
|
67
|
+
export const Type: any;
|
|
68
|
+
export type Type = any;
|
|
69
|
+
export const Static: any;
|
|
70
|
+
export type Static = any;
|
|
71
|
+
export const TObject: any;
|
|
72
|
+
export type TObject = any;
|
|
73
|
+
export const TSchema: any;
|
|
74
|
+
export type TSchema = any;
|
|
75
|
+
export const TProperties: any;
|
|
76
|
+
export type TProperties = any;
|
|
77
|
+
export const TString: any;
|
|
78
|
+
export type TString = any;
|
|
79
|
+
export const TArray: any;
|
|
80
|
+
export type TArray = any;
|
|
81
|
+
export const TOptional: any;
|
|
82
|
+
export type TOptional = any;
|
|
83
|
+
export const TBoolean: any;
|
|
84
|
+
export type TBoolean = any;
|
|
85
|
+
export const TNumber: any;
|
|
86
|
+
export type TNumber = any;
|
|
87
|
+
export const TUnion: any;
|
|
88
|
+
export type TUnion = any;
|
|
89
|
+
export const TLiteral: any;
|
|
90
|
+
export type TLiteral = any;
|
|
91
|
+
export const TRecord: any;
|
|
92
|
+
export type TRecord = any;
|
|
93
|
+
export const TAny: any;
|
|
94
|
+
export type TAny = any;
|
|
95
|
+
}
|
|
96
|
+
declare module "@sinclair/typebox" { export const Type: any; export type Static = any; }
|
|
@@ -256,7 +256,7 @@ class PipelineManager {
|
|
|
256
256
|
* Reload pipeline state from disk, discarding any cached in-memory state.
|
|
257
257
|
* Use this when another extension (e.g., architect) may have written state directly.
|
|
258
258
|
*/
|
|
259
|
-
|
|
259
|
+
reloadFromDisk(): void {
|
|
260
260
|
this.state = loadPipelineState(this.cwd);
|
|
261
261
|
}
|
|
262
262
|
|