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.
Files changed (237) hide show
  1. package/CHANGELOG.md +466 -3
  2. package/README.md +267 -299
  3. package/package.json +8 -14
  4. package/packages/cli/dist/commands/create.js +56 -88
  5. package/packages/cli/dist/commands/doctor.js +213 -0
  6. package/packages/cli/dist/commands/init.js +151 -54
  7. package/packages/cli/dist/commands/integrations.js +468 -0
  8. package/packages/cli/dist/commands/memory.js +63 -4
  9. package/packages/cli/dist/commands/setup-wizard.js +61 -0
  10. package/packages/cli/dist/commands/uninstall.js +1223 -0
  11. package/packages/cli/dist/commands/upgrade.js +489 -89
  12. package/packages/cli/dist/index.js +657 -49
  13. package/packages/cli/dist/integrations/declaration.js +158 -0
  14. package/packages/cli/dist/integrations/doctor-guards.js +126 -0
  15. package/packages/cli/dist/integrations/doctor-workflow.js +25 -0
  16. package/packages/cli/dist/integrations/mcp-json.js +107 -0
  17. package/packages/cli/dist/integrations/memory-doctor.js +124 -0
  18. package/packages/cli/dist/integrations/registry.js +22 -0
  19. package/packages/cli/dist/integrations/spawn.js +228 -0
  20. package/packages/cli/dist/integrations/spec-kit.js +280 -0
  21. package/packages/cli/dist/integrations/verify.js +193 -0
  22. package/packages/cli/dist/integrations/windows-job.js +275 -0
  23. package/packages/cli/dist/lib/elevated-paths.js +74 -0
  24. package/packages/cli/dist/lib/install-set.js +1 -44
  25. package/packages/cli/dist/lib/manifest.js +86 -9
  26. package/packages/cli/dist/lib/prompts.js +0 -23
  27. package/packages/cli/dist/lib/safe-path.js +193 -2
  28. package/packages/cli/dist/lib/safe-text.js +78 -0
  29. package/packages/cli/dist/lib/substitute.js +1 -52
  30. package/packages/cli/dist/lib/summary.js +5 -5
  31. package/packages/cli/dist/templates.js +4 -14
  32. package/scripts/prepare.mjs +28 -12
  33. package/templates/agent-os/subagent-routing.json +4 -4
  34. package/templates/agent-os/universal/.agents/skills/check-premises/SKILL.md +13 -9
  35. package/templates/agent-os/universal/.agents/skills/loop/SKILL.md +31 -6
  36. package/templates/agent-os/universal/.agents/skills/new-invariant/SKILL.md +5 -5
  37. package/templates/agent-os/universal/.agents/skills/pr-ship/SKILL.md +1 -1
  38. package/templates/agent-os/universal/.agents/skills/worktree-task/SKILL.md +3 -3
  39. package/templates/agent-os/universal/.claude/agents/code-reviewer.md +8 -6
  40. package/templates/agent-os/universal/.claude/agents/implementation-agent.md +41 -0
  41. package/templates/agent-os/universal/.claude/agents/prose-reviewer.md +8 -4
  42. package/templates/agent-os/universal/.claude/agents/security-scanner.md +4 -2
  43. package/templates/agent-os/universal/.claude/agents/test-writer.md +2 -2
  44. package/templates/agent-os/universal/.claude/hooks/inject-rules.mjs +83 -4
  45. package/templates/agent-os/universal/.claude/rules/autonomy.md +55 -27
  46. package/templates/agent-os/universal/.claude/rules/invariants.md +9 -8
  47. package/templates/agent-os/universal/.claude/rules/workflow.md +47 -22
  48. package/templates/agent-os/universal/.claude/scripts/decision-router.mjs +1 -1
  49. package/templates/agent-os/universal/.claude/scripts/detect-missed-gate.mjs +12 -9
  50. package/templates/agent-os/universal/.claude/scripts/doctor.mjs +100 -2
  51. package/templates/agent-os/universal/.claude/scripts/lib/claim-records.mjs +236 -6
  52. package/templates/agent-os/universal/.claude/scripts/queue/core.mjs +43 -0
  53. package/templates/agent-os/universal/.claude/scripts/queue/jira.mjs +18 -1
  54. package/templates/agent-os/universal/.claude/scripts/queue/state.mjs +2 -2
  55. package/templates/agent-os/universal/.claude/scripts/reconcile-external-prs.mjs +273 -35
  56. package/templates/agent-os/universal/.claude/scripts/revalidate.mjs +16 -0
  57. package/templates/agent-os/universal/.claude/settings.json +0 -8
  58. package/templates/agent-os/universal/.claude/skills/check-premises/SKILL.md +13 -9
  59. package/templates/agent-os/universal/.claude/skills/loop/SKILL.md +31 -6
  60. package/templates/agent-os/universal/.claude/skills/new-invariant/SKILL.md +5 -5
  61. package/templates/agent-os/universal/.claude/skills/pr-ship/SKILL.md +1 -1
  62. package/templates/agent-os/universal/.claude/skills/worktree-task/SKILL.md +3 -3
  63. package/templates/agent-os/universal/.codex/agents/code-reviewer.toml +2 -2
  64. package/templates/agent-os/universal/.codex/agents/implementation-agent.toml +6 -0
  65. package/templates/agent-os/universal/.codex/agents/prose-reviewer.toml +2 -2
  66. package/templates/agent-os/universal/.codex/agents/security-scanner.toml +1 -1
  67. package/templates/agent-os/universal/.codex/agents/test-writer.toml +1 -1
  68. package/templates/agent-os/universal/.codex/hooks.json +0 -10
  69. package/templates/agent-os/universal/AGENTS.md +208 -107
  70. package/templates/agent-os/universal/CLAUDE.md +15 -162
  71. package/templates/agent-os/universal/PLAN.md +17 -10
  72. package/templates/agent-os/universal/docs/decisions/agents-md-canonical.md +313 -0
  73. package/templates/agent-os/universal/docs/decisions/codex-adapter.md +15 -10
  74. package/templates/agent-os/universal/docs/decisions/review-lanes.md +12 -8
  75. package/templates/agent-os/universal/docs/decisions/session-start-wire-format.md +206 -0
  76. package/templates/agent-os/universal/docs/decisions/subagent-routing.md +6 -3
  77. package/templates/agent-os/universal/docs/decisions/workflow-layer-split.md +235 -0
  78. package/templates/agent-os/universal/layers.json +25 -30
  79. package/templates/hash-history.json +73 -24
  80. package/templates/release-ledger.json +3 -1
  81. package/packages/cli/dist/lib/composition.js +0 -20
  82. package/packages/cli/dist/lib/targets.js +0 -28
  83. package/packages/cli/dist/policy/benchmark/corpus.js +0 -165
  84. package/packages/cli/dist/policy/core/adapter.js +0 -18
  85. package/packages/cli/dist/policy/core/coverage.js +0 -253
  86. package/packages/cli/dist/policy/core/decision-record.js +0 -287
  87. package/packages/cli/dist/policy/core/declaration.js +0 -127
  88. package/packages/cli/dist/policy/core/evidence-matrix.js +0 -94
  89. package/packages/cli/dist/policy/core/probe.js +0 -442
  90. package/packages/cli/dist/policy/core/registry.js +0 -115
  91. package/packages/cli/dist/policy/core/validation.js +0 -275
  92. package/packages/cli/dist/policy/core/vocabulary.js +0 -123
  93. package/packages/cli/dist/policy/harness/claude.js +0 -47
  94. package/packages/cli/dist/policy/harness/codex.js +0 -87
  95. package/packages/cli/dist/policy/harness/index.js +0 -15
  96. package/packages/cli/dist/policy/harness/shared-hooks.js +0 -28
  97. package/packages/cli/dist/policy/index.js +0 -17
  98. package/templates/agent-os/init/AGENTS.md +0 -201
  99. package/templates/agent-os/init/CLAUDE.md +0 -201
  100. package/templates/agent-os/stack/aws-cdk/.agents/skills/post-deploy-verify/SKILL.md +0 -105
  101. package/templates/agent-os/stack/aws-cdk/.agents/skills/ro-debug/SKILL.md +0 -117
  102. package/templates/agent-os/stack/aws-cdk/.claude/agents/cdk-diff-reviewer.md +0 -89
  103. package/templates/agent-os/stack/aws-cdk/.claude/rules/aws-cdk.md +0 -105
  104. package/templates/agent-os/stack/aws-cdk/.claude/skills/post-deploy-verify/SKILL.md +0 -105
  105. package/templates/agent-os/stack/aws-cdk/.claude/skills/ro-debug/SKILL.md +0 -117
  106. package/templates/agent-os/stack/aws-cdk/.codex/agents/cdk-diff-reviewer.toml +0 -6
  107. package/templates/agent-os/stack/node-ts/.claude/hooks/dod-checks.json +0 -1
  108. package/templates/agent-os/stack/node-ts/.claude/rules/node-ts.md +0 -85
  109. package/templates/agent-os/universal/.claude/hooks/guard-core-purity.mjs +0 -89
  110. package/templates/agent-os/universal/.claude/hooks/guard-web-boundary.mjs +0 -68
  111. package/templates/agent-os/universal/.claude/rules/architecture.md +0 -81
  112. package/templates/skeleton/aws-serverless/.github/workflows/ci.yml +0 -28
  113. package/templates/skeleton/aws-serverless/.github/workflows/deploy.yml +0 -90
  114. package/templates/skeleton/aws-serverless/README.md +0 -181
  115. package/templates/skeleton/aws-serverless/apps/web/next.config.mjs +0 -17
  116. package/templates/skeleton/aws-serverless/apps/web/package.json +0 -19
  117. package/templates/skeleton/aws-serverless/apps/web/src/app/layout.tsx +0 -17
  118. package/templates/skeleton/aws-serverless/apps/web/src/app/page.tsx +0 -96
  119. package/templates/skeleton/aws-serverless/apps/web/src/lib/api.ts +0 -36
  120. package/templates/skeleton/aws-serverless/apps/web/src/lib/validate.ts +0 -23
  121. package/templates/skeleton/aws-serverless/apps/web/test/shared-validation.test.ts +0 -38
  122. package/templates/skeleton/aws-serverless/apps/web/tsconfig.json +0 -14
  123. package/templates/skeleton/aws-serverless/eslint.config.mjs +0 -20
  124. package/templates/skeleton/aws-serverless/gitignore +0 -56
  125. package/templates/skeleton/aws-serverless/infra/bin/app.ts +0 -79
  126. package/templates/skeleton/aws-serverless/infra/cdk.json +0 -3
  127. package/templates/skeleton/aws-serverless/infra/lib/app-stack.ts +0 -248
  128. package/templates/skeleton/aws-serverless/infra/lib/web-stack.ts +0 -45
  129. package/templates/skeleton/aws-serverless/infra/package.json +0 -18
  130. package/templates/skeleton/aws-serverless/infra/test/allowed-origins.test.ts +0 -301
  131. package/templates/skeleton/aws-serverless/infra/test/app-composition.test.ts +0 -137
  132. package/templates/skeleton/aws-serverless/infra/test/app-stack.test.ts +0 -120
  133. package/templates/skeleton/aws-serverless/infra/test/web-stack.test.ts +0 -44
  134. package/templates/skeleton/aws-serverless/package.json +0 -30
  135. package/templates/skeleton/aws-serverless/packages/core/package.json +0 -11
  136. package/templates/skeleton/aws-serverless/packages/core/src/events.ts +0 -14
  137. package/templates/skeleton/aws-serverless/packages/core/src/index.ts +0 -15
  138. package/templates/skeleton/aws-serverless/packages/core/src/note.ts +0 -69
  139. package/templates/skeleton/aws-serverless/packages/core/test/events.test.ts +0 -23
  140. package/templates/skeleton/aws-serverless/packages/core/test/note.test.ts +0 -101
  141. package/templates/skeleton/aws-serverless/packages/db/package.json +0 -14
  142. package/templates/skeleton/aws-serverless/packages/db/src/client.ts +0 -17
  143. package/templates/skeleton/aws-serverless/packages/db/src/index.ts +0 -2
  144. package/templates/skeleton/aws-serverless/packages/db/src/note-model.ts +0 -52
  145. package/templates/skeleton/aws-serverless/packages/db/test/note-model.test.ts +0 -91
  146. package/templates/skeleton/aws-serverless/packages/shared/package.json +0 -11
  147. package/templates/skeleton/aws-serverless/packages/shared/src/env.ts +0 -17
  148. package/templates/skeleton/aws-serverless/packages/shared/src/errors.ts +0 -33
  149. package/templates/skeleton/aws-serverless/packages/shared/src/index.ts +0 -3
  150. package/templates/skeleton/aws-serverless/packages/shared/src/logger.ts +0 -20
  151. package/templates/skeleton/aws-serverless/packages/shared/test/env.test.ts +0 -26
  152. package/templates/skeleton/aws-serverless/packages/shared/test/errors.test.ts +0 -28
  153. package/templates/skeleton/aws-serverless/packages/shared/test/logger.test.ts +0 -19
  154. package/templates/skeleton/aws-serverless/pnpm-lock.yaml +0 -2855
  155. package/templates/skeleton/aws-serverless/pnpm-workspace.yaml +0 -14
  156. package/templates/skeleton/aws-serverless/services/api/package.json +0 -15
  157. package/templates/skeleton/aws-serverless/services/api/src/adapters/sqs-publisher.ts +0 -26
  158. package/templates/skeleton/aws-serverless/services/api/src/handlers/create-note.ts +0 -69
  159. package/templates/skeleton/aws-serverless/services/api/src/handlers/list-notes.ts +0 -37
  160. package/templates/skeleton/aws-serverless/services/api/src/list-main.ts +0 -12
  161. package/templates/skeleton/aws-serverless/services/api/src/main.ts +0 -21
  162. package/templates/skeleton/aws-serverless/services/api/src/usecases/create-note.ts +0 -41
  163. package/templates/skeleton/aws-serverless/services/api/src/usecases/list-notes.ts +0 -14
  164. package/templates/skeleton/aws-serverless/services/api/test/create-note.handler.test.ts +0 -211
  165. package/templates/skeleton/aws-serverless/services/api/test/create-note.usecase.test.ts +0 -45
  166. package/templates/skeleton/aws-serverless/services/api/test/list-notes.test.ts +0 -122
  167. package/templates/skeleton/aws-serverless/services/api/test/sqs-publisher.test.ts +0 -22
  168. package/templates/skeleton/aws-serverless/services/worker/package.json +0 -12
  169. package/templates/skeleton/aws-serverless/services/worker/src/handlers/note-created.ts +0 -15
  170. package/templates/skeleton/aws-serverless/services/worker/src/main.ts +0 -7
  171. package/templates/skeleton/aws-serverless/services/worker/src/usecases/process-note-created.ts +0 -37
  172. package/templates/skeleton/aws-serverless/services/worker/test/note-created.test.ts +0 -61
  173. package/templates/skeleton/aws-serverless/tsconfig.base.json +0 -15
  174. package/templates/skeleton/aws-serverless/tsconfig.json +0 -16
  175. package/templates/skeleton/aws-serverless/vitest.config.ts +0 -14
  176. package/templates/skeleton/node-service/.github/workflows/ci.yml +0 -27
  177. package/templates/skeleton/node-service/.github/workflows/deploy.yml +0 -29
  178. package/templates/skeleton/node-service/README.md +0 -104
  179. package/templates/skeleton/node-service/apps/web/next.config.mjs +0 -17
  180. package/templates/skeleton/node-service/apps/web/package.json +0 -19
  181. package/templates/skeleton/node-service/apps/web/src/app/layout.tsx +0 -17
  182. package/templates/skeleton/node-service/apps/web/src/app/page.tsx +0 -96
  183. package/templates/skeleton/node-service/apps/web/src/lib/api.ts +0 -29
  184. package/templates/skeleton/node-service/apps/web/src/lib/validate.ts +0 -23
  185. package/templates/skeleton/node-service/apps/web/test/shared-validation.test.ts +0 -38
  186. package/templates/skeleton/node-service/apps/web/tsconfig.json +0 -14
  187. package/templates/skeleton/node-service/eslint.config.mjs +0 -20
  188. package/templates/skeleton/node-service/gitignore +0 -54
  189. package/templates/skeleton/node-service/package.json +0 -30
  190. package/templates/skeleton/node-service/packages/core/package.json +0 -11
  191. package/templates/skeleton/node-service/packages/core/src/events.ts +0 -14
  192. package/templates/skeleton/node-service/packages/core/src/index.ts +0 -15
  193. package/templates/skeleton/node-service/packages/core/src/note.ts +0 -69
  194. package/templates/skeleton/node-service/packages/core/test/events.test.ts +0 -23
  195. package/templates/skeleton/node-service/packages/core/test/note.test.ts +0 -101
  196. package/templates/skeleton/node-service/packages/db/package.json +0 -12
  197. package/templates/skeleton/node-service/packages/db/src/index.ts +0 -1
  198. package/templates/skeleton/node-service/packages/db/src/note-store.ts +0 -100
  199. package/templates/skeleton/node-service/packages/db/test/note-store.test.ts +0 -100
  200. package/templates/skeleton/node-service/packages/shared/package.json +0 -11
  201. package/templates/skeleton/node-service/packages/shared/src/env.ts +0 -17
  202. package/templates/skeleton/node-service/packages/shared/src/errors.ts +0 -33
  203. package/templates/skeleton/node-service/packages/shared/src/index.ts +0 -3
  204. package/templates/skeleton/node-service/packages/shared/src/logger.ts +0 -20
  205. package/templates/skeleton/node-service/packages/shared/test/env.test.ts +0 -26
  206. package/templates/skeleton/node-service/packages/shared/test/errors.test.ts +0 -28
  207. package/templates/skeleton/node-service/packages/shared/test/logger.test.ts +0 -19
  208. package/templates/skeleton/node-service/pnpm-lock.yaml +0 -2402
  209. package/templates/skeleton/node-service/pnpm-workspace.yaml +0 -13
  210. package/templates/skeleton/node-service/scripts/build-artifact.mjs +0 -34
  211. package/templates/skeleton/node-service/services/api/package.json +0 -17
  212. package/templates/skeleton/node-service/services/api/src/adapters/spool-publisher.ts +0 -23
  213. package/templates/skeleton/node-service/services/api/src/handlers/create-note.ts +0 -40
  214. package/templates/skeleton/node-service/services/api/src/handlers/list-notes.ts +0 -23
  215. package/templates/skeleton/node-service/services/api/src/main.ts +0 -40
  216. package/templates/skeleton/node-service/services/api/src/server.ts +0 -172
  217. package/templates/skeleton/node-service/services/api/src/static-dir.ts +0 -20
  218. package/templates/skeleton/node-service/services/api/src/usecases/create-note.ts +0 -30
  219. package/templates/skeleton/node-service/services/api/src/usecases/list-notes.ts +0 -14
  220. package/templates/skeleton/node-service/services/api/test/artifact.test.ts +0 -92
  221. package/templates/skeleton/node-service/services/api/test/create-note.handler.test.ts +0 -64
  222. package/templates/skeleton/node-service/services/api/test/create-note.usecase.test.ts +0 -43
  223. package/templates/skeleton/node-service/services/api/test/list-notes.test.ts +0 -48
  224. package/templates/skeleton/node-service/services/api/test/package-manager.test.ts +0 -40
  225. package/templates/skeleton/node-service/services/api/test/package-manager.ts +0 -51
  226. package/templates/skeleton/node-service/services/api/test/server.test.ts +0 -208
  227. package/templates/skeleton/node-service/services/api/test/spool-publisher.test.ts +0 -32
  228. package/templates/skeleton/node-service/services/api/test/static-dir.test.ts +0 -29
  229. package/templates/skeleton/node-service/services/worker/package.json +0 -16
  230. package/templates/skeleton/node-service/services/worker/src/main.ts +0 -28
  231. package/templates/skeleton/node-service/services/worker/src/spool.ts +0 -60
  232. package/templates/skeleton/node-service/services/worker/src/usecases/process-note-created.ts +0 -38
  233. package/templates/skeleton/node-service/services/worker/test/process-note-created.test.ts +0 -34
  234. package/templates/skeleton/node-service/services/worker/test/spool.test.ts +0 -76
  235. package/templates/skeleton/node-service/tsconfig.base.json +0 -15
  236. package/templates/skeleton/node-service/tsconfig.json +0 -13
  237. 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
+ }