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

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 (128) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +36 -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 +108 -6
  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 +65 -21
  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/workflow/enforce-stage.js +44 -0
  107. package/lib/workflow/plan-authority.js +225 -0
  108. package/package.json +8 -4
  109. package/scripts/commitlint.js +13 -15
  110. package/scripts/generate-risk-manifest.js +91 -0
  111. package/scripts/github-context-bridge.sh +10 -0
  112. package/scripts/legacy-claim-repair.js +145 -0
  113. package/scripts/lib/behavioral-eval-runtime.js +3 -2
  114. package/scripts/process-tree.js +14 -2
  115. package/scripts/protected-state-check.js +440 -17
  116. package/scripts/sync-agent-skills.js +333 -34
  117. package/scripts/test-full-suite.js +704 -18
  118. package/scripts/test-profile.js +13 -3
  119. package/scripts/test.js +95 -14
  120. package/skills/coverage.json +1 -0
  121. package/skills/review/SKILL.md +2 -0
  122. package/skills/review/evals/scorecard.json +2 -2
  123. package/skills/setup/SKILL.md +18 -0
  124. package/skills/setup/evals/scorecard.json +3 -3
  125. package/skills/shepherd/SKILL.md +19 -2
  126. package/skills/shepherd/evals/scorecard.json +3 -3
  127. package/skills/validate/SKILL.md +3 -0
  128. package/skills/validate/evals/scorecard.json +1 -1
@@ -102,6 +102,9 @@ function classifyAuthError(error) {
102
102
  if (status === 401 || /HTTP 401|bad credentials|token expired/i.test(text)) {
103
103
  return { class: 'expired' };
104
104
  }
105
+ if (status === 429 || /HTTP 429/i.test(text)) {
106
+ return { class: 'rate-limit', retryAfter };
107
+ }
105
108
  if (status === 403 || /HTTP 403/i.test(text)) {
106
109
  if (retryAfter || /rate limit|secondary rate/i.test(text)) {
107
110
  return { class: 'rate-limit', retryAfter };
@@ -116,6 +119,7 @@ class PrStateAdapter {
116
119
  * @param {object} [options]
117
120
  * @param {Function} [options.gh] - Runner for `gh` (cmd, args[]) → string.
118
121
  * @param {Function} [options.git] - Runner for `git` (cmd, args[]) → string.
122
+ * @param {string} [options.repository] - Exact owner/repo for numeric PR reads.
119
123
  */
120
124
  constructor(options = {}) {
121
125
  this.id = options.id || 'pr-state-adapter';
@@ -131,6 +135,7 @@ class PrStateAdapter {
131
135
  });
132
136
  this._gh = options.gh || defaultRunner;
133
137
  this._git = options.git || defaultRunner;
138
+ this._repository = options.repository || null;
134
139
  this.lastProtectionStatus = null;
135
140
  }
136
141
 
@@ -141,7 +146,11 @@ class PrStateAdapter {
141
146
  * @returns {Promise<{ headSha: string, mergeable: string, mergeStateStatus: string, checks: object[], threads: object[] }>}
142
147
  */
143
148
  async readState(pr) {
144
- const raw = this._gh('gh', ['pr', 'view', String(pr), '--json', PR_VIEW_FIELDS]);
149
+ const raw = this._gh('gh', [
150
+ 'pr', 'view', String(pr),
151
+ ...(this._repository ? ['--repo', this._repository] : []),
152
+ '--json', PR_VIEW_FIELDS,
153
+ ]);
145
154
  const data = JSON.parse(raw || '{}');
146
155
  const lifecycleReadable = typeof data.state === 'string'
147
156
  && ['OPEN', 'MERGED', 'CLOSED'].includes(data.state.toUpperCase())
@@ -732,7 +741,11 @@ class PrStateAdapter {
732
741
  * @returns {Promise<number|null>} epoch ms of the head commit, or null.
733
742
  */
734
743
  async readHeadCommitTime({ pr }) {
735
- const raw = this._gh('gh', ['pr', 'view', String(pr), '--json', 'commits', '-q', '.commits[-1].committedDate']);
744
+ const raw = this._gh('gh', [
745
+ 'pr', 'view', String(pr),
746
+ ...(this._repository ? ['--repo', this._repository] : []),
747
+ '--json', 'commits', '-q', '.commits[-1].committedDate',
748
+ ]);
736
749
  const t = Date.parse(String(raw || '').trim());
737
750
  return Number.isFinite(t) ? t : null;
738
751
  }
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ const { execFileSync } = require('node:child_process');
4
+
5
+ const BASE_REMOTE_CANDIDATES = ['upstream', 'origin'];
6
+ const DEFAULT_BRANCH_CANDIDATES = ['main', 'master'];
7
+ const GIT_PROBE_TIMEOUT_MS = 120000;
8
+
9
+ /**
10
+ * Quiet git probe options. `stdio: 'pipe'` keeps failed probes from leaking to
11
+ * the terminal — these are speculative queries, not user-facing commands.
12
+ *
13
+ * @param {string} cwd - Working directory for the probe.
14
+ * @returns {object} execFileSync options.
15
+ */
16
+ function getQuietProbeOptions(cwd) {
17
+ return { encoding: 'utf8', cwd, timeout: GIT_PROBE_TIMEOUT_MS, stdio: 'pipe' };
18
+ }
19
+
20
+ /**
21
+ * Resolve `refs/remotes/<remote>/HEAD` to the ref it points at, verifying the
22
+ * target actually exists.
23
+ *
24
+ * @param {Function} [exec] - Injected execFileSync.
25
+ * @param {string} [cwd] - Repository directory.
26
+ * @param {string} [remoteName] - Remote to probe.
27
+ * @returns {string|null} Fully-qualified ref, or null.
28
+ */
29
+ function resolveRemoteHeadTarget(exec = execFileSync, cwd = process.cwd(), remoteName = undefined) {
30
+ if (!remoteName) {
31
+ return null;
32
+ }
33
+ try {
34
+ const symbolicRef = exec('git', ['symbolic-ref', `refs/remotes/${remoteName}/HEAD`], getQuietProbeOptions(cwd)).trim();
35
+ if (!symbolicRef) {
36
+ return null;
37
+ }
38
+ exec('git', ['rev-parse', '--verify', symbolicRef], getQuietProbeOptions(cwd));
39
+ return symbolicRef;
40
+ } catch (_error) { // NOSONAR S2486 - intentional: missing ref is the expected probe failure
41
+ void _error;
42
+ return null;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * True when the remote has fetched tracking refs we can treat as a base:
48
+ * a recorded HEAD, or one of the conventional default branches.
49
+ *
50
+ * @param {Function} [exec] - Injected execFileSync.
51
+ * @param {string} [cwd] - Repository directory.
52
+ * @param {string} [remoteName] - Remote to probe.
53
+ * @returns {boolean} Whether a usable tracking base exists.
54
+ */
55
+ function remoteHasTrackingBase(exec = execFileSync, cwd = process.cwd(), remoteName = undefined) {
56
+ if (!remoteName) {
57
+ return false;
58
+ }
59
+ if (resolveRemoteHeadTarget(exec, cwd, remoteName)) {
60
+ return true;
61
+ }
62
+
63
+ for (const candidate of DEFAULT_BRANCH_CANDIDATES) {
64
+ try {
65
+ exec('git', ['rev-parse', '--verify', `refs/remotes/${remoteName}/${candidate}`], getQuietProbeOptions(cwd));
66
+ return true;
67
+ } catch (_error) { // NOSONAR S2486 - intentional: speculative probe, try next candidate
68
+ void _error;
69
+ // Probe next candidate.
70
+ }
71
+ }
72
+
73
+ return false;
74
+ }
75
+
76
+ /**
77
+ * Resolve the remote that owns the integration base. `upstream` wins over
78
+ * `origin` so a fork-style checkout resolves the official repository rather
79
+ * than the contributor's fork.
80
+ *
81
+ * @param {Function} [exec] - Injected execFileSync.
82
+ * @param {string} [cwd] - Repository directory.
83
+ * @returns {string} Remote name ('origin' when nothing else qualifies).
84
+ */
85
+ function resolveBaseRemote(exec = execFileSync, cwd = process.cwd()) {
86
+ for (const candidate of BASE_REMOTE_CANDIDATES) {
87
+ try {
88
+ exec('git', ['remote', 'get-url', candidate], getQuietProbeOptions(cwd));
89
+ if (remoteHasTrackingBase(exec, cwd, candidate)) {
90
+ return candidate;
91
+ }
92
+ } catch (_error) { // NOSONAR S2486 - intentional: speculative probe, try next candidate
93
+ void _error;
94
+ // Probe next candidate.
95
+ }
96
+ }
97
+
98
+ return 'origin';
99
+ }
100
+
101
+ /**
102
+ * Resolve the base remote's default branch name.
103
+ *
104
+ * @param {Function} [exec] - Injected execFileSync.
105
+ * @param {string} [cwd] - Repository directory.
106
+ * @param {string} [remoteName] - Remote to probe; defaults to the base remote.
107
+ * @returns {string} Branch name ('master' when nothing else resolves).
108
+ */
109
+ function resolveBaseBranch(exec = execFileSync, cwd = process.cwd(), remoteName = resolveBaseRemote(exec, cwd)) {
110
+ const symbolicRef = resolveRemoteHeadTarget(exec, cwd, remoteName);
111
+ if (symbolicRef) {
112
+ const match = new RegExp(`^refs/remotes/${remoteName}/(.+)$`).exec(symbolicRef);
113
+ if (match?.[1]) {
114
+ return match[1];
115
+ }
116
+ }
117
+
118
+ for (const candidate of DEFAULT_BRANCH_CANDIDATES) {
119
+ try {
120
+ exec('git', ['rev-parse', '--verify', `refs/remotes/${remoteName}/${candidate}`], getQuietProbeOptions(cwd));
121
+ return candidate;
122
+ } catch (_error) { // NOSONAR S2486 - intentional: speculative probe, try next candidate
123
+ void _error;
124
+ // Probe next candidate.
125
+ }
126
+ }
127
+
128
+ return 'master';
129
+ }
130
+
131
+ module.exports = {
132
+ BASE_REMOTE_CANDIDATES,
133
+ DEFAULT_BRANCH_CANDIDATES,
134
+ resolveRemoteHeadTarget,
135
+ remoteHasTrackingBase,
136
+ resolveBaseRemote,
137
+ resolveBaseBranch,
138
+ };