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.
Files changed (71) hide show
  1. package/README.md +11 -2
  2. package/dist/cli.js +232 -16
  3. package/dist/exports.js +232 -16
  4. package/package.json +1 -1
  5. package/templates/languages/rust-patterns.md +5 -2
  6. package/templates/pi/context/checklists.md +15 -1
  7. package/templates/pi/domain/exploration.md +9 -0
  8. package/templates/pi/domain/ubiquitous-language.md +12 -0
  9. package/templates/pi/extensions/architect-lib/generators.ts +163 -12
  10. package/templates/pi/extensions/architect-lib/helpers.ts +49 -0
  11. package/templates/pi/extensions/architect-lib/tdd-generator.ts +1 -1
  12. package/templates/pi/extensions/architect-lib/types.ts +44 -2
  13. package/templates/pi/extensions/architect.ts +105 -41
  14. package/templates/pi/extensions/ask-user-question.ts +2 -2
  15. package/templates/pi/extensions/bash-guard.ts +2 -2
  16. package/templates/pi/extensions/config-reload.ts +12 -5
  17. package/templates/pi/extensions/coordinator.ts +2 -2
  18. package/templates/pi/extensions/curator.ts +1 -1
  19. package/templates/pi/extensions/domain-explorer.ts +50 -3
  20. package/templates/pi/extensions/filechanges.ts +2 -2
  21. package/templates/pi/extensions/goal-loop.ts +3 -3
  22. package/templates/pi/extensions/hooks.ts +1 -1
  23. package/templates/pi/extensions/pi-env.d.ts +96 -0
  24. package/templates/pi/extensions/pipeline.ts +1 -1
  25. package/templates/pi/extensions/project-scaffolder.ts +1 -1
  26. package/templates/pi/extensions/read-only-mode.ts +5 -1
  27. package/templates/pi/extensions/tsconfig.json +9 -2
  28. package/templates/pi/preflight_report.json +1 -1
  29. package/templates/pi/prompts/ci-blueprint.md +2 -2
  30. package/templates/pi/scripts/check_model_judgment.sh +28 -0
  31. package/templates/pi/scripts/ci/check_judgment_contracts.sh +93 -0
  32. package/templates/pi/scripts/ci/run_preflight.sh +3 -3
  33. package/templates/pi/scripts/ci/run_stage.sh +1 -1
  34. package/templates/pi/scripts/ci/stage_lint.sh +1 -1
  35. package/templates/pi/scripts/coverage.sh +58 -0
  36. package/templates/pi/scripts/install_coverage_tools.sh +43 -0
  37. package/templates/pi/scripts/languages/go/check_model_judgment.sh +70 -0
  38. package/templates/pi/scripts/languages/go/check_runtime_reachability.sh +34 -0
  39. package/templates/pi/scripts/languages/go/coverage.sh +78 -0
  40. package/templates/pi/scripts/languages/go/install_coverage_tools.sh +17 -0
  41. package/templates/pi/scripts/languages/java/check_model_judgment.sh +142 -0
  42. package/templates/pi/scripts/languages/java/check_runtime_reachability.sh +31 -0
  43. package/templates/pi/scripts/languages/java/coverage.sh +103 -0
  44. package/templates/pi/scripts/languages/java/install_coverage_tools.sh +40 -0
  45. package/templates/pi/scripts/languages/java/validate-architecture.sh +20 -0
  46. package/templates/pi/scripts/languages/python/check_model_judgment.sh +67 -0
  47. package/templates/pi/scripts/languages/python/check_runtime_reachability.sh +34 -0
  48. package/templates/pi/scripts/languages/python/coverage.sh +88 -0
  49. package/templates/pi/scripts/languages/python/install_coverage_tools.sh +22 -0
  50. package/templates/pi/scripts/languages/rust/check_model_judgment.sh +61 -0
  51. package/templates/pi/scripts/languages/rust/check_runtime_reachability.sh +40 -0
  52. package/templates/pi/scripts/languages/rust/coverage.sh +79 -0
  53. package/templates/pi/scripts/languages/rust/install_coverage_tools.sh +20 -0
  54. package/templates/pi/scripts/languages/rust/validate-tests.sh +9 -6
  55. package/templates/pi/scripts/languages/typescript/check_model_judgment.sh +81 -0
  56. package/templates/pi/scripts/languages/typescript/check_runtime_reachability.sh +41 -0
  57. package/templates/pi/scripts/languages/typescript/coverage.sh +154 -0
  58. package/templates/pi/scripts/languages/typescript/install_coverage_tools.sh +41 -0
  59. package/templates/pi/scripts/languages/typescript/validate-canonical.sh +6 -2
  60. package/templates/pi/scripts/languages/typescript/validate-ci.sh +2 -2
  61. package/templates/pi/scripts/validate-architecture-readiness.sh +29 -0
  62. package/templates/pi/scripts/validate-exploration-depth.sh +112 -0
  63. package/templates/pi/scripts/validate-runtime-wiring.sh +35 -0
  64. package/templates/pi/skills/agents/architecture-validator.md +28 -0
  65. package/templates/pi/skills/agents/test-validator.md +1 -1
  66. package/templates/pi/skills/go-enterprise-codegen.md +46 -0
  67. package/templates/pi/skills/java-spring-enterprise-codegen.md +50 -0
  68. package/templates/pi/skills/python-enterprise-codegen.md +46 -0
  69. package/templates/pi/skills/rust-enterprise-codegen.md +46 -0
  70. package/templates/pi/skills/tdd-practice.md +17 -9
  71. 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
- async startEpic(
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: first module with planned components
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
- slice = findNextLogicalSlice(this.cwd, moduleFiles);
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) => `- ${i.title}${i.remoteIssueId ? ` (#${i.remoteIssueId})` : ""}`),
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
- function findFlag(tokens: string[], prefix: string): string | undefined {
389
- const eqMatch = tokens.find((a) => a.startsWith(`${prefix}=`));
390
- if (eqMatch) return eqMatch.split("=").slice(1).join("=");
391
- const idx = tokens.indexOf(prefix);
392
- if (idx >= 0 && idx + 1 < tokens.length && !tokens[idx + 1].startsWith("--")) return tokens[idx + 1];
393
- return undefined;
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
- handler: async (args, ctx) => {
399
- if (!manager) manager = new EpicManager(ctx.cwd);
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 = phaseName.match(/Phase\s+(\d+)/i);
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 = `PL-${String(Math.floor(Math.random() * 10000)).padStart(4, "0")}`;
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 comp = new Set((rs.phases.find((p) => p.index === targetPhase.index)?.completedModules || []));
583
- comp.add(mod.name);
584
- if (rs.phases.find((p) => p.index === targetPhase.index)) {
585
- rs.phases.find((p) => p.index === targetPhase.index)!.completedModules = Array.from(comp);
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 || !state.slices || state.slices.length === 0) {
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 = `PL-${String(Math.floor(Math.random() * 10000)).padStart(4, "0")}`;
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 || !state.slices || state.slices.length === 0) {
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 = `PL-${String(Math.floor(Math.random() * 10000)).padStart(4, "0")}`;
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`.replace(/\//g, "-");
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 `tests/unit/` directory.",
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
- if (!manager) manager = new EpicManager(ctx.cwd);
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<AskAnswer | null>(
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<AskAnswer[] | null>(
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<"run" | "abort">(
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: unknown) => void | Promise<void>): void;
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: unknown): unknown | Promise<unknown>;
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: unknown): void {
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: unknown) => {
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: unknown) => {
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) => void | Promise<void>): void;
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: string; message: string }) => void,
37
+ onUpdate: (update: { type?: string; message?: string; content?: unknown }) => void,
38
38
  ctx: ExtensionContext,
39
39
  ): unknown | Promise<unknown>;
40
40
  }): void;
@@ -252,7 +252,7 @@ class Curator {
252
252
  : new Date(skill.createdAt).getTime();
253
253
  const age = now - lastActivity;
254
254
 
255
- if (age > archiveThreshold && skill.state !== "archived") {
255
+ if (age > archiveThreshold) {
256
256
  if (!dryRun) {
257
257
  this.archiveSkill(name);
258
258
  }
@@ -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 1: Generate All ADRs",
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** — Expand each aggregate with " + lang + " stubs, methods, invariants keep `status:` and `depends:` markers",
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<string | null>(
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<void>(
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.state.status = "cleared";
289
- saveGoalState(this.cwd, this.state);
290
- this.state = null;
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) => void | Promise<void>): void;
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
- private reloadFromDisk(): void {
259
+ reloadFromDisk(): void {
260
260
  this.state = loadPipelineState(this.cwd);
261
261
  }
262
262
 
@@ -227,7 +227,7 @@ export default function (pi: ExtensionAPI) {
227
227
  dryRun ? "Mode: **Dry Run** (no files written)" : "Mode: **Live**",
228
228
  "",
229
229
  "```",
230
- result.trim(),
230
+ String(result).trim(),
231
231
  "```",
232
232
  "",
233
233
  dryRun