@yemi33/minions 0.1.2153 → 0.1.2155
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/engine/consolidation.js
CHANGED
|
@@ -802,13 +802,11 @@ function consolidateWithLLM(items, existingNotes, files, config) {
|
|
|
802
802
|
const dupCheck = checkDuplicateHash(items);
|
|
803
803
|
if (dupCheck.isDuplicate) {
|
|
804
804
|
log('info', `Skipped LLM consolidation: ${dupCheck.count}/${dupCheck.total} items are duplicates (hash: ${dupCheck.hash.slice(0, 8)})`);
|
|
805
|
-
// Archive duplicate files
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
} catch (err) { log('warn', `Inbox archive (dup skip): ${err.message}`); }
|
|
811
|
-
}
|
|
805
|
+
// Archive duplicate files through the shared helper so WI references +
|
|
806
|
+
// completion-report artifact links get repointed to the archive path too
|
|
807
|
+
// (W-mq1j85cj00055a8f) — the inline rename here used to skip that rewrite,
|
|
808
|
+
// leaving dup-note links dangling.
|
|
809
|
+
archiveInboxFiles(files);
|
|
812
810
|
for (const f of files) _processingFiles.delete(f);
|
|
813
811
|
_consolidationInFlight = false;
|
|
814
812
|
_consolidationStartedAt = 0;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// engine/db/migrations/013-backfill-broken-note-links.js
|
|
2
|
+
//
|
|
3
|
+
// One-time data repair (not a schema change). Before the work-item rewriter
|
|
4
|
+
// covered completion-report artifact links (W-mq1j85cj00055a8f, extended to
|
|
5
|
+
// _artifacts.notes[] / artifacts[]), any inbox note the engine auto-archived
|
|
6
|
+
// left the work item's note link pointing at the gone `notes/inbox/<name>` path
|
|
7
|
+
// — a dangling pill. This migration runs the backfill exactly once on the next
|
|
8
|
+
// engine start: it scans existing work_items, finds links whose inbox file is
|
|
9
|
+
// missing, discovers where the note went (notes/archive/ or knowledge/<cat>/),
|
|
10
|
+
// and repoints them. Best-effort and self-isolating — wrapped so a failure can
|
|
11
|
+
// never wedge boot or roll back the migration batch.
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
version: 13,
|
|
15
|
+
description: 'one-time backfill: repoint broken inbox note links in existing work items',
|
|
16
|
+
up(db) {
|
|
17
|
+
try {
|
|
18
|
+
const { backfillBrokenNoteLinks } = require('../../note-link-backfill');
|
|
19
|
+
const res = backfillBrokenNoteLinks(db);
|
|
20
|
+
if (res && res.fixedLinks > 0) {
|
|
21
|
+
// eslint-disable-next-line no-console -- migrate.js logs progress to stdout
|
|
22
|
+
console.log(`[db-migrate] v13: repointed ${res.fixedLinks} broken note link(s) across ${res.fixedItems} work item(s) (${res.scanned} scanned)`);
|
|
23
|
+
}
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// One-time cosmetic repair — never block boot. Record the version anyway
|
|
26
|
+
// so it doesn't retry forever against the same un-repairable state.
|
|
27
|
+
// eslint-disable-next-line no-console -- surface the skip without throwing
|
|
28
|
+
console.warn(`[db-migrate] v13: note-link backfill skipped: ${e && e.message ? e.message : e}`);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// engine/note-link-backfill.js
|
|
2
|
+
//
|
|
3
|
+
// One-time repair for work-item note links that broke BEFORE the live
|
|
4
|
+
// rewrite landed (W-mq1j85cj00055a8f extended to _artifacts.notes / artifacts).
|
|
5
|
+
//
|
|
6
|
+
// Before the rewriter covered completion-report artifact links, any inbox note
|
|
7
|
+
// that the engine auto-archived (or that was promoted to KB) left the work
|
|
8
|
+
// item's link pointing at the now-gone `notes/inbox/<name>` path — a dangling
|
|
9
|
+
// pill in the dashboard. This module scans existing work items, finds links
|
|
10
|
+
// whose inbox file is GONE, discovers where the note actually went (the flat
|
|
11
|
+
// `notes/archive/` dir or a `knowledge/<cat>/` dir), and repoints the link —
|
|
12
|
+
// re-encoding the `_artifacts.notes[].file` token to the renderer's
|
|
13
|
+
// `archive:<base>` / `kb:<cat>/<file>` form so the pill resolves again.
|
|
14
|
+
//
|
|
15
|
+
// It only ever touches links that are PROVABLY broken (inbox file missing) AND
|
|
16
|
+
// resolvable (a matching archive/KB file exists). Valid links, already-relocated
|
|
17
|
+
// tokens, and notes with no discoverable destination (e.g. merged into the flat
|
|
18
|
+
// notes.md) are left untouched. Idempotent: a second pass finds nothing because
|
|
19
|
+
// the repaired links no longer reference `notes/inbox/`.
|
|
20
|
+
//
|
|
21
|
+
// Wired as a one-time SQL migration (db/migrations/013-*) so it runs exactly
|
|
22
|
+
// once on the next engine start after an update. Updates the canonical SQL
|
|
23
|
+
// `work_items` rows only; the JSON mirror self-heals on the next mutateWorkItems
|
|
24
|
+
// write and SQL is what readers serve (work-items-store), so rewriting the
|
|
25
|
+
// passive mirror here would only risk dropping rows for no benefit.
|
|
26
|
+
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
const fs = require('fs');
|
|
30
|
+
const path = require('path');
|
|
31
|
+
|
|
32
|
+
const INBOX_REF_RE = /(?:^|\/)notes\/inbox\/([^/?#]+)/;
|
|
33
|
+
|
|
34
|
+
// Extract the inbox basename from a `…/notes/inbox/<name>` string (tolerating a
|
|
35
|
+
// trailing ?query / #fragment), or null if the string isn't an inbox ref.
|
|
36
|
+
function inboxNameFromRef(str) {
|
|
37
|
+
if (typeof str !== 'string') return null;
|
|
38
|
+
const m = str.match(INBOX_REF_RE);
|
|
39
|
+
return m ? m[1] : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Build a resolver: inbox basename → { newLocation, fileToken } | null.
|
|
43
|
+
// - null when the note is STILL in the inbox (link isn't broken), or when no
|
|
44
|
+
// destination can be discovered (note merged into notes.md, or deleted).
|
|
45
|
+
// - newLocation is the MINIONS_DIR-relative path; fileToken is the prefixed
|
|
46
|
+
// basename the work-item renderer resolves for note pills.
|
|
47
|
+
// The archive dir is flat: consolidation writes `${YYYY-MM-DD}-${name}` (with an
|
|
48
|
+
// optional uniquePath `-N` collision suffix). We index each archived file by the
|
|
49
|
+
// basename it derived from (date prefix stripped) and by its literal name.
|
|
50
|
+
function buildNoteResolver(dirs) {
|
|
51
|
+
const inboxDir = dirs.inboxDir;
|
|
52
|
+
const archiveDir = dirs.archiveDir;
|
|
53
|
+
const knowledgeDir = dirs.knowledgeDir;
|
|
54
|
+
const kbCategories = Array.isArray(dirs.kbCategories) ? dirs.kbCategories : [];
|
|
55
|
+
// Reuse the live rewriter's encoder so the backfill and the live path emit
|
|
56
|
+
// byte-identical pill tokens (one source for the archive:/kb: grammar).
|
|
57
|
+
const tokenFor = require('./shared')._artifactNoteFileToken;
|
|
58
|
+
|
|
59
|
+
const archiveByBase = new Map();
|
|
60
|
+
let archiveFiles = [];
|
|
61
|
+
try { archiveFiles = fs.readdirSync(archiveDir); } catch { archiveFiles = []; }
|
|
62
|
+
for (const f of archiveFiles) {
|
|
63
|
+
const origin = f.replace(/^\d{4}-\d{2}-\d{2}-/, '');
|
|
64
|
+
for (const key of new Set([origin, f])) {
|
|
65
|
+
const prev = archiveByBase.get(key);
|
|
66
|
+
// Lexicographic max keeps the newest date-prefixed file for a given note.
|
|
67
|
+
if (!prev || f > prev) archiveByBase.set(key, f);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const kbByBase = new Map();
|
|
72
|
+
for (const cat of kbCategories) {
|
|
73
|
+
let files = [];
|
|
74
|
+
try { files = fs.readdirSync(path.join(knowledgeDir, cat)); } catch { files = []; }
|
|
75
|
+
for (const f of files) if (!kbByBase.has(f)) kbByBase.set(f, cat + '/' + f);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return function resolve(baseName) {
|
|
79
|
+
if (!baseName) return null;
|
|
80
|
+
try { if (fs.existsSync(path.join(inboxDir, baseName))) return null; } catch { /* treat as gone */ }
|
|
81
|
+
const arch = archiveByBase.get(baseName);
|
|
82
|
+
if (arch) { const nl = 'notes/archive/' + arch; return { newLocation: nl, fileToken: tokenFor(nl) }; }
|
|
83
|
+
const kb = kbByBase.get(baseName);
|
|
84
|
+
if (kb) { const nl = 'knowledge/' + kb; return { newLocation: nl, fileToken: tokenFor(nl) }; }
|
|
85
|
+
return null;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Repoint every broken inbox note link on one work item in place. `resolve` is a
|
|
90
|
+
// buildNoteResolver() result (returns null for non-broken / unresolvable). Mirrors
|
|
91
|
+
// the link shapes shared.rewriteInboxRefsAcrossProjects handles: references[],
|
|
92
|
+
// _artifacts.notes[] (string + object), and artifacts[].path. Returns the count
|
|
93
|
+
// of links repointed.
|
|
94
|
+
function repointItemNoteLinks(item, resolve) {
|
|
95
|
+
if (!item || typeof item !== 'object') return 0;
|
|
96
|
+
let fixed = 0;
|
|
97
|
+
|
|
98
|
+
if (Array.isArray(item.references)) {
|
|
99
|
+
for (let i = 0; i < item.references.length; i++) {
|
|
100
|
+
const ref = item.references[i];
|
|
101
|
+
if (typeof ref === 'string') {
|
|
102
|
+
const name = inboxNameFromRef(ref);
|
|
103
|
+
if (name) { const d = resolve(name); if (d) { item.references[i] = d.newLocation; fixed++; } }
|
|
104
|
+
} else if (ref && typeof ref === 'object') {
|
|
105
|
+
for (const key of ['url', 'path', 'href']) {
|
|
106
|
+
const name = inboxNameFromRef(ref[key]);
|
|
107
|
+
if (name) { const d = resolve(name); if (d) { ref[key] = d.newLocation; fixed++; break; } }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const notes = item._artifacts && item._artifacts.notes;
|
|
114
|
+
if (Array.isArray(notes)) {
|
|
115
|
+
for (let i = 0; i < notes.length; i++) {
|
|
116
|
+
const n = notes[i];
|
|
117
|
+
if (typeof n === 'string') {
|
|
118
|
+
// Bare basename = an inbox pill. Skip tokens already relocated.
|
|
119
|
+
if (n.startsWith('archive:') || n.startsWith('kb:')) continue;
|
|
120
|
+
const d = resolve(n);
|
|
121
|
+
if (d && d.fileToken) { notes[i] = d.fileToken; fixed++; }
|
|
122
|
+
} else if (n && typeof n === 'object') {
|
|
123
|
+
let name = inboxNameFromRef(n.path);
|
|
124
|
+
if (!name && typeof n.file === 'string'
|
|
125
|
+
&& !n.file.startsWith('archive:') && !n.file.startsWith('kb:')) {
|
|
126
|
+
name = n.file;
|
|
127
|
+
}
|
|
128
|
+
if (name) {
|
|
129
|
+
const d = resolve(name);
|
|
130
|
+
if (d) {
|
|
131
|
+
if (typeof n.path === 'string') n.path = d.newLocation;
|
|
132
|
+
if (d.fileToken && typeof n.file === 'string') n.file = d.fileToken;
|
|
133
|
+
fixed++;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (Array.isArray(item.artifacts)) {
|
|
141
|
+
for (const a of item.artifacts) {
|
|
142
|
+
if (a && typeof a === 'object') {
|
|
143
|
+
const name = inboxNameFromRef(a.path);
|
|
144
|
+
if (name) { const d = resolve(name); if (d) { a.path = d.newLocation; fixed++; } }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return fixed;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Scan the canonical SQL `work_items` table and repair broken note links in
|
|
153
|
+
// place. db is a node:sqlite DatabaseSync (the migration's connection). Per-row
|
|
154
|
+
// try/catch so one corrupt row can't abort the pass. Returns a summary.
|
|
155
|
+
function backfillBrokenNoteLinks(db, opts = {}) {
|
|
156
|
+
const shared = require('./shared');
|
|
157
|
+
const minionsDir = opts.minionsDir || shared.MINIONS_DIR;
|
|
158
|
+
const resolve = buildNoteResolver({
|
|
159
|
+
inboxDir: path.join(minionsDir, 'notes', 'inbox'),
|
|
160
|
+
archiveDir: path.join(minionsDir, 'notes', 'archive'),
|
|
161
|
+
knowledgeDir: path.join(minionsDir, 'knowledge'),
|
|
162
|
+
kbCategories: shared.KB_CATEGORIES,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
let scopes;
|
|
166
|
+
try {
|
|
167
|
+
scopes = db.prepare('SELECT DISTINCT scope FROM work_items').all().map(r => r.scope);
|
|
168
|
+
} catch {
|
|
169
|
+
return { scanned: 0, fixedItems: 0, fixedLinks: 0 };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const now = opts.now || Date.now();
|
|
173
|
+
const upd = db.prepare('UPDATE work_items SET data = ?, updated_at = ? WHERE scope = ? AND id = ?');
|
|
174
|
+
let scanned = 0, fixedItems = 0, fixedLinks = 0;
|
|
175
|
+
|
|
176
|
+
for (const scope of scopes) {
|
|
177
|
+
let rows;
|
|
178
|
+
try { rows = db.prepare('SELECT id, data FROM work_items WHERE scope = ?').all(scope); } catch { continue; }
|
|
179
|
+
for (const row of rows) {
|
|
180
|
+
let item;
|
|
181
|
+
try { item = JSON.parse(row.data); } catch { continue; }
|
|
182
|
+
scanned++;
|
|
183
|
+
try {
|
|
184
|
+
const n = repointItemNoteLinks(item, resolve);
|
|
185
|
+
if (n > 0) {
|
|
186
|
+
upd.run(JSON.stringify(item), now, scope, item.id);
|
|
187
|
+
fixedItems++;
|
|
188
|
+
fixedLinks += n;
|
|
189
|
+
}
|
|
190
|
+
} catch { /* skip unfixable row */ }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { scanned, fixedItems, fixedLinks };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
inboxNameFromRef,
|
|
199
|
+
buildNoteResolver,
|
|
200
|
+
repointItemNoteLinks,
|
|
201
|
+
backfillBrokenNoteLinks,
|
|
202
|
+
};
|
package/engine/shared.js
CHANGED
|
@@ -5297,10 +5297,18 @@ function extractStructuredWorkItemPrRef(item) {
|
|
|
5297
5297
|
// work-items.json + the central one to the new canonical location.
|
|
5298
5298
|
//
|
|
5299
5299
|
// Match semantics (intentionally narrow per the WI scope):
|
|
5300
|
-
// -
|
|
5301
|
-
//
|
|
5302
|
-
//
|
|
5303
|
-
//
|
|
5300
|
+
// - item.references[]: string 'notes/inbox/<inboxName>' (anchored on start
|
|
5301
|
+
// or '/') or object { url | path | href }. Other keys (label, kind, …)
|
|
5302
|
+
// are preserved. Description prose is NOT scanned.
|
|
5303
|
+
// - item._artifacts.notes[] (completion-report note links, written by
|
|
5304
|
+
// lifecycle.promoteCompletionArtifacts): the work-item renderer resolves
|
|
5305
|
+
// the clickable pill from note.file — a prefixed basename ('archive:<base>'
|
|
5306
|
+
// / 'kb:<cat>/<file>' / bare inbox base) — so we re-encode `file` for the
|
|
5307
|
+
// destination via _artifactNoteFileToken AND rewrite the `path` field.
|
|
5308
|
+
// This is the link shape agent completions actually populate, so it's the
|
|
5309
|
+
// one that must survive auto-archive.
|
|
5310
|
+
// - item.artifacts[] (raw completion artifacts): the `path` field is
|
|
5311
|
+
// rewritten the same way as references.
|
|
5304
5312
|
// - Trailing `?query` or `#fragment` is tolerated; substring overlaps in
|
|
5305
5313
|
// unrelated path segments (e.g. .../notes/inbox/foobar for foo.md) are
|
|
5306
5314
|
// NOT rewritten.
|
|
@@ -5319,12 +5327,29 @@ function extractStructuredWorkItemPrRef(item) {
|
|
|
5319
5327
|
// take the default (the in-file `mutateWorkItems` closure reference). Tests
|
|
5320
5328
|
// inject a wrapper to exercise the per-file try/catch boundary without
|
|
5321
5329
|
// having to manufacture a real SQL-store failure.
|
|
5330
|
+
|
|
5331
|
+
// Map a consolidation destination to the prefixed `file` token the work-item
|
|
5332
|
+
// artifact renderer (dashboard/js/render-work-items.js) uses for note pills:
|
|
5333
|
+
// notes/archive/<base> → 'archive:<base>' (openInboxNote resolves in archive)
|
|
5334
|
+
// knowledge/<cat>/<file> → 'kb:<cat>/<file>' (kbOpenItem)
|
|
5335
|
+
// notes.md / anything else → null (no per-note anchor — leave file as-is)
|
|
5336
|
+
function _artifactNoteFileToken(newLocation) {
|
|
5337
|
+
const nl = String(newLocation).replace(/\\/g, '/');
|
|
5338
|
+
if (/(?:^|\/)notes\/archive\//.test(nl)) return 'archive:' + nl.replace(/^.*\//, '');
|
|
5339
|
+
const kb = nl.match(/(?:^|\/)knowledge\/([^/]+)\/(.+)$/);
|
|
5340
|
+
if (kb) return 'kb:' + kb[1] + '/' + kb[2];
|
|
5341
|
+
return null;
|
|
5342
|
+
}
|
|
5343
|
+
|
|
5322
5344
|
function rewriteInboxRefsAcrossProjects(inboxName, newLocation, opts = {}) {
|
|
5323
5345
|
if (!inboxName || typeof newLocation !== 'string' || !newLocation) return 0;
|
|
5324
5346
|
const baseName = String(inboxName).replace(/^.*[/\\]/, '').trim();
|
|
5325
5347
|
if (!baseName) return 0;
|
|
5326
5348
|
const escaped = baseName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
5327
5349
|
const inboxRefRe = new RegExp(`(?:^|/)notes/inbox/${escaped}(?=$|[?#])`);
|
|
5350
|
+
// The note-pill `file` token for the destination (archive:/kb:/…). Computed
|
|
5351
|
+
// once — it depends only on newLocation, not the item being scanned.
|
|
5352
|
+
const fileToken = _artifactNoteFileToken(newLocation);
|
|
5328
5353
|
const _mutate = (opts && typeof opts._mutate === 'function') ? opts._mutate : mutateWorkItems;
|
|
5329
5354
|
|
|
5330
5355
|
let rewritten = 0;
|
|
@@ -5342,22 +5367,53 @@ function rewriteInboxRefsAcrossProjects(inboxName, newLocation, opts = {}) {
|
|
|
5342
5367
|
_mutate(wiPath, items => {
|
|
5343
5368
|
if (!Array.isArray(items)) return items;
|
|
5344
5369
|
for (const item of items) {
|
|
5345
|
-
if (!item
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
} else if (ref && typeof ref === 'object') {
|
|
5354
|
-
for (const key of ['url', 'path', 'href']) {
|
|
5355
|
-
const v = ref[key];
|
|
5356
|
-
if (typeof v === 'string' && inboxRefRe.test(v)) {
|
|
5357
|
-
ref[key] = newLocation;
|
|
5370
|
+
if (!item) continue;
|
|
5371
|
+
// references[] — string | { url | path | href }.
|
|
5372
|
+
if (Array.isArray(item.references)) {
|
|
5373
|
+
for (let i = 0; i < item.references.length; i++) {
|
|
5374
|
+
const ref = item.references[i];
|
|
5375
|
+
if (typeof ref === 'string') {
|
|
5376
|
+
if (inboxRefRe.test(ref)) {
|
|
5377
|
+
item.references[i] = newLocation;
|
|
5358
5378
|
rewritten++;
|
|
5359
|
-
break;
|
|
5360
5379
|
}
|
|
5380
|
+
} else if (ref && typeof ref === 'object') {
|
|
5381
|
+
for (const key of ['url', 'path', 'href']) {
|
|
5382
|
+
const v = ref[key];
|
|
5383
|
+
if (typeof v === 'string' && inboxRefRe.test(v)) {
|
|
5384
|
+
ref[key] = newLocation;
|
|
5385
|
+
rewritten++;
|
|
5386
|
+
break;
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
}
|
|
5391
|
+
}
|
|
5392
|
+
// _artifacts.notes[] — completion-report note links. Match on the
|
|
5393
|
+
// inbox `path` (object form) or the bare basename `file` (the inbox
|
|
5394
|
+
// pill token), then re-encode `file` for the destination and fix
|
|
5395
|
+
// `path`. The renderer keys the clickable pill off `file`, so the
|
|
5396
|
+
// re-encode is what actually un-dangles the link.
|
|
5397
|
+
const notes = item._artifacts && item._artifacts.notes;
|
|
5398
|
+
if (Array.isArray(notes)) {
|
|
5399
|
+
for (let i = 0; i < notes.length; i++) {
|
|
5400
|
+
const n = notes[i];
|
|
5401
|
+
if (typeof n === 'string') {
|
|
5402
|
+
if (n === baseName && fileToken) { notes[i] = fileToken; rewritten++; }
|
|
5403
|
+
} else if (n && typeof n === 'object') {
|
|
5404
|
+
let hit = false;
|
|
5405
|
+
if (typeof n.path === 'string' && inboxRefRe.test(n.path)) { n.path = newLocation; hit = true; }
|
|
5406
|
+
if (n.file === baseName && fileToken) { n.file = fileToken; hit = true; }
|
|
5407
|
+
if (hit) rewritten++;
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
}
|
|
5411
|
+
// artifacts[] — raw completion artifacts ({ type, path, … }).
|
|
5412
|
+
if (Array.isArray(item.artifacts)) {
|
|
5413
|
+
for (const a of item.artifacts) {
|
|
5414
|
+
if (a && typeof a === 'object' && typeof a.path === 'string' && inboxRefRe.test(a.path)) {
|
|
5415
|
+
a.path = newLocation;
|
|
5416
|
+
rewritten++;
|
|
5361
5417
|
}
|
|
5362
5418
|
}
|
|
5363
5419
|
}
|
|
@@ -7447,6 +7503,7 @@ module.exports = {
|
|
|
7447
7503
|
extractWorkItemPrRef,
|
|
7448
7504
|
extractStructuredWorkItemPrRef,
|
|
7449
7505
|
rewriteInboxRefsAcrossProjects,
|
|
7506
|
+
_artifactNoteFileToken, // exported so the one-time note-link backfill shares the token grammar
|
|
7450
7507
|
getProjectPrScope,
|
|
7451
7508
|
getPrNumber,
|
|
7452
7509
|
getPrDisplayId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2155",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|