@akagilnc/pi-workflow-roles 0.1.4422 → 0.1.4489
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 +2 -0
- package/README.zh-CN.md +2 -0
- package/dist/acp-host/description.js +2 -3
- package/dist/acp-host/production-host.js +333 -258
- package/dist/auditor-soul.js +8 -1
- package/dist/diarist-contracts.js +2 -11
- package/dist/headless-host/description.js +3 -3
- package/dist/headless-host/production-host.js +340 -227
- package/dist/host-descriptions.js +3 -18
- package/dist/method-host-plugin/.claude-plugin/plugin.json +5 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/CONTEXT.md +48 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/LICENSE +21 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/SKILL.md +170 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/prompts/cmr-completeness.md +118 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/prompts/cmr-reviewer.md +128 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/provenance.json +41 -0
- package/dist/method-host-plugin/skills/diagnosing-bugs/SKILL.md +134 -0
- package/dist/method-host-plugin/skills/diagnosing-bugs/agents/openai.yaml +3 -0
- package/dist/method-host-plugin/skills/diagnosing-bugs/provenance.json +31 -0
- package/dist/method-host-plugin/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/dist/method-host-plugin/skills/resolving-merge-conflicts/SKILL.md +14 -0
- package/dist/method-host-plugin/skills/resolving-merge-conflicts/agents/openai.yaml +3 -0
- package/dist/method-host-plugin/skills/resolving-merge-conflicts/provenance.json +26 -0
- package/dist/method-host-plugin/skills/tdd/SKILL.md +38 -0
- package/dist/method-host-plugin/skills/tdd/agents/openai.yaml +3 -0
- package/dist/method-host-plugin/skills/tdd/mocking.md +59 -0
- package/dist/method-host-plugin/skills/tdd/provenance.json +36 -0
- package/dist/method-host-plugin/skills/tdd/tests.md +77 -0
- package/dist/public-cli/main.js +14 -21
- package/dist/session-opening-materials.js +17 -5
- package/dist/ticket-provenance-contracts.js +6 -25
- package/dist/ticket-provenance.js +223 -90
- package/extensions/role-runtime.ts +16 -6
- package/package.json +1 -1
- package/resources/method-host-plugin/.claude-plugin/plugin.json +5 -0
- package/scripts/build-package.mjs +6 -1
- package/src/acp-host/description.ts +2 -4
- package/src/acp-host/production-host.ts +0 -1
- package/src/auditor-soul.ts +10 -1
- package/src/diarist-contracts.ts +1 -19
- package/src/diarist-role.ts +3 -31
- package/src/diarist.ts +5 -19
- package/src/headless-host/description.ts +4 -3
- package/src/headless-host/role-turn-host.ts +27 -4
- package/src/host-descriptions.ts +3 -23
- package/src/host-native-method.ts +67 -0
- package/src/ledger-session-read.ts +1 -2
- package/src/role-envelope.ts +17 -47
- package/src/role-runtime-dependencies.ts +17 -2
- package/src/role-runtime.ts +14 -31
- package/src/session-opening-materials.ts +27 -11
- package/src/ticket-provenance-contracts.ts +11 -38
- package/src/ticket-provenance.ts +228 -109
|
@@ -78,29 +78,6 @@ export function projectTicketProvenanceSessions(value) {
|
|
|
78
78
|
}
|
|
79
79
|
return sessions;
|
|
80
80
|
}
|
|
81
|
-
/**
|
|
82
|
-
* 投影补写集合。缺该字段=无补写(不拒收);成员形状不成立的整条跳过——
|
|
83
|
-
* 它没有绑定位置,放不回去。
|
|
84
|
-
*/
|
|
85
|
-
export function projectTicketProvenanceAmendments(value) {
|
|
86
|
-
if (!Array.isArray(value))
|
|
87
|
-
return [];
|
|
88
|
-
const out = [];
|
|
89
|
-
for (const raw of value) {
|
|
90
|
-
if (!isRecord(raw))
|
|
91
|
-
continue;
|
|
92
|
-
const s = nonNegativeInteger(raw.s);
|
|
93
|
-
const line = positiveInteger(raw.line);
|
|
94
|
-
const speaker = projectSpeaker(raw.speaker);
|
|
95
|
-
const text = raw.text;
|
|
96
|
-
if (s === undefined || line === undefined || speaker === undefined)
|
|
97
|
-
continue;
|
|
98
|
-
if (typeof text !== "string")
|
|
99
|
-
continue;
|
|
100
|
-
out.push({ s, line, speaker, text });
|
|
101
|
-
}
|
|
102
|
-
return out;
|
|
103
|
-
}
|
|
104
81
|
/** 读回册子头(文件第一行)。形状不成立即 undefined——按无册处理,不猜。 */
|
|
105
82
|
export function projectTicketProvenanceHeader(value) {
|
|
106
83
|
if (!isRecord(value))
|
|
@@ -135,12 +112,16 @@ export function projectTicketProvenanceLine(value) {
|
|
|
135
112
|
return undefined;
|
|
136
113
|
if (typeof value.text !== "string")
|
|
137
114
|
return undefined;
|
|
138
|
-
const
|
|
115
|
+
const sourcePosition = nonNegativeInteger(value.sourcePosition);
|
|
116
|
+
const sourceIdentity = typeof value.sourceIdentity === "string" && value.sourceIdentity !== ""
|
|
117
|
+
? value.sourceIdentity
|
|
118
|
+
: undefined;
|
|
139
119
|
const id = typeof value.id === "string" && value.id !== "" ? value.id : undefined;
|
|
140
120
|
return {
|
|
141
121
|
speaker,
|
|
142
122
|
s,
|
|
143
|
-
...(
|
|
123
|
+
...(sourcePosition === undefined ? {} : { sourcePosition }),
|
|
124
|
+
...(sourceIdentity === undefined ? {} : { sourceIdentity }),
|
|
144
125
|
...(id === undefined ? {} : { id }),
|
|
145
126
|
text: value.text,
|
|
146
127
|
};
|
|
@@ -87,6 +87,24 @@ export function resolveTicketProvenanceVolume(ticketNumber, cwd, home) {
|
|
|
87
87
|
const path = resolveSitianRecordPath(ticketProvenanceRecordInput(ticketNumber, cwd, home));
|
|
88
88
|
return { recordFile: path.recordFile, volumeDir: path.sessionDir };
|
|
89
89
|
}
|
|
90
|
+
function projectTicketProvenanceRaw(value) {
|
|
91
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
92
|
+
return undefined;
|
|
93
|
+
const raw = value;
|
|
94
|
+
if (typeof raw.raw !== "string" || !Number.isInteger(raw.s) || raw.s < 0 ||
|
|
95
|
+
!Number.isInteger(raw.sourcePosition) || raw.sourcePosition < 0) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const sourceIdentity = typeof raw.sourceIdentity === "string" && raw.sourceIdentity !== ""
|
|
99
|
+
? raw.sourceIdentity
|
|
100
|
+
: undefined;
|
|
101
|
+
return {
|
|
102
|
+
raw: raw.raw,
|
|
103
|
+
s: raw.s,
|
|
104
|
+
sourcePosition: raw.sourcePosition,
|
|
105
|
+
...(sourceIdentity === undefined ? {} : { sourceIdentity }),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
90
108
|
function projectTicketProvenanceCommit(value) {
|
|
91
109
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
92
110
|
return undefined;
|
|
@@ -105,10 +123,19 @@ function projectTicketProvenanceCommit(value) {
|
|
|
105
123
|
return undefined;
|
|
106
124
|
const lines = [];
|
|
107
125
|
for (const raw of body.lines) {
|
|
126
|
+
if (typeof raw === "string") {
|
|
127
|
+
lines.push(raw);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
108
130
|
const line = projectTicketProvenanceLine(raw);
|
|
109
|
-
if (line
|
|
131
|
+
if (line !== undefined) {
|
|
132
|
+
lines.push(line);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const preserved = projectTicketProvenanceRaw(raw);
|
|
136
|
+
if (preserved === undefined)
|
|
110
137
|
return undefined;
|
|
111
|
-
lines.push(
|
|
138
|
+
lines.push(preserved);
|
|
112
139
|
}
|
|
113
140
|
return { timestamp: record.timestamp, sessions, lines };
|
|
114
141
|
}
|
|
@@ -121,7 +148,14 @@ export async function readTicketProvenance(ticketNumber, cwd, home) {
|
|
|
121
148
|
}
|
|
122
149
|
catch (error) {
|
|
123
150
|
if (error.code === "ENOENT") {
|
|
124
|
-
return {
|
|
151
|
+
return {
|
|
152
|
+
header: undefined,
|
|
153
|
+
lines: [],
|
|
154
|
+
unprojectedRaw: [],
|
|
155
|
+
recordFile,
|
|
156
|
+
sourceIdentities: new Map(),
|
|
157
|
+
legacySourcePositions: new Map(),
|
|
158
|
+
};
|
|
125
159
|
}
|
|
126
160
|
throw error;
|
|
127
161
|
}
|
|
@@ -129,6 +163,33 @@ export async function readTicketProvenance(ticketNumber, cwd, home) {
|
|
|
129
163
|
let header;
|
|
130
164
|
const lines = [];
|
|
131
165
|
const unprojectedRaw = [];
|
|
166
|
+
const sourceIdentities = new Map();
|
|
167
|
+
const legacySourcePositions = new Map();
|
|
168
|
+
const rememberIdentity = (s, identity) => {
|
|
169
|
+
const seen = sourceIdentities.get(s) ?? new Set();
|
|
170
|
+
const fresh = !seen.has(identity);
|
|
171
|
+
seen.add(identity);
|
|
172
|
+
sourceIdentities.set(s, seen);
|
|
173
|
+
return fresh;
|
|
174
|
+
};
|
|
175
|
+
const rememberLegacyPosition = (s, position) => {
|
|
176
|
+
const seen = legacySourcePositions.get(s) ?? new Set();
|
|
177
|
+
seen.add(position);
|
|
178
|
+
legacySourcePositions.set(s, seen);
|
|
179
|
+
};
|
|
180
|
+
const remapCoverage = (coverage, priorIndexes) => {
|
|
181
|
+
const normalized = new Map();
|
|
182
|
+
for (const [s, values] of coverage) {
|
|
183
|
+
const target = priorIndexes[s] ?? s;
|
|
184
|
+
const union = normalized.get(target) ?? new Set();
|
|
185
|
+
for (const value of values)
|
|
186
|
+
union.add(value);
|
|
187
|
+
normalized.set(target, union);
|
|
188
|
+
}
|
|
189
|
+
coverage.clear();
|
|
190
|
+
for (const [s, values] of normalized)
|
|
191
|
+
coverage.set(s, values);
|
|
192
|
+
};
|
|
132
193
|
let sawFirst = false;
|
|
133
194
|
for (let index = 0; index < physical.length; index += 1) {
|
|
134
195
|
const raw = physical[index];
|
|
@@ -158,10 +219,34 @@ export async function readTicketProvenance(ticketNumber, cwd, home) {
|
|
|
158
219
|
const commit = projectTicketProvenanceCommit(parsed);
|
|
159
220
|
if (commit !== undefined) {
|
|
160
221
|
const merged = mergeSessionBounds(header?.sessions, commit.sessions);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
222
|
+
remapCoverage(sourceIdentities, merged.priorIndexes);
|
|
223
|
+
remapCoverage(legacySourcePositions, merged.priorIndexes);
|
|
224
|
+
const remapped = [];
|
|
225
|
+
for (const entry of commit.lines) {
|
|
226
|
+
if (typeof entry === "string") {
|
|
227
|
+
unprojectedRaw.push(entry);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const s = merged.incomingIndexes[entry.s] ?? merged.priorIndexes[entry.s] ?? entry.s;
|
|
231
|
+
if ("raw" in entry) {
|
|
232
|
+
if (entry.sourceIdentity !== undefined) {
|
|
233
|
+
if (rememberIdentity(s, entry.sourceIdentity))
|
|
234
|
+
unprojectedRaw.push(entry.raw);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
rememberLegacyPosition(s, entry.sourcePosition);
|
|
238
|
+
unprojectedRaw.push(entry.raw);
|
|
239
|
+
}
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
remapped.push({ ...entry, s });
|
|
243
|
+
if (entry.id === undefined) {
|
|
244
|
+
if (entry.sourceIdentity !== undefined)
|
|
245
|
+
rememberIdentity(s, entry.sourceIdentity);
|
|
246
|
+
else if (entry.sourcePosition !== undefined)
|
|
247
|
+
rememberLegacyPosition(s, entry.sourcePosition);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
165
250
|
const now = commit.timestamp;
|
|
166
251
|
header = {
|
|
167
252
|
repo: header?.repo ?? resolveBookKeyFromGit(cwd),
|
|
@@ -180,7 +265,22 @@ export async function readTicketProvenance(ticketNumber, cwd, home) {
|
|
|
180
265
|
// Unknown stock / damaged rows remain readable and are never upgraded in place.
|
|
181
266
|
unprojectedRaw.push(raw);
|
|
182
267
|
}
|
|
183
|
-
|
|
268
|
+
for (const line of lines) {
|
|
269
|
+
if (line.id !== undefined)
|
|
270
|
+
continue;
|
|
271
|
+
if (line.sourceIdentity !== undefined)
|
|
272
|
+
rememberIdentity(line.s, line.sourceIdentity);
|
|
273
|
+
else if (line.sourcePosition !== undefined)
|
|
274
|
+
rememberLegacyPosition(line.s, line.sourcePosition);
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
header,
|
|
278
|
+
lines,
|
|
279
|
+
unprojectedRaw,
|
|
280
|
+
recordFile,
|
|
281
|
+
sourceIdentities,
|
|
282
|
+
legacySourcePositions,
|
|
283
|
+
};
|
|
184
284
|
}
|
|
185
285
|
/**
|
|
186
286
|
* Ensure the per-ticket directory + volume file exist (ADR 0075 ticket-provenance-file).
|
|
@@ -218,10 +318,10 @@ function resolveBoundIndex(bound, sessionLines) {
|
|
|
218
318
|
}
|
|
219
319
|
return undefined;
|
|
220
320
|
}
|
|
221
|
-
function amendmentKey(s, line) {
|
|
222
|
-
return `${s}:${line}`;
|
|
223
|
-
}
|
|
224
321
|
/** Exact range identity for cumulative header dedupe (idempotent resubmit). */
|
|
322
|
+
function dialogueIdentity(s, id) {
|
|
323
|
+
return `${s}\u0000${id}`;
|
|
324
|
+
}
|
|
225
325
|
function rangeDeclarationKey(range) {
|
|
226
326
|
return JSON.stringify({
|
|
227
327
|
from: range.from,
|
|
@@ -267,7 +367,7 @@ function mergeSessionBounds(prior, incoming) {
|
|
|
267
367
|
}
|
|
268
368
|
/**
|
|
269
369
|
* #918 第一节:已投影行即卷宗。只对 prior 未声明的 range(按 path identity + 精确
|
|
270
|
-
* from/to
|
|
370
|
+
* from/to)读源;已声明的投影原样结转,不重读历史源。
|
|
271
371
|
*/
|
|
272
372
|
function undeclaredSessionRanges(prior, merged) {
|
|
273
373
|
const priorKeys = new Map();
|
|
@@ -284,7 +384,8 @@ function undeclaredSessionRanges(prior, merged) {
|
|
|
284
384
|
const out = [];
|
|
285
385
|
for (let s = 0; s < merged.length; s += 1) {
|
|
286
386
|
const session = merged[s];
|
|
287
|
-
const
|
|
387
|
+
const identity = physicalPathIdentity(session.path);
|
|
388
|
+
const declared = priorKeys.get(identity) ?? new Set();
|
|
288
389
|
const fresh = session.ranges.filter((range) => !declared.has(rangeDeclarationKey(range)));
|
|
289
390
|
if (fresh.length === 0)
|
|
290
391
|
continue;
|
|
@@ -301,46 +402,85 @@ function undeclaredSessionRanges(prior, merged) {
|
|
|
301
402
|
}
|
|
302
403
|
return out;
|
|
303
404
|
}
|
|
304
|
-
/** Sort archive rows by session index then source line (stable within equal keys). */
|
|
305
|
-
function compareLinePosition(left, right) {
|
|
306
|
-
if (left.s !== right.s)
|
|
307
|
-
return left.s - right.s;
|
|
308
|
-
const leftLine = left.line ?? Number.MAX_SAFE_INTEGER;
|
|
309
|
-
const rightLine = right.line ?? Number.MAX_SAFE_INTEGER;
|
|
310
|
-
return leftLine - rightLine;
|
|
311
|
-
}
|
|
312
405
|
/**
|
|
313
406
|
* Merge newly projected rows into the carried archive.
|
|
314
|
-
*
|
|
315
|
-
* id 去重仍保留(跨 path 同 id 首见胜)。
|
|
407
|
+
* Native id remains the typed first-seen identity; exact range resubmission is a no-op.
|
|
316
408
|
*/
|
|
317
409
|
function mergeFreshIntoCarried(carried, fresh) {
|
|
318
|
-
const
|
|
319
|
-
const
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
410
|
+
const seenSources = new Map();
|
|
411
|
+
const bySession = new Map();
|
|
412
|
+
const absorb = (lines) => {
|
|
413
|
+
for (const line of lines) {
|
|
414
|
+
const sourceIdentity = line.id === undefined
|
|
415
|
+
? line.sourceIdentity
|
|
416
|
+
: `id\u0000${line.id}`;
|
|
417
|
+
const identity = sourceIdentity === undefined
|
|
418
|
+
? undefined
|
|
419
|
+
: `${line.s}\u0000${sourceIdentity}`;
|
|
420
|
+
const seen = identity === undefined ? undefined : seenSources.get(identity);
|
|
421
|
+
if (seen !== undefined) {
|
|
422
|
+
if (line.sourcePosition !== undefined) {
|
|
423
|
+
Object.assign(seen, { sourcePosition: line.sourcePosition });
|
|
424
|
+
}
|
|
331
425
|
continue;
|
|
332
|
-
|
|
426
|
+
}
|
|
427
|
+
const bucket = bySession.get(line.s) ?? [];
|
|
428
|
+
bucket.push({ ...line });
|
|
429
|
+
if (identity !== undefined)
|
|
430
|
+
seenSources.set(identity, bucket.at(-1));
|
|
431
|
+
bySession.set(line.s, bucket);
|
|
333
432
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
433
|
+
};
|
|
434
|
+
absorb(carried);
|
|
435
|
+
absorb(fresh);
|
|
436
|
+
return [...bySession.entries()]
|
|
437
|
+
.sort(([left], [right]) => left - right)
|
|
438
|
+
.flatMap(([, lines]) => lines
|
|
439
|
+
.map((line, archiveIndex) => ({ line, archiveIndex }))
|
|
440
|
+
.sort((left, right) => {
|
|
441
|
+
const leftPosition = left.line.sourcePosition;
|
|
442
|
+
const rightPosition = right.line.sourcePosition;
|
|
443
|
+
if (leftPosition === undefined && rightPosition === undefined) {
|
|
444
|
+
return left.archiveIndex - right.archiveIndex;
|
|
445
|
+
}
|
|
446
|
+
if (leftPosition === undefined)
|
|
447
|
+
return 1;
|
|
448
|
+
if (rightPosition === undefined)
|
|
449
|
+
return -1;
|
|
450
|
+
return leftPosition - rightPosition || left.archiveIndex - right.archiveIndex;
|
|
451
|
+
})
|
|
452
|
+
.map(({ line }) => line));
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Give every logical source row a replay-invariant ordinal. Native ids identify
|
|
456
|
+
* replayed rows directly; idless rows use their ordinal after the preceding
|
|
457
|
+
* native id. Neither key inspects dialogue text or physical line numbers.
|
|
458
|
+
*/
|
|
459
|
+
function stableSourceFacts(sessionLines) {
|
|
460
|
+
const positionByIdentity = new Map();
|
|
461
|
+
const facts = [];
|
|
462
|
+
let precedingId = "<start>";
|
|
463
|
+
let idlessOffset = 0;
|
|
464
|
+
for (const entry of sessionLines) {
|
|
465
|
+
const id = entry.row === undefined ? undefined : nativeEventId(entry.row);
|
|
466
|
+
if (id !== undefined) {
|
|
467
|
+
precedingId = id;
|
|
468
|
+
idlessOffset = 0;
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
idlessOffset += 1;
|
|
339
472
|
}
|
|
340
|
-
|
|
473
|
+
const identity = id === undefined
|
|
474
|
+
? `after\u0000${precedingId}\u0000${idlessOffset}`
|
|
475
|
+
: `id\u0000${id}`;
|
|
476
|
+
let position = positionByIdentity.get(identity);
|
|
477
|
+
if (position === undefined) {
|
|
478
|
+
position = positionByIdentity.size;
|
|
479
|
+
positionByIdentity.set(identity, position);
|
|
480
|
+
}
|
|
481
|
+
facts.push({ position, identity });
|
|
341
482
|
}
|
|
342
|
-
|
|
343
|
-
return out;
|
|
483
|
+
return facts;
|
|
344
484
|
}
|
|
345
485
|
/**
|
|
346
486
|
* Resolve submitted ranges against the session, then sort by source position and
|
|
@@ -396,52 +536,49 @@ async function projectSessionRanges(input) {
|
|
|
396
536
|
}
|
|
397
537
|
const rows = sessionLines.map((entry) => entry.row);
|
|
398
538
|
const dialogue = adaptSessionDialogue(rows);
|
|
539
|
+
const sourceFacts = stableSourceFacts(sessionLines);
|
|
540
|
+
const sourcePositionByIdentity = new Map();
|
|
541
|
+
for (const fact of sourceFacts) {
|
|
542
|
+
sourcePositionByIdentity.set(fact.identity, fact.position);
|
|
543
|
+
}
|
|
399
544
|
const seenIds = input.seenIds;
|
|
400
545
|
const lines = [];
|
|
401
|
-
const
|
|
546
|
+
const raw = [];
|
|
402
547
|
const ranges = normalizeResolvedRanges(input.session.ranges, sessionLines, input.session.path);
|
|
403
548
|
for (const range of ranges) {
|
|
404
549
|
for (let index = range.fromIndex; index <= range.toIndex; index += 1) {
|
|
550
|
+
const { position: sourcePosition, identity: sourceIdentity } = sourceFacts[index];
|
|
551
|
+
if (input.coveredSourceIdentities.has(sourceIdentity) ||
|
|
552
|
+
input.legacyCoveredSourcePositions.has(sourcePosition))
|
|
553
|
+
continue;
|
|
405
554
|
const entry = sessionLines[index];
|
|
406
555
|
if (entry.row === undefined) {
|
|
407
|
-
|
|
408
|
-
const amendment = input.amendmentsByKey.get(key);
|
|
409
|
-
if (amendment !== undefined) {
|
|
410
|
-
lines.push({
|
|
411
|
-
speaker: amendment.speaker,
|
|
412
|
-
s: input.s,
|
|
413
|
-
line: entry.line,
|
|
414
|
-
text: amendment.text,
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
else {
|
|
418
|
-
unparsable.push({ s: input.s, line: entry.line, raw: entry.raw });
|
|
419
|
-
}
|
|
556
|
+
raw.push({ raw: entry.raw, s: input.s, sourcePosition, sourceIdentity });
|
|
420
557
|
continue;
|
|
421
558
|
}
|
|
422
559
|
for (const event of dialogue[index] ?? []) {
|
|
423
560
|
if (event.id !== undefined) {
|
|
424
|
-
|
|
561
|
+
const identity = dialogueIdentity(input.s, event.id);
|
|
562
|
+
if (seenIds.has(identity))
|
|
425
563
|
continue;
|
|
426
|
-
seenIds.add(
|
|
564
|
+
seenIds.add(identity);
|
|
427
565
|
}
|
|
428
566
|
lines.push({
|
|
429
567
|
speaker: event.speaker,
|
|
430
568
|
s: input.s,
|
|
431
|
-
|
|
432
|
-
...(event.id === undefined ? {} : { id: event.id }),
|
|
569
|
+
sourcePosition,
|
|
570
|
+
...(event.id === undefined ? { sourceIdentity } : { id: event.id }),
|
|
433
571
|
text: event.text,
|
|
434
572
|
});
|
|
435
573
|
}
|
|
436
574
|
}
|
|
437
575
|
}
|
|
438
|
-
return { lines,
|
|
576
|
+
return { lines, raw, sourcePositionByIdentity };
|
|
439
577
|
}
|
|
440
578
|
/**
|
|
441
|
-
* Reproject the unique diary from cumulative bounds
|
|
442
|
-
* #918
|
|
443
|
-
*
|
|
444
|
-
* amendments 只在本轮读取的新范围确有对应坏行时生效;空 sessions 保持 no-op。
|
|
579
|
+
* Reproject the unique diary from cumulative bounds.
|
|
580
|
+
* #918:已投影行即卷宗;本轮只读 prior 未声明的 range(或新 session 区间)。
|
|
581
|
+
* 精确重复提交=幂等 no-op,不因历史源不可读而失败;空 sessions 保持 no-op。
|
|
445
582
|
* 证不出的 body 原字节经 unprojectedRaw 原样留存。
|
|
446
583
|
* Persistence appends one immutable commit through the Sitian appender seam.
|
|
447
584
|
*/
|
|
@@ -459,7 +596,6 @@ export async function reprojectTicketProvenance(input) {
|
|
|
459
596
|
sessions: [],
|
|
460
597
|
},
|
|
461
598
|
lines: prior.lines,
|
|
462
|
-
unparsable: [],
|
|
463
599
|
};
|
|
464
600
|
}
|
|
465
601
|
const merged = mergeSessionBounds(prior.header?.sessions, input.sessions);
|
|
@@ -476,32 +612,33 @@ export async function reprojectTicketProvenance(input) {
|
|
|
476
612
|
sessions: merged.sessions,
|
|
477
613
|
},
|
|
478
614
|
lines: prior.lines,
|
|
479
|
-
unparsable: [],
|
|
480
615
|
};
|
|
481
616
|
}
|
|
482
|
-
const
|
|
483
|
-
for (const amendment of input.amendments ?? []) {
|
|
484
|
-
const cumulativeIndex = merged.incomingIndexes[amendment.s];
|
|
485
|
-
if (cumulativeIndex === undefined)
|
|
486
|
-
continue;
|
|
487
|
-
amendmentsByKey.set(amendmentKey(cumulativeIndex, amendment.line), {
|
|
488
|
-
...amendment,
|
|
489
|
-
s: cumulativeIndex,
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
const seenIds = new Set(prior.lines.flatMap((line) => line.id === undefined ? [] : [line.id]));
|
|
617
|
+
const seenIds = new Set(prior.lines.flatMap((line) => line.id === undefined ? [] : [dialogueIdentity(line.s, line.id)]));
|
|
493
618
|
const fresh = [];
|
|
494
|
-
const
|
|
619
|
+
const raw = [];
|
|
495
620
|
for (const delta of deltas) {
|
|
496
621
|
const projected = await projectSessionRanges({
|
|
497
622
|
s: delta.s,
|
|
498
623
|
session: delta.session,
|
|
499
|
-
amendmentsByKey,
|
|
500
624
|
seenIds,
|
|
625
|
+
coveredSourceIdentities: prior.sourceIdentities.get(delta.s) ?? new Set(),
|
|
626
|
+
legacyCoveredSourcePositions: prior.legacySourcePositions.get(delta.s) ?? new Set(),
|
|
501
627
|
...(input.home === undefined ? {} : { home: input.home }),
|
|
502
628
|
});
|
|
629
|
+
fresh.push(...prior.lines.flatMap((line) => {
|
|
630
|
+
if (line.s !== delta.s)
|
|
631
|
+
return [];
|
|
632
|
+
const sourceIdentity = line.id === undefined
|
|
633
|
+
? line.sourceIdentity
|
|
634
|
+
: `id\u0000${line.id}`;
|
|
635
|
+
if (sourceIdentity === undefined)
|
|
636
|
+
return [];
|
|
637
|
+
const sourcePosition = projected.sourcePositionByIdentity.get(sourceIdentity);
|
|
638
|
+
return sourcePosition === undefined ? [] : [{ ...line, sourcePosition }];
|
|
639
|
+
}));
|
|
503
640
|
fresh.push(...projected.lines);
|
|
504
|
-
|
|
641
|
+
raw.push(...projected.raw);
|
|
505
642
|
}
|
|
506
643
|
const lines = mergeFreshIntoCarried(prior.lines, fresh);
|
|
507
644
|
const now = new Date().toISOString();
|
|
@@ -512,9 +649,6 @@ export async function reprojectTicketProvenance(input) {
|
|
|
512
649
|
updatedAt: now,
|
|
513
650
|
sessions: merged.sessions,
|
|
514
651
|
};
|
|
515
|
-
if (unparsable.length > 0) {
|
|
516
|
-
return { recordFile: prior.recordFile, header, lines, unparsable };
|
|
517
|
-
}
|
|
518
652
|
// One immutable projection commit. Its deterministic identity makes retries of
|
|
519
653
|
// the same logical increment converge, while unrelated concurrent increments
|
|
520
654
|
// append independently and are folded by readTicketProvenance.
|
|
@@ -525,7 +659,7 @@ export async function reprojectTicketProvenance(input) {
|
|
|
525
659
|
path: physicalPathIdentity(session.path),
|
|
526
660
|
ranges: session.ranges,
|
|
527
661
|
})),
|
|
528
|
-
lines: fresh,
|
|
662
|
+
lines: [...fresh, ...raw],
|
|
529
663
|
});
|
|
530
664
|
const identity = `ticket-provenance:${createHash("sha256").update(identityMaterial).digest("hex")}`;
|
|
531
665
|
const pointer = appendSitianRecord({
|
|
@@ -534,7 +668,7 @@ export async function reprojectTicketProvenance(input) {
|
|
|
534
668
|
payload: {
|
|
535
669
|
type: "ticket-provenance-append",
|
|
536
670
|
sessions: merged.sessions,
|
|
537
|
-
lines: fresh,
|
|
671
|
+
lines: [...fresh, ...raw],
|
|
538
672
|
},
|
|
539
673
|
});
|
|
540
674
|
const folded = await readTicketProvenance(input.ticketNumber, input.cwd, input.home);
|
|
@@ -542,6 +676,5 @@ export async function reprojectTicketProvenance(input) {
|
|
|
542
676
|
recordFile: pointer.recordFile,
|
|
543
677
|
header: folded.header ?? header,
|
|
544
678
|
lines: folded.lines,
|
|
545
|
-
unparsable: [],
|
|
546
679
|
};
|
|
547
680
|
}
|
|
@@ -31,9 +31,14 @@ import { JUDGE_OUTPUT_TOOL_NAME } from "../src/package-contracts/judge-output.ts
|
|
|
31
31
|
import { readOAuthKeepaliveProviders } from "../src/oauth-keepalive.ts";
|
|
32
32
|
import {
|
|
33
33
|
formatNavigatorRoleHelp,
|
|
34
|
+
type RoleRuntimeDependencies,
|
|
34
35
|
} from "../src/role-runtime.ts";
|
|
35
36
|
import { loadAuditorSoulFromSubjectInput } from "../src/auditor-soul.ts";
|
|
36
|
-
import {
|
|
37
|
+
import { loadPackagedRoleReferenceMaterials } from "../src/role-runtime-dependencies.ts";
|
|
38
|
+
import {
|
|
39
|
+
loadGatekeeperSessionMaterials,
|
|
40
|
+
loadMainRoleSessionMaterials,
|
|
41
|
+
} from "../src/session-opening-materials.ts";
|
|
37
42
|
const extensionPath = fileURLToPath(import.meta.url);
|
|
38
43
|
const packageRoot = fileURLToPath(new URL("..", import.meta.url));
|
|
39
44
|
const navigatorRoutePlaybookPath = fileURLToPath(new URL("../resources/navigator-route-playbook.md", import.meta.url));
|
|
@@ -105,12 +110,11 @@ export async function loadNavigatorWorkContext(
|
|
|
105
110
|
});
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
export
|
|
109
|
-
const oauthKeepaliveProviders = readOAuthKeepaliveProviders();
|
|
110
|
-
registerNavigatorModelCommand(pi);
|
|
113
|
+
export function createPiRoleRuntimeDependencies(pi: ExtensionAPI): RoleRuntimeDependencies {
|
|
111
114
|
const navigatorSessionFactory = createNativeNavigatorSessionFactory();
|
|
112
|
-
|
|
115
|
+
return {
|
|
113
116
|
packageRoot,
|
|
117
|
+
loadRoleReferenceMaterials: loadPackagedRoleReferenceMaterials,
|
|
114
118
|
loadJudgeSoul: () => loadMainRoleSessionMaterials("judge"),
|
|
115
119
|
loadFixerSoul: () => loadMainRoleSessionMaterials("fixer"),
|
|
116
120
|
loadFixPacket: (path) => readFile(path, "utf8"),
|
|
@@ -167,7 +171,13 @@ export default function roleRuntime(pi: ExtensionAPI): void {
|
|
|
167
171
|
}
|
|
168
172
|
return loadHomeCanonicalSkillBinding(name);
|
|
169
173
|
},
|
|
170
|
-
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export default function roleRuntime(pi: ExtensionAPI): void {
|
|
178
|
+
const oauthKeepaliveProviders = readOAuthKeepaliveProviders();
|
|
179
|
+
registerNavigatorModelCommand(pi);
|
|
180
|
+
createPiRoleRuntimeExtension(createPiRoleRuntimeDependencies(pi), {
|
|
171
181
|
transcriptFromContext,
|
|
172
182
|
oauthKeepalive: { providers: oauthKeepaliveProviders },
|
|
173
183
|
})(pi);
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chmod, copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { chmod, copyFile, cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { build } from "esbuild";
|
|
@@ -200,6 +200,11 @@ export async function buildPackageArtifacts() {
|
|
|
200
200
|
await buildAcpProductionHost();
|
|
201
201
|
await buildHeadlessProductionHost();
|
|
202
202
|
await buildMigrateBookTopology();
|
|
203
|
+
const pluginDir = join("dist", "method-host-plugin");
|
|
204
|
+
await rm(pluginDir, { recursive: true, force: true });
|
|
205
|
+
await mkdir(pluginDir, { recursive: true });
|
|
206
|
+
await cp("resources/method-host-plugin/.claude-plugin", join(pluginDir, ".claude-plugin"), { recursive: true });
|
|
207
|
+
await cp("resources/methods", join(pluginDir, "skills"), { recursive: true });
|
|
203
208
|
}
|
|
204
209
|
|
|
205
210
|
const isMain =
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One ACP host description. Every host-specific value the generic ACP adapter
|
|
3
|
-
* needs — binary location, argv shape, resume verb, binding filename,
|
|
3
|
+
* needs — binary location, argv shape, resume verb, binding filename,
|
|
4
4
|
* optional seat-profile soul — is data here; the lifecycle in role-turn-host.ts
|
|
5
5
|
* stays one copy (#732).
|
|
6
6
|
*/
|
|
@@ -30,7 +30,6 @@ export type AcpHostDescription = Readonly<{
|
|
|
30
30
|
boundResume: "session/load" | "session/new";
|
|
31
31
|
/** Durable ACP binding filename written beside the session principal. */
|
|
32
32
|
sessionBindingFile: string;
|
|
33
|
-
childEnv: Readonly<Record<string, string>>;
|
|
34
33
|
/**
|
|
35
34
|
* When set, the production factory ensures a seat profile whose SOUL.md is a
|
|
36
35
|
* symlink to the packaged role soul, and prefixes argv with `flag <name>`.
|
|
@@ -44,8 +43,7 @@ export function resolveAcpBinary(description: AcpHostDescription, operatorHome:
|
|
|
44
43
|
return join(operatorHome, ...description.binaryFromHome);
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
/** Stdio argv: optional profile flag, thinking flag
|
|
48
|
-
* prefix, optional model flag pair, suffix. */
|
|
46
|
+
/** Stdio argv: optional profile flag, thinking flag, prefix, model, suffix. */
|
|
49
47
|
export function acpStdioArgs(
|
|
50
48
|
description: AcpHostDescription,
|
|
51
49
|
model?: { readonly model?: string; readonly thinking?: string },
|
|
@@ -55,7 +55,6 @@ export function createProductionAcpRoleTurnHost(options: ProductionAcpHostOption
|
|
|
55
55
|
const { packageRoot, principalAuthority, description, hostName } = options;
|
|
56
56
|
const env: NodeJS.ProcessEnv = {
|
|
57
57
|
...process.env,
|
|
58
|
-
...description.childEnv,
|
|
59
58
|
AK_PACKAGE_ROOT: packageRoot,
|
|
60
59
|
};
|
|
61
60
|
|
package/src/auditor-soul.ts
CHANGED
|
@@ -85,10 +85,19 @@ export async function loadAuditorSoul(role: AuditorSoulRole): Promise<string> {
|
|
|
85
85
|
if (soul.trim().length === 0) {
|
|
86
86
|
throw new Error(`The ${role} auditor Soul is blank`);
|
|
87
87
|
}
|
|
88
|
-
return
|
|
88
|
+
return soul;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function loadAuditorReferenceMaterials(role: AuditorSoulRole): Promise<string> {
|
|
92
|
+
const soulPath = auditorSoulRelativePath(role);
|
|
93
|
+
return joinPackageMaterials(AUDITOR_SESSION_MATERIALS[role].filter((path) => path !== soulPath));
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
/** Runtime loader: subject input decides which soul file to assemble. */
|
|
92
97
|
export async function loadAuditorSoulFromSubjectInput(raw?: string): Promise<string> {
|
|
93
98
|
return loadAuditorSoul(resolveAuditorSubject(raw));
|
|
94
99
|
}
|
|
100
|
+
|
|
101
|
+
export function loadAuditorReferenceMaterialsFromSubjectInput(raw?: string): Promise<string> {
|
|
102
|
+
return loadAuditorReferenceMaterials(resolveAuditorSubject(raw));
|
|
103
|
+
}
|