@geminix/gxpm 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/AGENTS.md +148 -0
- package/CANON.md +53 -0
- package/CLAUDE.md +60 -0
- package/CONTEXT.md +49 -0
- package/DEBUG.md +59 -0
- package/ISSUE_CONTEXT.md +25 -0
- package/README.md +143 -0
- package/VERSION +1 -0
- package/agents/cleanup-auditor/cleanup-auditor.md +56 -0
- package/agents/grill-master.md +26 -0
- package/agents/implementer.md +32 -0
- package/agents/review-army/accessibility-reviewer.md +54 -0
- package/agents/review-army/code-quality-reviewer.md +54 -0
- package/agents/review-army/security-reviewer.md +56 -0
- package/agents/review-army/spec-compliance-reviewer.md +51 -0
- package/agents/review-army/test-reviewer.md +55 -0
- package/agents/reviewer.md +59 -0
- package/agents/ship-audit-army/docs-auditor.md +53 -0
- package/agents/ship-audit-army/performance-auditor.md +52 -0
- package/agents/ship-audit-army/security-auditor.md +52 -0
- package/agents/specifier.md +55 -0
- package/agents/triage-officer.md +27 -0
- package/bin/gxpm +17 -0
- package/bin/gxpm-browser +17 -0
- package/bin/gxpm-config +15 -0
- package/bin/gxpm-eval +13 -0
- package/bin/gxpm-global-discover +15 -0
- package/bin/gxpm-init +38 -0
- package/bin/gxpm-investigate +194 -0
- package/bin/gxpm-uninstall +15 -0
- package/bin/gxpm-update-check +165 -0
- package/commands/build.md +40 -0
- package/commands/help.md +53 -0
- package/commands/plan.md +34 -0
- package/commands/refine.md +46 -0
- package/commands/review.md +34 -0
- package/commands/ship.md +37 -0
- package/core/ac-check.ts +20 -0
- package/core/agent-runtime.ts +363 -0
- package/core/artifact-validator.ts +151 -0
- package/core/artifacts.ts +313 -0
- package/core/autopilot.ts +250 -0
- package/core/capabilities.ts +779 -0
- package/core/checkpoint.ts +370 -0
- package/core/cleanup.ts +32 -0
- package/core/command-probe.ts +82 -0
- package/core/config.ts +533 -0
- package/core/contracts/behavior-spec.schema.ts +38 -0
- package/core/contracts/converter.ts +61 -0
- package/core/contracts/host.ts +43 -0
- package/core/converters/converter.ts +93 -0
- package/core/converters/index.ts +8 -0
- package/core/converters/managed-artifact.ts +119 -0
- package/core/converters/parser.ts +159 -0
- package/core/converters/template-renderer.ts +35 -0
- package/core/converters/writer.ts +61 -0
- package/core/dag-executor.ts +426 -0
- package/core/dag-loader.ts +292 -0
- package/core/dag-schemas.ts +150 -0
- package/core/dispatch.ts +125 -0
- package/core/evidence.ts +148 -0
- package/core/gate.ts +269 -0
- package/core/hook-engine.ts +566 -0
- package/core/host-probe.ts +64 -0
- package/core/implement.ts +16 -0
- package/core/isolation-errors.ts +174 -0
- package/core/isolation-resolver.ts +921 -0
- package/core/issue-context.ts +381 -0
- package/core/issue-readiness.ts +457 -0
- package/core/issue-sync.ts +427 -0
- package/core/issues.ts +132 -0
- package/core/land.ts +108 -0
- package/core/orchestrator.ts +54 -0
- package/core/phase-artifact.ts +32 -0
- package/core/phase-gates.ts +130 -0
- package/core/phase-rewind.ts +94 -0
- package/core/plan-lint.ts +61 -0
- package/core/plan.ts +77 -0
- package/core/port-allocation.ts +50 -0
- package/core/pr-check.ts +15 -0
- package/core/preset-system/preset-resolver.ts +221 -0
- package/core/project-init-status.ts +127 -0
- package/core/qa.ts +15 -0
- package/core/resilience.ts +165 -0
- package/core/runs.ts +288 -0
- package/core/safe-path.test.ts +80 -0
- package/core/safe-path.ts +60 -0
- package/core/sdd-gate.test.ts +98 -0
- package/core/sdd-gate.ts +134 -0
- package/core/self-review.ts +62 -0
- package/core/session.ts +70 -0
- package/core/ship.ts +86 -0
- package/core/specify.ts +173 -0
- package/core/state.ts +1002 -0
- package/core/template-engine.ts +152 -0
- package/core/template-resolver.test.ts +70 -0
- package/core/template-resolver.ts +156 -0
- package/core/triage.ts +26 -0
- package/core/verify.ts +15 -0
- package/core/wiki-native.ts +2423 -0
- package/core/wiki.ts +27 -0
- package/core/workflow-event-emitter.ts +163 -0
- package/core/workflows/engine.ts +273 -0
- package/core/workflows/expressions.ts +76 -0
- package/core/workflows/index.ts +38 -0
- package/core/workflows/steps/command.ts +43 -0
- package/core/workflows/steps/gate.ts +47 -0
- package/core/workflows/steps/gxpm.ts +44 -0
- package/core/workflows/steps/linear.ts +31 -0
- package/core/workflows/steps/shell.ts +65 -0
- package/core/workflows/types.ts +62 -0
- package/core/workspace-runtime.ts +227 -0
- package/core/worktree-init-steps.ts +647 -0
- package/core/worktree-init.ts +330 -0
- package/core/worktree-owner.ts +143 -0
- package/docs/GXPM_VERIFY.md +98 -0
- package/docs/INSTALL_FOR_AGENTS.md +113 -0
- package/docs/README.md +57 -0
- package/docs/adr/adr-005-multi-platform-skill-converter.md +72 -0
- package/docs/agents/domain.md +30 -0
- package/docs/agents/issue-tracker.md +30 -0
- package/docs/agents/triage-labels.md +32 -0
- package/docs/architecture/gxpm-architecture-diagram.md +265 -0
- package/docs/architecture/gxpm-current-architecture.md +175 -0
- package/docs/architecture/gxpm-current-flow.md +278 -0
- package/docs/architecture/gxpm-replacement-architecture.md +211 -0
- package/docs/architecture/gxpm-target-architecture.md +449 -0
- package/docs/architecture/gxpm-v0-contract.md +311 -0
- package/docs/architecture/layered-workflow-boundaries.md +193 -0
- package/docs/architecture/preset-system.md +126 -0
- package/docs/architecture/scaffold-northstar.md +23 -0
- package/docs/brainstorms/2026-05-14-bdd-then-tdd-design.md +320 -0
- package/docs/brainstorms/README.md +22 -0
- package/docs/brainstorms/docs-knowledge-system-requirements.md +29 -0
- package/docs/governance/beta-skill-promotion.md +39 -0
- package/docs/governance/development-contract.md +144 -0
- package/docs/governance/gherkin-style.md +90 -0
- package/docs/governance/host-adapter.md +56 -0
- package/docs/governance/skill-authoring.md +87 -0
- package/docs/governance/skill-testing.md +356 -0
- package/docs/governance/template-authoring.md +53 -0
- package/docs/migrations/v0.2.md +51 -0
- package/docs/plans/README.md +23 -0
- package/docs/plans/bdd-then-tdd-plan.md +1767 -0
- package/docs/plans/docs-knowledge-system-plan.md +31 -0
- package/docs/plans/spec-kit-sdd-adoption-plan.md +305 -0
- package/docs/research/agents-md-best-practices.md +207 -0
- package/docs/research/archon-study.md +351 -0
- package/docs/research/claude-hooks-study.md +440 -0
- package/docs/research/codex-hooks-study.md +624 -0
- package/docs/research/everything-claude-code-study.md +252 -0
- package/docs/research/from-skills-to-layered-workflow.md +322 -0
- package/docs/research/gsd-study.md +69 -0
- package/docs/research/kimi-hooks-study.md +274 -0
- package/docs/research/mattpocock-skills-comparison.md +429 -0
- package/docs/research/mattpocock-skills-study.md +275 -0
- package/docs/research/oh-my-codex-study.md +279 -0
- package/docs/research/perplexity-agent-skills-design.md +168 -0
- package/docs/research/pmc-gstack-skill-study.md +122 -0
- package/docs/research/spec-kit-study.md +224 -0
- package/docs/research/superpowers-study.md +209 -0
- package/docs/roadmap/initial-roadmap.md +53 -0
- package/docs/solutions/README.md +45 -0
- package/docs/solutions/artifact-nesting-recovery.md +58 -0
- package/docs/solutions/session-context-restore-practice.md +67 -0
- package/docs/solutions/workflow/version-drift-recovery.md +49 -0
- package/docs/solutions/worktree-gate-recovery.md +62 -0
- package/docs/specs/README.md +28 -0
- package/docs/specs/claude.md +45 -0
- package/docs/specs/codex.md +44 -0
- package/docs/specs/cursor.md +44 -0
- package/hosts/adapters/claude.ts +29 -0
- package/hosts/adapters/codex.ts +27 -0
- package/hosts/adapters/cursor.ts +27 -0
- package/hosts/adapters/kimi.ts +27 -0
- package/hosts/claude.ts +23 -0
- package/hosts/codex.ts +26 -0
- package/hosts/cursor.ts +19 -0
- package/hosts/index.ts +33 -0
- package/hosts/registry.test.ts +52 -0
- package/hosts/registry.ts +57 -0
- package/hosts/schema.ts +58 -0
- package/package.json +52 -0
- package/scripts/browser.ts +185 -0
- package/scripts/cleanup.ts +142 -0
- package/scripts/commands/artifact.ts +115 -0
- package/scripts/commands/autopilot.ts +143 -0
- package/scripts/commands/capability.ts +57 -0
- package/scripts/commands/config.ts +69 -0
- package/scripts/commands/dag.ts +126 -0
- package/scripts/commands/feedback.ts +123 -0
- package/scripts/commands/gate.ts +291 -0
- package/scripts/commands/helpers.ts +126 -0
- package/scripts/commands/hook.ts +66 -0
- package/scripts/commands/init.ts +515 -0
- package/scripts/commands/issue.ts +825 -0
- package/scripts/commands/phase.ts +61 -0
- package/scripts/commands/preset.ts +159 -0
- package/scripts/commands/runtime.ts +199 -0
- package/scripts/commands/specify.ts +71 -0
- package/scripts/commands/upgrade.ts +243 -0
- package/scripts/commands/verify.ts +183 -0
- package/scripts/commands/wiki.ts +242 -0
- package/scripts/commands/workflow.ts +131 -0
- package/scripts/dev-skill.ts +55 -0
- package/scripts/discover-skills.ts +116 -0
- package/scripts/doctor.ts +410 -0
- package/scripts/dogfood-check.ts +125 -0
- package/scripts/eval-functional.ts +218 -0
- package/scripts/eval.ts +246 -0
- package/scripts/gen-skill-docs.ts +201 -0
- package/scripts/global-discover.ts +217 -0
- package/scripts/governance-check.ts +75 -0
- package/scripts/gxpm-check.ts +12 -0
- package/scripts/gxpm.ts +216 -0
- package/scripts/host-config.ts +62 -0
- package/scripts/install-claude-hooks.ts +138 -0
- package/scripts/install-codex-hooks.ts +271 -0
- package/scripts/install-hooks.ts +128 -0
- package/scripts/install-kimi-hooks.ts +92 -0
- package/scripts/install-skill.ts +184 -0
- package/scripts/phase-artifact-commands.ts +100 -0
- package/scripts/post-land-sync.ts +46 -0
- package/scripts/scaffold-check.ts +85 -0
- package/scripts/skill-naming-check.ts +78 -0
- package/scripts/skill-structure-check.ts +157 -0
- package/scripts/skills-lock-check.ts +60 -0
- package/scripts/sync-markdown-artifacts.ts +172 -0
- package/scripts/uninstall.ts +162 -0
- package/scripts/version.ts +47 -0
- package/scripts/wait-pr-ready.ts +407 -0
- package/skills/gxpm/SKILL.md +485 -0
- package/skills/gxpm/SKILL.md.tmpl +422 -0
- package/skills/gxpm/references/CANON.md +53 -0
- package/skills/gxpm/references/key-rules.md +130 -0
- package/skills/gxpm-architecture/SKILL.md +106 -0
- package/skills/gxpm-architecture/references/DEEPENING.md +37 -0
- package/skills/gxpm-architecture/references/INTERFACE-DESIGN.md +44 -0
- package/skills/gxpm-autopilot/SKILL.md +116 -0
- package/skills/gxpm-autopilot/SKILL.md.tmpl +107 -0
- package/skills/gxpm-browser/SKILL.md +105 -0
- package/skills/gxpm-browser/SKILL.md.tmpl +41 -0
- package/skills/gxpm-browser/references/commands.md +43 -0
- package/skills/gxpm-browser/references/evidence-path.md +20 -0
- package/skills/gxpm-build/SKILL.md +78 -0
- package/skills/gxpm-cleanup/SKILL.md +76 -0
- package/skills/gxpm-debug-issue/SKILL.md +39 -0
- package/skills/gxpm-diagnose/SKILL.md +220 -0
- package/skills/gxpm-diagnose/SKILL.md.tmpl +31 -0
- package/skills/gxpm-diagnose/references/feedback-loop.md +34 -0
- package/skills/gxpm-diagnose/references/feedback-loops.md +43 -0
- package/skills/gxpm-diagnose/references/phases.md +60 -0
- package/skills/gxpm-eval/SKILL.md +78 -0
- package/skills/gxpm-explore-codebase/SKILL.md +36 -0
- package/skills/gxpm-explore-codebase/scripts/summarize-communities.ts +51 -0
- package/skills/gxpm-feedback/SKILL.md +122 -0
- package/skills/gxpm-grill/SKILL.md +159 -0
- package/skills/gxpm-grill/SKILL.md.tmpl +77 -0
- package/skills/gxpm-grill/references/documentation-templates.md +56 -0
- package/skills/gxpm-grill/references/process.md +25 -0
- package/skills/gxpm-handoff/SKILL.md +112 -0
- package/skills/gxpm-hygiene/SKILL.md +69 -0
- package/skills/gxpm-implementer/SKILL.md +142 -0
- package/skills/gxpm-implementer/SKILL.md.tmpl +141 -0
- package/skills/gxpm-linear/SKILL.md +282 -0
- package/skills/gxpm-linear/SKILL.md.tmpl +86 -0
- package/skills/gxpm-linear/references/commands.md +75 -0
- package/skills/gxpm-linear/references/workflows.md +120 -0
- package/skills/gxpm-planning/SKILL.md +134 -0
- package/skills/gxpm-prototype/SKILL.md +64 -0
- package/skills/gxpm-refactor-safely/SKILL.md +62 -0
- package/skills/gxpm-review-army/SKILL.md +117 -0
- package/skills/gxpm-review-changes/SKILL.md +36 -0
- package/skills/gxpm-setup/SKILL.md +101 -0
- package/skills/gxpm-specifier/SKILL.md +135 -0
- package/skills/gxpm-tdd/SKILL.md +187 -0
- package/skills/gxpm-tdd/references/interface-design.md +23 -0
- package/skills/gxpm-tdd/references/mocking.md +27 -0
- package/skills/gxpm-tdd/references/red-green-refactor.md +61 -0
- package/skills/gxpm-tdd/references/troubleshooting.md +28 -0
- package/skills/gxpm-tdd/references/workflow.md +50 -0
- package/skills/gxpm-tdd/testing-anti-patterns.tmpl +304 -0
- package/skills/gxpm-triage/SKILL.md +160 -0
- package/skills/gxpm-verify/SKILL.md +107 -0
- package/skills/gxpm-write-skill/SKILL.md +131 -0
- package/skills/gxpm-zoom-out/SKILL.md +69 -0
- package/skills/maintain-hygiene-skills-lock/SKILL.md +54 -0
- package/skills/maintain-hygiene-skills-lock/SKILL.md.tmpl +53 -0
- package/templates/constitution-template.md +63 -0
- package/templates/hooks/gxpm-commit-msg +16 -0
- package/templates/hooks/gxpm-post-checkout +19 -0
- package/templates/hooks/gxpm-post-commit +7 -0
- package/templates/hooks/gxpm-post-merge +29 -0
- package/templates/hooks/gxpm-pre-commit +39 -0
- package/templates/hooks/gxpm-pre-push +33 -0
- package/templates/plan-template.md.tmpl +46 -0
- package/templates/spec-template.md.tmpl +63 -0
- package/templates/specify-stub.tmpl +22 -0
- package/templates/tasks-template.md.tmpl +32 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// GXPM STEP — invoke gxpm CLI commands within a workflow.
|
|
2
|
+
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import type { StepBase, StepResult, StepContext } from "../types";
|
|
5
|
+
|
|
6
|
+
export const GxpmStep: StepBase = {
|
|
7
|
+
typeKey: "gxpm",
|
|
8
|
+
|
|
9
|
+
validate(config) {
|
|
10
|
+
const errors: string[] = [];
|
|
11
|
+
if (!config.command || typeof config.command !== "string") {
|
|
12
|
+
errors.push("'command' is required and must be a string");
|
|
13
|
+
}
|
|
14
|
+
return errors;
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
execute(config, context) {
|
|
18
|
+
const command = config.command as string;
|
|
19
|
+
const fullCommand = `bun run bin/gxpm ${command}`;
|
|
20
|
+
const cwd = context.projectRoot;
|
|
21
|
+
const capture = config.captureOutput !== false;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const output = execSync(fullCommand, {
|
|
25
|
+
cwd,
|
|
26
|
+
encoding: "utf8",
|
|
27
|
+
stdio: capture ? ["pipe", "pipe", "pipe"] : "inherit",
|
|
28
|
+
timeout: (config.timeout as number) || 120000,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
status: "completed",
|
|
33
|
+
output: capture ? { stdout: output, command: fullCommand } : { command: fullCommand },
|
|
34
|
+
};
|
|
35
|
+
} catch (err) {
|
|
36
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
37
|
+
return {
|
|
38
|
+
status: config.ignoreFailure ? "completed" : "failed",
|
|
39
|
+
output: { command: fullCommand },
|
|
40
|
+
error: errorMessage,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// LINEAR STEP — interact with Linear issues.
|
|
2
|
+
// Thin wrapper; actual Linear integration delegates to existing core/linear module.
|
|
3
|
+
|
|
4
|
+
import type { StepBase, StepResult, StepContext } from "../types";
|
|
5
|
+
|
|
6
|
+
export const LinearStep: StepBase = {
|
|
7
|
+
typeKey: "linear",
|
|
8
|
+
|
|
9
|
+
validate(config) {
|
|
10
|
+
const errors: string[] = [];
|
|
11
|
+
if (!config.action || typeof config.action !== "string") {
|
|
12
|
+
errors.push("'action' is required");
|
|
13
|
+
}
|
|
14
|
+
const valid = ["create", "update", "list", "transition"];
|
|
15
|
+
if (!valid.includes(config.action as string)) {
|
|
16
|
+
errors.push(`'action' must be one of: ${valid.join(", ")}`);
|
|
17
|
+
}
|
|
18
|
+
return errors;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
async execute(config, _context) {
|
|
22
|
+
const action = config.action as string;
|
|
23
|
+
|
|
24
|
+
// Placeholder: will delegate to core/linear.ts when available
|
|
25
|
+
// For now, return a mock result to keep workflow engine testable
|
|
26
|
+
return {
|
|
27
|
+
status: "completed",
|
|
28
|
+
output: { action, mock: true, id: config.id as string },
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// SHELL STEP — execute an arbitrary shell command within a workflow.
|
|
2
|
+
// Captures stdout, stderr, and exitCode for downstream steps.
|
|
3
|
+
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import type { StepBase, StepResult, StepContext } from "../types";
|
|
6
|
+
|
|
7
|
+
export const ShellStep: StepBase = {
|
|
8
|
+
typeKey: "shell",
|
|
9
|
+
|
|
10
|
+
validate(config) {
|
|
11
|
+
const errors: string[] = [];
|
|
12
|
+
if (!config.command || typeof config.command !== "string") {
|
|
13
|
+
errors.push("'command' is required and must be a string");
|
|
14
|
+
}
|
|
15
|
+
return errors;
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
execute(config, context) {
|
|
19
|
+
const command = config.command as string;
|
|
20
|
+
const cwd = config.cwd
|
|
21
|
+
? String(config.cwd).replace("${projectRoot}", context.projectRoot)
|
|
22
|
+
: context.projectRoot;
|
|
23
|
+
const capture = config.captureOutput !== false;
|
|
24
|
+
const timeout = (config.timeout as number) || 60000;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const output = execSync(command, {
|
|
28
|
+
cwd,
|
|
29
|
+
encoding: "utf8",
|
|
30
|
+
stdio: capture ? ["pipe", "pipe", "pipe"] : "inherit",
|
|
31
|
+
timeout,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
status: "completed",
|
|
36
|
+
output: capture
|
|
37
|
+
? { stdout: output, exitCode: 0, command }
|
|
38
|
+
: { exitCode: 0, command },
|
|
39
|
+
};
|
|
40
|
+
} catch (err) {
|
|
41
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
42
|
+
// execSync throws on non-zero exit; try to capture stdout/stderr from the error object
|
|
43
|
+
const stdout =
|
|
44
|
+
err && typeof err === "object" && "stdout" in err
|
|
45
|
+
? String((err as { stdout?: unknown }).stdout)
|
|
46
|
+
: "";
|
|
47
|
+
const stderr =
|
|
48
|
+
err && typeof err === "object" && "stderr" in err
|
|
49
|
+
? String((err as { stderr?: unknown }).stderr)
|
|
50
|
+
: "";
|
|
51
|
+
const exitCode =
|
|
52
|
+
err && typeof err === "object" && "status" in err
|
|
53
|
+
? Number((err as { status?: unknown }).status)
|
|
54
|
+
: 1;
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
status: config.ignoreFailure ? "completed" : "failed",
|
|
58
|
+
output: capture
|
|
59
|
+
? { stdout, stderr, exitCode, command }
|
|
60
|
+
: { exitCode, command },
|
|
61
|
+
error: errorMessage,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// WORKFLOW ENGINE — core types and interfaces.
|
|
2
|
+
// Zero external dependencies beyond Node.js builtins.
|
|
3
|
+
|
|
4
|
+
export type RunStatus = "created" | "running" | "paused" | "completed" | "failed" | "aborted";
|
|
5
|
+
export type StepStatus = "pending" | "running" | "completed" | "failed" | "skipped" | "paused";
|
|
6
|
+
|
|
7
|
+
export interface WorkflowDefinition {
|
|
8
|
+
schemaVersion: string;
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
inputs: Record<string, WorkflowInputSchema>;
|
|
14
|
+
steps: StepConfig[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface WorkflowInputSchema {
|
|
18
|
+
type: "string" | "boolean" | "number" | "array";
|
|
19
|
+
required?: boolean;
|
|
20
|
+
default?: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface StepConfig {
|
|
24
|
+
id: string;
|
|
25
|
+
type: string;
|
|
26
|
+
config: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface StepContext {
|
|
30
|
+
inputs: Record<string, unknown>;
|
|
31
|
+
steps: Record<string, StepResult>;
|
|
32
|
+
runId: string;
|
|
33
|
+
projectRoot: string;
|
|
34
|
+
item?: unknown;
|
|
35
|
+
fanIn?: unknown[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface StepResult {
|
|
39
|
+
status: StepStatus;
|
|
40
|
+
output: Record<string, unknown>;
|
|
41
|
+
nextSteps?: StepConfig[];
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface RunState {
|
|
46
|
+
runId: string;
|
|
47
|
+
workflowId: string;
|
|
48
|
+
status: RunStatus;
|
|
49
|
+
currentStepIndex: number;
|
|
50
|
+
stepIndexStack: number[];
|
|
51
|
+
stepResults: Record<string, StepResult>;
|
|
52
|
+
inputs: Record<string, unknown>;
|
|
53
|
+
createdAt: string;
|
|
54
|
+
updatedAt: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface StepBase {
|
|
58
|
+
readonly typeKey: string;
|
|
59
|
+
execute(config: Record<string, unknown>, context: StepContext): StepResult | Promise<StepResult>;
|
|
60
|
+
validate?(config: Record<string, unknown>): string[];
|
|
61
|
+
canResume?(state: StepResult): boolean;
|
|
62
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
lstatSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
} from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
9
|
+
import { getResolvedConfigValue } from "./config";
|
|
10
|
+
import { resolveDevPort } from "./port-allocation";
|
|
11
|
+
import { readIssueState } from "./state";
|
|
12
|
+
import {
|
|
13
|
+
IsolationResolver,
|
|
14
|
+
createFileSystemStore,
|
|
15
|
+
createGitProvider,
|
|
16
|
+
type IsolationHints,
|
|
17
|
+
type IsolationMethod,
|
|
18
|
+
type IsolationResolution,
|
|
19
|
+
} from "./isolation-resolver";
|
|
20
|
+
import { runWorktreeInit, type WorktreeInitContext } from "./worktree-init";
|
|
21
|
+
import "./worktree-init-steps"; // side-effect: registers built-in init steps
|
|
22
|
+
|
|
23
|
+
export interface WorkspacePlanInput {
|
|
24
|
+
root?: string;
|
|
25
|
+
issueId: string;
|
|
26
|
+
workspaceRoot?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WorkspacePlan {
|
|
30
|
+
issueId: string;
|
|
31
|
+
workspaceKey: string;
|
|
32
|
+
workspaceRoot: string;
|
|
33
|
+
workspacePath: string;
|
|
34
|
+
exists: boolean;
|
|
35
|
+
devPort: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface WorkspaceEnsureResult extends WorkspacePlan {
|
|
39
|
+
created: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface WorkspaceIsolationResult extends WorkspaceEnsureResult {
|
|
43
|
+
method?: IsolationMethod;
|
|
44
|
+
warnings?: string[];
|
|
45
|
+
userMessage?: string;
|
|
46
|
+
resolution?: IsolationResolution;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface WorkspaceCleanupResult extends WorkspacePlan {
|
|
50
|
+
removed: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function planIssueWorkspace(input: WorkspacePlanInput): WorkspacePlan {
|
|
54
|
+
const root = input.root ?? process.cwd();
|
|
55
|
+
const state = readIssueState({ root, issueId: input.issueId });
|
|
56
|
+
const workspaceRoot = resolveWorkspaceRoot({ root, workspaceRoot: input.workspaceRoot });
|
|
57
|
+
const workspaceKey = sanitizeWorkspaceKey(state.issueId);
|
|
58
|
+
const workspacePath = join(workspaceRoot, workspaceKey);
|
|
59
|
+
assertPathInsideRoot(workspaceRoot, workspacePath);
|
|
60
|
+
|
|
61
|
+
const plan = {
|
|
62
|
+
issueId: state.issueId,
|
|
63
|
+
workspaceKey,
|
|
64
|
+
workspaceRoot,
|
|
65
|
+
workspacePath,
|
|
66
|
+
exists: existsSync(workspacePath),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
...plan,
|
|
71
|
+
devPort: resolveDevPort({ workspacePath, root }),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function ensureIssueWorkspace(input: WorkspacePlanInput): WorkspaceEnsureResult {
|
|
76
|
+
const plan = planIssueWorkspace(input);
|
|
77
|
+
mkdirSync(plan.workspaceRoot, { recursive: true });
|
|
78
|
+
assertPathInsideRoot(plan.workspaceRoot, plan.workspacePath);
|
|
79
|
+
|
|
80
|
+
const existed = existsSync(plan.workspacePath);
|
|
81
|
+
if (existed && !lstatSync(plan.workspacePath).isDirectory()) {
|
|
82
|
+
throw new Error(`Workspace path exists but is not a directory: ${plan.workspacePath}`);
|
|
83
|
+
}
|
|
84
|
+
if (!existed) {
|
|
85
|
+
mkdirSync(plan.workspacePath, { recursive: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { ...plan, exists: true, created: !existed };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function ensureIssueWorkspaceWithResolver(
|
|
92
|
+
input: WorkspacePlanInput & { hints?: IsolationHints },
|
|
93
|
+
): Promise<WorkspaceIsolationResult> {
|
|
94
|
+
const root = input.root ?? process.cwd();
|
|
95
|
+
const issueId = input.issueId;
|
|
96
|
+
const configuredBaseBranch = String(getResolvedConfigValue({ root, key: "worktree.baseBranch" }).value);
|
|
97
|
+
const hints: IsolationHints = {
|
|
98
|
+
...input.hints,
|
|
99
|
+
baseBranch: input.hints?.baseBranch ?? configuredBaseBranch,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Run resolver first
|
|
103
|
+
const store = createFileSystemStore(root);
|
|
104
|
+
const provider = createGitProvider();
|
|
105
|
+
const resolver = new IsolationResolver({ store, provider });
|
|
106
|
+
const resolution = await resolver.resolve({ issueId, root, hints });
|
|
107
|
+
|
|
108
|
+
let plan: WorkspacePlan;
|
|
109
|
+
let created = false;
|
|
110
|
+
|
|
111
|
+
let initWarnings: string[] = [];
|
|
112
|
+
|
|
113
|
+
if (resolution.status === "resolved" && resolution.env) {
|
|
114
|
+
// Use the resolved workspace path
|
|
115
|
+
const workspacePath = resolution.env.workspacePath;
|
|
116
|
+
const workspaceRoot = resolveWorkspaceRoot({ root, workspaceRoot: input.workspaceRoot });
|
|
117
|
+
plan = {
|
|
118
|
+
issueId,
|
|
119
|
+
workspaceKey: sanitizeWorkspaceKey(issueId),
|
|
120
|
+
workspaceRoot,
|
|
121
|
+
workspacePath,
|
|
122
|
+
exists: existsSync(workspacePath),
|
|
123
|
+
devPort: resolveDevPort({ workspacePath, root }),
|
|
124
|
+
};
|
|
125
|
+
if (!plan.exists) {
|
|
126
|
+
mkdirSync(workspacePath, { recursive: true });
|
|
127
|
+
created = true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Run worktree init pipeline for reused worktrees (new worktrees are
|
|
131
|
+
// already initialized inside the provider). Idempotent — safe to re-run.
|
|
132
|
+
const repoResult = Bun.spawnSync({
|
|
133
|
+
cmd: ["git", "rev-parse", "--show-toplevel"],
|
|
134
|
+
cwd: root,
|
|
135
|
+
stdout: "pipe",
|
|
136
|
+
stderr: "pipe",
|
|
137
|
+
});
|
|
138
|
+
if (repoResult.exitCode === 0) {
|
|
139
|
+
const canonicalRepoPath = repoResult.stdout.toString().trim();
|
|
140
|
+
const initCtx: WorktreeInitContext = {
|
|
141
|
+
canonicalRepoPath,
|
|
142
|
+
worktreePath: workspacePath,
|
|
143
|
+
branchName: resolution.env.branchName ?? `gxpm-${issueId}`,
|
|
144
|
+
issueId,
|
|
145
|
+
};
|
|
146
|
+
const initResult = await runWorktreeInit(initCtx);
|
|
147
|
+
initWarnings = initResult.warnings;
|
|
148
|
+
if (!initResult.ok) {
|
|
149
|
+
initWarnings.push(...initResult.errors);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} else if (resolution.status === "none") {
|
|
153
|
+
// No git repo: fall back to plain directory workspace
|
|
154
|
+
plan = planIssueWorkspace(input);
|
|
155
|
+
mkdirSync(plan.workspaceRoot, { recursive: true });
|
|
156
|
+
assertPathInsideRoot(plan.workspaceRoot, plan.workspacePath);
|
|
157
|
+
const existed = existsSync(plan.workspacePath);
|
|
158
|
+
if (!existed) {
|
|
159
|
+
mkdirSync(plan.workspacePath, { recursive: true });
|
|
160
|
+
created = true;
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
// blocked or stale_cleaned — return as-is with minimal plan
|
|
164
|
+
plan = planIssueWorkspace(input);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
...plan,
|
|
169
|
+
exists: true,
|
|
170
|
+
created,
|
|
171
|
+
method: resolution.method,
|
|
172
|
+
warnings: [...(resolution.warnings ?? []), ...initWarnings],
|
|
173
|
+
userMessage: resolution.userMessage,
|
|
174
|
+
resolution,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function cleanupIssueWorkspace(input: WorkspacePlanInput): WorkspaceCleanupResult {
|
|
179
|
+
const plan = planIssueWorkspace(input);
|
|
180
|
+
if (!plan.exists) {
|
|
181
|
+
return { ...plan, removed: false };
|
|
182
|
+
}
|
|
183
|
+
if (!lstatSync(plan.workspacePath).isDirectory()) {
|
|
184
|
+
throw new Error(`Workspace path exists but is not a directory: ${plan.workspacePath}`);
|
|
185
|
+
}
|
|
186
|
+
assertPathInsideRoot(plan.workspaceRoot, plan.workspacePath);
|
|
187
|
+
rmSync(plan.workspacePath, { recursive: true, force: true });
|
|
188
|
+
return { ...plan, exists: false, removed: true };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function sanitizeWorkspaceKey(identifier: string) {
|
|
192
|
+
const safe = identifier.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
193
|
+
return safe || "issue";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function isPathInsideRoot(
|
|
197
|
+
root: string,
|
|
198
|
+
candidate: string,
|
|
199
|
+
pathApi = { isAbsolute, relative, resolve },
|
|
200
|
+
) {
|
|
201
|
+
const relativePath = pathApi.relative(pathApi.resolve(root), pathApi.resolve(candidate));
|
|
202
|
+
return relativePath === "" || (!relativePath.startsWith("..") && !pathApi.isAbsolute(relativePath));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function resolveWorkspaceRoot(input: { root: string; workspaceRoot?: string }) {
|
|
206
|
+
const configured =
|
|
207
|
+
input.workspaceRoot ??
|
|
208
|
+
String(getResolvedConfigValue({ root: input.root, key: "workspace.root" }).value);
|
|
209
|
+
const expanded = expandHome(configured);
|
|
210
|
+
return isAbsolute(expanded) ? resolve(expanded) : resolve(input.root, expanded);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function expandHome(path: string) {
|
|
214
|
+
if (path === "~") {
|
|
215
|
+
return homedir();
|
|
216
|
+
}
|
|
217
|
+
if (path.startsWith("~/")) {
|
|
218
|
+
return join(homedir(), path.slice(2));
|
|
219
|
+
}
|
|
220
|
+
return path;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function assertPathInsideRoot(root: string, candidate: string) {
|
|
224
|
+
if (!isPathInsideRoot(root, candidate)) {
|
|
225
|
+
throw new Error(`Workspace path escapes workspace root: ${resolve(candidate)}`);
|
|
226
|
+
}
|
|
227
|
+
}
|