guardian-framework 0.1.0

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 (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +463 -0
  3. package/dist/cli.js +12958 -0
  4. package/dist/exports.js +12765 -0
  5. package/package.json +61 -0
  6. package/templates/languages/go-patterns.md +411 -0
  7. package/templates/languages/java-patterns.md +545 -0
  8. package/templates/languages/python-patterns.md +312 -0
  9. package/templates/languages/rust-patterns.md +232 -0
  10. package/templates/languages/typescript-patterns.md +313 -0
  11. package/templates/pi/INDEX.md +437 -0
  12. package/templates/pi/agent/AGENTS.md +166 -0
  13. package/templates/pi/agents/architecture-coordinator.md +40 -0
  14. package/templates/pi/agents/architecture-validator.md +36 -0
  15. package/templates/pi/agents/bootstrap-implementer.md +37 -0
  16. package/templates/pi/agents/issue-factory.md +31 -0
  17. package/templates/pi/agents/operations-validator.md +32 -0
  18. package/templates/pi/agents/security-validator.md +33 -0
  19. package/templates/pi/architecture/CHANGELOG.md +78 -0
  20. package/templates/pi/architecture/decisions/ADR-template.md +76 -0
  21. package/templates/pi/architecture/diagrams/system-overview.md +159 -0
  22. package/templates/pi/architecture/modules/module-template.md +152 -0
  23. package/templates/pi/context/checklists.md +91 -0
  24. package/templates/pi/context/domain-workflow.md +180 -0
  25. package/templates/pi/context/output-formats.md +135 -0
  26. package/templates/pi/context/patterns-base.md +47 -0
  27. package/templates/pi/context/patterns.md +38 -0
  28. package/templates/pi/context/project.md +88 -0
  29. package/templates/pi/domain/exploration.md +93 -0
  30. package/templates/pi/domain/ubiquitous-language.md +24 -0
  31. package/templates/pi/extensions/architect-lib/generators.ts +528 -0
  32. package/templates/pi/extensions/architect-lib/helpers.ts +344 -0
  33. package/templates/pi/extensions/architect-lib/types.ts +117 -0
  34. package/templates/pi/extensions/architect.ts +1425 -0
  35. package/templates/pi/extensions/ask-user-question.ts +579 -0
  36. package/templates/pi/extensions/bash-guard.ts +556 -0
  37. package/templates/pi/extensions/config-reload.ts +115 -0
  38. package/templates/pi/extensions/coordinator.ts +304 -0
  39. package/templates/pi/extensions/curator.ts +543 -0
  40. package/templates/pi/extensions/domain-explorer.ts +990 -0
  41. package/templates/pi/extensions/filechanges.ts +656 -0
  42. package/templates/pi/extensions/goal-loop.ts +717 -0
  43. package/templates/pi/extensions/hooks.ts +452 -0
  44. package/templates/pi/extensions/kanban.ts +547 -0
  45. package/templates/pi/extensions/pipeline.ts +1246 -0
  46. package/templates/pi/extensions/plan-mode.ts +209 -0
  47. package/templates/pi/extensions/project-scaffolder.ts +284 -0
  48. package/templates/pi/extensions/read-only-mode.ts +192 -0
  49. package/templates/pi/extensions/redaction.ts +128 -0
  50. package/templates/pi/extensions/session-persistence.ts +174 -0
  51. package/templates/pi/extensions/slash-commands.ts +229 -0
  52. package/templates/pi/extensions/snippets.ts +201 -0
  53. package/templates/pi/extensions/validation-runner.ts +100 -0
  54. package/templates/pi/github/agents/architecture-coordinator.agent.md +27 -0
  55. package/templates/pi/github/agents/architecture-validator.agent.md +20 -0
  56. package/templates/pi/github/agents/bootstrap-implementer.agent.md +20 -0
  57. package/templates/pi/github/agents/epic-planner.agent.md +70 -0
  58. package/templates/pi/github/agents/issue-factory.agent.md +20 -0
  59. package/templates/pi/github/agents/operations-validator.agent.md +20 -0
  60. package/templates/pi/github/agents/security-validator.agent.md +20 -0
  61. package/templates/pi/github/copilot/settings.json +16 -0
  62. package/templates/pi/github/copilot-instructions.md +155 -0
  63. package/templates/pi/github/instructions/architecture.instructions.md +71 -0
  64. package/templates/pi/github/instructions/validation.instructions.md +69 -0
  65. package/templates/pi/github/workflows/01-planning-workflow.md +75 -0
  66. package/templates/pi/github/workflows/02-issue-generation-workflow.md +69 -0
  67. package/templates/pi/github/workflows/03-implementation-workflow.md +80 -0
  68. package/templates/pi/github/workflows/04-validation-workflow.md +68 -0
  69. package/templates/pi/preflight_report.json +99 -0
  70. package/templates/pi/prompts/blueprint-update.md +262 -0
  71. package/templates/pi/prompts/blueprint-validate.md +218 -0
  72. package/templates/pi/prompts/bug-fix.md +50 -0
  73. package/templates/pi/prompts/ci-blueprint.md +131 -0
  74. package/templates/pi/prompts/context-refresh.md +258 -0
  75. package/templates/pi/prompts/epic-plan.md +356 -0
  76. package/templates/pi/prompts/epic-template.md +186 -0
  77. package/templates/pi/prompts/feature-development.md +131 -0
  78. package/templates/pi/prompts/git-issues.md +243 -0
  79. package/templates/pi/prompts/hotfix.md +71 -0
  80. package/templates/pi/prompts/issue-closeout.md +342 -0
  81. package/templates/pi/prompts/issue-draft.md +218 -0
  82. package/templates/pi/prompts/issue-implementation-series.md +173 -0
  83. package/templates/pi/prompts/issue-merge.md +372 -0
  84. package/templates/pi/prompts/issue-template-set.md +392 -0
  85. package/templates/pi/prompts/issue-template.md +207 -0
  86. package/templates/pi/prompts/pattern-extract.md +269 -0
  87. package/templates/pi/prompts/plan-to-issues.md +430 -0
  88. package/templates/pi/prompts/refactoring.md +82 -0
  89. package/templates/pi/prompts/scope-analyzer.md +254 -0
  90. package/templates/pi/prompts/sync-check.md +230 -0
  91. package/templates/pi/scripts/categorize-issues.sh +171 -0
  92. package/templates/pi/scripts/ci/check_architecture_conformance.sh +748 -0
  93. package/templates/pi/scripts/ci/check_planning_packet.py +228 -0
  94. package/templates/pi/scripts/ci/run_hardening_stages.sh +264 -0
  95. package/templates/pi/scripts/ci/run_preflight.sh +649 -0
  96. package/templates/pi/scripts/ci/run_stage.sh +193 -0
  97. package/templates/pi/scripts/ci/stage_docs_policy.sh +82 -0
  98. package/templates/pi/scripts/ci/stage_integration.sh +3 -0
  99. package/templates/pi/scripts/ci/stage_lint.sh +130 -0
  100. package/templates/pi/scripts/ci/stage_migration_verify.sh +3 -0
  101. package/templates/pi/scripts/ci/stage_package_build.sh +3 -0
  102. package/templates/pi/scripts/ci/stage_release_readiness.sh +3 -0
  103. package/templates/pi/scripts/ci/stage_remaining.sh +242 -0
  104. package/templates/pi/scripts/ci/stage_security.sh +3 -0
  105. package/templates/pi/scripts/ci/stage_static_analysis.sh +114 -0
  106. package/templates/pi/scripts/ci/stage_unit.sh +119 -0
  107. package/templates/pi/scripts/ci/validate_agent_output.py +181 -0
  108. package/templates/pi/scripts/ci/validate_agent_output.sh +243 -0
  109. package/templates/pi/scripts/create-feature-branch.sh +136 -0
  110. package/templates/pi/scripts/create-mr.sh +117 -0
  111. package/templates/pi/scripts/fetch-issues.sh +131 -0
  112. package/templates/pi/scripts/generate-architecture.sh +161 -0
  113. package/templates/pi/scripts/git/close-epic.sh +69 -0
  114. package/templates/pi/scripts/git/close-issue.sh +35 -0
  115. package/templates/pi/scripts/git/create-tracking-issue.sh +174 -0
  116. package/templates/pi/scripts/git/link-issue-to-epic.sh +51 -0
  117. package/templates/pi/scripts/git/update-tracking-issue.sh +66 -0
  118. package/templates/pi/scripts/languages/go/validate-architecture.sh +139 -0
  119. package/templates/pi/scripts/languages/go/validate-canonical.sh +129 -0
  120. package/templates/pi/scripts/languages/go/validate-ci.sh +132 -0
  121. package/templates/pi/scripts/languages/go/validate-integration.sh +156 -0
  122. package/templates/pi/scripts/languages/go/validate-operations.sh +146 -0
  123. package/templates/pi/scripts/languages/go/validate-security.sh +135 -0
  124. package/templates/pi/scripts/languages/go/validate-tests.sh +125 -0
  125. package/templates/pi/scripts/languages/java/validate-annotations.sh +185 -0
  126. package/templates/pi/scripts/languages/java/validate-architecture.sh +159 -0
  127. package/templates/pi/scripts/languages/java/validate-canonical.sh +114 -0
  128. package/templates/pi/scripts/languages/java/validate-ci.sh +269 -0
  129. package/templates/pi/scripts/languages/java/validate-integration.sh +149 -0
  130. package/templates/pi/scripts/languages/java/validate-operations.sh +142 -0
  131. package/templates/pi/scripts/languages/java/validate-security.sh +174 -0
  132. package/templates/pi/scripts/languages/java/validate-spring-architecture.sh +227 -0
  133. package/templates/pi/scripts/languages/java/validate-tests.sh +159 -0
  134. package/templates/pi/scripts/languages/python/validate-architecture.sh +99 -0
  135. package/templates/pi/scripts/languages/python/validate-canonical.sh +124 -0
  136. package/templates/pi/scripts/languages/python/validate-ci.sh +130 -0
  137. package/templates/pi/scripts/languages/python/validate-integration.sh +119 -0
  138. package/templates/pi/scripts/languages/python/validate-operations.sh +107 -0
  139. package/templates/pi/scripts/languages/python/validate-security.sh +68 -0
  140. package/templates/pi/scripts/languages/python/validate-tests.sh +102 -0
  141. package/templates/pi/scripts/languages/rust/validate-architecture.sh +188 -0
  142. package/templates/pi/scripts/languages/rust/validate-canonical.sh +146 -0
  143. package/templates/pi/scripts/languages/rust/validate-ci.sh +147 -0
  144. package/templates/pi/scripts/languages/rust/validate-integration.sh +149 -0
  145. package/templates/pi/scripts/languages/rust/validate-operations.sh +154 -0
  146. package/templates/pi/scripts/languages/rust/validate-security.sh +153 -0
  147. package/templates/pi/scripts/languages/rust/validate-tests.sh +150 -0
  148. package/templates/pi/scripts/languages/typescript/validate-architecture.sh +157 -0
  149. package/templates/pi/scripts/languages/typescript/validate-canonical.sh +135 -0
  150. package/templates/pi/scripts/languages/typescript/validate-ci.sh +244 -0
  151. package/templates/pi/scripts/languages/typescript/validate-integration.sh +166 -0
  152. package/templates/pi/scripts/languages/typescript/validate-operations.sh +162 -0
  153. package/templates/pi/scripts/languages/typescript/validate-security.sh +169 -0
  154. package/templates/pi/scripts/languages/typescript/validate-tests.sh +158 -0
  155. package/templates/pi/scripts/merge-mr.sh +95 -0
  156. package/templates/pi/scripts/mr-validation.sh +228 -0
  157. package/templates/pi/scripts/validate-architecture-readiness.sh +153 -0
  158. package/templates/pi/scripts/validate-architecture.sh +22 -0
  159. package/templates/pi/scripts/validate-canonical.sh +22 -0
  160. package/templates/pi/scripts/validate-ci.sh +37 -0
  161. package/templates/pi/scripts/validate-integration.sh +22 -0
  162. package/templates/pi/scripts/validate-operations.sh +22 -0
  163. package/templates/pi/scripts/validate-security.sh +22 -0
  164. package/templates/pi/scripts/validate-tests.sh +22 -0
  165. package/templates/pi/scripts/validate-ubiquitous-language.sh +291 -0
  166. package/templates/pi/scripts/validation-cache.sh +139 -0
  167. package/templates/pi/skills/agents/architecture-coordinator.md +178 -0
  168. package/templates/pi/skills/agents/architecture-generator.md +79 -0
  169. package/templates/pi/skills/agents/architecture-validator.md +74 -0
  170. package/templates/pi/skills/agents/ci-mr-validator.md +46 -0
  171. package/templates/pi/skills/agents/code-developer.md +76 -0
  172. package/templates/pi/skills/agents/commit.md +46 -0
  173. package/templates/pi/skills/agents/curator.md +71 -0
  174. package/templates/pi/skills/agents/debug.md +59 -0
  175. package/templates/pi/skills/agents/documentation-maintainer.md +36 -0
  176. package/templates/pi/skills/agents/goal-loop.md +125 -0
  177. package/templates/pi/skills/agents/hooks.md +122 -0
  178. package/templates/pi/skills/agents/integration-validator.md +35 -0
  179. package/templates/pi/skills/agents/issue-creator.md +75 -0
  180. package/templates/pi/skills/agents/issue-factory.md +143 -0
  181. package/templates/pi/skills/agents/kanban.md +85 -0
  182. package/templates/pi/skills/agents/land.md +59 -0
  183. package/templates/pi/skills/agents/operations-validator.md +43 -0
  184. package/templates/pi/skills/agents/pipeline.md +113 -0
  185. package/templates/pi/skills/agents/plan-mode.md +77 -0
  186. package/templates/pi/skills/agents/pull.md +40 -0
  187. package/templates/pi/skills/agents/push.md +41 -0
  188. package/templates/pi/skills/agents/security-validator.md +85 -0
  189. package/templates/pi/skills/agents/session-persistence.md +119 -0
  190. package/templates/pi/skills/agents/slash-commands.md +117 -0
  191. package/templates/pi/skills/agents/snippets.md +99 -0
  192. package/templates/pi/skills/agents/subagent-registry.md +78 -0
  193. package/templates/pi/skills/agents/test-validator.md +42 -0
  194. package/templates/pi/skills/validators/architecture-validator.md +43 -0
  195. package/templates/pi/skills/validators/ci-validator.md +27 -0
  196. package/templates/pi/skills/validators/context-compaction.md +73 -0
  197. package/templates/pi/skills/validators/integration-validator.md +38 -0
  198. package/templates/pi/skills/validators/model-registry.md +96 -0
  199. package/templates/pi/skills/validators/operations-validator.md +34 -0
  200. package/templates/pi/skills/validators/security-guards.md +72 -0
  201. package/templates/pi/skills/validators/security-validator.md +41 -0
  202. package/templates/pi/skills/validators/system-prompt-tiers.md +69 -0
  203. package/templates/pi/skills/validators/test-validator.md +33 -0
  204. package/templates/pi/types.ts +62 -0
  205. package/templates/pi/validators/README.md +93 -0
  206. package/templates/pi/validators/default.toml +209 -0
  207. package/templates/pi/validators/spring.toml +142 -0
  208. package/templates/pi/workpad.md +53 -0
  209. package/templates/project/java/Dockerfile +14 -0
  210. package/templates/project/java/README.md +40 -0
  211. package/templates/project/java/pom.xml +132 -0
  212. package/templates/project/typescript/Dockerfile +15 -0
  213. package/templates/project/typescript/README.md +29 -0
  214. package/templates/project/typescript/package.json +23 -0
  215. package/templates/project/typescript/tsconfig.json +21 -0
  216. package/templates/workflow.md +123 -0
@@ -0,0 +1,1425 @@
1
+ /**
2
+ * Architect Extension - Full Architecture-to-Implementation Pipeline
3
+ *
4
+ * Self-contained extension for pi. Reads architecture module docs from
5
+ * .pi/architecture/modules/ and produces epics + issues for implementation.
6
+ *
7
+ * COMMANDS
8
+ * /architect --epic "Name" [--tracking-issue N] Start new epic
9
+ * /architect status Show current state
10
+ * /architect next-epic Show next planned slice
11
+ * /architect abort Cancel current epic
12
+ *
13
+ * TOOLS
14
+ * architect_status - Current epic state and progress
15
+ * architect_discover - Discover modules and find next logical slice
16
+ */
17
+
18
+ import { execFileSync, execSync } from "node:child_process";
19
+ import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
20
+ import { dirname, join } from "node:path";
21
+
22
+
23
+ // ── Types ──
24
+
25
+ type ExtensionContext = {
26
+ cwd: string;
27
+ ui: {
28
+ notify(message: string, level?: string): void;
29
+ setStatus(key: string, message: string | null): void;
30
+ confirm(title: string, message: string): Promise<boolean>;
31
+ };
32
+ tools: { execute(name: string, params: Record<string, unknown>): Promise<unknown> };
33
+ };
34
+
35
+ type ExtensionAPI = {
36
+ on(event: string, handler: (event: unknown, ctx: ExtensionContext) => void | Promise<void>): void;
37
+ registerTool(options: {
38
+ name: string;
39
+ label: string;
40
+ description: string;
41
+ parameters: unknown;
42
+ execute(
43
+ toolCallId: string,
44
+ params: Record<string, unknown>,
45
+ signal: AbortSignal,
46
+ onUpdate: (update: { type: string; message: string }) => void,
47
+ ctx: ExtensionContext,
48
+ ): unknown | Promise<unknown>;
49
+ }): void;
50
+ registerCommand(
51
+ name: string,
52
+ options: {
53
+ description: string;
54
+ handler(args: string, ctx: ExtensionContext): unknown | Promise<unknown>;
55
+ },
56
+ ): void;
57
+ sendMessage<T = unknown>(
58
+ message: { customType?: string; content: string; display?: boolean; details?: Record<string, unknown> },
59
+ options?: { deliverAs?: "steer" | "followUp" | "nextTurn"; triggerTurn?: boolean },
60
+ ): void;
61
+ sendUserMessage(
62
+ content: string,
63
+ options?: { deliverAs?: "steer" | "followUp" },
64
+ ): void;
65
+ };
66
+
67
+ type ModuleComponent = {
68
+ name: string;
69
+ status: "planned" | "in-progress" | "implemented" | "deprecated";
70
+ description: string;
71
+ dependencies: string[];
72
+ };
73
+
74
+ type ArchitectureSlice = {
75
+ module: string;
76
+ components: ModuleComponent[];
77
+ nextLogicalSlice: ModuleComponent[];
78
+ };
79
+
80
+ type EpicState = {
81
+ name: string;
82
+ trackingIssueId: string | null;
83
+ epicId: string | null;
84
+ status: "planning" | "validating" | "publishing" | "executing" | "done" | "aborted";
85
+ slices: ArchitectureSlice[];
86
+ issues: { id: string; title: string; status: string; remoteIssueId?: string | null }[];
87
+ currentIssueIndex: number;
88
+ createdAt: string;
89
+ };
90
+
91
+ // ── Constants ──
92
+
93
+ const ARCH_MODULES_DIR = ".pi/architecture/modules";
94
+ const ISSUES_DIR = ".pi/issues";
95
+
96
+ // ── Helpers ──
97
+
98
+ function log(ctx: ExtensionContext, message: string, level = "info") {
99
+ ctx.ui.notify(message, level);
100
+ }
101
+
102
+ function runScript(cwd: string, script: string): { exitCode: number; stdout: string } {
103
+ try {
104
+ const stdout = execSync(`bash -c "${script}"`, { cwd, timeout: 120_000, encoding: "utf-8" });
105
+ return { exitCode: 0, stdout };
106
+ } catch (e: unknown) {
107
+ const err = e as { status?: number; stdout?: string; message?: string };
108
+ return { exitCode: err.status ?? 1, stdout: err.stdout ?? err.message ?? "" };
109
+ }
110
+ }
111
+
112
+ // Read repoTool from guardian-manifest.json (defaults to "gh")
113
+ function readRepoTool(cwd: string): string {
114
+ try {
115
+ const manifestPath = join(cwd, "guardian-manifest.json");
116
+ if (existsSync(manifestPath)) {
117
+ const raw = readFileSync(manifestPath, "utf-8");
118
+ const manifest = JSON.parse(raw) as { repoTool?: string };
119
+ if (manifest.repoTool === "glab") return "glab";
120
+ }
121
+ } catch {
122
+ // fall through to default
123
+ }
124
+ return "gh";
125
+ }
126
+
127
+ // Read the repository slug (owner/repo) from guardian-manifest.json
128
+ function readRepository(cwd: string): string | null {
129
+ try {
130
+ const manifestPath = join(cwd, "guardian-manifest.json");
131
+ if (existsSync(manifestPath)) {
132
+ const raw = readFileSync(manifestPath, "utf-8");
133
+ const manifest = JSON.parse(raw) as {
134
+ repository?: string;
135
+ templateContext?: { repository?: string };
136
+ };
137
+ if (manifest.repository) return manifest.repository;
138
+ if (manifest.templateContext?.repository)
139
+ return manifest.templateContext.repository;
140
+ }
141
+ } catch {
142
+ // ignore
143
+ }
144
+ return null;
145
+ }
146
+
147
+ /**
148
+ * Get Git platform base URL. For GitLab, tries to detect self-hosted instances.
149
+ */
150
+ function getGitBaseUrl(repoTool: string): string {
151
+ if (repoTool === "glab") {
152
+ try {
153
+ const uri = execSync("glab config get gitlab_uri 2>/dev/null", {
154
+ encoding: "utf-8",
155
+ }).trim();
156
+ if (uri) return uri.replace(/\/+$/, "");
157
+ } catch {
158
+ // fall through to default
159
+ }
160
+ return "https://gitlab.com";
161
+ }
162
+ return "https://github.com";
163
+ }
164
+
165
+ function commandExists(cmd: string): boolean {
166
+ try {
167
+ execSync(`command -v ${cmd}`, { stdio: "ignore" });
168
+ return true;
169
+ } catch {
170
+ return false;
171
+ }
172
+ }
173
+
174
+ // Try to create a remote GitHub/GitLab issue via the shell script wrapper.
175
+ // Uses execFileSync to avoid shell quoting issues with nested commands.
176
+ function createRemoteIssue(
177
+ cwd: string,
178
+ title: string,
179
+ bodyFilePath: string,
180
+ labels: string,
181
+ repository?: string,
182
+ ): { success: boolean; issueNumber: string | null; error?: string } {
183
+ const createScript = join(cwd, ".pi/scripts/git/create-tracking-issue.sh");
184
+ if (!existsSync(createScript)) {
185
+ return { success: false, issueNumber: null, error: "create-tracking-issue.sh not found" };
186
+ }
187
+
188
+ const args: string[] = [
189
+ createScript,
190
+ "--title",
191
+ title,
192
+ "--body-file",
193
+ bodyFilePath,
194
+ "--labels",
195
+ labels,
196
+ ];
197
+ if (repository) args.push("--repo", repository);
198
+
199
+ let stdout = "";
200
+ let exitCode = 0;
201
+ try {
202
+ stdout = execFileSync("bash", args, {
203
+ cwd,
204
+ timeout: 120_000,
205
+ encoding: "utf-8",
206
+ });
207
+ } catch (e: unknown) {
208
+ const err = e as { status?: number; stdout?: string; message?: string };
209
+ exitCode = err.status ?? 1;
210
+ stdout = err.stdout ?? err.message ?? "";
211
+ }
212
+
213
+ if (exitCode !== 0) {
214
+ return { success: false, issueNumber: null, error: stdout };
215
+ }
216
+
217
+ const numberMatch = stdout.match(/TRACKING_ID=(\d+)/);
218
+ if (numberMatch) {
219
+ return { success: true, issueNumber: numberMatch[1] };
220
+ }
221
+ const urlMatch = stdout.match(/#(\d+)/);
222
+ if (urlMatch) {
223
+ return { success: true, issueNumber: urlMatch[1] };
224
+ }
225
+ return { success: false, issueNumber: null, error: "Could not parse issue number" };
226
+ }
227
+
228
+ // Ensure the GitHub/GitLab repository exists and local git remote is configured.
229
+ // Returns the repository slug if remote is ready, empty string if not available.
230
+ function ensureRemoteRepo(
231
+ cwd: string,
232
+ repository: string,
233
+ epicName: string,
234
+ repoTool: string,
235
+ ): string {
236
+ // Check if remote already exists via git remote
237
+ const remoteCheck = runScript(cwd, "git remote get-url origin 2>/dev/null");
238
+ if (remoteCheck.exitCode === 0) {
239
+ return repository;
240
+ }
241
+
242
+ // Remote not configured locally — ensure the remote repo exists on GitHub/GitLab
243
+ if (repoTool === "gh") {
244
+ runScript(
245
+ cwd,
246
+ `gh repo create "${repository}" --private --description "Epic: ${epicName}" 2>&1`,
247
+ );
248
+ // Remove stale origin if it exists but points nowhere useful
249
+ runScript(cwd, "git remote remove origin 2>/dev/null");
250
+ const httpsUrl = `https://github.com/${repository}.git`;
251
+ runScript(cwd, `git remote add origin "${httpsUrl}"`);
252
+ return repository;
253
+ }
254
+
255
+ // GitLab path
256
+ runScript(
257
+ cwd,
258
+ `glab repo create "${repository}" --private --description "Epic: ${epicName}" 2>&1`,
259
+ );
260
+ runScript(cwd, "git remote remove origin 2>/dev/null");
261
+ const httpsUrl = `https://gitlab.com/${repository}.git`;
262
+ runScript(cwd, `git remote add origin "${httpsUrl}"`);
263
+ return repository;
264
+ }
265
+
266
+ // Link a remote issue to the epic tracking issue
267
+ function linkRemoteIssue(
268
+ cwd: string,
269
+ issueId: string,
270
+ epicId: string,
271
+ ): { success: boolean; error?: string } {
272
+ const linkScript = join(cwd, ".pi/scripts/git/link-issue-to-epic.sh");
273
+ if (!existsSync(linkScript)) {
274
+ return { success: false, error: "link-issue-to-epic.sh not found" };
275
+ }
276
+
277
+ const safeIssue = issueId.replace(/[^a-zA-Z0-9 _\-.]/g, "");
278
+ const safeEpic = epicId.replace(/[^a-zA-Z0-9 _\-.]/g, "");
279
+
280
+ const cmd = `bash "${linkScript}" --issue-id "${safeIssue}" --epic-id "${safeEpic}"`;
281
+ const result = runScript(cwd, cmd);
282
+ if (result.exitCode !== 0) {
283
+ return { success: false, error: result.stdout };
284
+ }
285
+ return { success: true };
286
+ }
287
+
288
+ // ── Architecture Discovery ──
289
+
290
+ function readGroupId(cwd: string): string {
291
+ // Try pom.xml
292
+ const pomPath = join(cwd, "pom.xml");
293
+ try {
294
+ const pom = readFileSync(pomPath, "utf-8");
295
+ const match = pom.match(/<groupId>([^<]+)<\/groupId>/);
296
+ if (match && match[1] !== "com.example") return match[1];
297
+ } catch {}
298
+ // Try build.gradle
299
+ const gradlePath = join(cwd, "build.gradle");
300
+ try {
301
+ const gradle = readFileSync(gradlePath, "utf-8");
302
+ const match = gradle.match(/group\s*=\s*['"]([^'"]+)['"]/);
303
+ if (match) return match[1];
304
+ } catch {}
305
+ return "com.example";
306
+ }
307
+
308
+ function findModuleByName(cwd: string, name: string): string | null {
309
+ const files = discoverModules(cwd);
310
+ const nameLower = name.toLowerCase().replace(/[^a-z0-9]/g, "");
311
+ for (const f of files) {
312
+ const key = f.replace(".md", "").toLowerCase().replace(/[^a-z0-9]/g, "");
313
+ if (key === nameLower || nameLower.includes(key) || key.includes(nameLower)) {
314
+ return f;
315
+ }
316
+ }
317
+ return null;
318
+ }
319
+
320
+ function discoverModules(cwd: string): string[] {
321
+ const dir = join(cwd, ARCH_MODULES_DIR);
322
+ if (!existsSync(dir)) return [];
323
+ try {
324
+ return readdirSync(dir).filter((f) => f.endsWith(".md"));
325
+ } catch {
326
+ return [];
327
+ }
328
+ }
329
+
330
+ function parseModuleFile(filePath: string): ModuleComponent[] {
331
+ if (!existsSync(filePath)) return [];
332
+ const content = readFileSync(filePath, "utf-8");
333
+ const components: ModuleComponent[] = [];
334
+
335
+ const lines = content.split("\n");
336
+ let inComponentSection = false;
337
+ let inDetailsSection = false;
338
+ let currentName = "";
339
+ let currentStatus = "";
340
+ let currentDesc = "";
341
+ let currentDeps: string[] = [];
342
+
343
+ function saveCurrent() {
344
+ if (currentName) {
345
+ // Default to planned if no explicit status found
346
+ const status = currentStatus || "planned";
347
+ const desc = currentDesc || `${currentName} component`;
348
+ components.push({
349
+ name: currentName,
350
+ status: status as ModuleComponent["status"],
351
+ description: desc.trim(),
352
+ dependencies: currentDeps.length > 0 ? currentDeps : ["none"],
353
+ });
354
+ }
355
+ }
356
+
357
+ for (const line of lines) {
358
+ const trimmed = line.trim();
359
+
360
+ // Enter component section (supports "## Components", "## Component Details", "## Component")
361
+ if (trimmed.match(/^##\s+Components?/i) || trimmed.match(/^##\s+Component\s+Details/i)) {
362
+ inComponentSection = true;
363
+ continue;
364
+ }
365
+
366
+ // Leave component section on next top-level section
367
+ if (inComponentSection && trimmed.match(/^##\s+/) && !trimmed.match(/^##\s+Components?/i)) {
368
+ saveCurrent();
369
+ currentName = "";
370
+ currentStatus = "";
371
+ currentDesc = "";
372
+ currentDeps = [];
373
+ inComponentSection = false;
374
+ inDetailsSection = false;
375
+ continue;
376
+ }
377
+
378
+ // Component heading (###) — start a new component entry
379
+ if (inComponentSection && trimmed.match(/^###\s+/)) {
380
+ // Skip non-component ### headings like "### Depends On" or "### Security"
381
+ const name = trimmed.replace(/^###\s+/, "");
382
+ if (name.match(/^(depends|security|testing|performance|error|change|data flow|responsibilities|overview|interfaces|inputs|outputs)/i)) {
383
+ continue;
384
+ }
385
+ saveCurrent();
386
+ currentName = name;
387
+ currentStatus = "";
388
+ currentDesc = "";
389
+ currentDeps = [];
390
+ continue;
391
+ }
392
+
393
+ if (!currentName) continue;
394
+
395
+ if (trimmed.startsWith("status:")) {
396
+ currentStatus = trimmed.replace("status:", "").trim().toLowerCase();
397
+ } else if (trimmed.startsWith("depends:")) {
398
+ const depsStr = trimmed.replace("depends:", "").trim();
399
+ if (depsStr && depsStr !== "none" && depsStr !== "[TODO") {
400
+ currentDeps = depsStr.split(",").map((d) => d.trim()).filter(Boolean);
401
+ }
402
+ } else if (trimmed.startsWith("**Purpose:**")) {
403
+ currentDesc = trimmed.replace(/\*\*Purpose:\*\*\s*/, "").trim();
404
+ } else if (!currentDesc && trimmed.length > 10 && !trimmed.startsWith("#") && !trimmed.startsWith("-") && !trimmed.startsWith("|") && !trimmed.startsWith(">") && !trimmed.startsWith("```")) {
405
+ // Use first substantial sentence as description
406
+ currentDesc = trimmed.slice(0, 200);
407
+ }
408
+ }
409
+
410
+ saveCurrent();
411
+ return components;
412
+ }
413
+
414
+ function findNextLogicalSlice(cwd: string, moduleFiles: string[]): ArchitectureSlice | null {
415
+ for (const moduleFile of moduleFiles) {
416
+ const components = parseModuleFile(join(cwd, ARCH_MODULES_DIR, moduleFile));
417
+ const planned = components.filter((c) => c.status === "planned");
418
+ if (planned.length > 0) {
419
+ return {
420
+ module: moduleFile.replace(".md", ""),
421
+ components,
422
+ nextLogicalSlice: planned,
423
+ };
424
+ }
425
+ }
426
+ return null;
427
+ }
428
+
429
+ // ── Issue Generation ──
430
+
431
+ function generateIssueMarkdown(
432
+ component: ModuleComponent,
433
+ slice: ArchitectureSlice,
434
+ issueIndex: number,
435
+ totalIssues: number,
436
+ ): string {
437
+ const moduleId = slice.module.replace(/^module-/, "");
438
+ const componentName = component.name.toLowerCase().replace(/\s+/g, "-");
439
+ const issueId = `ISSUE-${moduleId.toUpperCase()}-${issueIndex + 1}`;
440
+
441
+ return `---
442
+ guardian_issue:
443
+ id: "${issueId}"
444
+ epic: "TBD"
445
+ component: "${component.name}"
446
+ module: "${slice.module}"
447
+ status: planned
448
+ priority: high
449
+ dependencies:
450
+ ${component.dependencies.map((d) => ` - "${d}"`).join("\n")}
451
+
452
+ in_scope:
453
+ - Implement ${component.name} for the ${slice.module} module
454
+ - Write unit tests for all public interfaces
455
+ - Add integration tests with upstream/downstream components
456
+ - Create API documentation
457
+
458
+ out_of_scope:
459
+ - Changes to upstream components (${component.dependencies.join(", ")})
460
+ - UI/frontend changes
461
+ - Deployment pipeline configuration
462
+
463
+ affected_layers:
464
+ domain:
465
+ - New domain models for ${componentName}
466
+ application:
467
+ - New service/handler for ${componentName}
468
+ infrastructure:
469
+ - New database tables or external service connections
470
+ api:
471
+ - New endpoints or event handlers
472
+
473
+ canonical_references:
474
+ - module: ".pi/architecture/modules/${slice.module}.md#${componentName}"
475
+
476
+ acceptance_criteria:
477
+ - "CI pipeline passes (validate-ci.sh)"
478
+ - "All unit tests pass with ≥ 90% coverage"
479
+ - "Integration tests pass with upstream/downstream components"
480
+ - "validate-security.sh passes"
481
+ - "validate-architecture.sh passes"
482
+ - "validate-canonical.sh passes"
483
+
484
+ validators:
485
+ - ci
486
+ - tests
487
+ - security
488
+ - architecture
489
+ - canonical
490
+
491
+ implementation_notes: |
492
+ ${component.description || "Implement this component according to the architecture module."}
493
+
494
+ file_changes:
495
+ - "create: src/${moduleId}/${componentName}/"
496
+ - "create: tests/unit/${moduleId}/${componentName}/"
497
+ - "create: tests/integration/${moduleId}/${componentName}/"
498
+ ---
499
+
500
+ # ${issueId}: ${component.name}
501
+
502
+ ## Intent
503
+
504
+ ${component.description || `Implement ${component.name} for the ${slice.module} module.`}
505
+
506
+ ## Architecture Context
507
+
508
+ - **Module:** ${slice.module}
509
+ - **Component:** ${component.name}
510
+ - **Status:** ${component.status}
511
+ - **Dependencies:** ${component.dependencies.length > 0 ? component.dependencies.join(", ") : "none"}
512
+
513
+ ## Dependencies
514
+
515
+ \`\`\`
516
+ ${component.dependencies.map((d) => ` └── ${d}`).join("\n") || " └── (root component — no dependencies)"}
517
+ \`\`\`
518
+
519
+ ## In Scope
520
+
521
+ - Implement ${component.name} for the ${slice.module} module
522
+ - Write unit tests for all public interfaces
523
+ - Add integration tests with upstream/downstream components
524
+ - Create API documentation
525
+
526
+ ## Out of Scope
527
+
528
+ - Changes to upstream components
529
+ - UI/frontend changes
530
+ - Deployment pipeline configuration
531
+
532
+ ## Affected Layers
533
+
534
+ ### Domain
535
+ - New domain models for ${componentName}
536
+
537
+ ### Application
538
+ - New service/handler for ${componentName}
539
+
540
+ ### Infrastructure
541
+ - New database tables or external service connections
542
+
543
+ ### API
544
+ - New endpoints or event handlers
545
+
546
+ ## Canonical References
547
+
548
+ - **Module:** \`.pi/architecture/modules/${slice.module}.md#${componentName}\`
549
+
550
+ ## Acceptance Criteria
551
+
552
+ | # | Criterion | Validator |
553
+ |---|-----------|-----------|
554
+ | 1 | CI pipeline passes | \`validate-ci.sh\` |
555
+ | 2 | All unit tests pass with ≥ 90% coverage | \`validate-tests.sh\` |
556
+ | 3 | Integration tests pass | \`validate-integration.sh\` |
557
+ | 4 | Security checks pass | \`validate-security.sh\` |
558
+ | 5 | Architecture compliance | \`validate-architecture.sh\` |
559
+ | 6 | Canonical references valid | \`validate-canonical.sh\` |
560
+
561
+ ## Implementation
562
+
563
+ > **Agent:** This is your complete session context. All information you need is above.
564
+ > Start by reading the canonical reference files, then implement following the layer structure.
565
+
566
+ ### Steps
567
+
568
+ 1. Read canonical architecture references
569
+ 2. Create domain entities and interfaces
570
+ 3. Implement application service/handler
571
+ 4. Add infrastructure connections
572
+ 5. Write unit tests (≥ 90% coverage)
573
+ 6. Write integration tests
574
+ 7. Run all validators
575
+ 8. Create MR
576
+ `;
577
+ }
578
+ // ── Contract Freeze Generator ──
579
+
580
+ function generateContractFreezeMarkdown(
581
+ slice: ArchitectureSlice,
582
+ epicName: string,
583
+ ): string {
584
+ const moduleId = slice.module.replace(/^module-/, "");
585
+
586
+ return `---
587
+ guardian_issue:
588
+ id: "ISSUE-CONTRACT-FREEZE"
589
+ epic: "${epicName}"
590
+ component: "Contract Freeze"
591
+ module: "${slice.module}"
592
+ status: planned
593
+ priority: critical
594
+ dependencies: []
595
+
596
+ in_scope:
597
+ - Define public interfaces for all components in this epic
598
+ - Define DTOs, schemas, and API contracts
599
+ - Document event payloads and topics
600
+ - Create interface stubs with no implementation
601
+ - Freeze: no implementation changes without contract change
602
+
603
+ out_of_scope:
604
+ - Any implementation logic
605
+ - Database schema changes
606
+ - Infrastructure setup
607
+
608
+ affected_layers:
609
+ domain:
610
+ - Interface definitions for domain services
611
+ application:
612
+ - Input/output DTO definitions
613
+ api:
614
+ - REST/event contracts
615
+
616
+ canonical_references:
617
+ - module: ".pi/architecture/modules/${slice.module}.md"
618
+
619
+ acceptance_criteria:
620
+ - "All component interfaces defined as interfaces/types"
621
+ - "DTO schemas documented"
622
+ - "API contracts frozen and reviewed"
623
+ - "Implementation PRs reference these contracts"
624
+
625
+ validators:
626
+ - architecture
627
+ - canonical
628
+
629
+ implementation_notes: |
630
+ Define the contract before any implementation. Every implementation issue
631
+ depends on this contract being frozen first. The contract should include:
632
+ interfaces, types, DTOs, event schemas, API paths, error formats.
633
+
634
+ file_changes:
635
+ - "create: src/${moduleId}/contracts/"
636
+ - "create: src/${moduleId}/contracts/dtos/"
637
+ - "create: src/${moduleId}/contracts/events/"
638
+ ---
639
+
640
+ # Contract Freeze: ${slice.module}
641
+
642
+ ## Intent
643
+
644
+ Define and freeze all public interfaces, contracts, and schemas for the ${slice.module}
645
+ epic before any implementation begins. This prevents architecture drift — implementation
646
+ must satisfy contracts, not the other way around.
647
+
648
+ ## Included Components
649
+
650
+ ${slice.nextLogicalSlice.map((c: { name: string }) => `- ${c.name}`).join("\n")}
651
+
652
+ ## What Must Be Frozen
653
+
654
+ ### Interfaces
655
+ - Service interfaces for every component
656
+ - Repository/DAO interfaces
657
+ - Factory interfaces
658
+
659
+ ### Contracts
660
+ - Input/output DTO schemas
661
+ - API endpoint contracts (method, path, request/response)
662
+ - Event payload schemas
663
+ - Error response formats
664
+
665
+ ### Out of Bounds (no contracts needed)
666
+ - Internal implementation details
667
+ - Database column names (hidden behind repository)
668
+ - Framework-specific annotations
669
+
670
+ ## Acceptance Criteria
671
+
672
+ | # | Criterion | How to Verify |
673
+ |---|-----------|---------------|
674
+ | 1 | All component interfaces defined | Check src/<group>/<module>/domain/ and application/ |
675
+ | 2 | Contracts reviewed and frozen | PR approval |
676
+ | 3 | DTO schemas documented | OpenAPI / TypeSpec / equivalent |
677
+ | 4 | Implementation depends on contracts | No implementation without interface |
678
+
679
+ ## Implementation
680
+
681
+ > **Agent:** Create interface-only files. No implementation. Use Clean Architecture layers:
682
+ > 1. Read the architecture module to understand each component's role
683
+ > 2. Place domain interfaces in domain/, service interfaces in application/, API contracts in interfaces/http/
684
+ > 3. DTOs with proper validation decorators go in application/
685
+ > 4. Event schemas go in domain/event/
686
+ > 5. Repository interfaces go in infrastructure/repository/
687
+ >
688
+ > The goal is a reviewed, frozen contract that implementation issues can depend on.
689
+ `;
690
+ }
691
+
692
+ // ── Proofing Issue Generator ──
693
+
694
+ function generateProofingMarkdown(
695
+ slice: ArchitectureSlice,
696
+ epicName: string,
697
+ ): string {
698
+ const moduleId = slice.module.replace(/^module-/, "");
699
+
700
+ return `---
701
+ guardian_issue:
702
+ id: "ISSUE-PROOFING"
703
+ epic: "${epicName}"
704
+ component: "Proofing & CI Enforcement"
705
+ module: "${slice.module}"
706
+ status: planned
707
+ priority: critical
708
+ dependencies: []
709
+
710
+ in_scope:
711
+ - Create deterministic validation scripts for each contract
712
+ - Verify all interfaces have matching implementations
713
+ - Check test coverage meets thresholds
714
+ - Integrate proofing scripts into .pi/scripts/ci/
715
+ - Scripts must be self-contained shell scripts (zero token cost)
716
+
717
+ out_of_scope:
718
+ - Implementation changes
719
+ - New features
720
+ - Production deployment
721
+
722
+ affected_layers:
723
+ ci:
724
+ - New proofing scripts in .pi/scripts/ci/
725
+ - Updated CI stage configuration
726
+
727
+ canonical_references:
728
+ - module: ".pi/architecture/modules/${slice.module}.md"
729
+
730
+ acceptance_criteria:
731
+ - "All proofing scripts created and executable"
732
+ - "Each contract has at least one validation check"
733
+ - "Scripts pass on current implementation"
734
+ - "Scripts fail if implementation is removed"
735
+ - "Scripts integrated into CI pipeline (stage in run_hardening_stages.sh)"
736
+
737
+ validators:
738
+ - ci
739
+ - tests
740
+ - canonical
741
+
742
+ implementation_notes: |
743
+ Create deterministic shell scripts that validate: each defined interface has an
744
+ implementation, each implementation has tests, test coverage meets threshold,
745
+ contracts are not violated. These escape the LLM ad-hoc check trap — they run
746
+ every build for zero token cost.
747
+
748
+ file_changes:
749
+ - "create: .pi/scripts/ci/check_${moduleId}_contracts.sh"
750
+ - "create: .pi/scripts/ci/check_${moduleId}_coverage.sh"
751
+ - "modify: .pi/scripts/ci/run_hardening_stages.sh"
752
+ ---
753
+
754
+ # Proofing & CI Enforcement: ${slice.module}
755
+
756
+ ## Intent
757
+
758
+ Create deterministic, automated validation scripts that prove every contract from the
759
+ freeze phase is correctly implemented and tested. These scripts make compliance
760
+ automatic — no human review needed for routine checks.
761
+
762
+ ## What Each Script Does
763
+
764
+ ### Contract Implementation Check
765
+ - Reads each interface from the contract freeze
766
+ - Verifies a concrete implementation class exists
767
+ - Verifies all interface methods are implemented
768
+ - Reports violations with file:line references
769
+
770
+ ### Coverage Threshold Check
771
+ - Runs the project's coverage tool
772
+ - Asserts each module meets minimum coverage (default 80%)
773
+ - Fails the build if coverage drops
774
+
775
+ ### CI Integration
776
+ Each check becomes a CI stage in the hardening pipeline — it runs automatically
777
+ on every PR. No LLM cost. No human review. Just pass or fail.
778
+
779
+ ## Scripts To Create
780
+
781
+ | Script | Purpose | Location |
782
+ |--------|---------|----------|
783
+ | check_${moduleId}_contracts.sh | Validate contract implementation | .pi/scripts/ci/ |
784
+ | check_${moduleId}_coverage.sh | Enforce coverage thresholds | .pi/scripts/ci/ |
785
+ | stage_${moduleId}_proofing.sh | CI stage wrapper | .pi/scripts/ci/ |
786
+
787
+ ## CI Pipeline Update
788
+
789
+ Add the new stage to \`run_hardening_stages.sh\`:
790
+
791
+ \`\`\`bash
792
+ run_stage "11" "${moduleId}_proofing" \\
793
+ "\${SCRIPTS_DIR}/stage_${moduleId}_proofing.sh" \\
794
+ "always"
795
+ \`\`\`
796
+
797
+ ## Acceptance Criteria
798
+
799
+ | # | Criterion | Script |
800
+ |---|-----------|--------|
801
+ | 1 | All interfaces have implementations | check_contracts.sh |
802
+ | 2 | Coverage ≥ 80% per module | check_coverage.sh |
803
+ | 3 | CI runs checks on every PR | run_hardening_stages.sh |
804
+ | 4 | All scripts exit 0 on pass, 1 on fail | self-validating |
805
+
806
+ ## Implementation
807
+
808
+ > **Agent:** Create shell scripts. Keep them simple — grep, find, awk.
809
+ > No frameworks, no dependencies. Each script should be:
810
+ > 1. Runnable standalone (bash script.sh)
811
+ > 2. Runnable as a CI stage
812
+ > 3. Self-documenting with --help
813
+ > 4. Exit 0 for pass, 1 for fail
814
+ >
815
+ > End by running the full CI pipeline to verify integration:
816
+ > \`bash .pi/scripts/ci/run_hardening_stages.sh\`
817
+ `;
818
+ }
819
+
820
+ // ── Architecture Readiness Generator (expanded) ──
821
+
822
+ function generateArchitectureReadinessMarkdown(
823
+ slice: ArchitectureSlice,
824
+ epicName: string,
825
+ ): string {
826
+ const moduleId = slice.module.replace(/^module-/, "");
827
+
828
+ return `---
829
+ guardian_issue:
830
+ id: "ISSUE-READINESS"
831
+ epic: "${epicName}"
832
+ component: "Architecture Readiness"
833
+ module: "${slice.module}"
834
+ status: planned
835
+ priority: critical
836
+ dependencies: []
837
+
838
+ in_scope:
839
+ - Create runbook (startup, shutdown, recovery procedures)
840
+ - Create DR plan (backup, restore, failover)
841
+ - Add observability (metrics, tracing, structured logging)
842
+ - Add health check endpoints
843
+ - Update architecture documentation
844
+ - Sync canonical references
845
+ - Verify CI enforces all the above
846
+
847
+ out_of_scope:
848
+ - New feature work
849
+ - Implementation changes
850
+
851
+ affected_layers:
852
+ domain:
853
+ - Architecture documentation updates
854
+ application:
855
+ - Observability hooks
856
+ infrastructure:
857
+ - Health checks, monitoring config
858
+ ci:
859
+ - Verify proofing scripts + validators in CI
860
+
861
+ canonical_references:
862
+ - module: ".pi/architecture/modules/${slice.module}.md"
863
+
864
+ acceptance_criteria:
865
+ - "Runbook created and reviewed"
866
+ - "DR plan documented"
867
+ - "Observability patterns in place (tracing, metrics, logging)"
868
+ - "Health check endpoint responds"
869
+ - "Architecture docs synced with implementation"
870
+ - "Canonical references verified (validate-canonical.sh passes)"
871
+ - "Proofing scripts integrated in CI and passing"
872
+ - "All validators pass: ci, tests, security, architecture, canonical, operations"
873
+
874
+ validators:
875
+ - ci
876
+ - tests
877
+ - security
878
+ - architecture
879
+ - canonical
880
+ - operations
881
+
882
+ implementation_notes: |
883
+ The final issue in every epic. Production readiness means: the team can operate it
884
+ (runbook), recover from failure (DR plan), observe it (metrics/tracing/logging),
885
+ and CI will catch regressions (proofing scripts + validators).
886
+
887
+ file_changes:
888
+ - "create: docs/runbook-${moduleId}.md"
889
+ - "create: docs/dr-plan-${moduleId}.md"
890
+ - "modify: .pi/architecture/CHANGELOG.md"
891
+ - "modify: .pi/architecture/modules/${slice.module}.md"
892
+ ---
893
+
894
+ # Architecture Readiness: ${slice.module}
895
+
896
+ ## Intent
897
+
898
+ Make the ${slice.module} module production-ready. This is the final issue in every epic
899
+ — it closes the loop between implementation and operability.
900
+
901
+ ## Deliverables
902
+
903
+ ### Runbook
904
+ \`docs/runbook-${moduleId}.md\` covering:
905
+ - Startup sequence and dependencies
906
+ - Graceful shutdown procedure
907
+ - Common failure modes and recovery
908
+ - Configuration reference
909
+
910
+ ### DR Plan
911
+ \`docs/dr-plan-${moduleId}.md\` covering:
912
+ - Backup strategy and schedule
913
+ - Restore procedure
914
+ - Failover plan
915
+ - RTO/RPO targets
916
+
917
+ ### Observability
918
+ - Metrics: key business and technical metrics exposed
919
+ - Tracing: distributed tracing context propagated
920
+ - Logging: structured logging with correlation IDs
921
+ - Health: /health endpoint with dependency checks
922
+
923
+ ### CI Enforcement
924
+ Verify that:
925
+ - Proofing scripts from the proofing issue are in CI
926
+ - All validators (ci, tests, security, architecture, canonical, operations) pass
927
+ - A CI pipeline run against this state succeeds
928
+
929
+ ## Acceptance Criteria
930
+
931
+ | # | Criterion | Validator |
932
+ |---|-----------|-----------|
933
+ | 1 | Runbook exists | manual review |
934
+ | 2 | DR plan exists | manual review |
935
+ | 3 | Observability patterns present | validate-operations.sh |
936
+ | 4 | Canonical references synced | validate-canonical.sh |
937
+ | 5 | CI enforce validators | validate-ci.sh |
938
+ | 6 | All proofing scripts pass | run_hardening_stages.sh |
939
+ | 7 | Architecture docs updated | validate-architecture.sh |
940
+
941
+ ## Implementation
942
+
943
+ > **Agent:** Close out the epic properly:
944
+ > 1. Write runbook and DR plan docs
945
+ > 2. Add observability instrumentation
946
+ > 3. Update architecture module docs with final implementation details
947
+ > 4. Sync CHANGE LOG
948
+ > 5. Verify proofing scripts from the proofing issue pass
949
+ > 6. Run full validation suite
950
+ > 7. Architecture readiness validator: bash .pi/scripts/validate-architecture-readiness.sh
951
+ > 8. Create final MR
952
+ `;
953
+ }
954
+
955
+
956
+ // ── Epic State Persistence ──
957
+
958
+ const EPIC_STATE_KEY = ".pi/.guardian-epic-state.json";
959
+
960
+ function loadEpicState(cwd: string): EpicState | null {
961
+ const p = join(cwd, EPIC_STATE_KEY);
962
+ try {
963
+ if (!existsSync(p)) return null;
964
+ return JSON.parse(readFileSync(p, "utf-8")) as EpicState;
965
+ } catch {
966
+ return null;
967
+ }
968
+ }
969
+
970
+ function saveEpicState(cwd: string, state: EpicState): void {
971
+ const p = join(cwd, EPIC_STATE_KEY);
972
+ const dir = dirname(p);
973
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
974
+ writeFileSync(p, JSON.stringify(state, null, 2), "utf-8");
975
+ }
976
+
977
+ function formatEpicStatus(state: EpicState | null): string {
978
+ if (!state) return "No active epic";
979
+ const slice = state.slices?.[0];
980
+ if (!slice) return `Epic "${state.name}" — no slices`;
981
+ const components = slice.nextLogicalSlice || [];
982
+ const done = components.filter((c: ModuleComponent) => c.status === "implemented").length;
983
+ const total = components.length;
984
+ return [
985
+ `Epic: ${state.name}`,
986
+ `Module: ${slice.module}`,
987
+ `Progress: ${done}/${total} components`,
988
+ `Issues: ${(state.issues || []).length}`,
989
+ `Pipeline: ${state.status}`,
990
+ ].join("\n");
991
+ }
992
+
993
+ // ── Epic Manager ──
994
+
995
+ class EpicManager {
996
+ private state: EpicState | null;
997
+
998
+ constructor(private cwd: string) {
999
+ this.state = loadEpicState(cwd);
1000
+ }
1001
+
1002
+ getState(): EpicState | null {
1003
+ return this.state;
1004
+ }
1005
+
1006
+ async startEpic(
1007
+ ctx: ExtensionContext,
1008
+ name: string,
1009
+ trackingIssueId?: string,
1010
+ ): Promise<EpicState> {
1011
+ const moduleFiles = discoverModules(this.cwd);
1012
+ if (moduleFiles.length === 0) {
1013
+ throw new Error("No architecture modules found in .pi/architecture/modules/.");
1014
+ }
1015
+
1016
+ // Try to match epic name to a module doc
1017
+ const matchedModule = findModuleByName(this.cwd, name);
1018
+ let slice: ArchitectureSlice | null = null;
1019
+ if (matchedModule) {
1020
+ const components = parseModuleFile(join(this.cwd, ARCH_MODULES_DIR, matchedModule));
1021
+ const planned = components.filter((c: ModuleComponent) => c.status === "planned");
1022
+ if (planned.length > 0) {
1023
+ slice = { module: matchedModule.replace(".md", ""), components, nextLogicalSlice: planned };
1024
+ }
1025
+ }
1026
+ // Fallback: first module with planned components
1027
+ if (!slice) {
1028
+ slice = findNextLogicalSlice(this.cwd, moduleFiles);
1029
+ }
1030
+ if (!slice) {
1031
+ throw new Error("All architecture components are implemented. No next slice found.");
1032
+ }
1033
+
1034
+ ctx.ui.setStatus("architect", `Planning epic: ${name}`);
1035
+
1036
+ const repoTool = readRepoTool(this.cwd);
1037
+ const repository = readRepository(this.cwd);
1038
+ const targetRepo = repository || slice.module;
1039
+ let hasRemote = false;
1040
+ let remoteRepo = "";
1041
+
1042
+ if (repoTool === "glab" ? commandExists("glab") : commandExists("gh")) {
1043
+ const authCheck = runScript(
1044
+ this.cwd,
1045
+ repoTool === "glab" ? "glab auth status 2>/dev/null" : "gh auth status 2>/dev/null",
1046
+ );
1047
+ if (authCheck.exitCode === 0) {
1048
+ remoteRepo = ensureRemoteRepo(this.cwd, targetRepo, name, repoTool);
1049
+ hasRemote = remoteRepo.length > 0;
1050
+ }
1051
+ }
1052
+
1053
+ const issues: { id: string; title: string; status: string; remoteIssueId?: string | null }[] = [];
1054
+ const issuesDir = join(this.cwd, ".pi/issues");
1055
+ if (!existsSync(issuesDir)) mkdirSync(issuesDir, { recursive: true });
1056
+
1057
+ // 0. Auto-create tracking issue (unless user provided one)
1058
+ let effectiveTrackingId = trackingIssueId || null;
1059
+ if (!effectiveTrackingId && hasRemote && remoteRepo) {
1060
+ const trackingBody = [
1061
+ `# Epic: ${name}`,
1062
+ "",
1063
+ `**Module:** ${slice.module}`,
1064
+ `**Created:** ${new Date().toISOString()}`,
1065
+ "",
1066
+ "## Components",
1067
+ ...slice.nextLogicalSlice.map((c: ModuleComponent) => `- ${c.name}: ${c.description.slice(0, 120)}`),
1068
+ "",
1069
+ "## Issues",
1070
+ "| # | Issue | Status |",
1071
+ "|---|-------|--------|",
1072
+ "| 1 | Contract Freeze | planned |",
1073
+ ...slice.nextLogicalSlice.map((c: ModuleComponent, i: number) =>
1074
+ `| ${i + 2} | ${c.name} | planned |`,
1075
+ ),
1076
+ `| ${slice.nextLogicalSlice.length + 2} | Proofing & CI | planned |`,
1077
+ `| ${slice.nextLogicalSlice.length + 3} | Architecture Readiness | planned |`,
1078
+ "",
1079
+ "## Pipeline",
1080
+ "Steps: implement → validate → create-mr → merge",
1081
+ "",
1082
+ "---",
1083
+ "Auto-generated by Guardian Architect",
1084
+ ].join("\n");
1085
+ const trackingBodyFile = join(issuesDir, ".tracking-issue-body.md");
1086
+ writeFileSync(trackingBodyFile, trackingBody);
1087
+ const trackingResult = createRemoteIssue(
1088
+ this.cwd,
1089
+ `Epic: ${name}`,
1090
+ trackingBodyFile,
1091
+ "epic,tracking",
1092
+ remoteRepo,
1093
+ );
1094
+ if (trackingResult.success && trackingResult.issueNumber) {
1095
+ effectiveTrackingId = trackingResult.issueNumber;
1096
+ }
1097
+ try { if (existsSync(trackingBodyFile)) unlinkSync(trackingBodyFile); } catch { /* ignore */ }
1098
+ }
1099
+
1100
+ // 1. Contract freeze
1101
+ const freezeId = "issue-contract-freeze";
1102
+ const freezeEntry = {
1103
+ id: freezeId,
1104
+ title: "Contract Freeze: Define interfaces and contracts",
1105
+ status: "planned",
1106
+ remoteIssueId: null as string | null,
1107
+ };
1108
+ const freezeMarkdown = generateContractFreezeMarkdown(slice, name);
1109
+ writeFileSync(join(issuesDir, `${freezeId}.md`), freezeMarkdown);
1110
+ if (hasRemote && remoteRepo) {
1111
+ const result = createRemoteIssue(this.cwd, freezeEntry.title, join(issuesDir, `${freezeId}.md`), "epic,contract", remoteRepo);
1112
+ if (result.success && result.issueNumber) {
1113
+ freezeEntry.remoteIssueId = result.issueNumber;
1114
+ if (effectiveTrackingId) linkRemoteIssue(this.cwd, result.issueNumber, effectiveTrackingId);
1115
+ }
1116
+ }
1117
+ issues.push(freezeEntry);
1118
+
1119
+ // 2. Implementation issues
1120
+ for (let i = 0; i < slice.nextLogicalSlice.length; i++) {
1121
+ const comp = slice.nextLogicalSlice[i];
1122
+ const id = `issue-${comp.name.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
1123
+ const entry = {
1124
+ id,
1125
+ title: `${comp.name}: ${comp.description.slice(0, 80)}`,
1126
+ status: "planned" as string,
1127
+ remoteIssueId: null as string | null,
1128
+ };
1129
+ const md = generateIssueMarkdown(comp, slice, i, slice.nextLogicalSlice.length);
1130
+ writeFileSync(join(issuesDir, `${id}.md`), md);
1131
+ if (hasRemote && remoteRepo) {
1132
+ const result = createRemoteIssue(this.cwd, entry.title, join(issuesDir, `${id}.md`), "epic,implementation", remoteRepo);
1133
+ if (result.success && result.issueNumber) {
1134
+ entry.remoteIssueId = result.issueNumber;
1135
+ if (effectiveTrackingId) linkRemoteIssue(this.cwd, result.issueNumber, effectiveTrackingId);
1136
+ }
1137
+ }
1138
+ issues.push(entry);
1139
+ }
1140
+
1141
+ // 3. Proofing
1142
+ const proofingId = "issue-proofing";
1143
+ const proofingEntry = {
1144
+ id: proofingId,
1145
+ title: "Proofing: Validation scripts + CI integration",
1146
+ status: "planned" as string,
1147
+ remoteIssueId: null as string | null,
1148
+ };
1149
+ const proofingMd = generateProofingMarkdown(slice, name);
1150
+ writeFileSync(join(issuesDir, `${proofingId}.md`), proofingMd);
1151
+ if (hasRemote && remoteRepo) {
1152
+ const result = createRemoteIssue(this.cwd, proofingEntry.title, join(issuesDir, `${proofingId}.md`), "epic,proofing", remoteRepo);
1153
+ if (result.success && result.issueNumber) {
1154
+ proofingEntry.remoteIssueId = result.issueNumber;
1155
+ if (effectiveTrackingId) linkRemoteIssue(this.cwd, result.issueNumber, effectiveTrackingId);
1156
+ }
1157
+ }
1158
+ issues.push(proofingEntry);
1159
+
1160
+ // 4. Architecture readiness
1161
+ const readinessId = "issue-architecture-readiness";
1162
+ const readinessEntry = {
1163
+ id: readinessId,
1164
+ title: "Architecture Readiness: Runbook, DR, docs, CI enforcement",
1165
+ status: "planned" as string,
1166
+ remoteIssueId: null as string | null,
1167
+ };
1168
+ const readinessMd = generateArchitectureReadinessMarkdown(slice, name);
1169
+ writeFileSync(join(issuesDir, `${readinessId}.md`), readinessMd);
1170
+ if (hasRemote && remoteRepo) {
1171
+ const result = createRemoteIssue(this.cwd, readinessEntry.title, join(issuesDir, `${readinessId}.md`), "epic,architecture-readiness", remoteRepo);
1172
+ if (result.success && result.issueNumber) {
1173
+ readinessEntry.remoteIssueId = result.issueNumber;
1174
+ if (effectiveTrackingId) linkRemoteIssue(this.cwd, result.issueNumber, effectiveTrackingId);
1175
+ }
1176
+ }
1177
+ issues.push(readinessEntry);
1178
+
1179
+ const state: EpicState = {
1180
+ name,
1181
+ trackingIssueId: effectiveTrackingId,
1182
+ epicId: null,
1183
+ slices: [slice],
1184
+ issues,
1185
+ status: "planning",
1186
+ currentIssueIndex: 0,
1187
+ createdAt: new Date().toISOString(),
1188
+ };
1189
+
1190
+ this.state = state;
1191
+ saveEpicState(this.cwd, state);
1192
+ return state;
1193
+ }
1194
+
1195
+ async abortEpic(): Promise<void> {
1196
+ this.state = null;
1197
+ try {
1198
+ const p = join(this.cwd, ".pi/.guardian-epic-state.json");
1199
+ if (existsSync(p)) unlinkSync(p);
1200
+ } catch { /* ignore */ }
1201
+ }
1202
+ }
1203
+
1204
+ // ── Extension ──
1205
+
1206
+ export default function (pi: ExtensionAPI) {
1207
+ let manager: EpicManager | null = null;
1208
+
1209
+ function findFlag(tokens: string[], prefix: string): string | undefined {
1210
+ const eqMatch = tokens.find((a) => a.startsWith(`${prefix}=`));
1211
+ if (eqMatch) return eqMatch.split("=").slice(1).join("=");
1212
+ const idx = tokens.indexOf(prefix);
1213
+ if (idx >= 0 && idx + 1 < tokens.length && !tokens[idx + 1].startsWith("--")) return tokens[idx + 1];
1214
+ return undefined;
1215
+ }
1216
+
1217
+ pi.registerCommand("architect", {
1218
+ description: "Orchestrate the full architecture-to-implementation process",
1219
+ handler: async (args, ctx) => {
1220
+ if (!manager) manager = new EpicManager(ctx.cwd);
1221
+ const raw = typeof args === "string" ? args : "";
1222
+ const tokens = raw ? raw.split(/\s+/).filter(Boolean) : [];
1223
+ if (tokens.length === 0) {
1224
+ ctx.ui.notify("Usage: /architect [--epic Name] [--tracking-issue N] | status | next-epic | abort", "info");
1225
+ return;
1226
+ }
1227
+ const action = tokens[0];
1228
+
1229
+ if (action === "status" || action === "") {
1230
+ const state = manager.getState();
1231
+ ctx.ui.notify(formatEpicStatus(state), "info");
1232
+ return;
1233
+ }
1234
+
1235
+ if (action === "abort") {
1236
+ await manager.abortEpic();
1237
+ ctx.ui.notify("Epic aborted", "error");
1238
+ return;
1239
+ }
1240
+
1241
+ if (action === "next-epic") {
1242
+ const moduleFiles = discoverModules(ctx.cwd);
1243
+ const slice = findNextLogicalSlice(ctx.cwd, moduleFiles);
1244
+ if (!slice) {
1245
+ ctx.ui.notify("No more architecture slices to implement.", "info");
1246
+ return;
1247
+ }
1248
+ ctx.ui.notify(`Next epic: ${slice.module} (${slice.nextLogicalSlice.length} components planned)`, "info");
1249
+ return;
1250
+ }
1251
+
1252
+ const epicName = findFlag(tokens, "--epic");
1253
+ const trackingIssueId = findFlag(tokens, "--tracking-issue");
1254
+
1255
+ if (!epicName) {
1256
+ ctx.ui.notify('Usage: /architect --epic "Epic Name" [--tracking-issue N]', "error");
1257
+ return;
1258
+ }
1259
+
1260
+ try {
1261
+ if (!epicName || epicName.trim() === "") {
1262
+ ctx.ui.notify('Usage: /architect --epic "Epic Name"', "error");
1263
+ return;
1264
+ }
1265
+
1266
+ const state = await manager.startEpic(ctx, epicName, trackingIssueId);
1267
+
1268
+ if (!state || !state.slices || state.slices.length === 0) {
1269
+ ctx.ui.notify("Failed to discover architecture components. Check .pi/architecture/modules/.", "error");
1270
+ return;
1271
+ }
1272
+
1273
+ const slice = state.slices[0];
1274
+ const components = slice.nextLogicalSlice || [];
1275
+
1276
+ if (components.length === 0) {
1277
+ ctx.ui.notify("No planned components found in architecture module.", "error");
1278
+ return;
1279
+ }
1280
+
1281
+ const items = (state.issues || []).map((i) => i.id);
1282
+ if (items.length === 0) {
1283
+ ctx.ui.notify("Failed to generate issues.", "error");
1284
+ return;
1285
+ }
1286
+
1287
+ // Initialize git if needed
1288
+ try {
1289
+ const gitCheck = runScript(ctx.cwd, "git rev-parse --git-dir 2>/dev/null");
1290
+ if (gitCheck.exitCode !== 0) {
1291
+ runScript(ctx.cwd, "git init");
1292
+ runScript(ctx.cwd, "git add .");
1293
+ runScript(ctx.cwd, 'git commit -m "Initial Guardian scaffold"');
1294
+ }
1295
+ } catch { /* ignore */ }
1296
+
1297
+ // Remove stale pipeline state so the new one takes effect
1298
+ try {
1299
+ const oldPipelinePath = join(ctx.cwd, ".pi/.guardian-pipeline-state.json");
1300
+ if (existsSync(oldPipelinePath)) unlinkSync(oldPipelinePath);
1301
+ } catch { /* ignore */ }
1302
+
1303
+ // Write pipeline state directly (ctx.tools not available in command handlers)
1304
+ const pipelineId = `PL-${String(Math.floor(Math.random() * 10000)).padStart(4, "0")}`;
1305
+ const pipelineState = {
1306
+ id: pipelineId,
1307
+ name: epicName,
1308
+ items,
1309
+ steps: [
1310
+ { name: "implement", prompt: ".pi/prompts/issue-implementation-series.md", acceptance: { type: "validator", validators: ["ci"] } },
1311
+ { name: "validate", acceptance: { type: "validator", validators: ["ci", "tests", "security"] } },
1312
+ { name: "create-mr", prompt: ".pi/prompts/issue-closeout.md", acceptance: { type: "none" } },
1313
+ { name: "merge", prompt: ".pi/prompts/issue-merge.md", acceptance: { type: "validator", validators: ["ci", "canonical"] } },
1314
+ ],
1315
+ currentItemIndex: 0,
1316
+ currentStepIndex: 0,
1317
+ status: "running",
1318
+ retryCount: 0,
1319
+ results: [],
1320
+ mergeOnValid: true,
1321
+ createdAt: new Date().toISOString(),
1322
+ updatedAt: new Date().toISOString(),
1323
+ };
1324
+ const pipelineDir = dirname(join(ctx.cwd, ".pi/.guardian-pipeline-state.json"));
1325
+ if (!existsSync(pipelineDir)) mkdirSync(pipelineDir, { recursive: true });
1326
+ writeFileSync(join(ctx.cwd, ".pi/.guardian-pipeline-state.json"), JSON.stringify(pipelineState, null, 2));
1327
+
1328
+ const repository = readRepository(ctx.cwd) || "";
1329
+ const baseUrl = getGitBaseUrl(readRepoTool(ctx.cwd));
1330
+ const trackingUrl = state.trackingIssueId && repository
1331
+ ? `\n**Tracking issue:** ${baseUrl}/${repository}/issues/${state.trackingIssueId}`
1332
+ : "";
1333
+
1334
+ const firstItem = items[0];
1335
+ const issueFilename = `${firstItem}.md`.replace(/\//g, "-");
1336
+ const issuePath = join(ctx.cwd, ".pi/issues", issueFilename);
1337
+
1338
+ let issueContent = "";
1339
+ try {
1340
+ if (existsSync(issuePath)) {
1341
+ issueContent = readFileSync(issuePath, "utf-8").replace(/^---[\s\S]*?---\n/, "").trim();
1342
+ }
1343
+ } catch { /* ignore */ }
1344
+
1345
+ const instructions = [
1346
+ `Epic "${epicName}" started with ${items.length} issues across ${components.length} components.${trackingUrl}`,
1347
+ "",
1348
+ `Pipeline \`${pipelineId}\` created: ${items.length} items × 4 steps (implement → validate → create-mr → merge)`,
1349
+ `**Current:** Item "${firstItem}" → Step: implement`,
1350
+ "",
1351
+ "**Available pipeline tools:**",
1352
+ "- `pipeline_next_task` — get full context for current item+step",
1353
+ "- `pipeline_run_acceptance` — run validators for current step",
1354
+ "- `pipeline_advance` — mark step passed, move to next",
1355
+ "- `pipeline_fail` — mark step failed with reason",
1356
+ "- `pipeline_status` — check overall progress",
1357
+ "",
1358
+ "**Workflow per item:**",
1359
+ "1. Create branch: `feat/<issue-id>`",
1360
+ "2. Implement the component according to the issue context below",
1361
+ "3. Run `pipeline_run_acceptance` to validate your work",
1362
+ "4. Call `pipeline_advance` to move to the next step",
1363
+ "5. Pipeline auto-advances through: implement → validate → create-mr → merge",
1364
+ "",
1365
+ "---",
1366
+ "",
1367
+ "## Issue Context",
1368
+ "",
1369
+ issueContent || `Review .pi/issues/${issueFilename} for full details.`,
1370
+ ].join("\n");
1371
+
1372
+ pi.sendMessage(
1373
+ { content: instructions, display: true },
1374
+ { deliverAs: "followUp", triggerTurn: true },
1375
+ );
1376
+ return;
1377
+ } catch (e) {
1378
+ ctx.ui.notify(`Architect error: ${e}`, "error");
1379
+ }
1380
+ },
1381
+ });
1382
+
1383
+ pi.registerTool({
1384
+ name: "architect_status",
1385
+ label: "Architect Status",
1386
+ description: "Show the current epic status and progress.",
1387
+ parameters: { type: "object", properties: {} },
1388
+ async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
1389
+ if (!manager) manager = new EpicManager(ctx.cwd);
1390
+ const state = manager.getState();
1391
+ return { content: [{ type: "text", text: formatEpicStatus(state) }] };
1392
+ },
1393
+ });
1394
+
1395
+ pi.registerTool({
1396
+ name: "architect_discover",
1397
+ label: "Architect Discover",
1398
+ description: "Discover architecture modules and find the next logical slice.",
1399
+ parameters: { type: "object", properties: {} },
1400
+ async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
1401
+ const moduleFiles = discoverModules(ctx.cwd);
1402
+ if (moduleFiles.length === 0) {
1403
+ return { content: [{ type: "text", text: "No architecture modules found in .pi/architecture/modules/." }] };
1404
+ }
1405
+ const lines = ["## Architecture Modules\n"];
1406
+ for (const file of moduleFiles) {
1407
+ const components = parseModuleFile(join(ctx.cwd, ".pi/architecture/modules", file));
1408
+ const planned = components.filter((c) => c.status === "planned");
1409
+ lines.push(`### ${file.replace(".md", "")}`);
1410
+ lines.push(` Components: ${components.length} (${planned.length} planned)`);
1411
+ if (planned.length > 0) {
1412
+ lines.push(" Next slice:");
1413
+ for (const c of planned) lines.push(` - ${c.name}`);
1414
+ }
1415
+ lines.push("");
1416
+ }
1417
+ const slice = findNextLogicalSlice(ctx.cwd, moduleFiles);
1418
+ if (slice) {
1419
+ lines.push(`**Recommended next epic:** ${slice.module}`);
1420
+ lines.push(`Components: ${slice.nextLogicalSlice.map((c: ModuleComponent) => c.name).join(", ")}`);
1421
+ }
1422
+ return { content: [{ type: "text", text: lines.join("\n") }] };
1423
+ },
1424
+ });
1425
+ }