create-agent-rig 0.9.0 → 0.10.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/CHANGELOG.md +466 -3
- package/README.md +267 -299
- package/package.json +8 -14
- package/packages/cli/dist/commands/create.js +56 -88
- package/packages/cli/dist/commands/doctor.js +213 -0
- package/packages/cli/dist/commands/init.js +151 -54
- package/packages/cli/dist/commands/integrations.js +468 -0
- package/packages/cli/dist/commands/memory.js +63 -4
- package/packages/cli/dist/commands/setup-wizard.js +61 -0
- package/packages/cli/dist/commands/uninstall.js +1223 -0
- package/packages/cli/dist/commands/upgrade.js +489 -89
- package/packages/cli/dist/index.js +657 -49
- package/packages/cli/dist/integrations/declaration.js +158 -0
- package/packages/cli/dist/integrations/doctor-guards.js +126 -0
- package/packages/cli/dist/integrations/doctor-workflow.js +25 -0
- package/packages/cli/dist/integrations/mcp-json.js +107 -0
- package/packages/cli/dist/integrations/memory-doctor.js +124 -0
- package/packages/cli/dist/integrations/registry.js +22 -0
- package/packages/cli/dist/integrations/spawn.js +228 -0
- package/packages/cli/dist/integrations/spec-kit.js +280 -0
- package/packages/cli/dist/integrations/verify.js +193 -0
- package/packages/cli/dist/integrations/windows-job.js +275 -0
- package/packages/cli/dist/lib/elevated-paths.js +74 -0
- package/packages/cli/dist/lib/install-set.js +1 -44
- package/packages/cli/dist/lib/manifest.js +86 -9
- package/packages/cli/dist/lib/prompts.js +0 -23
- package/packages/cli/dist/lib/safe-path.js +193 -2
- package/packages/cli/dist/lib/safe-text.js +78 -0
- package/packages/cli/dist/lib/substitute.js +1 -52
- package/packages/cli/dist/lib/summary.js +5 -5
- package/packages/cli/dist/templates.js +4 -14
- package/scripts/prepare.mjs +28 -12
- package/templates/agent-os/subagent-routing.json +4 -4
- package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +13 -9
- package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +31 -6
- package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +5 -5
- package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +1 -1
- package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +3 -3
- package/templates/agent-os/universal/.claude/agents/code-reviewer.md +8 -6
- package/templates/agent-os/universal/.claude/agents/implementation-agent.md +41 -0
- package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +8 -4
- package/templates/agent-os/universal/.claude/agents/security-scanner.md +4 -2
- package/templates/agent-os/universal/.claude/agents/test-writer.md +2 -2
- package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +83 -4
- package/templates/agent-os/universal/.claude/rules/autonomy.md +55 -27
- package/templates/agent-os/universal/.claude/rules/invariants.md +9 -8
- package/templates/agent-os/universal/.claude/rules/workflow.md +47 -22
- package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1 -1
- package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +12 -9
- package/templates/agent-os/universal/.claude/scripts/doctor.mjs +100 -2
- package/templates/agent-os/universal/.claude/scripts/lib/claim-records.mjs +236 -6
- package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +43 -0
- package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +18 -1
- package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +2 -2
- package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +273 -35
- package/templates/agent-os/universal/.claude/scripts/revalidate.mjs +16 -0
- package/templates/agent-os/universal/.claude/settings.json +0 -8
- package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +13 -9
- package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +31 -6
- package/templates/agent-os/universal/.claude/skills/new-invariant/SKILL.md +5 -5
- package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +1 -1
- package/templates/agent-os/universal/.claude/skills/worktree-task/SKILL.md +3 -3
- package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +2 -2
- package/templates/agent-os/universal/.codex/agents/implementation-agent.toml +6 -0
- package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +2 -2
- package/templates/agent-os/universal/.codex/agents/security-scanner.toml +1 -1
- package/templates/agent-os/universal/.codex/agents/test-writer.toml +1 -1
- package/templates/agent-os/universal/.codex/hooks.json +0 -10
- package/templates/agent-os/universal/AGENTS.md +208 -107
- package/templates/agent-os/universal/CLAUDE.md +15 -162
- package/templates/agent-os/universal/PLAN.md +17 -10
- package/templates/agent-os/universal/docs/decisions/agents-md-canonical.md +313 -0
- package/templates/agent-os/universal/docs/decisions/codex-adapter.md +15 -10
- package/templates/agent-os/universal/docs/decisions/review-lanes.md +12 -8
- package/templates/agent-os/universal/docs/decisions/session-start-wire-format.md +206 -0
- package/templates/agent-os/universal/docs/decisions/subagent-routing.md +6 -3
- package/templates/agent-os/universal/docs/decisions/workflow-layer-split.md +235 -0
- package/templates/agent-os/universal/layers.json +25 -30
- package/templates/hash-history.json +73 -24
- package/templates/release-ledger.json +3 -1
- package/packages/cli/dist/lib/composition.js +0 -20
- package/packages/cli/dist/lib/targets.js +0 -28
- package/packages/cli/dist/policy/benchmark/corpus.js +0 -165
- package/packages/cli/dist/policy/core/adapter.js +0 -18
- package/packages/cli/dist/policy/core/coverage.js +0 -253
- package/packages/cli/dist/policy/core/decision-record.js +0 -287
- package/packages/cli/dist/policy/core/declaration.js +0 -127
- package/packages/cli/dist/policy/core/evidence-matrix.js +0 -94
- package/packages/cli/dist/policy/core/probe.js +0 -442
- package/packages/cli/dist/policy/core/registry.js +0 -115
- package/packages/cli/dist/policy/core/validation.js +0 -275
- package/packages/cli/dist/policy/core/vocabulary.js +0 -123
- package/packages/cli/dist/policy/harness/claude.js +0 -47
- package/packages/cli/dist/policy/harness/codex.js +0 -87
- package/packages/cli/dist/policy/harness/index.js +0 -15
- package/packages/cli/dist/policy/harness/shared-hooks.js +0 -28
- package/packages/cli/dist/policy/index.js +0 -17
- package/templates/agent-os/init/AGENTS.md +0 -201
- package/templates/agent-os/init/CLAUDE.md +0 -201
- package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +0 -117
- package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +0 -89
- package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +0 -105
- package/templates/agent-os/stack/aws-cdk/.claude/skills/ro-debug/SKILL.md +0 -117
- package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +0 -6
- package/templates/agent-os/stack/node-ts/.claude/hooks/dod-checks.json +0 -1
- package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +0 -85
- package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +0 -89
- package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +0 -68
- package/templates/agent-os/universal/.claude/rules/architecture.md +0 -81
- package/templates/skeleton/aws-serverless/.github/workflows/ci.yml +0 -28
- package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +0 -90
- package/templates/skeleton/aws-serverless/README.md +0 -181
- package/templates/skeleton/aws-serverless/apps/web/next.config.mjs +0 -17
- package/templates/skeleton/aws-serverless/apps/web/package.json +0 -19
- package/templates/skeleton/aws-serverless/apps/web/src/app/layout.tsx +0 -17
- package/templates/skeleton/aws-serverless/apps/web/src/app/page.tsx +0 -96
- package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +0 -36
- package/templates/skeleton/aws-serverless/apps/web/src/lib/validate.ts +0 -23
- package/templates/skeleton/aws-serverless/apps/web/test/shared-validation.test.ts +0 -38
- package/templates/skeleton/aws-serverless/apps/web/tsconfig.json +0 -14
- package/templates/skeleton/aws-serverless/eslint.config.mjs +0 -20
- package/templates/skeleton/aws-serverless/gitignore +0 -56
- package/templates/skeleton/aws-serverless/infra/bin/app.ts +0 -79
- package/templates/skeleton/aws-serverless/infra/cdk.json +0 -3
- package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +0 -248
- package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +0 -45
- package/templates/skeleton/aws-serverless/infra/package.json +0 -18
- package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +0 -301
- package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +0 -137
- package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +0 -120
- package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +0 -44
- package/templates/skeleton/aws-serverless/package.json +0 -30
- package/templates/skeleton/aws-serverless/packages/core/package.json +0 -11
- package/templates/skeleton/aws-serverless/packages/core/src/events.ts +0 -14
- package/templates/skeleton/aws-serverless/packages/core/src/index.ts +0 -15
- package/templates/skeleton/aws-serverless/packages/core/src/note.ts +0 -69
- package/templates/skeleton/aws-serverless/packages/core/test/events.test.ts +0 -23
- package/templates/skeleton/aws-serverless/packages/core/test/note.test.ts +0 -101
- package/templates/skeleton/aws-serverless/packages/db/package.json +0 -14
- package/templates/skeleton/aws-serverless/packages/db/src/client.ts +0 -17
- package/templates/skeleton/aws-serverless/packages/db/src/index.ts +0 -2
- package/templates/skeleton/aws-serverless/packages/db/src/note-model.ts +0 -52
- package/templates/skeleton/aws-serverless/packages/db/test/note-model.test.ts +0 -91
- package/templates/skeleton/aws-serverless/packages/shared/package.json +0 -11
- package/templates/skeleton/aws-serverless/packages/shared/src/env.ts +0 -17
- package/templates/skeleton/aws-serverless/packages/shared/src/errors.ts +0 -33
- package/templates/skeleton/aws-serverless/packages/shared/src/index.ts +0 -3
- package/templates/skeleton/aws-serverless/packages/shared/src/logger.ts +0 -20
- package/templates/skeleton/aws-serverless/packages/shared/test/env.test.ts +0 -26
- package/templates/skeleton/aws-serverless/packages/shared/test/errors.test.ts +0 -28
- package/templates/skeleton/aws-serverless/packages/shared/test/logger.test.ts +0 -19
- package/templates/skeleton/aws-serverless/pnpm-lock.yaml +0 -2855
- package/templates/skeleton/aws-serverless/pnpm-workspace.yaml +0 -14
- package/templates/skeleton/aws-serverless/services/api/package.json +0 -15
- package/templates/skeleton/aws-serverless/services/api/src/adapters/sqs-publisher.ts +0 -26
- package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +0 -69
- package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +0 -37
- package/templates/skeleton/aws-serverless/services/api/src/list-main.ts +0 -12
- package/templates/skeleton/aws-serverless/services/api/src/main.ts +0 -21
- package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +0 -41
- package/templates/skeleton/aws-serverless/services/api/src/usecases/list-notes.ts +0 -14
- package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +0 -211
- package/templates/skeleton/aws-serverless/services/api/test/create-note.usecase.test.ts +0 -45
- package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +0 -122
- package/templates/skeleton/aws-serverless/services/api/test/sqs-publisher.test.ts +0 -22
- package/templates/skeleton/aws-serverless/services/worker/package.json +0 -12
- package/templates/skeleton/aws-serverless/services/worker/src/handlers/note-created.ts +0 -15
- package/templates/skeleton/aws-serverless/services/worker/src/main.ts +0 -7
- package/templates/skeleton/aws-serverless/services/worker/src/usecases/process-note-created.ts +0 -37
- package/templates/skeleton/aws-serverless/services/worker/test/note-created.test.ts +0 -61
- package/templates/skeleton/aws-serverless/tsconfig.base.json +0 -15
- package/templates/skeleton/aws-serverless/tsconfig.json +0 -16
- package/templates/skeleton/aws-serverless/vitest.config.ts +0 -14
- package/templates/skeleton/node-service/.github/workflows/ci.yml +0 -27
- package/templates/skeleton/node-service/.github/workflows/deploy.yml +0 -29
- package/templates/skeleton/node-service/README.md +0 -104
- package/templates/skeleton/node-service/apps/web/next.config.mjs +0 -17
- package/templates/skeleton/node-service/apps/web/package.json +0 -19
- package/templates/skeleton/node-service/apps/web/src/app/layout.tsx +0 -17
- package/templates/skeleton/node-service/apps/web/src/app/page.tsx +0 -96
- package/templates/skeleton/node-service/apps/web/src/lib/api.ts +0 -29
- package/templates/skeleton/node-service/apps/web/src/lib/validate.ts +0 -23
- package/templates/skeleton/node-service/apps/web/test/shared-validation.test.ts +0 -38
- package/templates/skeleton/node-service/apps/web/tsconfig.json +0 -14
- package/templates/skeleton/node-service/eslint.config.mjs +0 -20
- package/templates/skeleton/node-service/gitignore +0 -54
- package/templates/skeleton/node-service/package.json +0 -30
- package/templates/skeleton/node-service/packages/core/package.json +0 -11
- package/templates/skeleton/node-service/packages/core/src/events.ts +0 -14
- package/templates/skeleton/node-service/packages/core/src/index.ts +0 -15
- package/templates/skeleton/node-service/packages/core/src/note.ts +0 -69
- package/templates/skeleton/node-service/packages/core/test/events.test.ts +0 -23
- package/templates/skeleton/node-service/packages/core/test/note.test.ts +0 -101
- package/templates/skeleton/node-service/packages/db/package.json +0 -12
- package/templates/skeleton/node-service/packages/db/src/index.ts +0 -1
- package/templates/skeleton/node-service/packages/db/src/note-store.ts +0 -100
- package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +0 -100
- package/templates/skeleton/node-service/packages/shared/package.json +0 -11
- package/templates/skeleton/node-service/packages/shared/src/env.ts +0 -17
- package/templates/skeleton/node-service/packages/shared/src/errors.ts +0 -33
- package/templates/skeleton/node-service/packages/shared/src/index.ts +0 -3
- package/templates/skeleton/node-service/packages/shared/src/logger.ts +0 -20
- package/templates/skeleton/node-service/packages/shared/test/env.test.ts +0 -26
- package/templates/skeleton/node-service/packages/shared/test/errors.test.ts +0 -28
- package/templates/skeleton/node-service/packages/shared/test/logger.test.ts +0 -19
- package/templates/skeleton/node-service/pnpm-lock.yaml +0 -2402
- package/templates/skeleton/node-service/pnpm-workspace.yaml +0 -13
- package/templates/skeleton/node-service/scripts/build-artifact.mjs +0 -34
- package/templates/skeleton/node-service/services/api/package.json +0 -17
- package/templates/skeleton/node-service/services/api/src/adapters/spool-publisher.ts +0 -23
- package/templates/skeleton/node-service/services/api/src/handlers/create-note.ts +0 -40
- package/templates/skeleton/node-service/services/api/src/handlers/list-notes.ts +0 -23
- package/templates/skeleton/node-service/services/api/src/main.ts +0 -40
- package/templates/skeleton/node-service/services/api/src/server.ts +0 -172
- package/templates/skeleton/node-service/services/api/src/static-dir.ts +0 -20
- package/templates/skeleton/node-service/services/api/src/usecases/create-note.ts +0 -30
- package/templates/skeleton/node-service/services/api/src/usecases/list-notes.ts +0 -14
- package/templates/skeleton/node-service/services/api/test/artifact.test.ts +0 -92
- package/templates/skeleton/node-service/services/api/test/create-note.handler.test.ts +0 -64
- package/templates/skeleton/node-service/services/api/test/create-note.usecase.test.ts +0 -43
- package/templates/skeleton/node-service/services/api/test/list-notes.test.ts +0 -48
- package/templates/skeleton/node-service/services/api/test/package-manager.test.ts +0 -40
- package/templates/skeleton/node-service/services/api/test/package-manager.ts +0 -51
- package/templates/skeleton/node-service/services/api/test/server.test.ts +0 -208
- package/templates/skeleton/node-service/services/api/test/spool-publisher.test.ts +0 -32
- package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +0 -29
- package/templates/skeleton/node-service/services/worker/package.json +0 -16
- package/templates/skeleton/node-service/services/worker/src/main.ts +0 -28
- package/templates/skeleton/node-service/services/worker/src/spool.ts +0 -60
- package/templates/skeleton/node-service/services/worker/src/usecases/process-note-created.ts +0 -38
- package/templates/skeleton/node-service/services/worker/test/process-note-created.test.ts +0 -34
- package/templates/skeleton/node-service/services/worker/test/spool.test.ts +0 -76
- package/templates/skeleton/node-service/tsconfig.base.json +0 -15
- package/templates/skeleton/node-service/tsconfig.json +0 -13
- package/templates/skeleton/node-service/vitest.config.ts +0 -12
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { realpath, stat } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
5
|
+
import { WINDOWS_JOB_SUPERVISOR } from './windows-job.js';
|
|
6
|
+
// Only compiled adapters choose executable/argv. Repository declarations never
|
|
7
|
+
// flow into these fields. The caller parses output; it must not log raw output.
|
|
8
|
+
export async function runProviderProcess(options) {
|
|
9
|
+
const timeoutMs = options.timeoutMs ?? 60_000;
|
|
10
|
+
const maxOutputBytes = options.maxOutputBytes ?? 256 * 1024;
|
|
11
|
+
const failed = () => ({
|
|
12
|
+
status: 'failed',
|
|
13
|
+
stdout: '',
|
|
14
|
+
stderr: '',
|
|
15
|
+
exitCode: null,
|
|
16
|
+
});
|
|
17
|
+
if (!path.isAbsolute(options.executable) ||
|
|
18
|
+
!path.isAbsolute(options.repoDir) ||
|
|
19
|
+
!Number.isSafeInteger(timeoutMs) ||
|
|
20
|
+
timeoutMs < 1 ||
|
|
21
|
+
timeoutMs > 300_000 ||
|
|
22
|
+
!Number.isSafeInteger(maxOutputBytes) ||
|
|
23
|
+
maxOutputBytes < 1 ||
|
|
24
|
+
maxOutputBytes > 1024 * 1024)
|
|
25
|
+
return failed();
|
|
26
|
+
let cwd;
|
|
27
|
+
try {
|
|
28
|
+
cwd = await realpath(options.repoDir);
|
|
29
|
+
if (!(await stat(cwd)).isDirectory())
|
|
30
|
+
return failed();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return failed();
|
|
34
|
+
}
|
|
35
|
+
const env = { NO_COLOR: '1', UV_NO_PROGRESS: '1', PYTHONUTF8: '1' };
|
|
36
|
+
const allowed = new Set([
|
|
37
|
+
'PATH',
|
|
38
|
+
// uv resolves git through PATHEXT on Windows; without it the job
|
|
39
|
+
// supervisor leaves only `.CPL` and every git-sourced provider fails.
|
|
40
|
+
'PATHEXT',
|
|
41
|
+
'HOME',
|
|
42
|
+
'USERPROFILE',
|
|
43
|
+
'APPDATA',
|
|
44
|
+
'LOCALAPPDATA',
|
|
45
|
+
'TEMP',
|
|
46
|
+
'TMP',
|
|
47
|
+
'SYSTEMROOT',
|
|
48
|
+
'LANG',
|
|
49
|
+
'LC_ALL',
|
|
50
|
+
'SSL_CERT_FILE',
|
|
51
|
+
'SSL_CERT_DIR',
|
|
52
|
+
'XDG_CACHE_HOME',
|
|
53
|
+
'UV_CACHE_DIR',
|
|
54
|
+
]);
|
|
55
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
56
|
+
if (allowed.has(key.toUpperCase()))
|
|
57
|
+
env[key] = value;
|
|
58
|
+
}
|
|
59
|
+
const isWindows = process.platform === 'win32';
|
|
60
|
+
let powershell;
|
|
61
|
+
if (isWindows) {
|
|
62
|
+
const systemRoot = process.env.SystemRoot ?? process.env.SYSTEMROOT;
|
|
63
|
+
if (systemRoot === undefined || !path.isAbsolute(systemRoot))
|
|
64
|
+
return failed();
|
|
65
|
+
powershell = path.join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
66
|
+
// PowerShell 7 compiles this fixed C# source in-process. Prefer the standard
|
|
67
|
+
// installed runtime: Windows PowerShell's external CodeDom compiler stalls
|
|
68
|
+
// on the hosted image before provider creation. Do not install a runtime or
|
|
69
|
+
// search repository-controlled PATH entries for this supervisor.
|
|
70
|
+
const programFiles = process.env.ProgramFiles ?? process.env.PROGRAMFILES;
|
|
71
|
+
if (programFiles !== undefined && path.isAbsolute(programFiles)) {
|
|
72
|
+
const modern = path.join(programFiles, 'PowerShell', '7', 'pwsh.exe');
|
|
73
|
+
try {
|
|
74
|
+
const resolved = await realpath(modern);
|
|
75
|
+
const relative = path.relative(cwd, resolved);
|
|
76
|
+
if ((relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) &&
|
|
77
|
+
(await stat(resolved)).isFile())
|
|
78
|
+
powershell = resolved;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// The built-in Windows PowerShell remains the baseline on machines
|
|
82
|
+
// without PowerShell 7; the same operation deadline still applies.
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
let child;
|
|
88
|
+
try {
|
|
89
|
+
if (isWindows)
|
|
90
|
+
env.RIG_WINDOWS_JOB_PAYLOAD = Buffer.from(JSON.stringify({ executable: options.executable, cwd, args: options.args })).toString('base64');
|
|
91
|
+
child = spawn(isWindows ? powershell : options.executable, isWindows
|
|
92
|
+
? ['-NoProfile', '-NonInteractive', '-Command', WINDOWS_JOB_SUPERVISOR]
|
|
93
|
+
: options.args, {
|
|
94
|
+
cwd,
|
|
95
|
+
env,
|
|
96
|
+
shell: false,
|
|
97
|
+
windowsHide: true,
|
|
98
|
+
detached: !isWindows,
|
|
99
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
resolve(failed());
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
let stdout = Buffer.alloc(0);
|
|
107
|
+
let stderr = Buffer.alloc(0);
|
|
108
|
+
let settled = false;
|
|
109
|
+
let stopping = false;
|
|
110
|
+
let exited = false;
|
|
111
|
+
let closed = false;
|
|
112
|
+
let exitCode = null;
|
|
113
|
+
const text = (bytes) => {
|
|
114
|
+
let result = bytes.toString('utf8');
|
|
115
|
+
while (Buffer.byteLength(result) > maxOutputBytes)
|
|
116
|
+
result = result.slice(0, -1);
|
|
117
|
+
return result;
|
|
118
|
+
};
|
|
119
|
+
const finish = (status) => {
|
|
120
|
+
if (settled)
|
|
121
|
+
return;
|
|
122
|
+
settled = true;
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
child.stdout?.destroy();
|
|
125
|
+
child.stderr?.destroy();
|
|
126
|
+
child.unref();
|
|
127
|
+
resolve({
|
|
128
|
+
status,
|
|
129
|
+
stdout: text(stdout),
|
|
130
|
+
stderr: text(stderr),
|
|
131
|
+
exitCode: status === 'ok' || status === 'failed' ? exitCode : null,
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
const groupAlive = () => {
|
|
135
|
+
if (!child.pid)
|
|
136
|
+
return false;
|
|
137
|
+
try {
|
|
138
|
+
process.kill(-child.pid, 0);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return error.code !== 'ESRCH';
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const terminate = async (status) => {
|
|
146
|
+
if (stopping || settled)
|
|
147
|
+
return;
|
|
148
|
+
stopping = true;
|
|
149
|
+
const pid = child.pid;
|
|
150
|
+
if (!pid) {
|
|
151
|
+
finish('failed');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
let confirmed;
|
|
155
|
+
if (process.platform === 'win32') {
|
|
156
|
+
// The fixed supervisor owns a KILL_ON_JOB_CLOSE job before the provider
|
|
157
|
+
// is resumed. Terminating it closes that handle and kills every member.
|
|
158
|
+
try {
|
|
159
|
+
child.kill();
|
|
160
|
+
confirmed = true;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
confirmed = false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
try {
|
|
168
|
+
process.kill(-pid, 'SIGKILL');
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (error.code !== 'ESRCH') {
|
|
172
|
+
finish('cleanup-unconfirmed');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const deadline = Date.now() + 2000;
|
|
177
|
+
while (groupAlive() && Date.now() < deadline)
|
|
178
|
+
await delay(20);
|
|
179
|
+
confirmed = !groupAlive();
|
|
180
|
+
}
|
|
181
|
+
if (confirmed && !closed) {
|
|
182
|
+
const deadline = Date.now() + 500;
|
|
183
|
+
while (!closed && Date.now() < deadline)
|
|
184
|
+
await delay(10);
|
|
185
|
+
}
|
|
186
|
+
finish(confirmed && closed ? status : 'cleanup-unconfirmed');
|
|
187
|
+
};
|
|
188
|
+
const collect = (stream, chunk) => {
|
|
189
|
+
const old = stream === 'stdout' ? stdout : stderr;
|
|
190
|
+
const next = Buffer.concat([
|
|
191
|
+
old,
|
|
192
|
+
chunk.subarray(0, Math.max(0, maxOutputBytes - old.length)),
|
|
193
|
+
]);
|
|
194
|
+
if (stream === 'stdout')
|
|
195
|
+
stdout = next;
|
|
196
|
+
else
|
|
197
|
+
stderr = next;
|
|
198
|
+
if (old.length + chunk.length > maxOutputBytes)
|
|
199
|
+
void terminate('output-limit');
|
|
200
|
+
};
|
|
201
|
+
child.stdout?.on('data', (chunk) => collect('stdout', chunk));
|
|
202
|
+
child.stderr?.on('data', (chunk) => collect('stderr', chunk));
|
|
203
|
+
child.on('error', () => {
|
|
204
|
+
if (!stopping)
|
|
205
|
+
finish('failed');
|
|
206
|
+
});
|
|
207
|
+
child.on('exit', (code) => {
|
|
208
|
+
exited = true;
|
|
209
|
+
exitCode = code;
|
|
210
|
+
});
|
|
211
|
+
child.on('close', (code) => {
|
|
212
|
+
closed = true;
|
|
213
|
+
exitCode = code;
|
|
214
|
+
if (stopping)
|
|
215
|
+
return;
|
|
216
|
+
if (process.platform === 'win32' && code === 125) {
|
|
217
|
+
finish('cleanup-unconfirmed');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (process.platform !== 'win32' && groupAlive()) {
|
|
221
|
+
void terminate('cleanup-unconfirmed');
|
|
222
|
+
}
|
|
223
|
+
else
|
|
224
|
+
finish(code === 0 ? 'ok' : 'failed');
|
|
225
|
+
});
|
|
226
|
+
const timer = setTimeout(() => void terminate(exited ? 'cleanup-unconfirmed' : 'timeout'), timeoutMs);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { access, realpath, stat } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { resolveReadableInside } from '../lib/safe-path.js';
|
|
5
|
+
import { isPlainObject } from '../lib/safe-text.js';
|
|
6
|
+
import { runProviderProcess } from './spawn.js';
|
|
7
|
+
export const SPEC_KIT_VERSION = '1.0.8';
|
|
8
|
+
const SOURCE = `git+https://github.com/github/spec-kit@v${SPEC_KIT_VERSION}`;
|
|
9
|
+
const PREFIX = ['--from', SOURCE, 'specify'];
|
|
10
|
+
// v1.0.8's status vocabulary. Unknown upstream strings never reach diagnostics.
|
|
11
|
+
const STATUS_CODES = new Set([
|
|
12
|
+
'default-integration-missing',
|
|
13
|
+
'default-integration-not-installed',
|
|
14
|
+
'installed-integrations-invalid',
|
|
15
|
+
'integration-key-invalid',
|
|
16
|
+
'integration-state-missing',
|
|
17
|
+
'integration-state-unreadable',
|
|
18
|
+
'managed-file-collision',
|
|
19
|
+
'managed-files-missing',
|
|
20
|
+
'managed-files-modified',
|
|
21
|
+
'manifest-missing',
|
|
22
|
+
'manifest-paths-invalid',
|
|
23
|
+
'manifest-unreadable',
|
|
24
|
+
'no-installed-integrations',
|
|
25
|
+
'project-root-unresolved',
|
|
26
|
+
'unknown-integration',
|
|
27
|
+
'unsafe-multi-install',
|
|
28
|
+
]);
|
|
29
|
+
async function locate(name, repoDir, env = process.env) {
|
|
30
|
+
const root = await realpath(repoDir);
|
|
31
|
+
const inside = (file) => {
|
|
32
|
+
const relative = path.relative(root, file);
|
|
33
|
+
return (relative === '' ||
|
|
34
|
+
(!path.isAbsolute(relative) && relative !== '..' && !relative.startsWith(`..${path.sep}`)));
|
|
35
|
+
};
|
|
36
|
+
const filename = process.platform === 'win32' ? `${name}.exe` : name;
|
|
37
|
+
for (const directory of (env.PATH ?? env.Path ?? '').split(path.delimiter)) {
|
|
38
|
+
if (!path.isAbsolute(directory))
|
|
39
|
+
continue;
|
|
40
|
+
try {
|
|
41
|
+
const candidate = path.join(directory, filename);
|
|
42
|
+
const file = await realpath(candidate);
|
|
43
|
+
if (inside(candidate) || inside(file))
|
|
44
|
+
continue;
|
|
45
|
+
if (!(await stat(file)).isFile())
|
|
46
|
+
continue;
|
|
47
|
+
await access(file, process.platform === 'win32' ? constants.F_OK : constants.X_OK);
|
|
48
|
+
return candidate;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* Try the next machine PATH entry, never the repository. */
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/** Only the official read-only status command; network and Python downloads disabled. */
|
|
57
|
+
export async function inspectSpecKit(options) {
|
|
58
|
+
const uvx = await locate('uvx', options.repoDir, options.env);
|
|
59
|
+
if (!uvx)
|
|
60
|
+
return {
|
|
61
|
+
status: 'warn',
|
|
62
|
+
reason: 'upstream-status-unavailable',
|
|
63
|
+
launcher: 'missing',
|
|
64
|
+
runtime: 'unverified',
|
|
65
|
+
};
|
|
66
|
+
const result = await (options.runner ?? runProviderProcess)({
|
|
67
|
+
executable: uvx,
|
|
68
|
+
args: [
|
|
69
|
+
'--offline',
|
|
70
|
+
'--no-config',
|
|
71
|
+
'--no-python-downloads',
|
|
72
|
+
...PREFIX,
|
|
73
|
+
'integration',
|
|
74
|
+
'status',
|
|
75
|
+
'--json',
|
|
76
|
+
],
|
|
77
|
+
repoDir: options.repoDir,
|
|
78
|
+
timeoutMs: 30_000,
|
|
79
|
+
maxOutputBytes: 256 * 1024,
|
|
80
|
+
});
|
|
81
|
+
if (result.status === 'cleanup-unconfirmed')
|
|
82
|
+
return {
|
|
83
|
+
status: 'fail',
|
|
84
|
+
reason: 'upstream-cleanup-unconfirmed',
|
|
85
|
+
launcher: 'observed',
|
|
86
|
+
runtime: 'unverified',
|
|
87
|
+
};
|
|
88
|
+
const observed = observe(result);
|
|
89
|
+
if (observed === undefined)
|
|
90
|
+
return {
|
|
91
|
+
status: result.status === 'failed' ? 'warn' : 'fail',
|
|
92
|
+
reason: 'upstream-status-unavailable',
|
|
93
|
+
launcher: 'observed',
|
|
94
|
+
runtime: 'unverified',
|
|
95
|
+
};
|
|
96
|
+
const complete = options.harnesses.every((harness) => observed.installedIntegrations.includes(harness === 'claude-code' ? 'claude' : 'codex'));
|
|
97
|
+
const status = observed.status === 'error' || result.exitCode !== 0
|
|
98
|
+
? 'fail'
|
|
99
|
+
: observed.status === 'warning' || !complete
|
|
100
|
+
? 'warn'
|
|
101
|
+
: 'pass';
|
|
102
|
+
return {
|
|
103
|
+
status,
|
|
104
|
+
reason: !complete ? 'upstream-harness-status-incomplete' : `upstream-status-${observed.status}`,
|
|
105
|
+
launcher: 'observed',
|
|
106
|
+
runtime: 'verified',
|
|
107
|
+
observed,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function observe(result) {
|
|
111
|
+
if (result.status !== 'ok' && result.status !== 'failed')
|
|
112
|
+
return undefined;
|
|
113
|
+
let value;
|
|
114
|
+
try {
|
|
115
|
+
value = JSON.parse(result.stdout);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
if (!isPlainObject(value) ||
|
|
121
|
+
!['ok', 'warning', 'error'].includes(String(value.status)) ||
|
|
122
|
+
!Array.isArray(value.installed_integrations) ||
|
|
123
|
+
value.installed_integrations.length > 64 ||
|
|
124
|
+
!value.installed_integrations.every((id) => typeof id === 'string' && /^[a-z0-9][a-z0-9-]{0,63}$/.test(id)))
|
|
125
|
+
return undefined;
|
|
126
|
+
// Diagnosis keeps finite codes, not upstream free text that could contain
|
|
127
|
+
// credentials. Rig never persists this observed runtime state as intent.
|
|
128
|
+
const findings = Array.isArray(value.findings)
|
|
129
|
+
? value.findings.flatMap((finding) => isPlainObject(finding) &&
|
|
130
|
+
typeof finding.code === 'string' &&
|
|
131
|
+
STATUS_CODES.has(finding.code) &&
|
|
132
|
+
['info', 'warning', 'error'].includes(String(finding.severity))
|
|
133
|
+
? [{ code: finding.code, severity: String(finding.severity) }]
|
|
134
|
+
: [])
|
|
135
|
+
: [];
|
|
136
|
+
if (!Array.isArray(value.findings) || findings.length !== value.findings.length)
|
|
137
|
+
return undefined;
|
|
138
|
+
return {
|
|
139
|
+
status: value.status,
|
|
140
|
+
installedIntegrations: value.installed_integrations.filter((id) => id === 'claude' || id === 'codex'),
|
|
141
|
+
findings,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export async function runSpecKitLifecycle(options) {
|
|
145
|
+
const plan = [
|
|
146
|
+
`Spec Kit ${SPEC_KIT_VERSION}; requires local uv, uvx and git, invoked from the repository root. Downloads the official pinned source and dependencies into the uv cache; no global tool installation.`,
|
|
147
|
+
`uvx --from ${SOURCE} specify`,
|
|
148
|
+
'Configure integration files without checking whether Claude Code or Codex is installed; this does not verify harness runtime or trust.',
|
|
149
|
+
];
|
|
150
|
+
const refuse = (reason, observed) => ({
|
|
151
|
+
ok: false,
|
|
152
|
+
reason,
|
|
153
|
+
plan,
|
|
154
|
+
...(observed === undefined ? {} : { observed }),
|
|
155
|
+
});
|
|
156
|
+
if (!options.harnesses.length ||
|
|
157
|
+
new Set(options.harnesses).size !== options.harnesses.length ||
|
|
158
|
+
options.harnesses.some((harness) => harness !== 'claude-code' && harness !== 'codex'))
|
|
159
|
+
return refuse('invalid-harness-selection');
|
|
160
|
+
const harnesses = options.harnesses.map((harness) => harness === 'claude-code' ? 'claude' : 'codex');
|
|
161
|
+
const project = await resolveReadableInside(options.repoDir, '.specify', 'directory');
|
|
162
|
+
if (project.status !== 'ok' && project.status !== 'absent')
|
|
163
|
+
return refuse('spec-kit-project-path-unsafe');
|
|
164
|
+
const initialized = project.status === 'ok';
|
|
165
|
+
if (options.operation === 'add' && !initialized) {
|
|
166
|
+
plan.push(`init --here --force --non-interactive --ignore-agent-tools --integration ${harnesses[0]}`, ...harnesses.slice(1).map((harness) => `integration install ${harness}`));
|
|
167
|
+
}
|
|
168
|
+
else if (options.operation === 'add') {
|
|
169
|
+
plan.push('integration status --json', ...harnesses.map((harness) => `integration install ${harness} (only if absent from status)`));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
plan.push(...harnesses.map((harness) => `integration ${options.operation === 'apply' ? 'upgrade' : 'uninstall'} ${harness}`));
|
|
173
|
+
}
|
|
174
|
+
plan.push('integration status --json');
|
|
175
|
+
if (!options.consent)
|
|
176
|
+
return refuse('consent-required');
|
|
177
|
+
if (initialized && !options.managed && !options.adopt)
|
|
178
|
+
return refuse('explicit-adoption-required');
|
|
179
|
+
if (options.operation !== 'add' && (!initialized || !options.managed))
|
|
180
|
+
return refuse('managed-spec-kit-project-required');
|
|
181
|
+
const [uv, uvx, git] = await Promise.all([
|
|
182
|
+
locate('uv', options.repoDir),
|
|
183
|
+
locate('uvx', options.repoDir),
|
|
184
|
+
locate('git', options.repoDir),
|
|
185
|
+
]);
|
|
186
|
+
if (!uv || !uvx || !git)
|
|
187
|
+
return refuse('uv-uvx-and-git-required');
|
|
188
|
+
const runner = options.runner ?? runProviderProcess;
|
|
189
|
+
const invoke = (executable, args) => runner({
|
|
190
|
+
executable,
|
|
191
|
+
args,
|
|
192
|
+
repoDir: options.repoDir,
|
|
193
|
+
timeoutMs: 120_000,
|
|
194
|
+
maxOutputBytes: 256 * 1024,
|
|
195
|
+
});
|
|
196
|
+
const upstream = (args) => invoke(uvx, [...PREFIX, ...args]);
|
|
197
|
+
const succeeded = (result) => result.status === 'ok' && result.exitCode === 0;
|
|
198
|
+
const root = await invoke(git, ['-c', 'core.fsmonitor=false', 'rev-parse', '--show-toplevel']);
|
|
199
|
+
if (!succeeded(root))
|
|
200
|
+
return refuse('repository-root-required');
|
|
201
|
+
try {
|
|
202
|
+
if (path.relative(await realpath(options.repoDir), await realpath(root.stdout.trim())) !== '')
|
|
203
|
+
return refuse('repository-root-required');
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
return refuse('repository-root-required');
|
|
207
|
+
}
|
|
208
|
+
if (!initialized || !options.managed) {
|
|
209
|
+
const dirty = await invoke(git, [
|
|
210
|
+
'-c',
|
|
211
|
+
'core.fsmonitor=false',
|
|
212
|
+
'--no-optional-locks',
|
|
213
|
+
'status',
|
|
214
|
+
'--porcelain=v1',
|
|
215
|
+
'--untracked-files=all',
|
|
216
|
+
]);
|
|
217
|
+
if (!succeeded(dirty))
|
|
218
|
+
return refuse('git-status-unavailable');
|
|
219
|
+
if (dirty.stdout.trim())
|
|
220
|
+
return refuse('dirty-initialization-or-adoption-refused');
|
|
221
|
+
}
|
|
222
|
+
const commands = [];
|
|
223
|
+
if (options.operation === 'add' && !initialized) {
|
|
224
|
+
commands.push([
|
|
225
|
+
'init',
|
|
226
|
+
'--here',
|
|
227
|
+
'--force',
|
|
228
|
+
'--non-interactive',
|
|
229
|
+
'--ignore-agent-tools',
|
|
230
|
+
'--integration',
|
|
231
|
+
harnesses[0],
|
|
232
|
+
], ...harnesses.slice(1).map((harness) => ['integration', 'install', harness]));
|
|
233
|
+
}
|
|
234
|
+
else if (options.operation === 'add') {
|
|
235
|
+
const status = await upstream(['integration', 'status', '--json']);
|
|
236
|
+
const current = observe(status);
|
|
237
|
+
if (!succeeded(status) || current?.status !== 'ok')
|
|
238
|
+
return refuse('upstream-status-not-ready', current);
|
|
239
|
+
commands.push(...harnesses
|
|
240
|
+
.filter((harness) => !current.installedIntegrations.includes(harness))
|
|
241
|
+
.map((harness) => ['integration', 'install', harness]));
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
commands.push(...harnesses.map((harness) => [
|
|
245
|
+
'integration',
|
|
246
|
+
options.operation === 'apply' ? 'upgrade' : 'uninstall',
|
|
247
|
+
harness,
|
|
248
|
+
]));
|
|
249
|
+
}
|
|
250
|
+
let partial = false;
|
|
251
|
+
for (const command of commands) {
|
|
252
|
+
const result = await upstream(command);
|
|
253
|
+
if (result.status === 'cleanup-unconfirmed')
|
|
254
|
+
return refuse('upstream-cleanup-unconfirmed');
|
|
255
|
+
if (!succeeded(result)) {
|
|
256
|
+
partial = true;
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const status = await upstream(['integration', 'status', '--json']);
|
|
261
|
+
const observed = observe(status);
|
|
262
|
+
// v1.0.8 removes integration.json after the last successful uninstall.
|
|
263
|
+
// Its authoritative status then exits 1 with this exact empty-state finding.
|
|
264
|
+
if (options.operation === 'remove' &&
|
|
265
|
+
!partial &&
|
|
266
|
+
status.status === 'failed' &&
|
|
267
|
+
status.exitCode === 1 &&
|
|
268
|
+
observed?.status === 'error' &&
|
|
269
|
+
observed.installedIntegrations.length === 0 &&
|
|
270
|
+
observed.findings.length === 1 &&
|
|
271
|
+
observed.findings[0]?.code === 'integration-state-missing' &&
|
|
272
|
+
observed.findings[0]?.severity === 'error')
|
|
273
|
+
return { ok: true, observed, plan };
|
|
274
|
+
if (partial || !succeeded(status) || observed?.status !== 'ok')
|
|
275
|
+
return refuse('upstream-incomplete-use-adopt-and-status', observed);
|
|
276
|
+
const complete = harnesses.every((harness) => observed.installedIntegrations.includes(harness) === (options.operation !== 'remove'));
|
|
277
|
+
return complete
|
|
278
|
+
? { ok: true, observed, plan }
|
|
279
|
+
: refuse('upstream-harness-status-incomplete', observed);
|
|
280
|
+
}
|