forge-workflow 0.1.0-beta.4 → 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 (196) hide show
  1. package/AGENTS.md +18 -7
  2. package/CHANGELOG.md +79 -1
  3. package/CLAUDE.md +0 -12
  4. package/CODING_STANDARDS.md +72 -0
  5. package/README.md +6 -2
  6. package/bin/forge-cmd.js +20 -0
  7. package/bin/forge.js +28 -375
  8. package/docs/INDEX.md +1 -1
  9. package/docs/guides/BEADS_GITHUB_SYNC.md +2 -31
  10. package/docs/guides/MIGRATION.md +4 -4
  11. package/docs/guides/SETUP.md +16 -16
  12. package/docs/reference/COMMANDS.md +8 -5
  13. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +4 -0
  14. package/docs/reference/INSIGHTS_RECAP.md +9 -20
  15. package/docs/reference/INSTALL.md +4 -0
  16. package/docs/reference/LEGACY_CLAIM_REPAIR.md +112 -0
  17. package/docs/reference/RELEASE.md +5 -3
  18. package/docs/reference/TOOLCHAIN.md +8 -0
  19. package/docs/reference/github-accounts.md +134 -0
  20. package/docs/reference/protected-state-surfaces.md +4 -4
  21. package/docs/reference/shepherd.md +114 -35
  22. package/lefthook.yml +12 -0
  23. package/lib/activation/ensure-forge-home.js +33 -15
  24. package/lib/adapters/pr-state-adapter.js +359 -144
  25. package/lib/audit-evidence.js +71 -110
  26. package/lib/base-remote.js +138 -0
  27. package/lib/beta5-compatibility-evidence.js +1093 -0
  28. package/lib/bun-lockfile-proof.js +413 -0
  29. package/lib/bun-workflow-pins.js +461 -0
  30. package/lib/capabilities/index.js +9 -0
  31. package/lib/capabilities/model.js +141 -0
  32. package/lib/capabilities/probes.js +347 -0
  33. package/lib/capped-jsonl-log.js +236 -0
  34. package/lib/codex-skills.js +2 -2
  35. package/lib/commands/_manifest.js +1 -0
  36. package/lib/commands/_registry.js +50 -20
  37. package/lib/commands/clean.js +252 -32
  38. package/lib/commands/dev.js +4 -33
  39. package/lib/commands/doctor.js +37 -6
  40. package/lib/commands/gate.js +197 -27
  41. package/lib/commands/github.js +215 -0
  42. package/lib/commands/hooks.js +276 -30
  43. package/lib/commands/insights.js +8 -3
  44. package/lib/commands/memory.js +66 -2
  45. package/lib/commands/merge.js +1265 -58
  46. package/lib/commands/plan.js +33 -2
  47. package/lib/commands/pr.js +3 -1
  48. package/lib/commands/preflight.js +21 -4
  49. package/lib/commands/prime.js +21 -8
  50. package/lib/commands/push.js +146 -54
  51. package/lib/commands/recall.js +127 -49
  52. package/lib/commands/recap.js +6 -1
  53. package/lib/commands/release.js +39 -3
  54. package/lib/commands/remember.js +28 -4
  55. package/lib/commands/serve.js +26 -9
  56. package/lib/commands/setup.js +323 -98
  57. package/lib/commands/shepherd.js +591 -73
  58. package/lib/commands/ship.js +36 -91
  59. package/lib/commands/skill.js +127 -11
  60. package/lib/commands/status.js +17 -1
  61. package/lib/commands/team.js +47 -8
  62. package/lib/commands/test.js +187 -38
  63. package/lib/commands/validate.js +65 -21
  64. package/lib/commands/worktree.js +359 -45
  65. package/lib/core/runtime-graph.js +1 -1
  66. package/lib/doc-assertions.js +297 -0
  67. package/lib/existing-tdd-gate.js +253 -0
  68. package/lib/fixtures/beta5-corpus/v1/README.md +9 -0
  69. package/lib/fixtures/beta5-corpus/v1/contract/command-contract.json +26 -0
  70. package/lib/fixtures/beta5-corpus/v1/contract/package-contract.json +13 -0
  71. package/lib/fixtures/beta5-corpus/v1/contract/workflow-stage-matrix.json +8 -0
  72. package/lib/fixtures/beta5-corpus/v1/manifest.json +25 -0
  73. package/lib/fixtures/beta5-corpus/v1/state/comments.jsonl +1 -0
  74. package/lib/fixtures/beta5-corpus/v1/state/config.yaml +6 -0
  75. package/lib/fixtures/beta5-corpus/v1/state/dependencies.jsonl +1 -0
  76. package/lib/fixtures/beta5-corpus/v1/state/issues.jsonl +2 -0
  77. package/lib/fixtures/beta5-corpus/v1/state/kernel.sql +20 -0
  78. package/lib/forge-context.js +1 -4
  79. package/lib/forge-issues.js +134 -32
  80. package/lib/gate-events.js +98 -10
  81. package/lib/git-defaults.js +56 -0
  82. package/lib/github-context.js +308 -0
  83. package/lib/global-flags.js +1 -0
  84. package/lib/harness-capability-matrix.js +3 -3
  85. package/lib/hook-renderer.js +122 -5
  86. package/lib/insights.js +96 -80
  87. package/lib/issue-render.js +19 -0
  88. package/lib/kernel/backing-issue.js +14 -2
  89. package/lib/kernel/broker.js +739 -31
  90. package/lib/kernel/claim-reconciler.js +238 -0
  91. package/lib/kernel/cli-broker-factory.js +12 -1
  92. package/lib/kernel/close-on-merge.js +154 -0
  93. package/lib/kernel/fs-class.js +42 -25
  94. package/lib/kernel/lease-enforcer.js +9 -4
  95. package/lib/kernel/legacy-claim-repair.js +442 -0
  96. package/lib/kernel/live-claim-projection.js +26 -0
  97. package/lib/kernel/migrations.js +118 -3
  98. package/lib/kernel/readiness-model.js +184 -12
  99. package/lib/kernel/schema.js +49 -1
  100. package/lib/kernel/sqlite-driver.js +3435 -172
  101. package/lib/kernel/taxonomy-validator.js +4 -1
  102. package/lib/kernel/windows-private-acl.js +239 -0
  103. package/lib/lefthook-wiring.js +21 -1
  104. package/lib/memory/hygiene.js +191 -0
  105. package/lib/memory/router.js +110 -28
  106. package/lib/memory/usage-evidence.js +4 -0
  107. package/lib/memory-digest.js +106 -15
  108. package/lib/memory-recall-events.js +145 -0
  109. package/lib/memory-recall.js +71 -10
  110. package/lib/merge-rules.js +143 -21
  111. package/lib/npm-publish-workflow.js +465 -0
  112. package/lib/orientation.js +68 -43
  113. package/lib/package-root.js +2 -0
  114. package/lib/plugin-catalog.js +14 -4
  115. package/lib/pr-bundle.js +5 -6
  116. package/lib/pr-monitor/auto-actions.js +169 -28
  117. package/lib/pr-monitor/differ.js +110 -4
  118. package/lib/pr-monitor/events.js +0 -0
  119. package/lib/pr-monitor/flow-monitor.js +1424 -0
  120. package/lib/pr-monitor/gather.js +251 -44
  121. package/lib/pr-monitor/journal.js +18 -39
  122. package/lib/pr-monitor/monitor.js +117 -10
  123. package/lib/pr-monitor/process-identity.js +117 -0
  124. package/lib/pr-monitor/reconcile-executor.js +1129 -470
  125. package/lib/pr-monitor/reconcile.js +0 -0
  126. package/lib/pr-monitor/render-summary.js +293 -0
  127. package/lib/pr-monitor/review-preflight.js +269 -0
  128. package/lib/pr-monitor/shepherd-lease.js +38 -20
  129. package/lib/pr-monitor/verdict.js +438 -0
  130. package/lib/pr-monitor/watch-lifecycle.js +145 -27
  131. package/lib/pr-monitor/watch-owner.js +1414 -0
  132. package/lib/pr-monitor/watch.js +129 -58
  133. package/lib/pr-pull.js +33 -14
  134. package/lib/pr-shepherd.js +51 -11
  135. package/lib/preflight/gates.js +65 -18
  136. package/lib/preflight/runner.js +5 -0
  137. package/lib/project-memory.js +178 -4
  138. package/lib/protected-state-authority.js +1100 -0
  139. package/lib/protected-state-surfaces.js +243 -45
  140. package/lib/release-readiness.js +53 -7
  141. package/lib/review-adapter.js +65 -0
  142. package/lib/shell-utils.js +1 -1
  143. package/lib/skills-sync.js +71 -35
  144. package/lib/smart-merge.js +28 -4
  145. package/lib/symlink-utils.js +74 -26
  146. package/lib/upgrade-safety.js +39 -0
  147. package/lib/using-forge.js +19 -6
  148. package/lib/validation/risk-manifest.js +339 -0
  149. package/lib/workflow/enforce-stage.js +44 -0
  150. package/lib/workflow/plan-authority.js +225 -0
  151. package/package.json +12 -9
  152. package/scripts/commitlint.js +13 -15
  153. package/scripts/doc-asserting-tests.js +158 -0
  154. package/scripts/generate-risk-manifest.js +91 -0
  155. package/scripts/github-context-bridge.sh +10 -0
  156. package/scripts/legacy-claim-repair.js +145 -0
  157. package/scripts/lib/behavioral-eval-runner.js +310 -0
  158. package/scripts/lib/behavioral-eval-runtime.js +457 -0
  159. package/scripts/lib/eval-evidence.js +328 -0
  160. package/scripts/lib/eval-runner.js +81 -41
  161. package/scripts/lib/immutable-eval-corpus.js +309 -0
  162. package/scripts/lib/promotion-evidence-loader.js +94 -0
  163. package/scripts/lib/promotion-scorecard.js +314 -0
  164. package/scripts/npm-release-receipt.js +134 -0
  165. package/scripts/process-tree.js +773 -0
  166. package/scripts/protected-state-check.js +479 -31
  167. package/scripts/run-command-eval.js +29 -1
  168. package/scripts/sync-agent-skills.js +333 -34
  169. package/scripts/sync-d20-audit.js +172 -0
  170. package/scripts/test-full-suite.js +935 -37
  171. package/scripts/test-profile.js +13 -3
  172. package/scripts/test.js +271 -57
  173. package/skills/coverage.json +1 -0
  174. package/skills/review/SKILL.md +6 -11
  175. package/skills/review/evals/scorecard.json +4 -4
  176. package/skills/rollback/SKILL.md +4 -11
  177. package/skills/rollback/evals/scorecard.json +3 -3
  178. package/skills/setup/SKILL.md +18 -0
  179. package/skills/setup/evals/scorecard.json +3 -3
  180. package/skills/shepherd/SKILL.md +39 -16
  181. package/skills/shepherd/evals/scorecard.json +4 -4
  182. package/skills/ship/SKILL.md +4 -12
  183. package/skills/ship/evals/scorecard.json +3 -3
  184. package/skills/validate/SKILL.md +3 -0
  185. package/skills/validate/evals/scorecard.json +1 -1
  186. package/skills/worktree/SKILL.md +6 -1
  187. package/skills/worktree/evals/scorecard.json +2 -2
  188. package/lib/beads-setup.js +0 -538
  189. package/lib/beads-sync-scaffold.js +0 -189
  190. package/lib/pat-setup.js +0 -207
  191. package/lib/pr-monitor/render-sticky.js +0 -206
  192. package/lib/pr-monitor/upsert-sticky.js +0 -169
  193. package/scripts/beads-context.sh +0 -577
  194. package/scripts/beads-migrate-to-dolt.sh +0 -7
  195. package/scripts/beads-upgrade-smoke.sh +0 -284
  196. package/scripts/lib/beads-migrate-to-dolt.mjs +0 -503
@@ -13,7 +13,7 @@ const fs = require('node:fs');
13
13
  const os = require('node:os');
14
14
  const path = require('node:path');
15
15
  const readline = require('node:readline');
16
- const { execSync } = require('node:child_process');
16
+ const { execFileSync, execSync } = require('node:child_process');
17
17
 
18
18
  // Compute packageDir relative to this file (lib/commands/setup.js -> project root).
19
19
  // packageDir stays a runtime value for asset copying; requires use static
@@ -28,6 +28,7 @@ const VERSION = packageJson.version;
28
28
  // packaged-ASSET read below goes through `getPackageRoot(packageDir)`; the
29
29
  // module-`require` paths above keep `packageDir` (bundler handles those).
30
30
  const { getPackageRoot } = require('../package-root');
31
+ const { cleanupDeprecatedSyncFiles } = require('../deprecated-sync-cleanup');
31
32
 
32
33
  // Load PluginManager for discoverable agent architecture
33
34
  const PluginManager = require('../plugin-manager');
@@ -53,9 +54,8 @@ const { askYesNo: _askYesNoBase } = require('../ui-utils');
53
54
  const contextMerge = require('../context-merge');
54
55
  const projectDiscovery = require('../project-discovery');
55
56
 
56
- // Load lib modules for symlink, beads, and PAT setup
57
+ // Load lib modules for symlink setup
57
58
  const { createSymlinkOrCopy: libCreateSymlinkOrCopy } = require('../symlink-utils');
58
- const { scaffoldBeadsSync } = require('../beads-sync-scaffold');
59
59
  const { resolveSyncBackend } = require('../sync-backend');
60
60
  const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
61
61
 
@@ -69,11 +69,13 @@ const { smartMergeAgentsMd } = require('../smart-merge');
69
69
  const { checkLefthookStatus } = require('../lefthook-check');
70
70
  const {
71
71
  FORGE_USER_LEFTHOOK_YML,
72
+ FORGE_USER_LEFTHOOK_YML_NO_TDD,
72
73
  forgeShouldWriteLefthookConfig,
73
74
  installNativeGitHooks,
74
75
  verifyHooksActive,
75
76
  resolveGitHooksDir,
76
77
  } = require('../lefthook-wiring');
78
+ const { detectExistingTddGate, describeExistingGateDeferral } = require('../existing-tdd-gate');
77
79
  const { resolveShellRuntime } = require('../runtime-health');
78
80
  const {
79
81
  buildCodexSkillInstallPlan,
@@ -89,6 +91,10 @@ const initCommand = require('./init');
89
91
  const fileUtils = require('../file-utils');
90
92
  const detectionUtils = require('../detection-utils');
91
93
  const { detectHusky, migrateHusky } = require('../husky-migration');
94
+ const {
95
+ issueGeneratedHarnessClaudeAuthorization,
96
+ completeGeneratedHarnessClaudeAuthorization,
97
+ } = require('../protected-state-authority');
92
98
 
93
99
  // --- Module-level state (deferred from ForgeContext migration) ---
94
100
  // Follow-up tracked in forge-vi7v: migrate setup state to a ForgeContext instance passed via handler args.
@@ -99,7 +105,6 @@ let FORCE_MODE = false;
99
105
  let VERBOSE_MODE = false;
100
106
  let NON_INTERACTIVE = false;
101
107
  let SYMLINK_ONLY = false;
102
- let SYNC_ENABLED = false;
103
108
  let actionLog = new SetupActionLog();
104
109
  let PKG_MANAGER = 'npm';
105
110
  let SETUP_NOTES = [];
@@ -162,6 +167,92 @@ function safeExec(cmd, opts = {}) {
162
167
  }
163
168
  }
164
169
 
170
+ /**
171
+ * Resolve the current source revision without passing Git arguments through a shell.
172
+ * @param {string} cwd - Repository working directory
173
+ * @param {Function} commandRunner - execFileSync-compatible runner for tests
174
+ * @returns {string} Full source HEAD, or empty when Git cannot resolve it
175
+ */
176
+ function resolveCurrentSourceHead(cwd = projectRoot, commandRunner = execFileSync) {
177
+ try {
178
+ return commandRunner('git', ['rev-parse', '--verify', 'HEAD^{commit}'], {
179
+ cwd,
180
+ stdio: 'pipe',
181
+ }).toString().trim();
182
+ } catch (_e) { // NOSONAR — fail closed when the source revision is unavailable
183
+ return '';
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Normalize Git's Windows checkout line endings before authorizing the exact
189
+ * root CLAUDE.md pointer bytes.
190
+ * @param {Buffer|string} content - Existing CLAUDE.md bytes
191
+ * @returns {Buffer} LF-normalized content
192
+ */
193
+ function normalizeClaudePointerContent(content) {
194
+ return Buffer.from(String(content || '').replace(/\r\n/g, '\n'));
195
+ }
196
+
197
+ function readCleanClaudePointerWorktreeContent(cwd, commandRunner) {
198
+ const tempIndexDir = fs.mkdtempSync(path.join(os.tmpdir(), 'forge-claude-index-'));
199
+ const tempIndex = path.join(tempIndexDir, 'index');
200
+ const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
201
+ try {
202
+ commandRunner('git', ['read-tree', 'HEAD'], {
203
+ cwd,
204
+ env,
205
+ stdio: 'pipe',
206
+ });
207
+ commandRunner('git', ['add', '--', 'CLAUDE.md'], {
208
+ cwd,
209
+ env,
210
+ stdio: 'pipe',
211
+ });
212
+ return commandRunner('git', ['show', ':CLAUDE.md'], {
213
+ cwd,
214
+ env,
215
+ encoding: null,
216
+ stdio: 'pipe',
217
+ });
218
+ } finally {
219
+ fs.rmSync(tempIndexDir, { recursive: true, force: true });
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Read the exact staged CLAUDE.md bytes that the protected-state hook will hash.
225
+ * @param {string} cwd - Repository working directory
226
+ * @param {Function} commandRunner - execFileSync-compatible runner for tests
227
+ * @returns {Buffer} Staged bytes, or the worktree bytes when the file is untracked
228
+ */
229
+ function readStagedClaudePointerContent(cwd = projectRoot, commandRunner = execFileSync) {
230
+ try {
231
+ const stagedContent = commandRunner('git', ['show', ':CLAUDE.md'], {
232
+ cwd,
233
+ encoding: null,
234
+ stdio: 'pipe',
235
+ });
236
+ try {
237
+ return readCleanClaudePointerWorktreeContent(cwd, commandRunner);
238
+ } catch (_worktreeError) {
239
+ return stagedContent;
240
+ }
241
+ } catch (showError) {
242
+ try {
243
+ commandRunner('git', ['ls-files', '--error-unmatch', '--', 'CLAUDE.md'], {
244
+ cwd,
245
+ encoding: 'utf8',
246
+ stdio: 'pipe',
247
+ });
248
+ throw showError;
249
+ } catch (trackedError) {
250
+ if (trackedError?.status !== 1) throw showError;
251
+ }
252
+ return readCleanClaudePointerWorktreeContent(cwd, commandRunner);
253
+ }
254
+ }
255
+
165
256
  /**
166
257
  * Detect package manager
167
258
  */
@@ -174,7 +265,6 @@ function detectPackageManager() {
174
265
 
175
266
 
176
267
  const WORKFLOW_RUNTIME_ASSETS = Object.freeze([
177
- 'scripts/beads-context.sh',
178
268
  'scripts/conflict-detect.sh',
179
269
  'scripts/dep-guard-analyze.js',
180
270
  'scripts/dep-guard.sh',
@@ -218,7 +308,6 @@ function validateAgents(agentList) {
218
308
  // Prerequisite check function
219
309
  function checkPrerequisites(options = {}) {
220
310
  const requireGithubCli = options.requireGithubCli !== false;
221
- const requireBeadsCli = options.requireBeadsCli === true;
222
311
  const requireJq = options.requireJq === true;
223
312
  const commandRunner = options.commandRunner || safeExec;
224
313
  const errors = [];
@@ -255,15 +344,13 @@ function checkPrerequisites(options = {}) {
255
344
  }
256
345
  }
257
346
 
258
- if (requireBeadsCli) {
259
- // Issue tracking runs on the local Forge Kernel store, which auto-provisions
260
- // on first use — there is no external CLI to install. Surface only whether
261
- // team sync is wired up yet.
262
- if (resolveSyncBackend({ projectRoot, env: options.env }) === 'local-noop') {
263
- console.log(' ✓ Kernel issue store (local-noop sync: single-machine until a sync server is configured)');
264
- } else {
265
- console.log(' ✓ Kernel issue store');
266
- }
347
+ // Issue tracking runs on the local Forge Kernel store, which auto-provisions
348
+ // on first use — there is no external CLI to install. Surface only whether
349
+ // team sync is wired up yet.
350
+ if (resolveSyncBackend({ projectRoot, env: options.env }) === 'local-noop') {
351
+ console.log(' ✓ Kernel issue store (local-noop sync: single-machine until a sync server is configured)');
352
+ } else {
353
+ console.log(' ✓ Kernel issue store');
267
354
  }
268
355
 
269
356
  // Check Node.js version
@@ -800,9 +887,7 @@ async function detectProjectStatus() {
800
887
  claudeMdLines: 0,
801
888
  // Project tools status — the Kernel issue store is always present (it
802
889
  // auto-provisions on first use), so issue tracking needs no install probe.
803
- hasBeads: true,
804
890
  hasSkills: isSkillsInitialized(),
805
- beadsInstallType: 'kernel',
806
891
  skillsInstallType: checkForSkills(),
807
892
  // Enhanced: Auto-detected project context
808
893
  autoDetected: null
@@ -1654,6 +1739,13 @@ function ensureDirWithNote(dir, purpose) { // eslint-disable-line no-unused-vars
1654
1739
 
1655
1740
  // Setup core documentation and directories
1656
1741
  function setupCoreDocs() {
1742
+ // Review skills reference this root contract in every installed repository.
1743
+ // It is a consumer-owned extension point after creation, so setup never overwrites it.
1744
+ const codingStandardsDest = path.join(projectRoot, 'CODING_STANDARDS.md');
1745
+ if (!fs.existsSync(codingStandardsDest)) {
1746
+ copyFile(path.join(getPackageRoot(packageDir), 'CODING_STANDARDS.md'), 'CODING_STANDARDS.md');
1747
+ }
1748
+
1657
1749
  // docs/planning/ and docs/research/ are created lazily on first use
1658
1750
  // by /plan Phase 1 and Phase 2 respectively, via ensureDirWithNote().
1659
1751
  // TEMPLATE.md and PROGRESS.md are also deferred to first use.
@@ -2058,12 +2150,40 @@ function setupCursorHooksConfig() {
2058
2150
 
2059
2151
  // Helper: Create agent link file
2060
2152
  // When symlinkOnly is true (--symlink flag), skip copy fallback
2061
- function createAgentLinkFile(agent, symlinkOnly = false) {
2153
+ async function createAgentLinkFile(agent, symlinkOnly = false) {
2062
2154
  if (!shouldLinkAgentsMd(agent)) return;
2063
2155
 
2064
2156
  const result = createSymlinkOrCopy('AGENTS.md', agent.linkFile, { symlinkOnly });
2065
2157
  if (result) {
2066
- console.log(` ${result === 'linked' ? 'Linked' : 'Copied'}: ${agent.linkFile}`);
2158
+ const action = result === 'linked' ? 'Linked' : result === 'copied' ? 'Copied' : 'Preserved import';
2159
+ console.log(` ${action}: ${agent.linkFile}`);
2160
+ }
2161
+ if (agent.linkFile !== 'CLAUDE.md' || result !== 'existing-import') return;
2162
+
2163
+ try {
2164
+ const sourceHead = resolveCurrentSourceHead();
2165
+ const content = readStagedClaudePointerContent();
2166
+ const actor = process.env.FORGE_PROTECTED_STATE_ACTOR || process.env.FORGE_ACTOR || process.env.USER || process.env.USERNAME || 'unknown';
2167
+ const authorization = await issueGeneratedHarnessClaudeAuthorization(projectRoot, {
2168
+ actor,
2169
+ content,
2170
+ sourceHead,
2171
+ });
2172
+ if (!authorization.success) {
2173
+ console.warn(` Warning: Could not authorize existing CLAUDE.md import: ${authorization.error}`);
2174
+ return;
2175
+ }
2176
+ const completion = await completeGeneratedHarnessClaudeAuthorization(projectRoot, {
2177
+ actor,
2178
+ capabilityId: authorization.capabilityId,
2179
+ content,
2180
+ sourceHead,
2181
+ });
2182
+ if (!completion.success) {
2183
+ console.warn(` Warning: Could not complete CLAUDE.md import authorization: ${completion.error}`);
2184
+ }
2185
+ } catch (error) {
2186
+ console.warn(` Warning: Could not record CLAUDE.md import authorization: ${error.message}`);
2067
2187
  }
2068
2188
  }
2069
2189
 
@@ -2071,7 +2191,7 @@ function createAgentLinkFile(agent, symlinkOnly = false) {
2071
2191
 
2072
2192
 
2073
2193
  // Setup specific agent
2074
- async function setupAgent(agentKey, skipFiles = {}) {
2194
+ async function setupAgent(agentKey, skipFiles = {}, options = {}) {
2075
2195
  const agent = AGENTS[agentKey];
2076
2196
  if (!agent) return;
2077
2197
 
@@ -2111,15 +2231,17 @@ async function setupAgent(agentKey, skipFiles = {}) {
2111
2231
  // Projects Forge's TDD-gate + protected-path enforcement onto each harness's
2112
2232
  // native hook surface. Codex hooks are GLOBAL-config scope and intentionally
2113
2233
  // not written at project setup (see lib/hook-renderer.js).
2114
- if (agentKey === 'claude') {
2115
- setupClaudeHooksConfig();
2116
- }
2117
- if (agent.customSetup === 'cursor') {
2118
- setupCursorHooksConfig();
2234
+ if (!options.skillsOnly) {
2235
+ if (agentKey === 'claude') {
2236
+ setupClaudeHooksConfig();
2237
+ }
2238
+ if (agent.customSetup === 'cursor') {
2239
+ setupCursorHooksConfig();
2240
+ }
2119
2241
  }
2120
2242
 
2121
2243
  // Create link file (SYMLINK_ONLY = --symlink flag disables copy fallback)
2122
- createAgentLinkFile(agent, SYMLINK_ONLY);
2244
+ await createAgentLinkFile(agent, SYMLINK_ONLY);
2123
2245
  }
2124
2246
 
2125
2247
 
@@ -2756,6 +2878,48 @@ function resolveHookEnforcementState(root = projectRoot) {
2756
2878
  }
2757
2879
  }
2758
2880
 
2881
+ // True when .forge/config.yaml already states a rail.tdd_intent choice EXPLICITLY (either
2882
+ // config shape the resolver accepts). Setup must never overwrite that: otherwise the
2883
+ // `forge gate enable rail.tdd_intent` we advertise would be undone by the next `forge setup`.
2884
+ function hasExplicitTddRailChoice(root) {
2885
+ try {
2886
+ const { loadRawConfig } = require('../config-writer');
2887
+ const config = loadRawConfig(root) || {};
2888
+ const viaGates = config.workflow && config.workflow.gates && config.workflow.gates['rail.tdd_intent'];
2889
+ const viaRails = config.rails && config.rails['rail.tdd_intent'];
2890
+ return Boolean(
2891
+ (viaGates && viaGates.enabled !== undefined) || (viaRails && viaRails.enabled !== undefined)
2892
+ );
2893
+ } catch {
2894
+ return false; // unreadable config → treat as unset; setConfigOverride reports any real failure
2895
+ }
2896
+ }
2897
+
2898
+ /**
2899
+ * Defer Forge's TDD gate to a pre-existing one: turn `rail.tdd_intent` off in
2900
+ * .forge/config.yaml so the installed hook scripts are genuinely inert, instead of stacking a
2901
+ * second gate on the same commit (kernel 5b425a85 / 2699b234). No-op when nothing was detected
2902
+ * (a repo with no gate keeps the default-ON rail) or when the user already chose explicitly.
2903
+ *
2904
+ * @returns {{ deferred: boolean, reason?: string }}
2905
+ */
2906
+ function applyExistingGateDeferral(root, detection) {
2907
+ if (!detection || !detection.found) return { deferred: false, reason: 'no-existing-gate' };
2908
+ if (hasExplicitTddRailChoice(root)) return { deferred: false, reason: 'explicit-user-choice' };
2909
+
2910
+ const { setConfigOverride } = require('../config-writer');
2911
+ try {
2912
+ setConfigOverride(root, ['workflow', 'gates', 'rail.tdd_intent', 'enabled'], false);
2913
+ } catch (error) {
2914
+ // An unreadable/malformed .forge/config.yaml must not make `forge setup` fail outright
2915
+ // just because a pre-existing gate was detected. Report and DON'T defer: the caller keys
2916
+ // hook wiring off `deferred`, so falling back to the normal install keeps config and
2917
+ // wiring agreeing, instead of turning the gate off in neither place or in only one.
2918
+ return { deferred: false, reason: 'config-write-failed', error: error.message };
2919
+ }
2920
+ return { deferred: true };
2921
+ }
2922
+
2759
2923
  function describeHookEnforcement(root = projectRoot) {
2760
2924
  const state = resolveHookEnforcementState(root);
2761
2925
  if (!state.resolved) {
@@ -2837,6 +3001,32 @@ function installGitHooks(options = {}) { // NOSONAR — Extracted as-is from bin
2837
3001
  // init deliberately degrades to a warning rather than failing, and repair runs inside
2838
3002
  // another command's flow.
2839
3003
  const loud = options.loud === true;
3004
+
3005
+ // BEFORE wiring anything: does this repo already enforce TDD/source-test coupling on
3006
+ // pre-commit? If so, defer to it rather than silently stacking a second gate on the same
3007
+ // commit — the in-the-wild beta.3 report (kernel 5b425a85 / 2699b234). The deferral is
3008
+ // VISIBLE (report below) and reversible with one command.
3009
+ const existingGate = detectExistingTddGate(projectRoot);
3010
+ const deferral = applyExistingGateDeferral(projectRoot, existingGate);
3011
+ // Only claim a deferral when one actually happened. describeExistingGateDeferral() states
3012
+ // that Forge did NOT install its gate and that rail.tdd_intent is off — both false when we
3013
+ // declined to defer, so printing it unconditionally would misreport the installed state.
3014
+ if (deferral.deferred) {
3015
+ console.warn(describeExistingGateDeferral(existingGate));
3016
+ addSetupNote(`Deferred Forge's TDD gate to the existing pre-commit gate in ${existingGate.source}.`);
3017
+ } else if (existingGate.found) {
3018
+ const why = deferral.reason === 'explicit-user-choice'
3019
+ ? 'rail.tdd_intent is set explicitly in .forge/config.yaml, so your choice stands'
3020
+ : `Forge could not update .forge/config.yaml (${deferral.error})`;
3021
+ console.warn(
3022
+ ` ⚠ Detected an existing pre-commit TDD/coupling gate in ${existingGate.source}, but did NOT defer to it:\n` +
3023
+ ` ${why}.\n` +
3024
+ ' Forge\'s own TDD gate is installed as usual, so BOTH gates run on the same commit.\n' +
3025
+ ' Want only yours? Run: forge gate disable rail.tdd_intent'
3026
+ );
3027
+ addSetupNote(`Existing pre-commit gate in ${existingGate.source} detected; Forge's TDD gate installed anyway (${deferral.reason}).`);
3028
+ }
3029
+
2840
3030
  // Honest header: when the TDD gate is disabled in config the hooks install but are inert,
2841
3031
  // so don't announce "(TDD enforcement)" for a state the user turned off (issue eda6d866).
2842
3032
  console.log(
@@ -2876,7 +3066,18 @@ function installGitHooks(options = {}) { // NOSONAR — Extracted as-is from bin
2876
3066
  // clobber a config that already has active jobs (kernel c713fce7).
2877
3067
  const lefthookTarget = path.join(projectRoot, 'lefthook.yml');
2878
3068
  if (forgeShouldWriteLefthookConfig(lefthookTarget)) {
2879
- fs.writeFileSync(lefthookTarget, FORGE_USER_LEFTHOOK_YML, 'utf8');
3069
+ // When we actually DEFERRED, write the variant WITHOUT Forge's forge-tdd job so the
3070
+ // commit is never blocked by two gates. Key this off `deferral.deferred`, not off
3071
+ // `existingGate.found`: applyExistingGateDeferral declines to defer when the user has
3072
+ // an explicit rail.tdd_intent choice, and suppressing the wiring anyway would leave
3073
+ // config saying "enabled" while the hook was never installed — making
3074
+ // `forge gate enable rail.tdd_intent` silently ineffective in exactly the repos this
3075
+ // feature targets.
3076
+ fs.writeFileSync(
3077
+ lefthookTarget,
3078
+ deferral.deferred ? FORGE_USER_LEFTHOOK_YML_NO_TDD : FORGE_USER_LEFTHOOK_YML,
3079
+ 'utf8'
3080
+ );
2880
3081
  actionLog.add('lefthook.yml', 'created');
2881
3082
  console.log(' ✓ Created lefthook.yml');
2882
3083
  }
@@ -2924,7 +3125,12 @@ function installGitHooks(options = {}) { // NOSONAR — Extracted as-is from bin
2924
3125
  // inert (B3). This repair path only warns (never sets a failure exit code) since it can
2925
3126
  // run mid-stage under enforce-stage's repairWorkflowRuntimeAssets().
2926
3127
  if (!lefthookInstalled) {
2927
- const native = installNativeGitHooks(projectRoot);
3128
+ // Skip the native pre-commit hook when the repo already has its own TDD/coupling gate —
3129
+ // pre-push is still wired, so only the double-gated surface is given up.
3130
+ const native = installNativeGitHooks(
3131
+ projectRoot,
3132
+ existingGate.found ? { skipHooks: ['pre-commit'] } : {}
3133
+ );
2928
3134
  if (native.installed) {
2929
3135
  console.log(` ✓ Native git hooks installed (${native.written.join(', ')}) - lefthook fallback`);
2930
3136
  } else if (native.skipped && native.skipped.length > 0) {
@@ -3093,7 +3299,7 @@ function initializeSkills(installType) {
3093
3299
 
3094
3300
  // Ensure the issue store during interactive setup. No prompt: the Forge Kernel
3095
3301
  // ships with Forge and auto-provisions, so there is nothing to install or pick.
3096
- async function promptBeadsSetup(_question) {
3302
+ async function promptIssueStoreSetup(_question) {
3097
3303
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
3098
3304
  console.log('Issue Store (Forge Kernel)');
3099
3305
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
@@ -3219,17 +3425,14 @@ async function promptSkillsSetup(question) {
3219
3425
  console.log('');
3220
3426
  }
3221
3427
 
3222
- // Interactive setup for Beads and Skills
3223
-
3224
-
3225
- // Interactive setup for Beads and Skills
3428
+ // Interactive setup for the issue store and Skills
3226
3429
  async function setupProjectTools(rl, question) {
3227
3430
  console.log('');
3228
3431
  console.log('═══════════════════════════════════════════════════════════');
3229
3432
  console.log(' STEP 2: Project Tools (Recommended)');
3230
3433
  console.log('═══════════════════════════════════════════════════════════');
3231
3434
  console.log('');
3232
- console.log('Forge recommends three tools for enhanced workflows:');
3435
+ console.log('Forge recommends these tools for enhanced workflows:');
3233
3436
  console.log('');
3234
3437
  console.log('• Issue tracking (Forge Kernel) - zero-install, git-backed');
3235
3438
  console.log(' Persists tasks across sessions, tracks dependencies.');
@@ -3241,7 +3444,7 @@ async function setupProjectTools(rl, question) {
3241
3444
  console.log('');
3242
3445
 
3243
3446
  // Use helper functions to reduce complexity
3244
- await promptBeadsSetup(question);
3447
+ await promptIssueStoreSetup(question);
3245
3448
  await promptSkillsSetup(question);
3246
3449
  }
3247
3450
 
@@ -3395,7 +3598,8 @@ async function ensureGitHooksInstalled(targetRoot = projectRoot) {
3395
3598
 
3396
3599
 
3397
3600
  // Quick setup with defaults
3398
- async function quickSetup(selectedAgents, skipExternal) {
3601
+ async function quickSetup(selectedAgents, skipExternal, options = {}) {
3602
+ const skillsOnly = options.skillsOnly === true;
3399
3603
  showBanner('Quick Setup');
3400
3604
  console.log('');
3401
3605
  console.log('Quick mode: Using defaults...');
@@ -3403,7 +3607,6 @@ async function quickSetup(selectedAgents, skipExternal) {
3403
3607
 
3404
3608
  // Check prerequisites
3405
3609
  checkPrerequisites({
3406
- requireBeadsCli: true,
3407
3610
  requireGithubCli: true,
3408
3611
  requireJq: true,
3409
3612
  });
@@ -3423,41 +3626,46 @@ async function quickSetup(selectedAgents, skipExternal) {
3423
3626
 
3424
3627
  ensureWorkflowShellPolicy(selectedAgents);
3425
3628
 
3426
- // Auto-install lefthook if missing
3427
- autoInstallLefthook();
3629
+ // Auto-install lefthook if missing unless the caller explicitly requested skills only.
3630
+ if (!skillsOnly) {
3631
+ autoInstallLefthook();
3632
+ }
3428
3633
 
3429
3634
  // Auto-setup project tools (Kernel issue store, Skills)
3430
3635
  await autoSetupToolsInQuickMode();
3431
3636
 
3432
3637
  // Setup Claude first if selected, then setup remaining agents
3433
3638
  if (selectedAgents.includes('claude')) {
3434
- await setupAgent('claude');
3639
+ await setupAgent('claude', {}, { skillsOnly });
3435
3640
  }
3436
- await setupSelectedAgents(selectedAgents);
3641
+ await setupSelectedAgents(selectedAgents, undefined, { skillsOnly });
3437
3642
  ensureWorkflowRuntimeAssets(selectedAgents);
3438
3643
 
3439
3644
  // Detect Husky and migrate before installing Lefthook hooks
3440
- await handleHuskyMigration();
3645
+ if (!skillsOnly) {
3646
+ await handleHuskyMigration();
3647
+ }
3441
3648
 
3442
3649
  // Install git hooks for TDD enforcement. LOUD: this is the `forge setup` handler,
3443
3650
  // so an inert-hooks result must fail non-zero (B3), not end green.
3444
3651
  console.log('');
3445
- installGitHooks({ loud: true });
3652
+ if (skillsOnly) {
3653
+ console.log('Skipping git and harness hooks (--skills-only).');
3654
+ } else {
3655
+ installGitHooks({ loud: true });
3656
+ }
3446
3657
 
3447
3658
  // Configure external services with defaults (unless skipped)
3448
3659
  configureDefaultExternalServices(skipExternal);
3449
3660
 
3450
- // --sync flag: scaffold Beads GitHub sync workflows without prompting
3451
- if (SYNC_ENABLED) {
3452
- await handleSyncScaffold();
3453
- }
3661
+ removeDeprecatedSyncFiles();
3454
3662
 
3455
3663
  // Progressive setup summary
3456
3664
  console.log('');
3457
3665
  console.log(renderSetupSummary(actionLog, selectedAgents, VERBOSE_MODE, { status: getSetupSummaryStatus() }));
3458
3666
  printSetupNotes();
3459
3667
  // One-step onboarding: run `forge init` when config is still absent (ac0b38c7).
3460
- // Hooks + Beads already handled above, so init skips those side effects.
3668
+ // Hooks + issue store already handled above, so init skips those side effects.
3461
3669
  await finalizeWorkflowConfig({ hooksAlreadyInstalled: true });
3462
3670
  console.log('');
3463
3671
  }
@@ -3617,13 +3825,13 @@ async function promptForOverwriteDecisions(question, projectStatus, flags = {})
3617
3825
 
3618
3826
 
3619
3827
  // Helper: Setup all selected agents - extracted to reduce cognitive complexity
3620
- async function setupSelectedAgents(selectedAgents, skipFiles) {
3828
+ async function setupSelectedAgents(selectedAgents, skipFiles, options = {}) {
3621
3829
  const totalAgents = selectedAgents.length;
3622
3830
  for (const [index, agentKey] of selectedAgents.entries()) {
3623
3831
  const agent = AGENTS[agentKey];
3624
3832
  console.log(`\n[${index + 1}/${totalAgents}] Setting up ${agent.name}...`);
3625
3833
  if (agentKey !== 'claude') { // Claude already done above
3626
- await setupAgent(agentKey, skipFiles);
3834
+ await setupAgent(agentKey, skipFiles, options);
3627
3835
  }
3628
3836
  }
3629
3837
 
@@ -3691,7 +3899,6 @@ async function interactiveSetupWithFlags(flags) {
3691
3899
 
3692
3900
  // Check agent-independent prerequisites first
3693
3901
  checkPrerequisites({
3694
- requireBeadsCli: true,
3695
3902
  requireGithubCli: false,
3696
3903
  requireJq: true,
3697
3904
  });
@@ -3740,11 +3947,11 @@ async function interactiveSetupWithFlags(flags) {
3740
3947
 
3741
3948
  // Setup Claude first if selected (delegated to helper), then remaining agents
3742
3949
  if (selectedAgents.includes('claude')) {
3743
- await setupAgent('claude', skipFiles);
3950
+ await setupAgent('claude', skipFiles, flags);
3744
3951
  }
3745
3952
 
3746
3953
  // Setup each selected agent with progress indication (delegated to helper)
3747
- await setupSelectedAgents(selectedAgents, skipFiles);
3954
+ await setupSelectedAgents(selectedAgents, skipFiles, flags);
3748
3955
  ensureWorkflowRuntimeAssets(selectedAgents);
3749
3956
 
3750
3957
  // Handle external services step (delegated to helper)
@@ -3758,7 +3965,7 @@ async function interactiveSetupWithFlags(flags) {
3758
3965
 
3759
3966
  // One-step onboarding: this path never writes .forge/config.yaml itself, so
3760
3967
  // run `forge init` when the config is still absent (ac0b38c7).
3761
- await finalizeWorkflowConfig();
3968
+ await finalizeWorkflowConfig({ hooksAlreadyInstalled: flags.skillsOnly === true });
3762
3969
  }
3763
3970
 
3764
3971
  // Main
@@ -3831,7 +4038,7 @@ function determineSelectedAgents(flags) {
3831
4038
  // Shared setup executor — used by handleSetupCommand
3832
4039
 
3833
4040
  // Dry-run setup — enumerate planned actions without writing files
3834
- function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js; complexity reduction deferred
4041
+ function dryRunSetup(agents, options = {}) { // NOSONAR — Extracted as-is from bin/forge.js; complexity reduction deferred
3835
4042
  const collector = new ActionCollector();
3836
4043
 
3837
4044
  // Helper: add create or skip based on whether file exists
@@ -3844,8 +4051,9 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
3844
4051
  }
3845
4052
  }
3846
4053
 
3847
- // AGENTS.md
4054
+ // Root policy files
3848
4055
  addFileAction('AGENTS.md', 'Copy workflow documentation');
4056
+ addFileAction('CODING_STANDARDS.md', 'Install Forge coding standards');
3849
4057
 
3850
4058
  // Per-agent planned actions
3851
4059
  for (const agentKey of agents) {
@@ -3901,10 +4109,12 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
3901
4109
  }
3902
4110
  }
3903
4111
 
3904
- // Git hooks
3905
- addFileAction('lefthook.yml', 'Git hook configuration');
3906
- addFileAction('.forge/hooks/check-tdd.js', 'TDD enforcement hook');
3907
- addFileAction('.forge/hooks/forge-native-hook.js', 'Native-hook enforcement adapter (Claude/Cursor)');
4112
+ // Git and harness hooks are opt-out for skills-only setup.
4113
+ if (options.skillsOnly !== true) {
4114
+ addFileAction('lefthook.yml', 'Git hook configuration');
4115
+ addFileAction('.forge/hooks/check-tdd.js', 'TDD enforcement hook');
4116
+ addFileAction('.forge/hooks/forge-native-hook.js', 'Native-hook enforcement adapter (Claude/Cursor)');
4117
+ }
3908
4118
 
3909
4119
  // Print dry-run summary
3910
4120
  console.log('');
@@ -3919,14 +4129,19 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
3919
4129
 
3920
4130
 
3921
4131
  async function executeSetup(config) {
3922
- const { agents, skipExternal, keepExisting = false, commandRunner } = config;
4132
+ const {
4133
+ agents,
4134
+ skipExternal,
4135
+ keepExisting = false,
4136
+ commandRunner,
4137
+ skillsOnly = false,
4138
+ } = config;
3923
4139
 
3924
4140
  showBanner('Installing for specified agents...');
3925
4141
  console.log('');
3926
4142
 
3927
4143
  // Check prerequisites
3928
4144
  checkPrerequisites({
3929
- requireBeadsCli: true,
3930
4145
  requireGithubCli: requiresGithubCliForSetup(agents),
3931
4146
  requireJq: true,
3932
4147
  commandRunner,
@@ -3953,60 +4168,62 @@ async function executeSetup(config) {
3953
4168
 
3954
4169
  // Setup Claude first if selected, then remaining agents
3955
4170
  if (agents.includes('claude')) {
3956
- await setupAgent('claude', skipFiles);
4171
+ await setupAgent('claude', skipFiles, { skillsOnly });
3957
4172
  }
3958
- await setupSelectedAgents(agents, skipFiles);
4173
+ await setupSelectedAgents(agents, skipFiles, { skillsOnly });
3959
4174
  ensureWorkflowRuntimeAssets(agents);
3960
4175
  ensureWorkflowShellPolicy(agents);
3961
- repairDeclaredLefthookDependency(agents);
4176
+ if (!skillsOnly) {
4177
+ repairDeclaredLefthookDependency(agents);
4178
+ }
3962
4179
 
3963
4180
  // Detect Husky and migrate before installing Lefthook hooks
3964
- await handleHuskyMigration();
4181
+ if (!skillsOnly) {
4182
+ await handleHuskyMigration();
4183
+ }
3965
4184
 
3966
4185
  // Install git hooks for TDD enforcement. LOUD: this is the `forge setup` handler,
3967
4186
  // so an inert-hooks result must fail non-zero (B3), not end green.
3968
4187
  console.log('');
3969
- installGitHooks({ loud: true });
4188
+ if (skillsOnly) {
4189
+ console.log('Skipping git and harness hooks (--skills-only).');
4190
+ } else {
4191
+ installGitHooks({ loud: true });
4192
+ }
3970
4193
 
3971
4194
  // External services (unless skipped)
3972
4195
  await handleExternalServices(skipExternal, agents);
3973
4196
 
3974
- // --sync flag: scaffold Beads GitHub sync workflows without prompting
3975
- if (SYNC_ENABLED) {
3976
- await handleSyncScaffold();
3977
- }
4197
+ removeDeprecatedSyncFiles();
3978
4198
 
3979
4199
  // Progressive setup summary
3980
4200
  console.log('');
3981
4201
  console.log(renderSetupSummary(actionLog, agents, VERBOSE_MODE, { status: getSetupSummaryStatus() }));
3982
4202
  printSetupNotes();
3983
4203
  // One-step onboarding: run `forge init` when config is still absent (ac0b38c7).
3984
- // Hooks + Beads already handled above, so init skips those side effects.
4204
+ // Hooks + issue store already handled above, so init skips those side effects.
3985
4205
  await finalizeWorkflowConfig({ hooksAlreadyInstalled: true });
3986
4206
  console.log('');
3987
4207
  }
3988
4208
 
3989
- // Helper: Scaffold Beads GitHub sync when --sync flag is provided
3990
-
3991
-
3992
- // Helper: Scaffold Beads GitHub sync when --sync flag is provided
3993
- async function handleSyncScaffold() {
3994
- console.log('');
3995
- console.log('Beads GitHub sync scaffolding is deprecated (--sync).');
4209
+ /**
4210
+ * Remove generated Beads/GitHub sync files left behind by older installs.
4211
+ *
4212
+ * Runs on every setup — there is no longer a flag for it. Only files whose
4213
+ * content matches a known generated template are removed, so user-owned files
4214
+ * at the same paths are preserved.
4215
+ */
4216
+ function removeDeprecatedSyncFiles() {
3996
4217
  try {
3997
- const result = scaffoldBeadsSync(projectRoot, getPackageRoot(packageDir));
3998
- console.log(` ${result.message}`);
3999
- for (const f of result.filesRemoved || []) {
4000
- console.log(` Removed deprecated sync file: ${f}`);
4218
+ const result = cleanupDeprecatedSyncFiles(projectRoot, { packageDir: getPackageRoot(packageDir) });
4219
+ for (const file of result.removed || []) {
4220
+ console.log(` Removed deprecated sync file: ${file}`);
4001
4221
  }
4002
4222
  } catch (err) {
4003
- console.error(` Error scaffolding GitHub-Beads sync: ${err.message}`);
4223
+ console.error(` Error removing deprecated sync files: ${err.message}`);
4004
4224
  }
4005
4225
  }
4006
4226
 
4007
- // Helper: Handle setup command in non-quick mode
4008
-
4009
-
4010
4227
  // Helper: Handle setup command in non-quick mode
4011
4228
  async function handleSetupCommand(selectedAgents, flags) {
4012
4229
  if (!Array.isArray(selectedAgents) || selectedAgents.length === 0) {
@@ -4024,6 +4241,7 @@ async function handleSetupCommand(selectedAgents, flags) {
4024
4241
  skipExternal: flags.skipExternal,
4025
4242
  keepExisting: flags.keep,
4026
4243
  commandRunner: flags.commandRunner,
4244
+ skillsOnly: flags.skillsOnly,
4027
4245
  });
4028
4246
  } finally {
4029
4247
  projectRoot = savedRoot;
@@ -4168,6 +4386,7 @@ const SETUP_FLAG_DEFAULTS = Object.freeze({
4168
4386
  sync: false,
4169
4387
  symlink: false,
4170
4388
  nonInteractive: false,
4389
+ skillsOnly: false,
4171
4390
  });
4172
4391
 
4173
4392
  const SIMPLE_SETUP_FLAG_UPDATES = Object.freeze({
@@ -4186,6 +4405,8 @@ const SIMPLE_SETUP_FLAG_UPDATES = Object.freeze({
4186
4405
  '--symlink': { symlink: true },
4187
4406
  '--yes': { yes: true, nonInteractive: true },
4188
4407
  '-y': { yes: true, nonInteractive: true },
4408
+ '--skills-only': { skillsOnly: true },
4409
+ '--no-hooks': { skillsOnly: true },
4189
4410
  });
4190
4411
 
4191
4412
  function parseAgentFlag(argv, currentIndex, isFlagToken) {
@@ -4262,9 +4483,9 @@ function mergeSetupFlags(flags, argv) {
4262
4483
  standard: Boolean(flags.standard || setupFlags.standard),
4263
4484
  full: Boolean(flags.full || setupFlags.full),
4264
4485
  skipExternal: Boolean(flags.skipExternal || setupFlags.skipExternal),
4265
- sync: Boolean(flags.sync || setupFlags.sync),
4266
4486
  symlink: Boolean(flags.symlink || setupFlags.symlink),
4267
4487
  nonInteractive: Boolean(flags.nonInteractive || setupFlags.nonInteractive),
4488
+ skillsOnly: Boolean(flags.skillsOnly || setupFlags.skillsOnly),
4268
4489
  };
4269
4490
  }
4270
4491
 
@@ -4309,7 +4530,6 @@ module.exports = {
4309
4530
  if (flags.verbose) VERBOSE_MODE = true;
4310
4531
  if (flags.nonInteractive || flags.yes) NON_INTERACTIVE = true;
4311
4532
  if (flags.symlink) SYMLINK_ONLY = true;
4312
- if (flags.sync) SYNC_ENABLED = true;
4313
4533
  actionLog = new SetupActionLog();
4314
4534
  resetSetupNotes();
4315
4535
  PKG_MANAGER = detectPackageManager();
@@ -4327,6 +4547,12 @@ module.exports = {
4327
4547
  }
4328
4548
 
4329
4549
  const [profile] = selectedProfiles;
4550
+ if (flags.skillsOnly) {
4551
+ return {
4552
+ success: false,
4553
+ error: `--skills-only cannot be combined with --${profile}.`,
4554
+ };
4555
+ }
4330
4556
  return initCommand.handler([`--profile=${profile}`, '--yes', ...(flags.force ? ['--force'] : [])], flags, projectRoot);
4331
4557
  }
4332
4558
 
@@ -4351,7 +4577,7 @@ module.exports = {
4351
4577
 
4352
4578
  if (flags.dryRun) {
4353
4579
  if (selectedAgents.length === 0) selectedAgents = ['claude'];
4354
- dryRunSetup(selectedAgents);
4580
+ dryRunSetup(selectedAgents, { skillsOnly: flags.skillsOnly });
4355
4581
  return { success: true };
4356
4582
  }
4357
4583
 
@@ -4360,12 +4586,7 @@ module.exports = {
4360
4586
  if (selectedAgents.length === 0 || (flags.yes && !flags.agents)) {
4361
4587
  selectedAgents = Object.keys(AGENTS);
4362
4588
  }
4363
- await quickSetup(selectedAgents, flags.skipExternal);
4364
- return { success: true };
4365
- }
4366
-
4367
- if (flags.sync && selectedAgents.length === 0) {
4368
- await handleSyncScaffold();
4589
+ await quickSetup(selectedAgents, flags.skipExternal, { skillsOnly: flags.skillsOnly });
4369
4590
  return { success: true };
4370
4591
  }
4371
4592
 
@@ -4406,6 +4627,8 @@ module.exports = {
4406
4627
  ensureGitHooksInstalled,
4407
4628
  forgeShouldWriteLefthookConfig,
4408
4629
  FORGE_USER_LEFTHOOK_YML,
4630
+ applyExistingGateDeferral,
4631
+ installGitHooks,
4409
4632
  setupClaudeMcpConfig,
4410
4633
  setupCursorMcpConfig,
4411
4634
  setupClaudePermissions,
@@ -4428,16 +4651,18 @@ module.exports = {
4428
4651
  repairRuntimeReadiness,
4429
4652
  _showBanner: showBanner,
4430
4653
  backupAndRemoveLegacyCursorRules,
4654
+ resolveCurrentSourceHead,
4655
+ normalizeClaudePointerContent,
4656
+ readStagedClaudePointerContent,
4431
4657
 
4432
4658
  // State accessors for testing
4433
- _getState: () => ({ projectRoot, FORCE_MODE, VERBOSE_MODE, NON_INTERACTIVE, SYMLINK_ONLY, SYNC_ENABLED, PKG_MANAGER }),
4659
+ _getState: () => ({ projectRoot, FORCE_MODE, VERBOSE_MODE, NON_INTERACTIVE, SYMLINK_ONLY, PKG_MANAGER }),
4434
4660
  _setState: (state) => {
4435
4661
  if (state.projectRoot !== undefined) projectRoot = state.projectRoot;
4436
4662
  if (state.FORCE_MODE !== undefined) FORCE_MODE = state.FORCE_MODE;
4437
4663
  if (state.VERBOSE_MODE !== undefined) VERBOSE_MODE = state.VERBOSE_MODE;
4438
4664
  if (state.NON_INTERACTIVE !== undefined) NON_INTERACTIVE = state.NON_INTERACTIVE;
4439
4665
  if (state.SYMLINK_ONLY !== undefined) SYMLINK_ONLY = state.SYMLINK_ONLY;
4440
- if (state.SYNC_ENABLED !== undefined) SYNC_ENABLED = state.SYNC_ENABLED;
4441
4666
  if (state.PKG_MANAGER !== undefined) PKG_MANAGER = state.PKG_MANAGER;
4442
4667
  },
4443
4668
  };