omp-conductor 0.14.0 → 0.15.1
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 +336 -169
- package/package.json +8 -5
- package/schema/config.schema.json +609 -0
- package/src/board.ts +19 -32
- package/src/brief-upgrade.ts +1 -1
- package/src/briefs/orchestrator.md +9 -5
- package/src/briefs/policy.md +4 -4
- package/src/briefs/probes/gates.md +51 -0
- package/src/briefs/probes/project-context.md +59 -0
- package/src/briefs/probes/release-procedure.md +81 -0
- package/src/cli.ts +235 -199
- package/src/config-schema.ts +352 -0
- package/src/config.ts +1046 -796
- package/src/confinement.ts +54 -0
- package/src/daemon.ts +442 -374
- package/src/escalate.ts +43 -3
- package/src/fleet.ts +317 -43
- package/src/generate-schema.ts +21 -0
- package/src/graph.ts +3 -3
- package/src/host.ts +16 -0
- package/src/omp.ts +21 -1
- package/src/orchestrator-tick.ts +298 -39
- package/src/privileged.ts +264 -0
- package/src/reports.ts +1 -1
- package/src/session-host.ts +3 -0
- package/src/setup-host.ts +225 -25
- package/src/setup-install.ts +320 -0
- package/src/setup-probe.ts +412 -0
- package/src/{plugin.ts → setup-wizard.ts} +790 -465
- package/src/setup.ts +264 -20
- package/src/types.ts +2 -2
- package/src/upgrade.ts +44 -10
- package/src/verbs/server.ts +32 -9
- package/src/wizard-ui.ts +249 -0
- package/src/worker.ts +6 -1
- package/skills/conductor-onboarding/SKILL.md +0 -748
- package/skills/conductor-update/SKILL.md +0 -51
package/src/board.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
codeGraphFromHealthz,
|
|
8
8
|
fleetLayers,
|
|
9
9
|
probeTelegramHealth,
|
|
10
|
+
workerPhasesFromHealthz,
|
|
10
11
|
type FleetLayers,
|
|
11
12
|
type TelegramHealth,
|
|
12
13
|
} from "./fleet.ts";
|
|
@@ -922,36 +923,6 @@ export function renderBoard(
|
|
|
922
923
|
].join("\n");
|
|
923
924
|
}
|
|
924
925
|
|
|
925
|
-
export function workerPhasesFromHealthz(
|
|
926
|
-
body: string | undefined,
|
|
927
|
-
project: string,
|
|
928
|
-
): ReadonlyMap<number, WorkerPausePhase> {
|
|
929
|
-
const phases = new Map<number, WorkerPausePhase>();
|
|
930
|
-
if (body === undefined) return phases;
|
|
931
|
-
try {
|
|
932
|
-
const payload = JSON.parse(body) as unknown;
|
|
933
|
-
if (payload === null || typeof payload !== "object") return phases;
|
|
934
|
-
if (Reflect.get(payload, "project") !== project) return phases;
|
|
935
|
-
const workers = Reflect.get(payload, "workers");
|
|
936
|
-
if (!Array.isArray(workers)) return phases;
|
|
937
|
-
for (const worker of workers) {
|
|
938
|
-
if (worker === null || typeof worker !== "object") continue;
|
|
939
|
-
const issue = Reflect.get(worker, "issue");
|
|
940
|
-
const phase = Reflect.get(worker, "phase");
|
|
941
|
-
if (
|
|
942
|
-
Number.isSafeInteger(issue) &&
|
|
943
|
-
(issue as number) > 0 &&
|
|
944
|
-
(phase === "pausing" || phase === "paused")
|
|
945
|
-
) {
|
|
946
|
-
phases.set(issue as number, phase);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
} catch {
|
|
950
|
-
// An unreadable health body means no trustworthy pause phase.
|
|
951
|
-
}
|
|
952
|
-
return phases;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
926
|
async function probeBoardHealth(project: ProjectConfig): Promise<BoardHealthProbe> {
|
|
956
927
|
const layers = fleetLayers(project.name);
|
|
957
928
|
const record = livingDaemon();
|
|
@@ -966,8 +937,24 @@ async function probeBoardHealth(project: ProjectConfig): Promise<BoardHealthProb
|
|
|
966
937
|
else if (health?.ok !== true) daemon = "unreachable";
|
|
967
938
|
else {
|
|
968
939
|
try {
|
|
969
|
-
const payload = JSON.parse(health.body ?? "null") as
|
|
970
|
-
|
|
940
|
+
const payload = JSON.parse(health.body ?? "null") as unknown;
|
|
941
|
+
if (payload === null || typeof payload !== "object") {
|
|
942
|
+
daemon = "unreachable";
|
|
943
|
+
} else if (Reflect.get(payload, "project") === project.name) {
|
|
944
|
+
daemon = "ok";
|
|
945
|
+
} else {
|
|
946
|
+
const projects = Reflect.get(payload, "projects");
|
|
947
|
+
daemon =
|
|
948
|
+
Array.isArray(projects) &&
|
|
949
|
+
projects.some(
|
|
950
|
+
(entry) =>
|
|
951
|
+
entry !== null &&
|
|
952
|
+
typeof entry === "object" &&
|
|
953
|
+
Reflect.get(entry, "project") === project.name,
|
|
954
|
+
)
|
|
955
|
+
? "ok"
|
|
956
|
+
: "other-project";
|
|
957
|
+
}
|
|
971
958
|
} catch {
|
|
972
959
|
daemon = "unreachable";
|
|
973
960
|
}
|
package/src/brief-upgrade.ts
CHANGED
|
@@ -575,7 +575,7 @@ export function formatBriefReport(path: string, layout: BriefLayout, missing: re
|
|
|
575
575
|
return [
|
|
576
576
|
`brief ${path}`,
|
|
577
577
|
"",
|
|
578
|
-
"No brief here yet. Run
|
|
578
|
+
"No brief here yet. Run `omp-conductor setup brief` and say yes to writing ORCHESTRATOR.md;",
|
|
579
579
|
"it writes POLICY.md beside it, which is the half you then own.",
|
|
580
580
|
].join("\n");
|
|
581
581
|
}
|
|
@@ -16,8 +16,10 @@ Point the heartbeat at the workspace that holds `ORCHESTRATOR.md` /
|
|
|
16
16
|
`POLICY.md` — a `.conductor-tick.json` whose default message re-reads both.
|
|
17
17
|
|
|
18
18
|
This floor ships conservative so an unedited `POLICY.md` is still a safe fleet.
|
|
19
|
-
To tailor Releases and Project context,
|
|
20
|
-
|
|
19
|
+
To tailor Releases and Project context, run `omp-conductor setup brief`: it asks
|
|
20
|
+
the judgment questions no repo reading can answer, then reads your repos to draft
|
|
21
|
+
the rest and shows each draft for confirmation. Editing `POLICY.md` by hand does
|
|
22
|
+
the same job.
|
|
21
23
|
|
|
22
24
|
---
|
|
23
25
|
|
|
@@ -421,9 +423,11 @@ Four controls stop different work:
|
|
|
421
423
|
spend a failure or continuation budget. Use it when the operator explicitly
|
|
422
424
|
ends obsolete or already-delivered work. A salvage failure keeps the tree and
|
|
423
425
|
names the path; recover that copy before any forced unblock.
|
|
424
|
-
- **Fleet dispatch:** `omp-conductor
|
|
425
|
-
mutations. Work admitted before
|
|
426
|
-
|
|
426
|
+
- **Fleet dispatch:** `omp-conductor hold` stops new claims and work-starting
|
|
427
|
+
mutations. Work admitted before it may still complete, and completion verbs and
|
|
428
|
+
releases remain available. Note it also disarms ticks, so it silences *you*:
|
|
429
|
+
stopping the fleet is the operator's call, and the right move is to report that
|
|
430
|
+
it needs stopping rather than to run this yourself.
|
|
427
431
|
- **Orchestrator ticks:** `omp-conductor disarm` removes the operator-owned
|
|
428
432
|
`ARMED` marker so ticks skip. It does not pause workers or stop processes.
|
|
429
433
|
|
package/src/briefs/policy.md
CHANGED
|
@@ -52,10 +52,10 @@ propose the corrected steps.
|
|
|
52
52
|
|
|
53
53
|
## Project context (filled during onboarding)
|
|
54
54
|
|
|
55
|
-
Empty until
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
Empty until setup fills it in: the product in a paragraph, a map of which repo
|
|
56
|
+
owns what, the grooming guidance Duty 2 needs to judge priority and spot issues
|
|
57
|
+
that would collide, and where the roadmap lives. `omp-conductor setup` asks for
|
|
58
|
+
the last of those and proposes the rest.
|
|
59
59
|
|
|
60
60
|
## Reporting
|
|
61
61
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
You are reading one repository to answer a single question for an operator who is
|
|
2
|
+
configuring an automated dispatcher. You have been given
|
|
3
|
+
reading tools only: there is no shell, no editor and no verb here, so reading and
|
|
4
|
+
answering is the whole of what you can do.
|
|
5
|
+
|
|
6
|
+
## The question
|
|
7
|
+
|
|
8
|
+
What are the **pre-push gates** for `{{REPO}}` (branch `{{BRANCH}}`)? A pre-push
|
|
9
|
+
gate is a command a change must pass locally *before* it is pushed, and the answer
|
|
10
|
+
is only useful if it matches what CI actually runs.
|
|
11
|
+
|
|
12
|
+
## Where to look
|
|
13
|
+
|
|
14
|
+
In this order, stopping when you have enough:
|
|
15
|
+
|
|
16
|
+
1. CI workflow definitions — `.github/workflows/*.yml`, or the equivalent for
|
|
17
|
+
whatever CI this repo uses. This is the authority: it is what will fail a PR.
|
|
18
|
+
2. `package.json` scripts, `Makefile`, `justfile`, `Taskfile.yml`, `tox.ini`,
|
|
19
|
+
`pyproject.toml`, `Cargo.toml`.
|
|
20
|
+
3. Any `CONTRIBUTING.md` / `AGENTS.md` / `CLAUDE.md` that names a pre-push
|
|
21
|
+
routine.
|
|
22
|
+
|
|
23
|
+
## Three traps, each of which produces a wrong answer
|
|
24
|
+
|
|
25
|
+
- **Whole tree, not source directory.** If CI lints the repository and a script
|
|
26
|
+
lints only `src/`, the gate is the repository-wide command. A gate narrower than
|
|
27
|
+
CI is how a lint error reaches the runners with nobody watching.
|
|
28
|
+
- **Cheap only.** Gates run on a shared host, many at once. Include type checks,
|
|
29
|
+
lint and unit tests. **Exclude** docker builds, image builds, production builds,
|
|
30
|
+
browser/e2e suites, and anything that needs a service or a credential — CI owns
|
|
31
|
+
those.
|
|
32
|
+
- **An honest "no gates" is a real answer.** If this repo genuinely has no cheap
|
|
33
|
+
local check, say so with an empty list. Inventing a plausible `npm test` for a
|
|
34
|
+
repo that has no test script produces a gate that fails on every push.
|
|
35
|
+
|
|
36
|
+
Each gate needs the **`cwd` it runs from**, relative to the repository root, as a
|
|
37
|
+
monorepo's checks usually do not run from the top. Use `"."` for the root.
|
|
38
|
+
|
|
39
|
+
## Answer
|
|
40
|
+
|
|
41
|
+
One fenced JSON block, nothing else after it:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"gates": [{ "cmd": "bun run check", "cwd": "." }],
|
|
46
|
+
"evidence": "one line naming the files you read that establish these"
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`evidence` is what lets the operator judge your answer without repeating your
|
|
51
|
+
reading. Name files, not impressions.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
You are reading one repository to draft a section of an operator's standing brief.
|
|
2
|
+
You have been given reading tools only: there is no shell,
|
|
3
|
+
no editor and no verb here, so reading and answering is the whole of what you can
|
|
4
|
+
do.
|
|
5
|
+
|
|
6
|
+
## What you are writing
|
|
7
|
+
|
|
8
|
+
The `## Project context` section of `POLICY.md`, which a supervising orchestrator
|
|
9
|
+
session re-reads on **every** tick. Keep it **under 40 lines**: a brief nobody
|
|
10
|
+
finishes reading is a brief that gets skimmed.
|
|
11
|
+
|
|
12
|
+
It needs exactly four things, in this order.
|
|
13
|
+
|
|
14
|
+
1. **The product in one paragraph.** What it does, for whom. Not a feature list.
|
|
15
|
+
2. **A repo map** — one line per routing key below, naming what that repo owns in
|
|
16
|
+
the operator's vocabulary. This is what turns a routing label into a judgement.
|
|
17
|
+
3. **Grooming guidance.** Which repos ship together, so a change in one is known
|
|
18
|
+
to need a matching PR in the other. And which *kinds* of issue touch the same
|
|
19
|
+
files: those must not be queued concurrently, because two agents editing one
|
|
20
|
+
file produce two pull requests that cannot both merge.
|
|
21
|
+
4. **Where the roadmap lives, and how to judge priority against it** — in one
|
|
22
|
+
line.
|
|
23
|
+
|
|
24
|
+
## Where you are
|
|
25
|
+
|
|
26
|
+
Your working directory holds **one directory per routing repo**, each a shallow
|
|
27
|
+
clone, listed below. Read across all of them — that is what makes items 2 and 3
|
|
28
|
+
answerable, and a draft written from one repo has to guess the rest.
|
|
29
|
+
|
|
30
|
+
In each, read the `README` (what it is for, and who uses it), whatever top-level
|
|
31
|
+
architecture doc exists (`docs/`, `ARCHITECTURE.md`, an ADR directory) for which
|
|
32
|
+
repo owns which concern, and `AGENTS.md` / `CONTRIBUTING.md` for the repo's own
|
|
33
|
+
rules — those outrank the brief.
|
|
34
|
+
|
|
35
|
+
## Stated inputs — these are given, not yours to decide
|
|
36
|
+
|
|
37
|
+
The routing keys this project dispatches to, and the directory each was cloned
|
|
38
|
+
into. Refer to repos by their **routing key**, never by the directory name — the
|
|
39
|
+
directory is scratch, the key is what an issue carries:
|
|
40
|
+
|
|
41
|
+
{{REPOS}}
|
|
42
|
+
|
|
43
|
+
The operator's own answer about the roadmap and the current priority:
|
|
44
|
+
|
|
45
|
+
> {{ROADMAP}}
|
|
46
|
+
|
|
47
|
+
**Item 4 must quote that answer.** You are reading a repository, which shows what
|
|
48
|
+
is open and never what matters; the operator supplied the ranking and it is not
|
|
49
|
+
yours to improve. If the quote above is empty, write item 4 as a single line
|
|
50
|
+
saying the operator has not named a roadmap yet, and nothing more.
|
|
51
|
+
|
|
52
|
+
## Answer
|
|
53
|
+
|
|
54
|
+
One fenced markdown block containing the section and nothing else — start it at
|
|
55
|
+
the `## Project context` heading. No preamble, no commentary after it.
|
|
56
|
+
|
|
57
|
+
Write what the repository supports. Where you are guessing, say so in the text
|
|
58
|
+
itself in a few words: the operator edits this file, and a marked guess is one
|
|
59
|
+
they can correct, while a confident invention is one they will not notice.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
You are reading one repository to turn an operator's stated release *intent* into
|
|
2
|
+
that repository's own machinery. You have been given reading tools only: there is no shell,
|
|
3
|
+
no editor and no verb here, so reading and answering is the whole of what you can
|
|
4
|
+
do.
|
|
5
|
+
|
|
6
|
+
## The one thing you must not do
|
|
7
|
+
|
|
8
|
+
**You are not deciding the release policy.** The operator has already decided it,
|
|
9
|
+
below. Your job is to find how this repository actually ships and express their
|
|
10
|
+
intent in its terms. Do not widen the boundary, do not add a step they did not
|
|
11
|
+
ask for, and do not write the procedure from the intent alone without reading the
|
|
12
|
+
repository — an invented procedure reads exactly like a real one and fails the
|
|
13
|
+
first time it is followed.
|
|
14
|
+
|
|
15
|
+
## Stated intent — given, not yours to revise
|
|
16
|
+
|
|
17
|
+
- **Where the orchestrator's leg ends:** {{BOUNDARY}}
|
|
18
|
+
- **What may be released, and from which branch:** {{WHAT}}
|
|
19
|
+
- **When — batched how, after which named checks:** {{WHEN}}
|
|
20
|
+
- **Proof that must be held first:** {{PROOF}}
|
|
21
|
+
- **What must still be asked, every time:** {{ASK}}
|
|
22
|
+
- **Permanently forbidden:** {{FORBIDDEN}}
|
|
23
|
+
- **A release worth cutting:** {{WORTH_CUTTING}}
|
|
24
|
+
- **Rollback owner:** {{ROLLBACK_OWNER}}
|
|
25
|
+
|
|
26
|
+
## Where you are, and what to read
|
|
27
|
+
|
|
28
|
+
Your working directory holds **one directory per routing repo**, each a shallow
|
|
29
|
+
clone. Release machinery does not always live in the repo you would expect, so look
|
|
30
|
+
across all of them before writing: the repo that ships may not be the first one.
|
|
31
|
+
|
|
32
|
+
Each routing key and the directory it was cloned into:
|
|
33
|
+
|
|
34
|
+
{{REPOS}}
|
|
35
|
+
|
|
36
|
+
**Name repos by their routing key, never by the directory.** The directory is
|
|
37
|
+
scratch and will not exist when anyone reads your answer; the key is what an issue
|
|
38
|
+
carries and what an orchestrator routes on.
|
|
39
|
+
|
|
40
|
+
In each, read the release workflows (`.github/workflows/*release*`, `*publish*`,
|
|
41
|
+
`*tag*`), the package manifest's version and publish configuration, any
|
|
42
|
+
`RELEASING.md` / `CONTRIBUTING.md` release section, and how a version is bumped.
|
|
43
|
+
|
|
44
|
+
For every workflow that publishes anything — a release, a tag, an image, a package,
|
|
45
|
+
a deployment — **the trigger is the fact that matters**:
|
|
46
|
+
|
|
47
|
+
- `workflow_dispatch` → the release is *dispatched*. That workflow is the authority,
|
|
48
|
+
and the instruction is "dispatch it with the planned version", never "do what it
|
|
49
|
+
does".
|
|
50
|
+
- `on: push: tags:` → a pushed tag is the trigger, so **tagging is releasing**.
|
|
51
|
+
- `on: release: published` → the GitHub Release is the trigger.
|
|
52
|
+
|
|
53
|
+
Also record **what the workflow enforces** — many reject a tag unless several
|
|
54
|
+
version files agree — because that is what turns a release attempt into a failure
|
|
55
|
+
an operator has to unpick.
|
|
56
|
+
|
|
57
|
+
Name the repo each step belongs to. A procedure that does not say where it runs is
|
|
58
|
+
one an orchestrator applies in the wrong checkout.
|
|
59
|
+
|
|
60
|
+
## What to write
|
|
61
|
+
|
|
62
|
+
- **The named authority, and how it is invoked.** If shipping is a protected or
|
|
63
|
+
dispatchable workflow, the instruction is to *dispatch it and verify the run* —
|
|
64
|
+
never to reproduce what it does by hand, even when every step is visible. A
|
|
65
|
+
hand-rolled release skips the checks the workflow exists to enforce.
|
|
66
|
+
- **The steps on the agent's side, each with the check that proves it.** A step
|
|
67
|
+
with no verifiable outcome is a step that silently did not happen.
|
|
68
|
+
- **The forbidden list, with a file citation** — cite the file that says so
|
|
69
|
+
(`AGENTS.md`, a runbook) so the rule survives a future session that thinks it has
|
|
70
|
+
found a shortcut. Force-push, secrets and production data are forbidden
|
|
71
|
+
everywhere, always, whether or not this repository restates them.
|
|
72
|
+
- **Where the leg ends**, restated as a concrete artefact or commit, matching the
|
|
73
|
+
boundary above.
|
|
74
|
+
|
|
75
|
+
If the repository has no release machinery at all, say that in one line rather
|
|
76
|
+
than inventing a plausible one. That is a finding the operator needs.
|
|
77
|
+
|
|
78
|
+
## Answer
|
|
79
|
+
|
|
80
|
+
One fenced markdown block containing the procedure and nothing else. No preamble,
|
|
81
|
+
no commentary after it.
|