@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,142 @@
|
|
|
1
|
+
import { mkdirSync, copyFileSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { readArtifact } from "../core/artifacts";
|
|
4
|
+
import { appendIssueEvent, getIssuePaths, readIssueState } from "../core/state";
|
|
5
|
+
|
|
6
|
+
export function runCleanupLandCommand(argv: string[], issueId: string): void {
|
|
7
|
+
const root = process.cwd();
|
|
8
|
+
const execute = argv.includes("--execute");
|
|
9
|
+
const force = argv.includes("--force");
|
|
10
|
+
|
|
11
|
+
// 1. Read issue state and verify phase
|
|
12
|
+
const state = readIssueState({ root, issueId });
|
|
13
|
+
if (state.currentPhase !== "land") {
|
|
14
|
+
throw new Error("cleanup only applies to landed issues");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 2. Resolve cleanup target from dispatch-handoff artifact
|
|
18
|
+
let handoffPayload: Record<string, unknown>;
|
|
19
|
+
try {
|
|
20
|
+
const artifact = readArtifact({ root, issueId, type: "dispatch-handoff" });
|
|
21
|
+
handoffPayload = artifact.payload as Record<string, unknown>;
|
|
22
|
+
} catch {
|
|
23
|
+
throw new Error("cleanup requires dispatch-handoff artifact");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Prefer newer fields (worktree / branch), then current handoff workspace,
|
|
27
|
+
// and finally legacy worktreePath / targetBranch.
|
|
28
|
+
const worktree = (handoffPayload.worktree ?? handoffPayload.workspace ?? handoffPayload.worktreePath) as
|
|
29
|
+
| string
|
|
30
|
+
| undefined;
|
|
31
|
+
const branch = (handoffPayload.branch ?? handoffPayload.targetBranch) as string | undefined;
|
|
32
|
+
|
|
33
|
+
if (!worktree) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"cleanup requires one of dispatch-handoff.payload.worktree, dispatch-handoff.payload.workspace, or dispatch-handoff.payload.worktreePath",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (!branch) {
|
|
39
|
+
throw new Error("cleanup requires dispatch-handoff.payload.branch");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 3. Reject dangerous targets
|
|
43
|
+
const resolvedWorktree = resolve(worktree);
|
|
44
|
+
const resolvedRoot = resolve(root);
|
|
45
|
+
if (resolvedWorktree === resolvedRoot || branch === "main") {
|
|
46
|
+
throw new Error("refusing to clean main worktree/branch");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 4. Dry-run (default)
|
|
50
|
+
if (!execute) {
|
|
51
|
+
console.log(`WOULD REMOVE worktree: ${worktree}`);
|
|
52
|
+
console.log(`WOULD DELETE branch: ${branch}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 5. Execute mode: assert clean worktree
|
|
57
|
+
const statusResult = Bun.spawnSync({
|
|
58
|
+
cmd: ["git", "status", "--short"],
|
|
59
|
+
cwd: worktree,
|
|
60
|
+
stdout: "pipe",
|
|
61
|
+
stderr: "pipe",
|
|
62
|
+
});
|
|
63
|
+
if (statusResult.exitCode !== 0) {
|
|
64
|
+
throw new Error(`worktree dirty, refusing to remove; commit or stash first`);
|
|
65
|
+
}
|
|
66
|
+
const statusOutput = statusResult.stdout.toString().trim();
|
|
67
|
+
if (statusOutput.length > 0) {
|
|
68
|
+
throw new Error(`worktree dirty, refusing to remove; commit or stash first`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 6. Assert current cwd is not inside target worktree
|
|
72
|
+
const cwdResolved = resolve(process.cwd());
|
|
73
|
+
if (cwdResolved === resolvedWorktree || cwdResolved.startsWith(resolvedWorktree + "/")) {
|
|
74
|
+
throw new Error("currently inside target worktree; cd out first");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 7. Remove worktree
|
|
78
|
+
const removeResult = Bun.spawnSync({
|
|
79
|
+
cmd: ["git", "worktree", "remove", worktree],
|
|
80
|
+
cwd: root,
|
|
81
|
+
stdout: "pipe",
|
|
82
|
+
stderr: "pipe",
|
|
83
|
+
});
|
|
84
|
+
if (removeResult.exitCode !== 0) {
|
|
85
|
+
throw new Error(`git worktree remove failed: ${removeResult.stderr.toString().trim()}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 8. Delete branch
|
|
89
|
+
const branchFlag = force ? "-D" : "-d";
|
|
90
|
+
const branchResult = Bun.spawnSync({
|
|
91
|
+
cmd: ["git", "branch", branchFlag, branch],
|
|
92
|
+
cwd: root,
|
|
93
|
+
stdout: "pipe",
|
|
94
|
+
stderr: "pipe",
|
|
95
|
+
});
|
|
96
|
+
if (branchResult.exitCode !== 0) {
|
|
97
|
+
const stderr = branchResult.stderr.toString().trim();
|
|
98
|
+
if (!force) {
|
|
99
|
+
throw new Error(`${stderr}\nrerun with --force to delete unmerged branch`);
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`git branch -D failed: ${stderr}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 9. Append cleanup.executed event on success only
|
|
105
|
+
const paths = getIssuePaths(root, issueId);
|
|
106
|
+
appendIssueEvent({
|
|
107
|
+
issueDir: paths.issueDir,
|
|
108
|
+
event: {
|
|
109
|
+
schemaVersion: 1,
|
|
110
|
+
type: "cleanup.executed",
|
|
111
|
+
issueId,
|
|
112
|
+
timestamp: new Date().toISOString(),
|
|
113
|
+
payload: { worktree, branch, forced: force, dryRun: false },
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// 10. Archive issue directory to .gxpm/archive/
|
|
118
|
+
const archiveDir = join(root, ".gxpm", "archive", `${new Date().toISOString().split("T")[0]}-${issueId}`);
|
|
119
|
+
try {
|
|
120
|
+
copyDirRecursive(paths.issueDir, archiveDir);
|
|
121
|
+
console.log(`archived issue: ${archiveDir}`);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.warn(`archive failed (non-blocking): ${err instanceof Error ? err.message : String(err)}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log(`removed worktree: ${worktree}`);
|
|
127
|
+
console.log(`deleted branch: ${branch}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function copyDirRecursive(src: string, dest: string): void {
|
|
131
|
+
mkdirSync(dest, { recursive: true });
|
|
132
|
+
const entries = readdirSync(src, { withFileTypes: true });
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
const srcPath = join(src, entry.name);
|
|
135
|
+
const destPath = join(dest, entry.name);
|
|
136
|
+
if (entry.isDirectory()) {
|
|
137
|
+
copyDirRecursive(srcPath, destPath);
|
|
138
|
+
} else {
|
|
139
|
+
copyFileSync(srcPath, destPath);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { mkdtempSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { hasArtifact, listArtifacts, readArtifact, writeArtifact } from "../../core/artifacts";
|
|
5
|
+
import { probeArtifactPayloadCommands } from "../../core/command-probe";
|
|
6
|
+
import { readJsonPayloadFromArgs } from "./helpers";
|
|
7
|
+
import { validateArtifact, formatValidationResult } from "../../core/artifact-validator";
|
|
8
|
+
|
|
9
|
+
export function runArtifactCommand(argv: string[], subcommand: string | undefined, issueId: string | undefined, type: string | undefined) {
|
|
10
|
+
if (subcommand === "list") {
|
|
11
|
+
if (!issueId) throw new Error("Usage: gxpm artifact list <issue-id>");
|
|
12
|
+
const artifacts = listArtifacts({ issueId });
|
|
13
|
+
if (artifacts.length === 0) {
|
|
14
|
+
console.log("no artifacts");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
for (const artifact of artifacts) {
|
|
18
|
+
console.log(`${artifact.type}\t${artifact.path}\t${artifact.writtenAt}`);
|
|
19
|
+
}
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (subcommand === "read") {
|
|
24
|
+
if (!issueId || !type) throw new Error("Usage: gxpm artifact read <issue-id> <type>");
|
|
25
|
+
console.log(JSON.stringify(readArtifact({ issueId, type }), null, 2));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (subcommand === "write") {
|
|
30
|
+
if (!issueId || !type) {
|
|
31
|
+
throw new Error("Usage: gxpm artifact write <issue-id> <type> [--probe-cli] --json <json> | --from <file> | --stdin");
|
|
32
|
+
}
|
|
33
|
+
runArtifactWrite(argv, issueId, type);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (subcommand === "edit") {
|
|
38
|
+
if (!issueId || !type) throw new Error("Usage: gxpm artifact edit <issue-id> <type>");
|
|
39
|
+
runArtifactEdit(issueId, type);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
throw new Error(`Unknown command: ${["artifact", subcommand].filter(Boolean).join(" ")}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function runArtifactEdit(issueId: string, type: string) {
|
|
47
|
+
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
48
|
+
|
|
49
|
+
let initial = "{}\n";
|
|
50
|
+
if (hasArtifact({ issueId, type })) {
|
|
51
|
+
const stored = readArtifact({ issueId, type });
|
|
52
|
+
initial = `${JSON.stringify(stored.payload, null, 2)}\n`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const tmpFile = join(
|
|
56
|
+
mkdtempSync(join(tmpdir(), `gxpm-edit-${issueId}-${type}-`)),
|
|
57
|
+
`${type}.json`,
|
|
58
|
+
);
|
|
59
|
+
writeFileSync(tmpFile, initial);
|
|
60
|
+
|
|
61
|
+
const editorResult = Bun.spawnSync({
|
|
62
|
+
cmd: [editor, tmpFile],
|
|
63
|
+
stdin: "inherit",
|
|
64
|
+
stdout: "inherit",
|
|
65
|
+
stderr: "inherit",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (editorResult.exitCode !== 0) {
|
|
69
|
+
console.error(`editor "${editor}" exited with code ${editorResult.exitCode}; tempfile preserved at ${tmpFile}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const after = readFileSync(tmpFile, "utf8");
|
|
74
|
+
let payload: unknown;
|
|
75
|
+
try {
|
|
76
|
+
payload = JSON.parse(after);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error(
|
|
79
|
+
`gxpm artifact edit: invalid JSON saved by editor — ${error instanceof Error ? error.message : String(error)}`,
|
|
80
|
+
);
|
|
81
|
+
console.error(`Your edit is preserved at: ${tmpFile}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
writeArtifact({ issueId, type, payload });
|
|
86
|
+
// best-effort cleanup
|
|
87
|
+
try {
|
|
88
|
+
unlinkSync(tmpFile);
|
|
89
|
+
} catch {}
|
|
90
|
+
console.log(`updated ${type} for ${issueId}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function runArtifactWrite(argv: string[], issueId: string, type: string) {
|
|
94
|
+
const payload = readJsonPayloadFromArgs(argv, "gxpm artifact write");
|
|
95
|
+
if (argv.includes("--probe-cli")) {
|
|
96
|
+
const findings = probeArtifactPayloadCommands(payload);
|
|
97
|
+
if (findings.length > 0) {
|
|
98
|
+
const detail = findings.map((finding) => `${finding.command} (${finding.reason})`).join("\n");
|
|
99
|
+
throw new Error(`gxpm artifact write: invalid command references\n${detail}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const result = validateArtifact(type, asPayloadRecord(payload));
|
|
103
|
+
if (!result.valid) {
|
|
104
|
+
throw new Error(`gxpm artifact write: ${formatValidationResult(result)}`);
|
|
105
|
+
}
|
|
106
|
+
const record = writeArtifact({ issueId, type, payload });
|
|
107
|
+
console.log(`wrote ${record.type} for ${issueId} at ${record.path}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function asPayloadRecord(payload: unknown): Record<string, unknown> {
|
|
111
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
112
|
+
return {};
|
|
113
|
+
}
|
|
114
|
+
return payload as Record<string, unknown>;
|
|
115
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { createIssueState } from "../../core/state";
|
|
2
|
+
import { getNextAvailableIssueId } from "../../core/issues";
|
|
3
|
+
import {
|
|
4
|
+
assertAutopilotProfile,
|
|
5
|
+
formatAutopilotGrantContext,
|
|
6
|
+
listActiveAutopilotGrants,
|
|
7
|
+
readAutopilotGrant,
|
|
8
|
+
startAutopilotGrant,
|
|
9
|
+
stopAutopilotGrant,
|
|
10
|
+
type AutopilotGrant,
|
|
11
|
+
type AutopilotProfile,
|
|
12
|
+
} from "../../core/autopilot";
|
|
13
|
+
import { optionRequiredValue, optionValue, parsePositiveIntegerOption } from "./helpers";
|
|
14
|
+
|
|
15
|
+
const USAGE = [
|
|
16
|
+
"Usage:",
|
|
17
|
+
" gxpm autopilot start <issue-id>|--auto-id [--profile full-delivery] [--prompt <text>] [--ttl-minutes N] [--json]",
|
|
18
|
+
" gxpm autopilot status <issue-id> [--json]",
|
|
19
|
+
" gxpm autopilot list [--json]",
|
|
20
|
+
" gxpm autopilot stop <issue-id> [--reason <text>] [--json]",
|
|
21
|
+
].join("\n");
|
|
22
|
+
|
|
23
|
+
export function runAutopilotCommand(argv: string[], subcommand: string | undefined, issueId: string | undefined) {
|
|
24
|
+
if (subcommand === "start") {
|
|
25
|
+
runAutopilotStart(argv, issueId);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (subcommand === "status") {
|
|
30
|
+
runAutopilotStatus(argv, issueId);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (subcommand === "list") {
|
|
35
|
+
runAutopilotList(argv);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (subcommand === "stop") {
|
|
40
|
+
runAutopilotStop(argv, issueId);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
throw new Error(USAGE);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function runAutopilotStart(argv: string[], issueId: string | undefined) {
|
|
48
|
+
const json = argv.includes("--json");
|
|
49
|
+
const autoId = argv.includes("--auto-id");
|
|
50
|
+
if (autoId && issueId && !issueId.startsWith("-")) {
|
|
51
|
+
throw new Error("Usage: choose either `gxpm autopilot start <issue-id>` or `gxpm autopilot start --auto-id`");
|
|
52
|
+
}
|
|
53
|
+
const targetIssueId = autoId ? getNextAvailableIssueId() : issueId;
|
|
54
|
+
if (!targetIssueId || targetIssueId.startsWith("-")) {
|
|
55
|
+
throw new Error(USAGE);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const profile = parseProfile(argv);
|
|
59
|
+
const ttlMinutes = parsePositiveIntegerOption(argv, "--ttl-minutes");
|
|
60
|
+
const prompt = optionValue(argv, "--prompt") ?? undefined;
|
|
61
|
+
let createdIssue = false;
|
|
62
|
+
if (autoId) {
|
|
63
|
+
createIssueState({ issueId: targetIssueId });
|
|
64
|
+
createdIssue = true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const grant = startAutopilotGrant({
|
|
68
|
+
issueId: targetIssueId,
|
|
69
|
+
profile,
|
|
70
|
+
prompt,
|
|
71
|
+
ttlMinutes,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (json) {
|
|
75
|
+
console.log(JSON.stringify({ issueId: targetIssueId, createdIssue, grant }, null, 2));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log(`autopilot grant active for ${targetIssueId}`);
|
|
80
|
+
if (createdIssue) console.log("createdIssue: true");
|
|
81
|
+
printGrantSummary(grant);
|
|
82
|
+
console.log("");
|
|
83
|
+
console.log(`Next: gxpm issue status ${targetIssueId} && gxpm issue next ${targetIssueId}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function runAutopilotStatus(argv: string[], issueId: string | undefined) {
|
|
87
|
+
if (!issueId || issueId.startsWith("-")) {
|
|
88
|
+
throw new Error(USAGE);
|
|
89
|
+
}
|
|
90
|
+
const grant = readAutopilotGrant({ issueId });
|
|
91
|
+
if (!grant) {
|
|
92
|
+
throw new Error(`Autopilot grant not found for ${issueId}`);
|
|
93
|
+
}
|
|
94
|
+
if (argv.includes("--json")) {
|
|
95
|
+
console.log(JSON.stringify(grant, null, 2));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
printGrantSummary(grant);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function runAutopilotList(argv: string[]) {
|
|
102
|
+
const active = listActiveAutopilotGrants();
|
|
103
|
+
if (argv.includes("--json")) {
|
|
104
|
+
console.log(JSON.stringify(active, null, 2));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (active.length === 0) {
|
|
108
|
+
console.log("no active autopilot grants");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
console.log(formatAutopilotGrantContext(active));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function runAutopilotStop(argv: string[], issueId: string | undefined) {
|
|
115
|
+
if (!issueId || issueId.startsWith("-")) {
|
|
116
|
+
throw new Error(USAGE);
|
|
117
|
+
}
|
|
118
|
+
const reason = argv.includes("--reason") ? optionRequiredValue(argv, "--reason") : undefined;
|
|
119
|
+
const grant = stopAutopilotGrant({ issueId, reason });
|
|
120
|
+
if (argv.includes("--json")) {
|
|
121
|
+
console.log(JSON.stringify(grant, null, 2));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
console.log(`autopilot grant stopped for ${issueId}`);
|
|
125
|
+
printGrantSummary(grant);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function parseProfile(argv: string[]): AutopilotProfile {
|
|
129
|
+
const raw = optionValue(argv, "--profile") ?? "full-delivery";
|
|
130
|
+
assertAutopilotProfile(raw);
|
|
131
|
+
return raw;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function printGrantSummary(grant: AutopilotGrant) {
|
|
135
|
+
console.log(`profile: ${grant.profile}`);
|
|
136
|
+
console.log(`status: ${grant.status}`);
|
|
137
|
+
console.log(`runId: ${grant.runId}`);
|
|
138
|
+
console.log(`startedAt: ${grant.startedAt}`);
|
|
139
|
+
if (grant.expiresAt) console.log(`expiresAt: ${grant.expiresAt}`);
|
|
140
|
+
if (grant.stopReason) console.log(`stopReason: ${grant.stopReason}`);
|
|
141
|
+
console.log(`allowedActions: ${grant.allowedActions.join(", ")}`);
|
|
142
|
+
console.log(`hardStops: ${grant.hardStops.join(", ")}`);
|
|
143
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { listCapabilities, requireCapability } from "../../core/capabilities";
|
|
2
|
+
|
|
3
|
+
export function runCapabilityCommand(
|
|
4
|
+
argv: string[],
|
|
5
|
+
subcommand: string | undefined,
|
|
6
|
+
capabilityId: string | undefined,
|
|
7
|
+
) {
|
|
8
|
+
if (subcommand === "list") {
|
|
9
|
+
const capabilities = listCapabilities();
|
|
10
|
+
if (argv.includes("--json")) {
|
|
11
|
+
console.log(JSON.stringify(capabilities, null, 2));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const idWidth = Math.max("ID".length, ...capabilities.map((capability) => capability.id.length));
|
|
15
|
+
const runtimeWidth = Math.max(
|
|
16
|
+
"RUNTIME".length,
|
|
17
|
+
...capabilities.map((capability) => capability.runtime.length),
|
|
18
|
+
);
|
|
19
|
+
console.log(`${"ID".padEnd(idWidth)} ${"RUNTIME".padEnd(runtimeWidth)} STATUS MUTATION`);
|
|
20
|
+
for (const capability of capabilities) {
|
|
21
|
+
console.log(
|
|
22
|
+
`${capability.id.padEnd(idWidth)} ${capability.runtime.padEnd(runtimeWidth)} ${capability.status.padEnd(7)} ${capability.mutationPolicy.scope}`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (subcommand === "show") {
|
|
29
|
+
if (!capabilityId || capabilityId.startsWith("--")) {
|
|
30
|
+
throw new Error("Usage: gxpm capability show <capability-id> [--json]");
|
|
31
|
+
}
|
|
32
|
+
const capability = requireCapability(capabilityId);
|
|
33
|
+
if (argv.includes("--json")) {
|
|
34
|
+
console.log(JSON.stringify(capability, null, 2));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
console.log(`id: ${capability.id}`);
|
|
38
|
+
console.log(`title: ${capability.title}`);
|
|
39
|
+
console.log(`runtime: ${capability.runtime}`);
|
|
40
|
+
console.log(`status: ${capability.status}`);
|
|
41
|
+
console.log(`mutation: ${capability.mutationPolicy.scope}`);
|
|
42
|
+
console.log(`input: ${capability.inputContract}`);
|
|
43
|
+
console.log(`output: ${capability.outputContract.description}`);
|
|
44
|
+
console.log(`idempotency: ${capability.idempotency}`);
|
|
45
|
+
console.log("failureModes:");
|
|
46
|
+
for (const mode of capability.failureModes) {
|
|
47
|
+
console.log(`- ${mode}`);
|
|
48
|
+
}
|
|
49
|
+
console.log("commands:");
|
|
50
|
+
for (const command of capability.commands) {
|
|
51
|
+
console.log(`- ${command}`);
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
throw new Error("Usage: gxpm capability list [--json]\n gxpm capability show <capability-id> [--json]");
|
|
57
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { getConfigValue, getResolvedConfigValue, listConfigEntries, resolveWorktreePolicy, setConfigValue } from "../../core/config";
|
|
2
|
+
import { readSyncState, resolveSyncProvider } from "../../core/issue-sync";
|
|
3
|
+
|
|
4
|
+
export function runWorktreePolicyCommand(argv: string[], subcommand: string | undefined) {
|
|
5
|
+
if (subcommand !== "policy") {
|
|
6
|
+
throw new Error(`Unknown command: ${["worktree", subcommand].filter(Boolean).join(" ")}`);
|
|
7
|
+
}
|
|
8
|
+
const policy = resolveWorktreePolicy();
|
|
9
|
+
if (argv.includes("--json")) {
|
|
10
|
+
console.log(JSON.stringify(policy, null, 2));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
console.log(`worktree.enforcement: ${policy.enforcement}`);
|
|
14
|
+
console.log(`worktree.default: ${policy.default}`);
|
|
15
|
+
console.log(`source: ${policy.source}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function runConfigCommand(
|
|
19
|
+
argv: string[],
|
|
20
|
+
subcommand: string | undefined,
|
|
21
|
+
thirdArg: string | undefined,
|
|
22
|
+
fourthArg: string | undefined,
|
|
23
|
+
) {
|
|
24
|
+
if (subcommand === "get") {
|
|
25
|
+
if (!thirdArg) throw new Error("Usage: gxpm config get <key>");
|
|
26
|
+
const result = getResolvedConfigValue({ key: thirdArg });
|
|
27
|
+
if (argv.includes("--raw")) {
|
|
28
|
+
console.log(String(result.value));
|
|
29
|
+
} else {
|
|
30
|
+
console.log(`${thirdArg}: ${JSON.stringify(result.value)}`);
|
|
31
|
+
console.log(`source: ${result.source}`);
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (subcommand === "set") {
|
|
37
|
+
if (!thirdArg || fourthArg === undefined) {
|
|
38
|
+
throw new Error("Usage: gxpm config set <key> <value> [--global]");
|
|
39
|
+
}
|
|
40
|
+
const scope = argv.includes("--global") ? "global" : "repo";
|
|
41
|
+
const path = setConfigValue({
|
|
42
|
+
scope,
|
|
43
|
+
key: thirdArg,
|
|
44
|
+
value: parseConfigValueLiteral(fourthArg),
|
|
45
|
+
});
|
|
46
|
+
console.log(`set ${thirdArg} = ${fourthArg} (${scope}); wrote ${path}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (subcommand === "list" || !subcommand) {
|
|
51
|
+
if (argv.includes("--json")) {
|
|
52
|
+
console.log(JSON.stringify(listConfigEntries(), null, 2));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const entry of listConfigEntries()) {
|
|
56
|
+
console.log(`${entry.key}: ${JSON.stringify(entry.value)} (${entry.source})`);
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
throw new Error(`Unknown config subcommand: ${subcommand}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseConfigValueLiteral(raw: string): unknown {
|
|
65
|
+
if (raw === "true") return true;
|
|
66
|
+
if (raw === "false") return false;
|
|
67
|
+
if (/^-?\d+$/.test(raw)) return Number(raw);
|
|
68
|
+
return raw;
|
|
69
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { parseWorkflow } from "../../core/dag-loader";
|
|
4
|
+
import { executeDag } from "../../core/dag-executor";
|
|
5
|
+
import type { DagNode, NodeOutput } from "../../core/dag-schemas";
|
|
6
|
+
|
|
7
|
+
export function runDagCommand(argv: string[]) {
|
|
8
|
+
const subcommand = argv[1];
|
|
9
|
+
const filePath = argv[2];
|
|
10
|
+
|
|
11
|
+
if (!subcommand) {
|
|
12
|
+
console.log("Usage: gxpm dag <validate|run> <workflow-file>");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (subcommand === "validate") {
|
|
17
|
+
if (!filePath) {
|
|
18
|
+
throw new Error("Usage: gxpm dag validate <workflow-file>");
|
|
19
|
+
}
|
|
20
|
+
const fullPath = resolve(filePath);
|
|
21
|
+
const content = readFileSync(fullPath, "utf8");
|
|
22
|
+
const raw = JSON.parse(content);
|
|
23
|
+
const result = parseWorkflow(raw, filePath);
|
|
24
|
+
|
|
25
|
+
if (result.error) {
|
|
26
|
+
console.error(`Validation failed: ${result.error.error}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
} else {
|
|
29
|
+
console.log(`Workflow '${result.workflow!.name}' is valid.`);
|
|
30
|
+
console.log(`Nodes: ${result.workflow!.nodes.length}`);
|
|
31
|
+
for (const node of result.workflow!.nodes) {
|
|
32
|
+
const deps = node.depends_on?.length ? ` (depends_on: ${node.depends_on.join(", ")})` : "";
|
|
33
|
+
console.log(` - ${node.id}: ${node.type}${deps}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (subcommand === "run") {
|
|
40
|
+
if (!filePath) {
|
|
41
|
+
throw new Error("Usage: gxpm dag run <workflow-file>");
|
|
42
|
+
}
|
|
43
|
+
const fullPath = resolve(filePath);
|
|
44
|
+
const content = readFileSync(fullPath, "utf8");
|
|
45
|
+
const raw = JSON.parse(content);
|
|
46
|
+
const result = parseWorkflow(raw, filePath);
|
|
47
|
+
|
|
48
|
+
if (result.error) {
|
|
49
|
+
throw new Error(`Validation failed: ${result.error.error}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const workflow = result.workflow!;
|
|
53
|
+
const startTime = Date.now();
|
|
54
|
+
|
|
55
|
+
// Simple echo executor for CLI demo
|
|
56
|
+
const executor = async (node: DagNode): Promise<NodeOutput> => {
|
|
57
|
+
console.log(`[${node.id}] Running ${node.type} node...`);
|
|
58
|
+
if ("bash" in node && node.bash) {
|
|
59
|
+
const proc = Bun.spawn(["bash", "-c", node.bash], {
|
|
60
|
+
cwd: process.cwd(),
|
|
61
|
+
stdout: "pipe",
|
|
62
|
+
stderr: "pipe",
|
|
63
|
+
});
|
|
64
|
+
const stdout = await new Response(proc.stdout).text();
|
|
65
|
+
const stderr = await new Response(proc.stderr).text();
|
|
66
|
+
const exitCode = proc.exitCode ?? 1;
|
|
67
|
+
if (exitCode !== 0) {
|
|
68
|
+
return { state: "failed", output: stdout, error: stderr || `exit code ${exitCode}` };
|
|
69
|
+
}
|
|
70
|
+
return { state: "completed", output: stdout.trim() };
|
|
71
|
+
}
|
|
72
|
+
if ("script" in node && node.script) {
|
|
73
|
+
const proc = Bun.spawn(["bash", node.script], {
|
|
74
|
+
cwd: process.cwd(),
|
|
75
|
+
stdout: "pipe",
|
|
76
|
+
stderr: "pipe",
|
|
77
|
+
});
|
|
78
|
+
const stdout = await new Response(proc.stdout).text();
|
|
79
|
+
const stderr = await new Response(proc.stderr).text();
|
|
80
|
+
const exitCode = proc.exitCode ?? 1;
|
|
81
|
+
if (exitCode !== 0) {
|
|
82
|
+
return { state: "failed", output: stdout, error: stderr || `exit code ${exitCode}` };
|
|
83
|
+
}
|
|
84
|
+
return { state: "completed", output: stdout.trim() };
|
|
85
|
+
}
|
|
86
|
+
if ("prompt" in node && node.prompt) {
|
|
87
|
+
return { state: "completed", output: node.prompt };
|
|
88
|
+
}
|
|
89
|
+
if ("phase" in node && node.phase) {
|
|
90
|
+
return { state: "completed", output: node.phase };
|
|
91
|
+
}
|
|
92
|
+
if (node.type === "approval") {
|
|
93
|
+
return { state: "completed", output: "approved" };
|
|
94
|
+
}
|
|
95
|
+
if (node.type === "cancel") {
|
|
96
|
+
return { state: "completed", output: "cancelled" };
|
|
97
|
+
}
|
|
98
|
+
return { state: "completed", output: "" };
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
executeDag(workflow.nodes, executor, {
|
|
102
|
+
workflowName: workflow.name,
|
|
103
|
+
onNodeStart: (id) => console.log(`[${id}] START`),
|
|
104
|
+
onNodeComplete: (id, output) =>
|
|
105
|
+
console.log(`[${id}] ${output.state.toUpperCase()}${output.error ? `: ${output.error}` : ""}`),
|
|
106
|
+
})
|
|
107
|
+
.then((result) => {
|
|
108
|
+
const duration = Date.now() - startTime;
|
|
109
|
+
console.log(`\nWorkflow ${result.success ? "succeeded" : "failed"} in ${duration}ms`);
|
|
110
|
+
if (!result.success && result.error) {
|
|
111
|
+
console.error(`Error: ${result.error}`);
|
|
112
|
+
}
|
|
113
|
+
for (const [id, output] of result.nodeOutputs) {
|
|
114
|
+
console.log(` ${id}: ${output.state}${output.output ? ` → ${output.output.slice(0, 80)}` : ""}`);
|
|
115
|
+
}
|
|
116
|
+
process.exit(result.success ? 0 : 1);
|
|
117
|
+
})
|
|
118
|
+
.catch((err) => {
|
|
119
|
+
console.error(`Execution error: ${err instanceof Error ? err.message : String(err)}`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
throw new Error(`Unknown dag subcommand: ${subcommand}`);
|
|
126
|
+
}
|