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.
- package/AGENTS.md +18 -7
- package/CHANGELOG.md +79 -1
- package/CLAUDE.md +0 -12
- package/CODING_STANDARDS.md +72 -0
- package/README.md +6 -2
- package/bin/forge-cmd.js +20 -0
- package/bin/forge.js +28 -375
- package/docs/INDEX.md +1 -1
- package/docs/guides/BEADS_GITHUB_SYNC.md +2 -31
- package/docs/guides/MIGRATION.md +4 -4
- package/docs/guides/SETUP.md +16 -16
- package/docs/reference/COMMANDS.md +8 -5
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +4 -0
- package/docs/reference/INSIGHTS_RECAP.md +9 -20
- package/docs/reference/INSTALL.md +4 -0
- package/docs/reference/LEGACY_CLAIM_REPAIR.md +112 -0
- package/docs/reference/RELEASE.md +5 -3
- package/docs/reference/TOOLCHAIN.md +8 -0
- package/docs/reference/github-accounts.md +134 -0
- package/docs/reference/protected-state-surfaces.md +4 -4
- package/docs/reference/shepherd.md +114 -35
- package/lefthook.yml +12 -0
- package/lib/activation/ensure-forge-home.js +33 -15
- package/lib/adapters/pr-state-adapter.js +359 -144
- package/lib/audit-evidence.js +71 -110
- package/lib/base-remote.js +138 -0
- package/lib/beta5-compatibility-evidence.js +1093 -0
- package/lib/bun-lockfile-proof.js +413 -0
- package/lib/bun-workflow-pins.js +461 -0
- package/lib/capabilities/index.js +9 -0
- package/lib/capabilities/model.js +141 -0
- package/lib/capabilities/probes.js +347 -0
- package/lib/capped-jsonl-log.js +236 -0
- package/lib/codex-skills.js +2 -2
- package/lib/commands/_manifest.js +1 -0
- package/lib/commands/_registry.js +50 -20
- package/lib/commands/clean.js +252 -32
- package/lib/commands/dev.js +4 -33
- package/lib/commands/doctor.js +37 -6
- package/lib/commands/gate.js +197 -27
- package/lib/commands/github.js +215 -0
- package/lib/commands/hooks.js +276 -30
- package/lib/commands/insights.js +8 -3
- package/lib/commands/memory.js +66 -2
- package/lib/commands/merge.js +1265 -58
- package/lib/commands/plan.js +33 -2
- package/lib/commands/pr.js +3 -1
- package/lib/commands/preflight.js +21 -4
- package/lib/commands/prime.js +21 -8
- package/lib/commands/push.js +146 -54
- package/lib/commands/recall.js +127 -49
- package/lib/commands/recap.js +6 -1
- package/lib/commands/release.js +39 -3
- package/lib/commands/remember.js +28 -4
- package/lib/commands/serve.js +26 -9
- package/lib/commands/setup.js +323 -98
- package/lib/commands/shepherd.js +591 -73
- package/lib/commands/ship.js +36 -91
- package/lib/commands/skill.js +127 -11
- package/lib/commands/status.js +17 -1
- package/lib/commands/team.js +47 -8
- package/lib/commands/test.js +187 -38
- package/lib/commands/validate.js +65 -21
- package/lib/commands/worktree.js +359 -45
- package/lib/core/runtime-graph.js +1 -1
- package/lib/doc-assertions.js +297 -0
- package/lib/existing-tdd-gate.js +253 -0
- package/lib/fixtures/beta5-corpus/v1/README.md +9 -0
- package/lib/fixtures/beta5-corpus/v1/contract/command-contract.json +26 -0
- package/lib/fixtures/beta5-corpus/v1/contract/package-contract.json +13 -0
- package/lib/fixtures/beta5-corpus/v1/contract/workflow-stage-matrix.json +8 -0
- package/lib/fixtures/beta5-corpus/v1/manifest.json +25 -0
- package/lib/fixtures/beta5-corpus/v1/state/comments.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/config.yaml +6 -0
- package/lib/fixtures/beta5-corpus/v1/state/dependencies.jsonl +1 -0
- package/lib/fixtures/beta5-corpus/v1/state/issues.jsonl +2 -0
- package/lib/fixtures/beta5-corpus/v1/state/kernel.sql +20 -0
- package/lib/forge-context.js +1 -4
- package/lib/forge-issues.js +134 -32
- package/lib/gate-events.js +98 -10
- package/lib/git-defaults.js +56 -0
- package/lib/github-context.js +308 -0
- package/lib/global-flags.js +1 -0
- package/lib/harness-capability-matrix.js +3 -3
- package/lib/hook-renderer.js +122 -5
- package/lib/insights.js +96 -80
- package/lib/issue-render.js +19 -0
- package/lib/kernel/backing-issue.js +14 -2
- package/lib/kernel/broker.js +739 -31
- package/lib/kernel/claim-reconciler.js +238 -0
- package/lib/kernel/cli-broker-factory.js +12 -1
- package/lib/kernel/close-on-merge.js +154 -0
- package/lib/kernel/fs-class.js +42 -25
- package/lib/kernel/lease-enforcer.js +9 -4
- package/lib/kernel/legacy-claim-repair.js +442 -0
- package/lib/kernel/live-claim-projection.js +26 -0
- package/lib/kernel/migrations.js +118 -3
- package/lib/kernel/readiness-model.js +184 -12
- package/lib/kernel/schema.js +49 -1
- package/lib/kernel/sqlite-driver.js +3435 -172
- package/lib/kernel/taxonomy-validator.js +4 -1
- package/lib/kernel/windows-private-acl.js +239 -0
- package/lib/lefthook-wiring.js +21 -1
- package/lib/memory/hygiene.js +191 -0
- package/lib/memory/router.js +110 -28
- package/lib/memory/usage-evidence.js +4 -0
- package/lib/memory-digest.js +106 -15
- package/lib/memory-recall-events.js +145 -0
- package/lib/memory-recall.js +71 -10
- package/lib/merge-rules.js +143 -21
- package/lib/npm-publish-workflow.js +465 -0
- package/lib/orientation.js +68 -43
- package/lib/package-root.js +2 -0
- package/lib/plugin-catalog.js +14 -4
- package/lib/pr-bundle.js +5 -6
- package/lib/pr-monitor/auto-actions.js +169 -28
- package/lib/pr-monitor/differ.js +110 -4
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/flow-monitor.js +1424 -0
- package/lib/pr-monitor/gather.js +251 -44
- package/lib/pr-monitor/journal.js +18 -39
- package/lib/pr-monitor/monitor.js +117 -10
- package/lib/pr-monitor/process-identity.js +117 -0
- package/lib/pr-monitor/reconcile-executor.js +1129 -470
- package/lib/pr-monitor/reconcile.js +0 -0
- package/lib/pr-monitor/render-summary.js +293 -0
- package/lib/pr-monitor/review-preflight.js +269 -0
- package/lib/pr-monitor/shepherd-lease.js +38 -20
- package/lib/pr-monitor/verdict.js +438 -0
- package/lib/pr-monitor/watch-lifecycle.js +145 -27
- package/lib/pr-monitor/watch-owner.js +1414 -0
- package/lib/pr-monitor/watch.js +129 -58
- package/lib/pr-pull.js +33 -14
- package/lib/pr-shepherd.js +51 -11
- package/lib/preflight/gates.js +65 -18
- package/lib/preflight/runner.js +5 -0
- package/lib/project-memory.js +178 -4
- package/lib/protected-state-authority.js +1100 -0
- package/lib/protected-state-surfaces.js +243 -45
- package/lib/release-readiness.js +53 -7
- package/lib/review-adapter.js +65 -0
- package/lib/shell-utils.js +1 -1
- package/lib/skills-sync.js +71 -35
- package/lib/smart-merge.js +28 -4
- package/lib/symlink-utils.js +74 -26
- package/lib/upgrade-safety.js +39 -0
- package/lib/using-forge.js +19 -6
- package/lib/validation/risk-manifest.js +339 -0
- package/lib/workflow/enforce-stage.js +44 -0
- package/lib/workflow/plan-authority.js +225 -0
- package/package.json +12 -9
- package/scripts/commitlint.js +13 -15
- package/scripts/doc-asserting-tests.js +158 -0
- package/scripts/generate-risk-manifest.js +91 -0
- package/scripts/github-context-bridge.sh +10 -0
- package/scripts/legacy-claim-repair.js +145 -0
- package/scripts/lib/behavioral-eval-runner.js +310 -0
- package/scripts/lib/behavioral-eval-runtime.js +457 -0
- package/scripts/lib/eval-evidence.js +328 -0
- package/scripts/lib/eval-runner.js +81 -41
- package/scripts/lib/immutable-eval-corpus.js +309 -0
- package/scripts/lib/promotion-evidence-loader.js +94 -0
- package/scripts/lib/promotion-scorecard.js +314 -0
- package/scripts/npm-release-receipt.js +134 -0
- package/scripts/process-tree.js +773 -0
- package/scripts/protected-state-check.js +479 -31
- package/scripts/run-command-eval.js +29 -1
- package/scripts/sync-agent-skills.js +333 -34
- package/scripts/sync-d20-audit.js +172 -0
- package/scripts/test-full-suite.js +935 -37
- package/scripts/test-profile.js +13 -3
- package/scripts/test.js +271 -57
- package/skills/coverage.json +1 -0
- package/skills/review/SKILL.md +6 -11
- package/skills/review/evals/scorecard.json +4 -4
- package/skills/rollback/SKILL.md +4 -11
- package/skills/rollback/evals/scorecard.json +3 -3
- package/skills/setup/SKILL.md +18 -0
- package/skills/setup/evals/scorecard.json +3 -3
- package/skills/shepherd/SKILL.md +39 -16
- package/skills/shepherd/evals/scorecard.json +4 -4
- package/skills/ship/SKILL.md +4 -12
- package/skills/ship/evals/scorecard.json +3 -3
- package/skills/validate/SKILL.md +3 -0
- package/skills/validate/evals/scorecard.json +1 -1
- package/skills/worktree/SKILL.md +6 -1
- package/skills/worktree/evals/scorecard.json +2 -2
- package/lib/beads-setup.js +0 -538
- package/lib/beads-sync-scaffold.js +0 -189
- package/lib/pat-setup.js +0 -207
- package/lib/pr-monitor/render-sticky.js +0 -206
- package/lib/pr-monitor/upsert-sticky.js +0 -169
- package/scripts/beads-context.sh +0 -577
- package/scripts/beads-migrate-to-dolt.sh +0 -7
- package/scripts/beads-upgrade-smoke.sh +0 -284
- package/scripts/lib/beads-migrate-to-dolt.mjs +0 -503
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Doc-assertion index — which test suites assert on which tracked markdown.
|
|
5
|
+
*
|
|
6
|
+
* Several suites read repository markdown and assert on its CONTENT (the README
|
|
7
|
+
* package-size badge, the AGENTS.md convention section, docs/ consistency). A
|
|
8
|
+
* markdown-only PR is classified as "docs" and skips the code matrix, so without
|
|
9
|
+
* this index those suites never run on the PR that breaks them — they fail later,
|
|
10
|
+
* on master, for the next unrelated code PR. That happened twice (kernel issue
|
|
11
|
+
* 63556816): the README size badge (#307/#310) and the AGENTS.md convention test
|
|
12
|
+
* after #325.
|
|
13
|
+
*
|
|
14
|
+
* The mapping is DERIVED by scanning the test sources rather than hardcoded, so a
|
|
15
|
+
* newly added markdown-reading suite is selected automatically instead of drifting
|
|
16
|
+
* out of a stale list.
|
|
17
|
+
*
|
|
18
|
+
* Detection is deliberately anchored, not literal-matching: only markdown reached
|
|
19
|
+
* from the REPOSITORY ROOT counts. Tests that write a throwaway `AGENTS.md` into a
|
|
20
|
+
* temp fixture directory join a tmpdir, not the repo root, so they are not selected.
|
|
21
|
+
*
|
|
22
|
+
* Suites that DISCOVER markdown (directory traversal, a glob, a `.md` filter) name
|
|
23
|
+
* no file to attribute, so they are selected on ANY markdown change instead — see
|
|
24
|
+
* `scansMarkdownDynamically`.
|
|
25
|
+
*
|
|
26
|
+
* @module doc-assertions
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const defaultFs = require('node:fs');
|
|
30
|
+
const path = require('node:path');
|
|
31
|
+
|
|
32
|
+
/** `path.join(...)` / `path.resolve(...)` (or bare `join`/`resolve`) with a flat argument list. */
|
|
33
|
+
const PATH_CALL = /(?:path\s*\.\s*)?\b(?:join|resolve)\s*\(([^()]*)\)/g;
|
|
34
|
+
|
|
35
|
+
/** A `const ROOT = path.resolve(__dirname, '..')`-style anchor declaration. */
|
|
36
|
+
const ANCHOR_DECL = /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:path\s*\.\s*)?(?:join|resolve)\s*\(([^()]*)\)/g;
|
|
37
|
+
|
|
38
|
+
/** A quoted string literal ending in `.md`. */
|
|
39
|
+
const MARKDOWN_LITERAL = /['"`]([^'"`\n]*\.md)['"`]/g;
|
|
40
|
+
|
|
41
|
+
/** A call that enumerates a directory or expands a glob instead of naming files. */
|
|
42
|
+
// `.sync` member forms matter: `glob.sync('**/*.md')` has a `.` where this pattern would
|
|
43
|
+
// otherwise expect `(`, so without the optional `.sync` the scanner is invisible and the
|
|
44
|
+
// suite is under-selected — the exact failure this module exists to prevent.
|
|
45
|
+
const DIRECTORY_SCAN_CALL = /\b(?:readdir|readdirSync|opendir|opendirSync|glob(?:Sync|\.sync)?|globby(?:\.sync)?|fastGlob(?:\.sync)?|walk|walkSync)\s*\(/;
|
|
46
|
+
|
|
47
|
+
/** `.md` used as an EXTENSION test rather than a filename: glob, regex, or bare extension. */
|
|
48
|
+
const MARKDOWN_EXTENSION_FILTER = /\*\.md\b|\\\.md\b|['"`]\.md['"`]/;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Reports whether a repository-relative path is a markdown file.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} file Repository-relative path.
|
|
54
|
+
* @returns {boolean} True for markdown paths.
|
|
55
|
+
*/
|
|
56
|
+
function isMarkdownPath(file) {
|
|
57
|
+
return typeof file === 'string' && file.toLowerCase().endsWith('.md');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Converts an absolute path to a forward-slash repository-relative path. */
|
|
61
|
+
function toRepoRelative(repoRoot, absolutePath) {
|
|
62
|
+
return path.relative(repoRoot, absolutePath).split(path.sep).join('/');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Returns the value of a single-quoted/double-quoted/backtick string literal, or null. */
|
|
66
|
+
function readStringLiteral(token) {
|
|
67
|
+
const match = /^'([^']*)'$|^"([^"]*)"$|^`([^`$]*)`$/.exec(token.trim());
|
|
68
|
+
if (!match) return null;
|
|
69
|
+
return match[1] ?? match[2] ?? match[3];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Splits a flat call-argument list into trimmed, non-empty tokens. */
|
|
73
|
+
function splitArguments(rawArgs) {
|
|
74
|
+
return rawArgs.split(',').map((token) => token.trim()).filter(Boolean);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Collects every path anchor a test file can resolve against, keyed by identifier.
|
|
79
|
+
*
|
|
80
|
+
* Starts from `__dirname` and follows literal-only `path.join`/`path.resolve`
|
|
81
|
+
* declarations (e.g. `const ROOT = path.resolve(__dirname, '..')`).
|
|
82
|
+
*
|
|
83
|
+
* @param {string} source Test file source.
|
|
84
|
+
* @param {string} testDir Absolute directory containing the test file.
|
|
85
|
+
* @returns {Map<string, string>} Identifier → absolute directory.
|
|
86
|
+
*/
|
|
87
|
+
function collectAnchors(source, testDir) {
|
|
88
|
+
const anchors = new Map([['__dirname', testDir]]);
|
|
89
|
+
let match;
|
|
90
|
+
ANCHOR_DECL.lastIndex = 0;
|
|
91
|
+
while ((match = ANCHOR_DECL.exec(source)) !== null) {
|
|
92
|
+
const args = splitArguments(match[2]);
|
|
93
|
+
if (args.length === 0) continue;
|
|
94
|
+
const base = anchors.get(args[0]);
|
|
95
|
+
if (!base) continue;
|
|
96
|
+
const segments = args.slice(1).map(readStringLiteral);
|
|
97
|
+
if (segments.some((segment) => segment === null)) continue;
|
|
98
|
+
anchors.set(match[1], path.resolve(base, ...segments));
|
|
99
|
+
}
|
|
100
|
+
return anchors;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Finds markdown a single test file reads from the repository root.
|
|
105
|
+
*
|
|
106
|
+
* Two shapes are recognized:
|
|
107
|
+
* 1. A fully literal anchored path, e.g. `path.resolve(__dirname, '..', 'AGENTS.md')`.
|
|
108
|
+
* 2. A root-relative read helper, e.g. `const ROOT = join(__dirname, '..')` plus
|
|
109
|
+
* `readFileSync(join(ROOT, relPath))`. Because the path is assembled at run
|
|
110
|
+
* time, every markdown literal in the file that resolves to a real repository
|
|
111
|
+
* file is attributed to that test (this is how `test/docs-consistency.test.js`
|
|
112
|
+
* reads its documents).
|
|
113
|
+
*
|
|
114
|
+
* @param {string} repoRoot Absolute repository root.
|
|
115
|
+
* @param {string} testFile Absolute path to the test file.
|
|
116
|
+
* @param {string} source Test file source.
|
|
117
|
+
* @param {Object} fs Injected fs module.
|
|
118
|
+
* @returns {Set<string>} Repository-relative markdown paths the test asserts on.
|
|
119
|
+
*/
|
|
120
|
+
function findAssertedMarkdown(repoRoot, testFile, source, fs) {
|
|
121
|
+
const anchors = collectAnchors(source, path.dirname(testFile));
|
|
122
|
+
const found = new Set();
|
|
123
|
+
|
|
124
|
+
const addIfTracked = (relativePath) => {
|
|
125
|
+
if (!isMarkdownPath(relativePath)) return;
|
|
126
|
+
if (relativePath.startsWith('..')) return;
|
|
127
|
+
if (!fs.existsSync(path.join(repoRoot, relativePath))) return;
|
|
128
|
+
found.add(relativePath);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
let match;
|
|
132
|
+
PATH_CALL.lastIndex = 0;
|
|
133
|
+
while ((match = PATH_CALL.exec(source)) !== null) {
|
|
134
|
+
const args = splitArguments(match[1]);
|
|
135
|
+
if (args.length === 0) continue;
|
|
136
|
+
const base = anchors.get(args[0]);
|
|
137
|
+
if (!base) continue;
|
|
138
|
+
const segments = args.slice(1).map(readStringLiteral);
|
|
139
|
+
if (segments.some((segment) => segment === null)) continue;
|
|
140
|
+
addIfTracked(toRepoRelative(repoRoot, path.resolve(base, ...segments)));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const rootAnchors = [...anchors]
|
|
144
|
+
.filter(([, directory]) => path.resolve(directory) === path.resolve(repoRoot))
|
|
145
|
+
.map(([identifier]) => identifier);
|
|
146
|
+
const readsRootRelativePaths = rootAnchors.some((identifier) => new RegExp(
|
|
147
|
+
`\\b(?:join|resolve)\\s*\\(\\s*${identifier}\\s*,\\s*[A-Za-z_$]`,
|
|
148
|
+
).test(source));
|
|
149
|
+
|
|
150
|
+
if (readsRootRelativePaths) {
|
|
151
|
+
MARKDOWN_LITERAL.lastIndex = 0;
|
|
152
|
+
while ((match = MARKDOWN_LITERAL.exec(source)) !== null) {
|
|
153
|
+
addIfTracked(match[1].replace(/^\.\//, ''));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return found;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Reports whether a test DISCOVERS markdown instead of naming it.
|
|
162
|
+
*
|
|
163
|
+
* `findAssertedMarkdown` can only attribute markdown that appears as a literal, so
|
|
164
|
+
* a suite that walks a directory (or expands a glob) and filters on `.md` maps to
|
|
165
|
+
* nothing and would never be selected — the false green this module exists to
|
|
166
|
+
* remove (`test/cleanup/dropped-agent-docs.test.js` is exactly that shape).
|
|
167
|
+
*
|
|
168
|
+
* Deliberately biased to OVER-selection: the two signals need not be on the same
|
|
169
|
+
* expression, because a few extra suites cost CI seconds while under-selection is
|
|
170
|
+
* what broke master twice. No attempt is made to resolve traversal roots into a
|
|
171
|
+
* file set — that would silently under-select again.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} source Test file source.
|
|
174
|
+
* @returns {boolean} True when the test reaches markdown it does not name.
|
|
175
|
+
*/
|
|
176
|
+
function scansMarkdownDynamically(source) {
|
|
177
|
+
return DIRECTORY_SCAN_CALL.test(source) && MARKDOWN_EXTENSION_FILTER.test(source);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Recursively lists `*.test.js` files under a directory. */
|
|
181
|
+
function listTestFiles(directory, fs, collected = []) {
|
|
182
|
+
let entries;
|
|
183
|
+
try {
|
|
184
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
185
|
+
} catch (_e) { // NOSONAR S2486
|
|
186
|
+
/* intentional: missing test directory yields no suites */
|
|
187
|
+
return collected;
|
|
188
|
+
}
|
|
189
|
+
for (const entry of entries) {
|
|
190
|
+
const entryPath = path.join(directory, entry.name);
|
|
191
|
+
if (entry.isDirectory()) {
|
|
192
|
+
listTestFiles(entryPath, fs, collected);
|
|
193
|
+
} else if (entry.name.endsWith('.test.js')) {
|
|
194
|
+
collected.push(entryPath);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return collected;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Sorts test paths deterministically. */
|
|
201
|
+
function sortPaths(paths) {
|
|
202
|
+
return [...paths].sort((a, b) => a.localeCompare(b));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Scans the test tree once, splitting suites into the two ways they reach markdown.
|
|
207
|
+
*
|
|
208
|
+
* @param {string} repoRoot Absolute repository root.
|
|
209
|
+
* @param {Object} fs Injected fs module.
|
|
210
|
+
* @returns {{index: Map<string, string[]>, scanners: string[]}} Named-markdown index
|
|
211
|
+
* plus the suites that discover markdown dynamically.
|
|
212
|
+
*/
|
|
213
|
+
function scanTestSuites(repoRoot, fs) {
|
|
214
|
+
const index = new Map();
|
|
215
|
+
const scanners = new Set();
|
|
216
|
+
|
|
217
|
+
for (const testFile of listTestFiles(path.join(repoRoot, 'test'), fs)) {
|
|
218
|
+
let source;
|
|
219
|
+
try {
|
|
220
|
+
source = fs.readFileSync(testFile, 'utf8');
|
|
221
|
+
} catch (_e) { // NOSONAR S2486
|
|
222
|
+
/* intentional: unreadable test file contributes nothing */
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const testPath = toRepoRelative(repoRoot, testFile);
|
|
226
|
+
for (const docPath of findAssertedMarkdown(repoRoot, testFile, source, fs)) {
|
|
227
|
+
if (!index.has(docPath)) index.set(docPath, new Set());
|
|
228
|
+
index.get(docPath).add(testPath);
|
|
229
|
+
}
|
|
230
|
+
if (scansMarkdownDynamically(source)) scanners.add(testPath);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
index: new Map([...index].map(([docPath, suites]) => [docPath, sortPaths(suites)])),
|
|
235
|
+
scanners: sortPaths(scanners),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Builds the markdown → test-suite index for a checkout.
|
|
241
|
+
*
|
|
242
|
+
* Only covers suites that NAME their markdown; suites that discover it are returned
|
|
243
|
+
* by {@link findDynamicMarkdownScanners} because they map to no specific file.
|
|
244
|
+
*
|
|
245
|
+
* @param {string} repoRoot Absolute repository root.
|
|
246
|
+
* @param {Object} [fs=defaultFs] Injected fs module.
|
|
247
|
+
* @returns {Map<string, string[]>} Repository-relative markdown path → sorted test files.
|
|
248
|
+
*/
|
|
249
|
+
function buildDocAssertionIndex(repoRoot, fs = defaultFs) {
|
|
250
|
+
return scanTestSuites(repoRoot, fs).index;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Lists the suites that read markdown they never name (traversal, glob, `.md` filter).
|
|
255
|
+
*
|
|
256
|
+
* @param {string} repoRoot Absolute repository root.
|
|
257
|
+
* @param {Object} [fs=defaultFs] Injected fs module.
|
|
258
|
+
* @returns {string[]} Sorted test file paths.
|
|
259
|
+
*/
|
|
260
|
+
function findDynamicMarkdownScanners(repoRoot, fs = defaultFs) {
|
|
261
|
+
return scanTestSuites(repoRoot, fs).scanners;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Selects the suites that assert on the changed markdown.
|
|
266
|
+
*
|
|
267
|
+
* Returns an empty selection when no markdown changed, so the index is only built
|
|
268
|
+
* for changes that can actually break a doc-asserting suite.
|
|
269
|
+
*
|
|
270
|
+
* @param {string[]} changedFiles Repository-relative changed paths.
|
|
271
|
+
* @param {string} repoRoot Absolute repository root.
|
|
272
|
+
* @param {Object} [fs=defaultFs] Injected fs module.
|
|
273
|
+
* @returns {string[]} Sorted, de-duplicated test file paths.
|
|
274
|
+
*/
|
|
275
|
+
function selectDocAssertingTests(changedFiles, repoRoot, fs = defaultFs) {
|
|
276
|
+
const changedMarkdown = (changedFiles || []).filter(isMarkdownPath);
|
|
277
|
+
if (changedMarkdown.length === 0) return [];
|
|
278
|
+
|
|
279
|
+
const { index, scanners } = scanTestSuites(repoRoot, fs);
|
|
280
|
+
// Scanners discover markdown by traversal, so no changed path maps to them —
|
|
281
|
+
// any markdown change selects all of them.
|
|
282
|
+
const selected = new Set(scanners);
|
|
283
|
+
for (const docPath of changedMarkdown) {
|
|
284
|
+
for (const suite of index.get(docPath) || []) {
|
|
285
|
+
selected.add(suite);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return sortPaths(selected);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
module.exports = {
|
|
292
|
+
buildDocAssertionIndex,
|
|
293
|
+
findDynamicMarkdownScanners,
|
|
294
|
+
isMarkdownPath,
|
|
295
|
+
scansMarkdownDynamically,
|
|
296
|
+
selectDocAssertingTests,
|
|
297
|
+
};
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect a repo's PRE-EXISTING pre-commit TDD / source-test coupling gate, so `forge setup`
|
|
3
|
+
* can DEFER to it instead of stacking Forge's own `rail.tdd_intent` gate on top.
|
|
4
|
+
*
|
|
5
|
+
* The in-the-wild beta.3 adoption report (kernel 5b425a85, predecessor 2699b234): a repo that
|
|
6
|
+
* already ships e.g. `scripts/check-source-test-coupling.mjs` on pre-commit ended up with TWO
|
|
7
|
+
* TDD gates blocking the same commit, with no detection and no reconciliation.
|
|
8
|
+
*
|
|
9
|
+
* Detection is by MECHANISM, not by a filename list: we enumerate the pre-commit COMMANDS that
|
|
10
|
+
* whatever pre-commit runner is actually installed (lefthook / husky / the `pre-commit`
|
|
11
|
+
* framework / a raw `.git/hooks/pre-commit`) will really execute, then classify those command
|
|
12
|
+
* strings. A repo's gate can be named anything, so the mechanism is what we key on.
|
|
13
|
+
*
|
|
14
|
+
* @module lib/existing-tdd-gate
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const fs = require('node:fs');
|
|
20
|
+
const path = require('node:path');
|
|
21
|
+
const YAML = require('yaml');
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
FORGE_NATIVE_HOOK_SENTINEL,
|
|
25
|
+
resolveGitHooksDir,
|
|
26
|
+
} = require('./lefthook-wiring');
|
|
27
|
+
|
|
28
|
+
// A pre-commit command is a TDD/coupling gate when its text carries one of these narrow
|
|
29
|
+
// tokens: a `tdd` word, a source↔test coupling phrase, or an explicit require/check/enforce
|
|
30
|
+
// tests task. Deliberately narrow — a plain test RUNNER (`npm test`) or a formatter is not a
|
|
31
|
+
// TDD-intent gate and must not trigger a deferral.
|
|
32
|
+
const TDD_GATE_PATTERNS = [
|
|
33
|
+
/(^|[^a-z0-9])tdd([^a-z0-9]|$)/i,
|
|
34
|
+
/(source[-_. ]?test|test[-_. ]?source)/i,
|
|
35
|
+
/((test|spec)[-_. ]?coupling|coupling[-_. ]?(test|spec|check))/i,
|
|
36
|
+
/(require|enforce|verify|guard)[-_. ]?(tests?|specs?)([^a-z0-9]|$)/i,
|
|
37
|
+
/check[-_. ]?(tests?|specs?)([^a-z0-9]|$)/i,
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// Commands that ARE Forge's own gate. Excluded before classification so a second `forge setup`
|
|
41
|
+
// never "detects" the gate its own first run installed and defers to itself.
|
|
42
|
+
const FORGE_OWN_COMMAND_MARKERS = ['check-tdd.js', 'forge-native-hook.js'];
|
|
43
|
+
|
|
44
|
+
// Signatures of a hook lefthook GENERATED (a disposable artifact that only dispatches to
|
|
45
|
+
// lefthook.yml, which we read directly) — mirrors lib/lefthook-wiring.js's own classifier.
|
|
46
|
+
const LEFTHOOK_GENERATED_MARKERS = ['call_lefthook', 'LEFTHOOK_BIN'];
|
|
47
|
+
|
|
48
|
+
const LEFTHOOK_CONFIG_FILES = [
|
|
49
|
+
'lefthook.yml',
|
|
50
|
+
'lefthook.yaml',
|
|
51
|
+
'.lefthook.yml',
|
|
52
|
+
'.lefthook.yaml',
|
|
53
|
+
'lefthook-local.yml',
|
|
54
|
+
'lefthook-local.yaml',
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const PRE_COMMIT_FRAMEWORK_FILES = ['.pre-commit-config.yaml', '.pre-commit-config.yml'];
|
|
58
|
+
|
|
59
|
+
function isForgeOwnCommand(text) {
|
|
60
|
+
return FORGE_OWN_COMMAND_MARKERS.some((marker) => text.includes(marker));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function looksLikeTddGate(text) {
|
|
64
|
+
return TDD_GATE_PATTERNS.some((pattern) => pattern.test(text));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readFileOrNull(absolute) {
|
|
68
|
+
try {
|
|
69
|
+
return fs.readFileSync(absolute, 'utf8');
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// One candidate = one command string a pre-commit runner will execute, plus where it came from.
|
|
76
|
+
function candidate(source, command) {
|
|
77
|
+
return { source, command: String(command).trim() };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Shell-script hooks (husky, raw .git/hooks): every meaningful line is a command.
|
|
81
|
+
function commandsFromScript(source, body) {
|
|
82
|
+
return body
|
|
83
|
+
.split(/\r?\n/)
|
|
84
|
+
.map((line) => line.trim())
|
|
85
|
+
.filter((line) => line.length > 0 && !line.startsWith('#'))
|
|
86
|
+
.map((line) => candidate(source, line));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// lefthook pre-commit: `commands.<name>.run`, `scripts.<name>`, and the v1.11+ `jobs` list.
|
|
90
|
+
// The job NAME carries signal too (a job called `tdd-guard`), so both are collected.
|
|
91
|
+
function commandsFromLefthookPreCommit(source, preCommit) {
|
|
92
|
+
const out = [];
|
|
93
|
+
if (!preCommit || typeof preCommit !== 'object') return out;
|
|
94
|
+
|
|
95
|
+
for (const [name, job] of Object.entries(preCommit.commands || {})) {
|
|
96
|
+
out.push(candidate(source, `${name}: ${(job && job.run) || ''}`));
|
|
97
|
+
}
|
|
98
|
+
for (const [name, job] of Object.entries(preCommit.scripts || {})) {
|
|
99
|
+
out.push(candidate(source, `${name} ${(job && job.runner) || ''}`));
|
|
100
|
+
}
|
|
101
|
+
for (const job of Array.isArray(preCommit.jobs) ? preCommit.jobs : []) {
|
|
102
|
+
if (job && typeof job === 'object') {
|
|
103
|
+
out.push(candidate(source, `${job.name || ''}: ${job.run || ''}`));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Returns { candidates } normally, or { unknown: true } when a pre-commit mechanism is present
|
|
110
|
+
// but its content cannot be read/parsed — we cannot rule out a TDD gate, so the caller defers.
|
|
111
|
+
function collectFromLefthookConfigs(projectRoot) {
|
|
112
|
+
const candidates = [];
|
|
113
|
+
for (const name of LEFTHOOK_CONFIG_FILES) {
|
|
114
|
+
const absolute = path.join(projectRoot, name);
|
|
115
|
+
if (!fs.existsSync(absolute)) continue;
|
|
116
|
+
const body = readFileOrNull(absolute);
|
|
117
|
+
if (body === null) return { unknown: true, source: name };
|
|
118
|
+
|
|
119
|
+
let doc;
|
|
120
|
+
try {
|
|
121
|
+
doc = YAML.parse(body);
|
|
122
|
+
} catch {
|
|
123
|
+
// Unparseable: only ambiguous if it actually declares a pre-commit section.
|
|
124
|
+
if (body.includes('pre-commit')) return { unknown: true, source: name };
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (!doc || typeof doc !== 'object') continue;
|
|
128
|
+
candidates.push(...commandsFromLefthookPreCommit(name, doc['pre-commit']));
|
|
129
|
+
}
|
|
130
|
+
return { candidates };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function collectFromHusky(projectRoot) {
|
|
134
|
+
const relative = '.husky/pre-commit'; // display label — always POSIX-style, never OS-separated
|
|
135
|
+
const absolute = path.join(projectRoot, '.husky', 'pre-commit');
|
|
136
|
+
if (!fs.existsSync(absolute)) return { candidates: [] };
|
|
137
|
+
const body = readFileOrNull(absolute);
|
|
138
|
+
if (body === null) return { unknown: true, source: relative };
|
|
139
|
+
return { candidates: commandsFromScript(relative, body) };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function collectFromPreCommitFramework(projectRoot) {
|
|
143
|
+
const candidates = [];
|
|
144
|
+
for (const name of PRE_COMMIT_FRAMEWORK_FILES) {
|
|
145
|
+
const absolute = path.join(projectRoot, name);
|
|
146
|
+
if (!fs.existsSync(absolute)) continue;
|
|
147
|
+
const body = readFileOrNull(absolute);
|
|
148
|
+
if (body === null) return { unknown: true, source: name };
|
|
149
|
+
|
|
150
|
+
let doc;
|
|
151
|
+
try {
|
|
152
|
+
doc = YAML.parse(body);
|
|
153
|
+
} catch {
|
|
154
|
+
return { unknown: true, source: name };
|
|
155
|
+
}
|
|
156
|
+
for (const repo of (doc && Array.isArray(doc.repos)) ? doc.repos : []) {
|
|
157
|
+
for (const hook of (repo && Array.isArray(repo.hooks)) ? repo.hooks : []) {
|
|
158
|
+
if (!hook || typeof hook !== 'object') continue;
|
|
159
|
+
candidates.push(candidate(name, `${hook.id || ''} ${hook.name || ''} ${hook.entry || ''}`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { candidates };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// The raw hook git will actually run. Forge's own native hook and a lefthook-GENERATED
|
|
167
|
+
// dispatcher are skipped: neither is a third-party gate (the latter's real jobs live in
|
|
168
|
+
// lefthook.yml, which collectFromLefthookConfigs already read).
|
|
169
|
+
function collectFromNativeHook(projectRoot) {
|
|
170
|
+
const hooksDir = resolveGitHooksDir(projectRoot);
|
|
171
|
+
if (!hooksDir) return { candidates: [] };
|
|
172
|
+
const absolute = path.join(hooksDir, 'pre-commit');
|
|
173
|
+
if (!fs.existsSync(absolute)) return { candidates: [] };
|
|
174
|
+
const body = readFileOrNull(absolute);
|
|
175
|
+
if (body === null) return { unknown: true, source: 'pre-commit (git hooks dir)' };
|
|
176
|
+
if (body.includes(FORGE_NATIVE_HOOK_SENTINEL)) return { candidates: [] };
|
|
177
|
+
if (LEFTHOOK_GENERATED_MARKERS.some((marker) => body.includes(marker))) return { candidates: [] };
|
|
178
|
+
return { candidates: commandsFromScript('pre-commit (git hooks dir)', body) };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Detect a pre-existing pre-commit TDD / source-test coupling gate in the project.
|
|
183
|
+
*
|
|
184
|
+
* @param {string} projectRoot - Absolute path to the project root.
|
|
185
|
+
* @returns {{ found: boolean, source?: string, command?: string, unknown?: boolean }}
|
|
186
|
+
* `found:true` with `source`/`command` for a classified gate; `found:true, unknown:true`
|
|
187
|
+
* when a pre-commit mechanism exists but could not be read (defer — the safe side);
|
|
188
|
+
* `found:false` when nothing pre-existing enforces TDD.
|
|
189
|
+
*/
|
|
190
|
+
function detectExistingTddGate(projectRoot) {
|
|
191
|
+
const collectors = [
|
|
192
|
+
collectFromLefthookConfigs,
|
|
193
|
+
collectFromHusky,
|
|
194
|
+
collectFromPreCommitFramework,
|
|
195
|
+
collectFromNativeHook,
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
let ambiguous = null;
|
|
199
|
+
const candidates = [];
|
|
200
|
+
for (const collect of collectors) {
|
|
201
|
+
let result;
|
|
202
|
+
try {
|
|
203
|
+
result = collect(projectRoot);
|
|
204
|
+
} catch {
|
|
205
|
+
continue; // a collector failure must never break setup
|
|
206
|
+
}
|
|
207
|
+
if (result.unknown) {
|
|
208
|
+
ambiguous = ambiguous || { source: result.source };
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
candidates.push(...(result.candidates || []));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const item of candidates) {
|
|
215
|
+
if (isForgeOwnCommand(item.command)) continue;
|
|
216
|
+
if (looksLikeTddGate(item.command)) {
|
|
217
|
+
return { found: true, source: item.source, command: item.command };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (ambiguous) {
|
|
222
|
+
return { found: true, unknown: true, source: ambiguous.source, command: '' };
|
|
223
|
+
}
|
|
224
|
+
return { found: false };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The user-facing deferral report. Silent stacking and silent skipping are BOTH wrong — the
|
|
229
|
+
* adopter must see what was detected, what Forge did not install, and how to choose otherwise.
|
|
230
|
+
*
|
|
231
|
+
* @param {{ found: boolean, source?: string, command?: string, unknown?: boolean }} detection
|
|
232
|
+
* @returns {string|null} Report text, or null when there is nothing to defer to.
|
|
233
|
+
*/
|
|
234
|
+
function describeExistingGateDeferral(detection) {
|
|
235
|
+
if (!detection || !detection.found) return null;
|
|
236
|
+
|
|
237
|
+
const what = detection.unknown
|
|
238
|
+
? `an existing pre-commit hook Forge could not read (${detection.source})`
|
|
239
|
+
: `an existing pre-commit TDD/coupling gate in ${detection.source}:\n ${detection.command}`;
|
|
240
|
+
|
|
241
|
+
return [
|
|
242
|
+
` ⚠ Detected ${what}`,
|
|
243
|
+
' Forge did NOT install its own TDD gate on pre-commit — deferring to yours, so you',
|
|
244
|
+
' do not get two gates blocking the same commit. rail.tdd_intent is off in .forge/config.yaml.',
|
|
245
|
+
" Prefer Forge's gate instead? Remove or disable your own, then run:",
|
|
246
|
+
' forge gate enable rail.tdd_intent',
|
|
247
|
+
].join('\n');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
module.exports = {
|
|
251
|
+
detectExistingTddGate,
|
|
252
|
+
describeExistingGateDeferral,
|
|
253
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Forge 0.1.0-beta.5 compatibility corpus v1
|
|
2
|
+
|
|
3
|
+
This immutable corpus is derived from release commit
|
|
4
|
+
`ebeb4e5b31fc2dacdf23c5936c02fb2656990f49`. Contract snapshots retain only
|
|
5
|
+
public package, binary, command, alias, and workflow facts. State rows are
|
|
6
|
+
synthetic and contain no user, device, prompt, tool-log, or repository data.
|
|
7
|
+
|
|
8
|
+
`manifest.json` hashes every corpus member. Change requires a new corpus
|
|
9
|
+
version; never rewrite v1 after it becomes release evidence.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"commands": ["adapter", "add", "audit", "blocked", "claim", "claims", "clean", "close", "comment", "control", "create", "dev", "doc-gate", "doctor", "explain", "export", "gate", "hooks", "inbox", "init", "insights", "issue", "issues", "lint", "list", "memory", "merge", "migrate", "new", "options", "orient", "orphans", "patch", "plan", "pr", "preflight", "prime", "push", "ready", "recall", "recap", "recommend", "release", "remember", "role", "serve", "setup", "shepherd", "ship", "show", "skill", "stage", "stale", "status", "sync", "team", "test", "update", "upgrade", "validate", "worktree"],
|
|
3
|
+
"aliases": {
|
|
4
|
+
"blocked": { "canonical": "issue blocked", "visible": false },
|
|
5
|
+
"claim": { "canonical": "issue claim", "visible": false },
|
|
6
|
+
"claims": { "canonical": "issue claims", "visible": false },
|
|
7
|
+
"close": { "canonical": "issue close", "visible": false },
|
|
8
|
+
"create": { "canonical": "issue create", "visible": false },
|
|
9
|
+
"doc-gate": { "canonical": "gate doc", "visible": false },
|
|
10
|
+
"insights": { "canonical": "memory insights", "visible": true },
|
|
11
|
+
"issues": { "canonical": "issue list", "visible": false },
|
|
12
|
+
"lint": { "canonical": "issue lint", "visible": false },
|
|
13
|
+
"list": { "canonical": "issue list", "visible": false },
|
|
14
|
+
"merge": { "canonical": "pr merge", "visible": false },
|
|
15
|
+
"orphans": { "canonical": "issue orphans", "visible": false },
|
|
16
|
+
"preflight": { "canonical": "pr preflight", "visible": true },
|
|
17
|
+
"ready": { "canonical": "issue ready", "visible": false },
|
|
18
|
+
"recall": { "canonical": "memory recall", "visible": true },
|
|
19
|
+
"remember": { "canonical": "memory add", "visible": true },
|
|
20
|
+
"shepherd": { "canonical": "pr shepherd", "visible": false },
|
|
21
|
+
"ship": { "canonical": "pr ship", "visible": true },
|
|
22
|
+
"show": { "canonical": "issue show", "visible": false },
|
|
23
|
+
"stale": { "canonical": "issue stale", "visible": false },
|
|
24
|
+
"update": { "canonical": "issue update", "visible": false }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "forge-workflow",
|
|
3
|
+
"version": "0.1.0-beta.5",
|
|
4
|
+
"release_commit": "ebeb4e5b31fc2dacdf23c5936c02fb2656990f49",
|
|
5
|
+
"binaries": {
|
|
6
|
+
"forge": "bin/forge.js",
|
|
7
|
+
"forge-workflow": "bin/forge.js",
|
|
8
|
+
"forge-preflight": "bin/forge-preflight.js"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=22.16.0"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"critical": ["plan", "dev", "validate", "ship", "review", "verify"],
|
|
3
|
+
"standard": ["plan", "dev", "validate", "ship", "review"],
|
|
4
|
+
"refactor": ["plan", "dev", "validate", "ship"],
|
|
5
|
+
"simple": ["dev", "validate", "ship"],
|
|
6
|
+
"hotfix": ["dev", "validate", "ship"],
|
|
7
|
+
"docs": ["verify", "ship"]
|
|
8
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "forge.beta5-corpus.v1",
|
|
3
|
+
"corpus_version": 1,
|
|
4
|
+
"release_version": "0.1.0-beta.5",
|
|
5
|
+
"release_commit": "ebeb4e5b31fc2dacdf23c5936c02fb2656990f49",
|
|
6
|
+
"source_blobs": {
|
|
7
|
+
"package.json": "6f3f112341cd50e0e76fad615beb007b9e6dff2c",
|
|
8
|
+
"lib/commands/_manifest.js": "62595c38b66b139244ca1f54cb034f67d849247d",
|
|
9
|
+
"lib/commands/_aliases.js": "2b16d61d1cdb9ef3e594b5a5093206d05844b460",
|
|
10
|
+
"lib/workflow/stages.js": "0b7ae32bddcbb9bd6dd52a07ca22e43364621918"
|
|
11
|
+
},
|
|
12
|
+
"hash_algorithm": "sha256",
|
|
13
|
+
"content_hash": "c6978eed757c6255f2d25ea6d49ada44e5b3da91830d348016267981d13afd18",
|
|
14
|
+
"files": [
|
|
15
|
+
{ "path": "README.md", "sha256": "3cfcc78856e397f2cfd3fee9fa2fffb630e27ea3e76ee8d8b5d9a77cb366a709" },
|
|
16
|
+
{ "path": "contract/command-contract.json", "sha256": "8b4f885091fa8afe72b7b5a5a8039869c871f4fb1dc3344728b2c4322f491a84" },
|
|
17
|
+
{ "path": "contract/package-contract.json", "sha256": "a65280ede51d62c1913140c83b40e6c3312c991c7a173e7a9ad52d3d5ea0fd47" },
|
|
18
|
+
{ "path": "contract/workflow-stage-matrix.json", "sha256": "2aadafdb5f5c1817c8289dd6134d034e3ccc6579438935520bd3c6b90401e5bf" },
|
|
19
|
+
{ "path": "state/comments.jsonl", "sha256": "ae4f694f207a11a22b6798943cc6d07487d1dc2be83a78ceadad947a3ff40113" },
|
|
20
|
+
{ "path": "state/config.yaml", "sha256": "0b877d9180cf14951657254b50cd771e131970e92582a7c0337f36418a35e1c0" },
|
|
21
|
+
{ "path": "state/dependencies.jsonl", "sha256": "cb43cc053d6dd885254a26f6d6b024883f770eb68f50b715cc2f0f05e2cd31e5" },
|
|
22
|
+
{ "path": "state/issues.jsonl", "sha256": "425567c5c7b70fd6bbdf9ead3aff78c09a77ebe7b78d3c856c045743b8cb924e" },
|
|
23
|
+
{ "path": "state/kernel.sql", "sha256": "4208aabcadc6cec3234ebb5abd4cbf32cc74805433d04a05801e0b6354b53d27" }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"synthetic-comment-1","issue_id":"synthetic-issue-1","body":"Synthetic comment","actor":"fixture"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"issue_id":"synthetic-issue-1","blocks_issue_id":"synthetic-issue-2"}
|