@gitroll/tacitt-cli 0.6.12 → 0.6.13

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.
Files changed (38) hide show
  1. package/dist/{agent-setup-B12VbvN_.mjs → agent-setup-Doy_AnPY.mjs} +2 -2
  2. package/dist/agent-setup-worker.mjs +5 -5
  3. package/dist/cli.mjs +10 -10
  4. package/dist/codex-title-worker.mjs +1 -1
  5. package/dist/configured-preview-worker.mjs +5 -5
  6. package/dist/configured-session-worker.mjs +5 -5
  7. package/dist/{frame-B3He21G8.mjs → frame-BPbypzmt.mjs} +1 -1
  8. package/dist/{heap-Blrat--4.mjs → heap-DCS_WXIA.mjs} +7 -3
  9. package/dist/inspection-worker.mjs +4 -4
  10. package/dist/{job-CBuIMTQ0.mjs → job--zxwgaPy.mjs} +135 -36
  11. package/dist/{locate-D-ZaHpI0.mjs → locate-PLEyLYW-.mjs} +287 -236
  12. package/dist/{model-DR9odhWP.mjs → model-CyXvsWLz.mjs} +1 -1
  13. package/dist/operation-executor.mjs +3 -3
  14. package/dist/preview-worker.mjs +1 -1
  15. package/dist/privacy-validator.mjs +1 -1
  16. package/dist/{query-BYDcNKy2.mjs → query-BC7MIaWc.mjs} +335 -115
  17. package/dist/{run-DQ9xyKSt.mjs → run-Dod7Vykp.mjs} +6 -6
  18. package/dist/{src-DeNJ8vOD.mjs → src-CP-K2PBv.mjs} +1779 -1686
  19. package/dist/{src-Do-qzuMn.mjs → src-CttNuxOn.mjs} +590 -542
  20. package/dist/{src-CKQlTA2Z.mjs → src-dL6y2mcu.mjs} +180 -30
  21. package/dist/store-database-worker.mjs +1 -1
  22. package/package.json +1 -1
  23. package/skills/tacitt/SKILL.md +3 -3
  24. package/skills/tacitt/references/b-method.md +4 -2
  25. package/skills/tacitt/references/bootstrap.md +1 -1
  26. package/skills/tacitt/references/commands.md +10 -4
  27. package/skills/tacitt/references/journey.md +3 -3
  28. package/skills/tacitt/references/parallel-review.md +22 -9
  29. package/skills/tacitt/references/remote-workers.md +6 -2
  30. package/skills/tacitt/scripts/INDEX.md +4 -4
  31. package/skills/tacitt/scripts/answer.mjs +12 -1
  32. package/skills/tacitt/scripts/describe.mjs +3 -2
  33. package/skills/tacitt/scripts/journey-classification.mjs +1 -0
  34. package/skills/tacitt/scripts/journey-scan-timing.mjs +3 -2
  35. package/skills/tacitt/scripts/journey.mjs +1 -1
  36. package/skills/tacitt/scripts/privacy-validator.mjs +84 -33
  37. package/skills/tacitt/scripts/review.d.mts +1 -0
  38. package/skills/tacitt/scripts/review.mjs +296 -35
@@ -6,8 +6,8 @@
6
6
  <!-- index-entries
7
7
  ./ | Source / reference | Release-pinned phase and review helpers using the paired CLI for frozen inputs and batch persistence.
8
8
  INDEX.md | Directory index | Lists executable helpers shipped with the exact Skill release.
9
- answer.mjs | CLI publisher | Validates task-bound classification and privacy drafts, including required privacy unit coverage and reused-card policy with repair diagnostics, reports card issue paths without mutating validator errors, and atomically publishes canonical answers.
10
- describe.mjs | Runtime | Binds proposed generated metadata, never Hidden, to the current decision/context.
9
+ answer.mjs | CLI publisher | Validates task-bound classification and privacy drafts, including required privacy unit coverage and reused-card policy with repair diagnostics, reports card issue paths without mutating validator errors, atomically publishes canonical answers, and emits hash-verified source pages with manifest-derived byte framing while preserving legacy page output.
10
+ describe.mjs | Runtime | Binds proposed generated metadata to the current decision/context. A value the person hid is shown as `[hidden]` in the Work title and the workspace title; descriptions stay unchanged and titles the person typed are not masked by Hide answers.
11
11
  journey-card-consolidation.d.mts | Declaration | Types global card generation, policy comparison, identity boundaries, answer validation and final key expansion.
12
12
  journey-card-consolidation.mjs | Runtime | Immutably merges matching existing-policy extensions without a second model task (ADR 0251); policy or identity mismatches fall back to digest-bound global review, with exact key coverage and five/ten question limits.
13
13
  journey-card-values.d.mts | Declaration | Types card value settlement for helper tests.
@@ -19,7 +19,7 @@ journey-classification-tasks.d.mts | Declaration | Types initial classification
19
19
  journey-classification-tasks.mjs | Runtime | Original-root routing, capacity-balanced bounded tasks and unresolved-first ready ordering with stable assignments.
20
20
  journey-classification-validation.d.mts | Declaration | Shared classification validator contracts.
21
21
  journey-classification-validation.mjs | Runtime | Shared root judgment, stable target identity, fixed project metadata, consolidation validation, target-preserving assignment expansion and the late check that an item joins only placed Works with one access record (ADR 0274).
22
- journey-classification.mjs | Runtime | Batched known-project metadata, unknown-root grouping and bounded evidence, versioned checkpoints and generation progress.
22
+ journey-classification.mjs | Runtime | Batched known-project metadata, unknown-root grouping and streamed four-row evidence, versioned checkpoints and generation progress.
23
23
  journey-consolidation-submit.mjs | Runtime | Expands candidate assignments, applies per-root overrides, combines small topics into Unsorted, attaches known projects and checks exact root coverage.
24
24
  journey-consolidation.d.mts | Declaration | Consolidation helper contract for bounded evidence regression tests.
25
25
  journey-consolidation.mjs | Runtime | Builds proposed-name and stable-target candidate buckets, persists complete reconciliation input, and returns compact dispatch descriptors.
@@ -38,7 +38,7 @@ local-cli.mjs | Read-only package verifier | Validates an explicit installed CLI
38
38
  review-stream-client.d.mts | Type declarations | Types the fixed request/shutdown limits, private lifecycle callbacks and review-stream client methods.
39
39
  review-stream-client.mjs | Runtime | Bounds requests and owned-child shutdown, permits retry only for explicit pre-request startup busy, and preserves unknown lost-write outcomes for receipt replay.
40
40
  review.d.mts | Type declarations | Types the review helper's exported task rendering, answer reading, per-Work progress and entry functions for tests.
41
- review.mjs | Runtime | Session-ready scanning, immediate sixteen-slot refill, blocked-Work exclusion, largest-Work queue ordering, isolated retries, batched native reader and turn receipts with launch reservations, bounded reissue and visible interruption recovery, planned card capacity, outcome-reported preparation recovery and read-only status, metadata-gated questions, Track B-gated final preview and hash-bound answer recovery.
41
+ review.mjs | Runtime | Session-ready scanning, ready-pack-first twenty-four-slot refill, 30000-token pack target, manifest-derived page byte capture, default reader pull with native self-binding, host-capability fallback and replayable slot transfer, blocked-Work exclusion, largest-Work queue ordering, isolated retries, batched native reader and turn receipts with launch reservations, bounded reissue and visible interruption recovery, planned card capacity, outcome-reported preparation recovery and read-only status, metadata-gated questions, Track B-gated final preview and hash-bound answer recovery.
42
42
  verify-cli.mjs | Verification entry | Checks a managed local receipt and its current package bytes before the Skill starts a journey.
43
43
  -->
44
44
 
@@ -82,8 +82,19 @@ async function publish(args) {
82
82
  const receipt = /\nREAD_RECEIPT ([a-f0-9]{64})\n$/u.exec(output)?.[1]
83
83
  if (!receipt || createHash('sha256').update(output).digest('hex') !== line.slice(prefix.length))
84
84
  throw new Error('SOURCE_BINDING')
85
+ const bytePrefix = 'Page bytes ' + ref + '/' + ordinal + ' '
86
+ const byteLine = lines.slice(start + 1).find((item) => item.startsWith(bytePrefix))
87
+ const expectedBytes = byteLine?.slice(bytePrefix.length)
88
+ if (
89
+ expectedBytes !== undefined &&
90
+ (!/^[1-9]\d*$/u.test(expectedBytes) || Number(expectedBytes) !== Buffer.byteLength(output))
91
+ )
92
+ throw new Error('SOURCE_BINDING')
93
+ // Legacy manifests keep their original output; new capture code consumes this trusted framing.
94
+ const framed =
95
+ expectedBytes === undefined ? output : 'READ_BYTES ' + expectedBytes + '\n' + output
85
96
  await new Promise((done, reject) =>
86
- process.stdout.write(output, (error) => (error ? reject(error) : done())),
97
+ process.stdout.write(framed, (error) => (error ? reject(error) : done())),
87
98
  )
88
99
  // A new page is a heartbeat; repeated reads of the same page cannot renew a lost reader.
89
100
  const directory = taskPath.replace(/\.md$/u, '.reads')
@@ -1,7 +1,8 @@
1
1
  import { randomUUID } from 'node:crypto'
2
2
 
3
- // The parent supplies only its authorized context and proposed generated metadata, which is
4
- // never Hidden (ADR 0174).
3
+ // The parent supplies only its authorized context and proposed generated metadata.
4
+ // A value the person hid is shown as [hidden] in the Work title and the workspace title.
5
+ // Descriptions stay unchanged; titles the person typed are not masked by Hide answers.
5
6
  const object = (value) => typeof value === 'object' && value !== null && !Array.isArray(value)
6
7
  const fail = () => {
7
8
  throw new Error('INVALID_DESCRIPTION_ANNOTATIONS')
@@ -102,6 +102,7 @@ export async function classificationEvidence(directory, manifest, view, call, li
102
102
  let cursor
103
103
  for (let pageNumber = 0; pageNumber < 2; pageNumber++) {
104
104
  const content = await call('journey.classificationRead', {
105
+ limit: 4,
105
106
  journeyId: view.journeyId,
106
107
  workId,
107
108
  rootSessionId: root.rootSessionId,
@@ -198,7 +198,7 @@ export async function observeScanTiming(run, observation) {
198
198
  if (!receipt || receipt.completedAt) return
199
199
  const at = observation.at
200
200
  const measurements = receipt.measurements ?? {
201
- slots: { capacity: 16, busyChildMs: 0, waitingBatchWallMs: 0, refillGapMs: [], children: [] },
201
+ slots: { capacity: 24, busyChildMs: 0, waitingBatchWallMs: 0, refillGapMs: [], children: [] },
202
202
  preparation: {
203
203
  intervals: [],
204
204
  milliseconds: { 'before-first-batch': 0, 'mid-run': 0, tail: 0 },
@@ -207,7 +207,8 @@ export async function observeScanTiming(run, observation) {
207
207
  observationBasis: {
208
208
  dispatch: 'helper-reservation',
209
209
  publication: 'answer-file-mtime',
210
- slotRelease: 'confirmed-native-close-when-bound-otherwise-helper-observation',
210
+ slotRelease:
211
+ 'confirmed-native-close-or-persistent-assignment-handoff-otherwise-helper-observation',
211
212
  cards: 'parent-reported-count-at-helper-boundary',
212
213
  preparation: 'helper-observed-interval',
213
214
  lockWait: 'unavailable-in-rpc',
@@ -212,7 +212,7 @@ async function runPhaseCore(
212
212
  { run, journeyId, command, stage, refresh = false, limit = 3, waitMs = 0, sleep, ensureHost },
213
213
  call,
214
214
  ) {
215
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 16) throw new Error('INVALID_ARGUMENTS')
215
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 24) throw new Error('INVALID_ARGUMENTS')
216
216
  // Append-batch consolidation uses immutable local drafts. No current journey view is
217
217
  // needed until publication; retain write recovery and live value settlement on that path.
218
218
  if (stage === 'cards' && !refresh && ['consolidate', 'ingest'].includes(command)) {
@@ -9833,6 +9833,8 @@ const decisionCardSchema = decisionCardInputSchema.extend({ answer: object({
9833
9833
  rule: string().max(4001),
9834
9834
  additionalRule: string().max(2e3),
9835
9835
  remember: boolean(),
9836
+ /** Where the answer came from; only the person's panel Hide is remembered (issue #126). */
9837
+ source: _enum(["conversation", "panel"]).optional(),
9836
9838
  excludedTermKeys: array(string().min(1)).max(2e4).optional()
9837
9839
  }).strict().nullable() }).strict();
9838
9840
  const journeyPrivacySchema = object({
@@ -10340,6 +10342,11 @@ object({
10340
10342
  /** ADR 0086: per-value Hide or Keep over the term types' answers; absent means none. */
10341
10343
  valueOverrides: array(journeyValueOverrideSchema).max(1e4).optional(),
10342
10344
  /**
10345
+ * Values the person hid in earlier journeys on this machine, frozen at formation (issue #126).
10346
+ * Private: never in a view, reader guidance or anything sent.
10347
+ */
10348
+ rememberedHides: array(string().min(1).max(1024)).max(1e5).optional(),
10349
+ /**
10343
10350
  * ADR 0086: the snapshot is read by dictionary review. A snapshot without it was read by B
10344
10351
  * review, whose evidence this release no longer renders, so it is obsolete.
10345
10352
  */
@@ -10644,43 +10651,82 @@ object({
10644
10651
  reviewBatchSchema.omit({ digest: true });
10645
10652
  //#endregion
10646
10653
  //#region packages/application/src/journey/terms/locate.ts
10647
- /** NFC core text of each analysed field, contiguous core chunks joined, with its Work. */
10648
- function coreTexts(batches) {
10649
- const texts = [];
10654
+ /**
10655
+ * Issue #140: per batch and field, contiguous core and context chunks in offset order, never
10656
+ * joined across fields, field revisions or gaps. A core boundary maps to the NFC length of the
10657
+ * text before it.
10658
+ */
10659
+ function* termSearchRuns(batches) {
10650
10660
  for (const batch of batches) {
10651
- const fields = Map.groupBy(batch.chunks.filter((chunk) => chunk.core), (chunk) => JSON.stringify([chunk.fieldId, chunk.fieldHash]));
10652
- for (const chunks of fields.values()) {
10653
- let end = -1, text = "";
10654
- for (const chunk of chunks.toSorted((a, b) => a.offset - b.offset)) {
10655
- if (chunk.offset !== end && text) {
10656
- texts.push({
10657
- workId: batch.workId,
10658
- text: text.normalize("NFC")
10659
- });
10660
- text = "";
10661
+ const fields = Map.groupBy(batch.chunks, (chunk) => JSON.stringify([chunk.fieldId, chunk.fieldHash]));
10662
+ for (const group of fields.values()) {
10663
+ let run = [];
10664
+ const runs = [run];
10665
+ for (const chunk of group.toSorted((a, b) => a.offset - b.offset)) {
10666
+ const last = run.at(-1);
10667
+ if (last && last.offset + last.text.length !== chunk.offset) runs.push(run = []);
10668
+ run.push(chunk);
10669
+ }
10670
+ for (const chunks of runs) {
10671
+ if (!chunks.some((chunk) => chunk.core)) continue;
10672
+ const raw = chunks.map((chunk) => chunk.text).join("");
10673
+ const text = raw.normalize("NFC");
10674
+ const at = (end) => Math.min(text.length, raw.slice(0, end).normalize("NFC").length);
10675
+ const cores = [];
10676
+ let offset = 0;
10677
+ for (const chunk of chunks) {
10678
+ const start = offset;
10679
+ offset += chunk.text.length;
10680
+ if (!chunk.core) continue;
10681
+ const last = cores.at(-1);
10682
+ if (last && last[1] === start) last[1] = offset;
10683
+ else cores.push([start, offset]);
10661
10684
  }
10662
- text += chunk.text;
10663
- end = chunk.offset + chunk.text.length;
10685
+ yield {
10686
+ batch,
10687
+ chunks,
10688
+ text,
10689
+ cores: cores.map(([start, end]) => [at(start), at(end)])
10690
+ };
10664
10691
  }
10665
- if (text) texts.push({
10666
- workId: batch.workId,
10667
- text: text.normalize("NFC")
10668
- });
10669
10692
  }
10670
10693
  }
10671
- return texts;
10672
10694
  }
10673
- function occurrences(text, term) {
10674
- let count = 0;
10675
- for (let at = text.indexOf(term); at >= 0; at = text.indexOf(term, at + term.length)) count++;
10676
- return count;
10695
+ /**
10696
+ * Occurrences of `value` in a run: `owned` counts non-overlapping occurrences that start in a
10697
+ * core range; `first` is the first occurrence overlapping a core range, or -1. An occurrence that
10698
+ * starts outside the core is skipped by one position only, so it never hides an owned one.
10699
+ */
10700
+ function runOccurrences(run, value) {
10701
+ let owned = 0, first = -1;
10702
+ if (!value) return {
10703
+ owned,
10704
+ first
10705
+ };
10706
+ for (let at = run.text.indexOf(value); at >= 0;) {
10707
+ const end = at + value.length;
10708
+ if (run.cores.some(([start, stop]) => at >= start && at < stop)) {
10709
+ owned++;
10710
+ if (first < 0) first = at;
10711
+ at = run.text.indexOf(value, end);
10712
+ continue;
10713
+ }
10714
+ if (first < 0 && run.cores.some(([start, stop]) => at < stop && end > start)) first = at;
10715
+ at = run.text.indexOf(value, at + 1);
10716
+ }
10717
+ return {
10718
+ owned,
10719
+ first
10720
+ };
10677
10721
  }
10678
10722
  /**
10679
- * ADR 0086: a value is kept only if it occurs verbatim (NFC, case-sensitive) in a core chunk of
10680
- * the pack's units; its complete admitted NFC value becomes one dictionary entry (ADR 0088).
10723
+ * ADR 0086, refined by issue #140: a value is kept only if it occurs verbatim (NFC,
10724
+ * case-sensitive) in a pack field's contiguous core and context text, overlapping a core chunk;
10725
+ * only occurrences that start in a core chunk are counted. Its complete admitted NFC value
10726
+ * becomes one dictionary entry (ADR 0088).
10681
10727
  */
10682
10728
  function locateTerms(batches, terms) {
10683
- const texts = coreTexts(batches);
10729
+ const runs = [...termSearchRuns(batches)];
10684
10730
  const located = /* @__PURE__ */ new Map();
10685
10731
  const unlocatedIndices = [];
10686
10732
  let kept = 0, unlocated = 0;
@@ -10693,20 +10739,25 @@ function locateTerms(batches, terms) {
10693
10739
  }];
10694
10740
  }));
10695
10741
  for (const { type, value, index } of unique.values()) {
10696
- if (value.includes("█") || admitCustomTerm(value)?.value !== value || !texts.some((item) => item.text.includes(value))) {
10742
+ let count = 0;
10743
+ let workId;
10744
+ if (!value.includes("█") && admitCustomTerm(value)?.value === value) for (const run of runs) {
10745
+ const { owned, first } = runOccurrences(run, value);
10746
+ if (first < 0) continue;
10747
+ count += owned;
10748
+ workId ??= run.batch.workId;
10749
+ }
10750
+ if (workId === void 0) {
10697
10751
  unlocated++;
10698
10752
  unlocatedIndices.push(index);
10699
10753
  continue;
10700
10754
  }
10701
10755
  kept++;
10702
- const holders = texts.filter((item) => item.text.includes(value));
10703
- const first = holders[0];
10704
- if (!first) continue;
10705
10756
  located.set(JSON.stringify([type, value]), {
10706
10757
  type,
10707
10758
  value,
10708
- count: holders.reduce((sum, item) => sum + occurrences(item.text, value), 0),
10709
- workId: first.workId
10759
+ count,
10760
+ workId
10710
10761
  });
10711
10762
  }
10712
10763
  return {
@@ -85,4 +85,5 @@ export function writePackTask(
85
85
  }[]
86
86
  }[],
87
87
  attemptId?: string,
88
+ reader?: { readerId?: string; readerTurn?: string; repair?: boolean },
88
89
  ): Promise<string>