session-orchestrator 3.21.0 → 3.22.0
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/rules/000-session-orchestrator.mdc +3 -2
- package/.cursor/rules/040-discovery.mdc +6 -6
- package/.cursor/rules/050-plan.mdc +8 -8
- package/CHANGELOG.md +101 -0
- package/README.md +10 -10
- package/agents/memory-proposal-collector.md +6 -4
- package/commands/eli5.md +33 -0
- package/commands/release.md +5 -3
- package/commands/test.md +2 -2
- package/docs/components.md +6 -5
- package/docs/scope-collision-guard.md +3 -3
- package/docs/session-config-reference.md +31 -8
- package/hooks/_lib/lock-bootstrap.mjs +19 -13
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +11 -1
- package/hooks/on-session-end.mjs +24 -92
- package/hooks/on-session-start.mjs +195 -104
- package/hooks/pre-auq-clarity.mjs +787 -0
- package/hooks/pre-bash-issue-budget.mjs +17 -18
- package/package.json +3 -1
- package/pi/prompts/eli5.md +12 -0
- package/scripts/auq-audit.mjs +825 -0
- package/scripts/autopilot.mjs +7 -8
- package/scripts/lib/auq/clarity.mjs +1314 -0
- package/scripts/lib/auq/parse.mjs +1006 -0
- package/scripts/lib/auq/schema.mjs +1457 -0
- package/scripts/lib/ci-status-banner.mjs +63 -57
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +32 -9
- package/scripts/lib/config/vault-integration.mjs +12 -1
- package/scripts/lib/dispatcher/rank.mjs +4 -7
- package/scripts/lib/gates/gate-full.mjs +3 -3
- package/scripts/lib/gates/gate-helpers.mjs +17 -6
- package/scripts/lib/io.mjs +239 -0
- package/scripts/lib/issue-budget.mjs +63 -9
- package/scripts/lib/owner-interview.mjs +78 -32
- package/scripts/lib/peer-discovery.mjs +73 -22
- package/scripts/lib/project-hygiene.mjs +64 -4
- package/scripts/lib/reconcile/renderer.mjs +17 -4
- package/scripts/lib/resource-probe/evaluate.mjs +330 -149
- package/scripts/lib/resource-probe/probe-platform.mjs +35 -0
- package/scripts/lib/resource-probe.mjs +18 -2
- package/scripts/lib/spiral-carryover.mjs +23 -2
- package/scripts/lib/state-md/mission-status.mjs +147 -50
- package/scripts/lib/validate/check-auq-clarity.mjs +274 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +30 -0
- package/scripts/lib/validate/check-rules.mjs +153 -9
- package/scripts/lib/vault-backfill/glab.mjs +91 -58
- package/scripts/lib/vault-backfill/manifest.mjs +28 -8
- package/scripts/lib/vcs-repo-spec.mjs +182 -13
- package/scripts/lib/wave-resource-gate.mjs +67 -73
- package/scripts/materialize-wave-scope.mjs +281 -0
- package/scripts/release.mjs +443 -122
- package/scripts/run-quality-gate.mjs +14 -0
- package/scripts/validate-plugin.mjs +3 -0
- package/scripts/validate-wave-scope.mjs +6 -1
- package/scripts/vault-backfill.mjs +32 -5
- package/skills/_shared/parallel-aware-auq.md +30 -24
- package/skills/_shared/parallel-aware-preamble.md +31 -2
- package/skills/_shared/state-ownership.md +32 -6
- package/skills/bootstrap/SKILL.md +2 -1
- package/skills/brainstorm/SKILL.md +18 -18
- package/skills/brainstorm/soul.md +12 -0
- package/skills/discovery/SKILL.md +28 -24
- package/skills/eli5/SKILL.md +43 -0
- package/skills/evolve/SKILL.md +8 -9
- package/skills/gitlab-ops/SKILL.md +30 -26
- package/skills/grill/SKILL.md +6 -6
- package/skills/grill/soul.md +16 -0
- package/skills/memory-cleanup/SKILL.md +2 -2
- package/skills/npm-publish/SKILL.md +4 -4
- package/skills/peekaboo-driver/SKILL.md +3 -3
- package/skills/plan/SKILL.md +18 -16
- package/skills/plan/mode-feature.md +1 -1
- package/skills/plan/mode-new.md +35 -23
- package/skills/plan/soul.md +12 -0
- package/skills/reconcile/SKILL.md +3 -3
- package/skills/session-end/SKILL.md +53 -20
- package/skills/session-end/phase-3-6-tail.md +37 -2
- package/skills/session-start/SKILL.md +69 -35
- package/skills/session-start/phase-2-5-docs-planning.md +8 -8
- package/skills/session-start/phase-4-5-resource-health.md +82 -19
- package/skills/session-start/soul.md +110 -0
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/using-orchestrator/SKILL.md +1 -1
- package/skills/wave-executor/wave-loop.md +27 -5
- package/skills/write-executable-plan/SKILL.md +6 -6
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +0 -10
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +0 -9
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +0 -8
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +0 -7
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +0 -9
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +0 -11
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +0 -11
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/schema-drift.test.mjs +0 -133
|
@@ -64,6 +64,116 @@ The active level is `efficiency.output-level` in `~/.config/session-orchestrator
|
|
|
64
64
|
- Shape: explain the WHY behind each recommendation, name the alternatives you rejected and why, spell out unfamiliar terms on first use.
|
|
65
65
|
- Escalation: `expand <topic>` — see § Escalation above.
|
|
66
66
|
|
|
67
|
+
### Register — how a sentence reads
|
|
68
|
+
|
|
69
|
+
The budgets above set *how much* you say. This sets *how*. It binds at every
|
|
70
|
+
level and is not itself a budget: applying it changes word order and word
|
|
71
|
+
choice, not line count. It is the canonical statement for this repo — the
|
|
72
|
+
other three souls (`plan`, `brainstorm`, `grill`) point here rather than
|
|
73
|
+
copying it.
|
|
74
|
+
|
|
75
|
+
**Write for someone who knows this project but has not seen what you just saw.**
|
|
76
|
+
What he needs to decide stands in the text, not in the file it points at — in
|
|
77
|
+
the AUQ payload and in every finding you post.
|
|
78
|
+
|
|
79
|
+
This is not "explain it like he is five". The operator owns this repo. He is
|
|
80
|
+
not missing knowledge, he is missing **observation** — he did not watch the
|
|
81
|
+
command you just ran or read the file you just opened. A knowledge framing
|
|
82
|
+
would be factually wrong and condescending at the same time. Write across, not
|
|
83
|
+
down: same expertise as yours, minus your last ten minutes.
|
|
84
|
+
|
|
85
|
+
#### Plain words, real things
|
|
86
|
+
|
|
87
|
+
> **Say more simply what actually happens — and introduce nothing that does not exist.**
|
|
88
|
+
>
|
|
89
|
+
> **The test:** delete every noun the system does not contain. If the sentence
|
|
90
|
+
> is still true and complete, it was no analogy. If it collapses, the analogy
|
|
91
|
+
> was load-bearing — replace it with a description of what actually happens.
|
|
92
|
+
|
|
93
|
+
Five worked cases, in rising difficulty:
|
|
94
|
+
|
|
95
|
+
1. "Waiting means the other session finishes first." — **allowed.** Sessions
|
|
96
|
+
and waiting both exist; nothing foreign was introduced.
|
|
97
|
+
2. "Think of the session as a level crossing." — **forbidden.** Delete "level
|
|
98
|
+
crossing" and nothing is left. Say what happens instead: one session holds
|
|
99
|
+
`.orchestrator/session.lock`, the other waits for it.
|
|
100
|
+
3. "The token budget is used up." — **allowed.** `TOKEN_BUDGET_EXCEEDED` is a
|
|
101
|
+
real identifier and "budget" is the system's own word. Adding "…like a tank
|
|
102
|
+
of fuel" would be forbidden — the tank does not exist.
|
|
103
|
+
4. "Think of the kill-switches as a fuse box." — **forbidden, and wrong on the
|
|
104
|
+
facts.** Fuses trip on overload; the kill-switches also test elapsed time and
|
|
105
|
+
confidence. The image sounds helpful and is not. A wrong picture costs more
|
|
106
|
+
than no picture, because the operator reasons from it.
|
|
107
|
+
5. **Dead metaphors.** A proper name may itself be a metaphor —
|
|
108
|
+
`pre-bash-destructive-guard` is called a guard — and you use the name as
|
|
109
|
+
given. Reviving the image is the violation: "the guard will not let it
|
|
110
|
+
through" invites the operator to picture a guard and then reason from the
|
|
111
|
+
picture instead of from the hook. Name the identifier, then say what it
|
|
112
|
+
does: the hook denies the Bash call.
|
|
113
|
+
|
|
114
|
+
#### Precedence over § "Never traded for brevity"
|
|
115
|
+
|
|
116
|
+
There is a real collision above: "say it more simply" can water down a precise
|
|
117
|
+
error message. Resolve it in three steps.
|
|
118
|
+
|
|
119
|
+
1. **Simplifying removes words, never facts.** If a path, a number, an error
|
|
120
|
+
code, an identifier, or an instruction to act disappears, that is data loss,
|
|
121
|
+
not simplification — and § "Never traded for brevity" already forbids it.
|
|
122
|
+
2. **When both will not fit in one sentence: precision in the sentence,
|
|
123
|
+
plainness in the one beside it.** The exact term is never replaced, only
|
|
124
|
+
accompanied. It is what the operator greps, quotes, and pastes into an issue.
|
|
125
|
+
3. **The mechanical tie-breaker:** could the token you are about to cut ever
|
|
126
|
+
appear in a `grep`? Then it stays.
|
|
127
|
+
|
|
128
|
+
Measured 2026-08-22 at `a4f93cf`: of 191 option descriptions in this repo, the
|
|
129
|
+
20 that match the safety lexicon (`SAFETY_PATTERN` in
|
|
130
|
+
`scripts/lib/auq/schema.mjs`) run 26–108 codepoints — all of them under both K6
|
|
131
|
+
length thresholds (`descriptionCharsWarn` 120, `descriptionCharsFail` 150). The
|
|
132
|
+
collision therefore does not occur today. This precedence rule is a precaution,
|
|
133
|
+
not a repair.
|
|
134
|
+
|
|
135
|
+
#### Worked example — an operator-visible message
|
|
136
|
+
|
|
137
|
+
`formatBlockReason()` in `scripts/lib/issue-budget.mjs` is what the operator
|
|
138
|
+
sees when the issue cap blocks a creation. Rendered with the collector-issue
|
|
139
|
+
sink, before:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
issue-budget: session cap reached — 12/12 issues already created.
|
|
143
|
+
This request was NOT created. It is parked as overflow entry #3 in:
|
|
144
|
+
.orchestrator/runtime/issue-budget-overflow.jsonl
|
|
145
|
+
session-end Phase 5 will fold all overflow entries into ONE collector issue `[Backlog-Sammel] <session-id>, N zurückgestellte Punkte`. Nothing is lost.
|
|
146
|
+
Exempt from the cap: priority::critical, the carryover class (SPIRAL/FAILED, [Carryover]),
|
|
147
|
+
and broken-window closure issues — those are never deferred.
|
|
148
|
+
To raise the cap for this repo, edit `issue-budget.max-per-session` in the Session Config;
|
|
149
|
+
`mode: warn` reports without blocking, `mode: off` disables the gate.
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
After:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
Nothing is lost — the issue is parked, and nothing needs doing right now.
|
|
156
|
+
issue-budget: session cap reached — 12/12 issues already created, so this one was NOT created.
|
|
157
|
+
It is parked as overflow entry #3 in:
|
|
158
|
+
.orchestrator/runtime/issue-budget-overflow.jsonl
|
|
159
|
+
session-end Phase 5 folds all overflow entries into ONE collector issue `[Backlog-Sammel] <session-id>, N zurückgestellte Punkte`.
|
|
160
|
+
Exempt from the cap: priority::critical, the carryover class (SPIRAL/FAILED, [Carryover]),
|
|
161
|
+
and broken-window closure issues — those are never deferred.
|
|
162
|
+
To raise the cap for this repo, edit `issue-budget.max-per-session` in the Session Config; `mode: warn` reports without blocking, `mode: off` disables the gate.
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Three changes, and only these three: the operator's own question — *must I do
|
|
166
|
+
something?* — moved to line 1, carrying "Nothing is lost" up from line 4 where
|
|
167
|
+
he used to reach it last; `will fold` became the active `folds`; and the first
|
|
168
|
+
two lines merged on a causal `so`, which is why "This request" is now "this
|
|
169
|
+
one" — the same subject, already named in the sentence.
|
|
170
|
+
|
|
171
|
+
The whole word-level diff is four dropped tokens: `This`, `request`, `will`,
|
|
172
|
+
`fold`. Not one of them is a path, a count, a label, a config key, or a mode
|
|
173
|
+
value; every one of those survives character for character. Eight lines before,
|
|
174
|
+
eight lines after — **this register is not a diet.** It is the same facts, in
|
|
175
|
+
the order the reader needs them.
|
|
176
|
+
|
|
67
177
|
### Companion dials
|
|
68
178
|
|
|
69
179
|
Same file, same lookup, same fallback-to-default rule:
|
|
@@ -216,11 +216,11 @@ Group `medium` and `low` findings and present via a single `AskUserQuestion` cal
|
|
|
216
216
|
AskUserQuestion({
|
|
217
217
|
questions: [{
|
|
218
218
|
question: `<N> medium/low findings to triage. How to handle?`,
|
|
219
|
-
header: "
|
|
219
|
+
header: "Triage",
|
|
220
220
|
options: [
|
|
221
221
|
{
|
|
222
222
|
label: "Create all (Recommended)",
|
|
223
|
-
description: "
|
|
223
|
+
description: "Files <N> new issues at once, all with label from:test-runner — fastest, and you can still close any of them afterwards."
|
|
224
224
|
},
|
|
225
225
|
{
|
|
226
226
|
label: "Review each",
|
|
@@ -67,7 +67,7 @@ When two or more candidates score ≥ 0.85 with delta < 0.15, call `AskUserQuest
|
|
|
67
67
|
AskUserQuestion({
|
|
68
68
|
questions: [{
|
|
69
69
|
question: "Your message matches multiple workflows. Which one did you mean?",
|
|
70
|
-
header: "
|
|
70
|
+
header: "Dispatch",
|
|
71
71
|
multiSelect: false,
|
|
72
72
|
options: [
|
|
73
73
|
{ label: "<top-skill display name> (Recommended)", description: "<one-line description of what it does>" },
|
|
@@ -1114,20 +1114,40 @@ Before each wave dispatch:
|
|
|
1114
1114
|
```
|
|
1115
1115
|
The `gates` field (optional) mirrors `enforcement-gates` from Session Config (#77). When present, hooks check each gate individually via `gate_enabled()`. Missing gate entries default to enabled, preserving default behavior.
|
|
1116
1116
|
2. Validate by piping through `node "$PLUGIN_ROOT/scripts/validate-wave-scope.mjs"` (where `$PLUGIN_ROOT` is `$CLAUDE_PLUGIN_ROOT`, `$CODEX_PLUGIN_ROOT`, or `$CURSOR_RULES_DIR` per platform — see `skills/_shared/config-reading.md`). If validation fails (exit 1), fix the JSON based on stderr errors and retry.
|
|
1117
|
-
3. **`allowedPaths` is COMPUTED from
|
|
1117
|
+
3. **`allowedPaths` is COMPUTED from one canonical declaration array — never hand-transcribed (#1020/#1083).** Transcribing either declaration shape or the union by hand produced scope divergences. Globs stay verbatim (`scripts/*.sh`) — the enforcement hook resolves them at check time.
|
|
1118
1118
|
|
|
1119
|
-
**3.1 —
|
|
1119
|
+
**3.1 — materialize both declaration shapes once.** Build one JSON array from the session plan, one `{id, files}` record for every agent plus exactly one `coordinator` record for the coordinator's planned direct edits. `files` arrays, their entries and their order are the plan's verbatim declarations. Materialize it ONCE and capture the aggregate-sidecar path:
|
|
1120
1120
|
|
|
1121
|
-
|
|
1121
|
+
```bash
|
|
1122
|
+
WAVE_SCOPE_RECORDS='[{"id":"W3-I1","files":["scripts/example.mjs"]},{"id":"coordinator","files":["skills/wave-executor/wave-loop.md"]}]'
|
|
1123
|
+
WAVE_SCOPES_SIDECAR="$(
|
|
1124
|
+
printf '%s' "$WAVE_SCOPE_RECORDS" | node "$PLUGIN_ROOT/scripts/materialize-wave-scope.mjs" \
|
|
1125
|
+
--state-dir "$STATE_DIR" --wave "$WAVE"
|
|
1126
|
+
)"
|
|
1127
|
+
[ -n "$WAVE_SCOPES_SIDECAR" ] || { echo "materialize-wave-scope produced no sidecar path" >&2; exit 1; }
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
The non-empty check is not decoration. The materializer sends every diagnostic
|
|
1131
|
+
to stderr, so a failure leaves `$WAVE_SCOPES_SIDECAR` empty, and an empty path
|
|
1132
|
+
is what step 3.2 would then pass to `--assert-disjoint`. That combination used
|
|
1133
|
+
to exit 0 with the collision gate never run — the same signal-free-ALLOW shape
|
|
1134
|
+
#1083 exists to close. `validate-wave-scope.mjs` now refuses an empty flag
|
|
1135
|
+
value as well, so this guard and that refusal are belt and braces.
|
|
1136
|
+
|
|
1137
|
+
`materialize-wave-scope.mjs` validates the COMPLETE input before writing; it writes `<state-dir>/filescopes/wave-<N>/<agent-id>.json` as each bare `files` array first, then writes `<state-dir>/filescopes/wave-<N>.scopes.json` as the unchanged aggregate record array last. Its human stdout is only that final sidecar path, so the command substitution above is the canonical `$WAVE_SCOPES_SIDECAR`. On error, do not continue with a partial declaration set; correct the plan and run the one command again.
|
|
1122
1138
|
|
|
1123
|
-
|
|
1139
|
+
The per-agent path IS `$AGENT_FILESCOPE_JSON` — the same file `--assert-subset` (#796 below), Grounding Injection (#85), the Learnings-Index (#1014) and File-Scope Injection (#1020) consume. Never write a `$TMPDIR` copy: it degrades to a signal-free allow when an injector cannot find the addressable wave-keyed file. The coordinator's record is materialized as `coordinator.json` and included in the aggregate, so its direct edits are covered by the two checks below.
|
|
1140
|
+
|
|
1141
|
+
> **`<state-dir>/filescopes/` is control state, like `wave-scope.json` itself — never a wave territory.** Step 3.1 necessarily runs before the union exists, so writing these files reports `bash-write-verify: N file(s) changed by a Bash call OUTSIDE the wave's allowedPaths` naming `filescopes/wave-<N>/*.json`. Expected once per wave rollover at this step; it is information, not a scope violation. Never widen `allowedPaths` to silence it — that would grant agents write access to the deconfliction record itself.
|
|
1142
|
+
|
|
1143
|
+
**3.2 — assert disjointness BEFORE computing the union.** The materialized aggregate is an ARRAY of `{id, files}` records (never an object map: a duplicated agent id must stay visible), including `coordinator.json`. Run:
|
|
1124
1144
|
|
|
1125
1145
|
```bash
|
|
1126
1146
|
node "$PLUGIN_ROOT/scripts/validate-wave-scope.mjs" \
|
|
1127
1147
|
--assert-disjoint "$WAVE_SCOPES_SIDECAR" < <state-dir>/wave-scope.json
|
|
1128
1148
|
```
|
|
1129
1149
|
|
|
1130
|
-
Exit 1 (one stderr message per collision) means two agents were handed the same file: fix the session plan,
|
|
1150
|
+
Exit 1 (one stderr message per collision) means two agents were handed the same file: fix the session plan, re-materialize, re-assert. Never widen the union to make it pass. This runs **before** 3.3 because a union computed over colliding scopes launders the defect into the very artefact meant to prevent it — `allowedPaths` then grants the file and every later gate sees a legal write.
|
|
1131
1151
|
|
|
1132
1152
|
**3.3 — compute the union.** `--union` is a QUERY MODE that still requires a schema-valid manifest on stdin, so write the skeleton first with `"allowedPaths": []`, then:
|
|
1133
1153
|
|
|
@@ -1138,6 +1158,8 @@ Before each wave dispatch:
|
|
|
1138
1158
|
|
|
1139
1159
|
It prints the computed `allowedPaths` array as JSON on stdout **instead of** the manifest echo — one JSON document per run, the flag decides which. Insert that array as `allowedPaths`, then write the final `wave-scope.json`. It already applies the Test-Sibling Expansion below (`expandTestSiblings(unionFileScopes(scopes), { role })`, role read from the manifest), so do not also run the helper by hand.
|
|
1140
1160
|
|
|
1161
|
+
**Artifact production, disjointness and union computation are mechanized. Native prompt injection is a separate follow-up.** The materializer creates the durable declarations; the validator proves disjointness and computes the union. It does not install or prove the platform's prompt-injection transport, which remains independently responsible for reading `$AGENT_FILESCOPE_JSON` before dispatch.
|
|
1162
|
+
|
|
1141
1163
|
**The `--assert-subset` assertion (#796, below) stays unchanged and keeps running.** It checks a DIFFERENT property — each agent's scope ⊆ the union — and a double assignment is structurally invisible to it: a file claimed twice is a subset twice over. `--assert-disjoint` is an addition, never a replacement.
|
|
1142
1164
|
|
|
1143
1165
|
**Test-Sibling Expansion (#970):** an `allowedPaths` entry that names a production file but NOT its test sibling makes the wave's own regression test unwritable — the scope guard then mechanically enforces exactly the inconsistency the quality gate exists to catch. Cross-repo evidence, three occurrences in ONE session: a migrations glob without the SQL-test directory (the regression test could not be written); a lone `.actions.ts` file (the wave's cross-tenant security test stayed red); a dead-export deletion whose importing test lay outside every scope (the suite ended red). Do NOT hand-derive the sibling paths — step 3.3's `--union` runs `expandTestSiblings(…, { role })` for you, so the hook, the validator and this prose state one rule.
|
|
@@ -163,11 +163,11 @@ Any hit → surface to user via AUQ:
|
|
|
163
163
|
```
|
|
164
164
|
AskUserQuestion({
|
|
165
165
|
questions: [{
|
|
166
|
-
question: "The
|
|
167
|
-
header: "
|
|
166
|
+
question: "The draft plan still contains forbidden placeholder strings. How do you want to proceed?",
|
|
167
|
+
header: "Platzhalter",
|
|
168
168
|
options: [
|
|
169
|
-
{ label: "Fix automatically (Recommended)", description: "I
|
|
170
|
-
{ label: "Show me each hit interactively", description: "Walk me through each one so I can provide the missing detail." }
|
|
169
|
+
{ label: "Fix automatically (Recommended)", description: "I fill in the missing specifics myself, then write the plan — fastest, and you review the result at hand-off.", preview: "[each hit: Task number, Step number, matched text]" },
|
|
170
|
+
{ label: "Show me each hit interactively", description: "Walk me through each one so I can provide the missing detail.", preview: "[the same hits, one at a time, starting with the first]" }
|
|
171
171
|
],
|
|
172
172
|
multiSelect: false
|
|
173
173
|
}]
|
|
@@ -202,8 +202,8 @@ Present the plan path and task count to the user via AUQ:
|
|
|
202
202
|
```
|
|
203
203
|
AskUserQuestion({
|
|
204
204
|
questions: [{
|
|
205
|
-
question: "
|
|
206
|
-
header: "Plan
|
|
205
|
+
question: "Plan written to docs/plans/YYYY-MM-DD-<slug>.md — [N] tasks, ~[total] min. How to execute?",
|
|
206
|
+
header: "Plan fertig",
|
|
207
207
|
options: [
|
|
208
208
|
{ label: "Dispatch via wave-executor (Recommended)", description: "Parallel agents execute all tasks simultaneously. Fastest for file-disjoint tasks." },
|
|
209
209
|
{ label: "Execute coordinator-direct", description: "One task at a time in this session. Safer for tasks with shared state." },
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Sample Baseline Rule (test fixture)
|
|
2
|
-
|
|
3
|
-
This file is used by scripts/tests/fetch-baseline.bats as the mocked response body
|
|
4
|
-
for the GitLab API raw-file endpoint. It has no real content — presence is enough
|
|
5
|
-
to verify the fetch + cache path.
|
|
6
|
-
|
|
7
|
-
- Rule ID: TEST-001
|
|
8
|
-
- Purpose: end-to-end fetch verification
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: nested-tags-note
|
|
3
|
-
type: note
|
|
4
|
-
created: 2026-04-13
|
|
5
|
-
updated: 2026-04-13
|
|
6
|
-
title: Nested Tags Note
|
|
7
|
-
tags: [meta/schema, status/in-progress, area/infrastructure, session/2026-04-13]
|
|
8
|
-
status: active
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
A note that uses Obsidian nested-tag conventions (slash-separated hierarchy).
|
|
File without changes
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Map of Content
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Map of Content
|
|
6
|
-
|
|
7
|
-
This is an index file that intentionally lacks the full note frontmatter
|
|
8
|
-
(no `id`, `type`, `created`, or `updated`). Validator should report errors
|
|
9
|
-
unless this file is excluded via `--exclude "**/_MOC.md"`.
|
|
10
|
-
|
|
11
|
-
- [[hello-world]]
|
|
File without changes
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* schema-drift.test.mjs — Integration tests for sync-vault-schema.mjs
|
|
3
|
-
*
|
|
4
|
-
* Verifies that the vendored schema block in validator.mjs stays in lockstep
|
|
5
|
-
* with the canonical TypeScript source. Tests run the actual script against
|
|
6
|
-
* actual files — no mocks.
|
|
7
|
-
*
|
|
8
|
-
* Run from session-orchestrator root:
|
|
9
|
-
* pnpm exec vitest run skills/vault-sync/tests/schema-drift.test.mjs
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { describe, it, expect } from 'vitest';
|
|
13
|
-
import { execFileSync } from 'node:child_process';
|
|
14
|
-
import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from 'node:fs';
|
|
15
|
-
import { join, dirname } from 'node:path';
|
|
16
|
-
import { fileURLToPath } from 'node:url';
|
|
17
|
-
import { tmpdir } from 'node:os';
|
|
18
|
-
|
|
19
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
-
|
|
21
|
-
// Paths relative to this test file
|
|
22
|
-
const SYNC_SCRIPT = join(__dirname, '../../../scripts/sync-vault-schema.mjs');
|
|
23
|
-
const VALIDATOR_MJS = join(__dirname, '../validator.mjs');
|
|
24
|
-
const CANONICAL = join(
|
|
25
|
-
__dirname,
|
|
26
|
-
'../../../../projects-baseline/packages/zod-schemas/src/vault-frontmatter.ts',
|
|
27
|
-
);
|
|
28
|
-
// Tests below shell out to sync-vault-schema.mjs, which by default reads
|
|
29
|
-
// the canonical schema from a sibling projects-baseline checkout. In CI
|
|
30
|
-
// (and on any contributor machine without that checkout) the canonical
|
|
31
|
-
// file is absent, so we skip these integration tests. The schema-drift
|
|
32
|
-
// CI stage runs the script directly against a freshly cloned canonical.
|
|
33
|
-
const HAS_CANONICAL = existsSync(CANONICAL);
|
|
34
|
-
|
|
35
|
-
const SENTINEL_BEGIN = 'BEGIN GENERATED SCHEMA';
|
|
36
|
-
const SENTINEL_END = 'END GENERATED SCHEMA';
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Run sync-vault-schema.mjs with given args.
|
|
40
|
-
* Returns { status, stdout, stderr }.
|
|
41
|
-
* Never throws — captures non-zero exits via try/catch.
|
|
42
|
-
*/
|
|
43
|
-
function runScript(args = []) {
|
|
44
|
-
try {
|
|
45
|
-
const stdout = execFileSync(process.execPath, [SYNC_SCRIPT, ...args], {
|
|
46
|
-
encoding: 'utf-8',
|
|
47
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
48
|
-
});
|
|
49
|
-
return { status: 0, stdout, stderr: '' };
|
|
50
|
-
} catch (err) {
|
|
51
|
-
return {
|
|
52
|
-
status: err.status ?? 1,
|
|
53
|
-
stdout: err.stdout ?? '',
|
|
54
|
-
stderr: err.stderr ?? '',
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
describe.skipIf(!HAS_CANONICAL)('sync-vault-schema.mjs', () => {
|
|
60
|
-
it('idempotency — running --write twice produces no diff between runs', () => {
|
|
61
|
-
// Read validator.mjs before any writes
|
|
62
|
-
const before = readFileSync(VALIDATOR_MJS, 'utf-8');
|
|
63
|
-
|
|
64
|
-
// First --write
|
|
65
|
-
const first = runScript(['--write']);
|
|
66
|
-
expect(first.status, `first --write failed: ${first.stderr}`).toBe(0);
|
|
67
|
-
|
|
68
|
-
// Read after first write
|
|
69
|
-
const afterFirst = readFileSync(VALIDATOR_MJS, 'utf-8');
|
|
70
|
-
expect(afterFirst).toBe(before);
|
|
71
|
-
|
|
72
|
-
// Second --write
|
|
73
|
-
const second = runScript(['--write']);
|
|
74
|
-
expect(second.status, `second --write failed: ${second.stderr}`).toBe(0);
|
|
75
|
-
|
|
76
|
-
// Read after second write
|
|
77
|
-
const afterSecond = readFileSync(VALIDATOR_MJS, 'utf-8');
|
|
78
|
-
expect(afterSecond).toBe(before);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('check-mode-clean — --check exits 0 when canonical and vendored are in sync', () => {
|
|
82
|
-
const result = runScript(['--check']);
|
|
83
|
-
expect(result.status, `--check failed unexpectedly: ${result.stderr}`).toBe(0);
|
|
84
|
-
expect(result.stdout).toContain('no drift');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('check-mode-detects-drift — --check exits 1 with diff when canonical differs', () => {
|
|
88
|
-
// Build a temporary fake canonical with a different max for the id field
|
|
89
|
-
const rand = Math.random().toString(36).slice(2);
|
|
90
|
-
const tmpDir = join(tmpdir(), `sync-vault-schema-test-${rand}`);
|
|
91
|
-
mkdirSync(tmpDir, { recursive: true });
|
|
92
|
-
const fakeCanonical = join(tmpDir, 'vault-frontmatter.ts');
|
|
93
|
-
|
|
94
|
-
// Read the real canonical and alter it: change .max(128) to .max(256) for id
|
|
95
|
-
const realCanonical = readFileSync(CANONICAL, 'utf-8');
|
|
96
|
-
const altered = realCanonical.replace('.max(128)', '.max(256)');
|
|
97
|
-
writeFileSync(fakeCanonical, altered, 'utf-8');
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
const result = runScript(['--check', '--canonical', fakeCanonical]);
|
|
101
|
-
expect(result.status, 'expected exit 1 for drift').toBe(1);
|
|
102
|
-
// Diff should mention the changed value
|
|
103
|
-
expect(result.stderr).toMatch(/256|drift/);
|
|
104
|
-
} finally {
|
|
105
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('failure-mode-missing-canonical — exits 2 with clear message when canonical not found', () => {
|
|
110
|
-
const rand = Math.random().toString(36).slice(2);
|
|
111
|
-
const fakePath = join(tmpdir(), `does-not-exist-${rand}.ts`);
|
|
112
|
-
|
|
113
|
-
const result = runScript(['--check', '--canonical', fakePath]);
|
|
114
|
-
expect(result.status, 'expected exit 2 for missing file').toBe(2);
|
|
115
|
-
expect(result.stderr).toContain(fakePath);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('sentinel-presence — validator.mjs contains both sentinel markers exactly once in correct order', () => {
|
|
119
|
-
const content = readFileSync(VALIDATOR_MJS, 'utf-8');
|
|
120
|
-
|
|
121
|
-
// Count occurrences
|
|
122
|
-
const beginCount = (content.match(/BEGIN GENERATED SCHEMA/g) ?? []).length;
|
|
123
|
-
const endCount = (content.match(/END GENERATED SCHEMA/g) ?? []).length;
|
|
124
|
-
|
|
125
|
-
expect(beginCount, 'BEGIN GENERATED SCHEMA must appear exactly once').toBe(1);
|
|
126
|
-
expect(endCount, 'END GENERATED SCHEMA must appear exactly once').toBe(1);
|
|
127
|
-
|
|
128
|
-
// BEGIN must appear before END
|
|
129
|
-
const beginPos = content.indexOf(SENTINEL_BEGIN);
|
|
130
|
-
const endPos = content.indexOf(SENTINEL_END);
|
|
131
|
-
expect(beginPos, 'BEGIN sentinel must appear before END sentinel').toBeLessThan(endPos);
|
|
132
|
-
});
|
|
133
|
-
});
|