@akagilnc/pi-workflow-roles 0.1.3708 → 0.1.3712
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/dist/acp-host/production-host.js +151 -514
- package/dist/adr-path-refs.js +1 -1
- package/dist/diarist-contracts.js +31 -21
- package/dist/diarist-role.js +17 -8
- package/dist/diarist.js +39 -388
- package/dist/package-contracts/terminating-tools.js +1 -1
- package/dist/packaged-role-registry.js +4 -8
- package/dist/pi/role-turn-host.js +1 -7
- package/dist/public-cli/main.js +93 -1470
- package/dist/role-runtime.js +60 -88
- package/dist/ticket-provenance-contracts.js +0 -5
- package/dist/ticket-provenance.js +2 -149
- package/package.json +1 -1
- package/resources/diarist-collect.md +21 -11
- package/src/acp-host/role-envelope.ts +1 -1
- package/src/adr-path-refs.ts +1 -1
- package/src/diarist-contracts.ts +48 -32
- package/src/diarist-role.ts +31 -16
- package/src/diarist.ts +54 -635
- package/src/host-contracts.ts +1 -5
- package/src/package-contracts/terminating-tools.ts +1 -1
- package/src/packaged-role-registry.ts +4 -8
- package/src/pi/role-turn-host.ts +1 -7
- package/src/public-cli/countersign-run.ts +2 -3
- package/src/public-cli/diarist-run.ts +15 -117
- package/src/role-runtime.ts +73 -105
- package/src/ticket-provenance-contracts.ts +6 -12
- package/src/ticket-provenance.ts +1 -207
- package/dist/diarist-mechanical.js +0 -411
- package/src/diarist-mechanical.ts +0 -571
package/dist/role-runtime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { writeSync } from "node:fs";
|
|
1
|
+
import { readFileSync, writeSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
2
3
|
import { ExplicitInternalActivationError, } from "./host-contracts.js";
|
|
3
4
|
import { recordReviewerDispatchRejectionSync } from "./public-cli/reviewer-dispatch-rejection.js";
|
|
4
5
|
import { Value } from "typebox/value";
|
|
@@ -6,7 +7,7 @@ import { sitianReport } from "./sitian-facade.js";
|
|
|
6
7
|
import { createSubmissionLedgerHost } from "./submission-ledger.js";
|
|
7
8
|
import { createCollectorLedger } from "./collector-ledger.js";
|
|
8
9
|
import { activationTraceRecordSchema, namedActivationCause } from "./activation-trace.js";
|
|
9
|
-
import { resolveActivationLedgerHomeForPath } from "./activation-ledger-topology.js";
|
|
10
|
+
import { homeFromRunDirectory, resolveActivationLedgerHomeForPath, } from "./activation-ledger-topology.js";
|
|
10
11
|
import { appendAcceptedActivationToBook, buildAcceptedActivationFact, correlationIdentityFromEnv, durableSessionPointer, resolveActivationLedgerHome, resolveBookKeyFromGit, } from "./activation-ledger.js";
|
|
11
12
|
import { writeStderrJsonlRecord } from "./stderr-jsonl.js";
|
|
12
13
|
import { createToolExecutionObservationFace, systemToolExecutionObservationMonoNow, writeToolExecutionObservationRecord, } from "./tool-execution-observation.js";
|
|
@@ -24,8 +25,8 @@ import { GLEANER_LEFT_ACCEPTED_TEXT, GLEANER_LEFT_BASE_FLAG } from "./gleaner-le
|
|
|
24
25
|
import { INSPECTOR_TOOL_SPEC, } from "./inspector-role.js";
|
|
25
26
|
import { INSPECTOR_ACCEPTED_TEXT } from "./inspector-contracts.js";
|
|
26
27
|
import { DIARIST_TOOL_SPEC, } from "./diarist-role.js";
|
|
27
|
-
import { DIARIST_ACCEPTED_TEXT,
|
|
28
|
-
import {
|
|
28
|
+
import { DIARIST_ACCEPTED_TEXT, projectDiaristEntries, } from "./diarist-contracts.js";
|
|
29
|
+
import { commitDiaristEntries } from "./diarist.js";
|
|
29
30
|
import { bindTicketNumberOnRunDirectory } from "./public-cli/invocation.js";
|
|
30
31
|
import { GATEKEEPER_TOOL_SPEC, } from "./gatekeeper-role.js";
|
|
31
32
|
import { NAVIGATOR_TOOL_SPEC, } from "./navigator-role.js";
|
|
@@ -90,13 +91,6 @@ const GLEANER_LEFT_TRANSPORT_FLAGS = Object.freeze([
|
|
|
90
91
|
definition: GLEANER_LEFT_BASE_FLAG.definition,
|
|
91
92
|
}),
|
|
92
93
|
]);
|
|
93
|
-
/** Diarist private transport: frozen source catalog path (ADR 0018 / #708). */
|
|
94
|
-
const DIARIST_TRANSPORT_FLAGS = Object.freeze([
|
|
95
|
-
Object.freeze({
|
|
96
|
-
name: DIARIST_SOURCES_FLAG.name,
|
|
97
|
-
definition: DIARIST_SOURCES_FLAG.definition,
|
|
98
|
-
}),
|
|
99
|
-
]);
|
|
100
94
|
/**
|
|
101
95
|
* Decode private transport flags into frozen admitted inputs.
|
|
102
96
|
* Envelope-owned; necessary JSON decode only (public --authority-ref owns grammar).
|
|
@@ -499,8 +493,9 @@ export function createAuditorRoleRuntime(roleHost, dependencies) {
|
|
|
499
493
|
};
|
|
500
494
|
}
|
|
501
495
|
/**
|
|
502
|
-
* LLM typed court-target assertion from diarist output (ADR 0075).
|
|
496
|
+
* LLM typed court-target assertion from diarist output (ADR 0075 / #779).
|
|
503
497
|
* null/absent = true-unbound; positive safe integer = ticket N.
|
|
498
|
+
* Shape-only read of the typed field — not content judgment of free text.
|
|
504
499
|
* Any other shape fails honestly — never washes into unbound.
|
|
505
500
|
*/
|
|
506
501
|
function readDiaristTicketAssertion(submitted) {
|
|
@@ -519,21 +514,40 @@ function readDiaristTicketAssertion(submitted) {
|
|
|
519
514
|
return { kind: "ticket", ticketNumber: n };
|
|
520
515
|
}
|
|
521
516
|
}
|
|
522
|
-
throw new
|
|
517
|
+
throw new Error("diarist ticketNumber must be a safe integer >= 1, null, or absent");
|
|
518
|
+
}
|
|
519
|
+
/** Run coordinates + optional pre-bound ticket from durable pages (#779). */
|
|
520
|
+
function readDiaristRunCoordinates() {
|
|
521
|
+
const runDirectory = process.env.AK_ROLE_RUN_DIR;
|
|
522
|
+
if (typeof runDirectory !== "string" || runDirectory.trim() === "") {
|
|
523
|
+
throw new Error("diarist accept requires AK_ROLE_RUN_DIR");
|
|
524
|
+
}
|
|
525
|
+
const admittedPath = join(runDirectory, "admitted-request.json");
|
|
526
|
+
const admitted = JSON.parse(readFileSync(admittedPath, "utf8"));
|
|
527
|
+
if (typeof admitted.projectRoot !== "string" || admitted.projectRoot.trim() === "") {
|
|
528
|
+
throw new Error(`diarist admitted-request missing projectRoot (${admittedPath})`);
|
|
529
|
+
}
|
|
530
|
+
const bound = typeof admitted.ticketNumber === "number" &&
|
|
531
|
+
Number.isSafeInteger(admitted.ticketNumber) &&
|
|
532
|
+
admitted.ticketNumber >= 1
|
|
533
|
+
? admitted.ticketNumber
|
|
534
|
+
: undefined;
|
|
535
|
+
return {
|
|
536
|
+
runDirectory,
|
|
537
|
+
projectRoot: admitted.projectRoot,
|
|
538
|
+
home: homeFromRunDirectory(runDirectory),
|
|
539
|
+
...(bound === undefined ? {} : { boundTicketNumber: bound }),
|
|
540
|
+
};
|
|
523
541
|
}
|
|
524
542
|
/**
|
|
525
|
-
* #708: 起居郎 public seat on the shared filed-officer envelope.
|
|
526
|
-
* Semantic collection happened in this role's own turn
|
|
527
|
-
*
|
|
528
|
-
*
|
|
529
|
-
*
|
|
543
|
+
* #708 / #779: 起居郎 public seat on the shared filed-officer envelope.
|
|
544
|
+
* Semantic collection happened in this role's own turn (LLM finds materials).
|
|
545
|
+
* Accept hook: bind typed ticket assertion → idempotent sitian append of whole
|
|
546
|
+
* blocks the LLM submitted. No frozen catalog, no quote/ticket reverse-verify
|
|
547
|
+
* of LLM output. Machine facts never come from model self-report (锚定宪法).
|
|
530
548
|
*/
|
|
531
|
-
export function createDiaristRoleRuntime(roleHost, dependencies
|
|
532
|
-
|
|
533
|
-
// saw and the rows accept commits are the same bytes. Accept never re-reads
|
|
534
|
-
// the file, so anything written to it mid-turn cannot become a diary entry.
|
|
535
|
-
let catalog;
|
|
536
|
-
const runtime = createFiledOfficerRuntime(roleHost, {
|
|
549
|
+
export function createDiaristRoleRuntime(roleHost, dependencies) {
|
|
550
|
+
return createFiledOfficerRuntime(roleHost, {
|
|
537
551
|
role: "diarist",
|
|
538
552
|
tool: DIARIST_TOOL_SPEC,
|
|
539
553
|
acceptedText: DIARIST_ACCEPTED_TEXT,
|
|
@@ -543,8 +557,8 @@ export function createDiaristRoleRuntime(roleHost, dependencies, getSourcesFlag)
|
|
|
543
557
|
? parameters
|
|
544
558
|
: undefined;
|
|
545
559
|
// LLM cannot identify the court target — escalate without machine facts.
|
|
546
|
-
// Strip sitian (锚定宪法) and ticketNumber (
|
|
547
|
-
//
|
|
560
|
+
// Strip sitian (锚定宪法) and ticketNumber (must not leak into
|
|
561
|
+
// caller-visible admitted typed key via decisiveFacts mirror).
|
|
548
562
|
if (submitted?.status === "escalate") {
|
|
549
563
|
if (!("sitian" in submitted) && !("ticketNumber" in submitted)) {
|
|
550
564
|
return submitted;
|
|
@@ -555,35 +569,17 @@ export function createDiaristRoleRuntime(roleHost, dependencies, getSourcesFlag)
|
|
|
555
569
|
return stripped;
|
|
556
570
|
}
|
|
557
571
|
const assertion = readDiaristTicketAssertion(submitted);
|
|
558
|
-
|
|
559
|
-
// A self-reported `sitian` is not machine fact (锚定宪法).
|
|
560
|
-
if (catalog === undefined) {
|
|
561
|
-
if (assertion.kind === "ticket") {
|
|
562
|
-
throw new DiaristTicketVerificationError("assertion-uninterpretable", `diarist asserted ticket #${assertion.ticketNumber} but no source catalog was frozen for this turn`);
|
|
563
|
-
}
|
|
564
|
-
if (submitted === undefined || !("sitian" in submitted))
|
|
565
|
-
return undefined;
|
|
566
|
-
const stripped = { ...submitted };
|
|
567
|
-
delete stripped.sitian;
|
|
568
|
-
return stripped;
|
|
569
|
-
}
|
|
572
|
+
const coords = readDiaristRunCoordinates();
|
|
570
573
|
// Already bound before the turn (typed handoff / resume): skip re-recognition
|
|
571
|
-
// (ADR 0075 已绑定 ticket 优先)
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
sitian: facts,
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
|
-
// First summons: LLM typed assertion → mechanical verify → bind → commit.
|
|
584
|
-
// true-unbound → 无录 (no volume). Verification failure throws (失败诚实).
|
|
585
|
-
// Cannot-identify must arrive as status=escalate above — never as silent unbound.
|
|
586
|
-
if (assertion.kind === "true-unbound") {
|
|
574
|
+
// and commit under that identity (ADR 0075 已绑定 ticket 优先).
|
|
575
|
+
const ticketNumber = coords.boundTicketNumber !== undefined
|
|
576
|
+
? coords.boundTicketNumber
|
|
577
|
+
: assertion.kind === "ticket"
|
|
578
|
+
? assertion.ticketNumber
|
|
579
|
+
: undefined;
|
|
580
|
+
// true-unbound → 无录 (no volume). Cannot-identify must arrive as
|
|
581
|
+
// status=escalate above — never as silent unbound.
|
|
582
|
+
if (ticketNumber === undefined) {
|
|
587
583
|
if (submitted === undefined || !("sitian" in submitted)) {
|
|
588
584
|
return { ...(submitted ?? { receipt: parameters }), ticketNumber: null };
|
|
589
585
|
}
|
|
@@ -594,43 +590,22 @@ export function createDiaristRoleRuntime(roleHost, dependencies, getSourcesFlag)
|
|
|
594
590
|
delete stripped.sitian;
|
|
595
591
|
return stripped;
|
|
596
592
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
ticketNumber: assertion.ticketNumber,
|
|
606
|
-
};
|
|
607
|
-
const facts = await commitDiaristSelections({
|
|
608
|
-
catalog: boundCatalog,
|
|
609
|
-
selections: projectDiaristSelections(parameters),
|
|
593
|
+
if (coords.boundTicketNumber === undefined) {
|
|
594
|
+
await bindTicketNumberOnRunDirectory(coords.runDirectory, ticketNumber);
|
|
595
|
+
}
|
|
596
|
+
const facts = await commitDiaristEntries({
|
|
597
|
+
ticketNumber,
|
|
598
|
+
cwd: coords.projectRoot,
|
|
599
|
+
home: coords.home,
|
|
600
|
+
entries: projectDiaristEntries(parameters),
|
|
610
601
|
});
|
|
611
602
|
return {
|
|
612
603
|
...(submitted ?? { receipt: parameters }),
|
|
613
|
-
ticketNumber
|
|
604
|
+
ticketNumber,
|
|
614
605
|
sitian: facts,
|
|
615
606
|
};
|
|
616
607
|
},
|
|
617
608
|
}, dependencies);
|
|
618
|
-
return {
|
|
619
|
-
async activate() {
|
|
620
|
-
catalog = loadFrozenDiaristCatalog(getSourcesFlag);
|
|
621
|
-
return runtime.activate();
|
|
622
|
-
},
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
/** Envelope-owned decode + one-shot load of the frozen catalog (ADR 0018). */
|
|
626
|
-
function loadFrozenDiaristCatalog(getFlag) {
|
|
627
|
-
const raw = getFlag();
|
|
628
|
-
if (raw === undefined)
|
|
629
|
-
return undefined;
|
|
630
|
-
if (typeof raw !== "string" || raw.trim() === "") {
|
|
631
|
-
throw new Error("Diarist source catalog flag must be a nonempty path");
|
|
632
|
-
}
|
|
633
|
-
return loadDiaristSourceCatalog(raw);
|
|
634
609
|
}
|
|
635
610
|
/** Countersign status words the queue reads (#753). */
|
|
636
611
|
const COUNTERSIGN_QUEUE_STATUSES = new Set(["converged", "continue", "escalate"]);
|
|
@@ -695,9 +670,6 @@ export function createRoleRuntimeExtension(dependencies) {
|
|
|
695
670
|
for (const flag of GLEANER_LEFT_TRANSPORT_FLAGS) {
|
|
696
671
|
roleHost.registerFlag(flag.name, flag.definition);
|
|
697
672
|
}
|
|
698
|
-
for (const flag of DIARIST_TRANSPORT_FLAGS) {
|
|
699
|
-
roleHost.registerFlag(flag.name, flag.definition);
|
|
700
|
-
}
|
|
701
673
|
let admitted = false;
|
|
702
674
|
let selectedRole;
|
|
703
675
|
/** Live Reviewer parent activation for envelope agent_start prompt assembly. */
|
|
@@ -1164,7 +1136,7 @@ export function createRoleRuntimeExtension(dependencies) {
|
|
|
1164
1136
|
throw new Error("Diarist runtime dependencies are not configured");
|
|
1165
1137
|
return dependencies.loadDiaristSoul();
|
|
1166
1138
|
},
|
|
1167
|
-
}
|
|
1139
|
+
});
|
|
1168
1140
|
let sessionMergerGitState = dependencies.mergerGitState;
|
|
1169
1141
|
const merger = createMergerRoleRuntime(roleHost, {
|
|
1170
1142
|
async loadSoul() { if (!dependencies.loadMergerSoul)
|
|
@@ -6,11 +6,6 @@
|
|
|
6
6
|
export const TICKET_PROVENANCE_KIND = "ticket-provenance";
|
|
7
7
|
/** Human-read view filename co-located with the JSONL volume. */
|
|
8
8
|
export const TICKET_PROVENANCE_HUMAN_VIEW = "起居录.md";
|
|
9
|
-
/**
|
|
10
|
-
* Incremental watermark: identities already offered to the collector this ticket
|
|
11
|
-
* (selected or not). Process state next to the volume — not a diary dual-source.
|
|
12
|
-
*/
|
|
13
|
-
export const TICKET_PROVENANCE_OFFERED_WATERMARK = "offered-identities.jsonl";
|
|
14
9
|
/** Payload discriminator: diagnostic residue (not a diary body entry). */
|
|
15
10
|
export const TICKET_PROVENANCE_RECORD_CLASS_DIAGNOSTIC = "diagnostic";
|
|
16
11
|
function isRecord(value) {
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Write/read via sitian facade only; no parallel destination logic.
|
|
4
4
|
*/
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
|
-
import { appendFileSync,
|
|
6
|
+
import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { resolveSitianRecordPath, sitianReport, readSitianRecords, } from "./sitian-facade.js";
|
|
9
|
-
import { TICKET_PROVENANCE_HUMAN_VIEW, TICKET_PROVENANCE_KIND,
|
|
9
|
+
import { TICKET_PROVENANCE_HUMAN_VIEW, TICKET_PROVENANCE_KIND, projectTicketProvenanceDiagnostic, projectTicketProvenanceEntry, } from "./ticket-provenance-contracts.js";
|
|
10
10
|
/** Subject string for ticket-keyed volumes — history follows the ticket. */
|
|
11
11
|
export function ticketProvenanceSubject(ticketNumber) {
|
|
12
12
|
if (!Number.isSafeInteger(ticketNumber) || ticketNumber < 1) {
|
|
@@ -75,82 +75,8 @@ export function resolveTicketProvenanceVolume(ticketNumber, cwd, home) {
|
|
|
75
75
|
recordFile: path.recordFile,
|
|
76
76
|
volumeDir: path.sessionDir,
|
|
77
77
|
humanViewFile: join(path.sessionDir, TICKET_PROVENANCE_HUMAN_VIEW),
|
|
78
|
-
offeredWatermarkFile: join(path.sessionDir, TICKET_PROVENANCE_OFFERED_WATERMARK),
|
|
79
78
|
};
|
|
80
79
|
}
|
|
81
|
-
/** Honest failure when the offered-identity watermark cannot be read as written. */
|
|
82
|
-
export class TicketProvenanceWatermarkError extends Error {
|
|
83
|
-
code = "ticket-provenance-watermark";
|
|
84
|
-
reason;
|
|
85
|
-
constructor(reason, message, options) {
|
|
86
|
-
super(message, options);
|
|
87
|
-
this.name = "TicketProvenanceWatermarkError";
|
|
88
|
-
this.reason = reason;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Read identities already offered to the collector (append-only watermark).
|
|
93
|
-
* Absent file → empty set. Any non-empty line that is not a JSON object with a
|
|
94
|
-
* non-empty string `identity` throws TicketProvenanceWatermarkError (失败诚实:
|
|
95
|
-
* never interpret corruption as "unseen" and silently re-offer).
|
|
96
|
-
*/
|
|
97
|
-
export function readOfferedIdentities(ticketNumber, cwd, home) {
|
|
98
|
-
const { offeredWatermarkFile } = resolveTicketProvenanceVolume(ticketNumber, cwd, home);
|
|
99
|
-
if (!existsSync(offeredWatermarkFile))
|
|
100
|
-
return new Set();
|
|
101
|
-
let text;
|
|
102
|
-
try {
|
|
103
|
-
text = readFileSync(offeredWatermarkFile, "utf8");
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
throw new TicketProvenanceWatermarkError("unreadable", `ticket-provenance offered watermark unreadable (${offeredWatermarkFile})`, { cause: error });
|
|
107
|
-
}
|
|
108
|
-
const seen = new Set();
|
|
109
|
-
const lines = text.split("\n");
|
|
110
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
111
|
-
const trimmed = lines[index].trim();
|
|
112
|
-
if (!trimmed)
|
|
113
|
-
continue;
|
|
114
|
-
let parsed;
|
|
115
|
-
try {
|
|
116
|
-
parsed = JSON.parse(trimmed);
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
throw new TicketProvenanceWatermarkError("malformed-json", `ticket-provenance offered watermark malformed JSON at line ${index + 1} (${offeredWatermarkFile})`, { cause: error });
|
|
120
|
-
}
|
|
121
|
-
if (typeof parsed !== "object" ||
|
|
122
|
-
parsed === null ||
|
|
123
|
-
Array.isArray(parsed) ||
|
|
124
|
-
typeof parsed.identity !== "string" ||
|
|
125
|
-
parsed.identity.length === 0) {
|
|
126
|
-
throw new TicketProvenanceWatermarkError("bad-shape", `ticket-provenance offered watermark bad shape at line ${index + 1} (${offeredWatermarkFile})`);
|
|
127
|
-
}
|
|
128
|
-
seen.add(parsed.identity);
|
|
129
|
-
}
|
|
130
|
-
return seen;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Append offered identities after a successful collector pass (selected or not).
|
|
134
|
-
* Idempotent per identity within the file (skip already-present). Creates volume
|
|
135
|
-
* dir only when writing the first watermark row.
|
|
136
|
-
*/
|
|
137
|
-
export function recordOfferedIdentities(input) {
|
|
138
|
-
if (input.identities.length === 0)
|
|
139
|
-
return;
|
|
140
|
-
const { volumeDir, offeredWatermarkFile } = resolveTicketProvenanceVolume(input.ticketNumber, input.cwd, input.home);
|
|
141
|
-
const already = new Set(readOfferedIdentities(input.ticketNumber, input.cwd, input.home));
|
|
142
|
-
const rows = [];
|
|
143
|
-
for (const identity of input.identities) {
|
|
144
|
-
if (identity.length === 0 || already.has(identity))
|
|
145
|
-
continue;
|
|
146
|
-
rows.push(`${JSON.stringify({ identity })}\n`);
|
|
147
|
-
already.add(identity);
|
|
148
|
-
}
|
|
149
|
-
if (rows.length === 0)
|
|
150
|
-
return;
|
|
151
|
-
mkdirSync(volumeDir, { recursive: true });
|
|
152
|
-
appendFileSync(offeredWatermarkFile, rows.join(""), "utf8");
|
|
153
|
-
}
|
|
154
80
|
/** Read projected diary entries + diagnostics for a ticket (empty when volume absent). */
|
|
155
81
|
export async function readTicketProvenance(ticketNumber, cwd, home) {
|
|
156
82
|
const { recordFile } = resolveTicketProvenanceVolume(ticketNumber, cwd, home);
|
|
@@ -254,79 +180,6 @@ export function ensureTicketProvenanceVolume(ticketNumber, cwd, home) {
|
|
|
254
180
|
appendFileSync(volume.recordFile, "", "utf8");
|
|
255
181
|
return volume;
|
|
256
182
|
}
|
|
257
|
-
/**
|
|
258
|
-
* Append a typed diagnostic onto the ticket-provenance volume (append-only).
|
|
259
|
-
* Same sitian kind + subject partition as diary entries; payload uses recordClass
|
|
260
|
-
* discriminator — never forged as a source entry. No sidecar / parallel ledger.
|
|
261
|
-
*/
|
|
262
|
-
export function appendTicketProvenanceDiagnostic(input) {
|
|
263
|
-
const subject = ticketProvenanceSubject(input.ticketNumber);
|
|
264
|
-
// quote-verify: content-stable identity so mid-batch retry does not duplicate.
|
|
265
|
-
// collector/issue-source: recordedAt keeps per-court failure history.
|
|
266
|
-
const identityParts = input.diagnostic.diagnosticKind === "quote-verify-failed"
|
|
267
|
-
? [
|
|
268
|
-
subject,
|
|
269
|
-
input.diagnostic.recordClass,
|
|
270
|
-
input.diagnostic.diagnosticKind,
|
|
271
|
-
input.diagnostic.cause,
|
|
272
|
-
input.diagnostic.reason ?? "",
|
|
273
|
-
]
|
|
274
|
-
: [
|
|
275
|
-
subject,
|
|
276
|
-
input.diagnostic.recordClass,
|
|
277
|
-
input.diagnostic.diagnosticKind,
|
|
278
|
-
input.diagnostic.recordedAt,
|
|
279
|
-
input.diagnostic.cause,
|
|
280
|
-
input.diagnostic.reason ?? "",
|
|
281
|
-
];
|
|
282
|
-
const identity = createHash("sha256")
|
|
283
|
-
.update(identityParts.join("\u0000"), "utf8")
|
|
284
|
-
.digest("hex");
|
|
285
|
-
return sitianReport({
|
|
286
|
-
level: "event",
|
|
287
|
-
kind: TICKET_PROVENANCE_KIND,
|
|
288
|
-
identity,
|
|
289
|
-
subject,
|
|
290
|
-
cwd: input.cwd,
|
|
291
|
-
...(input.home === undefined ? {} : { home: input.home }),
|
|
292
|
-
host: input.host ?? "diarist",
|
|
293
|
-
source: input.source ?? "diarist-diagnostic",
|
|
294
|
-
payload: input.diagnostic,
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
/** Issue-face source acquisition failure — typed diagnostic on the ticket volume. */
|
|
298
|
-
export function appendIssueSourceFailureDiagnostic(input) {
|
|
299
|
-
const recordedAt = input.recordedAt ?? new Date().toISOString();
|
|
300
|
-
return appendTicketProvenanceDiagnostic({
|
|
301
|
-
ticketNumber: input.ticketNumber,
|
|
302
|
-
cwd: input.cwd,
|
|
303
|
-
...(input.home === undefined ? {} : { home: input.home }),
|
|
304
|
-
source: "diarist-issue-source",
|
|
305
|
-
diagnostic: {
|
|
306
|
-
recordClass: TICKET_PROVENANCE_RECORD_CLASS_DIAGNOSTIC,
|
|
307
|
-
diagnosticKind: "issue-source-failed",
|
|
308
|
-
cause: input.cause,
|
|
309
|
-
recordedAt,
|
|
310
|
-
reason: input.reason,
|
|
311
|
-
},
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
/** Quote reverse-verify failure — single diagnostic expression (not a diary entry). */
|
|
315
|
-
export function appendQuoteVerifyFailureDiagnostic(input) {
|
|
316
|
-
const recordedAt = input.recordedAt ?? new Date().toISOString();
|
|
317
|
-
return appendTicketProvenanceDiagnostic({
|
|
318
|
-
ticketNumber: input.ticketNumber,
|
|
319
|
-
cwd: input.cwd,
|
|
320
|
-
...(input.home === undefined ? {} : { home: input.home }),
|
|
321
|
-
source: "diarist-quote-verify",
|
|
322
|
-
diagnostic: {
|
|
323
|
-
recordClass: TICKET_PROVENANCE_RECORD_CLASS_DIAGNOSTIC,
|
|
324
|
-
diagnosticKind: "quote-verify-failed",
|
|
325
|
-
cause: input.cause,
|
|
326
|
-
recordedAt,
|
|
327
|
-
},
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
183
|
/** Write the co-located human view next to the JSONL volume (derived, not dual-source).
|
|
331
184
|
* Ensures volume dir exists so empty courts still get the md face. */
|
|
332
185
|
export function writeTicketProvenanceHumanView(input) {
|
package/package.json
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
# 起居郎收集方法(owner-domain)
|
|
2
2
|
|
|
3
|
-
本文件是起居郎语义收集的方法真源(ADR 0075 `diarist-llm-collector` / `transcribe-whole-blocks
|
|
3
|
+
本文件是起居郎语义收集的方法真源(ADR 0075 `diarist-llm-collector` / `transcribe-whole-blocks`;#779 调用方无感)。
|
|
4
4
|
|
|
5
5
|
## 任务
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
为本案找出决策相关材料,以**整块**交卷入本票起居录。材料由你自己去找——不要等调用方贴路径、附件或预切清单。
|
|
8
|
+
|
|
9
|
+
## 如何找材料
|
|
10
|
+
|
|
11
|
+
你是 LLM,自己读:
|
|
12
|
+
|
|
13
|
+
- 本仓 Claude 会话卷:`~/.claude/projects/` 下与本仓相关的目录(路径里的 `_` 在目录名里常写作 `-`;主 clone 与 worktree 可能共用或分列,自行辨认)
|
|
14
|
+
- 本票票面与评论:用 `gh` 或等价方式读 GitHub issue
|
|
15
|
+
- 票面引用的 ADR 原件:仓内 `docs/adr/`
|
|
16
|
+
|
|
17
|
+
调用方只给传召文与 `--project`;不附清单、不写候选路径。找不到就如实说明,不编造。
|
|
8
18
|
|
|
9
19
|
## 何谓决策相关
|
|
10
20
|
|
|
@@ -16,20 +26,20 @@
|
|
|
16
26
|
- 陛下(owner)原话、确认、否决、收口条件
|
|
17
27
|
- 为上述裁决提供必要上下文的紧邻块
|
|
18
28
|
|
|
19
|
-
|
|
29
|
+
无关闲聊、纯工具噪声、与本票决策无涉的他票材料不选。相关性由你裁决。
|
|
20
30
|
|
|
21
31
|
## 誊录整块
|
|
22
32
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
33
|
+
- 入选即整块保留:交卷对象里写完整 `transcript`,并带上可回指的 `sourceRef`(会话文件+entry、issue URL、ADR path 等)。
|
|
34
|
+
- 不指针化、不摘要替代正文。
|
|
35
|
+
- 宁多勿漏:拿不准是否决策相关时入选。
|
|
26
36
|
|
|
27
37
|
## 本票身份
|
|
28
38
|
|
|
29
|
-
由你(本席 LLM)判断当前要办的是哪张票,在交卷对象上以 typed 字段交出——代码不做认票裁定(ADR 0075 `diarist-resolves-ticket-llm-layer`;owner
|
|
39
|
+
由你(本席 LLM)判断当前要办的是哪张票,在交卷对象上以 typed 字段交出——代码不做认票裁定(ADR 0075 `diarist-resolves-ticket-llm-layer`;owner:代码不准做判断/机械层不判断 LLM 输出)。
|
|
30
40
|
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
41
|
+
- 认得出本庭对象:`status: completed` 且 `ticketNumber: N`,并交 `entries`(可空)。
|
|
42
|
+
- 真无票对象(方案/派单/处置案等):`status: completed` 且 `ticketNumber: null`(或省略)。真无票是合法结果,不入录。
|
|
43
|
+
- 认不出本庭对象是哪一张:`status: escalate` 并写明 `reason`。识别不了就上抛,不得省略票号假装无录,也不得猜一个号。
|
|
44
|
+
- 不得从旁及的他票或任何猜测里挑一个号顶替认不出的身份。
|
|
35
45
|
- 传召文自然会提轮次、决定编号、commit sha、邻票号;你认的是本庭对象,不是文中出现的每一个数字。
|
|
@@ -79,7 +79,7 @@ export function projectAcpActivationFlags(request: RoleTurnRequest): Map<string,
|
|
|
79
79
|
: "casePath" in activation ? activation.casePath
|
|
80
80
|
: "inputPath" in activation ? activation.inputPath
|
|
81
81
|
: "sourceRun" in activation ? activation.sourceRun
|
|
82
|
-
:
|
|
82
|
+
: undefined;
|
|
83
83
|
if (path !== undefined) flags.set(inputFlag, path);
|
|
84
84
|
}
|
|
85
85
|
if (activation.role === "fixer" && activation.prerequisitesPath !== undefined) flags.set("ak-fixer-prerequisites", activation.prerequisitesPath);
|
package/src/adr-path-refs.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared docs/adr path extraction from free text (issue body / ticket face).
|
|
3
|
-
* Single authority for reviewer Spec discovery
|
|
3
|
+
* Single authority for reviewer Spec discovery.
|
|
4
4
|
*
|
|
5
5
|
* Shape only: segments under docs/adr/ without `..` or empty parts. Real IO
|
|
6
6
|
* confinement still lives at the read seam (ADR 0038).
|
package/src/diarist-contracts.ts
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public 起居郎 (diarist) terminating receipt contracts — ADR 0075 `diarist-is-role`.
|
|
3
|
-
* Lawful explicit releases: completed (
|
|
3
|
+
* Lawful explicit releases: completed (入录) | escalate (认不出本庭对象上抛).
|
|
4
4
|
* Machine facts about the volume come from the mechanical sitian seam, never
|
|
5
5
|
* from model self-report (锚定宪法); this module owns the receipt shape only.
|
|
6
|
+
*
|
|
7
|
+
* #779: no frozen candidate catalog; LLM finds sources and submits whole blocks.
|
|
8
|
+
* Mechanical layer does not judge LLM output (no quote/ticket/relevance verify).
|
|
6
9
|
*/
|
|
7
10
|
|
|
11
|
+
import type {
|
|
12
|
+
TicketProvenanceSourceKind,
|
|
13
|
+
TicketProvenanceSourceRef,
|
|
14
|
+
} from "./ticket-provenance-contracts.ts";
|
|
15
|
+
|
|
8
16
|
export const DIARIST_OUTPUT_TOOL_NAME = "ak_diarist_output";
|
|
9
17
|
export const DIARIST_ACCEPTED_TEXT = "起居郎回执已接受";
|
|
10
18
|
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/** One selected block reference into the frozen candidate catalog. */
|
|
22
|
-
export type DiaristSelection = {
|
|
23
|
-
/** Index into the catalog the diarist received. */
|
|
24
|
-
readonly candidateIndex: number;
|
|
25
|
-
/** Quotes claimed verbatim from that block (reverse-verified mechanically). */
|
|
26
|
-
readonly quotes: readonly string[];
|
|
19
|
+
/**
|
|
20
|
+
* One whole block the diarist chose to enter (ADR 0075 `transcribe-whole-blocks`).
|
|
21
|
+
* LLM supplies the block bytes + source pointer; mechanical layer appends as-is.
|
|
22
|
+
*/
|
|
23
|
+
export type DiaristEntrySubmission = {
|
|
24
|
+
readonly sourceKind: TicketProvenanceSourceKind;
|
|
25
|
+
readonly sourceRef: TicketProvenanceSourceRef;
|
|
26
|
+
/** Whole-block transcript — not a pointer-only stand-in. */
|
|
27
|
+
readonly transcript: string;
|
|
28
|
+
readonly timestamp: string;
|
|
27
29
|
/** Human-facing note (relation to this case). Not a machine gate. */
|
|
28
30
|
readonly note?: string;
|
|
29
31
|
};
|
|
@@ -31,13 +33,14 @@ export type DiaristSelection = {
|
|
|
31
33
|
export type DiaristOutput =
|
|
32
34
|
| {
|
|
33
35
|
readonly status: "completed";
|
|
34
|
-
readonly selections: readonly DiaristSelection[];
|
|
35
36
|
/**
|
|
36
37
|
* Typed court-target assertion (ADR 0075 `diarist-resolves-ticket-llm-layer`).
|
|
37
38
|
* Positive integer = 本庭对象=票N; null/absent = true-unbound (真无票→无录).
|
|
38
|
-
*
|
|
39
|
+
* LLM owns recognition; mechanical layer does not re-judge the number.
|
|
39
40
|
*/
|
|
40
41
|
readonly ticketNumber?: number | null;
|
|
42
|
+
/** Whole blocks to append under the asserted ticket. Empty list is lawful. */
|
|
43
|
+
readonly entries?: readonly DiaristEntrySubmission[];
|
|
41
44
|
}
|
|
42
45
|
| {
|
|
43
46
|
/** LLM cannot tell which ticket this summons is about — escalate, never wash into 无录. */
|
|
@@ -62,25 +65,38 @@ export function validateRecordedDiaristOutput(value: unknown): DiaristOutput {
|
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
/**
|
|
65
|
-
* Lenient projection of submitted
|
|
66
|
-
* gate). Rows that
|
|
67
|
-
* never bounced back at the role.
|
|
68
|
+
* Lenient projection of submitted entries (第 0 条: shape is not an admission
|
|
69
|
+
* gate). Rows that cannot form a lawful volume entry are dropped from the
|
|
70
|
+
* commit, never bounced back at the role. This is write-seam self-check only —
|
|
71
|
+
* not quote/ticket/relevance judgment of LLM content (#779).
|
|
68
72
|
*/
|
|
69
|
-
export function
|
|
70
|
-
const rows = (value as {
|
|
73
|
+
export function projectDiaristEntries(value: unknown): DiaristEntrySubmission[] {
|
|
74
|
+
const rows = (value as { entries?: unknown } | null)?.entries;
|
|
71
75
|
if (!Array.isArray(rows)) return [];
|
|
72
|
-
const out:
|
|
76
|
+
const out: DiaristEntrySubmission[] = [];
|
|
73
77
|
for (const row of rows) {
|
|
74
78
|
if (row === null || typeof row !== "object" || Array.isArray(row)) continue;
|
|
75
79
|
const record = row as Record<string, unknown>;
|
|
76
|
-
|
|
77
|
-
if (typeof
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
if (typeof record.sourceKind !== "string") continue;
|
|
81
|
+
if (typeof record.transcript !== "string" || record.transcript.length === 0) continue;
|
|
82
|
+
if (typeof record.timestamp !== "string" || record.timestamp.length === 0) continue;
|
|
83
|
+
if (record.sourceRef === null || typeof record.sourceRef !== "object" || Array.isArray(record.sourceRef)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const ref = record.sourceRef as Record<string, unknown>;
|
|
87
|
+
const sourceRef: TicketProvenanceSourceRef = {
|
|
88
|
+
...(typeof ref.sessionFile === "string" ? { sessionFile: ref.sessionFile } : {}),
|
|
89
|
+
...(typeof ref.entryId === "string" || typeof ref.entryId === "number"
|
|
90
|
+
? { entryId: ref.entryId }
|
|
91
|
+
: {}),
|
|
92
|
+
...(typeof ref.path === "string" ? { path: ref.path } : {}),
|
|
93
|
+
...(typeof ref.url === "string" ? { url: ref.url } : {}),
|
|
94
|
+
};
|
|
81
95
|
out.push({
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
sourceKind: record.sourceKind as TicketProvenanceSourceKind,
|
|
97
|
+
sourceRef,
|
|
98
|
+
transcript: record.transcript,
|
|
99
|
+
timestamp: record.timestamp,
|
|
84
100
|
...(typeof record.note === "string" ? { note: record.note } : {}),
|
|
85
101
|
});
|
|
86
102
|
}
|