forge-workflow 0.1.0-beta.2 → 0.1.0-beta.4

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 (155) hide show
  1. package/.forge/hooks/check-tdd.js +79 -5
  2. package/.forge/hooks/forge-native-hook.js +194 -8
  3. package/AGENTS.md +2 -1
  4. package/CHANGELOG.md +28 -0
  5. package/QUICKSTART.md +6 -2
  6. package/README.md +3 -1
  7. package/bin/forge-cmd.js +1 -1
  8. package/bin/forge.js +95 -19
  9. package/docs/guides/SETUP.md +4 -1
  10. package/docs/guides/SUPPORT.md +5 -0
  11. package/docs/reference/COMMANDS.md +12 -1
  12. package/docs/reference/shepherd.md +114 -3
  13. package/lib/activation/ensure-forge-home.js +135 -0
  14. package/lib/adapters/beads-kernel-compat.js +67 -0
  15. package/lib/adapters/greptile-review-adapter.js +1 -1
  16. package/lib/adapters/pr-state-adapter.js +103 -8
  17. package/lib/adoption-profiles.js +17 -4
  18. package/lib/agents-config.js +5 -0
  19. package/lib/beads-detect.js +60 -0
  20. package/lib/commands/_aliases.js +248 -0
  21. package/lib/commands/_issue.js +65 -41
  22. package/lib/commands/_manifest.js +3 -1
  23. package/lib/commands/_registry.js +14 -0
  24. package/lib/commands/_resolve-command-opts.js +36 -60
  25. package/lib/commands/claim.js +2 -4
  26. package/lib/commands/gate.js +19 -2
  27. package/lib/commands/hooks.js +294 -12
  28. package/lib/commands/init.js +26 -20
  29. package/lib/commands/memory.js +81 -0
  30. package/lib/commands/migrate.js +0 -161
  31. package/lib/commands/plan.js +71 -123
  32. package/lib/commands/pr.js +88 -0
  33. package/lib/commands/prime.js +8 -1
  34. package/lib/commands/push.js +66 -0
  35. package/lib/commands/recall.js +67 -12
  36. package/lib/commands/recap.js +18 -4
  37. package/lib/commands/release.js +15 -3
  38. package/lib/commands/remember.js +86 -20
  39. package/lib/commands/serve.js +5 -2
  40. package/lib/commands/setup.js +135 -73
  41. package/lib/commands/shepherd.js +102 -4
  42. package/lib/commands/ship.js +40 -4
  43. package/lib/commands/skill.js +275 -0
  44. package/lib/commands/status.js +37 -32
  45. package/lib/commands/test.js +32 -0
  46. package/lib/commands/worktree.js +87 -5
  47. package/lib/core/runtime-graph.js +122 -17
  48. package/lib/forge-issues.js +13 -464
  49. package/lib/gate-events.js +54 -55
  50. package/lib/global-flags.js +30 -0
  51. package/lib/grounding/context-events.js +230 -0
  52. package/lib/grounding/read-first.js +112 -0
  53. package/lib/harness-capability-matrix.js +2 -2
  54. package/lib/hook-renderer.js +144 -12
  55. package/lib/issue-backend.js +42 -3
  56. package/lib/kernel/backing-issue.js +7 -1
  57. package/lib/kernel/broker.js +28 -0
  58. package/lib/kernel/migrations.js +30 -2
  59. package/lib/kernel/owned-kernel.js +43 -0
  60. package/lib/kernel/schema.js +35 -0
  61. package/lib/kernel/sqlite-driver.js +187 -1
  62. package/lib/memory-recall.js +151 -0
  63. package/lib/orientation.js +303 -6
  64. package/lib/pr-bundle.js +6 -2
  65. package/lib/pr-monitor/auto-actions.js +175 -0
  66. package/lib/pr-monitor/digest.js +206 -0
  67. package/lib/pr-monitor/reconcile-executor.js +659 -0
  68. package/lib/pr-monitor/reconcile-tick.js +138 -0
  69. package/lib/pr-monitor/reconcile.js +0 -0
  70. package/lib/pr-monitor/render-sticky.js +57 -8
  71. package/lib/pr-monitor/shepherd-lease.js +243 -0
  72. package/lib/pr-monitor/upsert-sticky.js +169 -0
  73. package/lib/pr-monitor/watch-lifecycle.js +1 -1
  74. package/lib/pr-pull.js +113 -17
  75. package/lib/project-memory.js +8 -0
  76. package/lib/release-readiness.js +17 -1
  77. package/lib/rules-sync.js +4 -0
  78. package/lib/runtime-health.js +15 -46
  79. package/lib/skill-eval.js +750 -0
  80. package/lib/status/identity.js +46 -0
  81. package/lib/status/presenter.js +0 -35
  82. package/lib/status/snapshot.js +11 -16
  83. package/lib/upgrade-safety.js +52 -1
  84. package/lib/using-forge.js +315 -0
  85. package/lib/workflow/enforce-stage.js +64 -7
  86. package/lib/workflow/state-manager.js +23 -23
  87. package/package.json +2 -2
  88. package/rules/using-forge.md +24 -0
  89. package/scripts/forge-team/index.sh +0 -5
  90. package/scripts/forge-team/tests/dispatcher.test.sh +1 -1
  91. package/scripts/forge-team/tests/workflow-integration.test.sh +0 -1
  92. package/scripts/pr-auto-actions.js +93 -0
  93. package/scripts/pr-verdict-label.js +50 -0
  94. package/scripts/test.js +8 -1
  95. package/skills/claim-safety/SKILL.md +4 -0
  96. package/skills/claim-safety/evals/scorecard.json +41 -0
  97. package/skills/coverage.json +83 -0
  98. package/skills/dev/SKILL.md +4 -0
  99. package/skills/dev/evals/scorecard.json +41 -0
  100. package/skills/gates/SKILL.md +80 -0
  101. package/skills/gates/evals/evals.json +38 -0
  102. package/skills/gates/evals/scorecard.json +41 -0
  103. package/skills/hermes-forge/SKILL.md +1 -0
  104. package/skills/hermes-forge/evals/scorecard.json +41 -0
  105. package/skills/issue-basics/SKILL.md +1 -0
  106. package/skills/issue-basics/evals/scorecard.json +41 -0
  107. package/skills/kernel/SKILL.md +38 -0
  108. package/skills/kernel/evals/scorecard.json +41 -0
  109. package/skills/memory/SKILL.md +16 -1
  110. package/skills/memory/evals/scorecard.json +41 -0
  111. package/skills/parallel-deep-research/SKILL.md +1 -0
  112. package/skills/parallel-deep-research/evals/scorecard.json +41 -0
  113. package/skills/plan/SKILL.md +6 -0
  114. package/skills/plan/evals/scorecard.json +41 -0
  115. package/skills/portability/SKILL.md +47 -0
  116. package/skills/portability/evals/evals.json +34 -0
  117. package/skills/portability/evals/scorecard.json +41 -0
  118. package/skills/research/SKILL.md +1 -0
  119. package/skills/research/evals/scorecard.json +41 -0
  120. package/skills/review/SKILL.md +6 -0
  121. package/skills/review/evals/scorecard.json +41 -0
  122. package/skills/rollback/SKILL.md +1 -0
  123. package/skills/rollback/evals/scorecard.json +41 -0
  124. package/skills/setup/SKILL.md +91 -0
  125. package/skills/setup/evals/evals.json +42 -0
  126. package/skills/setup/evals/scorecard.json +41 -0
  127. package/skills/shepherd/SKILL.md +76 -36
  128. package/skills/shepherd/evals/evals.json +21 -9
  129. package/skills/shepherd/evals/scorecard.json +41 -0
  130. package/skills/ship/SKILL.md +6 -0
  131. package/skills/ship/evals/scorecard.json +41 -0
  132. package/skills/smith/SKILL.md +8 -0
  133. package/skills/smith/evals/scorecard.json +41 -0
  134. package/skills/sonarcloud/SKILL.md +1 -0
  135. package/skills/sonarcloud/evals/scorecard.json +41 -0
  136. package/skills/sonarcloud-analysis/SKILL.md +1 -0
  137. package/skills/sonarcloud-analysis/evals/scorecard.json +41 -0
  138. package/skills/status/SKILL.md +3 -0
  139. package/skills/status/evals/scorecard.json +41 -0
  140. package/skills/triage-ready/SKILL.md +2 -0
  141. package/skills/triage-ready/evals/scorecard.json +41 -0
  142. package/skills/using-forge/SKILL.md +104 -0
  143. package/skills/using-forge/evals/scorecard.json +41 -0
  144. package/skills/validate/SKILL.md +4 -0
  145. package/skills/validate/evals/scorecard.json +41 -0
  146. package/skills/verify/SKILL.md +4 -0
  147. package/skills/verify/evals/scorecard.json +41 -0
  148. package/skills/worktree/SKILL.md +87 -0
  149. package/skills/worktree/evals/evals.json +38 -0
  150. package/skills/worktree/evals/scorecard.json +41 -0
  151. package/lib/adapters/beads-issue-adapter.js +0 -127
  152. package/lib/commands/board.js +0 -64
  153. package/lib/status/beads-snapshot.js +0 -145
  154. package/scripts/forge-team/lib/dashboard.sh +0 -316
  155. package/scripts/forge-team/tests/dashboard.test.sh +0 -155
@@ -161,6 +161,55 @@ const PLAN_SUBSKILL_DEFINITIONS = [
161
161
 
162
162
  const PLAN_SUBSKILL_IDS = new Set(PLAN_SUBSKILL_DEFINITIONS.map(definition => definition.id));
163
163
 
164
+ // The `smith` orchestrator composes the stage skills end-to-end. Its sub-skills
165
+ // are the stage skill NAMES (each a real `skills/<name>/` dir), unlike plan's
166
+ // fine-grained internal planning phases. Both resolve through the ONE generic
167
+ // registry below (keyed by owning skill), so composition is no longer hardcoded
168
+ // to plan. See docs/work kernel-native-skills composition epic (a0776e61) +
169
+ // smith-orchestrator (7da81cbd).
170
+ const SMITH_SUBSKILL_DEFINITIONS = ['plan', 'dev', 'validate', 'ship', 'review', 'verify']
171
+ .map(stage => ({ id: stage, label: `${stage} stage`, owner: 'smith' }));
172
+
173
+ // Skills that `plan` COMPOSES at the whole-skill level (its SKILL.md `subskills:`
174
+ // frontmatter), distinct from the fine-grained plan.* planning micro-phases in
175
+ // PLAN_SUBSKILL_DEFINITIONS. A composed whole-skill is an advertised sub-skill
176
+ // that must both VALIDATE and RESOLVE, but it is NOT a partialInvocation
177
+ // micro-phase (it maps to no runtime-graph action), so it lives OUTSIDE
178
+ // PLAN_SUBSKILL_IDS (which governs planning.template.only/skip).
179
+ const PLAN_COMPOSED_SUBSKILLS = ['research'];
180
+ const PLAN_COMPOSED_SUBSKILL_DEFINITIONS = PLAN_COMPOSED_SUBSKILLS.map(id => ({
181
+ id,
182
+ label: `${id.charAt(0).toUpperCase()}${id.slice(1)} (composed skill)`,
183
+ kind: 'composed-skill',
184
+ owner: 'plan',
185
+ }));
186
+
187
+ // Generic per-skill sub-skill registry, keyed by owning skill id. Generalizes
188
+ // plan's previously hardcoded PLAN_SUBSKILL_DEFINITIONS / validatePlanSubSkillList
189
+ // so any composing skill (plan, smith, ...) declares its sub-skill set through a
190
+ // single mechanism. This registry is the SINGLE source for BOTH validation (ids)
191
+ // and resolution (definitions), so getSubSkillDefinitions and validateSubSkillList
192
+ // can never disagree. plan's set = its planning micro-phases + composed whole-skills.
193
+ const SUBSKILL_REGISTRY = {
194
+ plan: [...PLAN_SUBSKILL_DEFINITIONS, ...PLAN_COMPOSED_SUBSKILL_DEFINITIONS],
195
+ smith: SMITH_SUBSKILL_DEFINITIONS,
196
+ };
197
+
198
+ // Validation id sets DERIVED from the registry definitions — guarantees
199
+ // validate-vs-resolve parity for every owner (a subskill that validates also
200
+ // resolves, and vice versa).
201
+ const SUBSKILL_IDS_BY_OWNER = Object.fromEntries(
202
+ Object.entries(SUBSKILL_REGISTRY).map(([owner, defs]) => [owner, new Set(defs.map(d => d.id))])
203
+ );
204
+
205
+ function getSubSkillDefinitions(owner) {
206
+ return SUBSKILL_REGISTRY[owner] ? [...SUBSKILL_REGISTRY[owner]] : [];
207
+ }
208
+
209
+ function getSubSkillIds(owner) {
210
+ return new Set(SUBSKILL_IDS_BY_OWNER[owner] || []);
211
+ }
212
+
164
213
  function planningSubSkillFromDefinition(definition) {
165
214
  return PlanningSubSkill({
166
215
  id: definition.id,
@@ -412,6 +461,27 @@ const RESOLVED_RUNTIME_GRAPH = {
412
461
  label: 'Issue write verification (check-after-write)',
413
462
  requires: [],
414
463
  }),
464
+ // Grounding gates (epic 6ef96e92, design docs/work/2026-07-16-grounding-
465
+ // enforcement/design.md) — the first gates that DENY (fd4c03b3's first real
466
+ // payment). gate.read_first: acting on an issue requires having read it —
467
+ // consulted fail-closed at the `forge claim` boundary against a
468
+ // `context.loaded` kernel event (lib/grounding/context-events.js), remedy
469
+ // `forge recap <id>`. gate.cite: shipped artifacts must cite their sources —
470
+ // registered here (togglable) but its scanner lands in P3; it denies nothing
471
+ // yet. Both phase-less, default-ON, UNLOCKED, same toggle surface as
472
+ // gate.issue_verify (`forge gate disable gate.read_first`). Master switch is
473
+ // the unlocked rail.grounding below. `requires: []` — the context.loaded
474
+ // event (not evidence) is the exit condition.
475
+ Gate({
476
+ id: 'gate.read_first',
477
+ label: 'Read the issue before acting on it',
478
+ requires: [],
479
+ }),
480
+ Gate({
481
+ id: 'gate.cite',
482
+ label: 'Cite sources in shipped artifacts',
483
+ requires: [],
484
+ }),
415
485
  ],
416
486
  evidence: [
417
487
  Evidence({
@@ -446,12 +516,18 @@ const RESOLVED_RUNTIME_GRAPH = {
446
516
  // may opt out via `forge gate disable rail.kernel_tracking`, consumed by the
447
517
  // resolver's rail-aware gate loop over workflow.gates.<id>.enabled).
448
518
  rails: [
449
- { key: 'tdd_intent', label: 'TDD intent evidence', description: 'Source changes require test intent and TDD evidence.' },
519
+ { key: 'tdd_intent', label: 'TDD intent evidence', description: 'Source changes require test intent and TDD evidence. Strong default (ON), but not a hard floor — disable with `forge gate disable rail.tdd_intent`.', locked: false },
450
520
  { key: 'secret_scan', label: 'Secret scan', description: 'Validation must not knowingly ship secrets.' },
451
521
  { key: 'branch_protection', label: 'Branch protection', description: 'Ship through reviewed branches instead of direct protected-branch edits.' },
452
522
  { key: 'signed_commits', label: 'Signed commits', description: 'Preserve commit provenance requirements where configured.' },
453
523
  { key: 'schema_integrity', label: 'Schema integrity', description: 'Keep runtime graph and config schemas internally consistent.' },
454
524
  { key: 'kernel_tracking', label: 'Kernel issue tracking', description: 'Every issue, idea, bug, and decision discussed is filed to the Forge Kernel.', locked: false },
525
+ { key: 'auto_shepherd', label: 'Auto-start PR shepherd watch', description: 'On `forge ship` success, auto-start the detached, self-stopping `forge shepherd watch <pr>` monitor so a shipped PR is tended without a manual trigger. Default-ON, UNLOCKED — opt out with `forge gate disable rail.auto_shepherd`.', locked: false },
526
+ // grounding — the one-switch master toggle over gate.read_first + gate.cite.
527
+ // UNLOCKED, default-ON (like kernel_tracking): a maintainer may opt out via
528
+ // `forge gate disable rail.grounding`, which the read_first consult treats as
529
+ // "allow, logged". Ingrains "read the documented source before acting; cite it".
530
+ { key: 'grounding', label: 'Documented-grounding enforcement', description: 'Read the issue before acting on it; cite sources in shipped artifacts.', locked: false },
455
531
  ].map(def => Rail({ id: `rail.${def.key}`, ...def })),
456
532
  adapters: [
457
533
  Adapter({
@@ -605,14 +681,18 @@ function applyRailConfig(graph, config, errors) {
605
681
  if (!enabled.valid) {
606
682
  continue;
607
683
  }
608
- if (enabled.value === false) {
684
+ if (enabled.value === false && rail.locked === true) {
685
+ // Locked L1 rails remain a non-negotiable floor; only unlocked rails
686
+ // (e.g. tdd_intent, kernel_tracking) may be disabled via config.
609
687
  errors.push({
610
688
  code: 'LOCKED_L1_RAIL_DISABLED',
611
689
  message: `Cannot disable locked L1 rail '${key}'.`,
612
690
  });
613
691
  continue;
614
692
  }
615
- if (options && Object.keys(options).length > 0) {
693
+ if (enabled.present) {
694
+ markConfigured(rail, { enabled: enabled.value });
695
+ } else if (options && Object.keys(options).length > 0) {
616
696
  rail.configSource = CONFIG_SOURCE;
617
697
  }
618
698
  }
@@ -746,31 +826,51 @@ function assertStringList(value, errors, code, message) {
746
826
  return valid;
747
827
  }
748
828
 
749
- function validatePlanSubSkillList(value, errors, path) {
750
- if (!assertStringList(
751
- value,
752
- errors,
753
- 'INVALID_PLAN_SUBSKILL_LIST',
754
- `${path} must be a list of planning sub-skill IDs.`
755
- )) {
829
+ function subSkillCodesForOwner(owner) {
830
+ return owner === 'plan'
831
+ ? { listCode: 'INVALID_PLAN_SUBSKILL_LIST', unknownCode: 'UNKNOWN_PLAN_SUBSKILL', noun: 'planning sub-skill' }
832
+ : { listCode: 'INVALID_SUBSKILL_LIST', unknownCode: 'UNKNOWN_SUBSKILL', noun: `${owner} sub-skill` };
833
+ }
834
+
835
+ // Core list validator against an explicit known-id Set. The two callers below
836
+ // deliberately pass DIFFERENT id sets so the two contracts never leak into each
837
+ // other (see validatePlanSubSkillList).
838
+ function validateAgainstKnownIds(knownIds, codes, value, errors, path) {
839
+ const { listCode, unknownCode, noun } = codes;
840
+ if (!assertStringList(value, errors, listCode, `${path} must be a list of ${noun} IDs.`)) {
756
841
  return undefined;
757
842
  }
758
843
 
759
844
  const normalized = value.map(item => item.trim());
760
845
  let hasUnknown = false;
761
846
  for (const id of normalized) {
762
- if (!PLAN_SUBSKILL_IDS.has(id)) {
847
+ if (!knownIds.has(id)) {
763
848
  hasUnknown = true;
764
849
  errors.push({
765
- code: 'UNKNOWN_PLAN_SUBSKILL',
766
- message: `Unknown planning sub-skill '${id}' in ${CONFIG_SOURCE}.`,
850
+ code: unknownCode,
851
+ message: `Unknown ${noun} '${id}' in ${CONFIG_SOURCE}.`,
767
852
  });
768
853
  }
769
854
  }
770
- if (hasUnknown) {
771
- return undefined;
772
- }
773
- return normalized;
855
+ return hasUnknown ? undefined : normalized;
856
+ }
857
+
858
+ // Generic COMPOSITION validator, keyed by owning skill — validates a skill's
859
+ // ADVERTISED (SKILL.md frontmatter) subskills. For `plan` the known-id set is the
860
+ // composition union (plan.* micro-phases + composed whole-skills like research).
861
+ function validateSubSkillList(owner, value, errors, path) {
862
+ const knownIds = SUBSKILL_IDS_BY_OWNER[owner] || new Set();
863
+ return validateAgainstKnownIds(knownIds, subSkillCodesForOwner(owner), value, errors, path);
864
+ }
865
+
866
+ // partialInvocation validator (.forge/config.yaml planning.template.only/skip).
867
+ // SEPARATE contract from composition: it MUST accept ONLY the fine-grained plan.*
868
+ // MICRO-PHASE ids (PLAN_SUBSKILL_IDS), never composed whole-skill ids like
869
+ // `research` — a composed skill maps to no runtime-graph action, so `only:
870
+ // [research]` would be silent dead config. Validates against PLAN_SUBSKILL_IDS,
871
+ // NOT the composition union, so it fails closed on composed ids.
872
+ function validatePlanSubSkillList(value, errors, path) {
873
+ return validateAgainstKnownIds(PLAN_SUBSKILL_IDS, subSkillCodesForOwner('plan'), value, errors, path);
774
874
  }
775
875
 
776
876
  function applyPlanningMode(template, nextTemplate, errors) {
@@ -937,6 +1037,11 @@ module.exports = {
937
1037
  PlanningSubSkill,
938
1038
  Role,
939
1039
  ROLE_IDS,
1040
+ SUBSKILL_REGISTRY,
1041
+ getSubSkillDefinitions,
1042
+ getSubSkillIds,
1043
+ validateSubSkillList,
1044
+ validatePlanSubSkillList,
940
1045
  loadRuntimeGraphConfig,
941
1046
  lintRuntimeGraphConfig,
942
1047
  resolveRuntimeGraph,