omp-conductor 0.3.3 → 0.3.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.
- package/README.md +93 -1
- package/package.json +1 -1
- package/skills/conductor-onboarding/SKILL.md +98 -5
- package/src/briefs/worker.md +16 -5
- package/src/cli.ts +60 -0
- package/src/config.ts +40 -4
- package/src/daemon.ts +15 -1
- package/src/escalate.ts +14 -4
- package/src/graph.ts +508 -0
- package/src/plugin.ts +53 -1
- package/src/setup.ts +32 -0
- package/src/types.ts +25 -0
- package/src/worktree.ts +81 -2
package/src/types.ts
CHANGED
|
@@ -46,6 +46,31 @@ export interface RepoTarget {
|
|
|
46
46
|
* subset lets lint errors outside the source dir reach the runners.
|
|
47
47
|
*/
|
|
48
48
|
gates: { cmd: string; cwd: string }[];
|
|
49
|
+
/**
|
|
50
|
+
* Absolute path of the **conductor-owned, index-only clone** of this repo
|
|
51
|
+
* whose code-graph index workers query. Optional: absent means this repo has
|
|
52
|
+
* no graph, and the worker brief says nothing about one.
|
|
53
|
+
*
|
|
54
|
+
* Two things it deliberately is not, and both were paid for:
|
|
55
|
+
*
|
|
56
|
+
* - **Not a worker's worktree.** A code-graph index is keyed by the realpath
|
|
57
|
+
* of the directory it was built from, with no git-worktree awareness, so a
|
|
58
|
+
* run's throwaway `worktrees/<issue>` path is always an empty project. A
|
|
59
|
+
* worker that queried its own cwd would find nothing, conclude there is no
|
|
60
|
+
* graph, and go back to grepping — which is the entire cost this field
|
|
61
|
+
* exists to remove.
|
|
62
|
+
* - **Not a human's checkout.** Refreshing an index means hard-resetting the
|
|
63
|
+
* clone to its default branch. Doing that where somebody works destroys
|
|
64
|
+
* their uncommitted edits; making it safe instead (a fast-forward pull)
|
|
65
|
+
* means the graph reflects whatever feature branch they left checked out.
|
|
66
|
+
* So this names a disposable clone nothing human ever edits, which is what
|
|
67
|
+
* makes the reset both safe and deterministic.
|
|
68
|
+
*
|
|
69
|
+
* `omp-conductor graph-setup` prints how to create and refresh it. Nothing in
|
|
70
|
+
* this package reads an index itself: the daemon only passes this path into
|
|
71
|
+
* the worker brief.
|
|
72
|
+
*/
|
|
73
|
+
graphProject?: string;
|
|
49
74
|
}
|
|
50
75
|
|
|
51
76
|
/**
|
package/src/worktree.ts
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* for the delta.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
12
|
-
import { join } from "node:path";
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
13
|
|
|
14
14
|
import type { RepoTarget } from "./types.ts";
|
|
15
15
|
|
|
@@ -96,6 +96,50 @@ async function gitSucceeds(args: string[], cwd?: string): Promise<boolean> {
|
|
|
96
96
|
return code === 0;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/** Fences the block below so it can be found, replaced, and never duplicated. */
|
|
100
|
+
const EXCLUDE_BEGIN = "# >>> omp-conductor (managed; edit outside this block)";
|
|
101
|
+
const EXCLUDE_END = "# <<< omp-conductor";
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Appended to every mirror's `info/exclude`, and so in force in every worktree
|
|
105
|
+
* cut from it. Deliberately the same shapes salvage therefore skips, and
|
|
106
|
+
* for the same reason — a worker's own scaffolding is not the repo's business.
|
|
107
|
+
*
|
|
108
|
+
* Two layers because they catch different moments: this one keeps scratch out
|
|
109
|
+
* of a worker's own `git add -A` and out of its `git status`, which salvage
|
|
110
|
+
* never observes; the salvage list catches whatever a worker created before
|
|
111
|
+
* this landed, or wrote past an ignore with `add -f`.
|
|
112
|
+
*/
|
|
113
|
+
const LOCAL_EXCLUDE = [".scratch*/", ".scratch*", ".env.local", "*.local.sh"];
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Adds the managed block to an `info/exclude`, preserving everything else.
|
|
117
|
+
*
|
|
118
|
+
* `info/exclude` is a *local* ignore file, which means it is exactly where an
|
|
119
|
+
* operator or another tool puts patterns they could not put in the tracked
|
|
120
|
+
* `.gitignore` — so overwriting it would silently destroy work that has no
|
|
121
|
+
* other copy. The block is fenced and replaced in place, so re-running this on
|
|
122
|
+
* every dispatch neither duplicates our lines nor disturbs theirs.
|
|
123
|
+
*/
|
|
124
|
+
export function mergeExclude(existing: string): string {
|
|
125
|
+
const begin = existing.indexOf(EXCLUDE_BEGIN);
|
|
126
|
+
const end = existing.indexOf(EXCLUDE_END);
|
|
127
|
+
const theirs =
|
|
128
|
+
begin === -1 || end === -1 || end < begin
|
|
129
|
+
? existing
|
|
130
|
+
: existing.slice(0, begin) + existing.slice(end + EXCLUDE_END.length + 1);
|
|
131
|
+
|
|
132
|
+
// Normalised before recomposing, so the result is byte-identical on every
|
|
133
|
+
// call. Trimming the tail matters twice over: a hand-edited file often has no
|
|
134
|
+
// trailing newline (the first managed line would glue onto their last
|
|
135
|
+
// pattern and match nothing), and without it the blank separator below would
|
|
136
|
+
// accumulate one more newline on each of the thousands of dispatches that
|
|
137
|
+
// call this.
|
|
138
|
+
const body = theirs.replace(/\n+$/, "");
|
|
139
|
+
const head = body === "" ? [] : [body, ""];
|
|
140
|
+
return [...head, EXCLUDE_BEGIN, ...LOCAL_EXCLUDE, EXCLUDE_END, ""].join("\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
99
143
|
/**
|
|
100
144
|
* Rewrites the two `clone --mirror` defaults that are actively dangerous for a
|
|
101
145
|
* cache we cut worktrees from. Applied on every `ensureMirror` so a mirror left
|
|
@@ -122,6 +166,20 @@ async function configureMirror(mirrorPath: string): Promise<void> {
|
|
|
122
166
|
["config", "--replace-all", "remote.origin.fetch", TRACKING_REFSPEC],
|
|
123
167
|
mirrorPath,
|
|
124
168
|
);
|
|
169
|
+
|
|
170
|
+
// A mirror's `info/exclude` is the common git dir for every worktree cut from
|
|
171
|
+
// it, so one write here keeps a worker's own scratch out of `git status` in
|
|
172
|
+
// all of them — without touching the repo's tracked `.gitignore`, which is
|
|
173
|
+
// the operator's file and not ours to edit.
|
|
174
|
+
//
|
|
175
|
+
// Belt and braces with the salvage excludes rather than a replacement for
|
|
176
|
+
// them: this stops scratch reaching a *worker's* own `git add`, which salvage
|
|
177
|
+
// never sees. Merged rather than written, because `info/exclude` is precisely
|
|
178
|
+
// where an operator keeps patterns that cannot go in the tracked file — and
|
|
179
|
+
// this runs on every dispatch, so overwriting would destroy them repeatedly.
|
|
180
|
+
const exclude = join(mirrorPath, "info", "exclude");
|
|
181
|
+
mkdirSync(dirname(exclude), { recursive: true });
|
|
182
|
+
writeFileSync(exclude, mergeExclude(existsSync(exclude) ? readFileSync(exclude, "utf8") : ""));
|
|
125
183
|
}
|
|
126
184
|
|
|
127
185
|
/**
|
|
@@ -343,7 +401,28 @@ export async function salvageWip(
|
|
|
343
401
|
const branch = await git(["rev-parse", "--abbrev-ref", "HEAD"], worktree);
|
|
344
402
|
|
|
345
403
|
// `-A` on purpose: the losses this exists for were mostly *new* files.
|
|
404
|
+
//
|
|
405
|
+
// Filtering scratch is git's job, not a pathspec's. The mirror's
|
|
406
|
+
// `info/exclude` (see {@link mergeExclude}) is the common git dir for this
|
|
407
|
+
// worktree, and git's own rules then give exactly the semantics needed:
|
|
408
|
+
// untracked ignored files are skipped, while modifications to *tracked*
|
|
409
|
+
// files are staged even when the name matches an ignore. That second half
|
|
410
|
+
// is why an exclude pathspec here was wrong — it matched on filename alone,
|
|
411
|
+
// so a repo legitimately versioning a `bootstrap.local.sh` would have lost
|
|
412
|
+
// a worker's edits to it, salvage destroying the work it exists to save.
|
|
413
|
+
//
|
|
414
|
+
// A literal `:(exclude)<path>` was also an outright bug: git counts it as
|
|
415
|
+
// naming the path, so an already-ignored file made `add` exit 1 and every
|
|
416
|
+
// cap-kill would have reported a salvage *failure*.
|
|
346
417
|
await git(["add", "-A"], worktree);
|
|
418
|
+
|
|
419
|
+
// The dirty check above ran before git applied its ignores, so a tree whose
|
|
420
|
+
// only changes were ignored scratch had work by that test and none by this.
|
|
421
|
+
// Without this, `commit` exits non-zero on an empty index and a tree
|
|
422
|
+
// holding nothing worth keeping gets reported as a salvage *failure*.
|
|
423
|
+
if ((await git(["diff", "--cached", "--name-only"], worktree)) === "") {
|
|
424
|
+
return { kind: "nothing" };
|
|
425
|
+
}
|
|
347
426
|
await git(
|
|
348
427
|
[
|
|
349
428
|
...SALVAGE_COMMIT_CONFIG,
|