@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,647 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in WorktreeInitSteps for gxpm.
|
|
3
|
+
*
|
|
4
|
+
* Steps are registered by name and consumed by WorktreeInitPipeline.
|
|
5
|
+
* Each step is self-contained and reads configuration from gxpm config.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { dirname, join, relative, resolve, basename } from "node:path";
|
|
10
|
+
import { getResolvedConfigValue } from "./config";
|
|
11
|
+
import { writeWorktreeOwnerMarker, writeIssueContextMd } from "./worktree-owner";
|
|
12
|
+
import {
|
|
13
|
+
type WorktreeInitStep,
|
|
14
|
+
type WorktreeInitContext,
|
|
15
|
+
type WorktreeInitStepResult,
|
|
16
|
+
type PortAllocation,
|
|
17
|
+
registerBuiltinStep,
|
|
18
|
+
ensureSymlink,
|
|
19
|
+
setEnvVar,
|
|
20
|
+
readEnvFile,
|
|
21
|
+
safeWorktreeSlug,
|
|
22
|
+
hashSlot,
|
|
23
|
+
checkPortAvailable,
|
|
24
|
+
appendGitExcludeEntry,
|
|
25
|
+
} from "./worktree-init";
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Helpers
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
function resolveConfigStringArray(root: string, key: string): string[] {
|
|
32
|
+
const cfg = getResolvedConfigValue({ root, key });
|
|
33
|
+
if (Array.isArray(cfg.value)) return cfg.value as string[];
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveConfigString(root: string, key: string, fallback: string): string {
|
|
38
|
+
const cfg = getResolvedConfigValue({ root, key });
|
|
39
|
+
return typeof cfg.value === "string" ? cfg.value : fallback;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveConfigNumber(root: string, key: string, fallback: number): number {
|
|
43
|
+
const cfg = getResolvedConfigValue({ root, key });
|
|
44
|
+
return typeof cfg.value === "number" ? cfg.value : fallback;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function warnIfNotOk(result: { ok: boolean; warning?: string }, warnings: string[]): void {
|
|
48
|
+
if (!result.ok && result.warning) warnings.push(result.warning);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function findNodeModulesDirs(basePath: string, depth = 0): string[] {
|
|
52
|
+
const results: string[] = [];
|
|
53
|
+
if (depth > 10) return results;
|
|
54
|
+
let entries;
|
|
55
|
+
try {
|
|
56
|
+
entries = readdirSync(basePath, { withFileTypes: true });
|
|
57
|
+
} catch {
|
|
58
|
+
return results;
|
|
59
|
+
}
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
if (!entry.isDirectory()) continue;
|
|
62
|
+
const fullPath = join(basePath, entry.name);
|
|
63
|
+
if (entry.name === "node_modules") {
|
|
64
|
+
results.push(fullPath);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
results.push(...findNodeModulesDirs(fullPath, depth + 1));
|
|
68
|
+
}
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Step: symlink-node-modules
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
registerBuiltinStep("symlink-node-modules", () => ({
|
|
77
|
+
name: "symlink-node-modules",
|
|
78
|
+
run(ctx): WorktreeInitStepResult {
|
|
79
|
+
const mode = resolveConfigString(ctx.canonicalRepoPath, "worktree.nodeModulesMode", "symlink");
|
|
80
|
+
const warnings: string[] = [];
|
|
81
|
+
const canonicalNodeModules = findNodeModulesDirs(ctx.canonicalRepoPath);
|
|
82
|
+
|
|
83
|
+
for (const canonicalNm of canonicalNodeModules) {
|
|
84
|
+
const rel = relative(ctx.canonicalRepoPath, canonicalNm);
|
|
85
|
+
const worktreeNm = resolve(ctx.worktreePath, rel);
|
|
86
|
+
|
|
87
|
+
// Skip if this is the root node_modules and mode is overlay
|
|
88
|
+
if (rel === "node_modules" && mode === "overlay") {
|
|
89
|
+
setupRootNodeModulesOverlay(ctx.canonicalRepoPath, ctx.worktreePath, warnings);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let current;
|
|
94
|
+
try {
|
|
95
|
+
current = lstatSync(worktreeNm);
|
|
96
|
+
} catch {
|
|
97
|
+
current = undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (current) {
|
|
101
|
+
if (current.isSymbolicLink()) {
|
|
102
|
+
let pointsToCanonical = false;
|
|
103
|
+
try {
|
|
104
|
+
const existingTarget = resolve(ctx.worktreePath, readlinkSync(worktreeNm));
|
|
105
|
+
pointsToCanonical = realpathSync(existingTarget) === realpathSync(canonicalNm);
|
|
106
|
+
} catch {
|
|
107
|
+
pointsToCanonical = false;
|
|
108
|
+
}
|
|
109
|
+
if (!pointsToCanonical) {
|
|
110
|
+
try {
|
|
111
|
+
rmSync(worktreeNm, { force: true });
|
|
112
|
+
mkdirSync(dirname(worktreeNm), { recursive: true });
|
|
113
|
+
symlinkSync(canonicalNm, worktreeNm, "dir");
|
|
114
|
+
warnings.push(`Worktree ${rel} symlink pointed elsewhere; rewrote to canonical.`);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
117
|
+
warnings.push(`Worktree ${rel} symlink rewrite failed: ${message}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (current.isDirectory()) {
|
|
123
|
+
warnings.push(
|
|
124
|
+
`Worktree ${rel} exists as a real directory; leaving unchanged to avoid data loss.`,
|
|
125
|
+
);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
mkdirSync(dirname(worktreeNm), { recursive: true });
|
|
132
|
+
symlinkSync(canonicalNm, worktreeNm, "dir");
|
|
133
|
+
} catch (error) {
|
|
134
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
135
|
+
warnings.push(`Failed to create ${rel} symlink: ${message}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { ok: true, warnings };
|
|
140
|
+
},
|
|
141
|
+
}));
|
|
142
|
+
|
|
143
|
+
function setupRootNodeModulesOverlay(main: string, wt: string, warnings: string[]): void {
|
|
144
|
+
const mainNm = join(main, "node_modules");
|
|
145
|
+
const wtNm = join(wt, "node_modules");
|
|
146
|
+
|
|
147
|
+
if (!existsSync(mainNm)) return;
|
|
148
|
+
|
|
149
|
+
if (existsSync(wtNm)) {
|
|
150
|
+
if (lstatSync(wtNm).isSymbolicLink()) {
|
|
151
|
+
rmSync(wtNm, { force: true });
|
|
152
|
+
} else if (lstatSync(wtNm).isDirectory()) {
|
|
153
|
+
warnings.push("node_modules overlay conflict: real directory exists; skipping overlay");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
mkdirSync(join(wtNm, "@gxg"), { recursive: true });
|
|
159
|
+
|
|
160
|
+
// Symlink .bin
|
|
161
|
+
const binResult = ensureSymlink(join(mainNm, ".bin"), join(wtNm, ".bin"));
|
|
162
|
+
if (!binResult.ok) warnings.push(binResult.warning ?? ".bin symlink failed");
|
|
163
|
+
|
|
164
|
+
// Discover workspace local packages and symlink @gxg/*
|
|
165
|
+
const workspaceTargets: string[] = [];
|
|
166
|
+
if (existsSync(join(wt, "server"))) workspaceTargets.push(join(wt, "server"));
|
|
167
|
+
if (existsSync(join(wt, "apps", "web"))) workspaceTargets.push(join(wt, "apps", "web"));
|
|
168
|
+
if (existsSync(join(wt, "packages"))) {
|
|
169
|
+
for (const pkgDir of readdirSync(join(wt, "packages"))) {
|
|
170
|
+
const p = join(wt, "packages", pkgDir);
|
|
171
|
+
if (existsSync(p) && lstatSync(p).isDirectory()) workspaceTargets.push(p);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const target of workspaceTargets) {
|
|
176
|
+
const pkgJson = join(target, "package.json");
|
|
177
|
+
if (!existsSync(pkgJson)) continue;
|
|
178
|
+
try {
|
|
179
|
+
const pkg = JSON.parse(readFileSync(pkgJson, "utf8")) as { name?: string };
|
|
180
|
+
if (typeof pkg.name === "string" && pkg.name.startsWith("@gxg/")) {
|
|
181
|
+
const shortName = pkg.name.slice(5);
|
|
182
|
+
const dst = join(wtNm, "@gxg", shortName);
|
|
183
|
+
const r = ensureSymlink(target, dst);
|
|
184
|
+
if (!r.ok) warnings.push(r.warning ?? `${pkg.name} symlink failed`);
|
|
185
|
+
}
|
|
186
|
+
} catch {
|
|
187
|
+
// ignore parse errors
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Step: symlink-env
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
registerBuiltinStep("symlink-env", () => ({
|
|
197
|
+
name: "symlink-env",
|
|
198
|
+
run(ctx): WorktreeInitStepResult {
|
|
199
|
+
const warnings: string[] = [];
|
|
200
|
+
const envFiles = resolveConfigStringArray(ctx.canonicalRepoPath, "worktree.envFiles");
|
|
201
|
+
|
|
202
|
+
for (const subpath of envFiles) {
|
|
203
|
+
const src = join(ctx.canonicalRepoPath, subpath);
|
|
204
|
+
const dst = join(ctx.worktreePath, subpath);
|
|
205
|
+
if (!existsSync(src)) continue;
|
|
206
|
+
const r = ensureSymlink(src, dst);
|
|
207
|
+
warnIfNotOk(r, warnings);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return { ok: true, warnings };
|
|
211
|
+
},
|
|
212
|
+
}));
|
|
213
|
+
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
// Step: symlink-shared-dirs
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
registerBuiltinStep("symlink-shared-dirs", () => ({
|
|
219
|
+
name: "symlink-shared-dirs",
|
|
220
|
+
run(ctx): WorktreeInitStepResult {
|
|
221
|
+
const warnings: string[] = [];
|
|
222
|
+
const dirs = resolveConfigStringArray(ctx.canonicalRepoPath, "worktree.sharedDirs");
|
|
223
|
+
|
|
224
|
+
for (const subpath of dirs) {
|
|
225
|
+
const src = join(ctx.canonicalRepoPath, subpath);
|
|
226
|
+
const dst = join(ctx.worktreePath, subpath);
|
|
227
|
+
if (!existsSync(src)) continue;
|
|
228
|
+
const r = ensureSymlink(src, dst);
|
|
229
|
+
warnIfNotOk(r, warnings);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { ok: true, warnings };
|
|
233
|
+
},
|
|
234
|
+
}));
|
|
235
|
+
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
// Step: port-allocation
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
registerBuiltinStep("port-allocation", () => ({
|
|
241
|
+
name: "port-allocation",
|
|
242
|
+
run(ctx): WorktreeInitStepResult {
|
|
243
|
+
const root = ctx.canonicalRepoPath;
|
|
244
|
+
const maxSlot = resolveConfigNumber(root, "worktree.portSlots", 10);
|
|
245
|
+
const baseWeb = resolveConfigNumber(root, "worktree.portBaseWeb", 5173);
|
|
246
|
+
const baseServer = resolveConfigNumber(root, "worktree.portBaseServer", 3000);
|
|
247
|
+
const baseStudio = resolveConfigNumber(root, "worktree.portBaseStudio", 4111);
|
|
248
|
+
|
|
249
|
+
const wtName = basename(ctx.worktreePath);
|
|
250
|
+
const initialSlot = hashSlot(wtName, maxSlot);
|
|
251
|
+
|
|
252
|
+
let selectedSlot = 0;
|
|
253
|
+
const ownershipErrors: string[] = [];
|
|
254
|
+
|
|
255
|
+
for (let attempt = 0; attempt < maxSlot; attempt++) {
|
|
256
|
+
const candidate = ((initialSlot + attempt - 1) % maxSlot) + 1;
|
|
257
|
+
const offset = candidate * 10;
|
|
258
|
+
const webPort = baseWeb + offset;
|
|
259
|
+
const serverPort = baseServer + offset;
|
|
260
|
+
const studioPort = baseStudio + offset;
|
|
261
|
+
|
|
262
|
+
const webOk = checkPortAvailable(webPort, ctx.worktreePath);
|
|
263
|
+
const serverOk = checkPortAvailable(serverPort, ctx.worktreePath);
|
|
264
|
+
const studioOk = checkPortAvailable(studioPort, ctx.worktreePath);
|
|
265
|
+
|
|
266
|
+
if (webOk && serverOk && studioOk) {
|
|
267
|
+
selectedSlot = candidate;
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
if (!webOk) ownershipErrors.push(`slot ${candidate}: web port ${webPort} occupied`);
|
|
271
|
+
if (!serverOk) ownershipErrors.push(`slot ${candidate}: server port ${serverPort} occupied`);
|
|
272
|
+
if (!studioOk) ownershipErrors.push(`slot ${candidate}: studio port ${studioPort} occupied`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (selectedSlot === 0) {
|
|
276
|
+
return {
|
|
277
|
+
ok: false,
|
|
278
|
+
error: `No available worktree port slot (checked ${maxSlot}). ${ownershipErrors.join("; ")}`,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const offset = selectedSlot * 10;
|
|
283
|
+
const wtPathHash = createHash("sha256").update(ctx.worktreePath).digest("hex").slice(0, 8);
|
|
284
|
+
const queuePrefix = `gxpm_wt_${safeWorktreeSlug(wtName)}_${wtPathHash}_${selectedSlot}`;
|
|
285
|
+
|
|
286
|
+
ctx.ports = {
|
|
287
|
+
slot: selectedSlot,
|
|
288
|
+
webPort: baseWeb + offset,
|
|
289
|
+
serverPort: baseServer + offset,
|
|
290
|
+
studioPort: baseStudio + offset,
|
|
291
|
+
queuePrefix,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const warnings = selectedSlot !== initialSlot
|
|
295
|
+
? [`Slot ${initialSlot} occupied, fell back to slot ${selectedSlot}`]
|
|
296
|
+
: undefined;
|
|
297
|
+
|
|
298
|
+
return { ok: true, warnings };
|
|
299
|
+
},
|
|
300
|
+
}));
|
|
301
|
+
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Step: generate-env-local
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
|
|
306
|
+
registerBuiltinStep("generate-env-local", () => ({
|
|
307
|
+
name: "generate-env-local",
|
|
308
|
+
run(ctx): WorktreeInitStepResult {
|
|
309
|
+
if (!ctx.ports) {
|
|
310
|
+
return { ok: false, error: "port-allocation step must run before generate-env-local" };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const root = ctx.canonicalRepoPath;
|
|
314
|
+
const envLocalFiles = resolveConfigStringArray(root, "worktree.envLocalFiles");
|
|
315
|
+
const warnings: string[] = [];
|
|
316
|
+
|
|
317
|
+
// Inherit main repo .env.local values first
|
|
318
|
+
for (const subpath of envLocalFiles) {
|
|
319
|
+
const mainLocal = join(root, subpath);
|
|
320
|
+
const wtLocal = join(ctx.worktreePath, subpath);
|
|
321
|
+
if (existsSync(mainLocal)) {
|
|
322
|
+
const mainVars = readEnvFile(mainLocal);
|
|
323
|
+
for (const [k, v] of Object.entries(mainVars)) {
|
|
324
|
+
setEnvVar(wtLocal, k, v);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const { webPort, serverPort, studioPort, queuePrefix } = ctx.ports;
|
|
330
|
+
|
|
331
|
+
// Web .env.local
|
|
332
|
+
const webEnvLocal = join(ctx.worktreePath, "apps", "web", ".env.local");
|
|
333
|
+
setEnvVar(webEnvLocal, "VITE_DEV_PORT", String(webPort));
|
|
334
|
+
setEnvVar(webEnvLocal, "VITE_API_BASE_URL", `http://127.0.0.1:${serverPort}`);
|
|
335
|
+
|
|
336
|
+
// Server .env.local
|
|
337
|
+
const serverEnvLocal = join(ctx.worktreePath, "server", ".env.local");
|
|
338
|
+
setEnvVar(serverEnvLocal, "PORT", String(serverPort));
|
|
339
|
+
setEnvVar(serverEnvLocal, "HOST", "0.0.0.0");
|
|
340
|
+
setEnvVar(serverEnvLocal, "STUDIO_PORT", String(studioPort));
|
|
341
|
+
setEnvVar(serverEnvLocal, "BULLMQ_PREFIX", queuePrefix);
|
|
342
|
+
setEnvVar(serverEnvLocal, "DEV_CORS_EXTRA_ORIGINS", `http://localhost:${webPort},http://127.0.0.1:${webPort}`);
|
|
343
|
+
|
|
344
|
+
// Git/worktree identity
|
|
345
|
+
const branchResult = Bun.spawnSync({
|
|
346
|
+
cmd: ["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
347
|
+
cwd: ctx.worktreePath,
|
|
348
|
+
stdout: "pipe",
|
|
349
|
+
stderr: "pipe",
|
|
350
|
+
});
|
|
351
|
+
const branch = branchResult.exitCode === 0 ? branchResult.stdout.toString().trim() : "unknown";
|
|
352
|
+
|
|
353
|
+
const shaResult = Bun.spawnSync({
|
|
354
|
+
cmd: ["git", "rev-parse", "--short", "HEAD"],
|
|
355
|
+
cwd: ctx.worktreePath,
|
|
356
|
+
stdout: "pipe",
|
|
357
|
+
stderr: "pipe",
|
|
358
|
+
});
|
|
359
|
+
const sha = shaResult.exitCode === 0 ? shaResult.stdout.toString().trim() : "unknown";
|
|
360
|
+
|
|
361
|
+
setEnvVar(serverEnvLocal, "GIT_BRANCH", branch);
|
|
362
|
+
setEnvVar(serverEnvLocal, "GIT_SHA", sha);
|
|
363
|
+
setEnvVar(serverEnvLocal, "WORKTREE_NAME", basename(ctx.worktreePath));
|
|
364
|
+
|
|
365
|
+
return { ok: true, warnings };
|
|
366
|
+
},
|
|
367
|
+
}));
|
|
368
|
+
|
|
369
|
+
// ---------------------------------------------------------------------------
|
|
370
|
+
// Step: generate-warp-md
|
|
371
|
+
// ---------------------------------------------------------------------------
|
|
372
|
+
|
|
373
|
+
registerBuiltinStep("generate-warp-md", () => ({
|
|
374
|
+
name: "generate-warp-md",
|
|
375
|
+
run(ctx): WorktreeInitStepResult {
|
|
376
|
+
if (!ctx.ports) {
|
|
377
|
+
return { ok: false, error: "port-allocation step must run before generate-warp-md" };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const root = ctx.canonicalRepoPath;
|
|
381
|
+
const warpPath = resolveConfigString(root, "worktree.warpMdPath", "warp.md");
|
|
382
|
+
const dst = join(ctx.worktreePath, warpPath);
|
|
383
|
+
|
|
384
|
+
const branchResult = Bun.spawnSync({
|
|
385
|
+
cmd: ["git", "branch", "--show-current"],
|
|
386
|
+
cwd: ctx.worktreePath,
|
|
387
|
+
stdout: "pipe",
|
|
388
|
+
stderr: "pipe",
|
|
389
|
+
});
|
|
390
|
+
const branch = branchResult.exitCode === 0 ? branchResult.stdout.toString().trim() : "unknown";
|
|
391
|
+
|
|
392
|
+
const { slot, webPort, serverPort, studioPort, queuePrefix } = ctx.ports;
|
|
393
|
+
|
|
394
|
+
const content = `# Worktree Port Configuration
|
|
395
|
+
|
|
396
|
+
This worktree (Slot ${slot}) uses isolated ports and queue prefix, separated from the main repo and other worktrees for parallel debugging.
|
|
397
|
+
|
|
398
|
+
## Port Allocation
|
|
399
|
+
|
|
400
|
+
| Service | Port | URL |
|
|
401
|
+
|---------|------|-----|
|
|
402
|
+
| Web (Vite) | ${webPort} | http://127.0.0.1:${webPort} |
|
|
403
|
+
| API Server | ${serverPort} | http://127.0.0.1:${serverPort} |
|
|
404
|
+
| Mastra Studio | ${studioPort} | http://127.0.0.1:${studioPort} (debug only, requires explicit authorization) |
|
|
405
|
+
| BullMQ Prefix | ${queuePrefix} | Redis queue isolation |
|
|
406
|
+
|
|
407
|
+
## Branch Baseline Traceability
|
|
408
|
+
|
|
409
|
+
| Field | Value |
|
|
410
|
+
|-------|-------|
|
|
411
|
+
| Current branch | ${branch} |
|
|
412
|
+
| Baseline ref | ${ctx.baseBranch ?? "unknown"} |
|
|
413
|
+
| Baseline sha | ${ctx.baseSha ?? "unknown"} |
|
|
414
|
+
|
|
415
|
+
## Agent Rules
|
|
416
|
+
|
|
417
|
+
- This worktree uses the ports above. **Do not** use main repo defaults (${resolveConfigNumber(root, "worktree.portBaseWeb", 5173)} / ${resolveConfigNumber(root, "worktree.portBaseServer", 3000)} / ${resolveConfigNumber(root, "worktree.portBaseStudio", 4111)}).
|
|
418
|
+
- Ports and queue isolation are injected via \`apps/web/.env.local\` and \`server/.env.local\`.
|
|
419
|
+
- Start services with \`npm run dev\` — **do not** append \`--port\`.
|
|
420
|
+
- Do not override PORT, VITE_DEV_PORT, STUDIO_PORT, or BULLMQ_PREFIX in .env.local.
|
|
421
|
+
- Report URLs using the ports in this table, not defaults.
|
|
422
|
+
- Do **not** start Mastra Studio without explicit user authorization.
|
|
423
|
+
`;
|
|
424
|
+
|
|
425
|
+
writeFileSync(dst, content);
|
|
426
|
+
return { ok: true };
|
|
427
|
+
},
|
|
428
|
+
}));
|
|
429
|
+
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
431
|
+
// Step: generate-launch-json
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
433
|
+
|
|
434
|
+
registerBuiltinStep("generate-launch-json", () => ({
|
|
435
|
+
name: "generate-launch-json",
|
|
436
|
+
run(ctx): WorktreeInitStepResult {
|
|
437
|
+
if (!ctx.ports) {
|
|
438
|
+
return { ok: false, error: "port-allocation step must run before generate-launch-json" };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const root = ctx.canonicalRepoPath;
|
|
442
|
+
const launchPath = resolveConfigString(root, "worktree.launchJsonPath", ".claude/launch.json");
|
|
443
|
+
const dst = join(ctx.worktreePath, launchPath);
|
|
444
|
+
|
|
445
|
+
const { webPort, serverPort } = ctx.ports;
|
|
446
|
+
|
|
447
|
+
const config = {
|
|
448
|
+
version: "0.0.2",
|
|
449
|
+
configurations: [
|
|
450
|
+
{
|
|
451
|
+
name: "dev",
|
|
452
|
+
runtimeExecutable: "npm",
|
|
453
|
+
runtimeArgs: ["run", "dev"],
|
|
454
|
+
port: webPort,
|
|
455
|
+
autoPort: false,
|
|
456
|
+
env: {
|
|
457
|
+
PORT: String(serverPort),
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
],
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
464
|
+
writeFileSync(dst, JSON.stringify(config, null, 2) + "\n");
|
|
465
|
+
|
|
466
|
+
// Exclude from git to avoid dirty state
|
|
467
|
+
appendGitExcludeEntry(ctx.worktreePath, launchPath);
|
|
468
|
+
|
|
469
|
+
return { ok: true };
|
|
470
|
+
},
|
|
471
|
+
}));
|
|
472
|
+
|
|
473
|
+
// ---------------------------------------------------------------------------
|
|
474
|
+
// Step: git-hooks
|
|
475
|
+
// ---------------------------------------------------------------------------
|
|
476
|
+
|
|
477
|
+
registerBuiltinStep("git-hooks", () => ({
|
|
478
|
+
name: "git-hooks",
|
|
479
|
+
run(ctx): WorktreeInitStepResult {
|
|
480
|
+
const root = ctx.canonicalRepoPath;
|
|
481
|
+
const hooksScript = resolveConfigString(root, "worktree.hooksScript", "");
|
|
482
|
+
if (!hooksScript) return { ok: true }; // nothing to do
|
|
483
|
+
|
|
484
|
+
const scriptPath = join(ctx.worktreePath, hooksScript);
|
|
485
|
+
if (!existsSync(scriptPath)) {
|
|
486
|
+
return { ok: false, error: `hooks script not found: ${scriptPath}` };
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const result = Bun.spawnSync({
|
|
490
|
+
cmd: ["bash", scriptPath],
|
|
491
|
+
cwd: ctx.worktreePath,
|
|
492
|
+
stdout: "pipe",
|
|
493
|
+
stderr: "pipe",
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
if (result.exitCode !== 0) {
|
|
497
|
+
return { ok: false, error: result.stderr.toString().trim() || "git-hooks script failed" };
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return { ok: true };
|
|
501
|
+
},
|
|
502
|
+
}));
|
|
503
|
+
|
|
504
|
+
// ---------------------------------------------------------------------------
|
|
505
|
+
// Step: baseline-freshness
|
|
506
|
+
// ---------------------------------------------------------------------------
|
|
507
|
+
|
|
508
|
+
registerBuiltinStep("baseline-freshness", () => ({
|
|
509
|
+
name: "baseline-freshness",
|
|
510
|
+
run(ctx): WorktreeInitStepResult {
|
|
511
|
+
const root = ctx.canonicalRepoPath;
|
|
512
|
+
const baseBranch = ctx.baseBranch ?? resolveConfigString(root, "worktree.baseBranch", "main");
|
|
513
|
+
const script = resolveConfigString(root, "worktree.baselineFreshnessScript", "");
|
|
514
|
+
|
|
515
|
+
if (script) {
|
|
516
|
+
const scriptPath = join(ctx.worktreePath, script);
|
|
517
|
+
if (!existsSync(scriptPath)) {
|
|
518
|
+
return { ok: false, error: `baseline freshness script not found: ${scriptPath}` };
|
|
519
|
+
}
|
|
520
|
+
const result = Bun.spawnSync({
|
|
521
|
+
cmd: ["node", scriptPath, "--no-fetch"],
|
|
522
|
+
cwd: ctx.worktreePath,
|
|
523
|
+
stdout: "pipe",
|
|
524
|
+
stderr: "pipe",
|
|
525
|
+
});
|
|
526
|
+
if (result.exitCode !== 0) {
|
|
527
|
+
return {
|
|
528
|
+
ok: true,
|
|
529
|
+
warnings: [
|
|
530
|
+
`Worktree baseline may be behind origin/${baseBranch}. Consider rebase or merge to reduce conflict cost.`,
|
|
531
|
+
],
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
const out = result.stdout.toString();
|
|
535
|
+
if (out.includes("Baseline drift detected")) {
|
|
536
|
+
return {
|
|
537
|
+
ok: true,
|
|
538
|
+
warnings: [
|
|
539
|
+
`Worktree baseline is behind origin/${baseBranch}. Early sync recommended to avoid rising conflict cost.`,
|
|
540
|
+
],
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
return { ok: true };
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Built-in lightweight check
|
|
547
|
+
try {
|
|
548
|
+
const result = Bun.spawnSync({
|
|
549
|
+
cmd: ["git", "merge-base", "--is-ancestor", `origin/${baseBranch}`, ctx.branchName],
|
|
550
|
+
cwd: ctx.worktreePath,
|
|
551
|
+
stdout: "pipe",
|
|
552
|
+
stderr: "pipe",
|
|
553
|
+
});
|
|
554
|
+
if (result.exitCode !== 0) {
|
|
555
|
+
return {
|
|
556
|
+
ok: true,
|
|
557
|
+
warnings: [
|
|
558
|
+
`Worktree branch '${ctx.branchName}' is not based on '${baseBranch}'. Recreate if needed.`,
|
|
559
|
+
],
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
} catch {
|
|
563
|
+
// non-blocking
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return { ok: true };
|
|
567
|
+
},
|
|
568
|
+
}));
|
|
569
|
+
|
|
570
|
+
// ---------------------------------------------------------------------------
|
|
571
|
+
// Step: owner-marker
|
|
572
|
+
// ---------------------------------------------------------------------------
|
|
573
|
+
|
|
574
|
+
registerBuiltinStep("owner-marker", {
|
|
575
|
+
name: "owner-marker",
|
|
576
|
+
run(ctx): WorktreeInitStepResult {
|
|
577
|
+
try {
|
|
578
|
+
writeWorktreeOwnerMarker(ctx.worktreePath, {
|
|
579
|
+
ownerIssueId: ctx.issueId,
|
|
580
|
+
linkedIssues: [],
|
|
581
|
+
branchName: ctx.branchName,
|
|
582
|
+
workspacePath: ctx.worktreePath,
|
|
583
|
+
});
|
|
584
|
+
return { ok: true };
|
|
585
|
+
} catch (err) {
|
|
586
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
587
|
+
return { ok: false, error: message };
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// ---------------------------------------------------------------------------
|
|
593
|
+
// Step: issue-context
|
|
594
|
+
// ---------------------------------------------------------------------------
|
|
595
|
+
|
|
596
|
+
registerBuiltinStep("issue-context", {
|
|
597
|
+
name: "issue-context",
|
|
598
|
+
run(ctx): WorktreeInitStepResult {
|
|
599
|
+
try {
|
|
600
|
+
writeIssueContextMd(ctx.worktreePath, {
|
|
601
|
+
issueId: ctx.issueId,
|
|
602
|
+
currentPhase: "dispatch",
|
|
603
|
+
branchName: ctx.branchName,
|
|
604
|
+
workspacePath: ctx.worktreePath,
|
|
605
|
+
updatedAt: new Date().toISOString(),
|
|
606
|
+
});
|
|
607
|
+
return { ok: true };
|
|
608
|
+
} catch (err) {
|
|
609
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
610
|
+
return { ok: false, error: message };
|
|
611
|
+
}
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// ---------------------------------------------------------------------------
|
|
616
|
+
// Step: dep-check (fail-fast dependency verification)
|
|
617
|
+
// ---------------------------------------------------------------------------
|
|
618
|
+
|
|
619
|
+
registerBuiltinStep("dep-check", () => ({
|
|
620
|
+
name: "dep-check",
|
|
621
|
+
run(ctx): WorktreeInitStepResult {
|
|
622
|
+
const root = ctx.canonicalRepoPath;
|
|
623
|
+
const warnings: string[] = [];
|
|
624
|
+
|
|
625
|
+
// Check main repo node_modules exists
|
|
626
|
+
const mainNm = join(root, "node_modules");
|
|
627
|
+
if (!existsSync(mainNm)) {
|
|
628
|
+
return {
|
|
629
|
+
ok: false,
|
|
630
|
+
error: `Main repo node_modules missing. Run 'npm install' in ${root} first.`,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Check workspace sub-dirs
|
|
635
|
+
const subDirs = ["server/node_modules", "apps/web/node_modules"];
|
|
636
|
+
for (const sub of subDirs) {
|
|
637
|
+
const src = join(root, sub);
|
|
638
|
+
const dst = join(ctx.worktreePath, sub);
|
|
639
|
+
if (existsSync(src) && !existsSync(dst)) {
|
|
640
|
+
const r = ensureSymlink(src, dst);
|
|
641
|
+
warnIfNotOk(r, warnings);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return { ok: true, warnings };
|
|
646
|
+
},
|
|
647
|
+
}));
|