guardian-framework 0.1.6 → 0.1.8

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 CHANGED
@@ -1335,7 +1335,7 @@ __export(exports_package, {
1335
1335
  bin: () => bin,
1336
1336
  author: () => author
1337
1337
  });
1338
- var name = "guardian-framework", version = "0.1.6", 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;
1338
+ var name = "guardian-framework", version = "0.1.8", 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;
1339
1339
  var init_package = __esm(() => {
1340
1340
  exports = {
1341
1341
  ".": {
package/dist/exports.js CHANGED
@@ -995,7 +995,7 @@ __export(exports_package, {
995
995
  bin: () => bin,
996
996
  author: () => author
997
997
  });
998
- var name = "guardian-framework", version = "0.1.6", 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;
998
+ var name = "guardian-framework", version = "0.1.8", 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;
999
999
  var init_package = __esm(() => {
1000
1000
  exports = {
1001
1001
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardian-framework",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Token-optimized agentic framework scaffolder with pi-first architecture",
5
5
  "type": "module",
6
6
  "main": "dist/exports.js",
@@ -0,0 +1,99 @@
1
+ # Implementation Roadmap
2
+
3
+ <!--
4
+ @canonical .pi/architecture/implementation-roadmap.md
5
+ Status: Proposed
6
+ Date: [Date]
7
+ -->
8
+
9
+ ## Overview
10
+
11
+ This roadmap defines the phased implementation of the project, organizing bounded
12
+ contexts into delivery phases with explicit dependencies, database migrations, and
13
+ acceptance criteria.
14
+
15
+ **Format conventions:**
16
+ - Each `## Phase N` section defines one delivery phase
17
+ - `### Modules` table lists the bounded contexts implemented in this phase
18
+ - `### Dependencies` lists phase prerequisites
19
+ - `### Database Migrations` lists SQL migration files
20
+ - `### Acceptance Criteria` lists verifiable checkboxes
21
+
22
+ ---
23
+
24
+ ## Phase 0: [Phase Name] (Days [Start]–[End])
25
+
26
+ **Goal:** [One-sentence goal for this phase]
27
+
28
+ ### Modules
29
+
30
+ | Module | Deliverables | Architecture Doc |
31
+ |--------|-------------|------------------|
32
+ | [Module Name] | [Key deliverables] | `.pi/architecture/modules/[module].md` |
33
+ | [Module Name] | [Key deliverables] | `.pi/architecture/modules/[module].md` |
34
+
35
+ ### Dependencies
36
+
37
+ - [Phase name or "None"]
38
+
39
+ ### Database Migrations
40
+
41
+ - `NNN_[migration_name]`: [Description of what it creates]
42
+
43
+ ### Acceptance Criteria
44
+
45
+ - [ ] [Verifiable criterion]
46
+ - [ ] [Verifiable criterion]
47
+
48
+ ---
49
+
50
+ ## Phase 1: [Phase Name] (Days [Start]–[End])
51
+
52
+ **Goal:** [One-sentence goal for this phase]
53
+
54
+ ### Modules
55
+
56
+ | Module | Deliverables | Architecture Doc |
57
+ |--------|-------------|------------------|
58
+ | [Module Name] | [Key deliverables] | `.pi/architecture/modules/[module].md` |
59
+
60
+ ### Dependencies
61
+
62
+ - Phase 0
63
+
64
+ ### Database Migrations
65
+
66
+ - `NNN_[migration_name]`: [Description]
67
+
68
+ ### Acceptance Criteria
69
+
70
+ - [ ] [Verifiable criterion]
71
+
72
+ ---
73
+
74
+ ## Dependency Graph
75
+
76
+ ```
77
+ Phase 0 ([Name])
78
+
79
+
80
+ Phase 1 ([Name])
81
+
82
+
83
+ Phase 2 ([Name])
84
+ ```
85
+
86
+ ## Effort Estimates
87
+
88
+ | Phase | Modules | Estimated Days | Risk | Dependencies |
89
+ |-------|---------|---------------|------|-------------|
90
+ | P0: [Name] | [N] | [N] | [Low/Medium/High] | None |
91
+ | P1: [Name] | [N] | [N] | [Low/Medium/High] | P0 |
92
+ | P2: [Name] | [N] | [N] | [Low/Medium/High] | P1 |
93
+
94
+ ## Key Milestones
95
+
96
+ | Milestone | Target | What's True |
97
+ |-----------|--------|-------------|
98
+ | **M0: [Name]** | Day [N] | [What's working] |
99
+ | **M1: [Name]** | Day [N] | [What's working] |
@@ -6,12 +6,17 @@
6
6
  *
7
7
  * COMMANDS
8
8
  * /architect --epic "Name" [--tracking-issue N] Start new epic
9
+ * /architect --roadmap Show roadmap status
10
+ * /architect --phase "Phase 1" Start a roadmap phase
11
+ * /architect --phase-status Show current phase
12
+ * /architect --phase-done <N> Mark phase complete
9
13
  * /architect status Show current state
10
14
  * /architect next-epic Show next planned slice
11
15
  * /architect abort Cancel current epic
12
16
  *
13
17
  * TOOLS
14
18
  * architect_status - Current epic state and progress
19
+ * architect_roadmap - Show roadmap phases and status
15
20
  * architect_discover - Discover modules and find next logical slice
16
21
  */
17
22
 
@@ -88,10 +93,39 @@ type EpicState = {
88
93
  createdAt: string;
89
94
  };
90
95
 
96
+ // ── Roadmap Types ──
97
+
98
+ type RoadmapPhaseModule = {
99
+ name: string;
100
+ deliverables: string;
101
+ doc: string;
102
+ };
103
+
104
+ type RoadmapPhase = {
105
+ index: number;
106
+ title: string;
107
+ goal: string;
108
+ days: string;
109
+ modules: RoadmapPhaseModule[];
110
+ dependencies: string[];
111
+ migrations: string[];
112
+ criteria: string[];
113
+ status: "pending" | "in_progress" | "done";
114
+ };
115
+
116
+ type RoadmapState = {
117
+ phases: RoadmapPhase[];
118
+ currentPhaseIndex: number;
119
+ startedAt: string;
120
+ updatedAt: string;
121
+ };
122
+
91
123
  // ── Constants ──
92
124
 
93
125
  const ARCH_MODULES_DIR = ".pi/architecture/modules";
94
126
  const ISSUES_DIR = ".pi/issues";
127
+ const ROADMAP_FILE = ".pi/architecture/implementation-roadmap.md";
128
+ const ROADMAP_STATE_FILE = ".pi/.guardian-roadmap-state.json";
95
129
 
96
130
  // ── Helpers ──
97
131
 
@@ -442,6 +476,204 @@ function findNextLogicalSlice(cwd: string, moduleFiles: string[]): ArchitectureS
442
476
  return null;
443
477
  }
444
478
 
479
+ // ── Roadmap Parsing ──
480
+
481
+ function parseRoadmap(cwd: string): RoadmapPhase[] {
482
+ const path = join(cwd, ROADMAP_FILE);
483
+ if (!existsSync(path)) return [];
484
+ const content = readFileSync(path, "utf-8");
485
+ const lines = content.split("\n");
486
+ const phases: RoadmapPhase[] = [];
487
+ let currentPhase: Partial<RoadmapPhase> | null = null;
488
+ let inModules = false;
489
+ let inDeps = false;
490
+ let inMigrations = false;
491
+ let inCriteria = false;
492
+
493
+ for (let i = 0; i < lines.length; i++) {
494
+ const line = lines[i];
495
+ const trimmed = line.trim();
496
+
497
+ // Detect phase heading: ## Phase N: Name (Days X-Y)
498
+ const phaseMatch = trimmed.match(/^## Phase (\d+):\s*(.+?)\s*\((Days\s*[^)]+)\)?$/i);
499
+ if (phaseMatch) {
500
+ if (currentPhase && currentPhase.title) {
501
+ phases.push({
502
+ index: currentPhase.index!,
503
+ title: currentPhase.title!,
504
+ goal: currentPhase.goal || "",
505
+ days: currentPhase.days || "",
506
+ modules: currentPhase.modules || [],
507
+ dependencies: currentPhase.dependencies || [],
508
+ migrations: currentPhase.migrations || [],
509
+ criteria: currentPhase.criteria || [],
510
+ status: "pending",
511
+ });
512
+ }
513
+ currentPhase = {
514
+ index: parseInt(phaseMatch[1], 10),
515
+ title: phaseMatch[2].trim(),
516
+ days: phaseMatch[3].trim(),
517
+ modules: [],
518
+ dependencies: [],
519
+ migrations: [],
520
+ criteria: [],
521
+ };
522
+ inModules = false; inDeps = false; inMigrations = false; inCriteria = false;
523
+ continue;
524
+ }
525
+
526
+ if (!currentPhase) continue;
527
+
528
+ // Goal line
529
+ const goalMatch = trimmed.match(/^\*\*Goal:\*\*\s*(.+)/i);
530
+ if (goalMatch) {
531
+ currentPhase.goal = goalMatch[1].trim();
532
+ continue;
533
+ }
534
+
535
+ // Section headers
536
+ if (trimmed.match(/^###\s+Modules?/i)) {
537
+ inModules = true; inDeps = false; inMigrations = false; inCriteria = false; continue;
538
+ }
539
+ if (trimmed.match(/^###\s+Dependencies?/i)) {
540
+ inModules = false; inDeps = true; inMigrations = false; inCriteria = false; continue;
541
+ }
542
+ if (trimmed.match(/^###\s+Database\s+Migrations?/i)) {
543
+ inModules = false; inDeps = false; inMigrations = true; inCriteria = false; continue;
544
+ }
545
+ if (trimmed.match(/^###\s+Acceptance\s+Criteria/i)) {
546
+ inModules = false; inDeps = false; inMigrations = false; inCriteria = true; continue;
547
+ }
548
+
549
+ // Exit section on next ### heading
550
+ if (trimmed.startsWith("###") && !trimmed.match(/^###\s+(Modules?|Dependencies?|Database\s+Migrations?|Acceptance\s+Criteria)/i)) {
551
+ inModules = false; inDeps = false; inMigrations = false; inCriteria = false;
552
+ }
553
+
554
+ // Parse module table rows: | Module | Deliverables | Doc |
555
+ if (inModules && trimmed.startsWith("|") && !trimmed.startsWith("|---") && !trimmed.startsWith("| Module")) {
556
+ const parts = trimmed.split("|").map((p: string) => p.trim()).filter(Boolean);
557
+ if (parts.length >= 2) {
558
+ currentPhase.modules!.push({
559
+ name: parts[0],
560
+ deliverables: parts[1] || "",
561
+ doc: parts[2] || `.pi/architecture/modules/${parts[0].toLowerCase().replace(/[^a-z0-9]+/g, "-")}.md`,
562
+ });
563
+ }
564
+ }
565
+
566
+ // Parse dependencies: list items
567
+ if (inDeps && trimmed.startsWith("-")) {
568
+ currentPhase.dependencies!.push(trimmed.replace(/^[-\s]+/, "").trim());
569
+ }
570
+
571
+ // Parse migrations: - NNN_name: description
572
+ if (inMigrations && trimmed.startsWith("-")) {
573
+ currentPhase.migrations!.push(trimmed.replace(/^[-\s]+/, "").trim());
574
+ }
575
+
576
+ // Parse acceptance criteria: - [ ] item
577
+ if (inCriteria && trimmed.startsWith("- [")) {
578
+ currentPhase.criteria!.push(trimmed.replace(/^\[-\s*\]\s*/, "").trim());
579
+ }
580
+ }
581
+
582
+ // Push last phase
583
+ if (currentPhase && currentPhase.title) {
584
+ phases.push({
585
+ index: currentPhase.index!,
586
+ title: currentPhase.title!,
587
+ goal: currentPhase.goal || "",
588
+ days: currentPhase.days || "",
589
+ modules: currentPhase.modules || [],
590
+ dependencies: currentPhase.dependencies || [],
591
+ migrations: currentPhase.migrations || [],
592
+ criteria: currentPhase.criteria || [],
593
+ status: "pending",
594
+ });
595
+ }
596
+
597
+ // Restore status from saved state
598
+ const saved = loadRoadmapState(cwd);
599
+ if (saved) {
600
+ for (const phase of phases) {
601
+ const savedPhase = saved.phases.find((p: RoadmapPhase) => p.index === phase.index);
602
+ if (savedPhase) phase.status = savedPhase.status;
603
+ }
604
+ }
605
+
606
+ return phases;
607
+ }
608
+
609
+ function saveRoadmapState(cwd: string, state: RoadmapState): void {
610
+ const dir = dirname(join(cwd, ROADMAP_STATE_FILE));
611
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
612
+ writeFileSync(join(cwd, ROADMAP_STATE_FILE), JSON.stringify(state, null, 2));
613
+ }
614
+
615
+ function loadRoadmapState(cwd: string): RoadmapState | null {
616
+ const p = join(cwd, ROADMAP_STATE_FILE);
617
+ if (!existsSync(p)) return null;
618
+ try {
619
+ return JSON.parse(readFileSync(p, "utf-8"));
620
+ } catch {
621
+ return null;
622
+ }
623
+ }
624
+
625
+ function formatRoadmapStatus(phases: RoadmapPhase[]): string {
626
+ if (phases.length === 0) {
627
+ return "No implementation-roadmap.md found in .pi/architecture/.";
628
+ }
629
+ const lines = ["## Implementation Roadmap", ""];
630
+ for (const phase of phases) {
631
+ const icon = phase.status === "done" ? "✅" : phase.status === "in_progress" ? "🔄" : "⏳";
632
+ lines.push(`### Phase ${phase.index}: ${phase.title} ${icon}`);
633
+ lines.push(`**Goal:** ${phase.goal}`);
634
+ lines.push(`**Days:** ${phase.days}`);
635
+ lines.push(`**Status:** ${phase.status}`);
636
+ lines.push(`**Modules (${phase.modules.length}):** ${phase.modules.map((m: RoadmapPhaseModule) => m.name).join(", ")}`);
637
+ if (phase.migrations.length > 0) {
638
+ lines.push(`**Migrations:** ${phase.migrations.length}`);
639
+ }
640
+ if (phase.criteria.length > 0) {
641
+ const done = phase.criteria.filter(() => phase.status === "done").length;
642
+ lines.push(`**Criteria:** ${done}/${phase.criteria.length}`);
643
+ }
644
+ if (phase.dependencies.length > 0) {
645
+ lines.push(`**Depends on:** ${phase.dependencies.join(", ")}`);
646
+ }
647
+ lines.push("");
648
+ }
649
+
650
+ const done = phases.filter((p) => p.status === "done").length;
651
+ const inProgress = phases.filter((p) => p.status === "in_progress").length;
652
+ lines.push(`**Overall:** ${done}/${phases.length} phases done, ${inProgress} in progress`);
653
+ return lines.join("\n");
654
+ }
655
+
656
+ function getNextPendingPhase(phases: RoadmapPhase[]): RoadmapPhase | null {
657
+ for (const phase of phases) {
658
+ if (phase.status !== "done") {
659
+ // Check dependencies
660
+ const depsMet = phase.dependencies.every((dep: string) => {
661
+ // Dependency format: "Phase N" or "Phase N: Name"
662
+ const depMatch = dep.match(/Phase\s+(\d+)/i);
663
+ if (depMatch) {
664
+ const depIdx = parseInt(depMatch[1], 10);
665
+ const depPhase = phases.find((p: RoadmapPhase) => p.index === depIdx);
666
+ return depPhase && depPhase.status === "done";
667
+ }
668
+ return true; // non-phase dependency treated as met
669
+ });
670
+ if (!depsMet) return null; // can't start yet
671
+ return phase;
672
+ }
673
+ }
674
+ return null; // all done
675
+ }
676
+
445
677
  // ── Issue Generation ──
446
678
 
447
679
  function generateIssueMarkdown(
@@ -729,6 +961,8 @@ guardian_issue:
729
961
  - Check test coverage meets thresholds
730
962
  - Integrate proofing scripts into .pi/scripts/ci/
731
963
  - Scripts must be self-contained shell scripts (zero token cost)
964
+ - Scripts auto-register in local-ci.sh via regex (stage_*.sh, check_*.sh)
965
+ - Run bash .pi/scripts/ci/local-ci.sh — must pass before closing this issue
732
966
 
733
967
  out_of_scope:
734
968
  - Implementation changes
@@ -749,6 +983,7 @@ guardian_issue:
749
983
  - "Scripts pass on current implementation"
750
984
  - "Scripts fail if implementation is removed"
751
985
  - "Scripts integrated into CI pipeline (stage in run_hardening_stages.sh)"
986
+ - "bash .pi/scripts/ci/local-ci.sh exits 0 (all scripts pass together)"
752
987
 
753
988
  validators:
754
989
  - ci
@@ -764,7 +999,9 @@ guardian_issue:
764
999
  file_changes:
765
1000
  - "create: .pi/scripts/ci/check_${moduleId}_contracts.sh"
766
1001
  - "create: .pi/scripts/ci/check_${moduleId}_coverage.sh"
1002
+ - "create: .pi/scripts/ci/stage_${moduleId}_proofing.sh"
767
1003
  - "modify: .pi/scripts/ci/run_hardening_stages.sh"
1004
+ - "modify (auto): .pi/scripts/ci/local-ci.sh — scripts register via regex pattern, no manual edit needed"
768
1005
  ---
769
1006
 
770
1007
  # Proofing & CI Enforcement: ${slice.module}
@@ -818,6 +1055,7 @@ run_stage "11" "${moduleId}_proofing" \\
818
1055
  | 2 | Coverage ≥ 80% per module | check_coverage.sh |
819
1056
  | 3 | CI runs checks on every PR | run_hardening_stages.sh |
820
1057
  | 4 | All scripts exit 0 on pass, 1 on fail | self-validating |
1058
+ | 5 | All scripts pass together | bash .pi/scripts/ci/local-ci.sh |
821
1059
 
822
1060
  ## Implementation
823
1061
 
@@ -1242,13 +1480,16 @@ export default function (pi: ExtensionAPI) {
1242
1480
  }
1243
1481
 
1244
1482
  pi.registerCommand("architect", {
1245
- description: "Orchestrate the full architecture-to-implementation process",
1483
+ description: "Orchestrate the full architecture-to-implementation process including roadmap phases",
1246
1484
  handler: async (args, ctx) => {
1247
1485
  if (!manager) manager = new EpicManager(ctx.cwd);
1248
1486
  const raw = typeof args === "string" ? args : "";
1249
1487
  const tokens = raw ? raw.split(/\s+/).filter(Boolean) : [];
1250
1488
  if (tokens.length === 0) {
1251
- ctx.ui.notify("Usage: /architect [--epic Name] [--tracking-issue N] | status | next-epic | abort", "info");
1489
+ ctx.ui.notify(
1490
+ "Usage: /architect [--epic Name] [--tracking-issue N] | --roadmap | --phase \"Phase 1\" | --phase-status | --phase-done <N> | status | next-epic | abort",
1491
+ "info",
1492
+ );
1252
1493
  return;
1253
1494
  }
1254
1495
  const action = tokens[0];
@@ -1279,6 +1520,177 @@ export default function (pi: ExtensionAPI) {
1279
1520
  const epicName = findFlag(tokens, "--epic");
1280
1521
  const trackingIssueId = findFlag(tokens, "--tracking-issue");
1281
1522
 
1523
+ // ── Roadmap commands ──
1524
+ if (tokens[0] === "--roadmap" || action === "roadmap") {
1525
+ const phases = parseRoadmap(ctx.cwd);
1526
+ ctx.ui.notify(formatRoadmapStatus(phases), "info");
1527
+ return;
1528
+ }
1529
+
1530
+ if (tokens[0] === "--phase-status" || action === "phase-status") {
1531
+ const phases = parseRoadmap(ctx.cwd);
1532
+ const next = getNextPendingPhase(phases);
1533
+ if (!next) {
1534
+ const allDone = phases.every((p) => p.status === "done");
1535
+ ctx.ui.notify(allDone ? "All phases complete! 🎉" : "Next phase blocked by dependencies.", "info");
1536
+ return;
1537
+ }
1538
+ ctx.ui.notify(`Next phase: Phase ${next.index}: ${next.title} (${next.modules.length} modules)`, "info");
1539
+ return;
1540
+ }
1541
+
1542
+ if (tokens[0] === "--phase" || action === "phase") {
1543
+ const phaseName = findFlag(tokens, "--phase") || tokens.slice(1).join(" ");
1544
+ if (!phaseName) {
1545
+ ctx.ui.notify('Usage: /architect --phase "Phase 1"', "error");
1546
+ return;
1547
+ }
1548
+
1549
+ const phases = parseRoadmap(ctx.cwd);
1550
+ if (phases.length === 0) {
1551
+ ctx.ui.notify("No implementation-roadmap.md found in .pi/architecture/.", "error");
1552
+ return;
1553
+ }
1554
+
1555
+ // Find phase by name or index
1556
+ let targetPhase: RoadmapPhase | undefined;
1557
+ const idxMatch = phaseName.match(/Phase\s+(\d+)/i);
1558
+ if (idxMatch) {
1559
+ targetPhase = phases.find((p) => p.index === parseInt(idxMatch[1], 10));
1560
+ } else {
1561
+ targetPhase = phases.find((p) => p.title.toLowerCase().includes(phaseName.toLowerCase()));
1562
+ }
1563
+
1564
+ if (!targetPhase) {
1565
+ ctx.ui.notify(`Phase "${phaseName}" not found in roadmap.`, "error");
1566
+ return;
1567
+ }
1568
+
1569
+ // Check dependencies
1570
+ const unmetDeps = targetPhase.dependencies.filter((dep) => {
1571
+ const depMatch = dep.match(/Phase\s+(\d+)/i);
1572
+ if (!depMatch) return false;
1573
+ const depPhase = phases.find((p) => p.index === parseInt(depMatch[1], 10));
1574
+ return depPhase && depPhase.status !== "done";
1575
+ });
1576
+ if (unmetDeps.length > 0) {
1577
+ ctx.ui.notify(
1578
+ `Cannot start Phase ${targetPhase.index}: unmet dependencies (${unmetDeps.join(", ")}).`,
1579
+ "error",
1580
+ );
1581
+ return;
1582
+ }
1583
+
1584
+ if (targetPhase.status === "done") {
1585
+ ctx.ui.notify(`Phase ${targetPhase.index}: ${targetPhase.title} is already complete.`, "info");
1586
+ return;
1587
+ }
1588
+
1589
+ // Mark phase as in_progress
1590
+ const roadmapState = loadRoadmapState(ctx.cwd) || {
1591
+ phases: [],
1592
+ currentPhaseIndex: 0,
1593
+ startedAt: new Date().toISOString(),
1594
+ updatedAt: new Date().toISOString(),
1595
+ };
1596
+ roadmapState.phases = phases.map((p) => ({
1597
+ ...p,
1598
+ status: p.index === targetPhase!.index ? "in_progress" : p.status,
1599
+ }));
1600
+ roadmapState.currentPhaseIndex = targetPhase.index;
1601
+ roadmapState.updatedAt = new Date().toISOString();
1602
+ saveRoadmapState(ctx.cwd, roadmapState);
1603
+
1604
+ // Generate start message with module breakdown
1605
+ const moduleList = targetPhase.modules
1606
+ .map((m) => `- **${m.name}**: ${m.deliverables}`)
1607
+ .join("\n");
1608
+ const migrateList =
1609
+ targetPhase.migrations.length > 0
1610
+ ? "\n\n### Database Migrations\n" +
1611
+ targetPhase.migrations.map((m) => `- \`${m}\``).join("\n")
1612
+ : "";
1613
+
1614
+ const instructions = [
1615
+ `## Phase ${targetPhase.index}: ${targetPhase.title}`,
1616
+ `**Goal:** ${targetPhase.goal}`,
1617
+ `**Days:** ${targetPhase.days}`,
1618
+ "",
1619
+ "### Modules",
1620
+ moduleList,
1621
+ migrateList,
1622
+ "",
1623
+ "### How to proceed",
1624
+ "Create epics for each module above using:",
1625
+ ` \`/architect --epic "<module-name>"\``,
1626
+ "",
1627
+ "Complete all module epics, then mark phase done:",
1628
+ ` \`/architect --phase-done ${targetPhase.index}\``,
1629
+ "",
1630
+ "### Acceptance Criteria",
1631
+ targetPhase.criteria.map((c) => `- [ ] ${c}`).join("\n"),
1632
+ ].join("\n");
1633
+
1634
+ ctx.ui.notify(
1635
+ `Phase ${targetPhase.index}: ${targetPhase.title} started (${targetPhase.modules.length} modules).`,
1636
+ "success",
1637
+ );
1638
+ pi.sendMessage(
1639
+ { content: instructions, display: true },
1640
+ { deliverAs: "followUp", triggerTurn: true },
1641
+ );
1642
+ return;
1643
+ }
1644
+
1645
+ if (tokens[0] === "--phase-done" || action === "phase-done") {
1646
+ const phaseIdx = parseInt(tokens[1] || "", 10);
1647
+ if (isNaN(phaseIdx)) {
1648
+ ctx.ui.notify('Usage: /architect --phase-done <phase-number>', "error");
1649
+ return;
1650
+ }
1651
+ const phases = parseRoadmap(ctx.cwd);
1652
+ const phase = phases.find((p) => p.index === phaseIdx);
1653
+ if (!phase) {
1654
+ ctx.ui.notify(`Phase ${phaseIdx} not found.`, "error");
1655
+ return;
1656
+ }
1657
+ const roadmapState = loadRoadmapState(ctx.cwd) || {
1658
+ phases: [],
1659
+ currentPhaseIndex: 0,
1660
+ startedAt: new Date().toISOString(),
1661
+ updatedAt: new Date().toISOString(),
1662
+ };
1663
+ roadmapState.phases = phases.map((p) => ({
1664
+ ...p,
1665
+ status: p.index === phaseIdx ? "done" : p.status,
1666
+ }));
1667
+ roadmapState.updatedAt = new Date().toISOString();
1668
+ saveRoadmapState(ctx.cwd, roadmapState);
1669
+ ctx.ui.notify(`Phase ${phaseIdx}: ${phase.title} marked complete! ✅`, "success");
1670
+
1671
+ const next = getNextPendingPhase(
1672
+ phases.map((p) => ({ ...p, status: p.index === phaseIdx ? "done" : p.status })),
1673
+ );
1674
+ if (next) {
1675
+ pi.sendMessage(
1676
+ {
1677
+ content: `**Next up:** Phase ${next.index}: ${next.title} — run \`/architect --phase "Phase ${next.index}"\` to start.`,
1678
+ display: true,
1679
+ },
1680
+ { deliverAs: "followUp", triggerTurn: true },
1681
+ );
1682
+ } else {
1683
+ const allDone = phases.every((p) => p.index === phaseIdx || p.status === "done");
1684
+ if (allDone) {
1685
+ pi.sendMessage(
1686
+ { content: "🎉 **All roadmap phases complete!**", display: true },
1687
+ { deliverAs: "followUp", triggerTurn: true },
1688
+ );
1689
+ }
1690
+ }
1691
+ return;
1692
+ }
1693
+
1282
1694
  if (!epicName) {
1283
1695
  ctx.ui.notify('Usage: /architect --epic "Epic Name" [--tracking-issue N]', "error");
1284
1696
  return;
@@ -1449,4 +1861,15 @@ export default function (pi: ExtensionAPI) {
1449
1861
  return { content: [{ type: "text", text: lines.join("\n") }] };
1450
1862
  },
1451
1863
  });
1864
+
1865
+ pi.registerTool({
1866
+ name: "architect_roadmap",
1867
+ label: "Architect Roadmap",
1868
+ description: "Show the implementation roadmap phases and status.",
1869
+ parameters: { type: "object", properties: {} },
1870
+ async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
1871
+ const phases = parseRoadmap(ctx.cwd);
1872
+ return { content: [{ type: "text", text: formatRoadmapStatus(phases) }] };
1873
+ },
1874
+ });
1452
1875
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-07-02T20:16:37Z",
2
+ "timestamp": "2026-07-02T20:32:22Z",
3
3
  "mode": "all",
4
4
  "stages_run": [],
5
5
  "summary": {
@@ -8,7 +8,7 @@
8
8
  "failed": 1,
9
9
  "skipped": 12
10
10
  },
11
- "duration_seconds": 1,
11
+ "duration_seconds": 0,
12
12
  "results": [
13
13
  {
14
14
  "name": "check_mr_traceability.sh",
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env bash
2
+ # Local CI — runs ALL validation and proofing scripts locally
3
+ #
4
+ # Scans .pi/scripts/ci/ for stage_*.sh, check_*.sh, and validate-*.sh
5
+ # scripts and runs every one. This includes all epic proofing scripts.
6
+ # Use before committing to catch regressions across all modules.
7
+ #
8
+ # Usage:
9
+ # bash .pi/scripts/ci/local-ci.sh # Run all
10
+ # bash .pi/scripts/ci/local-ci.sh --list # List matching scripts only
11
+ # bash .pi/scripts/ci/local-ci.sh --verbose # Verbose output
12
+ # bash .pi/scripts/ci/local-ci.sh --json # JSON report
13
+ #
14
+ # Registration: add new scripts by placing them in .pi/scripts/ci/
15
+ # with names matching: stage_*.sh, check_*.sh, or validate-*.sh
16
+
17
+ set -euo pipefail
18
+
19
+ PI_DIR=".pi"
20
+ CI_DIR="${PI_DIR}/scripts/ci"
21
+ START_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
22
+
23
+ # Colors
24
+ RED='\033[0;31m'
25
+ GREEN='\033[0;32m'
26
+ YELLOW='\033[1;33m'
27
+ BLUE='\033[0;34m'
28
+ NC='\033[0m'
29
+
30
+ # Options
31
+ LIST_ONLY=false
32
+ VERBOSE=false
33
+ JSON=false
34
+ TOTAL=0
35
+ PASSED=0
36
+ FAILED=0
37
+ RESULTS=()
38
+ START_TIME=$(date +%s)
39
+
40
+ while [[ $# -gt 0 ]]; do
41
+ case $1 in
42
+ --list) LIST_ONLY=true; shift ;;
43
+ --verbose) VERBOSE=true; shift ;;
44
+ --json) JSON=true; shift ;;
45
+ *) shift ;;
46
+ esac
47
+ done
48
+
49
+ cd "$START_DIR"
50
+
51
+ # Discover all scripts via regex: stage_*.sh, check_*.sh, validate-*.sh
52
+ SCRIPTS=()
53
+ while IFS= read -r -d '' script; do
54
+ SCRIPTS+=("$script")
55
+ done < <(find "$CI_DIR" -maxdepth 1 -type f -name 'stage_*.sh' -print0 2>/dev/null)
56
+ while IFS= read -r -d '' script; do
57
+ SCRIPTS+=("$script")
58
+ done < <(find "$CI_DIR" -maxdepth 1 -type f -name 'check_*.sh' -print0 2>/dev/null)
59
+ while IFS= read -r -d '' script; do
60
+ SCRIPTS+=("$script")
61
+ done < <(find "$CI_DIR" -maxdepth 1 -type f -name 'validate-*.sh' -print0 2>/dev/null)
62
+
63
+ if [[ ${#SCRIPTS[@]} -eq 0 ]]; then
64
+ echo "No matching scripts found in $CI_DIR"
65
+ exit 0
66
+ fi
67
+
68
+ # Sort for deterministic order
69
+ IFS=$'\n' SCRIPTS=($(sort <<<"${SCRIPTS[*]}")); unset IFS
70
+
71
+ if [[ "$LIST_ONLY" == "true" ]]; then
72
+ echo "Scripts in $CI_DIR:"
73
+ for script in "${SCRIPTS[@]}"; do
74
+ echo " $script"
75
+ done
76
+ exit 0
77
+ fi
78
+
79
+ if [[ "$JSON" == "false" ]]; then
80
+ echo -e "${BLUE}══════════════════════════════════════════════${NC}"
81
+ echo -e "${BLUE} Local CI — Running ${#SCRIPTS[@]} scripts${NC}"
82
+ echo -e "${BLUE}══════════════════════════════════════════════${NC}"
83
+ echo ""
84
+ fi
85
+
86
+ for script in "${SCRIPTS[@]}"; do
87
+ name=$(basename "$script")
88
+ TOTAL=$((TOTAL + 1))
89
+
90
+ if [[ "$JSON" == "false" ]]; then
91
+ echo -e "${YELLOW}[RUN]${NC} $name"
92
+ fi
93
+
94
+ set +e
95
+ output=$(bash "$script" 2>&1)
96
+ exit_code=$?
97
+ set -e
98
+
99
+ if [[ $exit_code -eq 0 ]]; then
100
+ PASSED=$((PASSED + 1))
101
+ RESULTS+=("{\"script\":\"$name\",\"status\":\"pass\"}")
102
+ if [[ "$JSON" == "false" ]]; then
103
+ echo -e " ${GREEN}✓ PASS${NC}"
104
+ fi
105
+ else
106
+ FAILED=$((FAILED + 1))
107
+ RESULTS+=("{\"script\":\"$name\",\"status\":\"fail\",\"exit_code\":$exit_code}")
108
+ if [[ "$JSON" == "false" ]]; then
109
+ echo -e " ${RED}✗ FAIL (exit $exit_code)${NC}"
110
+ if [[ "$VERBOSE" == "true" ]]; then
111
+ echo "$output" | sed 's/^/ /'
112
+ fi
113
+ fi
114
+ fi
115
+ done
116
+
117
+ END_TIME=$(date +%s)
118
+ DURATION=$((END_TIME - START_TIME))
119
+
120
+ if [[ "$JSON" == "true" ]]; then
121
+ echo "{"
122
+ echo " \"total\": $TOTAL,"
123
+ echo " \"passed\": $PASSED,"
124
+ echo " \"failed\": $FAILED,"
125
+ echo " \"duration_seconds\": $DURATION,"
126
+ echo " \"results\": ["
127
+ for i in "${!RESULTS[@]}"; do
128
+ sep=","
129
+ if [[ $i -eq $((${#RESULTS[@]} - 1)) ]]; then sep=""; fi
130
+ echo " ${RESULTS[$i]}$sep"
131
+ done
132
+ echo " ]"
133
+ echo "}"
134
+ else
135
+ echo ""
136
+ echo -e "${BLUE}══════════════════════════════════════════════${NC}"
137
+ echo -e " Total: $TOTAL | ${GREEN}Passed: $PASSED${NC} | ${RED}Failed: $FAILED${NC} | ${DURATION}s"
138
+ echo -e "${BLUE}══════════════════════════════════════════════${NC}"
139
+ fi
140
+
141
+ if [[ $FAILED -gt 0 ]]; then
142
+ exit 1
143
+ fi
144
+ exit 0