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
@@ -14,6 +14,7 @@ const path = require('node:path');
14
14
 
15
15
  const { fireAndForget } = require('../pr-monitor/reconcile-executor');
16
16
  const { getResolvedRuntimeGraph } = require('../core/runtime-graph');
17
+ const { resolveBaseRemote, resolveBaseBranch } = require('../base-remote');
17
18
 
18
19
  const AUTO_SHEPHERD_RAIL = 'rail.auto_shepherd';
19
20
 
@@ -65,72 +66,6 @@ function getQuietGitExecOptions(cwd = process.cwd()) {
65
66
  return { ...getGitExecOptions(cwd), stdio: 'pipe' };
66
67
  }
67
68
 
68
- function resolveRemoteHeadTarget(exec = execFileSync, cwd = process.cwd(), remoteName) {
69
- try {
70
- const symbolicRef = exec('git', ['symbolic-ref', `refs/remotes/${remoteName}/HEAD`], getQuietGitExecOptions(cwd)).trim();
71
- if (!symbolicRef) {
72
- return null;
73
- }
74
- exec('git', ['rev-parse', '--verify', symbolicRef], getQuietGitExecOptions(cwd));
75
- return symbolicRef;
76
- } catch (_error) {
77
- return null;
78
- }
79
- }
80
-
81
- function remoteHasTrackingBase(exec = execFileSync, cwd = process.cwd(), remoteName) {
82
- if (resolveRemoteHeadTarget(exec, cwd, remoteName)) {
83
- return true;
84
- }
85
-
86
- for (const candidate of ['main', 'master']) {
87
- try {
88
- exec('git', ['rev-parse', '--verify', `refs/remotes/${remoteName}/${candidate}`], getQuietGitExecOptions(cwd));
89
- return true;
90
- } catch (_error) {
91
- // Probe next candidate.
92
- }
93
- }
94
-
95
- return false;
96
- }
97
-
98
- function resolveBaseRemote(exec = execFileSync, cwd = process.cwd()) {
99
- for (const candidate of ['upstream', 'origin']) {
100
- try {
101
- exec('git', ['remote', 'get-url', candidate], getQuietGitExecOptions(cwd));
102
- if (remoteHasTrackingBase(exec, cwd, candidate)) {
103
- return candidate;
104
- }
105
- } catch (_error) {
106
- // Probe next candidate.
107
- }
108
- }
109
-
110
- return 'origin';
111
- }
112
-
113
- function resolveBaseBranch(exec = execFileSync, cwd = process.cwd(), remoteName = resolveBaseRemote(exec, cwd)) {
114
- const symbolicRef = resolveRemoteHeadTarget(exec, cwd, remoteName);
115
- if (symbolicRef) {
116
- const match = new RegExp(`^refs/remotes/${remoteName}/(.+)$`).exec(symbolicRef);
117
- if (match && match[1]) {
118
- return match[1];
119
- }
120
- }
121
-
122
- for (const candidate of ['main', 'master']) {
123
- try {
124
- exec('git', ['rev-parse', '--verify', `refs/remotes/${remoteName}/${candidate}`], getQuietGitExecOptions(cwd));
125
- return candidate;
126
- } catch (_error) {
127
- // Probe next candidate.
128
- }
129
- }
130
-
131
- return 'master';
132
- }
133
-
134
69
  function refreshBaseReference(exec = execFileSync, cwd = process.cwd(), remoteName, baseBranch) {
135
70
  exec(
136
71
  'git',
@@ -457,10 +392,13 @@ function validatePRTitle(title) {
457
392
 
458
393
  async function createPR(options) { // NOSONAR S3776
459
394
  const { title, body, dryRun = false, exec = execFileSync, cwd = process.cwd() } = options;
395
+ const gh = options.githubContext?.bound
396
+ ? (args, runOptions) => options.githubContext.runGh(args, runOptions)
397
+ : (args, runOptions) => exec('gh', args, runOptions);
460
398
  const titleValidation = validatePRTitle(title);
461
399
  if (!titleValidation.valid) return { success: false, error: titleValidation.error };
462
400
  try {
463
- exec('gh', ['--version'], { ...getGhCheckOptions(), cwd }); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
401
+ gh(['--version'], { ...getGhCheckOptions(), cwd });
464
402
  } catch (error) {
465
403
  if (isCommandNotFound(error)) {
466
404
  return { success: false, error: 'GitHub CLI (gh) not found. Install from: https://cli.github.com/' };
@@ -495,7 +433,7 @@ async function createPR(options) { // NOSONAR S3776
495
433
  return { success: true, message: '[DRY RUN] Would create PR with title: ' + title, prUrl: 'https://github.com/owner/repo/pull/1' };
496
434
  }
497
435
  try {
498
- const result = exec('gh', ['pr', 'create', '--title', title, '--body', body], getGitExecOptions(cwd)); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
436
+ const result = gh(['pr', 'create', '--title', title, '--body', body], getGitExecOptions(cwd));
499
437
  const urlMatch = /https:\/\/github\.com\/[^\s]+/.exec(result);
500
438
  const prUrl = urlMatch ? urlMatch[0] : null;
501
439
  const numberMatch = /\/pull\/(\d+)/.exec(result);
@@ -585,7 +523,10 @@ async function executeShip(options) {
585
523
  testScenarios,
586
524
  coverage,
587
525
  });
588
- const result = await createPr({ title, body: prBody, dryRun });
526
+ const result = await createPr({
527
+ title, body: prBody, dryRun,
528
+ exec: options.exec, cwd: options.cwd || projectRoot, githubContext: options.githubContext,
529
+ });
589
530
  if (!result.success) return result;
590
531
  maybeTriggerShepherdAfterShip({ dryRun, projectRoot, fireAndForget: trigger });
591
532
  return {
@@ -601,6 +542,7 @@ async function executeShip(options) {
601
542
 
602
543
  module.exports = {
603
544
  name: 'ship',
545
+ githubAuth: true,
604
546
  description: 'Create a pull request from validated feature work',
605
547
  handler: async (args, flags = {}, projectRoot = process.cwd(), deps = {}) => {
606
548
  const result = await executeShip({
@@ -609,6 +551,10 @@ module.exports = {
609
551
  dryRun: Boolean(flags.dryRun || flags['--dry-run']),
610
552
  projectRoot,
611
553
  fireAndForget: deps.fireAndForget,
554
+ exec: deps.exec,
555
+ cwd: deps.cwd || projectRoot,
556
+ githubContext: deps.githubContext,
557
+ createPr: deps.createPr,
612
558
  });
613
559
  if (!result.success) {
614
560
  return result;
@@ -23,6 +23,7 @@ const { runBehavioralEvaluation } = require('../../scripts/lib/behavioral-eval-r
23
23
  const { resolveBehavioralEvaluation } = require('../../scripts/lib/behavioral-eval-runtime');
24
24
  const { loadPromotionEvidence } = require('../../scripts/lib/promotion-evidence-loader');
25
25
  const { scorePromotion } = require('../../scripts/lib/promotion-scorecard');
26
+ const { createGithubContext } = require('../github-context');
26
27
 
27
28
  const BEHAVIORAL_TIERS = Object.freeze([30, 100, 300]);
28
29
  const BEHAVIORAL_TIER_VALUES = new Set(BEHAVIORAL_TIERS.map(String));
@@ -158,12 +159,19 @@ async function handleFullEval(rest, flags, projectRoot, opts) {
158
159
  let behavioralOptions = opts.behavioralOptions || {};
159
160
  if (!opts.runBehavioralEvaluation || opts.resolveBehavioralEvaluation) {
160
161
  const resolver = opts.resolveBehavioralEvaluation || resolveBehavioralEvaluation;
162
+ let githubContext;
163
+ try {
164
+ githubContext = (opts.prepareGithubContext || createGithubContext)(projectRoot);
165
+ } catch {
166
+ return { success: false, error: 'Unable to prepare GitHub account context. Run forge github status for guidance.' };
167
+ }
161
168
  const resolved = await resolver({
162
169
  projectRoot,
163
170
  skillName: name,
164
171
  skillPath: path.join(ctx.skillsDir, name, 'SKILL.md'),
165
172
  tier,
166
173
  env: opts.env || process.env,
174
+ ...(githubContext.bound ? { deps: { runGh: githubContext.runGh } } : {}),
167
175
  });
168
176
  if (!resolved.ok) return formatResult(scoreResult(resolved.result));
169
177
  behavioralOptions = { ...resolved.options, ...behavioralOptions };
@@ -1,7 +1,25 @@
1
1
  const { existsSync } = require('node:fs');
2
2
  const { execFileSync } = require('node:child_process');
3
3
  const path = require('node:path');
4
- const { getPackageRoot } = require('../package-root');
4
+ const { getPackageRoot, isCompiledBinary } = require('../package-root');
5
+ const { firstPositionalIndex, stripGlobalFlags } = require('../global-flags');
6
+
7
+ function teamArguments(args) {
8
+ const subcommandIndex = firstPositionalIndex(args);
9
+ if (subcommandIndex === -1) return stripGlobalFlags(args);
10
+ const result = stripGlobalFlags(args.slice(0, subcommandIndex));
11
+ // Path is routing metadata anywhere; other flags after the subcommand belong to Team.
12
+ for (let index = subcommandIndex; index < args.length; index++) {
13
+ const arg = args[index];
14
+ if (arg.startsWith('--path=')) continue;
15
+ if (arg === '-p' || arg === '--path') {
16
+ if (args[index + 1] !== undefined && !args[index + 1].startsWith('-')) index++;
17
+ } else {
18
+ result.push(arg);
19
+ }
20
+ }
21
+ return result;
22
+ }
5
23
 
6
24
  // Resolve bash binary from fixed, known locations only (no PATH search).
7
25
  // SonarCloud S4036: PATH must not be searched for OS commands.
@@ -22,28 +40,49 @@ function _resolveBash() {
22
40
  return '/usr/bin/bash';
23
41
  }
24
42
 
25
- function handleTeam(args) {
43
+ function handleTeam(args, projectRoot = process.cwd(), deps = {}) {
26
44
  // Route through the asset root so `forge team` finds the script in BOTH channels:
27
45
  // the on-disk package (npm/npx) and a compiled binary (extracted embed dir).
28
- const scriptPath = path.join(getPackageRoot(), 'scripts', 'forge-team', 'index.sh');
46
+ const packageRoot = getPackageRoot();
47
+ const scriptPath = path.join(packageRoot, 'scripts', 'forge-team', 'index.sh');
29
48
  const bashPath = _resolveBash();
49
+ const runFile = deps.execFileSync || execFileSync;
50
+ const options = { stdio: 'inherit' };
51
+ if (deps.githubContext?.bound) {
52
+ // Bash also runs Git/Forge/helpers. Only the re-entering gh bridge may resolve credentials.
53
+ const compiled = (deps.isCompiledBinary || isCompiledBinary)();
54
+ const env = { ...(deps.env || process.env) };
55
+ for (const key of Object.keys(env)) {
56
+ if (['gh_token', 'github_token', 'gh_host'].includes(key.toLowerCase())) delete env[key];
57
+ }
58
+ options.cwd = projectRoot;
59
+ options.env = {
60
+ ...env,
61
+ GH_CMD: path.join(packageRoot, 'scripts', 'github-context-bridge.sh').replaceAll('\\', '/'),
62
+ FORGE_GITHUB_RUNTIME: process.execPath.replaceAll('\\', '/'),
63
+ FORGE_GITHUB_ENTRY: compiled ? '' : path.join(packageRoot, 'bin', 'forge.js').replaceAll('\\', '/'),
64
+ };
65
+ }
30
66
 
31
67
  try {
32
- execFileSync(bashPath, [scriptPath, ...args], {
33
- stdio: 'inherit',
34
- });
68
+ runFile(bashPath, [scriptPath, ...teamArguments(args)], options);
35
69
  } catch (err) {
36
70
  process.exit(err.status || 1);
37
71
  }
38
72
  }
39
73
 
40
- async function handler(args = []) {
41
- handleTeam(args);
74
+ async function handler(args, _flags, projectRoot, deps = {}) {
75
+ handleTeam(args || [], projectRoot, deps);
42
76
  return { success: true };
43
77
  }
44
78
 
45
79
  module.exports = {
46
80
  name: 'team',
81
+ githubAuth: (args = []) => {
82
+ const teamArgs = teamArguments(args);
83
+ return ['add', 'verify', 'sync', 'claim'].includes(teamArgs[0])
84
+ || (teamArgs[0] === 'workload' && teamArgs.slice(1).some(arg => arg.replace(/\r$/, '') === '--me'));
85
+ },
47
86
  description: 'Team coordination workflows and sync utilities',
48
87
  handler,
49
88
  handleTeam
@@ -32,6 +32,9 @@ const DIRECT_TEST_CANDIDATES = Object.freeze({
32
32
  'test/cli-flags.test.js',
33
33
  'test/forge-cli-registry.test.js',
34
34
  'test/setup-runtime-flags.test.js',
35
+ 'test/github-launcher.test.js',
36
+ 'test/integration/github-account-context.test.js',
37
+ 'test/structural/github-account-public-surface.test.js',
35
38
  ],
36
39
  // The second CLI surface (command descriptions + shepherd wiring). Without an
37
40
  // entry here it resolves to zero tests and pushes fall back to the full suite.
@@ -47,15 +50,143 @@ const DIRECT_TEST_CANDIDATES = Object.freeze({
47
50
  'test/doc-assertions.test.js',
48
51
  'test/scripts/doc-asserting-tests.test.js',
49
52
  ],
53
+ // The suites that assert the repo .gitignore's actual content. The path is
54
+ // already known to the classifier, so without candidates here it resolves
55
+ // zero tests and a one-line ignore addition pays the full-suite lane.
56
+ '.gitignore': [
57
+ 'test/check-forge-token.test.js',
58
+ 'test/cleanup/dropped-agent-config.test.js',
59
+ 'test/coverage-config.test.js',
60
+ 'test/eval/eval-history.test.js',
61
+ 'test/freshness-token.test.js',
62
+ 'test/mutation-config.test.js',
63
+ 'test/test-dashboard.test.js',
64
+ ],
50
65
  'lib/lefthook-check.js': ['test/lefthook-check.test.js', 'test/runtime-health.test.js'],
51
66
  'lib/runtime-health.js': ['test/runtime-health.test.js'],
52
67
  // skill-eval hosts the accuracy-lint detectors (auditCommandDocumentation /
53
68
  // auditRouterPrecision), so edits there must also run their detector suite.
54
69
  'lib/skill-eval.js': ['test/skill-eval.test.js', 'test/skill-accuracy.test.js'],
70
+ // Schema is shared by every kernel authority; keep it on the full-suite lane.
71
+ 'lib/kernel/schema.js': [],
72
+ // Migrations have consumers across the kernel; an empty mapping deliberately
73
+ // selects the full-suite lane, matching the shared SQLite driver.
74
+ 'lib/kernel/migrations.js': [],
75
+ 'lib/commands/shepherd.js': [
76
+ 'test/commands/shepherd.test.js',
77
+ 'test/commands/github-route-matrix.test.js',
78
+ 'test/pr-monitor/arm-on-push.test.js',
79
+ 'test/pr-monitor/shepherd-watch.test.js',
80
+ 'test/pr-monitor/watch-owner-cutover.test.js',
81
+ 'test/pr-monitor/watch-owner-launch-contention.test.js',
82
+ 'test/pr-monitor/watch-owner-launch-gate.test.js',
83
+ ],
84
+ 'lib/commands/github.js': [
85
+ 'test/commands/github.test.js',
86
+ 'test/github-launcher.test.js',
87
+ 'test/integration/github-account-context.test.js',
88
+ ],
89
+ 'lib/github-context.js': ['test/github-context.test.js', 'test/integration/github-account-context.test.js'],
90
+ 'lib/commands/_registry.js': [
91
+ 'test/commands/_registry.test.js',
92
+ 'test/commands/_registry-github-context.test.js',
93
+ ],
94
+ 'lib/commands/pr.js': ['test/commands/pr.test.js', 'test/commands/github-route-matrix.test.js'],
95
+ 'lib/commands/ship.js': ['test/commands/ship.test.js', 'test/commands/github-route-matrix.test.js'],
96
+ 'lib/commands/merge.js': ['test/commands/merge.test.js', 'test/commands/github-route-matrix.test.js'],
97
+ 'lib/commands/team.js': ['test/commands/team.test.js', 'test/commands/github-route-matrix.test.js'],
98
+ 'lib/commands/clean.js': ['test/commands/clean.test.js', 'test/commands/github-route-matrix.test.js'],
99
+ 'scripts/github-context-bridge.sh': ['test/commands/github-route-matrix.test.js'],
100
+ 'lib/commands/push.js': ['test/commands/push.test.js', 'test/commands/github-indirect-routes.test.js'],
101
+ 'lib/commands/hooks.js': ['test/commands/hooks.test.js', 'test/hooks-session-start.test.js', 'test/commands/github-indirect-routes.test.js'],
102
+ 'lib/commands/serve.js': ['test/commands/serve.test.js', 'test/commands/github-indirect-routes.test.js'],
103
+ 'lib/commands/skill.js': ['test/commands/skill.test.js', 'test/commands/github-indirect-routes.test.js'],
104
+ 'scripts/lib/behavioral-eval-runtime.js': ['test/eval/behavioral-eval-runtime.test.js', 'test/commands/github-indirect-routes.test.js'],
105
+ 'web/dashboard/generate-snapshot.mjs': ['test/commands/github-indirect-routes.test.js'],
106
+ // The shared SQLite driver has more consumers than a bounded direct list can
107
+ // safely represent; its empty mapping deliberately selects the full-suite lane.
108
+ 'lib/kernel/sqlite-driver.js': [],
109
+ 'lib/pr-monitor/watch-owner.js': [
110
+ 'test/pr-monitor/watch-owner.test.js',
111
+ 'test/kernel/watch-owner-transaction.test.js',
112
+ ],
113
+ 'lib/pr-monitor/reconcile.js': [
114
+ 'test/pr-monitor/reconcile.test.js',
115
+ 'test/pr-monitor/reconcile-owner-authority.test.js',
116
+ ],
117
+ 'lib/pr-monitor/reconcile-executor.js': [
118
+ 'test/commands/github-indirect-routes.test.js',
119
+ 'test/pr-monitor/auto-trigger-containment.test.js',
120
+ 'test/pr-monitor/reconcile-executor-owner-authority.test.js',
121
+ 'test/pr-monitor/reconcile-executor.test.js',
122
+ 'test/pr-monitor/reconcile-daemon-owner-authority.test.js',
123
+ 'test/pr-monitor/reconcile-migration-gate.test.js',
124
+ 'test/pr-monitor/watch-owner-launch-contention.test.js',
125
+ 'test/windows-hide-background-spawns.test.js',
126
+ ],
127
+ 'lib/pr-monitor/watch.js': [
128
+ 'test/pr-monitor/watch-owner-cutover.test.js',
129
+ 'test/pr-monitor/watch.test.js',
130
+ ],
131
+ 'lib/pr-monitor/watch-lifecycle.js': [
132
+ 'test/commands/github-indirect-routes.test.js',
133
+ 'test/pr-monitor/watch-lifecycle.test.js',
134
+ 'test/pr-monitor/watch-owner-launch-contention.test.js',
135
+ 'test/pr-monitor/watch-owner-launch-gate.test.js',
136
+ ],
137
+ 'lib/pr-monitor/shepherd-lease.js': [
138
+ 'test/pr-monitor/shepherd-lease.test.js',
139
+ 'test/pr-monitor/shepherd-lease-election-only.test.js',
140
+ ],
141
+ 'lib/pr-monitor/monitor.js': [
142
+ 'test/pr-monitor/monitor.test.js',
143
+ 'test/pr-monitor/monitor-owner-signal.test.js',
144
+ ],
55
145
  'lib/upgrade-safety.js': ['test/commands/upgrade.test.js'],
56
146
  'scripts/test.js': ['test/scripts/test-runner.test.js'],
147
+ // `bun.lock` is deliberately absent: a dependency change can break anything,
148
+ // so it stays on the full-suite lane.
149
+ // lefthook.yml is the repository's hook wiring, so this must also cover the
150
+ // suites that assert the individual pre-commit/pre-push commands exist.
151
+ 'lefthook.yml': [
152
+ 'test/lefthook-check.test.js',
153
+ 'test/lefthook-wiring.test.js',
154
+ 'test/commands/lefthook-user-config.test.js',
155
+ 'test/branch-protection.test.js',
156
+ 'test/commitlint.test.js',
157
+ 'test/cross-platform-install.test.js',
158
+ 'test/sync-d20-audit-script.test.js',
159
+ ],
160
+ 'eslint.config.js': ['test/lint-script.test.js'],
161
+ '.coderabbit.yaml': ['test/coderabbit-config.test.js'],
162
+ '.claude-plugin/marketplace.json': [
163
+ 'test/activation/plugin-scaffold.test.js',
164
+ 'test/plugin-catalog.test.js',
165
+ ],
57
166
  });
58
167
 
168
+ // Shared authority modules must run the full suite even when another changed file
169
+ // resolves a targeted test. Keep this signal separate from test candidates: an
170
+ // empty candidate list is not enough once a mapped consumer is also changed.
171
+ const FULL_SUITE_REQUIRED_FILES = new Set([
172
+ 'lib/kernel/schema.js',
173
+ 'lib/kernel/migrations.js',
174
+ 'lib/kernel/sqlite-driver.js',
175
+ ]);
176
+
177
+ // Directory-level mappings for source trees that own a dedicated suite. Without
178
+ // these a change under any of them resolves to zero tests and forces the full
179
+ // suite (see hasZeroResolvedTests in scripts/test.js).
180
+ const PREFIX_TEST_TARGETS = Object.freeze([
181
+ ['validation/', ['test/validation/risk-manifest.test.js', 'test/validation/risk-manifest-generator.test.js']],
182
+ // `eval/corpus/` must come first: the corpus hash and manifest checks live in
183
+ // immutable-corpus, and eval-sets only reads `eval/commands/`.
184
+ ['eval/corpus/', ['test/eval/immutable-corpus.test.js']],
185
+ ['eval/', ['test/eval/eval-sets.test.js']],
186
+ ['rules/', ['test/rules-sync.test.js', 'test/skill-dispatch-parity.test.js']],
187
+ ['plugin/', ['test/activation/plugin-scaffold.test.js', 'test/plugin-catalog.test.js', 'test/release-readiness.test.js']],
188
+ ]);
189
+
59
190
  // Skill sources (canonical `skills/**` and the committed `.agents/skills/**` mirror)
60
191
  // map to the fast skill suite. This is what keeps a skills-only PR on the targeted
61
192
  // lane instead of the full ~1500-test suite. `skills-sync-drift` guards the mirror.
@@ -176,7 +307,9 @@ function getChangedFiles(execFileSync, options = {}) {
176
307
  * @returns {string[]} Array of test file paths (e.g. ['test/foo.test.js'])
177
308
  */
178
309
  function getAffectedTestFiles(projectRoot, execFileSync, fs = defaultFs, options = {}) {
179
- const changedFiles = getChangedFiles(execFileSync, options);
310
+ const changedFiles = Array.isArray(options.changedFiles)
311
+ ? options.changedFiles
312
+ : getChangedFiles(execFileSync, options);
180
313
  const testFiles = new Set();
181
314
  for (const file of changedFiles) {
182
315
  for (const candidate of getTestCandidatesForChangedFile(file)) {
@@ -215,10 +348,24 @@ function getTestCandidatesForChangedFile(file) {
215
348
  return [...SKILL_TEST_TARGETS];
216
349
  }
217
350
 
351
+ // Maintainer-only contributor skills. They document the AGENTS.md product /
352
+ // maintainer split, so the docs-bleed gate is the suite that can catch a
353
+ // contradiction between them.
354
+ if (file.startsWith('.forge/contributor-skills/')) {
355
+ return ['test/structural/agents-docs-bleed.test.js'];
356
+ }
357
+
358
+ for (const [prefix, targets] of PREFIX_TEST_TARGETS) {
359
+ if (file.startsWith(prefix)) {
360
+ return [...targets];
361
+ }
362
+ }
363
+
218
364
  if (file === 'README.md'
219
365
  || file === 'QUICKSTART.md'
220
366
  || file === 'CHANGELOG.md'
221
367
  || file === 'AGENTS.md'
368
+ || file === 'CODING_STANDARDS.md'
222
369
  || file === 'DEVELOPMENT.md'
223
370
  || file === 'docs/INDEX.md'
224
371
  || file === 'docs/PROJECT_DESIGN.md'
@@ -226,7 +373,9 @@ function getTestCandidatesForChangedFile(file) {
226
373
  || file.startsWith('docs/guides/')
227
374
  || file.startsWith('docs/reference/')
228
375
  || file.startsWith('docs/work/')) {
229
- return ['test/docs-consistency.test.js'];
376
+ return file === 'AGENTS.md'
377
+ ? ['test/docs-consistency.test.js', 'test/structural/agents-docs-bleed.test.js']
378
+ : ['test/docs-consistency.test.js'];
230
379
  }
231
380
 
232
381
  if (file.startsWith('lib/') && file.endsWith('.js')) {
@@ -270,10 +419,15 @@ function getTestCandidatesForChangedFile(file) {
270
419
  return [];
271
420
  }
272
421
 
422
+ function isFullSuiteRequiredFile(file) {
423
+ return FULL_SUITE_REQUIRED_FILES.has(file);
424
+ }
425
+
273
426
  module.exports = {
274
427
  getChangedFiles,
275
428
  getAffectedTestFiles,
276
429
  getTestCandidatesForChangedFile,
430
+ isFullSuiteRequiredFile,
277
431
  name: 'test',
278
432
  description: 'Run tests with smart defaults (timeout, affected-only)',
279
433
  usage: 'forge test [--affected]',
@@ -318,10 +472,15 @@ module.exports = {
318
472
 
319
473
  // 4. --affected flag: find changed test files
320
474
  if (flags['--affected'] || flags.affected) {
321
- const affectedTests = getAffectedTestFiles(projectRoot, execFileSync, fs, {
475
+ const diffOptions = {
322
476
  sinceUpstream: flags.sinceUpstream || flags['--since-upstream'],
477
+ };
478
+ const changedFiles = getChangedFiles(execFileSync, diffOptions);
479
+ const affectedTests = getAffectedTestFiles(projectRoot, execFileSync, fs, {
480
+ ...diffOptions,
481
+ changedFiles,
323
482
  });
324
- if (affectedTests.length > 0) {
483
+ if (!changedFiles.some(isFullSuiteRequiredFile) && affectedTests.length > 0) {
325
484
  testArgs = ['run', 'test', ...affectedTests];
326
485
  }
327
486
  // If no affected tests found, fall back to running all tests