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.
- package/LICENSE +21 -0
- package/README.md +463 -0
- package/dist/cli.js +12958 -0
- package/dist/exports.js +12765 -0
- package/package.json +61 -0
- package/templates/languages/go-patterns.md +411 -0
- package/templates/languages/java-patterns.md +545 -0
- package/templates/languages/python-patterns.md +312 -0
- package/templates/languages/rust-patterns.md +232 -0
- package/templates/languages/typescript-patterns.md +313 -0
- package/templates/pi/INDEX.md +437 -0
- package/templates/pi/agent/AGENTS.md +166 -0
- package/templates/pi/agents/architecture-coordinator.md +40 -0
- package/templates/pi/agents/architecture-validator.md +36 -0
- package/templates/pi/agents/bootstrap-implementer.md +37 -0
- package/templates/pi/agents/issue-factory.md +31 -0
- package/templates/pi/agents/operations-validator.md +32 -0
- package/templates/pi/agents/security-validator.md +33 -0
- package/templates/pi/architecture/CHANGELOG.md +78 -0
- package/templates/pi/architecture/decisions/ADR-template.md +76 -0
- package/templates/pi/architecture/diagrams/system-overview.md +159 -0
- package/templates/pi/architecture/modules/module-template.md +152 -0
- package/templates/pi/context/checklists.md +91 -0
- package/templates/pi/context/domain-workflow.md +180 -0
- package/templates/pi/context/output-formats.md +135 -0
- package/templates/pi/context/patterns-base.md +47 -0
- package/templates/pi/context/patterns.md +38 -0
- package/templates/pi/context/project.md +88 -0
- package/templates/pi/domain/exploration.md +93 -0
- package/templates/pi/domain/ubiquitous-language.md +24 -0
- package/templates/pi/extensions/architect-lib/generators.ts +528 -0
- package/templates/pi/extensions/architect-lib/helpers.ts +344 -0
- package/templates/pi/extensions/architect-lib/types.ts +117 -0
- package/templates/pi/extensions/architect.ts +1425 -0
- package/templates/pi/extensions/ask-user-question.ts +579 -0
- package/templates/pi/extensions/bash-guard.ts +556 -0
- package/templates/pi/extensions/config-reload.ts +115 -0
- package/templates/pi/extensions/coordinator.ts +304 -0
- package/templates/pi/extensions/curator.ts +543 -0
- package/templates/pi/extensions/domain-explorer.ts +990 -0
- package/templates/pi/extensions/filechanges.ts +656 -0
- package/templates/pi/extensions/goal-loop.ts +717 -0
- package/templates/pi/extensions/hooks.ts +452 -0
- package/templates/pi/extensions/kanban.ts +547 -0
- package/templates/pi/extensions/pipeline.ts +1246 -0
- package/templates/pi/extensions/plan-mode.ts +209 -0
- package/templates/pi/extensions/project-scaffolder.ts +284 -0
- package/templates/pi/extensions/read-only-mode.ts +192 -0
- package/templates/pi/extensions/redaction.ts +128 -0
- package/templates/pi/extensions/session-persistence.ts +174 -0
- package/templates/pi/extensions/slash-commands.ts +229 -0
- package/templates/pi/extensions/snippets.ts +201 -0
- package/templates/pi/extensions/validation-runner.ts +100 -0
- package/templates/pi/github/agents/architecture-coordinator.agent.md +27 -0
- package/templates/pi/github/agents/architecture-validator.agent.md +20 -0
- package/templates/pi/github/agents/bootstrap-implementer.agent.md +20 -0
- package/templates/pi/github/agents/epic-planner.agent.md +70 -0
- package/templates/pi/github/agents/issue-factory.agent.md +20 -0
- package/templates/pi/github/agents/operations-validator.agent.md +20 -0
- package/templates/pi/github/agents/security-validator.agent.md +20 -0
- package/templates/pi/github/copilot/settings.json +16 -0
- package/templates/pi/github/copilot-instructions.md +155 -0
- package/templates/pi/github/instructions/architecture.instructions.md +71 -0
- package/templates/pi/github/instructions/validation.instructions.md +69 -0
- package/templates/pi/github/workflows/01-planning-workflow.md +75 -0
- package/templates/pi/github/workflows/02-issue-generation-workflow.md +69 -0
- package/templates/pi/github/workflows/03-implementation-workflow.md +80 -0
- package/templates/pi/github/workflows/04-validation-workflow.md +68 -0
- package/templates/pi/preflight_report.json +99 -0
- package/templates/pi/prompts/blueprint-update.md +262 -0
- package/templates/pi/prompts/blueprint-validate.md +218 -0
- package/templates/pi/prompts/bug-fix.md +50 -0
- package/templates/pi/prompts/ci-blueprint.md +131 -0
- package/templates/pi/prompts/context-refresh.md +258 -0
- package/templates/pi/prompts/epic-plan.md +356 -0
- package/templates/pi/prompts/epic-template.md +186 -0
- package/templates/pi/prompts/feature-development.md +131 -0
- package/templates/pi/prompts/git-issues.md +243 -0
- package/templates/pi/prompts/hotfix.md +71 -0
- package/templates/pi/prompts/issue-closeout.md +342 -0
- package/templates/pi/prompts/issue-draft.md +218 -0
- package/templates/pi/prompts/issue-implementation-series.md +173 -0
- package/templates/pi/prompts/issue-merge.md +372 -0
- package/templates/pi/prompts/issue-template-set.md +392 -0
- package/templates/pi/prompts/issue-template.md +207 -0
- package/templates/pi/prompts/pattern-extract.md +269 -0
- package/templates/pi/prompts/plan-to-issues.md +430 -0
- package/templates/pi/prompts/refactoring.md +82 -0
- package/templates/pi/prompts/scope-analyzer.md +254 -0
- package/templates/pi/prompts/sync-check.md +230 -0
- package/templates/pi/scripts/categorize-issues.sh +171 -0
- package/templates/pi/scripts/ci/check_architecture_conformance.sh +748 -0
- package/templates/pi/scripts/ci/check_planning_packet.py +228 -0
- package/templates/pi/scripts/ci/run_hardening_stages.sh +264 -0
- package/templates/pi/scripts/ci/run_preflight.sh +649 -0
- package/templates/pi/scripts/ci/run_stage.sh +193 -0
- package/templates/pi/scripts/ci/stage_docs_policy.sh +82 -0
- package/templates/pi/scripts/ci/stage_integration.sh +3 -0
- package/templates/pi/scripts/ci/stage_lint.sh +130 -0
- package/templates/pi/scripts/ci/stage_migration_verify.sh +3 -0
- package/templates/pi/scripts/ci/stage_package_build.sh +3 -0
- package/templates/pi/scripts/ci/stage_release_readiness.sh +3 -0
- package/templates/pi/scripts/ci/stage_remaining.sh +242 -0
- package/templates/pi/scripts/ci/stage_security.sh +3 -0
- package/templates/pi/scripts/ci/stage_static_analysis.sh +114 -0
- package/templates/pi/scripts/ci/stage_unit.sh +119 -0
- package/templates/pi/scripts/ci/validate_agent_output.py +181 -0
- package/templates/pi/scripts/ci/validate_agent_output.sh +243 -0
- package/templates/pi/scripts/create-feature-branch.sh +136 -0
- package/templates/pi/scripts/create-mr.sh +117 -0
- package/templates/pi/scripts/fetch-issues.sh +131 -0
- package/templates/pi/scripts/generate-architecture.sh +161 -0
- package/templates/pi/scripts/git/close-epic.sh +69 -0
- package/templates/pi/scripts/git/close-issue.sh +35 -0
- package/templates/pi/scripts/git/create-tracking-issue.sh +174 -0
- package/templates/pi/scripts/git/link-issue-to-epic.sh +51 -0
- package/templates/pi/scripts/git/update-tracking-issue.sh +66 -0
- package/templates/pi/scripts/languages/go/validate-architecture.sh +139 -0
- package/templates/pi/scripts/languages/go/validate-canonical.sh +129 -0
- package/templates/pi/scripts/languages/go/validate-ci.sh +132 -0
- package/templates/pi/scripts/languages/go/validate-integration.sh +156 -0
- package/templates/pi/scripts/languages/go/validate-operations.sh +146 -0
- package/templates/pi/scripts/languages/go/validate-security.sh +135 -0
- package/templates/pi/scripts/languages/go/validate-tests.sh +125 -0
- package/templates/pi/scripts/languages/java/validate-annotations.sh +185 -0
- package/templates/pi/scripts/languages/java/validate-architecture.sh +159 -0
- package/templates/pi/scripts/languages/java/validate-canonical.sh +114 -0
- package/templates/pi/scripts/languages/java/validate-ci.sh +269 -0
- package/templates/pi/scripts/languages/java/validate-integration.sh +149 -0
- package/templates/pi/scripts/languages/java/validate-operations.sh +142 -0
- package/templates/pi/scripts/languages/java/validate-security.sh +174 -0
- package/templates/pi/scripts/languages/java/validate-spring-architecture.sh +227 -0
- package/templates/pi/scripts/languages/java/validate-tests.sh +159 -0
- package/templates/pi/scripts/languages/python/validate-architecture.sh +99 -0
- package/templates/pi/scripts/languages/python/validate-canonical.sh +124 -0
- package/templates/pi/scripts/languages/python/validate-ci.sh +130 -0
- package/templates/pi/scripts/languages/python/validate-integration.sh +119 -0
- package/templates/pi/scripts/languages/python/validate-operations.sh +107 -0
- package/templates/pi/scripts/languages/python/validate-security.sh +68 -0
- package/templates/pi/scripts/languages/python/validate-tests.sh +102 -0
- package/templates/pi/scripts/languages/rust/validate-architecture.sh +188 -0
- package/templates/pi/scripts/languages/rust/validate-canonical.sh +146 -0
- package/templates/pi/scripts/languages/rust/validate-ci.sh +147 -0
- package/templates/pi/scripts/languages/rust/validate-integration.sh +149 -0
- package/templates/pi/scripts/languages/rust/validate-operations.sh +154 -0
- package/templates/pi/scripts/languages/rust/validate-security.sh +153 -0
- package/templates/pi/scripts/languages/rust/validate-tests.sh +150 -0
- package/templates/pi/scripts/languages/typescript/validate-architecture.sh +157 -0
- package/templates/pi/scripts/languages/typescript/validate-canonical.sh +135 -0
- package/templates/pi/scripts/languages/typescript/validate-ci.sh +244 -0
- package/templates/pi/scripts/languages/typescript/validate-integration.sh +166 -0
- package/templates/pi/scripts/languages/typescript/validate-operations.sh +162 -0
- package/templates/pi/scripts/languages/typescript/validate-security.sh +169 -0
- package/templates/pi/scripts/languages/typescript/validate-tests.sh +158 -0
- package/templates/pi/scripts/merge-mr.sh +95 -0
- package/templates/pi/scripts/mr-validation.sh +228 -0
- package/templates/pi/scripts/validate-architecture-readiness.sh +153 -0
- package/templates/pi/scripts/validate-architecture.sh +22 -0
- package/templates/pi/scripts/validate-canonical.sh +22 -0
- package/templates/pi/scripts/validate-ci.sh +37 -0
- package/templates/pi/scripts/validate-integration.sh +22 -0
- package/templates/pi/scripts/validate-operations.sh +22 -0
- package/templates/pi/scripts/validate-security.sh +22 -0
- package/templates/pi/scripts/validate-tests.sh +22 -0
- package/templates/pi/scripts/validate-ubiquitous-language.sh +291 -0
- package/templates/pi/scripts/validation-cache.sh +139 -0
- package/templates/pi/skills/agents/architecture-coordinator.md +178 -0
- package/templates/pi/skills/agents/architecture-generator.md +79 -0
- package/templates/pi/skills/agents/architecture-validator.md +74 -0
- package/templates/pi/skills/agents/ci-mr-validator.md +46 -0
- package/templates/pi/skills/agents/code-developer.md +76 -0
- package/templates/pi/skills/agents/commit.md +46 -0
- package/templates/pi/skills/agents/curator.md +71 -0
- package/templates/pi/skills/agents/debug.md +59 -0
- package/templates/pi/skills/agents/documentation-maintainer.md +36 -0
- package/templates/pi/skills/agents/goal-loop.md +125 -0
- package/templates/pi/skills/agents/hooks.md +122 -0
- package/templates/pi/skills/agents/integration-validator.md +35 -0
- package/templates/pi/skills/agents/issue-creator.md +75 -0
- package/templates/pi/skills/agents/issue-factory.md +143 -0
- package/templates/pi/skills/agents/kanban.md +85 -0
- package/templates/pi/skills/agents/land.md +59 -0
- package/templates/pi/skills/agents/operations-validator.md +43 -0
- package/templates/pi/skills/agents/pipeline.md +113 -0
- package/templates/pi/skills/agents/plan-mode.md +77 -0
- package/templates/pi/skills/agents/pull.md +40 -0
- package/templates/pi/skills/agents/push.md +41 -0
- package/templates/pi/skills/agents/security-validator.md +85 -0
- package/templates/pi/skills/agents/session-persistence.md +119 -0
- package/templates/pi/skills/agents/slash-commands.md +117 -0
- package/templates/pi/skills/agents/snippets.md +99 -0
- package/templates/pi/skills/agents/subagent-registry.md +78 -0
- package/templates/pi/skills/agents/test-validator.md +42 -0
- package/templates/pi/skills/validators/architecture-validator.md +43 -0
- package/templates/pi/skills/validators/ci-validator.md +27 -0
- package/templates/pi/skills/validators/context-compaction.md +73 -0
- package/templates/pi/skills/validators/integration-validator.md +38 -0
- package/templates/pi/skills/validators/model-registry.md +96 -0
- package/templates/pi/skills/validators/operations-validator.md +34 -0
- package/templates/pi/skills/validators/security-guards.md +72 -0
- package/templates/pi/skills/validators/security-validator.md +41 -0
- package/templates/pi/skills/validators/system-prompt-tiers.md +69 -0
- package/templates/pi/skills/validators/test-validator.md +33 -0
- package/templates/pi/types.ts +62 -0
- package/templates/pi/validators/README.md +93 -0
- package/templates/pi/validators/default.toml +209 -0
- package/templates/pi/validators/spring.toml +142 -0
- package/templates/pi/workpad.md +53 -0
- package/templates/project/java/Dockerfile +14 -0
- package/templates/project/java/README.md +40 -0
- package/templates/project/java/pom.xml +132 -0
- package/templates/project/typescript/Dockerfile +15 -0
- package/templates/project/typescript/README.md +29 -0
- package/templates/project/typescript/package.json +23 -0
- package/templates/project/typescript/tsconfig.json +21 -0
- package/templates/workflow.md +123 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { execFileSync, execSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { ExtensionContext, EpicState, ModuleComponent, ArchitectureSlice } from "./types.ts";
|
|
5
|
+
// ── Constants ──
|
|
6
|
+
|
|
7
|
+
export const EPIC_STATE_KEY = ".pi/.guardian-epic-state.json";
|
|
8
|
+
export const ARCH_MODULES_DIR = ".pi/architecture/modules";
|
|
9
|
+
export const ISSUES_DIR = ".pi/issues";
|
|
10
|
+
|
|
11
|
+
// ── Helpers ──
|
|
12
|
+
|
|
13
|
+
export function log(ctx: ExtensionContext, message: string, level = "info") {
|
|
14
|
+
ctx.ui.notify(message, level);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function runScript(cwd: string, script: string): { exitCode: number; stdout: string } {
|
|
18
|
+
try {
|
|
19
|
+
const stdout = execSync(`bash -c "${script}"`, { cwd, timeout: 120_000, encoding: "utf-8" });
|
|
20
|
+
return { exitCode: 0, stdout };
|
|
21
|
+
} catch (e: unknown) {
|
|
22
|
+
const err = e as { status?: number; stdout?: string; message?: string };
|
|
23
|
+
return { exitCode: err.status ?? 1, stdout: err.stdout ?? err.message ?? "" };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Read repoTool from guardian-manifest.json (defaults to "gh")
|
|
28
|
+
export function readRepoTool(cwd: string): string {
|
|
29
|
+
try {
|
|
30
|
+
const manifestPath = join(cwd, "guardian-manifest.json");
|
|
31
|
+
if (existsSync(manifestPath)) {
|
|
32
|
+
const raw = readFileSync(manifestPath, "utf-8");
|
|
33
|
+
const manifest = JSON.parse(raw) as { repoTool?: string };
|
|
34
|
+
if (manifest.repoTool === "glab") return "glab";
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
// fall through to default
|
|
38
|
+
}
|
|
39
|
+
return "gh";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Read the repository slug (owner/repo) from guardian-manifest.json
|
|
43
|
+
export function readRepository(cwd: string): string | null {
|
|
44
|
+
try {
|
|
45
|
+
const manifestPath = join(cwd, "guardian-manifest.json");
|
|
46
|
+
if (existsSync(manifestPath)) {
|
|
47
|
+
const raw = readFileSync(manifestPath, "utf-8");
|
|
48
|
+
const manifest = JSON.parse(raw) as {
|
|
49
|
+
repository?: string;
|
|
50
|
+
templateContext?: { repository?: string };
|
|
51
|
+
};
|
|
52
|
+
if (manifest.repository) return manifest.repository;
|
|
53
|
+
if (manifest.templateContext?.repository)
|
|
54
|
+
return manifest.templateContext.repository;
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// ignore
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get Git platform base URL. For GitLab, tries to detect self-hosted instances.
|
|
64
|
+
*/
|
|
65
|
+
export function getGitBaseUrl(repoTool: string): string {
|
|
66
|
+
if (repoTool === "glab") {
|
|
67
|
+
try {
|
|
68
|
+
const uri = execSync("glab config get gitlab_uri 2>/dev/null", {
|
|
69
|
+
encoding: "utf-8",
|
|
70
|
+
}).trim();
|
|
71
|
+
if (uri) return uri.replace(/\/+$/, "");
|
|
72
|
+
} catch {
|
|
73
|
+
// fall through to default
|
|
74
|
+
}
|
|
75
|
+
return "https://gitlab.com";
|
|
76
|
+
}
|
|
77
|
+
return "https://github.com";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function commandExists(cmd: string): boolean {
|
|
81
|
+
try {
|
|
82
|
+
execSync(`command -v ${cmd}`, { stdio: "ignore" });
|
|
83
|
+
return true;
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Try to create a remote GitHub/GitLab issue via the shell script wrapper.
|
|
90
|
+
// Uses execFileSync to avoid shell quoting issues with nested commands.
|
|
91
|
+
export function createRemoteIssue(
|
|
92
|
+
cwd: string,
|
|
93
|
+
title: string,
|
|
94
|
+
bodyFilePath: string,
|
|
95
|
+
labels: string,
|
|
96
|
+
repository?: string,
|
|
97
|
+
): { success: boolean; issueNumber: string | null; error?: string } {
|
|
98
|
+
const createScript = join(cwd, ".pi/scripts/git/create-tracking-issue.sh");
|
|
99
|
+
if (!existsSync(createScript)) {
|
|
100
|
+
return { success: false, issueNumber: null, error: "create-tracking-issue.sh not found" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const args: string[] = [
|
|
104
|
+
createScript,
|
|
105
|
+
"--title",
|
|
106
|
+
title,
|
|
107
|
+
"--body-file",
|
|
108
|
+
bodyFilePath,
|
|
109
|
+
"--labels",
|
|
110
|
+
labels,
|
|
111
|
+
];
|
|
112
|
+
if (repository) args.push("--repo", repository);
|
|
113
|
+
|
|
114
|
+
let stdout = "";
|
|
115
|
+
let exitCode = 0;
|
|
116
|
+
try {
|
|
117
|
+
stdout = execFileSync("bash", args, {
|
|
118
|
+
cwd,
|
|
119
|
+
timeout: 120_000,
|
|
120
|
+
encoding: "utf-8",
|
|
121
|
+
});
|
|
122
|
+
} catch (e: unknown) {
|
|
123
|
+
const err = e as { status?: number; stdout?: string; message?: string };
|
|
124
|
+
exitCode = err.status ?? 1;
|
|
125
|
+
stdout = err.stdout ?? err.message ?? "";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (exitCode !== 0) {
|
|
129
|
+
return { success: false, issueNumber: null, error: stdout };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const numberMatch = stdout.match(/TRACKING_ID=(\d+)/);
|
|
133
|
+
if (numberMatch) {
|
|
134
|
+
return { success: true, issueNumber: numberMatch[1] };
|
|
135
|
+
}
|
|
136
|
+
const urlMatch = stdout.match(/#(\d+)/);
|
|
137
|
+
if (urlMatch) {
|
|
138
|
+
return { success: true, issueNumber: urlMatch[1] };
|
|
139
|
+
}
|
|
140
|
+
return { success: false, issueNumber: null, error: "Could not parse issue number" };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Ensure the GitHub/GitLab repository exists and local git remote is configured.
|
|
144
|
+
// Returns the repository slug if remote is ready, empty string if not available.
|
|
145
|
+
export function ensureRemoteRepo(
|
|
146
|
+
cwd: string,
|
|
147
|
+
repository: string,
|
|
148
|
+
epicName: string,
|
|
149
|
+
repoTool: string,
|
|
150
|
+
): string {
|
|
151
|
+
// Check if remote already exists via git remote
|
|
152
|
+
const remoteCheck = runScript(cwd, "git remote get-url origin 2>/dev/null");
|
|
153
|
+
if (remoteCheck.exitCode === 0) {
|
|
154
|
+
return repository;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Remote not configured locally — ensure the remote repo exists on GitHub/GitLab
|
|
158
|
+
if (repoTool === "gh") {
|
|
159
|
+
runScript(
|
|
160
|
+
cwd,
|
|
161
|
+
`gh repo create "${repository}" --private --description "Epic: ${epicName}" 2>&1`,
|
|
162
|
+
);
|
|
163
|
+
// Remove stale origin if it exists but points nowhere useful
|
|
164
|
+
runScript(cwd, "git remote remove origin 2>/dev/null");
|
|
165
|
+
const httpsUrl = `https://github.com/${repository}.git`;
|
|
166
|
+
runScript(cwd, `git remote add origin "${httpsUrl}"`);
|
|
167
|
+
return repository;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// GitLab path — detect self-hosted base URL from glab config
|
|
171
|
+
const glabBaseUrl = getGitBaseUrl("glab");
|
|
172
|
+
runScript(
|
|
173
|
+
cwd,
|
|
174
|
+
`glab repo create "${repository}" --private --description "Epic: ${epicName}" 2>&1`,
|
|
175
|
+
);
|
|
176
|
+
runScript(cwd, "git remote remove origin 2>/dev/null");
|
|
177
|
+
const httpsUrl = `${glabBaseUrl}/${repository}.git`;
|
|
178
|
+
runScript(cwd, `git remote add origin "${httpsUrl}"`);
|
|
179
|
+
return repository;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Link a remote issue to the epic tracking issue
|
|
183
|
+
export function linkRemoteIssue(
|
|
184
|
+
cwd: string,
|
|
185
|
+
issueId: string,
|
|
186
|
+
epicId: string,
|
|
187
|
+
): { success: boolean; error?: string } {
|
|
188
|
+
const linkScript = join(cwd, ".pi/scripts/git/link-issue-to-epic.sh");
|
|
189
|
+
if (!existsSync(linkScript)) {
|
|
190
|
+
return { success: false, error: "link-issue-to-epic.sh not found" };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const safeIssue = issueId.replace(/[^a-zA-Z0-9 _\-.]/g, "");
|
|
194
|
+
const safeEpic = epicId.replace(/[^a-zA-Z0-9 _\-.]/g, "");
|
|
195
|
+
|
|
196
|
+
const cmd = `bash "${linkScript}" --issue-id "${safeIssue}" --epic-id "${safeEpic}"`;
|
|
197
|
+
const result = runScript(cwd, cmd);
|
|
198
|
+
if (result.exitCode !== 0) {
|
|
199
|
+
return { success: false, error: result.stdout };
|
|
200
|
+
}
|
|
201
|
+
return { success: true };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── Architecture Discovery ──
|
|
205
|
+
|
|
206
|
+
export function readGroupId(cwd: string): string {
|
|
207
|
+
// Try pom.xml
|
|
208
|
+
const pomPath = join(cwd, "pom.xml");
|
|
209
|
+
try {
|
|
210
|
+
const pom = readFileSync(pomPath, "utf-8");
|
|
211
|
+
const match = pom.match(/<groupId>([^<]+)<\/groupId>/);
|
|
212
|
+
if (match && match[1] !== "com.example") return match[1];
|
|
213
|
+
} catch {}
|
|
214
|
+
// Try build.gradle
|
|
215
|
+
const gradlePath = join(cwd, "build.gradle");
|
|
216
|
+
try {
|
|
217
|
+
const gradle = readFileSync(gradlePath, "utf-8");
|
|
218
|
+
const match = gradle.match(/group\s*=\s*['"]([^'"]+)['"]/);
|
|
219
|
+
if (match) return match[1];
|
|
220
|
+
} catch {}
|
|
221
|
+
return "com.example";
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function findModuleByName(cwd: string, name: string): string | null {
|
|
225
|
+
const files = discoverModules(cwd);
|
|
226
|
+
const nameLower = name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
227
|
+
for (const f of files) {
|
|
228
|
+
const key = f.replace(".md", "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
229
|
+
if (key === nameLower || nameLower.includes(key) || key.includes(nameLower)) {
|
|
230
|
+
return f;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function discoverModules(cwd: string): string[] {
|
|
237
|
+
const dir = join(cwd, ARCH_MODULES_DIR);
|
|
238
|
+
if (!existsSync(dir)) return [];
|
|
239
|
+
try {
|
|
240
|
+
return readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
241
|
+
} catch {
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function parseModuleFile(filePath: string): ModuleComponent[] {
|
|
247
|
+
if (!existsSync(filePath)) return [];
|
|
248
|
+
const content = readFileSync(filePath, "utf-8");
|
|
249
|
+
const components: ModuleComponent[] = [];
|
|
250
|
+
|
|
251
|
+
const lines = content.split("\n");
|
|
252
|
+
let inComponentSection = false;
|
|
253
|
+
let inDetailsSection = false;
|
|
254
|
+
let currentName = "";
|
|
255
|
+
let currentStatus = "";
|
|
256
|
+
let currentDesc = "";
|
|
257
|
+
let currentDeps: string[] = [];
|
|
258
|
+
|
|
259
|
+
function saveCurrent() {
|
|
260
|
+
if (currentName) {
|
|
261
|
+
// Default to planned if no explicit status found
|
|
262
|
+
const status = currentStatus || "planned";
|
|
263
|
+
const desc = currentDesc || `${currentName} component`;
|
|
264
|
+
components.push({
|
|
265
|
+
name: currentName,
|
|
266
|
+
status: status as ModuleComponent["status"],
|
|
267
|
+
description: desc.trim(),
|
|
268
|
+
dependencies: currentDeps.length > 0 ? currentDeps : ["none"],
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
for (const line of lines) {
|
|
274
|
+
const trimmed = line.trim();
|
|
275
|
+
|
|
276
|
+
// Enter component section (supports "## Components", "## Component Details", "## Component")
|
|
277
|
+
if (trimmed.match(/^##\s+Components?/i) || trimmed.match(/^##\s+Component\s+Details/i)) {
|
|
278
|
+
inComponentSection = true;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Leave component section on next top-level section
|
|
283
|
+
if (inComponentSection && trimmed.match(/^##\s+/) && !trimmed.match(/^##\s+Components?/i)) {
|
|
284
|
+
saveCurrent();
|
|
285
|
+
currentName = "";
|
|
286
|
+
currentStatus = "";
|
|
287
|
+
currentDesc = "";
|
|
288
|
+
currentDeps = [];
|
|
289
|
+
inComponentSection = false;
|
|
290
|
+
inDetailsSection = false;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Component heading (###) — start a new component entry
|
|
295
|
+
if (inComponentSection && trimmed.match(/^###\s+/)) {
|
|
296
|
+
// Skip non-component ### headings like "### Depends On" or "### Security"
|
|
297
|
+
const name = trimmed.replace(/^###\s+/, "");
|
|
298
|
+
if (name.match(/^(depends|security|testing|performance|error|change|data flow|responsibilities|overview|interfaces|inputs|outputs)/i)) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
saveCurrent();
|
|
302
|
+
currentName = name;
|
|
303
|
+
currentStatus = "";
|
|
304
|
+
currentDesc = "";
|
|
305
|
+
currentDeps = [];
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!currentName) continue;
|
|
310
|
+
|
|
311
|
+
if (trimmed.startsWith("status:")) {
|
|
312
|
+
currentStatus = trimmed.replace("status:", "").trim().toLowerCase();
|
|
313
|
+
} else if (trimmed.startsWith("depends:")) {
|
|
314
|
+
const depsStr = trimmed.replace("depends:", "").trim();
|
|
315
|
+
if (depsStr && depsStr !== "none" && depsStr !== "[TODO") {
|
|
316
|
+
currentDeps = depsStr.split(",").map((d) => d.trim()).filter(Boolean);
|
|
317
|
+
}
|
|
318
|
+
} else if (trimmed.startsWith("**Purpose:**")) {
|
|
319
|
+
currentDesc = trimmed.replace(/\*\*Purpose:\*\*\s*/, "").trim();
|
|
320
|
+
} else if (!currentDesc && trimmed.length > 10 && !trimmed.startsWith("#") && !trimmed.startsWith("-") && !trimmed.startsWith("|") && !trimmed.startsWith(">") && !trimmed.startsWith("```")) {
|
|
321
|
+
// Use first substantial sentence as description
|
|
322
|
+
currentDesc = trimmed.slice(0, 200);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
saveCurrent();
|
|
327
|
+
return components;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function findNextLogicalSlice(cwd: string, moduleFiles: string[]): ArchitectureSlice | null {
|
|
331
|
+
for (const moduleFile of moduleFiles) {
|
|
332
|
+
const components = parseModuleFile(join(cwd, ARCH_MODULES_DIR, moduleFile));
|
|
333
|
+
const planned = components.filter((c) => c.status === "planned");
|
|
334
|
+
if (planned.length > 0) {
|
|
335
|
+
return {
|
|
336
|
+
module: moduleFile.replace(".md", ""),
|
|
337
|
+
components,
|
|
338
|
+
nextLogicalSlice: planned,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architect Extension — Full Architecture-to-Implementation Pipeline
|
|
3
|
+
*
|
|
4
|
+
* OVERVIEW
|
|
5
|
+
* ========
|
|
6
|
+
* /architect is the planning engine that reads architecture module docs from
|
|
7
|
+
* .pi/architecture/modules/ and produces epics + issues for implementation.
|
|
8
|
+
* It is the bridge between domain exploration (/domain --explore) and
|
|
9
|
+
* implementation (/implement-series).
|
|
10
|
+
*
|
|
11
|
+
* PREREQUISITES
|
|
12
|
+
* ============
|
|
13
|
+
* - .pi/architecture/modules/*.md must exist (from /domain --architect-scaffold
|
|
14
|
+
* or manually created)
|
|
15
|
+
* - Git repository initialized (handled automatically if missing)
|
|
16
|
+
* - gh or glab CLI authenticated for remote issue creation (optional)
|
|
17
|
+
*
|
|
18
|
+
* COMMANDS
|
|
19
|
+
* =======
|
|
20
|
+
* /architect --epic "Name" [--tracking-issue N]
|
|
21
|
+
* Starts a new epic: discovers architecture modules, finds the next
|
|
22
|
+
* planned slice, generates issues, creates pipeline state, and sends
|
|
23
|
+
* implementation instructions to the agent via pi.sendMessage() with
|
|
24
|
+
* triggerTurn=true.
|
|
25
|
+
*
|
|
26
|
+
* /architect status
|
|
27
|
+
* Shows current epic state: module, component, pipeline progress,
|
|
28
|
+
* issues, and validators.
|
|
29
|
+
*
|
|
30
|
+
* /architect next-epic
|
|
31
|
+
* Shows which module and components should be implemented next,
|
|
32
|
+
* based on component status (planned vs implemented).
|
|
33
|
+
*
|
|
34
|
+
* /architect abort
|
|
35
|
+
* Cancels the current epic, cleans pipeline state.
|
|
36
|
+
*
|
|
37
|
+
* TOOLS (agent-callable)
|
|
38
|
+
* =====================
|
|
39
|
+
* architect_status — Returns current epic state and progress.
|
|
40
|
+
* architect_discover — Discovers modules and finds next logical slice.
|
|
41
|
+
*
|
|
42
|
+
* WORKFLOW
|
|
43
|
+
* =======
|
|
44
|
+
* 1. /domain --explore "intent" (discovery)
|
|
45
|
+
* 2. /domain --architect-scaffold <id> (generates modules)
|
|
46
|
+
* 3. /architect --epic "Name" (planning + issue gen)
|
|
47
|
+
* 4. Agent implements issues via pipeline
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
// ── Types ──
|
|
51
|
+
|
|
52
|
+
export type ExtensionContext = {
|
|
53
|
+
cwd: string;
|
|
54
|
+
ui: {
|
|
55
|
+
notify(message: string, level?: string): void;
|
|
56
|
+
setStatus(key: string, message: string | null): void;
|
|
57
|
+
confirm(title: string, message: string): Promise<boolean>;
|
|
58
|
+
};
|
|
59
|
+
tools: { execute(name: string, params: Record<string, unknown>): Promise<unknown> };
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export type ExtensionAPI = {
|
|
63
|
+
on(event: string, handler: (event: unknown, ctx: ExtensionContext) => void | Promise<void>): void;
|
|
64
|
+
registerTool(options: {
|
|
65
|
+
name: string;
|
|
66
|
+
label: string;
|
|
67
|
+
description: string;
|
|
68
|
+
parameters: unknown;
|
|
69
|
+
execute(
|
|
70
|
+
toolCallId: string,
|
|
71
|
+
params: Record<string, unknown>,
|
|
72
|
+
signal: AbortSignal,
|
|
73
|
+
onUpdate: (update: { type: string; message: string }) => void,
|
|
74
|
+
ctx: ExtensionContext,
|
|
75
|
+
): unknown | Promise<unknown>;
|
|
76
|
+
}): void;
|
|
77
|
+
registerCommand(
|
|
78
|
+
name: string,
|
|
79
|
+
options: {
|
|
80
|
+
description: string;
|
|
81
|
+
handler(args: string, ctx: ExtensionContext): unknown | Promise<unknown>;
|
|
82
|
+
},
|
|
83
|
+
): void;
|
|
84
|
+
sendMessage<T = unknown>(
|
|
85
|
+
message: { customType?: string; content: string; display?: boolean; details?: Record<string, unknown> },
|
|
86
|
+
options?: { deliverAs?: "steer" | "followUp" | "nextTurn"; triggerTurn?: boolean },
|
|
87
|
+
): void;
|
|
88
|
+
sendUserMessage(
|
|
89
|
+
content: string,
|
|
90
|
+
options?: { deliverAs?: "steer" | "followUp" },
|
|
91
|
+
): void;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type ModuleComponent = {
|
|
95
|
+
name: string;
|
|
96
|
+
status: "planned" | "in-progress" | "implemented" | "deprecated";
|
|
97
|
+
description: string;
|
|
98
|
+
dependencies: string[];
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export type ArchitectureSlice = {
|
|
102
|
+
module: string;
|
|
103
|
+
components: ModuleComponent[];
|
|
104
|
+
nextLogicalSlice: ModuleComponent[];
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type EpicState = {
|
|
108
|
+
name: string;
|
|
109
|
+
trackingIssueId: string | null;
|
|
110
|
+
epicId: string | null;
|
|
111
|
+
status: "planning" | "validating" | "publishing" | "executing" | "done" | "aborted";
|
|
112
|
+
slices: ArchitectureSlice[];
|
|
113
|
+
issues: { id: string; title: string; status: string; remoteIssueId?: string | null }[];
|
|
114
|
+
currentIssueIndex: number;
|
|
115
|
+
createdAt: string;
|
|
116
|
+
};
|
|
117
|
+
|