projectkeeper 0.0.0-stage → 1.0.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 +56 -0
- package/LICENSE +21 -0
- package/README.md +283 -2
- package/README.zh-CN.md +285 -0
- package/THIRD_PARTY_NOTICES.md +53 -0
- package/bin/pk.mjs +6 -0
- package/bin/postinstall.mjs +5 -0
- package/bin/strip-types.mjs +28 -0
- package/package.json +68 -4
- package/patches/README.md +111 -0
- package/patches/pi-ai-0.85.0.ts +122 -0
- package/patches/pi-ai-0.87.1.ts +122 -0
- package/scripts/apply-pi-streaming-patch.ts +86 -0
- package/skills/clerk/coverage/SKILL.md +35 -0
- package/skills/clerk/cross-check/SKILL.md +118 -0
- package/skills/clerk/deepen-dispatch/SKILL.md +69 -0
- package/skills/clerk/lane-follow-up/SKILL.md +94 -0
- package/skills/clerk/lane-plan/SKILL.md +107 -0
- package/skills/clerk/lane-slot/SKILL.md +113 -0
- package/skills/clerk/lane-topic/SKILL.md +116 -0
- package/skills/clerk/orientation/SKILL.md +160 -0
- package/skills/clerk/reconcile/SKILL.md +77 -0
- package/skills/clerk/skeleton-dispatch/SKILL.md +43 -0
- package/skills/clerk/spot-check/SKILL.md +52 -0
- package/skills/clerk/synthesis/SKILL.md +65 -0
- package/skills/table-follow-up/SKILL.md +42 -0
- package/src/cli.ts +319 -0
- package/src/codemap/README.md +13 -0
- package/src/codemap/engines.ts +102 -0
- package/src/codemap/facts.ts +504 -0
- package/src/context/agent-entry.ts +232 -0
- package/src/context/ask.ts +97 -0
- package/src/context/assemble.ts +1058 -0
- package/src/context/code-source.ts +103 -0
- package/src/context/k-briefs.ts +114 -0
- package/src/context/lineage.ts +100 -0
- package/src/context/object-brief.ts +300 -0
- package/src/context/owner-words.ts +191 -0
- package/src/context/pack-fixture.ts +342 -0
- package/src/context/project-rules.ts +160 -0
- package/src/intake/intake.ts +392 -0
- package/src/intake/material-rules.ts +166 -0
- package/src/intake/removal.ts +316 -0
- package/src/intake/skipped.ts +72 -0
- package/src/keeper/adjustment.ts +565 -0
- package/src/keeper/bounds/boundary.ts +509 -0
- package/src/keeper/bounds/command.ts +1665 -0
- package/src/keeper/bounds/paths.ts +164 -0
- package/src/keeper/bounds/reads.ts +718 -0
- package/src/keeper/bounds/shell-write-guard.ts +521 -0
- package/src/keeper/capabilities.ts +136 -0
- package/src/keeper/clerk-steps.ts +151 -0
- package/src/keeper/clerk-tools.ts +1424 -0
- package/src/keeper/conversation.ts +253 -0
- package/src/keeper/evidence.ts +662 -0
- package/src/keeper/fake-provider.ts +132 -0
- package/src/keeper/held.ts +15 -0
- package/src/keeper/history-tools.ts +163 -0
- package/src/keeper/jobs/answering.ts +53 -0
- package/src/keeper/key-check.ts +105 -0
- package/src/keeper/ledger-tools.ts +295 -0
- package/src/keeper/link-check.ts +185 -0
- package/src/keeper/merge.ts +260 -0
- package/src/keeper/note-origin.ts +56 -0
- package/src/keeper/numbers-check.ts +102 -0
- package/src/keeper/open-in-pi.ts +57 -0
- package/src/keeper/organize/absence.ts +266 -0
- package/src/keeper/organize/carried-on.ts +214 -0
- package/src/keeper/organize/clerk-coverage.ts +293 -0
- package/src/keeper/organize/clerk-prompts.ts +132 -0
- package/src/keeper/organize/clerk.ts +1395 -0
- package/src/keeper/organize/coverage-gate.ts +19 -0
- package/src/keeper/organize/coverage-tools.ts +765 -0
- package/src/keeper/organize/entries.ts +153 -0
- package/src/keeper/organize/entry-gate.ts +289 -0
- package/src/keeper/organize/fill-tools.ts +718 -0
- package/src/keeper/organize/follow-up.ts +280 -0
- package/src/keeper/organize/generation-check.ts +504 -0
- package/src/keeper/organize/graph.ts +230 -0
- package/src/keeper/organize/lane-tools.ts +289 -0
- package/src/keeper/organize/looked-tools.ts +54 -0
- package/src/keeper/organize/materials.ts +243 -0
- package/src/keeper/organize/owner-lines.ts +493 -0
- package/src/keeper/organize/place-tools.ts +216 -0
- package/src/keeper/organize/placement-inference.ts +1045 -0
- package/src/keeper/organize/placement-time.ts +320 -0
- package/src/keeper/organize/placing.ts +821 -0
- package/src/keeper/organize/positioning.ts +33 -0
- package/src/keeper/organize/readiness.ts +47 -0
- package/src/keeper/organize/reading.ts +401 -0
- package/src/keeper/organize/round-blocks.ts +65 -0
- package/src/keeper/organize/round-news.ts +164 -0
- package/src/keeper/organize/round-open.ts +586 -0
- package/src/keeper/organize/schedule-state.ts +39 -0
- package/src/keeper/organize/schedule.ts +137 -0
- package/src/keeper/organize/service.ts +409 -0
- package/src/keeper/organize/skills.ts +116 -0
- package/src/keeper/organize/slots.ts +85 -0
- package/src/keeper/organize/stage-gate.ts +175 -0
- package/src/keeper/organize/stage-tools.ts +471 -0
- package/src/keeper/organize/standing-notes.ts +141 -0
- package/src/keeper/organize/table-follow-up.ts +330 -0
- package/src/keeper/organize/takeover-clerk.ts +832 -0
- package/src/keeper/organize/takeover-state.ts +103 -0
- package/src/keeper/organize/takeover.ts +216 -0
- package/src/keeper/organize/update-pending.ts +535 -0
- package/src/keeper/organize/workbench-placement.ts +92 -0
- package/src/keeper/own-keys.ts +114 -0
- package/src/keeper/owner-text.ts +165 -0
- package/src/keeper/project-folder.ts +334 -0
- package/src/keeper/prompts.ts +101 -0
- package/src/keeper/references.ts +177 -0
- package/src/keeper/relook/compose.ts +120 -0
- package/src/keeper/relook/prompts.ts +36 -0
- package/src/keeper/roles.ts +259 -0
- package/src/keeper/round-tools.ts +227 -0
- package/src/keeper/route.ts +148 -0
- package/src/keeper/rules.ts +97 -0
- package/src/keeper/runtime.ts +1858 -0
- package/src/keeper/scope-tools.ts +194 -0
- package/src/keeper/sendback-text.ts +83 -0
- package/src/keeper/step-timing.ts +207 -0
- package/src/keeper/testing/clerk-script.ts +212 -0
- package/src/keeper/testing/streaming-arguments.ts +57 -0
- package/src/keeper/tools.ts +2340 -0
- package/src/keeper/utterance-tools.ts +64 -0
- package/src/keeper/verbatim.ts +397 -0
- package/src/ledger/adapters.ts +186 -0
- package/src/ledger/arrangements.ts +257 -0
- package/src/ledger/blame.ts +58 -0
- package/src/ledger/code-engine-child.ts +69 -0
- package/src/ledger/code-engine.ts +548 -0
- package/src/ledger/code.ts +607 -0
- package/src/ledger/docs.ts +294 -0
- package/src/ledger/git-read.ts +321 -0
- package/src/ledger/index.ts +1850 -0
- package/src/ledger/item-range.ts +16 -0
- package/src/ledger/lines.ts +328 -0
- package/src/ledger/numbering.ts +269 -0
- package/src/ledger/ranges.ts +74 -0
- package/src/ledger/rebuild-worker.ts +15 -0
- package/src/ledger/rebuild.ts +387 -0
- package/src/ledger/repo-scan.ts +181 -0
- package/src/ledger/schema.ts +450 -0
- package/src/ledger/sessions.ts +134 -0
- package/src/ledger/text-scan.ts +466 -0
- package/src/ledger/time.ts +185 -0
- package/src/ledger/ts-symbols.ts +180 -0
- package/src/ledger/views.ts +377 -0
- package/src/mcp/server.ts +127 -0
- package/src/mcp/tools.ts +187 -0
- package/src/model/ids.ts +23 -0
- package/src/model/k-types.ts +912 -0
- package/src/model/time.ts +85 -0
- package/src/model/types.ts +1382 -0
- package/src/model/views-k.ts +707 -0
- package/src/model/vocab.ts +269 -0
- package/src/process/analysis.ts +79 -0
- package/src/process/breakpoint-candidates.ts +383 -0
- package/src/process/breakpoints.ts +746 -0
- package/src/process/claim-check.ts +295 -0
- package/src/process/delivery-links.ts +52 -0
- package/src/process/facts.ts +487 -0
- package/src/process/generations.ts +138 -0
- package/src/process/index.ts +243 -0
- package/src/process/integration.ts +128 -0
- package/src/process/placement.ts +107 -0
- package/src/process/plan.ts +276 -0
- package/src/process/sendbacks.ts +244 -0
- package/src/process/text.ts +185 -0
- package/src/process/units.ts +543 -0
- package/src/process/unnumbered.ts +431 -0
- package/src/process/work.ts +884 -0
- package/src/scope/candidates.ts +86 -0
- package/src/scope/decisions.ts +229 -0
- package/src/scope/discover.ts +741 -0
- package/src/scope/ignore.ts +126 -0
- package/src/scope/ignored-place.ts +93 -0
- package/src/scope/skip.ts +161 -0
- package/src/scope/toolchain.ts +180 -0
- package/src/scope/worktree.ts +187 -0
- package/src/server/api.ts +549 -0
- package/src/server/app.ts +595 -0
- package/src/server/drafts-api.ts +170 -0
- package/src/server/folders-api.ts +243 -0
- package/src/server/graph-view.ts +639 -0
- package/src/server/http.ts +224 -0
- package/src/server/k-agent-api.ts +56 -0
- package/src/server/k-rounds-d99.ts +91 -0
- package/src/server/k-views.ts +405 -0
- package/src/server/keeper-page.ts +446 -0
- package/src/server/keys-api.ts +239 -0
- package/src/server/project-folder-api.ts +104 -0
- package/src/server/vendor.ts +14 -0
- package/src/server/workbench-content.ts +561 -0
- package/src/sources/anchor.ts +129 -0
- package/src/sources/files.ts +267 -0
- package/src/sources/gitobs.ts +41 -0
- package/src/sources/history.ts +231 -0
- package/src/sources/scan-worker.ts +10 -0
- package/src/sources/sessions/locate.ts +292 -0
- package/src/sources/sessions/read.ts +346 -0
- package/src/sources/sessions/scope.ts +42 -0
- package/src/sources/sessions/utterances.ts +223 -0
- package/src/sources/watch.ts +215 -0
- package/src/store/json-file.ts +50 -0
- package/src/store/paths.ts +39 -0
- package/src/store/project-store.ts +295 -0
- package/src/store/versions.ts +172 -0
- package/src/store/workspace.ts +216 -0
- package/src/table/check.ts +333 -0
- package/src/table/follow-up-tools.ts +30 -0
- package/src/table/follow-up.ts +344 -0
- package/src/table/import.ts +610 -0
- package/src/table/project-home.ts +61 -0
- package/src/table/sessions.ts +168 -0
- package/src/table/takeover.ts +483 -0
- package/src/util/git-check.ts +29 -0
- package/src/util/git.ts +411 -0
- package/src/util/own-ts.ts +15 -0
- package/src/util/paths.ts +225 -0
- package/template/FILLING-IN.md +148 -0
- package/template/examples/papertrail.json +747 -0
- package/template/projectkeeper.schema.json +1001 -0
- package/ui/app.js +1135 -0
- package/ui/failures.js +82 -0
- package/ui/flyout.js +120 -0
- package/ui/folder-chooser.js +264 -0
- package/ui/glossary.js +156 -0
- package/ui/graph-fit.js +157 -0
- package/ui/graph-material.js +61 -0
- package/ui/graph-palette.js +109 -0
- package/ui/graph-tools.js +62 -0
- package/ui/graph.js +1712 -0
- package/ui/index.html +31 -0
- package/ui/k/code-view.js +230 -0
- package/ui/k/draft-view.js +93 -0
- package/ui/k/k-views.css +224 -0
- package/ui/k/round-tree.js +450 -0
- package/ui/k/scope-k.js +159 -0
- package/ui/k-data.js +118 -0
- package/ui/k-fold.js +106 -0
- package/ui/k-process.css +302 -0
- package/ui/k-process.js +934 -0
- package/ui/keeper-activity.js +175 -0
- package/ui/keeper-view.js +801 -0
- package/ui/keeper.css +101 -0
- package/ui/key-order.js +62 -0
- package/ui/list-fold.js +67 -0
- package/ui/markdown.d.ts +21 -0
- package/ui/markdown.js +472 -0
- package/ui/placement.d.ts +92 -0
- package/ui/placement.js +445 -0
- package/ui/popover-place.d.ts +16 -0
- package/ui/popover-place.js +89 -0
- package/ui/popover.js +198 -0
- package/ui/project-folder.js +37 -0
- package/ui/shell-state.js +40 -0
- package/ui/strip-rows.js +171 -0
- package/ui/styles.css +492 -0
- package/ui/theme-pick.js +36 -0
- package/ui/theme.js +74 -0
- package/ui/themes/LICENSE.md +13 -0
- package/ui/themes/README.md +44 -0
- package/ui/themes/TOKENS.md +291 -0
- package/ui/themes/a1/board.css +1557 -0
- package/ui/themes/a1/pk.css +392 -0
- package/ui/themes/b1/board.css +1163 -0
- package/ui/themes/b1/pk.css +218 -0
- package/ui/themes/d2/board.css +518 -0
- package/ui/themes/d2/pk.css +243 -0
- package/ui/themes/guard.css +73 -0
- package/ui/themes/i1/board.css +776 -0
- package/ui/themes/i1/pk.css +223 -0
- package/ui/themes/manifest.json +13 -0
- package/ui/views.js +2796 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Breakpoint candidates and how one lights (Spec §2.12 没有痕迹,要读过才算, §3.3 抽查; D99; CKC-24 AC-7, CKC-23 AC-22).
|
|
3
|
+
*
|
|
4
|
+
* What the program computes (breakpoints.ts `reconcile`) is a candidate: a lead for reading, not a conclusion. A lookup
|
|
5
|
+
* by number that finds nothing may only mean the delivery is written in an execution arrangement, a milestone summary or
|
|
6
|
+
* a QC report. A candidate lights when
|
|
7
|
+
* 1. a lane read where the missing step would be and did not find it, and recorded where it looked (`looked`,
|
|
8
|
+
* pk_record_looked) — a lane that finds it links it instead (pk_link_process), and the candidate goes out;
|
|
9
|
+
* 2. the round's independent spot-check confirmed it (`checked`, pk_confirm kind breakpoint confirmed true);
|
|
10
|
+
* 3. the round is not First usable, which draws no "something is missing" conclusion (§3.7).
|
|
11
|
+
* Seen on the resident run: after the first usable picture, 16 Done work items were lit `No trace of done` and read
|
|
12
|
+
* `Not started`; their deliveries were in the execution plan, the milestone summaries and the QC reports (E147).
|
|
13
|
+
*
|
|
14
|
+
* Also here: the spot-check's targets (every looked candidate; what is checked in full — the notes and the synthesis'
|
|
15
|
+
* outputs, D103; then the weighted sample of the rest), and the start-up fix that turns breakpoints lit without a check
|
|
16
|
+
* back into candidates.
|
|
17
|
+
*
|
|
18
|
+
* CS (D104, after the CQ run): the placement readings reach the spot-check whichever round wrote them. Every standing
|
|
19
|
+
* reason that left an item unplaced is checked in full until a spot-check found it right (`uncheckedReasons`; the round's
|
|
20
|
+
* spot-check record keeps each reason it checked, with its words and the outcome); the program's placements nobody
|
|
21
|
+
* reviewed are in the sample with a floor (`PLACEMENT_SAMPLE_FLOOR`); and the spot-check's count says both
|
|
22
|
+
* (`SpotCheck.placement`, `placementCounts`). The program counts all of it: the model is given the targets, not the duty
|
|
23
|
+
* to remember them.
|
|
24
|
+
*/
|
|
25
|
+
import type { Breakpoint, ClerkRound, PlacementCheckPlan, ReasonCheck, ReasonTarget, RoundStepKind, SpotCheckPlacement } from '../model/k-types.ts';
|
|
26
|
+
import type { ReferenceItem, WorkThread } from '../model/types.ts';
|
|
27
|
+
import type { ProjectStore, TraceInfo } from '../store/project-store.ts';
|
|
28
|
+
import type { Ledger } from '../ledger/index.ts';
|
|
29
|
+
import { isCandidate } from './breakpoints.ts';
|
|
30
|
+
import { clip } from './text.ts';
|
|
31
|
+
import { isProgramNote, stillHolding } from '../keeper/organize/standing-notes.ts';
|
|
32
|
+
import { inferenceContext, inferredPlacementRows, triedLine, type InferenceContext } from '../keeper/organize/placement-inference.ts';
|
|
33
|
+
import { reachOf } from '../keeper/organize/placing.ts';
|
|
34
|
+
import { earlierWork } from '../keeper/organize/carried-on.ts';
|
|
35
|
+
import { plansOf } from './placement.ts';
|
|
36
|
+
import { workbenchUnplaced } from '../keeper/organize/workbench-placement.ts';
|
|
37
|
+
|
|
38
|
+
export { isCandidate } from './breakpoints.ts';
|
|
39
|
+
|
|
40
|
+
// ───────────────────────── lighting ─────────────────────────
|
|
41
|
+
|
|
42
|
+
/** Why this job may not light this breakpoint, or null when it may (the three conditions above). */
|
|
43
|
+
export function lightingRefusal(store: ProjectStore, bp: Breakpoint, step: { readonly roundId: string; readonly kind: RoundStepKind } | null): string | null {
|
|
44
|
+
const leave = 'Put it out if the missing step did happen (confirmed: false, with the evidence that shows it), or leave it as it is. Nothing was written.';
|
|
45
|
+
if (!step || step.kind !== 'spot-check') {
|
|
46
|
+
return `A breakpoint lights only when the round's independent spot-check confirms it, after a lane looked for the missing step and did not find it. This job is ${step ? `the round's ${step.kind}` : 'no step of a round'}. ${leave}`;
|
|
47
|
+
}
|
|
48
|
+
const round = store.clerkRounds.get(step.roundId);
|
|
49
|
+
if (!round) return `The round ${step.roundId} is not on record, so it cannot be told whether it may draw a "missing" conclusion. Nothing was written.`;
|
|
50
|
+
if (round.kind === 'First usable') {
|
|
51
|
+
return `This round is First usable: it draws no conclusion that something is missing, so nothing lights in it. ${bp.kind} on ${bp.targetId} stays a candidate for the deepening. Nothing was written.`;
|
|
52
|
+
}
|
|
53
|
+
if (bp.lit) return null;
|
|
54
|
+
if (!bp.looked) {
|
|
55
|
+
return `No lane has looked for the missing step of ${bp.kind} on ${bp.targetId} yet (pk_record_looked): a candidate lights only after a lane read where the step would be written and did not find it. ${leave}`;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Light a breakpoint the spot-check confirmed (its conditions checked by `lightingRefusal`): `checked`, lit, this round's. */
|
|
61
|
+
export function lightCandidate(store: ProjectStore, bp: Breakpoint, meta: { readonly roundId: string; readonly jobId: string | null; readonly at: string }, trace: TraceInfo): Breakpoint {
|
|
62
|
+
const next: Breakpoint = {
|
|
63
|
+
...bp, lit: true, out: null,
|
|
64
|
+
checked: { roundId: meta.roundId, jobId: meta.jobId, at: meta.at },
|
|
65
|
+
confirmedInRoundId: meta.roundId,
|
|
66
|
+
// The round it lit in: its news counts it (round-news.ts), and the process view dates it from here.
|
|
67
|
+
roundId: bp.lit ? bp.roundId : meta.roundId,
|
|
68
|
+
updatedAt: meta.at,
|
|
69
|
+
};
|
|
70
|
+
store.breakpoints.put(next, trace);
|
|
71
|
+
return next;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ───────────────────────── the spot-check's targets ─────────────────────────
|
|
75
|
+
|
|
76
|
+
/** How many of the round's other judgements the spot-check samples (clerk.ts `SPOT_CHECK_SAMPLE`). */
|
|
77
|
+
export const SPOT_CHECK_SAMPLE = 24;
|
|
78
|
+
/** What goes wrong most, by collection (clerk.ts `startSpotCheck`). */
|
|
79
|
+
const WEIGHT: Readonly<Record<string, number>> = { patches: 3, threads: 3, territories: 3, breakpoints: 3, sendbacks: 3, reference: 2, marks: 2, notes: 2, links: 1, relations: 1, areas: 1 };
|
|
80
|
+
/** Claims about the code, residue, deletion, "decided" and "nobody", which a spot check finds wrong most often. */
|
|
81
|
+
const CODEISH = /code|file|wired|接线|unused|residual|残留|deleted|删除|decided|undecided|未定|已定|nobody|没人|verified|验过/i;
|
|
82
|
+
|
|
83
|
+
export interface SpotCheckTarget {
|
|
84
|
+
readonly collection: string;
|
|
85
|
+
readonly id: string;
|
|
86
|
+
readonly summary: string;
|
|
87
|
+
readonly weight: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface SpotCheckTargets {
|
|
91
|
+
/** Every candidate a lane looked for and did not find: each is checked, and lights only if the check confirms it. */
|
|
92
|
+
readonly candidates: readonly SpotCheckTarget[];
|
|
93
|
+
/**
|
|
94
|
+
* D103 (E153): what the spot-check checks in full, not as a sample — the round's final re-check of the synthesis. Every
|
|
95
|
+
* note current at the end of the round (this round's and those standing from earlier rounds), everything else the
|
|
96
|
+
* synthesis job wrote (its six-things judgements, its send-backs, marks and assessments), and the round's Result.
|
|
97
|
+
*/
|
|
98
|
+
readonly full: readonly SpotCheckTarget[];
|
|
99
|
+
/** The weighted sample of the rest of what the round's jobs wrote: the bulk (links, placements, fills, candidates). */
|
|
100
|
+
readonly sample: readonly SpotCheckTarget[];
|
|
101
|
+
/** How many judgements the round's jobs wrote, the looked candidates and what is checked in full left out. */
|
|
102
|
+
readonly written: number;
|
|
103
|
+
/** CS: the reasons that leave an item unplaced among `full` — standing, not settled by a spot-check yet, whichever round wrote them. */
|
|
104
|
+
readonly reasons: readonly ReasonTarget[];
|
|
105
|
+
/** CS: the program's unreviewed placements in the sample (at least `PLACEMENT_SAMPLE_FLOOR`, or all when fewer), and how many there are in all. */
|
|
106
|
+
readonly placements: PlacementCheckPlan['placements'];
|
|
107
|
+
readonly placementsUnreviewed: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Collections a job writes for the record, not as a judgement of the project: no spot-check target. */
|
|
111
|
+
const BOOKKEEPING: ReadonlySet<string> = new Set(['jobs', 'judgements', 'clerkRounds', 'rounds', 'contexts', 'authorizations', 'sources']);
|
|
112
|
+
|
|
113
|
+
/** What a job of a round wrote that a check can be held against: its puts, bookkeeping and the round's documents left out. */
|
|
114
|
+
export function judgementWritesOf(store: ProjectStore, jobId: string): { readonly collection: string; readonly id: string; readonly summary: string }[] {
|
|
115
|
+
const seen = new Set<string>();
|
|
116
|
+
const out: { collection: string; id: string; summary: string }[] = [];
|
|
117
|
+
for (const e of store.traceByJob(jobId, 100_000)) {
|
|
118
|
+
const key = `${e.collection}:${e.id}`;
|
|
119
|
+
if (e.op !== 'put' || BOOKKEEPING.has(e.collection) || e.collection === 'roundDocs' || seen.has(key)) continue;
|
|
120
|
+
seen.add(key);
|
|
121
|
+
out.push({ collection: e.collection, id: e.id, summary: e.summary });
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* What the spot-check checks in full (D103, E153; the owner: 「那你说是不是应该最后再做一轮复检,反正也就不到1mb的事。」):
|
|
128
|
+
* - every note current now, and every note a job of this round wrote, changed or closed — this round's notes and those
|
|
129
|
+
* standing from earlier rounds, whatever the round did with them (a note the spot-check itself withdraws stays a target);
|
|
130
|
+
* the program's own notes (the depth question) are not judgements and are left out;
|
|
131
|
+
* - everything else the round's synthesis job wrote: each six-things judgement, send-back, mark and assessment;
|
|
132
|
+
* - the round's Result, for the claims in it.
|
|
133
|
+
* Stable while the spot-check runs: its own corrections add nothing and take nothing away.
|
|
134
|
+
*/
|
|
135
|
+
export function fullCheckTargets(store: ProjectStore, round: Pick<ClerkRound, 'id'>): SpotCheckTarget[] {
|
|
136
|
+
const out = new Map<string, SpotCheckTarget>();
|
|
137
|
+
const add = (collection: string, id: string, summary: string) => { const key = `${collection}:${id}`; if (!out.has(key)) out.set(key, { collection, id, summary, weight: WEIGHT[collection] ?? 2 }); };
|
|
138
|
+
const jobs = store.jobs.filter((j) => j.step?.roundId === round.id);
|
|
139
|
+
const noteLine = (id: string, how: string): string => {
|
|
140
|
+
const n = store.notes.get(id);
|
|
141
|
+
const v = n?.versions[n.versions.length - 1];
|
|
142
|
+
return `note “${clip(v?.title ?? id, 100)}”${v?.ask ? ` (${v.ask})` : ''} — ${how}${n?.sixThing ? ` · six thing ${n.sixThing}` : ''}: ${clip((v?.preview ?? '').replace(/\s+/g, ' '), 200)}`;
|
|
143
|
+
};
|
|
144
|
+
const started = store.clerkRounds.get(round.id)?.startedAt ?? '';
|
|
145
|
+
const held = stillHolding(store, round.id);
|
|
146
|
+
const how = (id: string): string => {
|
|
147
|
+
const n = store.notes.get(id);
|
|
148
|
+
if (!n) return 'written this round';
|
|
149
|
+
if (n.status !== 'Current') return `${n.status.toLowerCase()} this round`;
|
|
150
|
+
if ((n.versions[0]?.at ?? '') >= started) return 'written this round';
|
|
151
|
+
if ((n.versions[n.versions.length - 1]?.at ?? '') >= started) return 'standing from an earlier round, updated this round';
|
|
152
|
+
return held.has(id) ? 'standing from an earlier round, confirmed as still holding this round' : 'standing from an earlier round, with no result this round';
|
|
153
|
+
};
|
|
154
|
+
for (const n of store.notes.filter((x) => x.status === 'Current' && !isProgramNote(x)).sort((a, b) => a.id.localeCompare(b.id))) add('notes', n.id, noteLine(n.id, how(n.id)));
|
|
155
|
+
for (const job of jobs) for (const w of judgementWritesOf(store, job.id)) if (w.collection === 'notes' && store.notes.has(w.id)) add('notes', w.id, noteLine(w.id, how(w.id)));
|
|
156
|
+
for (const job of jobs.filter((j) => j.step?.kind === 'synthesis')) for (const w of judgementWritesOf(store, job.id)) add(w.collection, w.id, w.summary);
|
|
157
|
+
for (const d of store.roundDocs.filter((x) => x.roundId === round.id && x.kind === 'Result')) add('roundDocs', d.id, `the round's Result “${clip(d.title, 100)}” (${d.markdown.length} characters; pk_read_assets kind roundDoc): check the claims in it`);
|
|
158
|
+
return [...out.values()];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const nameOf = (store: ProjectStore, id: string): string => {
|
|
162
|
+
const t = store.threads.get(id);
|
|
163
|
+
if (t) return `${t.ids[0] ? `${t.ids[0]} ` : ''}${t.title}`;
|
|
164
|
+
const r = store.reference.get(id);
|
|
165
|
+
return r ? `${r.category} ${r.name}` : id;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// ───────────────────────── the placement readings (CQ, CS; D104) ─────────────────────────
|
|
169
|
+
|
|
170
|
+
/** Of the program's unreviewed placements, how many the sample holds at least; all of them when there are fewer (CS). */
|
|
171
|
+
export const PLACEMENT_SAMPLE_FLOOR = 5;
|
|
172
|
+
|
|
173
|
+
const GONE = new Set(['Replaced', 'Removed']);
|
|
174
|
+
const reasonKey = (r: Pick<ReasonTarget, 'collection' | 'id' | 'field'>): string => `${r.collection}\x1f${r.id}\x1f${r.field}`;
|
|
175
|
+
const collectionOf = (kind: 'thread' | 'reference'): 'threads' | 'reference' => (kind === 'thread' ? 'threads' : 'reference');
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Every reason that leaves an item unplaced as the store stands: a live work item in no plan with its `noPlanWhy`, or in
|
|
179
|
+
* no module with its `noAreaWhy`; a live reference item on no Area and no Plan with its `wholeProductWhy` — a decision or
|
|
180
|
+
* a boundary, and a requirement or a design where the Keeper wrote one (`pk_write_reference` takes the reason on all
|
|
181
|
+
* four; on the CQ run 42 stood on decisions and boundaries, 7 on requirements, 1 on a design). A reason left on an item
|
|
182
|
+
* that was placed since leaves nothing unplaced and is not one. DB: "in no plan" is the workbench's — a work item whose
|
|
183
|
+
* only plan has no band there stands by its `noPlanWhy`, and that reason is checked like any other.
|
|
184
|
+
*/
|
|
185
|
+
export function standingReasons(store: ProjectStore): ReasonTarget[] {
|
|
186
|
+
const reach = reachOf(store);
|
|
187
|
+
const inGeneration = earlierWork(store); // CZ: an item carried on under the same number needs its current plan like any current work
|
|
188
|
+
const out: ReasonTarget[] = [];
|
|
189
|
+
const bench = workbenchUnplaced(store);
|
|
190
|
+
const byReason = new Set(bench.lists.workWrittenNoPlan);
|
|
191
|
+
for (const t of store.threads.filter((x) => !GONE.has(x.validity)).sort((a, b) => a.id.localeCompare(b.id))) {
|
|
192
|
+
const noPlan = t.noPlanWhy?.trim();
|
|
193
|
+
const noArea = t.noAreaWhy?.trim();
|
|
194
|
+
// Placed by its reason on the workbench; an item the workbench does not draw (deferred, abandoned) by the plans it names.
|
|
195
|
+
const standsByReason = byReason.has(t.id) || (!bench.placement.place.has(t.id) && !inGeneration.has(t.id) && plansOf(store, t).length === 0);
|
|
196
|
+
if (noPlan && standsByReason) out.push({ collection: 'threads', id: t.id, field: 'noPlanWhy', reason: noPlan });
|
|
197
|
+
if (noArea && reach.ofThread(t).areas.length === 0) out.push({ collection: 'threads', id: t.id, field: 'noAreaWhy', reason: noArea });
|
|
198
|
+
}
|
|
199
|
+
for (const d of store.reference.filter((x) => !GONE.has(x.validity)).sort((a, b) => a.id.localeCompare(b.id))) {
|
|
200
|
+
const why = d.wholeProductWhy?.trim();
|
|
201
|
+
if (!why) continue;
|
|
202
|
+
const at = reach.ofReference(d.id);
|
|
203
|
+
if (!at.areas.length && !at.plans.length) out.push({ collection: 'reference', id: d.id, field: 'wholeProductWhy', reason: why });
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** The last spot-check record of each reason, whichever round's spot-check made it, with that round. */
|
|
209
|
+
function lastReasonChecks(store: ProjectStore): Map<string, { readonly check: ReasonCheck; readonly round: string }> {
|
|
210
|
+
const out = new Map<string, { check: ReasonCheck; round: string }>();
|
|
211
|
+
for (const r of store.clerkRounds.all().sort((a, b) => a.startedAt.localeCompare(b.startedAt))) {
|
|
212
|
+
for (const check of r.spotCheck?.reasonChecks ?? []) {
|
|
213
|
+
const before = out.get(reasonKey(check));
|
|
214
|
+
if (!before || before.check.at <= check.at) out.set(reasonKey(check), { check, round: `${r.kind} round ${r.number}` });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* CS: the standing reasons no spot-check has settled yet, whichever round wrote them — never checked (most are written in
|
|
222
|
+
* the first usable round, which has no spot-check: on the CQ run the store held 52 `wholeProductWhy` and none was among
|
|
223
|
+
* the deepening's 39 targets), changed since they were checked, or found wrong and still standing (`earlier` says which
|
|
224
|
+
* round found it wrong). A reason a spot-check found right and nobody changed since is not a target again.
|
|
225
|
+
*/
|
|
226
|
+
export function uncheckedReasons(store: ProjectStore): (ReasonTarget & { readonly earlier?: string })[] {
|
|
227
|
+
const checks = lastReasonChecks(store);
|
|
228
|
+
return standingReasons(store).flatMap((r) => {
|
|
229
|
+
const last = checks.get(reasonKey(r));
|
|
230
|
+
if (!last || last.check.reason !== r.reason) return [r];
|
|
231
|
+
return last.check.wrong ? [{ ...r, earlier: last.round }] : [];
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** What a spot-check is given of the placement readings, as the store stands: for a round whose spot-check step did not fix it. */
|
|
236
|
+
export function placementCheckNow(store: ProjectStore, at: string): PlacementCheckPlan {
|
|
237
|
+
return { at, reasons: uncheckedReasons(store).map(({ collection, id, field, reason }) => ({ collection, id, field, reason })), placements: inferredPlacementRows(store).map((p) => ({ kind: p.kind, id: p.id, targetId: p.targetId })) };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* CS: the spot-check's count of the placement readings, from the plan fixed as it started and the round's records of
|
|
242
|
+
* what was checked: the reasons it was given, those it recorded, those it found wrong; the program placements of its
|
|
243
|
+
* sample it recorded, and those it found wrong. `checks` are the reasons recorded, to keep on the round; `right` the
|
|
244
|
+
* program placements it found right, which gives them their result.
|
|
245
|
+
*/
|
|
246
|
+
export function placementCounts(plan: PlacementCheckPlan, recorded: readonly { readonly collection: string; readonly id: string; readonly wrong: boolean }[], at: string, before: readonly ReasonCheck[] = []): { readonly placement: SpotCheckPlacement; readonly checks: ReasonCheck[]; readonly right: PlacementCheckPlan['placements'] } {
|
|
247
|
+
const verdict = new Map(recorded.map((t) => [`${t.collection}\x1f${t.id}`, t.wrong]));
|
|
248
|
+
const was = new Map(before.map((c) => [reasonKey(c), c]));
|
|
249
|
+
const checks = plan.reasons.flatMap((r): ReasonCheck[] => {
|
|
250
|
+
const wrong = verdict.get(`${r.collection}\x1f${r.id}`);
|
|
251
|
+
return wrong === undefined ? [] : [{ ...r, wrong, at: was.get(reasonKey(r))?.at ?? at }];
|
|
252
|
+
});
|
|
253
|
+
const sampled = plan.placements.filter((p) => verdict.has(`${collectionOf(p.kind)}\x1f${p.id}`));
|
|
254
|
+
const wrong = sampled.filter((p) => verdict.get(`${collectionOf(p.kind)}\x1f${p.id}`) === true);
|
|
255
|
+
return {
|
|
256
|
+
placement: { reasons: plan.reasons.length, reasonsChecked: checks.length, reasonsWrong: checks.filter((c) => c.wrong).length, programPlacementsSampled: sampled.length, programPlacementsWrong: wrong.length },
|
|
257
|
+
checks, right: sampled.filter((p) => !wrong.includes(p)),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** The spot-check's placement count in one line, for the round's result and for the spot-check's own messages. */
|
|
262
|
+
export function placementLine(p: SpotCheckPlacement): string {
|
|
263
|
+
return `placement: ${p.reasonsChecked} of ${p.reasons} reason${p.reasons === 1 ? '' : 's'} for leaving an item unplaced checked · ${p.reasonsWrong} wrong; ${p.programPlacementsSampled} program placement${p.programPlacementsSampled === 1 ? '' : 's'} sampled · ${p.programPlacementsWrong} wrong`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* What the round's spot-check checks (Spec §3.3 抽查: **all** of the round's "missing" conclusions, then a sample of the
|
|
268
|
+
* rest): every looked candidate, whatever round looked; what is checked in full (`fullCheckTargets`: the notes, the
|
|
269
|
+
* synthesis' outputs, the Result; and — CQ, CS — every standing reason that left an item unplaced and that no spot-check
|
|
270
|
+
* has settled yet, whichever round wrote it); and the weighted sample `startSpotCheck` draws from the rest of what the
|
|
271
|
+
* round's jobs (the main agent, its lanes, the earlier steps; not the spot-check itself) wrote, which holds at least
|
|
272
|
+
* `PLACEMENT_SAMPLE_FLOOR` of the program's unreviewed placements of any round — the bulk stays a sample.
|
|
273
|
+
*/
|
|
274
|
+
export function spotCheckTargets(store: ProjectStore, round: Pick<ClerkRound, 'id'>, size = SPOT_CHECK_SAMPLE, ledger: Ledger | null = null): SpotCheckTargets {
|
|
275
|
+
const looked = store.breakpoints.filter((b) => isCandidate(b) && !!b.looked).sort((a, b) => a.kind.localeCompare(b.kind) || a.targetId.localeCompare(b.targetId));
|
|
276
|
+
const candidates = looked.map((b): SpotCheckTarget => ({
|
|
277
|
+
collection: 'breakpoints', id: b.id, weight: WEIGHT.breakpoints!,
|
|
278
|
+
summary: `${b.kind} on ${nameOf(store, b.targetId)} (${b.targetId}): ${clip(b.why, 160)} — looked in ${b.looked!.where.join('; ')}`,
|
|
279
|
+
}));
|
|
280
|
+
const skip = new Set(looked.map((b) => `breakpoints:${b.id}`));
|
|
281
|
+
const full = fullCheckTargets(store, round).filter((t) => !skip.has(`${t.collection}:${t.id}`));
|
|
282
|
+
for (const t of full) skip.add(`${t.collection}:${t.id}`);
|
|
283
|
+
const jobIds = store.jobs.filter((j) => j.step?.roundId === round.id && j.step.kind !== 'spot-check').map((j) => j.id);
|
|
284
|
+
// CQ (D104), CS: the placement readings. Every standing reason that left an item unplaced and that no spot-check has
|
|
285
|
+
// settled yet — a work item's noPlanWhy or noAreaWhy, a reference item's wholeProductWhy, whichever round wrote it — is
|
|
286
|
+
// checked in full, with the records the program's trace names.
|
|
287
|
+
let ctx: InferenceContext | null = null;
|
|
288
|
+
const tried = (item: WorkThread | ReferenceItem): string => { try { ctx ??= inferenceContext(store, ledger); return clip(triedLine(ctx, item), 320); } catch { return 'the program could not read the records'; } };
|
|
289
|
+
const reasons = uncheckedReasons(store);
|
|
290
|
+
const byItem = new Map<string, (typeof reasons)[number][]>();
|
|
291
|
+
for (const r of reasons) byItem.set(`${r.collection}:${r.id}`, [...(byItem.get(`${r.collection}:${r.id}`) ?? []), r]);
|
|
292
|
+
for (const [key, own] of byItem) {
|
|
293
|
+
const first = own[0]!;
|
|
294
|
+
const earlier = own.find((r) => r.earlier)?.earlier;
|
|
295
|
+
const again = earlier ? ` · the spot-check of ${earlier} found it wrong, and it still stands` : '';
|
|
296
|
+
let summary: string;
|
|
297
|
+
if (first.collection === 'threads') {
|
|
298
|
+
const t = store.threads.get(first.id)!;
|
|
299
|
+
const said = own.map((r) => `${r.field === 'noPlanWhy' ? 'no plan' : 'no module'} — “${clip(r.reason, 120)}”`).join('; ');
|
|
300
|
+
summary = `work item ${nameOf(store, t.id)} written as ${said}; the records the program's trace names: ${tried(t)}${again}`;
|
|
301
|
+
} else {
|
|
302
|
+
const d = store.reference.get(first.id)!;
|
|
303
|
+
summary = `${nameOf(store, d.id)} written on the whole product — “${clip(first.reason, 120)}”; the records the program's trace names: ${tried(d)}${again}`;
|
|
304
|
+
}
|
|
305
|
+
// An item the synthesis wrote is a target already: its reason is checked with it.
|
|
306
|
+
const at = full.findIndex((t) => `${t.collection}:${t.id}` === key);
|
|
307
|
+
if (at >= 0) full[at] = { ...full[at]!, summary: `${full[at]!.summary} · also: ${summary}` };
|
|
308
|
+
else full.push({ collection: first.collection, id: first.id, weight: WEIGHT[first.collection]!, summary });
|
|
309
|
+
skip.add(key);
|
|
310
|
+
}
|
|
311
|
+
// CS: the program's placements nobody confirmed or moved yet, of this round and of earlier ones, oldest first. The
|
|
312
|
+
// sample holds at least PLACEMENT_SAMPLE_FLOOR of them (all when fewer); the rest join the weighted pool.
|
|
313
|
+
const placed = new Map<string, { target: SpotCheckTarget; records: PlacementCheckPlan['placements'][number][] }>();
|
|
314
|
+
for (const p of inferredPlacementRows(store)) {
|
|
315
|
+
const collection = collectionOf(p.kind);
|
|
316
|
+
const key = `${collection}:${p.id}`;
|
|
317
|
+
if (skip.has(key)) continue;
|
|
318
|
+
const said = `on ${p.placedOn} — ${clip(p.chain, 200)} (${p.round}, entering ${p.stage}; not reviewed since)`;
|
|
319
|
+
const before = placed.get(key);
|
|
320
|
+
if (before) placed.set(key, { target: { ...before.target, summary: `${before.target.summary}; and ${said}` }, records: [...before.records, { kind: p.kind, id: p.id, targetId: p.targetId }] });
|
|
321
|
+
else placed.set(key, { target: { collection, id: p.id, weight: WEIGHT.threads!, summary: `${p.name}: placed by the program (Inferred) ${said}` }, records: [{ kind: p.kind, id: p.id, targetId: p.targetId }] });
|
|
322
|
+
}
|
|
323
|
+
for (const key of placed.keys()) skip.add(key);
|
|
324
|
+
const writes: SpotCheckTarget[] = [];
|
|
325
|
+
for (const jobId of jobIds) {
|
|
326
|
+
for (const e of store.traceByJob(jobId, 5_000)) {
|
|
327
|
+
const key = `${e.collection}:${e.id}`;
|
|
328
|
+
if (e.op !== 'put' || skip.has(key) || !(e.collection in WEIGHT)) continue;
|
|
329
|
+
skip.add(key);
|
|
330
|
+
writes.push({ collection: e.collection, id: e.id, summary: e.summary, weight: WEIGHT[e.collection]! + (CODEISH.test(e.summary) ? 2 : 0) });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const placedTargets = [...placed.values()].map((x) => x.target);
|
|
334
|
+
const floor = Math.min(PLACEMENT_SAMPLE_FLOOR, placedTargets.length, size);
|
|
335
|
+
const rest = [...placedTargets.slice(floor), ...writes].sort((a, b) => b.weight - a.weight || a.id.localeCompare(b.id)).slice(0, Math.max(0, size - floor));
|
|
336
|
+
const sample = [...placedTargets.slice(0, floor), ...rest];
|
|
337
|
+
const sampled = new Set(sample.map((t) => `${t.collection}:${t.id}`));
|
|
338
|
+
return {
|
|
339
|
+
candidates, full, sample, written: placedTargets.length + writes.length,
|
|
340
|
+
reasons: reasons.map(({ collection, id, field, reason }) => ({ collection, id, field, reason })),
|
|
341
|
+
placements: [...placed].filter(([key]) => sampled.has(key)).flatMap(([, x]) => x.records),
|
|
342
|
+
placementsUnreviewed: [...placed.values()].reduce((n, x) => n + x.records.length, 0),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** The spot-check's block of targets: the looked candidates with where they were looked for, then the sample. */
|
|
347
|
+
export function spotCheckBlock(store: ProjectStore, t: SpotCheckTargets): string {
|
|
348
|
+
const lines: string[] = [];
|
|
349
|
+
lines.push(`=== Breakpoint candidates a lane looked for and did not find (${t.candidates.length}): check every one against the original, the ledger and the code; pk_confirm kind breakpoint confirmed true lights it, confirmed false with the evidence puts it out`);
|
|
350
|
+
for (const c of t.candidates) {
|
|
351
|
+
const b = store.breakpoints.get(c.id);
|
|
352
|
+
lines.push(`- ${c.id} · ${c.summary}`);
|
|
353
|
+
for (const e of (b?.evidence ?? []).slice(0, 4)) lines.push(` · ${e.kind} ${e.kind === 'file' ? e.label : e.id}${e.line ? `: “${clip(e.line, 160)}”` : ` (${clip(e.label, 100)})`}`);
|
|
354
|
+
}
|
|
355
|
+
if (!t.candidates.length) lines.push('None this round.');
|
|
356
|
+
const reasonItems = new Set(t.reasons.map((r) => `${r.collection}:${r.id}`)).size;
|
|
357
|
+
lines.push(`=== Checked in full, not sampled (${t.full.length}): every note current now — this round's and those standing from earlier rounds — everything else the synthesis wrote, the claims of the round's Result, and every reason that leaves an item unplaced and that no spot-check has checked yet, whichever round wrote it (noPlanWhy, noAreaWhy, wholeProductWhy: ${t.reasons.length ? `${t.reasons.length} on ${reasonItems} item${reasonItems === 1 ? '' : 's'}` : 'none'}), each with the records the program's trace names. Check each against the original, the ledger or the code, and record each (pk_record_spot_check)`);
|
|
358
|
+
for (const s of t.full) lines.push(`- ${s.collection} ${s.id}: ${s.summary}`);
|
|
359
|
+
if (!t.full.length) lines.push('Nothing: no note is current, the synthesis wrote nothing, and no reason stands unchecked.');
|
|
360
|
+
lines.push(t.placementsUnreviewed
|
|
361
|
+
? `=== The sample (${t.sample.length} of ${t.written} other judgements: what this round wrote, and the program's ${t.placementsUnreviewed} Inferred placement${t.placementsUnreviewed === 1 ? '' : 's'} nobody has reviewed yet, of this round and earlier ones — ${t.placements.length} of them in the sample)`
|
|
362
|
+
: `=== The sample (${t.sample.length} of ${t.written} other judgements this round wrote)`);
|
|
363
|
+
for (const s of t.sample) lines.push(`- ${s.collection} ${s.id}: ${s.summary}`);
|
|
364
|
+
return lines.join('\n');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ───────────────────────── the start-up fix ─────────────────────────
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* At start, once: a breakpoint lit without the independent check (lit by the program before D99) goes back to being a
|
|
371
|
+
* candidate — not lit, not out, still recorded, for the lanes to look at and the spot-check to check. The owner's answers
|
|
372
|
+
* are left as they are. Returns how many went back. Like `linkPutOutDeliveries`, a second run finds nothing.
|
|
373
|
+
*/
|
|
374
|
+
export function unlightUnchecked(store: ProjectStore): number {
|
|
375
|
+
let n = 0;
|
|
376
|
+
for (const b of store.breakpoints.filter((x) => x.lit && !x.checked && !x.ownerResponse)) {
|
|
377
|
+
store.breakpoints.put({ ...b, lit: false, out: null, confirmedInRoundId: null, updatedAt: new Date().toISOString() }, {
|
|
378
|
+
jobId: null, summary: `Breakpoint ${b.kind} on ${b.targetId}: a candidate again — it was lit without a lane's look and the independent check`,
|
|
379
|
+
});
|
|
380
|
+
n++;
|
|
381
|
+
}
|
|
382
|
+
return n;
|
|
383
|
+
}
|