valent-pipeline 0.19.15 → 0.19.16
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/package.json +1 -1
- package/src/lib/git-flow.js +65 -0
package/package.json
CHANGED
package/src/lib/git-flow.js
CHANGED
|
@@ -156,6 +156,69 @@ function ensureExcluded(root, worktreeDir) {
|
|
|
156
156
|
appendFileSync(excludePath, `${content.endsWith('\n') || content === '' ? '' : '\n'}${line}\n`);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
// Per-story evidence STATE the harness rewrites every run/retry: the run journal (index.jsonl), the
|
|
160
|
+
// gate-pin ledger (pins.jsonl), the resolved task graph, and the stage-verdict ledger. These are
|
|
161
|
+
// DERIVED records — the board reads them off disk, and the on-disk evidence hash-chain (not git
|
|
162
|
+
// history) is the tamper model — yet commitAll's `git add -A` would TRACK them, and a tracked file
|
|
163
|
+
// that churns on a retry leaves the tree dirty. That dirt refuses the ship merge: both isDirty() AND
|
|
164
|
+
// git's own `merge`/`checkout` see WHOLE-tree dirt — GIT_DIRT_EXCLUDE (evidence.js) only spares the
|
|
165
|
+
// read-side coherence/pin checks, never the merge. Fix = keep them out of git via .git/info/exclude,
|
|
166
|
+
// never a tracked .gitignore edit (which would itself dirty the tree — the same rule ensureExcluded
|
|
167
|
+
// states for the worktree container). One list: a new state-file type is a single line here, not a
|
|
168
|
+
// per-project .gitignore chase. The board still reads them off disk; only their git-history copy goes.
|
|
169
|
+
const STATE_EXCLUDE_PATTERNS = [
|
|
170
|
+
'stories/*/evidence/index.jsonl',
|
|
171
|
+
'stories/*/evidence/pins.jsonl',
|
|
172
|
+
'stories/*/evidence/resolved-graph.json',
|
|
173
|
+
'stories/*/evidence/stage-results.json',
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Append the evidence-state ignore patterns to .git/info/exclude (idempotent; shared across linked
|
|
178
|
+
* worktrees via the common git dir). Local-only, so it never dirties the tree the flow keeps clean.
|
|
179
|
+
* Called at the top of commitAll BEFORE `git add -A`, so these files are never newly tracked.
|
|
180
|
+
*/
|
|
181
|
+
function ensureStateExcluded(root) {
|
|
182
|
+
let commonDir;
|
|
183
|
+
try {
|
|
184
|
+
commonDir = git(root, ['rev-parse', '--git-common-dir']);
|
|
185
|
+
} catch {
|
|
186
|
+
return; // not a repo / git unavailable — nothing to exclude (commitAll's own callers handle the skip)
|
|
187
|
+
}
|
|
188
|
+
const excludePath = join(isAbsolute(commonDir) ? commonDir : join(root, commonDir), 'info', 'exclude');
|
|
189
|
+
let content = '';
|
|
190
|
+
try {
|
|
191
|
+
content = readFileSync(excludePath, 'utf-8');
|
|
192
|
+
} catch { /* no exclude file yet */ }
|
|
193
|
+
const have = new Set(content.split(/\r?\n/));
|
|
194
|
+
const missing = STATE_EXCLUDE_PATTERNS.filter((p) => !have.has(p));
|
|
195
|
+
if (!missing.length) return;
|
|
196
|
+
mkdirSync(join(excludePath, '..'), { recursive: true });
|
|
197
|
+
appendFileSync(excludePath, `${content.endsWith('\n') || content === '' ? '' : '\n'}${missing.join('\n')}\n`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Self-heal projects an older version (or a prior run) already committed these files into: untrack
|
|
202
|
+
* them so they stop dirtying the tree. `git rm --cached` drops them from the index but KEEPS them on
|
|
203
|
+
* disk (the board still reads them). Best-effort over the whole set — a file that can't be untracked
|
|
204
|
+
* (e.g. staged AND modified) is left for the `git add -A` sweep, which still yields a clean tree for
|
|
205
|
+
* the merge. Only call on the commit path (after isDirty), so the staged removals ride the commit
|
|
206
|
+
* that is about to happen rather than producing a surprise commit on an otherwise-clean tree.
|
|
207
|
+
*/
|
|
208
|
+
function untrackStateFiles(root) {
|
|
209
|
+
let tracked = [];
|
|
210
|
+
try {
|
|
211
|
+
tracked = git(root, ['ls-files', '--', ...STATE_EXCLUDE_PATTERNS]).split('\n').filter(Boolean);
|
|
212
|
+
} catch {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (!tracked.length) return;
|
|
216
|
+
try {
|
|
217
|
+
git(root, ['rm', '--cached', '--quiet', '--', ...tracked]);
|
|
218
|
+
console.error(`git-flow: untracked ${tracked.length} regenerable evidence-state file(s) (kept on disk) — they churn every run and otherwise dirty the tree, refusing the ship merge.`);
|
|
219
|
+
} catch { /* leave them for the add -A sweep below — a committed churner is still a clean tree */ }
|
|
220
|
+
}
|
|
221
|
+
|
|
159
222
|
/** Find the registered worktree (path) that has `branch` checked out, or null. */
|
|
160
223
|
function worktreeForBranch(root, branch) {
|
|
161
224
|
const out = git(root, ['worktree', 'list', '--porcelain']);
|
|
@@ -292,7 +355,9 @@ function commitAll(root, message) {
|
|
|
292
355
|
`sweeping now would conclude it and commit unreviewed half-merged code. Resolve or ` +
|
|
293
356
|
`\`git merge --abort\` first.`);
|
|
294
357
|
}
|
|
358
|
+
ensureStateExcluded(root); // keep regenerable evidence-state churn out of git BEFORE `add -A` ever sees it
|
|
295
359
|
if (!isDirty(root)) return { committed: false };
|
|
360
|
+
untrackStateFiles(root); // self-heal any already-tracked churners into this very commit
|
|
296
361
|
git(root, ['add', '-A']);
|
|
297
362
|
git(root, ['commit', '-m', message]);
|
|
298
363
|
return { committed: true, sha: git(root, ['rev-parse', 'HEAD']) };
|