forge-workflow 0.1.0-beta.5 → 0.1.0-beta.7

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 (268) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +48 -0
  3. package/CLAUDE.md +0 -12
  4. package/CODING_STANDARDS.md +72 -0
  5. package/bin/forge.js +12 -1
  6. package/docs/guides/MIGRATION.md +3 -3
  7. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +4 -0
  8. package/docs/reference/INSTALL.md +4 -0
  9. package/docs/reference/LEGACY_CLAIM_REPAIR.md +112 -0
  10. package/docs/reference/RELEASE.md +4 -4
  11. package/docs/reference/github-accounts.md +134 -0
  12. package/docs/reference/shepherd.md +63 -13
  13. package/lib/adapters/pr-state-adapter.js +15 -2
  14. package/lib/base-remote.js +138 -0
  15. package/lib/beta5-compatibility-evidence.js +1093 -0
  16. package/lib/bun-lockfile-proof.js +413 -0
  17. package/lib/bun-workflow-pins.js +461 -0
  18. package/lib/capabilities/index.js +9 -0
  19. package/lib/capabilities/model.js +141 -0
  20. package/lib/capabilities/probes.js +347 -0
  21. package/lib/codex-skills.js +2 -2
  22. package/lib/commands/_manifest.js +1 -0
  23. package/lib/commands/_registry.js +48 -18
  24. package/lib/commands/clean.js +57 -1
  25. package/lib/commands/doctor.js +37 -6
  26. package/lib/commands/gate.js +197 -27
  27. package/lib/commands/github.js +215 -0
  28. package/lib/commands/hooks.js +54 -6
  29. package/lib/commands/memory.js +66 -2
  30. package/lib/commands/merge.js +720 -73
  31. package/lib/commands/plan.js +33 -2
  32. package/lib/commands/pr.js +2 -0
  33. package/lib/commands/preflight.js +10 -2
  34. package/lib/commands/push.js +120 -9
  35. package/lib/commands/recall.js +95 -61
  36. package/lib/commands/release.js +23 -2
  37. package/lib/commands/remember.js +28 -4
  38. package/lib/commands/serve.js +26 -9
  39. package/lib/commands/setup.js +132 -4
  40. package/lib/commands/shepherd.js +578 -72
  41. package/lib/commands/ship.js +15 -69
  42. package/lib/commands/skill.js +8 -0
  43. package/lib/commands/team.js +47 -8
  44. package/lib/commands/test.js +163 -4
  45. package/lib/commands/validate.js +78 -22
  46. package/lib/commands/worktree.js +155 -19
  47. package/lib/fixtures/beta5-corpus/v1/README.md +9 -0
  48. package/lib/fixtures/beta5-corpus/v1/contract/command-contract.json +26 -0
  49. package/lib/fixtures/beta5-corpus/v1/contract/package-contract.json +13 -0
  50. package/lib/fixtures/beta5-corpus/v1/contract/workflow-stage-matrix.json +8 -0
  51. package/lib/fixtures/beta5-corpus/v1/manifest.json +25 -0
  52. package/lib/fixtures/beta5-corpus/v1/state/comments.jsonl +1 -0
  53. package/lib/fixtures/beta5-corpus/v1/state/config.yaml +6 -0
  54. package/lib/fixtures/beta5-corpus/v1/state/dependencies.jsonl +1 -0
  55. package/lib/fixtures/beta5-corpus/v1/state/issues.jsonl +2 -0
  56. package/lib/fixtures/beta5-corpus/v1/state/kernel.sql +20 -0
  57. package/lib/forge-issues.js +78 -0
  58. package/lib/gate-events.js +98 -10
  59. package/lib/github-context.js +308 -0
  60. package/lib/global-flags.js +1 -0
  61. package/lib/hook-renderer.js +29 -1
  62. package/lib/issue-render.js +19 -0
  63. package/lib/kernel/broker.js +723 -31
  64. package/lib/kernel/claim-reconciler.js +238 -0
  65. package/lib/kernel/lease-enforcer.js +9 -4
  66. package/lib/kernel/legacy-claim-repair.js +442 -0
  67. package/lib/kernel/live-claim-projection.js +26 -0
  68. package/lib/kernel/migrations.js +118 -3
  69. package/lib/kernel/readiness-model.js +184 -12
  70. package/lib/kernel/schema.js +49 -1
  71. package/lib/kernel/sqlite-driver.js +3322 -183
  72. package/lib/kernel/taxonomy-validator.js +4 -1
  73. package/lib/kernel/windows-private-acl.js +239 -0
  74. package/lib/memory/hygiene.js +191 -0
  75. package/lib/memory/router.js +94 -27
  76. package/lib/memory/usage-evidence.js +4 -0
  77. package/lib/memory-digest.js +59 -0
  78. package/lib/merge-rules.js +135 -17
  79. package/lib/npm-publish-workflow.js +233 -40
  80. package/lib/package-root.js +2 -0
  81. package/lib/pr-monitor/auto-actions.js +169 -28
  82. package/lib/pr-monitor/differ.js +110 -4
  83. package/lib/pr-monitor/events.js +0 -0
  84. package/lib/pr-monitor/flow-monitor.js +1424 -0
  85. package/lib/pr-monitor/gather.js +251 -44
  86. package/lib/pr-monitor/journal.js +0 -37
  87. package/lib/pr-monitor/monitor.js +117 -10
  88. package/lib/pr-monitor/process-identity.js +117 -0
  89. package/lib/pr-monitor/reconcile-executor.js +1101 -625
  90. package/lib/pr-monitor/reconcile.js +0 -0
  91. package/lib/pr-monitor/render-summary.js +121 -24
  92. package/lib/pr-monitor/review-preflight.js +269 -0
  93. package/lib/pr-monitor/shepherd-lease.js +28 -19
  94. package/lib/pr-monitor/verdict.js +438 -0
  95. package/lib/pr-monitor/watch-lifecycle.js +144 -38
  96. package/lib/pr-monitor/watch-owner.js +1414 -0
  97. package/lib/pr-monitor/watch.js +129 -58
  98. package/lib/pr-shepherd.js +17 -3
  99. package/lib/project-memory.js +145 -3
  100. package/lib/protected-state-authority.js +799 -4
  101. package/lib/protected-state-surfaces.js +181 -3
  102. package/lib/release-readiness.js +2 -3
  103. package/lib/review-adapter.js +65 -0
  104. package/lib/skills-sync.js +65 -32
  105. package/lib/validation/risk-manifest.js +339 -0
  106. package/lib/validation-receipt.js +190 -0
  107. package/lib/workflow/enforce-stage.js +44 -0
  108. package/lib/workflow/plan-authority.js +225 -0
  109. package/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
  110. package/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
  111. package/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
  112. package/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
  113. package/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
  114. package/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
  115. package/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
  116. package/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
  117. package/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
  118. package/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
  119. package/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
  120. package/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
  121. package/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
  122. package/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
  123. package/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
  124. package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
  125. package/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
  126. package/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
  127. package/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
  128. package/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
  129. package/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
  130. package/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
  131. package/node_modules/@forge/contracts/index.js +32 -0
  132. package/node_modules/@forge/contracts/package.json +35 -0
  133. package/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
  134. package/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
  135. package/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
  136. package/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
  137. package/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
  138. package/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
  139. package/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
  140. package/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
  141. package/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
  142. package/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
  143. package/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
  144. package/node_modules/@forge/contracts/src/baseline.js +23 -0
  145. package/node_modules/@forge/contracts/src/canonical.js +151 -0
  146. package/node_modules/@forge/contracts/src/definitions.js +176 -0
  147. package/node_modules/@forge/contracts/src/identity.js +42 -0
  148. package/node_modules/@forge/contracts/src/schema.js +72 -0
  149. package/node_modules/@forge/contracts/src/validate.js +305 -0
  150. package/node_modules/@forge/flow/index.js +114 -0
  151. package/node_modules/@forge/flow/package.json +34 -0
  152. package/node_modules/@forge/flow/src/bounded-loop.js +415 -0
  153. package/node_modules/@forge/flow/src/efficiency-supervisor.js +89 -0
  154. package/node_modules/@forge/flow/src/executor.js +279 -0
  155. package/node_modules/@forge/flow/src/monitor-durability.js +419 -0
  156. package/node_modules/@forge/flow/src/monitor-runtime.js +460 -0
  157. package/node_modules/@forge/flow/src/process-lifecycle.js +469 -0
  158. package/node_modules/@forge/flow/src/skill-runtime.js +343 -0
  159. package/node_modules/@forge/memory/index.js +331 -0
  160. package/node_modules/@forge/memory/package.json +34 -0
  161. package/node_modules/@forge/memory/src/authority-provider.js +67 -0
  162. package/node_modules/@forge/memory/src/backend-registry.js +166 -0
  163. package/node_modules/@forge/memory/src/feedback-intake.js +243 -0
  164. package/node_modules/@forge/memory/src/pr-lifecycle-authority.js +946 -0
  165. package/node_modules/@forge/memory/src/usage-evidence.js +205 -0
  166. package/package.json +16 -4
  167. package/packages/flow/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
  168. package/packages/flow/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
  169. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
  170. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
  171. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
  172. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
  173. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
  174. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
  175. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
  176. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
  177. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
  178. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
  179. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
  180. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
  181. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
  182. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
  183. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
  184. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
  185. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
  186. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
  187. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
  188. package/packages/flow/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
  189. package/packages/flow/node_modules/@forge/contracts/index.js +32 -0
  190. package/packages/flow/node_modules/@forge/contracts/package.json +35 -0
  191. package/packages/flow/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
  192. package/packages/flow/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
  193. package/packages/flow/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
  194. package/packages/flow/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
  195. package/packages/flow/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
  196. package/packages/flow/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
  197. package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
  198. package/packages/flow/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
  199. package/packages/flow/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
  200. package/packages/flow/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
  201. package/packages/flow/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
  202. package/packages/flow/node_modules/@forge/contracts/src/baseline.js +23 -0
  203. package/packages/flow/node_modules/@forge/contracts/src/canonical.js +151 -0
  204. package/packages/flow/node_modules/@forge/contracts/src/definitions.js +176 -0
  205. package/packages/flow/node_modules/@forge/contracts/src/identity.js +42 -0
  206. package/packages/flow/node_modules/@forge/contracts/src/schema.js +72 -0
  207. package/packages/flow/node_modules/@forge/contracts/src/validate.js +305 -0
  208. package/packages/memory/node_modules/@forge/contracts/compatibility-matrix.v1.json +1 -0
  209. package/packages/memory/node_modules/@forge/contracts/contract-baseline.v1.json +134 -0
  210. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/canonical-hash.json +1 -0
  211. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/contract-inputs.v1.json +71 -0
  212. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/feedback-secret-reject.json +1 -0
  213. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/identity-conflict.json +1 -0
  214. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-envelope-inputs.v1.json +16 -0
  215. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/malformed-missing-required.json +1 -0
  216. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-bounds-reject.json +1 -0
  217. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/monitor-receipt-privacy-reject.json +1 -0
  218. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/not-executed-receipt.json +1 -0
  219. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/privacy-redaction.json +1 -0
  220. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/retry-identical.json +1 -0
  221. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/stale-authority-reject.json +1 -0
  222. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-privacy-reject.json +1 -0
  223. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-live-reject.json +1 -0
  224. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/structured-error-stripe-test-reject.json +1 -0
  225. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-advisory-roundtrip.json +1 -0
  226. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/unknown-consequential-reject.json +1 -0
  227. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-full.json +1 -0
  228. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/valid-minimal.json +1 -0
  229. package/packages/memory/node_modules/@forge/contracts/fixtures/v1/wrong-capability-digest.json +1 -0
  230. package/packages/memory/node_modules/@forge/contracts/index.js +32 -0
  231. package/packages/memory/node_modules/@forge/contracts/package.json +35 -0
  232. package/packages/memory/node_modules/@forge/contracts/schemas/v1/capability-manifest.schema.json +1 -0
  233. package/packages/memory/node_modules/@forge/contracts/schemas/v1/claim-request.schema.json +1 -0
  234. package/packages/memory/node_modules/@forge/contracts/schemas/v1/context-packet.schema.json +1 -0
  235. package/packages/memory/node_modules/@forge/contracts/schemas/v1/delivery-receipt.schema.json +1 -0
  236. package/packages/memory/node_modules/@forge/contracts/schemas/v1/feedback-report.schema.json +1 -0
  237. package/packages/memory/node_modules/@forge/contracts/schemas/v1/lease-receipt.schema.json +1 -0
  238. package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-event.schema.json +1 -0
  239. package/packages/memory/node_modules/@forge/contracts/schemas/v1/monitor-receipt.schema.json +1 -0
  240. package/packages/memory/node_modules/@forge/contracts/schemas/v1/run-receipt.schema.json +1 -0
  241. package/packages/memory/node_modules/@forge/contracts/schemas/v1/structured-error.schema.json +1 -0
  242. package/packages/memory/node_modules/@forge/contracts/schemas/v1/work-packet.schema.json +1 -0
  243. package/packages/memory/node_modules/@forge/contracts/src/baseline.js +23 -0
  244. package/packages/memory/node_modules/@forge/contracts/src/canonical.js +151 -0
  245. package/packages/memory/node_modules/@forge/contracts/src/definitions.js +176 -0
  246. package/packages/memory/node_modules/@forge/contracts/src/identity.js +42 -0
  247. package/packages/memory/node_modules/@forge/contracts/src/schema.js +72 -0
  248. package/packages/memory/node_modules/@forge/contracts/src/validate.js +305 -0
  249. package/scripts/commitlint.js +13 -15
  250. package/scripts/generate-risk-manifest.js +91 -0
  251. package/scripts/github-context-bridge.sh +10 -0
  252. package/scripts/legacy-claim-repair.js +145 -0
  253. package/scripts/lib/behavioral-eval-runtime.js +3 -2
  254. package/scripts/process-tree.js +14 -2
  255. package/scripts/protected-state-check.js +440 -17
  256. package/scripts/sync-agent-skills.js +333 -34
  257. package/scripts/test-full-suite.js +704 -18
  258. package/scripts/test-profile.js +13 -3
  259. package/scripts/test.js +96 -15
  260. package/skills/coverage.json +1 -0
  261. package/skills/review/SKILL.md +2 -0
  262. package/skills/review/evals/scorecard.json +2 -2
  263. package/skills/setup/SKILL.md +18 -0
  264. package/skills/setup/evals/scorecard.json +3 -3
  265. package/skills/shepherd/SKILL.md +19 -2
  266. package/skills/shepherd/evals/scorecard.json +3 -3
  267. package/skills/validate/SKILL.md +3 -0
  268. package/skills/validate/evals/scorecard.json +1 -1
@@ -12,6 +12,7 @@ const { execFileSync } = require('node:child_process');
12
12
  const fs = require('node:fs');
13
13
  const path = require('node:path');
14
14
  const { secureExecFileSync } = require('../shell-utils');
15
+ const validationReceipt = require('../validation-receipt');
15
16
 
16
17
  // Constants
17
18
  const CHECK_TYPES = {
@@ -44,10 +45,12 @@ const CONFLICT_DIVIDER_PATTERN = /^=======$/;
44
45
  const CONFLICT_END_PATTERN = /^>>>>>>>.*$/;
45
46
  const GIT_CONFLICT_SCAN_MAX_BUFFER = 16 * 1024 * 1024;
46
47
  const VALIDATION_COMMAND_TIMEOUT_MS = 600000;
47
- const VALIDATION_COMMAND_TIMEOUT_MINUTES = Math.round(VALIDATION_COMMAND_TIMEOUT_MS / 60000);
48
+ // Match the full-suite runner's measured headroom; its verbose output exceeds 1 MiB.
49
+ const FULL_SUITE_TIMEOUT_MS = 25 * 60 * 1000;
50
+ const FULL_SUITE_MAX_BUFFER = 16 * 1024 * 1024;
48
51
 
49
- function validationTimeoutMessage(label) {
50
- return `${label} timed out after ${VALIDATION_COMMAND_TIMEOUT_MINUTES} minutes`;
52
+ function validationTimeoutMessage(label, timeoutMs = VALIDATION_COMMAND_TIMEOUT_MS) {
53
+ return `${label} timed out after ${Math.round(timeoutMs / 60000)} minutes`;
51
54
  }
52
55
 
53
56
  function listConflictScanFiles(rootDir) {
@@ -80,8 +83,15 @@ function shouldSkipConflictScanPath(relativePath) {
80
83
  ));
81
84
  }
82
85
 
83
- function getExecOptions() {
84
- return { encoding: 'utf8', cwd: process.cwd(), timeout: VALIDATION_COMMAND_TIMEOUT_MS };
86
+ /** Build bounded process options, with extra headroom only for Forge's full suite. */
87
+ function getExecOptions(rootDir = process.cwd(), fullSuite = false) {
88
+ const options = {
89
+ encoding: 'utf8',
90
+ cwd: rootDir,
91
+ timeout: fullSuite ? FULL_SUITE_TIMEOUT_MS : VALIDATION_COMMAND_TIMEOUT_MS,
92
+ };
93
+ if (fullSuite) options.maxBuffer = FULL_SUITE_MAX_BUFFER;
94
+ return options;
85
95
  }
86
96
 
87
97
  const ERROR_PATTERNS = {
@@ -130,7 +140,7 @@ function parseNumber(match, index = 1, defaultValue = 0) {
130
140
  */
131
141
  function getCheckStatus(check) {
132
142
  if (!check) return null;
133
- if (check.skipped) return 'SKIPPED';
143
+ if (check.skipped === true) return 'SKIPPED';
134
144
  return check.success ? 'PASS' : 'FAIL';
135
145
  }
136
146
 
@@ -290,6 +300,7 @@ async function runTypeCheck() {
290
300
  return {
291
301
  success: true,
292
302
  skipped: true,
303
+ notConfigured: true,
293
304
  duration: Date.now() - startTime,
294
305
  message: 'TypeScript not configured (no tsconfig.json)',
295
306
  };
@@ -343,17 +354,18 @@ async function runTypeCheck() {
343
354
  * Run ESLint
344
355
  * Executes eslint . to check code quality
345
356
  *
357
+ * @param {typeof execFileSync} exec - Synchronous process runner
346
358
  * @returns {Promise<{success: boolean, duration: number, warnings?: number, errors?: number, message?: string}>} Lint result
347
359
  * @example
348
360
  * const result = await runLint();
349
361
  * console.log(`Warnings: ${result.warnings}, Errors: ${result.errors}`);
350
362
  */
351
- async function runLint() {
363
+ async function runLint(exec = execFileSync) {
352
364
  const startTime = Date.now();
353
365
 
354
366
  try {
355
367
  // Run eslint with no output (exit code determines success)
356
- execFileSync('eslint', ['.'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
368
+ exec('eslint', ['.'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
357
369
 
358
370
  return {
359
371
  success: true,
@@ -556,7 +568,7 @@ async function runSecurityScan() { // NOSONAR S3776
556
568
 
557
569
  /**
558
570
  * Run all tests
559
- * Executes bun test to run the test suite
571
+ * Uses Forge's full-suite runner when present, with raw Bun as an external-repository fallback
560
572
  *
561
573
  * @returns {Promise<{success: boolean, duration: number, passed: number, failed: number, total: number, message?: string}>} Test execution result
562
574
  * @example
@@ -565,6 +577,22 @@ async function runSecurityScan() { // NOSONAR S3776
565
577
  */
566
578
  function parseTestCounts(output) {
567
579
  const text = String(output || '');
580
+ const aggregateMatches = [...text.matchAll(
581
+ /Full suite aggregate: status=(PASS|FAIL|INCOMPLETE) tests=(\d+) assertions=\d+ passed=(\d+) failed=(\d+) errors=(\d+) skipped=(\d+)/g,
582
+ )]; // NOSONAR S5852 - fixed tokens and bounded numeric fields
583
+ const aggregate = aggregateMatches.at(-1);
584
+ if (aggregate) {
585
+ const total = parseNumber(aggregate, 2);
586
+ return {
587
+ status: aggregate[1],
588
+ passed: parseNumber(aggregate, 3),
589
+ failed: parseNumber(aggregate, 4),
590
+ errors: parseNumber(aggregate, 5),
591
+ skipped: parseNumber(aggregate, 6),
592
+ total,
593
+ testsFound: total > 0,
594
+ };
595
+ }
568
596
  const passed = parseNumber(/(\d+) pass/.exec(text)); // NOSONAR S5852 - bounded \d+ pattern
569
597
  const failed = parseNumber(/(\d+) fail/.exec(text)); // NOSONAR S5852 - bounded \d+ pattern
570
598
  const skipped = parseNumber(/(\d+) skip/.exec(text)); // NOSONAR S5852 - bounded \d+ pattern
@@ -573,13 +601,31 @@ function parseTestCounts(output) {
573
601
  return { passed, failed, skipped, total, testsFound: total > 0 };
574
602
  }
575
603
 
576
- async function runAllTests(exec = execFileSync) {
604
+ function isForgeCheckout(rootDir) {
605
+ try {
606
+ const manifest = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8'));
607
+ return manifest.name === 'forge-workflow'
608
+ && manifest.bin?.forge === 'bin/forge.js'
609
+ && manifest.scripts?.['test:full:parallel'] === 'node scripts/test-full-suite.js'
610
+ && fs.existsSync(path.join(rootDir, 'scripts', 'test-full-suite.js'));
611
+ } catch (error) {
612
+ if (error instanceof SyntaxError || error.code === 'ENOENT' || error.code === 'ENOTDIR') return false;
613
+ throw error;
614
+ }
615
+ }
616
+
617
+ async function runAllTests(exec = execFileSync, rootDir = process.cwd()) {
577
618
  const startTime = Date.now();
619
+ const useFullSuite = isForgeCheckout(rootDir);
620
+ const command = useFullSuite ? 'node' : 'bun';
621
+ const args = useFullSuite ? ['scripts/test-full-suite.js'] : ['test', '--timeout', '30000'];
622
+ const execOptions = getExecOptions(rootDir, useFullSuite);
578
623
 
579
624
  try {
580
- const result = exec('bun', ['test', '--timeout', '30000'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
625
+ const result = exec(command, args, execOptions); // NOSONAR S4036 - fixed CLI commands, no user input, developer tool context
581
626
 
582
- const { passed, failed, skipped, total, testsFound } = parseTestCounts(result);
627
+ const { status, passed, failed, errors = 0, skipped, total, testsFound } = parseTestCounts(result);
628
+ const success = failed === 0 && errors === 0 && (status === undefined || status === 'PASS');
583
629
 
584
630
  // B2 (N1): a bun run that executes 0 tests must NEVER report PASS — that
585
631
  // manufactures false confidence. Surface it as an explicit SKIP instead.
@@ -588,6 +634,7 @@ async function runAllTests(exec = execFileSync) {
588
634
  success: true,
589
635
  skipped: true,
590
636
  testsFound: false,
637
+ fullSuite: useFullSuite,
591
638
  duration: Date.now() - startTime,
592
639
  passed,
593
640
  failed,
@@ -597,27 +644,29 @@ async function runAllTests(exec = execFileSync) {
597
644
  }
598
645
 
599
646
  return {
600
- success: failed === 0,
647
+ success,
601
648
  testsFound: true,
649
+ fullSuite: useFullSuite,
602
650
  duration: Date.now() - startTime,
603
651
  passed,
604
652
  failed,
605
653
  skipped,
606
654
  total,
607
- message: failed === 0
655
+ message: success
608
656
  ? `All ${total} tests passed`
609
- : `${failed}/${total} tests failed`,
657
+ : `${failed + errors}/${total} tests failed`,
610
658
  };
611
659
  } catch (error) {
612
660
  // Check for timeout
613
- if (error.killed && error.signal === 'SIGTERM') {
661
+ if (error.code === 'ETIMEDOUT' || (error.killed && error.signal === 'SIGTERM')) {
614
662
  return {
615
663
  success: false,
664
+ fullSuite: useFullSuite,
616
665
  duration: Date.now() - startTime,
617
666
  passed: 0,
618
667
  failed: 0,
619
668
  total: 0,
620
- message: validationTimeoutMessage('Test execution'),
669
+ message: validationTimeoutMessage('Test execution', execOptions.timeout),
621
670
  };
622
671
  }
623
672
 
@@ -627,11 +676,12 @@ async function runAllTests(exec = execFileSync) {
627
676
  success: true,
628
677
  skipped: true,
629
678
  testsFound: false,
679
+ fullSuite: useFullSuite,
630
680
  duration: Date.now() - startTime,
631
681
  passed: 0,
632
682
  failed: 0,
633
683
  total: 0,
634
- message: 'Tests skipped: bun test runner not found. Install bun to run tests.',
684
+ message: 'Tests skipped: test runner not found. Install Node.js and Bun to run tests.',
635
685
  };
636
686
  }
637
687
 
@@ -641,12 +691,13 @@ async function runAllTests(exec = execFileSync) {
641
691
  const passed = counts.passed;
642
692
  // bun exited non-zero: if the output shows no explicit failure count, treat
643
693
  // it as at least one failure rather than swallowing the non-zero exit.
644
- const failed = counts.failed > 0 ? counts.failed : Math.max(1, counts.failed);
694
+ const failed = counts.failed > 0 ? counts.failed : Math.max(1, counts.errors || 0);
645
695
  const total = counts.total > 0 ? counts.total : passed + failed + counts.skipped;
646
696
 
647
697
  return {
648
698
  success: false,
649
699
  testsFound: counts.testsFound,
700
+ fullSuite: useFullSuite,
650
701
  duration: Date.now() - startTime,
651
702
  passed,
652
703
  failed,
@@ -680,6 +731,9 @@ async function runAllTests(exec = execFileSync) {
680
731
  */
681
732
  async function executeValidate(options = {}) { // NOSONAR S3776
682
733
  const { skip = [], continueOnError = true, rootDir = process.cwd() } = options || {};
734
+ const receiptApi = options?.validationReceipt || validationReceipt;
735
+ const validationSnapshot = receiptApi.beginValidation(rootDir);
736
+ const receiptSnapshot = skip.length === 0 ? validationSnapshot : null;
683
737
 
684
738
  const checks = {};
685
739
  const failedChecks = [];
@@ -753,8 +807,8 @@ async function executeValidate(options = {}) { // NOSONAR S3776
753
807
  // 4. Tests
754
808
  if (!skip.includes(CHECK_TYPES.TESTS)) {
755
809
  try {
756
- checks.tests = await runAllTests();
757
- if (!checks.tests.success && !checks.tests.skipped) {
810
+ checks.tests = await runAllTests(execFileSync, rootDir);
811
+ if (!checks.tests.success && checks.tests.skipped !== true) {
758
812
  failedChecks.push(CHECK_TYPES.TESTS);
759
813
  if (!continueOnError) {
760
814
  return buildResult(checks, failedChecks, errors, startTime);
@@ -766,7 +820,9 @@ async function executeValidate(options = {}) { // NOSONAR S3776
766
820
  }
767
821
  }
768
822
 
769
- return buildResult(checks, failedChecks, errors, startTime);
823
+ const result = buildResult(checks, failedChecks, errors, startTime);
824
+ result.validationReceipt = receiptApi.completeValidation(rootDir, receiptSnapshot, result);
825
+ return result;
770
826
  }
771
827
 
772
828
  /**
@@ -109,20 +109,106 @@ function isLinkPermissionError(error) {
109
109
  // inspect, and `yarn check` was removed in Berry, so there is nothing to verify.
110
110
  const PROBE_LESS_MANAGERS = new Set(['yarn']);
111
111
 
112
+ function samePath(left, right) {
113
+ return path.relative(left, right) === '';
114
+ }
115
+
112
116
  /**
113
- * Direct dependencies the worktree declares. Optional/peer deps are excluded —
114
- * they may legitimately be absent. An unreadable package.json yields none, so an
115
- * unverifiable worktree is left alone rather than force-reinstalled.
117
+ * Direct dependencies the root and its workspaces declare. Optional/peer deps
118
+ * are excluded — they may legitimately be absent. Workspace discovery fails
119
+ * closed so an incomplete manifest set cannot prove an install complete.
116
120
  * @param {string} worktreePath - Absolute path to the worktree
117
121
  * @param {object} fsApi - fs module (for DI)
118
- * @returns {string[]} Declared package names
122
+ * @returns {Array<{dir: string, name: string, workspaceDir: string|null}>} Declaring directory and package name
119
123
  */
120
124
  function declaredDependencies(worktreePath, fsApi) {
125
+ let rootPkg;
126
+ try {
127
+ rootPkg = JSON.parse(fsApi.readFileSync(path.join(worktreePath, 'package.json'), 'utf8'));
128
+ } catch (error) {
129
+ throw new Error(`Cannot verify dependencies: unreadable worktree package.json: ${error.message}`);
130
+ }
131
+
132
+ let workspacePatterns = [];
133
+ if (rootPkg.workspaces != null) {
134
+ workspacePatterns = Array.isArray(rootPkg.workspaces)
135
+ ? rootPkg.workspaces
136
+ : rootPkg.workspaces.packages;
137
+ if (!Array.isArray(workspacePatterns)) {
138
+ throw new Error('Cannot verify dependencies: workspaces must be an array or an object with a packages array');
139
+ }
140
+ }
141
+
142
+ if (workspacePatterns.length && typeof fsApi.globSync !== 'function') {
143
+ throw new Error('Cannot verify dependencies: this runtime does not provide workspace glob support');
144
+ }
145
+
146
+ const manifestPaths = new Set();
147
+ for (const rawPattern of workspacePatterns) {
148
+ if (typeof rawPattern !== 'string' || !rawPattern.trim()) {
149
+ throw new Error(`Cannot verify dependencies: invalid workspace pattern ${JSON.stringify(rawPattern)}`);
150
+ }
151
+ const exclude = rawPattern.startsWith('!');
152
+ const pattern = (exclude ? rawPattern.slice(1) : rawPattern).replace(/\\/g, '/').replace(/\/+$/, '');
153
+ const resolvedPattern = resolveRepoRelativePath(worktreePath, pattern);
154
+ if (!pattern || pattern.includes('\0') || path.isAbsolute(pattern) || !resolvedPattern.insideRoot) {
155
+ throw new Error(`Cannot verify dependencies: workspace pattern resolves outside the worktree: ${rawPattern}`);
156
+ }
157
+
158
+ let matches;
159
+ try {
160
+ matches = fsApi.globSync(`${pattern}/package.json`, {
161
+ cwd: worktreePath,
162
+ exclude: candidate => String(candidate).split(/[\\/]/).includes('node_modules'),
163
+ });
164
+ } catch (error) {
165
+ throw new Error(`Cannot verify dependencies: workspace glob failed for ${rawPattern}: ${error.message}`);
166
+ }
167
+ for (const match of matches) {
168
+ const resolved = resolveRepoRelativePath(worktreePath, String(match));
169
+ if (!resolved.insideRoot) {
170
+ throw new Error(`Cannot verify dependencies: workspace glob resolved outside the worktree: ${match}`);
171
+ }
172
+ const existing = [...manifestPaths].find(manifestPath => samePath(manifestPath, resolved.target));
173
+ if (exclude && existing) manifestPaths.delete(existing);
174
+ else if (!exclude && !existing) manifestPaths.add(resolved.target);
175
+ }
176
+ }
177
+
178
+ const manifests = [{ dir: worktreePath, pkg: rootPkg }];
179
+ for (const manifestPath of manifestPaths) {
180
+ try {
181
+ manifests.push({
182
+ dir: path.dirname(manifestPath),
183
+ pkg: JSON.parse(fsApi.readFileSync(manifestPath, 'utf8')),
184
+ });
185
+ } catch (error) {
186
+ if (error && ['ENOENT', 'ENOTDIR'].includes(error.code)) continue;
187
+ throw new Error(`Cannot verify dependencies: unreadable workspace manifest ${manifestPath}: ${error.message}`);
188
+ }
189
+ }
190
+
191
+ const workspaceDirs = new Map();
192
+ for (const { dir, pkg } of manifests.slice(1)) {
193
+ if (!pkg.name) continue;
194
+ if (workspaceDirs.has(pkg.name) && !samePath(workspaceDirs.get(pkg.name), dir)) {
195
+ throw new Error(`Cannot verify dependencies: duplicate workspace package name ${pkg.name}`);
196
+ }
197
+ workspaceDirs.set(pkg.name, dir);
198
+ }
199
+ return manifests.flatMap(({ dir, pkg }) => [
200
+ ...Object.keys(pkg.dependencies || {}),
201
+ ...Object.keys(pkg.devDependencies || {}),
202
+ ].map(name => ({ dir, name, workspaceDir: workspaceDirs.get(name) || null })));
203
+ }
204
+
205
+ function dependencyCandidatePresent(candidate, workspaceDir, fsApi) {
206
+ if (!fsApi.existsSync(path.join(candidate, 'package.json'))) return false;
207
+ if (!workspaceDir) return true;
121
208
  try {
122
- const pkg = JSON.parse(fsApi.readFileSync(path.join(worktreePath, 'package.json'), 'utf8'));
123
- return [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.devDependencies || {})];
124
- } catch (_err) { /* intentional: no readable package.json → nothing to verify */ // NOSONAR S2486
125
- return [];
209
+ return path.relative(fsApi.realpathSync(workspaceDir), fsApi.realpathSync(candidate)) === '';
210
+ } catch (_err) { /* intentional: an unresolvable workspace link is not installed */ // NOSONAR S2486
211
+ return false;
126
212
  }
127
213
  }
128
214
 
@@ -138,11 +224,33 @@ function declaredDependencies(worktreePath, fsApi) {
138
224
  *
139
225
  * @param {string} worktreePath - Absolute path to the worktree
140
226
  * @param {object} fsApi - fs module (for DI)
227
+ * @param {string|null} pkgManager - Detected package manager
141
228
  * @returns {boolean} true when nothing is missing
142
229
  */
143
- function dependenciesPresent(worktreePath, fsApi) {
230
+ function dependenciesPresent(worktreePath, fsApi, pkgManager) {
231
+ if (!pkgManager) return true;
144
232
  const deps = declaredDependencies(worktreePath, fsApi);
145
- return deps.every((name) => fsApi.existsSync(path.join(worktreePath, 'node_modules', name, 'package.json')));
233
+ return deps.every(({ dir, name, workspaceDir }) => {
234
+ const localPackage = path.join(dir, 'node_modules', name);
235
+ if (fsApi.existsSync(localPackage)) {
236
+ return dependencyCandidatePresent(localPackage, workspaceDir, fsApi);
237
+ }
238
+
239
+ const rootPackage = path.join(worktreePath, 'node_modules', name);
240
+ if (dir === worktreePath || (pkgManager === 'bun' && !workspaceDir)) return false;
241
+ return dependencyCandidatePresent(rootPackage, workspaceDir, fsApi);
242
+ });
243
+ }
244
+
245
+ function detachModulesLink(modulesPath, fsApi) {
246
+ try {
247
+ if (!fsApi.lstatSync(modulesPath).isSymbolicLink()) return false;
248
+ fsApi.unlinkSync(modulesPath);
249
+ return true;
250
+ } catch (error) {
251
+ if (error && error.code === 'ENOENT') return false;
252
+ throw error;
253
+ }
146
254
  }
147
255
 
148
256
  const STDERR_DETAIL_LIMIT = 400;
@@ -199,21 +307,21 @@ function spawnInstall(pkgManager, args, worktreePath, spawnFn) {
199
307
  * @param {string} projectRoot - Absolute path to the project root
200
308
  * @param {Function} spawnFn - spawnSync-compatible function
201
309
  * @param {object} fsApi - fs module (for DI)
310
+ * @param {string|null} [pkgManager] - Detected package manager
202
311
  * @returns {{ linked: boolean, installed: boolean, healed: boolean }}
203
312
  * @throws {Error} when the install fails, or the tree is still broken after --force.
204
313
  */
205
- function runInstall(worktreePath, projectRoot, spawnFn, fsApi) {
206
- const pkgManager = detectPackageManager(projectRoot, fsApi);
314
+ function runInstall(worktreePath, projectRoot, spawnFn, fsApi, pkgManager = detectPackageManager(projectRoot, fsApi)) {
207
315
  if (!pkgManager) return { linked: false, installed: false, healed: false };
208
316
 
209
317
  spawnInstall(pkgManager, ['install'], worktreePath, spawnFn);
210
318
 
211
- if (PROBE_LESS_MANAGERS.has(pkgManager) || dependenciesPresent(worktreePath, fsApi)) {
319
+ if (PROBE_LESS_MANAGERS.has(pkgManager) || dependenciesPresent(worktreePath, fsApi, pkgManager)) {
212
320
  return { linked: false, installed: true, healed: false };
213
321
  }
214
322
 
215
323
  spawnInstall(pkgManager, ['install', '--force'], worktreePath, spawnFn);
216
- if (!dependenciesPresent(worktreePath, fsApi)) {
324
+ if (!dependenciesPresent(worktreePath, fsApi, pkgManager)) {
217
325
  throw new Error(`Dependency install could not be verified: packages are still missing in ${worktreePath} after '${pkgManager} install --force'. Run it manually there and inspect the output.`);
218
326
  }
219
327
  return { linked: false, installed: true, healed: true };
@@ -237,24 +345,38 @@ function runInstall(worktreePath, projectRoot, spawnFn, fsApi) {
237
345
  function setupWorktreeDeps(worktreePath, projectRoot, { spawnFn, fsApi, platform }) {
238
346
  const srcModules = path.join(projectRoot, 'node_modules');
239
347
  const destModules = path.join(worktreePath, 'node_modules');
348
+ const pkgManager = detectPackageManager(projectRoot, fsApi);
349
+
350
+ // Keep the zero-install fast path only when root and workspace dependencies
351
+ // are complete; Bun's isolated linker keeps workspace packages below each workspace.
352
+ if (fsApi.existsSync(destModules)) {
353
+ if (dependenciesPresent(worktreePath, fsApi, pkgManager)) return { linked: false, installed: false, healed: false };
354
+ detachModulesLink(destModules, fsApi);
355
+ return runInstall(worktreePath, projectRoot, spawnFn, fsApi, pkgManager);
356
+ }
240
357
 
241
- // Already populated (e.g. git checkout carried it) — nothing to do.
242
- if (fsApi.existsSync(destModules)) return { linked: false, installed: false, healed: false };
358
+ // existsSync follows links, so remove a dangling directory entry before
359
+ // either replacing it with the shared install or installing in its place.
360
+ detachModulesLink(destModules, fsApi);
243
361
 
244
362
  // Fast path: link to the shared install when the main repo has one.
245
363
  if (fsApi.existsSync(srcModules)) {
246
364
  const linkType = platform === 'win32' ? 'junction' : 'dir';
247
365
  try {
248
366
  fsApi.symlinkSync(srcModules, destModules, linkType);
249
- return { linked: true, installed: false, healed: false };
250
367
  } catch (error) {
251
368
  // Degrade to a real install only when the link was refused for lack of
252
369
  // privilege/support; any other failure is surfaced to the caller.
253
370
  if (!isLinkPermissionError(error)) throw error;
371
+ return runInstall(worktreePath, projectRoot, spawnFn, fsApi, pkgManager);
372
+ }
373
+ if (dependenciesPresent(worktreePath, fsApi, pkgManager)) {
374
+ return { linked: true, installed: false, healed: false };
254
375
  }
376
+ fsApi.unlinkSync(destModules);
255
377
  }
256
378
 
257
- return runInstall(worktreePath, projectRoot, spawnFn, fsApi);
379
+ return runInstall(worktreePath, projectRoot, spawnFn, fsApi, pkgManager);
258
380
  }
259
381
 
260
382
  function readExistingWorktreeLinkage(driver, worktreePath, reuse) {
@@ -504,12 +626,26 @@ async function handleCreate(slug, flags, projectRoot, opts) {
504
626
  if (linkage.conflict) {
505
627
  return { success: false, reused: true, error: linkage.reason, worktreePath, linkage };
506
628
  }
629
+ let deps;
630
+ try {
631
+ deps = setupWorktreeDeps(worktreePath, projectRoot, {
632
+ spawnFn: runSpawn,
633
+ fsApi,
634
+ platform: opts._platform || process.platform,
635
+ });
636
+ } catch (error) {
637
+ return { success: false, reused: true, error: error.message, worktreePath, linkage };
638
+ }
507
639
  const backing = await autoFileBackingIssue({ projectRoot, worktreePath, branch: existingBranch, issueId, opts });
640
+ const healNote = deps.healed ? ' Healed dependencies (store was stale): reinstalled with --force.' : '';
508
641
  return {
509
642
  success: true,
510
643
  reused: true,
511
- message: `Worktree already exists at ${worktreePath}`,
644
+ message: `Worktree already exists at ${worktreePath}.${healNote}`,
512
645
  worktreePath,
646
+ depsLinked: deps.linked,
647
+ depsInstalled: deps.installed,
648
+ depsHealed: deps.healed,
513
649
  linkage,
514
650
  backing,
515
651
  };
@@ -0,0 +1,9 @@
1
+ # Forge 0.1.0-beta.5 compatibility corpus v1
2
+
3
+ This immutable corpus is derived from release commit
4
+ `ebeb4e5b31fc2dacdf23c5936c02fb2656990f49`. Contract snapshots retain only
5
+ public package, binary, command, alias, and workflow facts. State rows are
6
+ synthetic and contain no user, device, prompt, tool-log, or repository data.
7
+
8
+ `manifest.json` hashes every corpus member. Change requires a new corpus
9
+ version; never rewrite v1 after it becomes release evidence.
@@ -0,0 +1,26 @@
1
+ {
2
+ "commands": ["adapter", "add", "audit", "blocked", "claim", "claims", "clean", "close", "comment", "control", "create", "dev", "doc-gate", "doctor", "explain", "export", "gate", "hooks", "inbox", "init", "insights", "issue", "issues", "lint", "list", "memory", "merge", "migrate", "new", "options", "orient", "orphans", "patch", "plan", "pr", "preflight", "prime", "push", "ready", "recall", "recap", "recommend", "release", "remember", "role", "serve", "setup", "shepherd", "ship", "show", "skill", "stage", "stale", "status", "sync", "team", "test", "update", "upgrade", "validate", "worktree"],
3
+ "aliases": {
4
+ "blocked": { "canonical": "issue blocked", "visible": false },
5
+ "claim": { "canonical": "issue claim", "visible": false },
6
+ "claims": { "canonical": "issue claims", "visible": false },
7
+ "close": { "canonical": "issue close", "visible": false },
8
+ "create": { "canonical": "issue create", "visible": false },
9
+ "doc-gate": { "canonical": "gate doc", "visible": false },
10
+ "insights": { "canonical": "memory insights", "visible": true },
11
+ "issues": { "canonical": "issue list", "visible": false },
12
+ "lint": { "canonical": "issue lint", "visible": false },
13
+ "list": { "canonical": "issue list", "visible": false },
14
+ "merge": { "canonical": "pr merge", "visible": false },
15
+ "orphans": { "canonical": "issue orphans", "visible": false },
16
+ "preflight": { "canonical": "pr preflight", "visible": true },
17
+ "ready": { "canonical": "issue ready", "visible": false },
18
+ "recall": { "canonical": "memory recall", "visible": true },
19
+ "remember": { "canonical": "memory add", "visible": true },
20
+ "shepherd": { "canonical": "pr shepherd", "visible": false },
21
+ "ship": { "canonical": "pr ship", "visible": true },
22
+ "show": { "canonical": "issue show", "visible": false },
23
+ "stale": { "canonical": "issue stale", "visible": false },
24
+ "update": { "canonical": "issue update", "visible": false }
25
+ }
26
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "forge-workflow",
3
+ "version": "0.1.0-beta.5",
4
+ "release_commit": "ebeb4e5b31fc2dacdf23c5936c02fb2656990f49",
5
+ "binaries": {
6
+ "forge": "bin/forge.js",
7
+ "forge-workflow": "bin/forge.js",
8
+ "forge-preflight": "bin/forge-preflight.js"
9
+ },
10
+ "engines": {
11
+ "node": ">=22.16.0"
12
+ }
13
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "critical": ["plan", "dev", "validate", "ship", "review", "verify"],
3
+ "standard": ["plan", "dev", "validate", "ship", "review"],
4
+ "refactor": ["plan", "dev", "validate", "ship"],
5
+ "simple": ["dev", "validate", "ship"],
6
+ "hotfix": ["dev", "validate", "ship"],
7
+ "docs": ["verify", "ship"]
8
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "schema_version": "forge.beta5-corpus.v1",
3
+ "corpus_version": 1,
4
+ "release_version": "0.1.0-beta.5",
5
+ "release_commit": "ebeb4e5b31fc2dacdf23c5936c02fb2656990f49",
6
+ "source_blobs": {
7
+ "package.json": "6f3f112341cd50e0e76fad615beb007b9e6dff2c",
8
+ "lib/commands/_manifest.js": "62595c38b66b139244ca1f54cb034f67d849247d",
9
+ "lib/commands/_aliases.js": "2b16d61d1cdb9ef3e594b5a5093206d05844b460",
10
+ "lib/workflow/stages.js": "0b7ae32bddcbb9bd6dd52a07ca22e43364621918"
11
+ },
12
+ "hash_algorithm": "sha256",
13
+ "content_hash": "c6978eed757c6255f2d25ea6d49ada44e5b3da91830d348016267981d13afd18",
14
+ "files": [
15
+ { "path": "README.md", "sha256": "3cfcc78856e397f2cfd3fee9fa2fffb630e27ea3e76ee8d8b5d9a77cb366a709" },
16
+ { "path": "contract/command-contract.json", "sha256": "8b4f885091fa8afe72b7b5a5a8039869c871f4fb1dc3344728b2c4322f491a84" },
17
+ { "path": "contract/package-contract.json", "sha256": "a65280ede51d62c1913140c83b40e6c3312c991c7a173e7a9ad52d3d5ea0fd47" },
18
+ { "path": "contract/workflow-stage-matrix.json", "sha256": "2aadafdb5f5c1817c8289dd6134d034e3ccc6579438935520bd3c6b90401e5bf" },
19
+ { "path": "state/comments.jsonl", "sha256": "ae4f694f207a11a22b6798943cc6d07487d1dc2be83a78ceadad947a3ff40113" },
20
+ { "path": "state/config.yaml", "sha256": "0b877d9180cf14951657254b50cd771e131970e92582a7c0337f36418a35e1c0" },
21
+ { "path": "state/dependencies.jsonl", "sha256": "cb43cc053d6dd885254a26f6d6b024883f770eb68f50b715cc2f0f05e2cd31e5" },
22
+ { "path": "state/issues.jsonl", "sha256": "425567c5c7b70fd6bbdf9ead3aff78c09a77ebe7b78d3c856c045743b8cb924e" },
23
+ { "path": "state/kernel.sql", "sha256": "4208aabcadc6cec3234ebb5abd4cbf32cc74805433d04a05801e0b6354b53d27" }
24
+ ]
25
+ }
@@ -0,0 +1 @@
1
+ {"id":"synthetic-comment-1","issue_id":"synthetic-issue-1","body":"Synthetic comment","actor":"fixture"}
@@ -0,0 +1,6 @@
1
+ workflow:
2
+ profile: default
3
+ gates:
4
+ gate.issue_verify: true
5
+ rails:
6
+ rail.kernel_tracking: true
@@ -0,0 +1 @@
1
+ {"issue_id":"synthetic-issue-1","blocks_issue_id":"synthetic-issue-2"}
@@ -0,0 +1,2 @@
1
+ {"id":"synthetic-issue-1","title":"Synthetic issue","status":"open","entity_revision":2}
2
+ {"id":"synthetic-issue-2","title":"Synthetic dependency","status":"done","entity_revision":1}
@@ -0,0 +1,20 @@
1
+ PRAGMA user_version = 1;
2
+ CREATE TABLE kernel_migrations (id TEXT PRIMARY KEY, applied_at TEXT NOT NULL);
3
+ CREATE TABLE kernel_issues (id TEXT PRIMARY KEY, title TEXT NOT NULL, status TEXT NOT NULL, entity_revision INTEGER NOT NULL);
4
+ CREATE TABLE kernel_comments (id TEXT PRIMARY KEY, issue_id TEXT NOT NULL, body TEXT NOT NULL, actor TEXT NOT NULL);
5
+ CREATE TABLE kernel_dependencies (id TEXT PRIMARY KEY, issue_id TEXT NOT NULL, blocks_issue_id TEXT NOT NULL);
6
+ CREATE TABLE kernel_claims (id TEXT PRIMARY KEY, issue_id TEXT NOT NULL, actor TEXT NOT NULL, state TEXT NOT NULL);
7
+ CREATE TABLE kernel_worktrees (id TEXT PRIMARY KEY, path TEXT NOT NULL, branch TEXT NOT NULL, state TEXT NOT NULL);
8
+ CREATE TABLE kernel_stage_runs (id TEXT PRIMARY KEY, issue_id TEXT NOT NULL, stage TEXT NOT NULL, status TEXT NOT NULL);
9
+ CREATE TABLE kernel_projections (id TEXT PRIMARY KEY, target TEXT NOT NULL, entity_type TEXT NOT NULL, entity_id TEXT NOT NULL, status TEXT NOT NULL);
10
+ CREATE TABLE kernel_events (id TEXT PRIMARY KEY, entity_type TEXT NOT NULL, entity_id TEXT NOT NULL, event_type TEXT NOT NULL);
11
+ INSERT INTO kernel_migrations VALUES ('001_initial_kernel_schema', '2026-08-09T00:00:00.000Z');
12
+ INSERT INTO kernel_issues VALUES ('synthetic-issue-1', 'Synthetic issue', 'open', 2); -- NOSONAR S1192: repeated synthetic foreign key preserves fixture readability.
13
+ INSERT INTO kernel_issues VALUES ('synthetic-issue-2', 'Synthetic dependency', 'done', 1);
14
+ INSERT INTO kernel_comments VALUES ('synthetic-comment-1', 'synthetic-issue-1', 'Synthetic comment', 'fixture');
15
+ INSERT INTO kernel_dependencies VALUES ('synthetic-dependency-1', 'synthetic-issue-1', 'synthetic-issue-2');
16
+ INSERT INTO kernel_claims VALUES ('synthetic-claim-1', 'synthetic-issue-1', 'fixture', 'active');
17
+ INSERT INTO kernel_worktrees VALUES ('synthetic-worktree-1', '/synthetic/worktree', 'fixture/branch', 'active');
18
+ INSERT INTO kernel_stage_runs VALUES ('synthetic-run-1', 'synthetic-issue-1', 'dev', 'done');
19
+ INSERT INTO kernel_projections VALUES ('synthetic-projection-1', 'jsonl', 'issue', 'synthetic-issue-1', 'delivered');
20
+ INSERT INTO kernel_events VALUES ('synthetic-event-1', 'issue', 'synthetic-issue-1', 'issue.updated');