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.
- package/.forge/hooks/check-tdd.js +79 -5
- package/.forge/hooks/forge-native-hook.js +194 -8
- package/AGENTS.md +2 -1
- package/CHANGELOG.md +28 -0
- package/QUICKSTART.md +6 -2
- package/README.md +3 -1
- package/bin/forge-cmd.js +1 -1
- package/bin/forge.js +95 -19
- package/docs/guides/SETUP.md +4 -1
- package/docs/guides/SUPPORT.md +5 -0
- package/docs/reference/COMMANDS.md +12 -1
- package/docs/reference/shepherd.md +114 -3
- package/lib/activation/ensure-forge-home.js +135 -0
- package/lib/adapters/beads-kernel-compat.js +67 -0
- package/lib/adapters/greptile-review-adapter.js +1 -1
- package/lib/adapters/pr-state-adapter.js +103 -8
- package/lib/adoption-profiles.js +17 -4
- package/lib/agents-config.js +5 -0
- package/lib/beads-detect.js +60 -0
- package/lib/commands/_aliases.js +248 -0
- package/lib/commands/_issue.js +65 -41
- package/lib/commands/_manifest.js +3 -1
- package/lib/commands/_registry.js +14 -0
- package/lib/commands/_resolve-command-opts.js +36 -60
- package/lib/commands/claim.js +2 -4
- package/lib/commands/gate.js +19 -2
- package/lib/commands/hooks.js +294 -12
- package/lib/commands/init.js +26 -20
- package/lib/commands/memory.js +81 -0
- package/lib/commands/migrate.js +0 -161
- package/lib/commands/plan.js +71 -123
- package/lib/commands/pr.js +88 -0
- package/lib/commands/prime.js +8 -1
- package/lib/commands/push.js +66 -0
- package/lib/commands/recall.js +67 -12
- package/lib/commands/recap.js +18 -4
- package/lib/commands/release.js +15 -3
- package/lib/commands/remember.js +86 -20
- package/lib/commands/serve.js +5 -2
- package/lib/commands/setup.js +135 -73
- package/lib/commands/shepherd.js +102 -4
- package/lib/commands/ship.js +40 -4
- package/lib/commands/skill.js +275 -0
- package/lib/commands/status.js +37 -32
- package/lib/commands/test.js +32 -0
- package/lib/commands/worktree.js +87 -5
- package/lib/core/runtime-graph.js +122 -17
- package/lib/forge-issues.js +13 -464
- package/lib/gate-events.js +54 -55
- package/lib/global-flags.js +30 -0
- package/lib/grounding/context-events.js +230 -0
- package/lib/grounding/read-first.js +112 -0
- package/lib/harness-capability-matrix.js +2 -2
- package/lib/hook-renderer.js +144 -12
- package/lib/issue-backend.js +42 -3
- package/lib/kernel/backing-issue.js +7 -1
- package/lib/kernel/broker.js +28 -0
- package/lib/kernel/migrations.js +30 -2
- package/lib/kernel/owned-kernel.js +43 -0
- package/lib/kernel/schema.js +35 -0
- package/lib/kernel/sqlite-driver.js +187 -1
- package/lib/memory-recall.js +151 -0
- package/lib/orientation.js +303 -6
- package/lib/pr-bundle.js +6 -2
- package/lib/pr-monitor/auto-actions.js +175 -0
- package/lib/pr-monitor/digest.js +206 -0
- package/lib/pr-monitor/reconcile-executor.js +659 -0
- package/lib/pr-monitor/reconcile-tick.js +138 -0
- package/lib/pr-monitor/reconcile.js +0 -0
- package/lib/pr-monitor/render-sticky.js +57 -8
- package/lib/pr-monitor/shepherd-lease.js +243 -0
- package/lib/pr-monitor/upsert-sticky.js +169 -0
- package/lib/pr-monitor/watch-lifecycle.js +1 -1
- package/lib/pr-pull.js +113 -17
- package/lib/project-memory.js +8 -0
- package/lib/release-readiness.js +17 -1
- package/lib/rules-sync.js +4 -0
- package/lib/runtime-health.js +15 -46
- package/lib/skill-eval.js +750 -0
- package/lib/status/identity.js +46 -0
- package/lib/status/presenter.js +0 -35
- package/lib/status/snapshot.js +11 -16
- package/lib/upgrade-safety.js +52 -1
- package/lib/using-forge.js +315 -0
- package/lib/workflow/enforce-stage.js +64 -7
- package/lib/workflow/state-manager.js +23 -23
- package/package.json +2 -2
- package/rules/using-forge.md +24 -0
- package/scripts/forge-team/index.sh +0 -5
- package/scripts/forge-team/tests/dispatcher.test.sh +1 -1
- package/scripts/forge-team/tests/workflow-integration.test.sh +0 -1
- package/scripts/pr-auto-actions.js +93 -0
- package/scripts/pr-verdict-label.js +50 -0
- package/scripts/test.js +8 -1
- package/skills/claim-safety/SKILL.md +4 -0
- package/skills/claim-safety/evals/scorecard.json +41 -0
- package/skills/coverage.json +83 -0
- package/skills/dev/SKILL.md +4 -0
- package/skills/dev/evals/scorecard.json +41 -0
- package/skills/gates/SKILL.md +80 -0
- package/skills/gates/evals/evals.json +38 -0
- package/skills/gates/evals/scorecard.json +41 -0
- package/skills/hermes-forge/SKILL.md +1 -0
- package/skills/hermes-forge/evals/scorecard.json +41 -0
- package/skills/issue-basics/SKILL.md +1 -0
- package/skills/issue-basics/evals/scorecard.json +41 -0
- package/skills/kernel/SKILL.md +38 -0
- package/skills/kernel/evals/scorecard.json +41 -0
- package/skills/memory/SKILL.md +16 -1
- package/skills/memory/evals/scorecard.json +41 -0
- package/skills/parallel-deep-research/SKILL.md +1 -0
- package/skills/parallel-deep-research/evals/scorecard.json +41 -0
- package/skills/plan/SKILL.md +6 -0
- package/skills/plan/evals/scorecard.json +41 -0
- package/skills/portability/SKILL.md +47 -0
- package/skills/portability/evals/evals.json +34 -0
- package/skills/portability/evals/scorecard.json +41 -0
- package/skills/research/SKILL.md +1 -0
- package/skills/research/evals/scorecard.json +41 -0
- package/skills/review/SKILL.md +6 -0
- package/skills/review/evals/scorecard.json +41 -0
- package/skills/rollback/SKILL.md +1 -0
- package/skills/rollback/evals/scorecard.json +41 -0
- package/skills/setup/SKILL.md +91 -0
- package/skills/setup/evals/evals.json +42 -0
- package/skills/setup/evals/scorecard.json +41 -0
- package/skills/shepherd/SKILL.md +76 -36
- package/skills/shepherd/evals/evals.json +21 -9
- package/skills/shepherd/evals/scorecard.json +41 -0
- package/skills/ship/SKILL.md +6 -0
- package/skills/ship/evals/scorecard.json +41 -0
- package/skills/smith/SKILL.md +8 -0
- package/skills/smith/evals/scorecard.json +41 -0
- package/skills/sonarcloud/SKILL.md +1 -0
- package/skills/sonarcloud/evals/scorecard.json +41 -0
- package/skills/sonarcloud-analysis/SKILL.md +1 -0
- package/skills/sonarcloud-analysis/evals/scorecard.json +41 -0
- package/skills/status/SKILL.md +3 -0
- package/skills/status/evals/scorecard.json +41 -0
- package/skills/triage-ready/SKILL.md +2 -0
- package/skills/triage-ready/evals/scorecard.json +41 -0
- package/skills/using-forge/SKILL.md +104 -0
- package/skills/using-forge/evals/scorecard.json +41 -0
- package/skills/validate/SKILL.md +4 -0
- package/skills/validate/evals/scorecard.json +41 -0
- package/skills/verify/SKILL.md +4 -0
- package/skills/verify/evals/scorecard.json +41 -0
- package/skills/worktree/SKILL.md +87 -0
- package/skills/worktree/evals/evals.json +38 -0
- package/skills/worktree/evals/scorecard.json +41 -0
- package/lib/adapters/beads-issue-adapter.js +0 -127
- package/lib/commands/board.js +0 -64
- package/lib/status/beads-snapshot.js +0 -145
- package/scripts/forge-team/lib/dashboard.sh +0 -316
- package/scripts/forge-team/tests/dashboard.test.sh +0 -155
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PR-shepherd digest — the thin CONSUMER half of the constant monitor (epic
|
|
5
|
+
* c2d398e5, 33e1bbd3). The constant watch loop is the PRODUCER: it writes the
|
|
6
|
+
* per-PR NDJSON journals under `.forge/pr-monitor/<repo>-<pr>/` (the `forge
|
|
7
|
+
* shepherd events` pull surface reads those same records back). Nothing, though,
|
|
8
|
+
* surfaced those events to a working agent. This module is a pure READER: it
|
|
9
|
+
* reads the NEW budget events across all PR journals since a persisted per-PR
|
|
10
|
+
* CONSUMER cursor, renders a COMPACT capped summary, and advances the cursor —
|
|
11
|
+
* the exact payload a harness hook (Claude UserPromptSubmit) injects each turn.
|
|
12
|
+
*
|
|
13
|
+
* The CORE (events/journal/watch/monitor) is untouched: this only READS the
|
|
14
|
+
* journal via `journal.readEventsSince` and keeps its OWN `consumer.cursor`
|
|
15
|
+
* (distinct from the watcher's snapshot), so consumption never disturbs
|
|
16
|
+
* production. Every function is fail-open — a bad journal degrades to skipped,
|
|
17
|
+
* never throws — because it feeds a hook that must never block a prompt.
|
|
18
|
+
*
|
|
19
|
+
* @module pr-monitor/digest
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('node:fs');
|
|
23
|
+
const path = require('node:path');
|
|
24
|
+
|
|
25
|
+
const journalMod = require('./journal');
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The event types worth surfacing on each turn — the ACTIONABLE transitions
|
|
29
|
+
* (verdict flip, a failed check, a new review thread, terminal merge/close).
|
|
30
|
+
* Everything else (head pushes, green checks, degraded notices) stays in the
|
|
31
|
+
* journal for `forge shepherd events` but is NOT pushed, to keep the injected
|
|
32
|
+
* context tiny.
|
|
33
|
+
*/
|
|
34
|
+
const BUDGET_TYPES = Object.freeze(new Set([
|
|
35
|
+
'verdict.changed', 'check.failed', 'thread.opened', 'pr.merged', 'pr.closed',
|
|
36
|
+
]));
|
|
37
|
+
|
|
38
|
+
const DEFAULT_CAP = 8;
|
|
39
|
+
const CONSUMER_CURSOR_FILE = 'consumer.cursor';
|
|
40
|
+
|
|
41
|
+
/** Absolute `.forge/pr-monitor` root for a project. */
|
|
42
|
+
function monitorRoot(root) {
|
|
43
|
+
return path.join(root, '.forge', 'pr-monitor');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The per-PR consumer cursor path (distinct from the watcher's snapshot/pid). */
|
|
47
|
+
function cursorPath(dir) {
|
|
48
|
+
return path.join(dir, CONSUMER_CURSOR_FILE);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* List absolute PR journal dirs (those containing an events.ndjson). Fail-open:
|
|
53
|
+
* a missing monitor root or unreadable dir yields []. Injectable fs for tests.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} root
|
|
56
|
+
* @param {{ readdirSync?: Function, existsSync?: Function }} [deps]
|
|
57
|
+
* @returns {string[]}
|
|
58
|
+
*/
|
|
59
|
+
function discoverPrDirs(root, deps = {}) {
|
|
60
|
+
const readdir = deps.readdirSync || fs.readdirSync;
|
|
61
|
+
const exists = deps.existsSync || fs.existsSync;
|
|
62
|
+
try {
|
|
63
|
+
return readdir(monitorRoot(root), { withFileTypes: true })
|
|
64
|
+
.filter((e) => e.isDirectory())
|
|
65
|
+
.map((e) => path.join(monitorRoot(root), e.name))
|
|
66
|
+
.filter((dir) => exists(path.join(dir, 'events.ndjson')));
|
|
67
|
+
} catch {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Read a PR's consumer cursor (the last consumed seq). Fail-open → 0 (no cursor,
|
|
74
|
+
* unreadable, or malformed all mean "start from the beginning").
|
|
75
|
+
*
|
|
76
|
+
* @param {string} dir
|
|
77
|
+
* @param {{ readFileSync?: Function }} [deps]
|
|
78
|
+
* @returns {number}
|
|
79
|
+
*/
|
|
80
|
+
function readConsumerCursor(dir, deps = {}) {
|
|
81
|
+
const readFile = deps.readFileSync || fs.readFileSync;
|
|
82
|
+
try {
|
|
83
|
+
const obj = JSON.parse(readFile(cursorPath(dir), 'utf8'));
|
|
84
|
+
const seq = Number(obj && obj.seq);
|
|
85
|
+
return Number.isFinite(seq) && seq >= 0 ? seq : 0;
|
|
86
|
+
} catch {
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Persist a PR's consumer cursor. Fail-open: an unwritable cursor returns false
|
|
93
|
+
* (next turn re-reads the same events — a duplicate nudge, never a crash).
|
|
94
|
+
*
|
|
95
|
+
* @param {string} dir
|
|
96
|
+
* @param {number} seq
|
|
97
|
+
* @param {{ writeFileSync?: Function }} [deps]
|
|
98
|
+
* @returns {boolean}
|
|
99
|
+
*/
|
|
100
|
+
function writeConsumerCursor(dir, seq, deps = {}) {
|
|
101
|
+
const writeFile = deps.writeFileSync || fs.writeFileSync;
|
|
102
|
+
try {
|
|
103
|
+
writeFile(cursorPath(dir), `${JSON.stringify({ seq: Number(seq) || 0 })}\n`);
|
|
104
|
+
return true;
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* A compact one-line label for a budget event. PURE. Bounded so injected context
|
|
112
|
+
* stays tiny; the full record is always available via `forge shepherd events`.
|
|
113
|
+
*
|
|
114
|
+
* @param {object} e - a journal event.
|
|
115
|
+
* @returns {string}
|
|
116
|
+
*/
|
|
117
|
+
function renderEventLine(e) {
|
|
118
|
+
const pr = e.pr != null ? `#${e.pr}` : '#?';
|
|
119
|
+
const d = e.data || {};
|
|
120
|
+
let detail;
|
|
121
|
+
switch (e.type) {
|
|
122
|
+
case 'verdict.changed': detail = (e.verdict && (e.verdict.verdict || e.verdict.state)) || d.verdict || d.to || ''; break;
|
|
123
|
+
case 'check.failed': detail = d.name || d.check || ''; break;
|
|
124
|
+
case 'thread.opened': detail = d.author ? `by ${d.author}` : (d.path || ''); break;
|
|
125
|
+
case 'pr.merged': detail = 'merged'; break;
|
|
126
|
+
case 'pr.closed': detail = 'closed'; break;
|
|
127
|
+
default: detail = '';
|
|
128
|
+
}
|
|
129
|
+
const suffix = detail ? `: ${String(detail).slice(0, 60)}` : '';
|
|
130
|
+
return `- PR ${pr} ${e.type}${suffix}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* PURE: filter events to the budget types, cap the count, and render lines.
|
|
135
|
+
*
|
|
136
|
+
* @param {object[]} events
|
|
137
|
+
* @param {{ cap?: number }} [opts]
|
|
138
|
+
* @returns {{ lines: string[], total: number }}
|
|
139
|
+
*/
|
|
140
|
+
function renderDigestLines(events, { cap = DEFAULT_CAP } = {}) {
|
|
141
|
+
const budget = (Array.isArray(events) ? events : []).filter((e) => e && BUDGET_TYPES.has(e.type));
|
|
142
|
+
const lines = budget.map(renderEventLine);
|
|
143
|
+
return { lines: lines.slice(0, cap), total: lines.length };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Format the compact injected block (a header + capped lines + an overflow
|
|
148
|
+
* pointer), or '' when there is nothing to surface. PURE.
|
|
149
|
+
*/
|
|
150
|
+
function formatBlock(lines, total, cap, prs) {
|
|
151
|
+
if (lines.length === 0) return '';
|
|
152
|
+
const on = prs.length ? ` on PR(s) ${prs.join(', ')}` : '';
|
|
153
|
+
const more = total > cap ? `\n(+${total - cap} more — see \`forge shepherd events <pr> --since <seq>\`)` : '';
|
|
154
|
+
return `[forge PR shepherd] ${total} new event(s)${on}:\n${lines.join('\n')}${more}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Collect a compact digest of NEW budget events across every PR journal since the
|
|
159
|
+
* per-PR consumer cursor, advancing each cursor past EVERYTHING read (budget and
|
|
160
|
+
* non-budget alike, so skipped types never re-surface). Fail-open throughout.
|
|
161
|
+
*
|
|
162
|
+
* @param {object} args
|
|
163
|
+
* @param {string} args.root - project root.
|
|
164
|
+
* @param {object} [args.journal] - journal module (test injection).
|
|
165
|
+
* @param {number} [args.cap] - max lines in the block.
|
|
166
|
+
* @param {object} [args.fsDeps] - injectable fs for discovery + cursor I/O.
|
|
167
|
+
* @returns {{ text: string, total: number, prs: string[] }}
|
|
168
|
+
*/
|
|
169
|
+
function collectDigest({ root, journal = journalMod, cap = DEFAULT_CAP, fsDeps = {} } = {}) {
|
|
170
|
+
const dirs = discoverPrDirs(root, fsDeps);
|
|
171
|
+
const allLines = [];
|
|
172
|
+
const prs = new Set();
|
|
173
|
+
for (const dir of dirs) {
|
|
174
|
+
const cursor = readConsumerCursor(dir, fsDeps);
|
|
175
|
+
let evs;
|
|
176
|
+
try {
|
|
177
|
+
evs = journal.readEventsSince(dir, cursor);
|
|
178
|
+
} catch {
|
|
179
|
+
evs = [];
|
|
180
|
+
}
|
|
181
|
+
if (!Array.isArray(evs) || evs.length === 0) continue;
|
|
182
|
+
const maxSeq = evs.reduce((m, e) => Math.max(m, Number(e.seq) || 0), cursor);
|
|
183
|
+
for (const e of evs) {
|
|
184
|
+
if (!e || !BUDGET_TYPES.has(e.type)) continue;
|
|
185
|
+
allLines.push(renderEventLine(e));
|
|
186
|
+
if (e.pr != null) prs.add(String(e.pr));
|
|
187
|
+
}
|
|
188
|
+
writeConsumerCursor(dir, maxSeq, fsDeps);
|
|
189
|
+
}
|
|
190
|
+
const capped = allLines.slice(0, cap);
|
|
191
|
+
return { text: formatBlock(capped, allLines.length, cap, [...prs]), total: allLines.length, prs: [...prs] };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = {
|
|
195
|
+
BUDGET_TYPES,
|
|
196
|
+
DEFAULT_CAP,
|
|
197
|
+
monitorRoot,
|
|
198
|
+
cursorPath,
|
|
199
|
+
discoverPrDirs,
|
|
200
|
+
readConsumerCursor,
|
|
201
|
+
writeConsumerCursor,
|
|
202
|
+
renderEventLine,
|
|
203
|
+
renderDigestLines,
|
|
204
|
+
formatBlock,
|
|
205
|
+
collectDigest,
|
|
206
|
+
};
|