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

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 +14 -7
  2. package/CHANGELOG.md +43 -1
  3. package/README.md +6 -2
  4. package/bin/forge-cmd.js +21 -1
  5. package/bin/forge.js +16 -369
  6. package/docs/INDEX.md +1 -1
  7. package/docs/guides/BEADS_GITHUB_SYNC.md +2 -31
  8. package/docs/guides/MIGRATION.md +4 -4
  9. package/docs/guides/SETUP.md +16 -16
  10. package/docs/reference/COMMANDS.md +9 -4
  11. package/docs/reference/INSIGHTS_RECAP.md +9 -20
  12. package/docs/reference/RELEASE.md +5 -3
  13. package/docs/reference/TOOLCHAIN.md +8 -0
  14. package/docs/reference/protected-state-surfaces.md +4 -4
  15. package/docs/reference/shepherd.md +117 -17
  16. package/lefthook.yml +12 -0
  17. package/lib/activation/ensure-forge-home.js +33 -15
  18. package/lib/adapters/greptile-review-adapter.js +1 -1
  19. package/lib/adapters/pr-state-adapter.js +397 -100
  20. package/lib/agents-config.js +5 -0
  21. package/lib/audit-evidence.js +71 -110
  22. package/lib/capped-jsonl-log.js +236 -0
  23. package/lib/commands/_issue.js +31 -46
  24. package/lib/commands/_manifest.js +1 -1
  25. package/lib/commands/_registry.js +2 -2
  26. package/lib/commands/_resolve-command-opts.js +36 -29
  27. package/lib/commands/claim.js +2 -4
  28. package/lib/commands/clean.js +196 -32
  29. package/lib/commands/dev.js +4 -33
  30. package/lib/commands/hooks.js +358 -13
  31. package/lib/commands/insights.js +8 -3
  32. package/lib/commands/merge.js +600 -40
  33. package/lib/commands/plan.js +23 -115
  34. package/lib/commands/pr.js +1 -1
  35. package/lib/commands/preflight.js +11 -2
  36. package/lib/commands/prime.js +23 -3
  37. package/lib/commands/push.js +41 -51
  38. package/lib/commands/recall.js +60 -16
  39. package/lib/commands/recap.js +6 -1
  40. package/lib/commands/release.js +18 -4
  41. package/lib/commands/serve.js +5 -2
  42. package/lib/commands/setup.js +191 -95
  43. package/lib/commands/shepherd.js +49 -4
  44. package/lib/commands/ship.js +22 -23
  45. package/lib/commands/skill.js +383 -0
  46. package/lib/commands/status.js +54 -33
  47. package/lib/commands/test.js +56 -34
  48. package/lib/commands/worktree.js +247 -43
  49. package/lib/core/runtime-graph.js +89 -15
  50. package/lib/doc-assertions.js +297 -0
  51. package/lib/existing-tdd-gate.js +253 -0
  52. package/lib/forge-context.js +1 -4
  53. package/lib/forge-issues.js +64 -491
  54. package/lib/git-defaults.js +56 -0
  55. package/lib/harness-capability-matrix.js +5 -5
  56. package/lib/hook-renderer.js +147 -16
  57. package/lib/insights.js +96 -80
  58. package/lib/issue-backend.js +42 -3
  59. package/lib/kernel/backing-issue.js +14 -2
  60. package/lib/kernel/broker.js +44 -0
  61. package/lib/kernel/cli-broker-factory.js +12 -1
  62. package/lib/kernel/close-on-merge.js +154 -0
  63. package/lib/kernel/fs-class.js +42 -25
  64. package/lib/kernel/migrations.js +30 -2
  65. package/lib/kernel/schema.js +35 -0
  66. package/lib/kernel/sqlite-driver.js +292 -18
  67. package/lib/lefthook-wiring.js +21 -1
  68. package/lib/memory/router.js +16 -1
  69. package/lib/memory-digest.js +47 -15
  70. package/lib/memory-recall-events.js +145 -0
  71. package/lib/memory-recall.js +212 -0
  72. package/lib/merge-rules.js +8 -4
  73. package/lib/npm-publish-workflow.js +272 -0
  74. package/lib/orientation.js +371 -49
  75. package/lib/plugin-catalog.js +14 -4
  76. package/lib/pr-bundle.js +9 -6
  77. package/lib/pr-monitor/journal.js +18 -2
  78. package/lib/pr-monitor/reconcile-executor.js +842 -0
  79. package/lib/pr-monitor/reconcile-tick.js +138 -0
  80. package/lib/pr-monitor/reconcile.js +0 -0
  81. package/lib/pr-monitor/render-summary.js +196 -0
  82. package/lib/pr-monitor/shepherd-lease.js +252 -0
  83. package/lib/pr-monitor/watch-lifecycle.js +14 -2
  84. package/lib/pr-pull.js +98 -24
  85. package/lib/pr-shepherd.js +34 -8
  86. package/lib/preflight/gates.js +65 -18
  87. package/lib/preflight/runner.js +5 -0
  88. package/lib/project-memory.js +40 -0
  89. package/lib/protected-state-authority.js +305 -0
  90. package/lib/protected-state-surfaces.js +64 -44
  91. package/lib/release-readiness.js +51 -4
  92. package/lib/rules-sync.js +4 -0
  93. package/lib/runtime-health.js +15 -46
  94. package/lib/shell-utils.js +1 -1
  95. package/lib/skill-eval.js +750 -0
  96. package/lib/skills-sync.js +6 -3
  97. package/lib/smart-merge.js +28 -4
  98. package/lib/status/identity.js +46 -0
  99. package/lib/status/presenter.js +0 -35
  100. package/lib/status/snapshot.js +11 -16
  101. package/lib/symlink-utils.js +74 -26
  102. package/lib/upgrade-safety.js +47 -9
  103. package/lib/using-forge.js +328 -0
  104. package/lib/workflow/enforce-stage.js +5 -5
  105. package/lib/workflow/state-manager.js +23 -23
  106. package/package.json +6 -7
  107. package/rules/using-forge.md +24 -0
  108. package/scripts/doc-asserting-tests.js +158 -0
  109. package/scripts/forge-team/index.sh +0 -5
  110. package/scripts/forge-team/tests/dispatcher.test.sh +1 -1
  111. package/scripts/forge-team/tests/workflow-integration.test.sh +0 -1
  112. package/scripts/lib/behavioral-eval-runner.js +310 -0
  113. package/scripts/lib/behavioral-eval-runtime.js +456 -0
  114. package/scripts/lib/eval-evidence.js +328 -0
  115. package/scripts/lib/eval-runner.js +81 -41
  116. package/scripts/lib/immutable-eval-corpus.js +309 -0
  117. package/scripts/lib/promotion-evidence-loader.js +94 -0
  118. package/scripts/lib/promotion-scorecard.js +314 -0
  119. package/scripts/npm-release-receipt.js +134 -0
  120. package/scripts/process-tree.js +761 -0
  121. package/scripts/protected-state-check.js +47 -22
  122. package/scripts/run-command-eval.js +29 -1
  123. package/scripts/sync-d20-audit.js +172 -0
  124. package/scripts/test-full-suite.js +249 -37
  125. package/scripts/test.js +184 -44
  126. package/skills/claim-safety/SKILL.md +4 -0
  127. package/skills/claim-safety/evals/scorecard.json +41 -0
  128. package/skills/coverage.json +83 -0
  129. package/skills/dev/SKILL.md +4 -0
  130. package/skills/dev/evals/scorecard.json +41 -0
  131. package/skills/gates/SKILL.md +80 -0
  132. package/skills/gates/evals/evals.json +38 -0
  133. package/skills/gates/evals/scorecard.json +41 -0
  134. package/skills/hermes-forge/SKILL.md +1 -0
  135. package/skills/hermes-forge/evals/scorecard.json +41 -0
  136. package/skills/issue-basics/SKILL.md +1 -0
  137. package/skills/issue-basics/evals/scorecard.json +41 -0
  138. package/skills/kernel/SKILL.md +38 -0
  139. package/skills/kernel/evals/scorecard.json +41 -0
  140. package/skills/memory/SKILL.md +16 -1
  141. package/skills/memory/evals/scorecard.json +41 -0
  142. package/skills/parallel-deep-research/SKILL.md +1 -0
  143. package/skills/parallel-deep-research/evals/scorecard.json +41 -0
  144. package/skills/plan/SKILL.md +6 -0
  145. package/skills/plan/evals/scorecard.json +41 -0
  146. package/skills/portability/SKILL.md +47 -0
  147. package/skills/portability/evals/evals.json +34 -0
  148. package/skills/portability/evals/scorecard.json +41 -0
  149. package/skills/research/SKILL.md +1 -0
  150. package/skills/research/evals/scorecard.json +41 -0
  151. package/skills/review/SKILL.md +10 -11
  152. package/skills/review/evals/scorecard.json +41 -0
  153. package/skills/rollback/SKILL.md +5 -11
  154. package/skills/rollback/evals/scorecard.json +41 -0
  155. package/skills/setup/SKILL.md +91 -0
  156. package/skills/setup/evals/evals.json +42 -0
  157. package/skills/setup/evals/scorecard.json +41 -0
  158. package/skills/shepherd/SKILL.md +84 -38
  159. package/skills/shepherd/evals/evals.json +21 -9
  160. package/skills/shepherd/evals/scorecard.json +41 -0
  161. package/skills/ship/SKILL.md +10 -12
  162. package/skills/ship/evals/scorecard.json +41 -0
  163. package/skills/smith/SKILL.md +8 -0
  164. package/skills/smith/evals/scorecard.json +41 -0
  165. package/skills/sonarcloud/SKILL.md +1 -0
  166. package/skills/sonarcloud/evals/scorecard.json +41 -0
  167. package/skills/sonarcloud-analysis/SKILL.md +1 -0
  168. package/skills/sonarcloud-analysis/evals/scorecard.json +41 -0
  169. package/skills/status/SKILL.md +3 -0
  170. package/skills/status/evals/scorecard.json +41 -0
  171. package/skills/triage-ready/SKILL.md +2 -0
  172. package/skills/triage-ready/evals/scorecard.json +41 -0
  173. package/skills/using-forge/SKILL.md +104 -0
  174. package/skills/using-forge/evals/scorecard.json +41 -0
  175. package/skills/validate/SKILL.md +4 -0
  176. package/skills/validate/evals/scorecard.json +41 -0
  177. package/skills/verify/SKILL.md +4 -0
  178. package/skills/verify/evals/scorecard.json +41 -0
  179. package/skills/worktree/SKILL.md +92 -0
  180. package/skills/worktree/evals/evals.json +38 -0
  181. package/skills/worktree/evals/scorecard.json +41 -0
  182. package/lib/adapters/beads-issue-adapter.js +0 -127
  183. package/lib/beads-nudge.js +0 -91
  184. package/lib/beads-setup.js +0 -538
  185. package/lib/beads-sync-scaffold.js +0 -189
  186. package/lib/commands/board.js +0 -64
  187. package/lib/pat-setup.js +0 -207
  188. package/lib/pr-monitor/render-sticky.js +0 -192
  189. package/lib/pr-monitor/upsert-sticky.js +0 -169
  190. package/lib/status/beads-snapshot.js +0 -145
  191. package/scripts/beads-context.sh +0 -577
  192. package/scripts/beads-migrate-to-dolt.sh +0 -7
  193. package/scripts/beads-upgrade-smoke.sh +0 -284
  194. package/scripts/forge-team/lib/dashboard.sh +0 -316
  195. package/scripts/forge-team/tests/dashboard.test.sh +0 -155
  196. package/scripts/lib/beads-migrate-to-dolt.mjs +0 -503
@@ -18,7 +18,7 @@ const {
18
18
  const {
19
19
  loadState,
20
20
  extractWorkflowStateFromComments,
21
- readWorkflowStateFromBeads,
21
+ readWorkflowStateFromIssue,
22
22
  } = require('../workflow/state-manager');
23
23
  const { secureExecFileSync } = require('../shell-utils.js');
24
24
 
@@ -140,12 +140,12 @@ function analyzeChecks(context) {
140
140
  }
141
141
 
142
142
  /**
143
- * Analyze Beads issue state
144
- * @param {object} beadsIssue - Beads issue object
145
- * @returns {object} Beads analysis
143
+ * Analyze kernel issue state
144
+ * @param {object} issueRecord - kernel issue record
145
+ * @returns {object} issue analysis
146
146
  */
147
- function analyzeBeads(beadsIssue) {
148
- if (!beadsIssue) {
147
+ function analyzeIssue(issueRecord) {
148
+ if (!issueRecord) {
149
149
  return {
150
150
  hasActiveIssue: false,
151
151
  issueStatus: null,
@@ -155,10 +155,10 @@ function analyzeBeads(beadsIssue) {
155
155
 
156
156
  return {
157
157
  hasActiveIssue: true,
158
- issueStatus: beadsIssue.status,
159
- issueType: beadsIssue.type,
160
- isInProgress: beadsIssue.status === 'in_progress',
161
- isClosed: beadsIssue.status === 'closed',
158
+ issueStatus: issueRecord.status,
159
+ issueType: issueRecord.type,
160
+ isInProgress: issueRecord.status === 'in_progress',
161
+ isClosed: issueRecord.status === 'closed',
162
162
  };
163
163
  }
164
164
 
@@ -178,7 +178,7 @@ function calculateConfidence(factors, stage) {
178
178
  files: 30,
179
179
  pr: 25,
180
180
  checks: 15,
181
- beads: 10,
181
+ issue: 10,
182
182
  };
183
183
 
184
184
  // Branch state
@@ -235,16 +235,16 @@ function calculateConfidence(factors, stage) {
235
235
  score += weights.checks / 2; // Partial match
236
236
  }
237
237
 
238
- // Beads issue
239
- maxScore += weights.beads;
240
- if (factors.beads.isInProgress && stage >= 2 && stage <= 7) {
241
- score += weights.beads; // Active work
242
- } else if (factors.beads.isClosed && stage >= 8) {
243
- score += weights.beads; // Closed for late stages
244
- } else if (!factors.beads.hasActiveIssue && stage === 1) {
245
- score += weights.beads; // No issue for fresh start
238
+ // Issue state
239
+ maxScore += weights.issue;
240
+ if (factors.issue.isInProgress && stage >= 2 && stage <= 7) {
241
+ score += weights.issue; // Active work
242
+ } else if (factors.issue.isClosed && stage >= 8) {
243
+ score += weights.issue; // Closed for late stages
244
+ } else if (!factors.issue.hasActiveIssue && stage === 1) {
245
+ score += weights.issue; // No issue for fresh start
246
246
  } else {
247
- score += weights.beads / 2; // Partial match
247
+ score += weights.issue / 2; // Partial match
248
248
  }
249
249
 
250
250
  const percentage = Math.round((score / maxScore) * 100);
@@ -275,21 +275,21 @@ function detectStage(context) {
275
275
  const fileFactors = analyzeFiles(context);
276
276
  const prFactors = analyzePR(context.pr);
277
277
  const checkFactors = analyzeChecks(context);
278
- const beadsFactors = analyzeBeads(context.beadsIssue);
278
+ const issueFactors = analyzeIssue(context.issueRecord);
279
279
 
280
280
  const factors = {
281
281
  branch: branchFactors,
282
282
  files: fileFactors,
283
283
  pr: prFactors,
284
284
  checks: checkFactors,
285
- beads: beadsFactors,
285
+ issue: issueFactors,
286
286
  };
287
287
 
288
288
  // Stage detection logic
289
289
  let stage;
290
290
 
291
291
  // Stage 9: PR merged, verify docs
292
- if (prFactors.prMerged && beadsFactors.isClosed) {
292
+ if (prFactors.prMerged && issueFactors.isClosed) {
293
293
  stage = 9;
294
294
  }
295
295
  // Stage 8: PR approved, ready to merge
@@ -317,7 +317,7 @@ function detectStage(context) {
317
317
  stage = 3;
318
318
  }
319
319
  // Stage 2: Research in progress
320
- else if (beadsFactors.issueType === 'research' && beadsFactors.isInProgress) {
320
+ else if (issueFactors.issueType === 'research' && issueFactors.isInProgress) {
321
321
  stage = 2;
322
322
  }
323
323
  // Stage 1: Fresh project (default)
@@ -612,7 +612,7 @@ function resolveWorkflowState(inputs) {
612
612
  issueId: inputs.issueId,
613
613
  issue: inputs.issue,
614
614
  comments: inputs.bdComments,
615
- preferBeads: inputs.preferBeads,
615
+ preferIssueLookup: inputs.preferIssueLookup,
616
616
  });
617
617
 
618
618
  return { workflowState: state, fallbackReason: null };
@@ -649,7 +649,7 @@ function buildAuthoritativeStatus(workflowState) {
649
649
  branch: {},
650
650
  pr: {},
651
651
  checks: { allChecksPass: workflowState.completedStages.includes('validate') },
652
- beads: { hasActiveIssue: true },
652
+ issue: { hasActiveIssue: true },
653
653
  },
654
654
  };
655
655
  }
@@ -751,15 +751,36 @@ function formatStatus(result) {
751
751
  module.exports = {
752
752
  name: 'status',
753
753
  description: 'One-glance orientation: where you are, what to run next, and your work',
754
- usage: 'forge status [--full] [--json]',
754
+ usage: 'forge status [-v|--verbose] [--full] [--json]',
755
755
  flags: {
756
+ '-v, --verbose': 'Print the full session-entry briefing (replaces the deprecated `forge prime`)',
756
757
  '--full': 'Also show blocked, stale, and recently completed issues',
757
758
  '--json': 'Emit the machine-readable status envelope',
758
759
  },
759
760
  handler: async (args, flags, projectRoot) => {
761
+ // git-status-v semantics: bare status is the short pulse, -v is the long form.
762
+ // `--verbose` also arrives as flags.verbose from the global CLI flag parser.
763
+ const statusArgs = Array.isArray(args) ? args : [];
764
+ if (flags?.verbose === true || statusArgs.includes('-v') || statusArgs.includes('--verbose')) {
765
+ // Required lazily so the short pulse never loads the briefing's module graph.
766
+ const { renderBriefing } = require('./prime');
767
+ // The briefing reads --json from args (same as prime), so fold in the flag
768
+ // forms the short pulse accepts or `-v` would silently drop them.
769
+ const wantsJson = flags?.json === true || flags?.['--json'] === true;
770
+ const briefingArgs = wantsJson && !statusArgs.includes('--json')
771
+ ? [...statusArgs, '--json']
772
+ : statusArgs;
773
+ return renderBriefing(briefingArgs, projectRoot || process.cwd());
774
+ }
775
+
776
+ // Injectable snapshot read, same convention as setup.js's `options.commandRunner`
777
+ // (lib/commands/setup.js:223): a real default with a test-supplied override. Tests
778
+ // need a snapshot with controlled ids and orderings, which a seeded kernel cannot
779
+ // give them — the kernel stamps its own updated_at.
780
+ const readSnapshot = flags?.readStatusSnapshot || readStatusSnapshot;
760
781
  const inputs = parseStatusInputs(args, flags);
761
782
  const isZeroArgStatus = !inputs.issueId && !inputs.workflowState && !inputs.bdComments;
762
- inputs.preferBeads = !isZeroArgStatus && Boolean(inputs.issueId || inputs.bdComments);
783
+ inputs.preferIssueLookup = !isZeroArgStatus && Boolean(inputs.issueId || inputs.bdComments);
763
784
  if (!inputs.projectRoot && projectRoot) {
764
785
  inputs.projectRoot = projectRoot;
765
786
  }
@@ -769,11 +790,11 @@ module.exports = {
769
790
 
770
791
  if (isZeroArgStatus) {
771
792
  context = detectRepoContext(effectiveProjectRoot);
772
- // Read from the active issue backend (Kernel by default; Beads only when
773
- // explicitly selected). Previously hard-wired to the retired Beads
793
+ // Read from the Kernel, the only issue backend. Previously hard-wired to
794
+ // the retired Beads
774
795
  // issues.jsonl, which is empty on a kernel-default repo — so the flagship
775
796
  // view showed "Ready: none" despite a populated kernel (bug 40f35797).
776
- snapshot = await readStatusSnapshot(effectiveProjectRoot, {
797
+ snapshot = await readSnapshot(effectiveProjectRoot, {
777
798
  now: inputs.now,
778
799
  staleAfterDays: inputs.staleAfterDays,
779
800
  issueBackend: inputs.issueBackend,
@@ -834,7 +855,7 @@ module.exports = {
834
855
  extractWorkflowStateFromComments,
835
856
  resolveWorkflowState,
836
857
  parseStatusInputs,
837
- readWorkflowStateFromBeads,
858
+ readWorkflowStateFromIssue,
838
859
  detectStage,
839
860
  detectRepoContext,
840
861
  discoverCurrentIssue,
@@ -842,7 +863,7 @@ module.exports = {
842
863
  analyzeFiles,
843
864
  analyzePR,
844
865
  analyzeChecks,
845
- analyzeBeads,
866
+ analyzeIssue,
846
867
  calculateConfidence,
847
868
  formatStatus,
848
869
  extractDesignSlugs,
@@ -5,7 +5,6 @@
5
5
  *
6
6
  * Wraps test execution with smart defaults:
7
7
  * - Auto-detects package manager from lockfiles
8
- * - Checks Beads (Dolt) connectivity, sets BEADS_SKIP_TESTS if unavailable
9
8
  * - Supports --affected flag to run only tests for changed files
10
9
  *
11
10
  * Security: Uses execFileSync for subprocess calls (OWASP A03)
@@ -17,6 +16,8 @@ const { execFileSync: defaultExecFileSync, spawnSync: defaultSpawnSync } = requi
17
16
  const defaultFs = require('node:fs');
18
17
  const path = require('node:path');
19
18
 
19
+ const { selectDocAssertingTests } = require('../doc-assertions');
20
+
20
21
  /** @type {Array<[string, string]>} Lockfile → package manager mapping (order matters) */
21
22
  const LOCKFILE_MAP = [
22
23
  ['bun.lockb', 'bun'],
@@ -26,20 +27,53 @@ const LOCKFILE_MAP = [
26
27
  ];
27
28
 
28
29
  const DEFAULT_TIMEOUT = 120000;
29
- const BEADS_CHECK_TIMEOUT = 3000;
30
30
  const DIRECT_TEST_CANDIDATES = Object.freeze({
31
31
  'bin/forge.js': [
32
32
  'test/cli-flags.test.js',
33
33
  'test/forge-cli-registry.test.js',
34
- 'test/setup-github-sync.test.js',
35
34
  'test/setup-runtime-flags.test.js',
36
35
  ],
36
+ // The second CLI surface (command descriptions + shepherd wiring). Without an
37
+ // entry here it resolves to zero tests and pushes fall back to the full suite.
38
+ 'bin/forge-cmd.js': [
39
+ 'test/cli/forge-cmd.test.js',
40
+ 'test/forge-cmd-shepherd.test.js',
41
+ ],
42
+ 'bin/forge-preflight.js': ['test/bin/forge-preflight.test.js'],
43
+ // The CI entry point is covered by the doc-assertion regression suite; without
44
+ // this entry it resolves to zero tests and forces the full suite.
45
+ // (lib/doc-assertions.js resolves via the lib/ -> test/ convention below.)
46
+ 'scripts/doc-asserting-tests.js': [
47
+ 'test/doc-assertions.test.js',
48
+ 'test/scripts/doc-asserting-tests.test.js',
49
+ ],
37
50
  'lib/lefthook-check.js': ['test/lefthook-check.test.js', 'test/runtime-health.test.js'],
38
51
  'lib/runtime-health.js': ['test/runtime-health.test.js'],
52
+ // skill-eval hosts the accuracy-lint detectors (auditCommandDocumentation /
53
+ // auditRouterPrecision), so edits there must also run their detector suite.
54
+ 'lib/skill-eval.js': ['test/skill-eval.test.js', 'test/skill-accuracy.test.js'],
39
55
  'lib/upgrade-safety.js': ['test/commands/upgrade.test.js'],
40
56
  'scripts/test.js': ['test/scripts/test-runner.test.js'],
41
57
  });
42
58
 
59
+ // Skill sources (canonical `skills/**` and the committed `.agents/skills/**` mirror)
60
+ // map to the fast skill suite. This is what keeps a skills-only PR on the targeted
61
+ // lane instead of the full ~1500-test suite. `skills-sync-drift` guards the mirror.
62
+ const SKILL_TEST_TARGETS = Object.freeze([
63
+ 'test/skills-structure.test.js',
64
+ 'test/skill-coverage.test.js',
65
+ 'test/skill-eval.test.js',
66
+ 'test/skill-accuracy.test.js',
67
+ 'test/skill-dispatch-parity.test.js',
68
+ 'test/using-forge.test.js',
69
+ 'test/structural/skills-sync-drift.test.js',
70
+ 'test/skills/chain-integrity.test.js',
71
+ 'test/skills/skills-sync.test.js',
72
+ 'test/skills/stage-skills.test.js',
73
+ 'test/skills/using-forge-skill.test.js',
74
+ 'test/skills/context-cost.test.js',
75
+ ]);
76
+
43
77
  /**
44
78
  * Detect the package manager by checking which lockfile exists.
45
79
  *
@@ -56,22 +90,6 @@ function detectPackageManager(projectRoot, fs) {
56
90
  return 'npm';
57
91
  }
58
92
 
59
- /**
60
- * Check if Beads (bd CLI) is reachable.
61
- *
62
- * @param {Function} execFileSync - Injected execFileSync
63
- * @returns {boolean} true if bd is available
64
- */
65
- function checkBeadsConnectivity(execFileSync) {
66
- try {
67
- execFileSync('bd', ['list', '--limit=1'], { timeout: BEADS_CHECK_TIMEOUT });
68
- return true;
69
- } catch (_e) { // NOSONAR S2486
70
- /* intentional: bd not installed or unreachable */
71
- return false;
72
- }
73
- }
74
-
75
93
  function resolveBaseBranch(execFileSync) {
76
94
  let baseBranch = 'main';
77
95
  try {
@@ -168,6 +186,16 @@ function getAffectedTestFiles(projectRoot, execFileSync, fs = defaultFs, options
168
186
  }
169
187
  }
170
188
 
189
+ // Markdown changes must also run the suites that assert on that markdown's
190
+ // CONTENT (README size badge, AGENTS.md convention, docs consistency). The
191
+ // static mapping above only knows the docs-consistency suite, so a docs PR
192
+ // used to merge green and break master for the next code PR (issue 63556816).
193
+ // The set is derived from the test sources, so new markdown-reading suites are
194
+ // picked up without editing a list here.
195
+ for (const candidate of selectDocAssertingTests(changedFiles, projectRoot, fs)) {
196
+ testFiles.add(candidate);
197
+ }
198
+
171
199
  return Array.from(testFiles).sort((left, right) => left.localeCompare(right));
172
200
  }
173
201
 
@@ -183,6 +211,10 @@ function getTestCandidatesForChangedFile(file) {
183
211
  return [file];
184
212
  }
185
213
 
214
+ if (file.startsWith('skills/') || file.startsWith('.agents/skills/')) {
215
+ return [...SKILL_TEST_TARGETS];
216
+ }
217
+
186
218
  if (file === 'README.md'
187
219
  || file === 'QUICKSTART.md'
188
220
  || file === 'CHANGELOG.md'
@@ -259,7 +291,7 @@ module.exports = {
259
291
  * @param {Object} [deps.fs] - fs module
260
292
  * @param {Function} [deps.execFileSync] - child_process.execFileSync
261
293
  * @param {Function} [deps.spawnSync] - child_process.spawnSync
262
- * @returns {Promise<{ success: boolean, exitCode: number, beadsSkipped: boolean }>}
294
+ * @returns {Promise<{ success: boolean, exitCode: number }>}
263
295
  */
264
296
  async handler(_args, flags, projectRoot, deps = {}) {
265
297
  const fs = deps.fs || defaultFs;
@@ -281,19 +313,10 @@ module.exports = {
281
313
  }
282
314
  } catch (_e) { /* intentional: package.json missing or unreadable, use default timeout */ } // NOSONAR S2486
283
315
 
284
- // 3. Check Beads connectivity
285
- const beadsAvailable = checkBeadsConnectivity(execFileSync);
286
- const beadsSkipped = !beadsAvailable;
287
-
288
- const extraEnv = {};
289
- if (beadsSkipped) {
290
- extraEnv.BEADS_SKIP_TESTS = '1';
291
- }
292
-
293
- // 4. Build test command args
316
+ // 3. Build test command args
294
317
  let testArgs = ['run', 'test'];
295
318
 
296
- // 5. --affected flag: find changed test files
319
+ // 4. --affected flag: find changed test files
297
320
  if (flags['--affected'] || flags.affected) {
298
321
  const affectedTests = getAffectedTestFiles(projectRoot, execFileSync, fs, {
299
322
  sinceUpstream: flags.sinceUpstream || flags['--since-upstream'],
@@ -304,9 +327,9 @@ module.exports = {
304
327
  // If no affected tests found, fall back to running all tests
305
328
  }
306
329
 
307
- // 6. Run tests
330
+ // 5. Run tests
308
331
  const result = spawnSync(pkgManager, testArgs, {
309
- env: { ...process.env, ...extraEnv },
332
+ env: { ...process.env },
310
333
  timeout,
311
334
  stdio: 'inherit',
312
335
  shell: process.platform === 'win32',
@@ -317,7 +340,6 @@ module.exports = {
317
340
  return {
318
341
  success: exitCode === 0,
319
342
  exitCode,
320
- beadsSkipped,
321
343
  };
322
344
  },
323
345
  };