@imdeadpool/guardex 7.0.41 → 7.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/README.md +94 -13
- package/package.json +3 -1
- package/skills/gitguardex/SKILL.md +13 -0
- package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
- package/skills/gx-act/SKILL.md +82 -0
- package/src/agents/cleanup-sessions.js +126 -0
- package/src/agents/finish.js +172 -0
- package/src/agents/inspect.js +202 -0
- package/src/agents/launch.js +249 -0
- package/src/agents/registry.js +133 -0
- package/src/agents/selection-panel.js +571 -0
- package/src/agents/sessions.js +151 -0
- package/src/agents/start.js +591 -0
- package/src/agents/status.js +146 -0
- package/src/agents/terminal.js +152 -0
- package/src/budget/index.js +344 -0
- package/src/ci-init/index.js +265 -0
- package/src/cli/args.js +357 -3
- package/src/cli/commands/agents.js +364 -0
- package/src/cli/commands/bootstrap.js +92 -0
- package/src/cli/commands/branch.js +127 -0
- package/src/cli/commands/claude.js +674 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/finish.js +26 -0
- package/src/cli/commands/mcp.js +122 -0
- package/src/cli/commands/misc.js +304 -0
- package/src/cli/commands/pr.js +439 -0
- package/src/cli/commands/prompt.js +92 -0
- package/src/cli/commands/release.js +305 -0
- package/src/cli/commands/report.js +244 -0
- package/src/cli/commands/review.js +32 -0
- package/src/cli/commands/setup.js +242 -0
- package/src/cli/commands/status.js +338 -0
- package/src/cli/commands/watch.js +234 -0
- package/src/cli/main.js +85 -3613
- package/src/cli/shared/repo-env.js +161 -0
- package/src/cli/shared/sandbox.js +417 -0
- package/src/cli/shared/scaffolding.js +535 -0
- package/src/cli/shared/toolchain-shims.js +420 -0
- package/src/cockpit/action-runner.js +3 -0
- package/src/cockpit/actions.js +80 -0
- package/src/cockpit/control.js +1121 -0
- package/src/cockpit/index.js +426 -0
- package/src/cockpit/kitty-layout.js +549 -0
- package/src/cockpit/kitty-tree.js +144 -0
- package/src/cockpit/logs-reader.js +182 -0
- package/src/cockpit/menu.js +204 -0
- package/src/cockpit/pane-actions.js +597 -0
- package/src/cockpit/pane-menu.js +387 -0
- package/src/cockpit/projects-finder.js +178 -0
- package/src/cockpit/render.js +215 -0
- package/src/cockpit/settings-render.js +128 -0
- package/src/cockpit/settings.js +124 -0
- package/src/cockpit/shortcuts.js +24 -0
- package/src/cockpit/sidebar.js +311 -0
- package/src/cockpit/state.js +72 -0
- package/src/cockpit/theme.js +128 -0
- package/src/cockpit/welcome.js +266 -0
- package/src/context.js +304 -43
- package/src/core/runtime.js +6 -1
- package/src/doctor/index.js +45 -15
- package/src/finish/index.js +186 -7
- package/src/finish/preflight.js +177 -0
- package/src/finish/review-gate.js +182 -0
- package/src/git/index.js +511 -4
- package/src/hooks/index.js +0 -64
- package/src/kitty/command.js +101 -0
- package/src/kitty/runtime.js +250 -0
- package/src/mcp/collect.js +370 -0
- package/src/mcp/server.js +157 -0
- package/src/output/index.js +68 -2
- package/src/pr-review.js +264 -0
- package/src/pr.js +381 -0
- package/src/sandbox/index.js +13 -2
- package/src/scaffold/agent-worktree-prep.js +213 -0
- package/src/scaffold/index.js +127 -10
- package/src/speckit/index.js +226 -0
- package/src/submodule/index.js +288 -0
- package/src/terminal/index.js +45 -0
- package/src/terminal/kitty.js +622 -0
- package/src/terminal/tmux.js +125 -0
- package/src/tmux/command.js +27 -0
- package/src/tmux/session.js +89 -0
- package/src/toolchain/index.js +20 -0
- package/templates/AGENTS.monorepo-apps.md +26 -0
- package/templates/AGENTS.multiagent-safety.md +63 -323
- package/templates/AGENTS.multiagent-safety.min.md +11 -0
- package/templates/codex/skills/gitguardex/SKILL.md +2 -0
- package/templates/codex/skills/gx-act/SKILL.md +82 -0
- package/templates/githooks/pre-commit +44 -20
- package/templates/github/workflows/README.md +87 -0
- package/templates/github/workflows/ci-full.yml +55 -0
- package/templates/github/workflows/ci.yml +56 -0
- package/templates/github/workflows/cr.yml +20 -1
- package/templates/scripts/agent-branch-finish.sh +519 -23
- package/templates/scripts/agent-branch-merge.sh +4 -1
- package/templates/scripts/agent-branch-start.sh +176 -24
- package/templates/scripts/agent-preflight.sh +115 -0
- package/templates/scripts/agent-worktree-prune.sh +96 -5
- package/templates/scripts/codex-agent.sh +41 -97
- package/templates/scripts/openspec/init-plan-workspace.sh +43 -0
- package/templates/scripts/review-bot-watch.sh +31 -2
- package/templates/scripts/agent-session-state.js +0 -171
- package/templates/scripts/install-vscode-active-agents-extension.js +0 -135
- package/templates/vscode/guardex-active-agents/README.md +0 -34
- package/templates/vscode/guardex-active-agents/extension.js +0 -3782
- package/templates/vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json +0 -54
- package/templates/vscode/guardex-active-agents/fileicons/icons/agent.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/branch.svg +0 -7
- package/templates/vscode/guardex-active-agents/fileicons/icons/config.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/hook.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/openspec.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/plan.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/spec.svg +0 -5
- package/templates/vscode/guardex-active-agents/icon.png +0 -0
- package/templates/vscode/guardex-active-agents/media/active-agents-hivemind.svg +0 -14
- package/templates/vscode/guardex-active-agents/package.json +0 -169
- package/templates/vscode/guardex-active-agents/session-schema.js +0 -1348
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Shared environment / repo-toggle / origin-introspection helpers used by
|
|
2
|
+
// multiple subcommand modules. Pure code-motion from src/cli/main.js — no
|
|
3
|
+
// behavior changes.
|
|
4
|
+
const {
|
|
5
|
+
fs,
|
|
6
|
+
path,
|
|
7
|
+
GUARDEX_REPO_TOGGLE_ENV,
|
|
8
|
+
envFlagIsTruthy,
|
|
9
|
+
} = require('../../context');
|
|
10
|
+
const { run } = require('../../core/runtime');
|
|
11
|
+
const { readGitConfig } = require('../../git');
|
|
12
|
+
|
|
13
|
+
function todayDateStamp() {
|
|
14
|
+
return new Date().toISOString().slice(0, 10);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function inferGithubRepoFromOrigin(repoRoot) {
|
|
18
|
+
const rawOrigin = readGitConfig(repoRoot, 'remote.origin.url');
|
|
19
|
+
if (!rawOrigin) return '';
|
|
20
|
+
|
|
21
|
+
const httpsMatch = rawOrigin.match(/github\.com[:/](.+?)(?:\.git)?$/i);
|
|
22
|
+
if (!httpsMatch) return '';
|
|
23
|
+
const slug = (httpsMatch[1] || '').replace(/^\/+/, '').trim();
|
|
24
|
+
if (!slug || !slug.includes('/')) return '';
|
|
25
|
+
return `github.com/${slug}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function inferGithubRepoSlug(rawValue) {
|
|
29
|
+
const raw = String(rawValue || '').trim();
|
|
30
|
+
if (!raw) return '';
|
|
31
|
+
const match = raw.match(/github\.com[:/](.+?)(?:\.git)?$/i);
|
|
32
|
+
if (!match) return '';
|
|
33
|
+
const slug = String(match[1] || '')
|
|
34
|
+
.replace(/^\/+/, '')
|
|
35
|
+
.replace(/^github\.com\//i, '')
|
|
36
|
+
.trim();
|
|
37
|
+
if (!slug || !slug.includes('/')) return '';
|
|
38
|
+
return slug;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function originRemoteLooksLikeGithub(repoRoot) {
|
|
42
|
+
const originUrl = readGitConfig(repoRoot, 'remote.origin.url');
|
|
43
|
+
if (!originUrl) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return /github\.com[:/]/i.test(originUrl);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isInteractiveTerminal() {
|
|
50
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function legacyDefaultStatusEnabled() {
|
|
54
|
+
return envFlagIsTruthy(process.env.GUARDEX_LEGACY_STATUS);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function defaultCockpitDisabled() {
|
|
58
|
+
const raw = process.env.GUARDEX_DEFAULT_COCKPIT;
|
|
59
|
+
if (raw == null) return false;
|
|
60
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
61
|
+
return ['0', 'false', 'no', 'off'].includes(normalized);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseAutoApproval(name) {
|
|
65
|
+
const raw = process.env[name];
|
|
66
|
+
if (raw == null) return null;
|
|
67
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
68
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
|
|
69
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function parseBooleanLike(raw) {
|
|
74
|
+
if (raw == null) return null;
|
|
75
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
76
|
+
if (!normalized) return null;
|
|
77
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
|
|
78
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseDotenvAssignmentValue(raw) {
|
|
83
|
+
let value = String(raw || '').trim();
|
|
84
|
+
if (!value) return '';
|
|
85
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\''))) {
|
|
86
|
+
return value.slice(1, -1).trim();
|
|
87
|
+
}
|
|
88
|
+
value = value.replace(/\s+#.*$/, '').trim();
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readRepoDotenvValue(repoRoot, name) {
|
|
93
|
+
const envPath = path.join(repoRoot, '.env');
|
|
94
|
+
if (!fs.existsSync(envPath)) return null;
|
|
95
|
+
const pattern = new RegExp(`^\\s*(?:export\\s+)?${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*=\\s*(.*)$`);
|
|
96
|
+
const lines = fs.readFileSync(envPath, 'utf8').split(/\r?\n/);
|
|
97
|
+
for (const line of lines) {
|
|
98
|
+
const trimmed = line.trim();
|
|
99
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
100
|
+
const match = line.match(pattern);
|
|
101
|
+
if (!match) continue;
|
|
102
|
+
return parseDotenvAssignmentValue(match[1]);
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function resolveGuardexRepoToggle(repoRoot, env = process.env) {
|
|
108
|
+
const envRaw = env[GUARDEX_REPO_TOGGLE_ENV];
|
|
109
|
+
const envEnabled = parseBooleanLike(envRaw);
|
|
110
|
+
if (envEnabled !== null) {
|
|
111
|
+
return {
|
|
112
|
+
enabled: envEnabled,
|
|
113
|
+
source: 'process environment',
|
|
114
|
+
raw: String(envRaw).trim(),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const dotenvRaw = readRepoDotenvValue(repoRoot, GUARDEX_REPO_TOGGLE_ENV);
|
|
119
|
+
const dotenvEnabled = parseBooleanLike(dotenvRaw);
|
|
120
|
+
if (dotenvEnabled !== null) {
|
|
121
|
+
return {
|
|
122
|
+
enabled: dotenvEnabled,
|
|
123
|
+
source: 'repo .env',
|
|
124
|
+
raw: String(dotenvRaw).trim(),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
enabled: true,
|
|
130
|
+
source: 'default',
|
|
131
|
+
raw: '',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function describeGuardexRepoToggle(toggle) {
|
|
136
|
+
if (!toggle || toggle.source === 'default') {
|
|
137
|
+
return 'default enabled mode';
|
|
138
|
+
}
|
|
139
|
+
return `${toggle.source} (${GUARDEX_REPO_TOGGLE_ENV}=${toggle.raw})`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function isCommandAvailable(commandName) {
|
|
143
|
+
return run('which', [commandName]).status === 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
todayDateStamp,
|
|
148
|
+
inferGithubRepoFromOrigin,
|
|
149
|
+
inferGithubRepoSlug,
|
|
150
|
+
originRemoteLooksLikeGithub,
|
|
151
|
+
isInteractiveTerminal,
|
|
152
|
+
legacyDefaultStatusEnabled,
|
|
153
|
+
defaultCockpitDisabled,
|
|
154
|
+
parseAutoApproval,
|
|
155
|
+
parseBooleanLike,
|
|
156
|
+
parseDotenvAssignmentValue,
|
|
157
|
+
readRepoDotenvValue,
|
|
158
|
+
resolveGuardexRepoToggle,
|
|
159
|
+
describeGuardexRepoToggle,
|
|
160
|
+
isCommandAvailable,
|
|
161
|
+
};
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
// Sandbox/parent-workspace/protected-base helpers used by setup + doctor when
|
|
2
|
+
// a write would land on a protected base branch. Pure code-motion from
|
|
3
|
+
// src/cli/main.js — no behavior changes.
|
|
4
|
+
const {
|
|
5
|
+
fs,
|
|
6
|
+
path,
|
|
7
|
+
TOOL_NAME,
|
|
8
|
+
SHORT_TOOL_NAME,
|
|
9
|
+
LOCK_FILE_RELATIVE,
|
|
10
|
+
AGENT_WORKTREE_RELATIVE_DIRS,
|
|
11
|
+
defaultAgentWorktreeRelativeDir,
|
|
12
|
+
} = require('../../context');
|
|
13
|
+
const {
|
|
14
|
+
resolveRepoRoot,
|
|
15
|
+
gitRefExists,
|
|
16
|
+
currentBranchName,
|
|
17
|
+
readProtectedBranches,
|
|
18
|
+
ensureSetupProtectedBranches,
|
|
19
|
+
ensureSubmoduleAutoSync,
|
|
20
|
+
ensureRepoBranch,
|
|
21
|
+
} = require('../../git');
|
|
22
|
+
const sandboxModule = require('../../sandbox');
|
|
23
|
+
// These helpers are identical to sandbox/index.js; delegate to keep one source.
|
|
24
|
+
const {
|
|
25
|
+
protectedBaseWriteBlock,
|
|
26
|
+
extractAgentBranchStartMetadata,
|
|
27
|
+
resolveSandboxTarget,
|
|
28
|
+
isSpawnFailure,
|
|
29
|
+
cleanupProtectedBaseSandbox,
|
|
30
|
+
} = sandboxModule;
|
|
31
|
+
const doctorModule = require('../../doctor');
|
|
32
|
+
const { prepareAgentWorktree } = require('../../scaffold/agent-worktree-prep');
|
|
33
|
+
|
|
34
|
+
function formatWorktreePrepOps(operations) {
|
|
35
|
+
if (!operations || operations.length === 0) return '';
|
|
36
|
+
return operations
|
|
37
|
+
.map((op) => `[agent-branch-start] worktree-prep ${op.status} ${op.file}${op.note ? ' — ' + op.note : ''}`)
|
|
38
|
+
.join('\n') + '\n';
|
|
39
|
+
}
|
|
40
|
+
const {
|
|
41
|
+
run,
|
|
42
|
+
runPackageAsset,
|
|
43
|
+
} = require('../../core/runtime');
|
|
44
|
+
const { printOperations } = require('../../scaffold');
|
|
45
|
+
const {
|
|
46
|
+
appendForceArgs,
|
|
47
|
+
runInstallInternal,
|
|
48
|
+
runFixInternal,
|
|
49
|
+
runScanInternal,
|
|
50
|
+
printScanResult,
|
|
51
|
+
printWorktreePruneSummary,
|
|
52
|
+
} = require('./scaffolding');
|
|
53
|
+
|
|
54
|
+
function normalizeWorkspacePath(relativePath) {
|
|
55
|
+
return String(relativePath || '.').replace(/\\/g, '/');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildParentWorkspaceView(repoRoot) {
|
|
59
|
+
const parentDir = path.dirname(repoRoot);
|
|
60
|
+
const workspaceFileName = `${path.basename(repoRoot)}-branches.code-workspace`;
|
|
61
|
+
const workspacePath = path.join(parentDir, workspaceFileName);
|
|
62
|
+
const repoRelativePath = normalizeWorkspacePath(path.relative(parentDir, repoRoot) || '.');
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
workspacePath,
|
|
66
|
+
payload: {
|
|
67
|
+
folders: [
|
|
68
|
+
{ path: repoRelativePath },
|
|
69
|
+
...AGENT_WORKTREE_RELATIVE_DIRS.map((relativeDir) => ({
|
|
70
|
+
path: normalizeWorkspacePath(path.join(repoRelativePath === '.' ? '' : repoRelativePath, relativeDir)),
|
|
71
|
+
})),
|
|
72
|
+
],
|
|
73
|
+
settings: {
|
|
74
|
+
'scm.alwaysShowRepositories': true,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function ensureParentWorkspaceView(repoRoot, dryRun) {
|
|
81
|
+
const { workspacePath, payload } = buildParentWorkspaceView(repoRoot);
|
|
82
|
+
const operationFile = path.relative(repoRoot, workspacePath) || path.basename(workspacePath);
|
|
83
|
+
const nextContent = `${JSON.stringify(payload, null, 2)}\n`;
|
|
84
|
+
const note = 'parent VS Code workspace view';
|
|
85
|
+
|
|
86
|
+
if (!fs.existsSync(workspacePath)) {
|
|
87
|
+
if (!dryRun) {
|
|
88
|
+
fs.writeFileSync(workspacePath, nextContent, 'utf8');
|
|
89
|
+
}
|
|
90
|
+
return { status: dryRun ? 'would-create' : 'created', file: operationFile, note };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const currentContent = fs.readFileSync(workspacePath, 'utf8');
|
|
94
|
+
if (currentContent === nextContent) {
|
|
95
|
+
return { status: 'unchanged', file: operationFile, note };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!dryRun) {
|
|
99
|
+
fs.writeFileSync(workspacePath, nextContent, 'utf8');
|
|
100
|
+
}
|
|
101
|
+
return { status: dryRun ? 'would-update' : 'updated', file: operationFile, note };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function hasGuardexBootstrapFiles(repoRoot) {
|
|
105
|
+
const required = [
|
|
106
|
+
'AGENTS.md',
|
|
107
|
+
'.githooks/pre-commit',
|
|
108
|
+
'.githooks/pre-push',
|
|
109
|
+
LOCK_FILE_RELATIVE,
|
|
110
|
+
];
|
|
111
|
+
return required.every((relativePath) => fs.existsSync(path.join(repoRoot, relativePath)));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function assertProtectedMainWriteAllowed(options, commandName) {
|
|
115
|
+
return sandboxModule.assertProtectedMainWriteAllowed(options, commandName);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function runSetupBootstrapInternal(options) {
|
|
119
|
+
const installPayload = runInstallInternal(options);
|
|
120
|
+
installPayload.operations.push(
|
|
121
|
+
ensureSetupProtectedBranches(installPayload.repoRoot, Boolean(options.dryRun)),
|
|
122
|
+
);
|
|
123
|
+
installPayload.operations.push(
|
|
124
|
+
...ensureSubmoduleAutoSync(installPayload.repoRoot, Boolean(options.dryRun)),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
let parentWorkspace = null;
|
|
128
|
+
if (options.parentWorkspaceView) {
|
|
129
|
+
installPayload.operations.push(
|
|
130
|
+
ensureParentWorkspaceView(installPayload.repoRoot, Boolean(options.dryRun)),
|
|
131
|
+
);
|
|
132
|
+
if (!options.dryRun) {
|
|
133
|
+
parentWorkspace = buildParentWorkspaceView(installPayload.repoRoot);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const fixPayload = runFixInternal({
|
|
138
|
+
target: installPayload.repoRoot,
|
|
139
|
+
dryRun: options.dryRun,
|
|
140
|
+
force: options.force,
|
|
141
|
+
forceManagedPaths: options.forceManagedPaths,
|
|
142
|
+
dropStaleLocks: true,
|
|
143
|
+
skipAgents: options.skipAgents,
|
|
144
|
+
skipPackageJson: options.skipPackageJson,
|
|
145
|
+
skipGitignore: options.skipGitignore,
|
|
146
|
+
allowProtectedBaseWrite: options.allowProtectedBaseWrite,
|
|
147
|
+
contract: options.contract,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
installPayload,
|
|
152
|
+
fixPayload,
|
|
153
|
+
parentWorkspace,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function buildSandboxSetupArgs(options, sandboxTarget) {
|
|
158
|
+
const args = ['setup', '--target', sandboxTarget, '--no-global-install', '--no-recursive'];
|
|
159
|
+
appendForceArgs(args, options);
|
|
160
|
+
if (options.skipAgents) args.push('--skip-agents');
|
|
161
|
+
if (options.skipPackageJson) args.push('--skip-package-json');
|
|
162
|
+
if (options.skipGitignore) args.push('--no-gitignore');
|
|
163
|
+
if (options.dryRun) args.push('--dry-run');
|
|
164
|
+
if (options.contract) args.push('--contract');
|
|
165
|
+
return args;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function protectedBaseSandboxBranchPrefix() {
|
|
169
|
+
const now = new Date();
|
|
170
|
+
const stamp = [
|
|
171
|
+
now.getUTCFullYear(),
|
|
172
|
+
String(now.getUTCMonth() + 1).padStart(2, '0'),
|
|
173
|
+
String(now.getUTCDate()).padStart(2, '0'),
|
|
174
|
+
].join('') + '-' + [
|
|
175
|
+
String(now.getUTCHours()).padStart(2, '0'),
|
|
176
|
+
String(now.getUTCMinutes()).padStart(2, '0'),
|
|
177
|
+
String(now.getUTCSeconds()).padStart(2, '0'),
|
|
178
|
+
].join('');
|
|
179
|
+
return `agent/gx/${stamp}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function protectedBaseSandboxWorktreePath(repoRoot, branchName) {
|
|
183
|
+
return path.join(repoRoot, defaultAgentWorktreeRelativeDir(), branchName.replace(/\//g, '__'));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function resolveProtectedBaseSandboxStartRef(repoRoot, baseBranch) {
|
|
187
|
+
run('git', ['-C', repoRoot, 'fetch', 'origin', baseBranch, '--quiet'], { timeout: 20_000 });
|
|
188
|
+
if (gitRefExists(repoRoot, `refs/remotes/origin/${baseBranch}`)) {
|
|
189
|
+
return `origin/${baseBranch}`;
|
|
190
|
+
}
|
|
191
|
+
if (gitRefExists(repoRoot, `refs/heads/${baseBranch}`)) {
|
|
192
|
+
return baseBranch;
|
|
193
|
+
}
|
|
194
|
+
if (currentBranchName(repoRoot) === baseBranch) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
throw new Error(`Unable to find base ref for sandbox bootstrap: ${baseBranch}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function startProtectedBaseSandboxFallback(blocked, sandboxSuffix) {
|
|
201
|
+
const branchPrefix = protectedBaseSandboxBranchPrefix();
|
|
202
|
+
let selectedBranch = '';
|
|
203
|
+
let selectedWorktreePath = '';
|
|
204
|
+
|
|
205
|
+
for (let attempt = 0; attempt < 30; attempt += 1) {
|
|
206
|
+
const suffix = attempt === 0 ? sandboxSuffix : `${attempt + 1}-${sandboxSuffix}`;
|
|
207
|
+
const candidateBranch = `${branchPrefix}-${suffix}`;
|
|
208
|
+
const candidateWorktreePath = protectedBaseSandboxWorktreePath(blocked.repoRoot, candidateBranch);
|
|
209
|
+
if (gitRefExists(blocked.repoRoot, `refs/heads/${candidateBranch}`)) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (fs.existsSync(candidateWorktreePath)) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
selectedBranch = candidateBranch;
|
|
216
|
+
selectedWorktreePath = candidateWorktreePath;
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!selectedBranch || !selectedWorktreePath) {
|
|
221
|
+
throw new Error('Unable to allocate unique sandbox branch/worktree');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
fs.mkdirSync(path.dirname(selectedWorktreePath), { recursive: true });
|
|
225
|
+
const startRef = resolveProtectedBaseSandboxStartRef(blocked.repoRoot, blocked.branch);
|
|
226
|
+
const addArgs = startRef
|
|
227
|
+
? ['-C', blocked.repoRoot, 'worktree', 'add', '-b', selectedBranch, selectedWorktreePath, startRef]
|
|
228
|
+
: ['-C', blocked.repoRoot, 'worktree', 'add', '--orphan', selectedWorktreePath];
|
|
229
|
+
const addResult = run('git', addArgs);
|
|
230
|
+
if (isSpawnFailure(addResult)) {
|
|
231
|
+
throw addResult.error;
|
|
232
|
+
}
|
|
233
|
+
if (addResult.status !== 0) {
|
|
234
|
+
throw new Error((addResult.stderr || addResult.stdout || 'failed to create sandbox').trim());
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!startRef) {
|
|
238
|
+
const renameResult = run(
|
|
239
|
+
'git',
|
|
240
|
+
['-C', selectedWorktreePath, 'branch', '-m', selectedBranch],
|
|
241
|
+
{ timeout: 20_000 },
|
|
242
|
+
);
|
|
243
|
+
if (isSpawnFailure(renameResult)) {
|
|
244
|
+
throw renameResult.error;
|
|
245
|
+
}
|
|
246
|
+
if (renameResult.status !== 0) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
(renameResult.stderr || renameResult.stdout || 'failed to name orphan sandbox branch').trim(),
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const prepOps = prepareAgentWorktree(blocked.repoRoot, selectedWorktreePath);
|
|
254
|
+
return {
|
|
255
|
+
metadata: {
|
|
256
|
+
branch: selectedBranch,
|
|
257
|
+
worktreePath: selectedWorktreePath,
|
|
258
|
+
},
|
|
259
|
+
stdout:
|
|
260
|
+
`[agent-branch-start] Created branch: ${selectedBranch}\n` +
|
|
261
|
+
`[agent-branch-start] Worktree: ${selectedWorktreePath}\n` +
|
|
262
|
+
formatWorktreePrepOps(prepOps),
|
|
263
|
+
stderr: addResult.stderr || '',
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function startProtectedBaseSandbox(blocked, { taskName, sandboxSuffix }) {
|
|
268
|
+
if (sandboxSuffix === 'gx-doctor') {
|
|
269
|
+
return startProtectedBaseSandboxFallback(blocked, sandboxSuffix);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const startResult = runPackageAsset('branchStart', [
|
|
273
|
+
'--task',
|
|
274
|
+
taskName,
|
|
275
|
+
'--agent',
|
|
276
|
+
SHORT_TOOL_NAME,
|
|
277
|
+
'--base',
|
|
278
|
+
blocked.branch,
|
|
279
|
+
], { cwd: blocked.repoRoot });
|
|
280
|
+
if (isSpawnFailure(startResult)) {
|
|
281
|
+
throw startResult.error;
|
|
282
|
+
}
|
|
283
|
+
if (startResult.status !== 0) {
|
|
284
|
+
return startProtectedBaseSandboxFallback(blocked, sandboxSuffix);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const metadata = extractAgentBranchStartMetadata(startResult.stdout);
|
|
288
|
+
const currentBranch = currentBranchName(blocked.repoRoot);
|
|
289
|
+
const worktreePath = metadata.worktreePath ? path.resolve(metadata.worktreePath) : '';
|
|
290
|
+
const repoRootPath = path.resolve(blocked.repoRoot);
|
|
291
|
+
const hasSafeWorktree = Boolean(worktreePath) && worktreePath !== repoRootPath;
|
|
292
|
+
const branchChanged = Boolean(currentBranch) && currentBranch !== blocked.branch;
|
|
293
|
+
|
|
294
|
+
if (!hasSafeWorktree || branchChanged) {
|
|
295
|
+
const restoreResult = ensureRepoBranch(blocked.repoRoot, blocked.branch);
|
|
296
|
+
if (!restoreResult.ok) {
|
|
297
|
+
const detail = [restoreResult.stderr, restoreResult.stdout].filter(Boolean).join('\n').trim();
|
|
298
|
+
throw new Error(
|
|
299
|
+
`sandbox startup switched protected base checkout and could not restore '${blocked.branch}'.` +
|
|
300
|
+
(detail ? `\n${detail}` : ''),
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
return startProtectedBaseSandboxFallback(blocked, sandboxSuffix);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const worktreePathResolved = metadata.worktreePath
|
|
307
|
+
? path.resolve(metadata.worktreePath)
|
|
308
|
+
: '';
|
|
309
|
+
const prepOps = prepareAgentWorktree(blocked.repoRoot, worktreePathResolved);
|
|
310
|
+
return {
|
|
311
|
+
metadata,
|
|
312
|
+
stdout: (startResult.stdout || '') + formatWorktreePrepOps(prepOps),
|
|
313
|
+
stderr: startResult.stderr || '',
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function runSetupInSandbox(options, blocked, repoLabel = '') {
|
|
318
|
+
const startResult = startProtectedBaseSandbox(blocked, {
|
|
319
|
+
taskName: `${SHORT_TOOL_NAME}-setup`,
|
|
320
|
+
sandboxSuffix: 'gx-setup',
|
|
321
|
+
});
|
|
322
|
+
const metadata = startResult.metadata;
|
|
323
|
+
|
|
324
|
+
if (startResult.stdout) process.stdout.write(startResult.stdout);
|
|
325
|
+
if (startResult.stderr) process.stderr.write(startResult.stderr);
|
|
326
|
+
console.log(
|
|
327
|
+
`[${TOOL_NAME}] setup blocked on protected branch '${blocked.branch}' in an initialized repo; ` +
|
|
328
|
+
'refreshing through a sandbox worktree and syncing managed bootstrap files back locally.',
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
const sandboxTarget = resolveSandboxTarget(blocked.repoRoot, metadata.worktreePath, options.target);
|
|
332
|
+
const mainScriptPath = require.resolve('../main.js');
|
|
333
|
+
const nestedResult = run(
|
|
334
|
+
process.execPath,
|
|
335
|
+
[mainScriptPath, ...buildSandboxSetupArgs(options, sandboxTarget)],
|
|
336
|
+
{ cwd: metadata.worktreePath, env: { GUARDEX_DOCTOR_SANDBOX: '1' } },
|
|
337
|
+
);
|
|
338
|
+
if (isSpawnFailure(nestedResult)) {
|
|
339
|
+
throw nestedResult.error;
|
|
340
|
+
}
|
|
341
|
+
if (nestedResult.status !== 0) {
|
|
342
|
+
if (nestedResult.stdout) process.stdout.write(nestedResult.stdout);
|
|
343
|
+
if (nestedResult.stderr) process.stderr.write(nestedResult.stderr);
|
|
344
|
+
throw new Error(
|
|
345
|
+
`sandboxed setup failed for protected branch '${blocked.branch}'. ` +
|
|
346
|
+
`Inspect sandbox at ${metadata.worktreePath}`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const syncOptions = {
|
|
351
|
+
...options,
|
|
352
|
+
target: blocked.repoRoot,
|
|
353
|
+
recursive: false,
|
|
354
|
+
allowProtectedBaseWrite: true,
|
|
355
|
+
};
|
|
356
|
+
const { installPayload, fixPayload, parentWorkspace } = runSetupBootstrapInternal(syncOptions);
|
|
357
|
+
printOperations(`Setup/install${repoLabel}`, installPayload, syncOptions.dryRun);
|
|
358
|
+
printOperations(`Setup/fix${repoLabel}`, fixPayload, syncOptions.dryRun);
|
|
359
|
+
if (!syncOptions.dryRun && parentWorkspace) {
|
|
360
|
+
console.log(`[${TOOL_NAME}] Parent workspace view: ${parentWorkspace.workspacePath}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const scanResult = runScanInternal({ target: blocked.repoRoot, json: false });
|
|
364
|
+
const currentBaseBranch = currentBranchName(scanResult.repoRoot);
|
|
365
|
+
const autoFinishSummary = doctorModule.autoFinishReadyAgentBranches(scanResult.repoRoot, {
|
|
366
|
+
baseBranch: currentBaseBranch,
|
|
367
|
+
dryRun: syncOptions.dryRun,
|
|
368
|
+
});
|
|
369
|
+
printScanResult(scanResult, false);
|
|
370
|
+
if (autoFinishSummary.enabled) {
|
|
371
|
+
console.log(
|
|
372
|
+
`[${TOOL_NAME}] Auto-finish sweep (base=${currentBaseBranch}): attempted=${autoFinishSummary.attempted}, completed=${autoFinishSummary.completed}, skipped=${autoFinishSummary.skipped}, failed=${autoFinishSummary.failed}`,
|
|
373
|
+
);
|
|
374
|
+
for (const detail of autoFinishSummary.details) {
|
|
375
|
+
console.log(`[${TOOL_NAME}] ${detail}`);
|
|
376
|
+
}
|
|
377
|
+
} else if (autoFinishSummary.details.length > 0) {
|
|
378
|
+
console.log(`[${TOOL_NAME}] ${autoFinishSummary.details[0]}`);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const prunePayload = doctorModule.pruneStaleAgentWorktrees(scanResult.repoRoot, {
|
|
382
|
+
baseBranch: currentBaseBranch,
|
|
383
|
+
dryRun: syncOptions.dryRun,
|
|
384
|
+
});
|
|
385
|
+
printWorktreePruneSummary(prunePayload, { baseBranch: currentBaseBranch });
|
|
386
|
+
|
|
387
|
+
const cleanupResult = cleanupProtectedBaseSandbox(blocked.repoRoot, metadata);
|
|
388
|
+
console.log(
|
|
389
|
+
`[${TOOL_NAME}] Protected-base setup sandbox cleanup: ${cleanupResult.note} ` +
|
|
390
|
+
`(worktree=${cleanupResult.worktree}, branch=${cleanupResult.branch}).`,
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
scanResult,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
module.exports = {
|
|
399
|
+
normalizeWorkspacePath,
|
|
400
|
+
buildParentWorkspaceView,
|
|
401
|
+
ensureParentWorkspaceView,
|
|
402
|
+
hasGuardexBootstrapFiles,
|
|
403
|
+
protectedBaseWriteBlock,
|
|
404
|
+
assertProtectedMainWriteAllowed,
|
|
405
|
+
runSetupBootstrapInternal,
|
|
406
|
+
extractAgentBranchStartMetadata,
|
|
407
|
+
resolveSandboxTarget,
|
|
408
|
+
buildSandboxSetupArgs,
|
|
409
|
+
isSpawnFailure,
|
|
410
|
+
protectedBaseSandboxBranchPrefix,
|
|
411
|
+
protectedBaseSandboxWorktreePath,
|
|
412
|
+
resolveProtectedBaseSandboxStartRef,
|
|
413
|
+
startProtectedBaseSandboxFallback,
|
|
414
|
+
startProtectedBaseSandbox,
|
|
415
|
+
cleanupProtectedBaseSandbox,
|
|
416
|
+
runSetupInSandbox,
|
|
417
|
+
};
|