@mmerterden/multi-agent-pipeline 12.9.0 → 12.11.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/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,118 @@ Internal file-layout changes that don't affect the slash-command surface are sti
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [12.11.0] - 2026-07-26
|
|
20
|
+
|
|
21
|
+
### Tracker tile titles rendered HTML entities
|
|
22
|
+
|
|
23
|
+
A live run showed this in the TaskList widget:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Phase 1: Build & Launch
|
|
27
|
+
Phase 3: Drive & Compare
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`rules.md` already forbids entities in "titles, commit messages, task subjects, or
|
|
31
|
+
body text" because nothing downstream decodes them. A tile title IS a task subject,
|
|
32
|
+
so the rule covered this exactly. `output-quality-check.sh` enforced it over the PR
|
|
33
|
+
body and the Jira comment and nowhere else, so the one surface where the rule was
|
|
34
|
+
actually broken was the one surface nothing inspected. The gap was in the check's
|
|
35
|
+
coverage, not in the rule.
|
|
36
|
+
|
|
37
|
+
`phase-tracker.sh add` is the single funnel for every tile title, so it decodes
|
|
38
|
+
there, and warns on stderr: decoding silently would fix the display and hide the
|
|
39
|
+
bug that produced it.
|
|
40
|
+
|
|
41
|
+
**All three encodings**, because a title can arrive in any of them:
|
|
42
|
+
|
|
43
|
+
| | ampersand | less-than | em-dash |
|
|
44
|
+
|---|---|---|---|
|
|
45
|
+
| named | `&` | `<` | `—` |
|
|
46
|
+
| decimal | `&` | `<` | `—` |
|
|
47
|
+
| hex | `&` | `<` | `—` |
|
|
48
|
+
|
|
49
|
+
The first version of this fix handled only the named column, which is the same bug
|
|
50
|
+
fixed for one spelling out of three.
|
|
51
|
+
|
|
52
|
+
Two subtleties, each pinned by a test:
|
|
53
|
+
|
|
54
|
+
- **Nesting.** `&lt;` means the literal text `<`, not `<`. One
|
|
55
|
+
left-to-right pass gets this right for free, because a global replace never
|
|
56
|
+
rescans its own output. An ordered-sed version needed `&` decoded last for
|
|
57
|
+
the same effect, and that ordering was a latent trap.
|
|
58
|
+
- **Fancy punctuation degrades to ASCII.** `—` becomes `-`, never an
|
|
59
|
+
em-dash. This repo bans em/en-dash and ellipsis in shipped text and gates it in
|
|
60
|
+
the scorecard, so decoding them faithfully would make this function INJECT what
|
|
61
|
+
another gate rejects. Applies to the numeric spellings too, or the named form
|
|
62
|
+
would be safe while `—` still injected.
|
|
63
|
+
|
|
64
|
+
An unknown named entity is left alone, so a title legitimately containing `&foo;`
|
|
65
|
+
survives.
|
|
66
|
+
|
|
67
|
+
`output-quality-check.sh` gains a tracker-title backstop covering all three
|
|
68
|
+
encodings: an entity reaching the stored state means the funnel was bypassed.
|
|
69
|
+
|
|
70
|
+
The decoder is fed through a QUOTED HEREDOC rather than `node -e '...'`. Inside a
|
|
71
|
+
single-quoted shell string every apostrophe in the JS terminates the string early -
|
|
72
|
+
including, at one point, the apostrophe in the comment explaining the problem.
|
|
73
|
+
shellcheck flags it (SC2140) and the first version worked only by accident of where
|
|
74
|
+
the quotes fell.
|
|
75
|
+
|
|
76
|
+
12 tests. 306 unit total.
|
|
77
|
+
|
|
78
|
+
## [12.10.0] - 2026-07-26
|
|
79
|
+
|
|
80
|
+
Two pieces of abandoned residue, and a gate that had inverted.
|
|
81
|
+
|
|
82
|
+
### `~/.multi-agent/` is pruned on install
|
|
83
|
+
|
|
84
|
+
The "shared runtime" the Cursor / Antigravity / VS Code Copilot Chat adapters
|
|
85
|
+
needed, so their emitted agents could reach the gate scripts by absolute path.
|
|
86
|
+
Those adapters were deleted in v10.7.0 along with `installSharedRuntime`,
|
|
87
|
+
`_base.mjs`, `rewriteScriptRefs` and `smoke-shared-runtime.sh` - but not the tree
|
|
88
|
+
they had written. 222 files (174 scripts, 23 lib, 24 schemas) sat frozen at
|
|
89
|
+
whatever the last adapter-era install produced.
|
|
90
|
+
|
|
91
|
+
It is worse than dead weight because it is indistinguishable from a live install
|
|
92
|
+
when you look at it: same directory names, same file names. Its
|
|
93
|
+
`schemas/migrations/` is missing `prefs-2.3.0-to-2.4.0.mjs`, so reading it gives a
|
|
94
|
+
migration chain three versions short and a reasonable conclusion that the chain
|
|
95
|
+
itself is stale.
|
|
96
|
+
|
|
97
|
+
`ABANDONED_TREES` now takes `root: "home"` entries for trees beside `~/.claude`
|
|
98
|
+
rather than inside it, and 253 files (this plus the 31 in `~/.claude/eval`) are
|
|
99
|
+
removed on the next install.
|
|
100
|
+
|
|
101
|
+
### The live-prefs gate was rejecting the only correct state
|
|
102
|
+
|
|
103
|
+
`smoke-schema-validation.sh` step 7 read:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
if [ "$LVER" = "2.1.0" ]; then pass "live prefs already v2.1.0"
|
|
107
|
+
elif ... else fail "live prefs has unknown schemaVersion: $LVER"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`migrate-prefs.mjs` `TARGET_VERSION` had since moved to 2.4.0, which inverted the
|
|
111
|
+
gate: a prefs file left behind at 2.1.0 passed as "already current", and a file
|
|
112
|
+
correctly migrated to 2.4.0 fell through to the else arm and FAILED as "unknown".
|
|
113
|
+
The gate rejected the fully-migrated state it exists to encourage.
|
|
114
|
+
|
|
115
|
+
It now reads the target from `migrate-prefs.mjs` and the accepted set from the
|
|
116
|
+
schema's own `schemaVersion` enum, so it cannot disagree with either again, and a
|
|
117
|
+
known-but-older version reports how far behind it is (`v2.1.0 -> v2.4.0`) instead
|
|
118
|
+
of reading as current. This is a CONSUMER smoke (`/multi-agent:update` runs it), so
|
|
119
|
+
it resolves both paths through `SMOKE_DIR`, which is `pipeline/scripts` in the repo
|
|
120
|
+
and `~/.claude/scripts` in an install.
|
|
121
|
+
|
|
122
|
+
### Dead-file sweep
|
|
123
|
+
|
|
124
|
+
Swept refs, schemas, agents, libs and scripts for files nothing references. No
|
|
125
|
+
orphans. The two candidates were both false positives from the sweep's own exclude
|
|
126
|
+
flags: `design-check-config.schema.json` is referenced as the instance filename by
|
|
127
|
+
the design-check command, and `count-lib.sh` is sourced by two siblings inside
|
|
128
|
+
`pipeline/lib/`, which the sweep had excluded. Recorded here so the next sweep does
|
|
129
|
+
not re-flag them.
|
|
130
|
+
|
|
19
131
|
## [12.9.0] - 2026-07-26
|
|
20
132
|
|
|
21
133
|
Context engineering follow-through, and a guard that was blocking safe commands.
|
package/install/_common.mjs
CHANGED
|
@@ -252,25 +252,46 @@ export function pruneLegacyMultiAgentSkills(skillsDir) {
|
|
|
252
252
|
* neither did uninstall - 31 stale files sat there with nothing left that could
|
|
253
253
|
* ever read or remove them.
|
|
254
254
|
*
|
|
255
|
-
*
|
|
255
|
+
* `~/.multi-agent/` is the larger case: the "shared runtime" the Cursor /
|
|
256
|
+
* Antigravity / VS Code Copilot Chat adapters needed, so their emitted agents
|
|
257
|
+
* could reach the gate scripts by absolute path. Those adapters were deleted in
|
|
258
|
+
* v10.7.0 along with `installSharedRuntime`, `_base.mjs`, `rewriteScriptRefs` and
|
|
259
|
+
* `smoke-shared-runtime.sh` - but not the tree they wrote. 221 files (174 scripts,
|
|
260
|
+
* 23 lib, 24 schemas) frozen at whatever the last adapter-era install produced,
|
|
261
|
+
* including a migrations directory missing `prefs-2.3.0-to-2.4.0.mjs`. Browsing it
|
|
262
|
+
* looks exactly like browsing a current install, which is how it comes to be
|
|
263
|
+
* mistaken for one.
|
|
264
|
+
*
|
|
265
|
+
* `root: "home"` entries sit beside `~/.claude`, not inside it.
|
|
266
|
+
*
|
|
267
|
+
* @type {ReadonlyArray<{dir: string, root?: "claude"|"home", reason: string}>}
|
|
256
268
|
*/
|
|
257
269
|
export const ABANDONED_TREES = Object.freeze([
|
|
258
270
|
{
|
|
259
271
|
dir: "eval",
|
|
260
272
|
reason: "eval corpora; the harnesses that read them are maintainer-only and no longer ship",
|
|
261
273
|
},
|
|
274
|
+
{
|
|
275
|
+
dir: ".multi-agent",
|
|
276
|
+
root: "home",
|
|
277
|
+
reason:
|
|
278
|
+
"shared runtime for the Cursor / Antigravity / Copilot Chat adapters, all deleted in v10.7.0",
|
|
279
|
+
},
|
|
262
280
|
]);
|
|
263
281
|
|
|
264
282
|
/**
|
|
265
|
-
* Remove trees an older install left behind
|
|
283
|
+
* Remove trees an older install left behind.
|
|
266
284
|
*
|
|
267
|
-
* @param {string}
|
|
285
|
+
* @param {string} claudeDir e.g. `$HOME/.claude`
|
|
286
|
+
* @param {string} [home] `$HOME`, for entries marked `root: "home"`. Defaults to
|
|
287
|
+
* the parent of `claudeDir`, which is the layout every installer uses.
|
|
268
288
|
* @returns {number} directories removed
|
|
269
289
|
*/
|
|
270
|
-
export function pruneAbandonedTrees(
|
|
290
|
+
export function pruneAbandonedTrees(claudeDir, home = dirname(claudeDir)) {
|
|
271
291
|
let removed = 0;
|
|
272
|
-
for (const { dir, reason } of ABANDONED_TREES) {
|
|
273
|
-
const
|
|
292
|
+
for (const { dir, root, reason } of ABANDONED_TREES) {
|
|
293
|
+
const base = root === "home" ? home : claudeDir;
|
|
294
|
+
const target = join(base, dir);
|
|
274
295
|
if (!existsSync(target)) continue;
|
|
275
296
|
if (dryRun) {
|
|
276
297
|
console.log(` [dry-run] would remove abandoned tree ${target} (${reason})`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmerterden/multi-agent-pipeline",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.11.0",
|
|
4
4
|
"description": "8-phase AI development pipeline with full orchestration on Claude Code and Copilot CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -89,6 +89,40 @@ else
|
|
|
89
89
|
warn "no .pr-body.md in task dir (skipped PR-body check)"
|
|
90
90
|
fi
|
|
91
91
|
|
|
92
|
+
# 2b. Tracker tile titles
|
|
93
|
+
#
|
|
94
|
+
# `rules.md` forbids HTML entities in "titles, commit messages, task subjects, or
|
|
95
|
+
# body text", but this check only ever looked at the PR body and the Jira comment.
|
|
96
|
+
# Tile titles are task subjects, and that is where the rule actually broke: a real
|
|
97
|
+
# run rendered `Phase 1: Build & Launch` in the TaskList widget. The rule was
|
|
98
|
+
# right and its enforcement was scoped to the wrong two surfaces.
|
|
99
|
+
#
|
|
100
|
+
# `phase-tracker.sh add` now decodes at the funnel, so this is the backstop that
|
|
101
|
+
# says so: an entity reaching the stored state means the funnel was bypassed.
|
|
102
|
+
TRACKER_STATE="$TASK_DIR/tracker-state.json"
|
|
103
|
+
if [ -f "$TRACKER_STATE" ]; then
|
|
104
|
+
BAD_TITLES=$(python3 -c "
|
|
105
|
+
import json, re, sys
|
|
106
|
+
try:
|
|
107
|
+
d = json.load(open('$TRACKER_STATE'))
|
|
108
|
+
except Exception:
|
|
109
|
+
sys.exit(0)
|
|
110
|
+
# named, decimal and hex - a pattern covering only the named forms would miss
|
|
111
|
+
# exactly the two spellings the first version of the decoder missed.
|
|
112
|
+
pat = re.compile(r'&(amp|lt|gt|quot|apos|nbsp|mdash|ndash|hellip|#[0-9]+|#[xX][0-9a-fA-F]+);')
|
|
113
|
+
for p in d.get('phases', []):
|
|
114
|
+
n = p.get('name', '')
|
|
115
|
+
if pat.search(n):
|
|
116
|
+
print('%s: %s' % (p.get('id'), n))
|
|
117
|
+
" 2>/dev/null)
|
|
118
|
+
if [ -n "$BAD_TITLES" ]; then
|
|
119
|
+
bad "tracker tile title(s) carry HTML entities (they render literally):"
|
|
120
|
+
printf '%s\n' "$BAD_TITLES" | sed 's/^/ /'
|
|
121
|
+
else
|
|
122
|
+
ok "tracker tile titles free of HTML entities"
|
|
123
|
+
fi
|
|
124
|
+
fi
|
|
125
|
+
|
|
92
126
|
# 3. Jira comment
|
|
93
127
|
JIRA_COMMENT="$TASK_DIR/.jira-comment.txt"
|
|
94
128
|
if [ -f "$JIRA_COMMENT" ]; then
|
|
@@ -115,6 +115,84 @@ need_jq() {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
# Decode HTML entities in a tile title.
|
|
119
|
+
#
|
|
120
|
+
# `rules.md` forbids entities in "titles, commit messages, task subjects, or body
|
|
121
|
+
# text" because nothing downstream decodes them - they render literally. A tracker
|
|
122
|
+
# tile title IS a task subject, and it rendered exactly that way:
|
|
123
|
+
#
|
|
124
|
+
# Phase 1: Build & Launch
|
|
125
|
+
# Phase 3: Drive & Compare
|
|
126
|
+
#
|
|
127
|
+
# The rule existed and `output-quality-check.sh` enforced it, but only over the PR
|
|
128
|
+
# body and the Jira comment, never over a tile title. The one surface where the
|
|
129
|
+
# rule actually broke was the one surface nothing inspected.
|
|
130
|
+
#
|
|
131
|
+
# `add` is the single funnel for every tile title, so fixing it here fixes every
|
|
132
|
+
# caller. Decode rather than reject: the intent behind `&` is unambiguous, and
|
|
133
|
+
# a display path should render correctly now rather than abort a run over
|
|
134
|
+
# punctuation. The stderr warning keeps the caller mistake visible instead of
|
|
135
|
+
# silently papering over it.
|
|
136
|
+
#
|
|
137
|
+
# Handles all three encodings, because a title can arrive in any of them:
|
|
138
|
+
# named & < " —
|
|
139
|
+
# decimal & < "   —
|
|
140
|
+
# hex & < "   —
|
|
141
|
+
# A first version handled only the named forms, so the numeric spellings still
|
|
142
|
+
# rendered literally: the same bug fixed for one spelling out of three.
|
|
143
|
+
#
|
|
144
|
+
# ONE left-to-right pass, which also gets nesting right for free: a global replace
|
|
145
|
+
# never rescans its own output, so `&lt;` yields the literal text `<` rather
|
|
146
|
+
# than `<`. An ordered-sed version needed `&` decoded last for the same effect,
|
|
147
|
+
# and that ordering was a latent trap.
|
|
148
|
+
#
|
|
149
|
+
# Fancy punctuation degrades to ASCII instead of decoding faithfully. `—`
|
|
150
|
+
# becomes `-`, not an em-dash: this repo bans em/en-dash and ellipsis in shipped
|
|
151
|
+
# text and gates it in the scorecard, so decoding them would make this function
|
|
152
|
+
# INJECT what another gate rejects. Same for any numeric entity resolving to one.
|
|
153
|
+
#
|
|
154
|
+
# The program is fed through a QUOTED heredoc, not `node -e '...'`. Inside a
|
|
155
|
+
# single-quoted shell string every apostrophe in the JS (and in its comments)
|
|
156
|
+
# terminates the string early; shellcheck flags it as SC2140 and the first version
|
|
157
|
+
# here worked only by accident of where the quotes happened to fall. A quoted
|
|
158
|
+
# heredoc passes the body through verbatim, so the JS can use any quote it likes.
|
|
159
|
+
decode_entities() {
|
|
160
|
+
local raw="$1" out
|
|
161
|
+
out=$(MA_RAW="$raw" node - <<'MA_DECODE_JS'
|
|
162
|
+
const NAMED = {
|
|
163
|
+
amp: "&", lt: "<", gt: ">", quot: '"', apos: "'",
|
|
164
|
+
nbsp: " ",
|
|
165
|
+
// Deliberately ASCII: see the note above this function.
|
|
166
|
+
ndash: "-", mdash: "-", hellip: "...", laquo: "<<", raquo: ">>",
|
|
167
|
+
};
|
|
168
|
+
// Codepoints that must degrade to ASCII rather than decode faithfully.
|
|
169
|
+
const ASCII_FOR = new Map([
|
|
170
|
+
[0x00a0, " "], [0x2013, "-"], [0x2014, "-"], [0x2026, "..."],
|
|
171
|
+
[0x2018, "'"], [0x2019, "'"], [0x201c, '"'], [0x201d, '"'],
|
|
172
|
+
[0x00a7, ""],
|
|
173
|
+
]);
|
|
174
|
+
const src = process.env.MA_RAW || "";
|
|
175
|
+
process.stdout.write(
|
|
176
|
+
src.replace(/&(#[xX][0-9a-fA-F]+|#[0-9]+|[a-zA-Z][a-zA-Z0-9]*);/g, (m, body) => {
|
|
177
|
+
if (body[0] === "#") {
|
|
178
|
+
const hex = body[1] === "x" || body[1] === "X";
|
|
179
|
+
const cp = parseInt(hex ? body.slice(2) : body.slice(1), hex ? 16 : 10);
|
|
180
|
+
if (!Number.isFinite(cp) || cp < 0 || cp > 0x10ffff) return m;
|
|
181
|
+
if (ASCII_FOR.has(cp)) return ASCII_FOR.get(cp);
|
|
182
|
+
try { return String.fromCodePoint(cp); } catch { return m; }
|
|
183
|
+
}
|
|
184
|
+
const key = body.toLowerCase();
|
|
185
|
+
// An unknown named entity is left alone: guessing would corrupt a title that
|
|
186
|
+
// legitimately contains "&foo;".
|
|
187
|
+
return Object.prototype.hasOwnProperty.call(NAMED, key) ? NAMED[key] : m;
|
|
188
|
+
}),
|
|
189
|
+
);
|
|
190
|
+
MA_DECODE_JS
|
|
191
|
+
) || out="$raw"
|
|
192
|
+
[ "$out" != "$raw" ] && echo "phase-tracker: decoded HTML entities in title (rules.md forbids them): '$raw' -> '$out'" >&2
|
|
193
|
+
printf '%s' "$out"
|
|
194
|
+
}
|
|
195
|
+
|
|
118
196
|
# OTel-compatible span emission - v6.1.0+, opt-in via $MULTI_AGENT_OTEL_SPANS.
|
|
119
197
|
# Appends one JSON line to otel-spans.jsonl. Never fatal on failure.
|
|
120
198
|
#
|
|
@@ -575,7 +653,7 @@ case "$ACTION" in
|
|
|
575
653
|
add)
|
|
576
654
|
need_jq
|
|
577
655
|
[ "$#" -ge 2 ] || { echo "add needs <phase_id> <name>" >&2; exit 64; }
|
|
578
|
-
PID="$1"; PNAME="$2"
|
|
656
|
+
PID="$1"; PNAME="$(decode_entities "$2")"
|
|
579
657
|
acquire_state_lock
|
|
580
658
|
state=$(load_state)
|
|
581
659
|
# Idempotent: a phase id that already exists is a no-op (name, status, and
|
|
@@ -174,14 +174,40 @@ echo ""
|
|
|
174
174
|
echo "→ 7. Live preferences (if exists) - post-migration check"
|
|
175
175
|
if [ -f "$LIVE_PREFS" ]; then
|
|
176
176
|
LVER=$(node -e "console.log(JSON.parse(require('fs').readFileSync('$LIVE_PREFS','utf8')).schemaVersion || 'none')" 2>/dev/null)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
177
|
+
|
|
178
|
+
# The target is read from migrate-prefs.mjs, never hardcoded here.
|
|
179
|
+
#
|
|
180
|
+
# This block used to say `if [ "$LVER" = "2.1.0" ]; then pass "already v2.1.0"`
|
|
181
|
+
# with everything else falling through to `fail "unknown schemaVersion"`. The
|
|
182
|
+
# migration target had since moved to 2.4.0, which inverted the gate: a prefs
|
|
183
|
+
# file left behind at 2.1.0 reported PASS as "already current", while a file
|
|
184
|
+
# correctly migrated to 2.4.0 hit the else arm and FAILED as "unknown". The
|
|
185
|
+
# gate was rejecting the only fully-migrated state it exists to encourage.
|
|
186
|
+
#
|
|
187
|
+
# Reading the target from the migrator, and the accepted set from the schema
|
|
188
|
+
# enum, means this can never disagree with them again.
|
|
189
|
+
MIGRATOR="$SMOKE_DIR/migrate-prefs.mjs"
|
|
190
|
+
TARGET=$(grep -oE 'TARGET_VERSION = "[0-9.]+"' "$MIGRATOR" 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
|
|
191
|
+
KNOWN=$(node -e "
|
|
192
|
+
const s = require('$PREFS_SCHEMA');
|
|
193
|
+
process.stdout.write((s.properties.schemaVersion.enum || []).join(' '));
|
|
194
|
+
" 2>/dev/null)
|
|
195
|
+
|
|
196
|
+
if [ -z "$TARGET" ]; then
|
|
197
|
+
fail "cannot read TARGET_VERSION from migrate-prefs.mjs - the check has no reference point"
|
|
198
|
+
elif [ "$LVER" = "$TARGET" ]; then
|
|
199
|
+
pass "live prefs at the migration target (v$TARGET)"
|
|
200
|
+
elif [ "$LVER" = "none" ]; then
|
|
201
|
+
echo " ⓘ live prefs carries no schemaVersion - migration pending"
|
|
202
|
+
pass "live prefs present, needs migration (schemaVersion: none)"
|
|
203
|
+
elif printf '%s\n' $KNOWN | grep -qxF "$LVER"; then
|
|
204
|
+
# A known-but-older version is legitimately "migration pending", not a
|
|
205
|
+
# failure: migration is its own install step. Say how far behind it is, so a
|
|
206
|
+
# file sitting three versions back is visible instead of reading as current.
|
|
207
|
+
echo " ⓘ live prefs at v$LVER, target v$TARGET - migration pending"
|
|
208
|
+
pass "live prefs present, needs migration (v$LVER -> v$TARGET)"
|
|
183
209
|
else
|
|
184
|
-
fail "live prefs has
|
|
210
|
+
fail "live prefs has a schemaVersion the schema does not know: $LVER (known: $KNOWN)"
|
|
185
211
|
fi
|
|
186
212
|
else
|
|
187
213
|
echo " ⓘ no live prefs at $LIVE_PREFS - ok for fresh install"
|