@davesheffer/hunch 1.31.0 → 1.32.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/README.md +3 -6
- package/dist/cli/automaticReviewMemory.d.ts +13 -0
- package/dist/cli/dna.d.ts +2 -0
- package/dist/cli/index.d.ts +16 -0
- package/dist/cli/index.js +602 -43
- package/dist/cli/integrations.d.ts +2 -0
- package/dist/cli/integrations.js +5 -2
- package/dist/cli/invocation.d.ts +35 -0
- package/dist/cli/preflight.d.ts +1 -0
- package/dist/cli/reviewMemory.d.ts +4 -0
- package/dist/cli/reviewMemoryProvider.d.ts +14 -0
- package/dist/cli/serve.d.ts +2 -0
- package/dist/cli/taskReport.d.ts +6 -0
- package/dist/cli/taskReport.js +124 -0
- package/dist/cli/update.d.ts +14 -0
- package/dist/client/state.d.ts +248 -0
- package/dist/client/state.js +2 -0
- package/dist/constitution/adapters.d.ts +29 -0
- package/dist/constitution/behaviorAttestationBinding.d.ts +3 -0
- package/dist/constitution/behaviorEvaluator.d.ts +8 -0
- package/dist/constitution/behaviorProof.d.ts +9 -0
- package/dist/constitution/behaviorWorkspace.d.ts +25 -0
- package/dist/constitution/bootstrap.d.ts +30 -0
- package/dist/constitution/canonical.d.ts +15 -0
- package/dist/constitution/card.d.ts +58 -0
- package/dist/constitution/compiler.d.ts +52 -0
- package/dist/constitution/composition.d.ts +10 -0
- package/dist/constitution/corpus.d.ts +7 -0
- package/dist/constitution/correctionPolicyMaterializer.d.ts +57 -0
- package/dist/constitution/delta.d.ts +4 -0
- package/dist/constitution/disposition.d.ts +20 -0
- package/dist/constitution/evaluator.d.ts +47 -0
- package/dist/constitution/experiment.d.ts +466 -0
- package/dist/constitution/experimentRunner.d.ts +7 -0
- package/dist/constitution/g2.d.ts +209 -0
- package/dist/constitution/g2BehaviorAttestation.d.ts +45 -0
- package/dist/constitution/g2BehaviorCandidates.d.ts +123 -0
- package/dist/constitution/g2BehaviorDependencies.d.ts +83 -0
- package/dist/constitution/g2BehaviorMaterialization.d.ts +77 -0
- package/dist/constitution/g2BehaviorPolicyMaterializer.d.ts +48 -0
- package/dist/constitution/g2CandidateAttestation.d.ts +43 -0
- package/dist/constitution/g2Candidates.d.ts +80 -0
- package/dist/constitution/g2Drills.d.ts +33 -0
- package/dist/constitution/g3.d.ts +311 -0
- package/dist/constitution/g3Conformance.d.ts +33 -0
- package/dist/constitution/lifecycle.d.ts +25 -0
- package/dist/constitution/mutation.d.ts +15 -0
- package/dist/constitution/nodeTestEvidence.d.ts +13 -0
- package/dist/constitution/plan.d.ts +18 -0
- package/dist/constitution/policyRuntime.d.ts +5 -0
- package/dist/constitution/proof.d.ts +11 -0
- package/dist/constitution/repairPolicies.d.ts +34 -0
- package/dist/constitution/replacementFreeGit.d.ts +14 -0
- package/dist/constitution/replay.d.ts +40 -0
- package/dist/constitution/replayCache.d.ts +14 -0
- package/dist/constitution/replayWorker.d.ts +1 -0
- package/dist/constitution/repository.d.ts +129 -0
- package/dist/constitution/safeCheckout.d.ts +9 -0
- package/dist/constitution/schema.d.ts +1763 -0
- package/dist/constitution/scorecard.d.ts +219 -0
- package/dist/constitution/service.d.ts +398 -0
- package/dist/constitution/shadow.d.ts +54 -0
- package/dist/constitution/sourceMutation.d.ts +15 -0
- package/dist/constitution/staticGraphBaseline.d.ts +13 -0
- package/dist/constitution/structural.d.ts +64 -0
- package/dist/core/agenthook.d.ts +63 -0
- package/dist/core/agenthook.js +1 -0
- package/dist/core/automaticReviewMemory.d.ts +60 -0
- package/dist/core/autoreview.d.ts +66 -0
- package/dist/core/canonicalOrder.d.ts +3 -0
- package/dist/core/capturetoken.d.ts +19 -0
- package/dist/core/changeIdentity.d.ts +22 -0
- package/dist/core/changeProof.d.ts +14 -0
- package/dist/core/checkreport.d.ts +140 -0
- package/dist/core/commitrepair.d.ts +171 -0
- package/dist/core/commitrepair.js +238 -0
- package/dist/core/compare.d.ts +21 -0
- package/dist/core/config.d.ts +20 -0
- package/dist/core/conformance.d.ts +35 -0
- package/dist/core/constraintmatch.d.ts +54 -0
- package/dist/core/correction.d.ts +42 -0
- package/dist/core/correctionStage.d.ts +224 -0
- package/dist/core/declarationClusters.d.ts +149 -0
- package/dist/core/delivery.d.ts +110 -0
- package/dist/core/docanchors.d.ts +32 -0
- package/dist/core/docscan.d.ts +26 -0
- package/dist/core/drift.d.ts +13 -0
- package/dist/core/drift.js +31 -1
- package/dist/core/dupdetect.d.ts +43 -0
- package/dist/core/escalations.d.ts +129 -0
- package/dist/core/escalations.js +165 -0
- package/dist/core/events.d.ts +29 -0
- package/dist/core/evidenceMap.d.ts +164 -0
- package/dist/core/externalImports.d.ts +8 -0
- package/dist/core/format.d.ts +13 -0
- package/dist/core/glob.d.ts +9 -0
- package/dist/core/groundingLag.d.ts +84 -0
- package/dist/core/groundingLag.js +16 -2
- package/dist/core/groundingMerge.d.ts +15 -0
- package/dist/core/groundingMerge.js +95 -0
- package/dist/core/hookcache.d.ts +10 -0
- package/dist/core/hookpolicy.d.ts +47 -0
- package/dist/core/ids.d.ts +36 -0
- package/dist/core/importReview.d.ts +30 -0
- package/dist/core/io.d.ts +23 -0
- package/dist/core/jsonc.d.ts +4 -0
- package/dist/core/landscapeAdoption.d.ts +66 -0
- package/dist/core/landscapeDelivery.d.ts +76 -0
- package/dist/core/memorylog.d.ts +37 -0
- package/dist/core/migrate.d.ts +28 -0
- package/dist/core/outcomeExperience.d.ts +89 -0
- package/dist/core/overlaySafety.d.ts +24 -0
- package/dist/core/overlaySafety.js +7 -1
- package/dist/core/paths.d.ts +31 -0
- package/dist/core/pipeline.d.ts +289 -0
- package/dist/core/premises.d.ts +43 -0
- package/dist/core/provenance.d.ts +21 -0
- package/dist/core/publication.d.ts +54 -0
- package/dist/core/refrepair.d.ts +24 -0
- package/dist/core/refrepair.js +1 -1
- package/dist/core/relativeImports.d.ts +11 -0
- package/dist/core/repair.d.ts +44 -0
- package/dist/core/repairqueue.d.ts +40 -0
- package/dist/core/repairqueue.js +133 -0
- package/dist/core/reviewMemory.d.ts +40 -0
- package/dist/core/reviewqueue.d.ts +54 -0
- package/dist/core/safeRepoFile.d.ts +20 -0
- package/dist/core/served.d.ts +57 -0
- package/dist/core/served.js +14 -0
- package/dist/core/stateContract.d.ts +979 -0
- package/dist/core/stateContract.js +87 -4
- package/dist/core/stateDelivery.d.ts +78 -0
- package/dist/core/stateRecords.d.ts +383 -0
- package/dist/core/stateRecords.js +16 -1
- package/dist/core/stats.d.ts +88 -0
- package/dist/core/strictgate.d.ts +33 -0
- package/dist/core/taskReport.d.ts +226 -0
- package/dist/core/taskReport.js +514 -0
- package/dist/core/taskReportCapture.d.ts +8 -0
- package/dist/core/taskReportCapture.js +36 -0
- package/dist/core/taskReportEvidence.d.ts +26 -0
- package/dist/core/taskReportEvidence.js +268 -0
- package/dist/core/taskReportHook.d.ts +20 -0
- package/dist/core/taskReportHook.js +76 -0
- package/dist/core/taskReportPaths.d.ts +3 -0
- package/dist/core/taskReportPaths.js +23 -0
- package/dist/core/taskReportPublic.d.ts +31 -0
- package/dist/core/taskReportPublic.js +45 -0
- package/dist/core/taskReportRender.d.ts +6 -0
- package/dist/core/taskReportRender.js +97 -0
- package/dist/core/topics.d.ts +73 -0
- package/dist/core/types.d.ts +1239 -0
- package/dist/core/version.d.ts +10 -0
- package/dist/eval/guards.d.ts +45 -0
- package/dist/eval/harness.d.ts +58 -0
- package/dist/extractors/adrImport.d.ts +59 -0
- package/dist/extractors/comments.d.ts +7 -0
- package/dist/extractors/correctionSources.d.ts +11 -0
- package/dist/extractors/diff.d.ts +40 -0
- package/dist/extractors/git.d.ts +304 -0
- package/dist/extractors/git.js +208 -20
- package/dist/extractors/helm.d.ts +31 -0
- package/dist/extractors/indexer.d.ts +55 -0
- package/dist/extractors/landscapeDiscovery.d.ts +41 -0
- package/dist/extractors/languages.d.ts +77 -0
- package/dist/extractors/nativeTreeSitter.d.ts +16 -0
- package/dist/extractors/parse.d.ts +54 -0
- package/dist/extractors/php.d.ts +26 -0
- package/dist/extractors/repoSource.d.ts +53 -0
- package/dist/extractors/testreport.d.ts +36 -0
- package/dist/integrations/ciAction.d.ts +8 -0
- package/dist/integrations/claudeConfig.d.ts +44 -0
- package/dist/integrations/claudemd.d.ts +13 -0
- package/dist/integrations/claudemd.js +11 -2
- package/dist/integrations/gitignore.d.ts +16 -0
- package/dist/integrations/gitignore.js +7 -0
- package/dist/integrations/health.d.ts +74 -0
- package/dist/integrations/hooks.d.ts +41 -0
- package/dist/integrations/hooks.js +92 -66
- package/dist/integrations/madrExport.d.ts +39 -0
- package/dist/integrations/madrManifest.d.ts +69 -0
- package/dist/integrations/mergeDriver.d.ts +3 -0
- package/dist/integrations/mergeDriver.js +25 -8
- package/dist/integrations/probe.d.ts +4 -0
- package/dist/integrations/providers.d.ts +103 -0
- package/dist/integrations/scaffold.d.ts +29 -0
- package/dist/integrations/sync.d.ts +30 -0
- package/dist/integrations/sync.js +3 -2
- package/dist/integrations/team.d.ts +70 -0
- package/dist/integrations/worktree.d.ts +8 -0
- package/dist/mcp/roots.d.ts +16 -0
- package/dist/mcp/server.d.ts +36 -0
- package/dist/mcp/server.js +114 -25
- package/dist/mcp/taskReportTools.d.ts +3 -0
- package/dist/mcp/taskReportTools.js +106 -0
- package/dist/serve/app.d.ts +36 -0
- package/dist/serve/app.js +20 -0
- package/dist/serve/config.d.ts +100 -0
- package/dist/serve/writelock.d.ts +22 -0
- package/dist/store/changeLedger.d.ts +132 -0
- package/dist/store/changeLedger.js +40 -9
- package/dist/store/compact.d.ts +33 -0
- package/dist/store/db.d.ts +9 -0
- package/dist/store/embedder.d.ts +32 -0
- package/dist/store/hunchStore.d.ts +624 -0
- package/dist/store/hunchStore.js +13 -5
- package/dist/store/jsonStore.d.ts +125 -0
- package/dist/store/jsonStore.js +15 -0
- package/dist/store/merge.d.ts +39 -0
- package/dist/store/privateMigrate.d.ts +24 -0
- package/dist/store/replay.d.ts +56 -0
- package/dist/store/schema.d.ts +17 -0
- package/dist/store/stateBinding.d.ts +145 -0
- package/dist/store/stateBinding.js +110 -15
- package/dist/store/stateCapture.d.ts +15 -0
- package/dist/store/stateCapture.js +145 -0
- package/dist/synthesis/cliAdapter.d.ts +22 -0
- package/dist/synthesis/initiator.d.ts +13 -0
- package/dist/synthesis/provider.d.ts +307 -0
- package/dist/synthesis/synthesize.d.ts +117 -0
- package/dist/synthesis/tripwires.d.ts +9 -0
- package/dist/taskReports.d.ts +76 -0
- package/dist/taskReports.js +67 -0
- package/dist/wiki/adopt.d.ts +22 -0
- package/dist/wiki/graph.d.ts +87 -0
- package/dist/wiki/wiki.d.ts +242 -0
- package/package.json +6 -5
- package/server.json +2 -2
|
@@ -26,7 +26,7 @@ import { decisionId } from "../core/ids.js";
|
|
|
26
26
|
import { ENTITY_KINDS, SCHEMAS } from "../core/types.js";
|
|
27
27
|
import { captureConflicts, isLive } from "../core/topics.js";
|
|
28
28
|
import { buildDeliveryEnvelope } from "../core/delivery.js";
|
|
29
|
-
import { STATE_CAPABILITIES, STATE_CONTRACT_VERSION, STATE_FACETS, STATE_READ_VERSION, STATE_SUBSCRIBE_VERSION, STATE_WRITE_VERSION, ReadRequestSchema, ReadResponseSchema, WriteRequestSchema, WriteResultSchema, SubscribeRequestSchema, ChangeEventSchema, RecordsRequestSchema, RecordsResponseSchema, STATE_RECORDS_VERSION, ScopeSchema, scopePath, stateHash, actionReceiptId, commitmentId, derivedId, externalKey, subjectOfRef, assertReadWithinGrants, assertWriteWellFormed, assertDerivedState, isHumanConfirmed, } from "../core/stateContract.js";
|
|
29
|
+
import { STATE_CAPABILITIES, STATE_CONTRACT_VERSION, STATE_FACETS, STATE_READ_VERSION, STATE_SUBSCRIBE_VERSION, STATE_WRITE_VERSION, ReadRequestSchema, ReadResponseSchema, WriteRequestSchema, WriteResultSchema, SubscribeRequestSchema, ChangeEventSchema, RecordsRequestSchema, RecordsResponseSchema, STATE_RECORDS_VERSION, ScopeSchema, scopePath, stateHash, actionReceiptId, commitmentId, derivedId, relationshipId, externalKey, subjectOfRef, assertReadWithinGrants, assertWriteWellFormed, assertDerivedState, isHumanConfirmed, } from "../core/stateContract.js";
|
|
30
30
|
/** A typed refusal. `code` is stable for bindings; `conflict` names the incumbent when one exists. */
|
|
31
31
|
export class StateRefusal extends Error {
|
|
32
32
|
code;
|
|
@@ -169,6 +169,9 @@ export function readState(store, input) {
|
|
|
169
169
|
const request = ReadRequestSchema.parse(input);
|
|
170
170
|
if (!granted(request.principal, request.scope))
|
|
171
171
|
throw new StateRefusal("outside-grants", `scope ${scopePath(request.scope)} is outside the principal's grants`);
|
|
172
|
+
if (request.observed_page && (request.subject === undefined || request.scopes !== undefined || (request.facets && !request.facets.includes('derived')))) {
|
|
173
|
+
throw new StateRefusal('malformed', 'observation pages require a subject, the derived facet and a single partition without scopes');
|
|
174
|
+
}
|
|
172
175
|
const repo = partitionOf(store);
|
|
173
176
|
const facets = new Set(request.facets ?? STATE_FACETS);
|
|
174
177
|
const target = request.task ?? request.subject ?? scopePath(request.scope);
|
|
@@ -196,9 +199,23 @@ export function readState(store, input) {
|
|
|
196
199
|
// read for the entity id finds what was filed under its keys — one explicit hop, grants first.
|
|
197
200
|
const aliases = subjectAliases(store, request.principal, repo, subject);
|
|
198
201
|
const isSubject = (s) => s !== undefined && aliases.has(s);
|
|
202
|
+
// One hop only. Do not broaden aliases: a linked observation does not merge subjects,
|
|
203
|
+
// bring unrelated facts, receipts or commitments, or traverse another relationship.
|
|
204
|
+
const linkedObservations = new Map();
|
|
205
|
+
for (const r of store.recs("relationships")) {
|
|
206
|
+
if (!granted(request.principal, r.scope) || scopePath(r.scope) !== scopePath(request.scope))
|
|
207
|
+
continue;
|
|
208
|
+
if (r.type !== "observation_about" || r.lifecycle === "retired" || !isSubject(r.to) || !r.observation_hash)
|
|
209
|
+
continue;
|
|
210
|
+
const hashes = linkedObservations.get(r.from) ?? new Set();
|
|
211
|
+
hashes.add(r.observation_hash);
|
|
212
|
+
linkedObservations.set(r.from, hashes);
|
|
213
|
+
}
|
|
199
214
|
const current = [];
|
|
200
215
|
const inForce = [];
|
|
201
216
|
const done = [];
|
|
217
|
+
const observations = [];
|
|
218
|
+
let relationshipsTruncated = false;
|
|
202
219
|
const dependsOn = [];
|
|
203
220
|
const invalidatedBy = new Set();
|
|
204
221
|
/** authorization-before-retrieval: the grant check runs before the record is examined. */
|
|
@@ -265,15 +282,26 @@ export function readState(store, input) {
|
|
|
265
282
|
}
|
|
266
283
|
if (facets.has("derived"))
|
|
267
284
|
for (const d of store.recs("derived")) {
|
|
268
|
-
if (!isSubject(d.subject) && d.id !== subject)
|
|
269
|
-
continue;
|
|
270
285
|
const scope = admit("derived", d);
|
|
271
286
|
if (!scope)
|
|
272
287
|
continue;
|
|
288
|
+
if (request.observed_page && scopePath(scope) !== scopePath(request.scope))
|
|
289
|
+
continue;
|
|
290
|
+
const direct = isSubject(d.subject) || d.id === subject;
|
|
291
|
+
const linked = scopePath(scope) === scopePath(request.scope) && linkedObservations.get(d.id)?.has(stateHash(d));
|
|
292
|
+
if (!direct && !linked)
|
|
293
|
+
continue;
|
|
294
|
+
if (!direct) {
|
|
295
|
+
if (d.state === "unknown" && d.valid_to == null)
|
|
296
|
+
observations.push(d);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
273
299
|
if (d.state === "current" && d.valid_to == null) {
|
|
274
300
|
current.push(keep("derived", d, scope));
|
|
275
301
|
dependsOn.push(...d.dependencies);
|
|
276
302
|
}
|
|
303
|
+
else if (d.state === "unknown" && d.valid_to == null)
|
|
304
|
+
observations.push(d);
|
|
277
305
|
}
|
|
278
306
|
if (facets.has("entities"))
|
|
279
307
|
for (const e of store.recs("entities")) {
|
|
@@ -287,14 +315,39 @@ export function readState(store, input) {
|
|
|
287
315
|
}
|
|
288
316
|
if (facets.has("relationships"))
|
|
289
317
|
for (const r of store.recs("relationships")) {
|
|
318
|
+
if (r.lifecycle === "retired")
|
|
319
|
+
continue;
|
|
290
320
|
if (!isSubject(r.from) && !isSubject(r.to))
|
|
291
321
|
continue;
|
|
292
322
|
const scope = admit("relationships", r);
|
|
293
323
|
if (!scope)
|
|
294
324
|
continue;
|
|
325
|
+
if (current.length >= 256) {
|
|
326
|
+
relationshipsTruncated = true;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
295
329
|
current.push(keep("relationships", r, scope));
|
|
296
330
|
}
|
|
297
|
-
|
|
331
|
+
observations.sort((a, b) => Date.parse(b.computed_at) - Date.parse(a.computed_at) || a.id.localeCompare(b.id));
|
|
332
|
+
let offset = 0;
|
|
333
|
+
let page;
|
|
334
|
+
if (request.observed_page) {
|
|
335
|
+
// Fingerprint the authorized membership AND record contents. A change between
|
|
336
|
+
// pages is a conflict, never a silently skipped or duplicated observation.
|
|
337
|
+
const snapshot_hash = stateHash({ scope: request.scope, subject, observations });
|
|
338
|
+
const cursor = request.observed_page.cursor;
|
|
339
|
+
if (cursor && cursor.snapshot_hash !== snapshot_hash)
|
|
340
|
+
throw new StateRefusal('conflict', 'observations changed between pages; restart from the first page');
|
|
341
|
+
offset = cursor?.offset ?? 0;
|
|
342
|
+
if (offset > observations.length)
|
|
343
|
+
throw new StateRefusal('malformed', 'observation cursor is outside this snapshot');
|
|
344
|
+
page = { snapshot_hash, total: observations.length, next_cursor: offset + 64 < observations.length ? { snapshot_hash, offset: offset + 64 } : null };
|
|
345
|
+
}
|
|
346
|
+
const observed = observations.slice(offset, offset + 64).map(d => keep("derived", d, recordScope(d, repo)));
|
|
347
|
+
stateOfRecord = { subject, current, in_force: inForce, done, depends_on: dependsOn, invalidated_by: [...invalidatedBy].sort(),
|
|
348
|
+
...(relationshipsTruncated ? { relationships_truncated: true } : {}),
|
|
349
|
+
...(observed.length || page ? { observed, observed_truncated: observations.length > offset + observed.length } : {}),
|
|
350
|
+
...(page ? { observed_page: page } : {}) };
|
|
298
351
|
}
|
|
299
352
|
const response = ReadResponseSchema.parse({
|
|
300
353
|
schema: STATE_READ_VERSION,
|
|
@@ -364,6 +417,9 @@ export function mergeReadResponses(primary, others, extraDenied = []) {
|
|
|
364
417
|
current: dedupeRefs((s) => s.current),
|
|
365
418
|
in_force: dedupeRefs((s) => s.in_force),
|
|
366
419
|
done: dedupeRefs((s) => s.done),
|
|
420
|
+
...(sors.some(s => s.relationships_truncated) ? { relationships_truncated: true } : {}),
|
|
421
|
+
...(sors.some(s => s.observed?.length) ? { observed: dedupeRefs(s => s.observed ?? []).slice(0, 64),
|
|
422
|
+
observed_truncated: sors.some(s => s.observed_truncated) || dedupeRefs(s => s.observed ?? []).length > 64 } : {}),
|
|
367
423
|
depends_on: dependsOn,
|
|
368
424
|
invalidated_by: [...new Set(sors.flatMap((s) => s.invalidated_by))].sort(),
|
|
369
425
|
};
|
|
@@ -390,7 +446,7 @@ function subjectOf(facet, record) {
|
|
|
390
446
|
case "commitments":
|
|
391
447
|
case "derived": return typeof r.subject === "string" ? r.subject : undefined;
|
|
392
448
|
case "entities": return typeof r.id === "string" ? r.id : undefined;
|
|
393
|
-
case "relationships": return typeof r.from === "string" ? r.from : undefined;
|
|
449
|
+
case "relationships": return r.type === "observation_about" && typeof r.to === "string" ? r.to : typeof r.from === "string" ? r.from : undefined;
|
|
394
450
|
case "receipts": {
|
|
395
451
|
const t = r.target;
|
|
396
452
|
return t?.object_type && t.object_key ? `${t.object_type}:${t.object_key}` : undefined;
|
|
@@ -565,6 +621,8 @@ function normalizeRecord(facet, scope, raw, principal) {
|
|
|
565
621
|
expectedId = commitmentId(record);
|
|
566
622
|
else if (facet === "derived")
|
|
567
623
|
expectedId = derivedId(record);
|
|
624
|
+
else if (facet === "relationships")
|
|
625
|
+
expectedId = relationshipId(String(record.from), String(record.to), String(record.type));
|
|
568
626
|
else if (facet === "decisions" && typeof record.id !== "string")
|
|
569
627
|
expectedId = decisionId(String(record.topic ?? record.title ?? ""));
|
|
570
628
|
}
|
|
@@ -602,20 +660,46 @@ export function writeState(store, input, opts = {}) {
|
|
|
602
660
|
const { home, hunchDir, isPrivate } = stateHomeFor(store, request.scope);
|
|
603
661
|
const now = (opts.now ?? (() => new Date()))().toISOString();
|
|
604
662
|
const facet = request.facet;
|
|
663
|
+
const getHere = (id) => facet === "derived" || facet === "receipts" || facet === "commitments"
|
|
664
|
+
? store.getStateDirect(facet, id, home)
|
|
665
|
+
: home === "private" ? store.getPrivateRec(facet, id) : store.json.get(facet, id);
|
|
605
666
|
if (!ENTITY_KINDS.includes(facet))
|
|
606
667
|
throw new StateRefusal("unsupported", `facet ${facet} is not a store kind`);
|
|
607
668
|
const record = normalizeRecord(facet, request.scope, request.record, request.principal);
|
|
669
|
+
let replayLink;
|
|
670
|
+
if (facet === "relationships" && record.type === "observation_about") {
|
|
671
|
+
const link = record;
|
|
672
|
+
const observation = store.getStateDirect("derived", link.from, home);
|
|
673
|
+
if (!observation || scopePath(observation.scope) !== scopePath(request.scope) || !granted(request.principal, observation.scope))
|
|
674
|
+
throw new StateRefusal("conflict", "observation is absent from the granted partition");
|
|
675
|
+
if (!observation.transform_version.startsWith("agent-capture/1:"))
|
|
676
|
+
throw new StateRefusal("malformed", "only captured observations can be linked");
|
|
677
|
+
if (link.lifecycle !== "retired" && (observation.state !== "unknown" || observation.valid_to != null || stateHash(observation) !== link.observation_hash))
|
|
678
|
+
throw new StateRefusal("conflict", "observation changed or is no longer eligible; re-read before linking");
|
|
679
|
+
const prior = getHere(link.id);
|
|
680
|
+
if (prior?.lifecycle === "retired" && link.lifecycle !== "retired" && request.expected_version === null)
|
|
681
|
+
throw new StateRefusal("conflict", "retired observation link requires an explicit expected_version to reactivate");
|
|
682
|
+
// Repeated reads and different agents do not rewrite an identical association.
|
|
683
|
+
// Preserve its first author/evidence time and avoid index/Git work on replay.
|
|
684
|
+
if (prior && prior.from === link.from && prior.to === link.to && prior.type === link.type && prior.reason === link.reason
|
|
685
|
+
&& prior.observation_hash === link.observation_hash && prior.lifecycle === link.lifecycle
|
|
686
|
+
&& prior.evidence && link.evidence && externalKey(prior.evidence) === externalKey(link.evidence) && prior.evidence.content_hash === link.evidence.content_hash) {
|
|
687
|
+
replayLink = prior;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
608
690
|
const id = record.id;
|
|
609
691
|
assertExternalIdentity(store, request.principal, request.scope, facet, record);
|
|
610
692
|
/** The normalized PAYLOAD hash: what idempotency recognizes on a re-send. */
|
|
611
693
|
const hash = stateHash(record);
|
|
612
|
-
const ledger = readLedger(hunchDir, request.scope);
|
|
694
|
+
const ledger = opts.ledgerCache?.ledger ?? readLedger(hunchDir, request.scope);
|
|
695
|
+
if (opts.ledgerCache)
|
|
696
|
+
opts.ledgerCache.ledger = ledger;
|
|
613
697
|
const durability = () => opts.flush?.(isPrivate, `nuryel: write ${id}`) ?? "local";
|
|
614
698
|
/** The result reports the record ON FILE and its hash — the store may enrich a record on put
|
|
615
699
|
* (a private-mode decision gains `valid_from`), and a writer that goes on to rest a receipt
|
|
616
700
|
* on this record must hold the hash a reader will verify, never a pre-store one. */
|
|
617
701
|
const result = (outcome, conflict = null, rid = id) => {
|
|
618
|
-
const onFile =
|
|
702
|
+
const onFile = getHere(rid) ?? record;
|
|
619
703
|
return WriteResultSchema.parse({ schema: STATE_WRITE_VERSION, record_id: rid, record_hash: stateHash(onFile), durability: durability(), outcome, conflict, record: onFile });
|
|
620
704
|
};
|
|
621
705
|
// Idempotency: the same key replays the original; the same key with a different payload
|
|
@@ -631,9 +715,14 @@ export function writeState(store, input, opts = {}) {
|
|
|
631
715
|
const where = differing.length ? ` — this payload differs in: ${differing.join(", ")}` : (seen.record_id !== id ? ` — this payload derives a different identity (${id})` : "");
|
|
632
716
|
throw new StateRefusal("idempotency", `idempotency key "${request.idempotency_key}" was already used for ${seen.record_id}${where}. A key names ONE request payload: re-send the original payload to replay it, or use a new key to write this payload (the record keeps its derived id and is updated in place).`, { incumbent_id: seen.record_id, reason: "idempotency key reused with a different payload" });
|
|
633
717
|
}
|
|
634
|
-
const existing =
|
|
718
|
+
const existing = getHere(id);
|
|
719
|
+
if (facet === 'derived') {
|
|
720
|
+
const review = record.review;
|
|
721
|
+
if (review && stateHash(review) !== stateHash(existing?.review ?? null) && review.by !== request.principal.id)
|
|
722
|
+
throw new StateRefusal('malformed', 'reviewer must be the initiating principal');
|
|
723
|
+
}
|
|
635
724
|
if (existing && stateHash(existing) === hash) {
|
|
636
|
-
appendChanges(hunchDir, request.scope, [], { key: request.idempotency_key, entry: { record_id: id, record_hash: hash, payload_hash: hash, facet } }, now);
|
|
725
|
+
appendChanges(hunchDir, request.scope, [], { key: request.idempotency_key, entry: { record_id: id, record_hash: hash, payload_hash: hash, facet } }, now, opts.ledgerCache?.ledger);
|
|
637
726
|
return result("replayed");
|
|
638
727
|
}
|
|
639
728
|
if (existing && request.expected_version !== null) {
|
|
@@ -643,6 +732,11 @@ export function writeState(store, input, opts = {}) {
|
|
|
643
732
|
if (!ok)
|
|
644
733
|
throw new StateRefusal("conflict", `expected_version does not match the incumbent ${id}`, { incumbent_id: id, reason: "expected_version mismatch" });
|
|
645
734
|
}
|
|
735
|
+
if (replayLink) {
|
|
736
|
+
const recordHash = stateHash(replayLink);
|
|
737
|
+
appendChanges(hunchDir, request.scope, [], { key: request.idempotency_key, entry: { record_id: replayLink.id, record_hash: recordHash, payload_hash: hash, facet } }, now, opts.ledgerCache?.ledger);
|
|
738
|
+
return WriteResultSchema.parse({ schema: STATE_WRITE_VERSION, record_id: replayLink.id, record_hash: recordHash, record: replayLink, outcome: "replayed", conflict: null, durability: "local" });
|
|
739
|
+
}
|
|
646
740
|
// human-correction-outranks-agent-writes: what a human confirmed, an agent does not rewrite.
|
|
647
741
|
// Allowed for an agent: a replay (the same facts, the tier downgrade aside), a derived statement
|
|
648
742
|
// written back stale with the external cause that moved (the writer's currentness duty), a
|
|
@@ -670,7 +764,7 @@ export function writeState(store, input, opts = {}) {
|
|
|
670
764
|
};
|
|
671
765
|
const verdict = guard(existing, "overwrite");
|
|
672
766
|
if (verdict === "replay") {
|
|
673
|
-
appendChanges(hunchDir, request.scope, [], { key: request.idempotency_key, entry: { record_id: id, record_hash: stateHash(existing), payload_hash: hash, facet } }, now);
|
|
767
|
+
appendChanges(hunchDir, request.scope, [], { key: request.idempotency_key, entry: { record_id: id, record_hash: stateHash(existing), payload_hash: hash, facet } }, now, opts.ledgerCache?.ledger);
|
|
674
768
|
return result("replayed");
|
|
675
769
|
}
|
|
676
770
|
if (verdict === "keep-provenance")
|
|
@@ -719,15 +813,15 @@ export function writeState(store, input, opts = {}) {
|
|
|
719
813
|
const closedBy = facet === "commitments" ? assertClosedBy(store, request.principal, record) : null;
|
|
720
814
|
store.putCapture(facet, record, isPrivate);
|
|
721
815
|
/** What is on file now — the hash every event, ref and result carries. */
|
|
722
|
-
const onFileHash = stateHash(
|
|
816
|
+
const onFileHash = stateHash(getHere(id) ?? record);
|
|
723
817
|
const changes = [];
|
|
724
818
|
const cause = closedBy ? { kind: "receipt", receipt_id: closedBy } : request.cause ?? { kind: "write", principal: request.principal.id };
|
|
725
819
|
// A current derived statement written back as stale is an INVALIDATION, not an update: the
|
|
726
820
|
// ledger says so, and names the external pointer that moved when the writer gives one.
|
|
727
|
-
const invalidated = facet === "derived" && !!existing && existing.state === "current" && record.state === "stale";
|
|
821
|
+
const invalidated = facet === "derived" && !!existing && (existing.state === "current" || existing.state === "unknown") && record.state === "stale";
|
|
728
822
|
const invalidates = facet === "receipts" ? record.invalidates : [];
|
|
729
823
|
// An entity leaving service is a `retired` change (a merge names the survivor in the record).
|
|
730
|
-
const retired = facet === "entities" && record.lifecycle === "retired" && (!existing || existing.lifecycle !== "retired");
|
|
824
|
+
const retired = (facet === "entities" || facet === "relationships") && record.lifecycle === "retired" && (!existing || existing.lifecycle !== "retired");
|
|
731
825
|
const subject = subjectOf(facet, record);
|
|
732
826
|
if (supersedes) {
|
|
733
827
|
const closed = closeWindow(store, facet, supersedes, id, now, isPrivate);
|
|
@@ -737,8 +831,9 @@ export function writeState(store, input, opts = {}) {
|
|
|
737
831
|
}
|
|
738
832
|
}
|
|
739
833
|
changes.push({ facet, record_id: id, record_hash: onFileHash, change: invalidated ? "invalidated" : retired ? "retired" : existing ? "updated" : "created", subject, invalidates: invalidated && subject ? [subject] : invalidates, cause });
|
|
740
|
-
appendChanges(hunchDir, request.scope, changes, { key: request.idempotency_key, entry: { record_id: id, record_hash: onFileHash, payload_hash: hash, facet } }, now);
|
|
741
|
-
|
|
834
|
+
appendChanges(hunchDir, request.scope, changes, { key: request.idempotency_key, entry: { record_id: id, record_hash: onFileHash, payload_hash: hash, facet } }, now, opts.ledgerCache?.ledger);
|
|
835
|
+
if (!opts.deferReindex)
|
|
836
|
+
store.reindex();
|
|
742
837
|
return result(supersedes ? "superseded" : existing ? "updated" : "created");
|
|
743
838
|
}
|
|
744
839
|
// ---- subscribe -----------------------------------------------------------------------------
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HunchStore } from "./hunchStore.js";
|
|
2
|
+
import { type CaptureBatchResult, type WriteResult } from "../core/stateContract.js";
|
|
3
|
+
import { type WriteOptions } from "./stateBinding.js";
|
|
4
|
+
interface CaptureOptions extends WriteOptions {
|
|
5
|
+
sourceHashes?: Map<string, string>;
|
|
6
|
+
}
|
|
7
|
+
/** The caller selects relevant atomic claims; this deterministic boundary checks evidence
|
|
8
|
+
* fidelity and deduplication. It does not pretend to prove semantic entailment or relevance.
|
|
9
|
+
* Both bindings hold the partition write lock over lookup AND write. */
|
|
10
|
+
export declare function captureState(store: HunchStore, input: unknown, opts?: CaptureOptions): WriteResult;
|
|
11
|
+
/** Bounded partial-success batch. The caller holds the same partition lock as writeState.
|
|
12
|
+
* Every result has its input index; a refused claim never hides a later valid new detail.
|
|
13
|
+
* A duplicate-only batch performs no record, ledger, index or Git writes. */
|
|
14
|
+
export declare function captureBatchState(store: HunchStore, input: unknown, opts?: WriteOptions): CaptureBatchResult;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { CaptureRequestSchema, CaptureBatchRequestSchema, STATE_CAPTURE_VERSION, STATE_CAPTURE_BATCH_VERSION, STATE_WRITE_VERSION, assertDerivedState, captureTransform, derivedId, normalizeAssertion, canonicalObjectKey, externalKey, scopePath, stateHash } from "../core/stateContract.js";
|
|
2
|
+
import { isCredentialFreeValue } from "../core/provenance.js";
|
|
3
|
+
import { StateRefusal, stateHomeFor, writeState } from "./stateBinding.js";
|
|
4
|
+
/** The caller selects relevant atomic claims; this deterministic boundary checks evidence
|
|
5
|
+
* fidelity and deduplication. It does not pretend to prove semantic entailment or relevance.
|
|
6
|
+
* Both bindings hold the partition write lock over lookup AND write. */
|
|
7
|
+
export function captureState(store, input, opts = {}) {
|
|
8
|
+
const request = CaptureRequestSchema.parse(input);
|
|
9
|
+
if (!request.principal.grants.some(s => scopePath(s) === scopePath(request.scope)))
|
|
10
|
+
throw new StateRefusal("outside-grants", "capture scope is outside the principal's grants");
|
|
11
|
+
const { home } = stateHomeFor(store, request.scope);
|
|
12
|
+
const statement = normalizeAssertion(request.statement);
|
|
13
|
+
if (![statement, request.relevance.reason, ...request.evidence.map(e => e.excerpt)].every(isCredentialFreeValue))
|
|
14
|
+
throw new StateRefusal("malformed", "captured content must not contain credential material");
|
|
15
|
+
const evidence = request.evidence.map(e => {
|
|
16
|
+
if (!e.source_text.includes(e.excerpt))
|
|
17
|
+
throw new StateRefusal("malformed", "every excerpt must occur exactly in its supplied source text");
|
|
18
|
+
const hash = opts.sourceHashes?.get(e.source_text) ?? stateHash(e.source_text);
|
|
19
|
+
opts.sourceHashes?.set(e.source_text, hash);
|
|
20
|
+
if (e.ref.content_hash && e.ref.content_hash !== hash)
|
|
21
|
+
throw new StateRefusal("malformed", "source text does not match its declared content hash");
|
|
22
|
+
return { ref: { ...e.ref, object_key: canonicalObjectKey(e.ref.object_key), content_hash: hash }, excerpt: e.excerpt };
|
|
23
|
+
});
|
|
24
|
+
// Ignore read time, writer and unrelated source text when deduplicating an assertion.
|
|
25
|
+
// A different excerpt, statement, source identity or subject is a distinct observation.
|
|
26
|
+
const transform = captureTransform(request.scope, request.subject, statement, evidence.map(e => ({ source: externalKey(e.ref), excerpt: e.excerpt })));
|
|
27
|
+
const id = derivedId({ scope: request.scope, subject: request.subject, transform_version: transform, dependencies: [] });
|
|
28
|
+
const incumbent = store.getStateDirect("derived", id, home);
|
|
29
|
+
if (incumbent) {
|
|
30
|
+
assertDerivedState(incumbent);
|
|
31
|
+
if (incumbent.transform_version !== transform || scopePath(incumbent.scope) !== scopePath(request.scope) || incumbent.subject !== request.subject)
|
|
32
|
+
throw new StateRefusal("conflict", "capture identity collision; incumbent preserved");
|
|
33
|
+
// Never revive stale or human-corrected evidence, replace its author, or claim a new
|
|
34
|
+
// observation because another agent saw the same excerpt. Return what actually exists.
|
|
35
|
+
return { schema: STATE_WRITE_VERSION, record_id: incumbent.id, record_hash: stateHash(incumbent), durability: "local", outcome: "replayed", conflict: null, record: incumbent };
|
|
36
|
+
}
|
|
37
|
+
const refs = [...new Map(evidence.map(e => [stateHash(e.ref), e.ref])).entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, ref]) => ref);
|
|
38
|
+
const content = JSON.stringify({ schema: "nuryel.observation-content/1", statement, relevance: request.relevance,
|
|
39
|
+
evidence: evidence.map(e => ({ source: externalKey(e.ref), excerpt: e.excerpt })), captured_by: request.principal.id });
|
|
40
|
+
const record = {
|
|
41
|
+
schema: "nuryel.derived/1", scope: request.scope, subject: request.subject, content, content_hash: stateHash(content),
|
|
42
|
+
dependencies: refs.map(ref => ({ kind: "external", ref })), transform_version: transform,
|
|
43
|
+
computed_at: (opts.now ?? (() => new Date()))().toISOString(), valid_to: null, state: "unknown",
|
|
44
|
+
provenance: { source: "agent_recorded", confidence: 0.8, evidence: [`captured by ${request.principal.id}`, ...refs.map(externalKey)] },
|
|
45
|
+
};
|
|
46
|
+
return writeState(store, { schema: STATE_WRITE_VERSION, principal: request.principal, scope: request.scope, facet: "derived", record, idempotency_key: transform }, opts);
|
|
47
|
+
}
|
|
48
|
+
/** Bounded partial-success batch. The caller holds the same partition lock as writeState.
|
|
49
|
+
* Every result has its input index; a refused claim never hides a later valid new detail.
|
|
50
|
+
* A duplicate-only batch performs no record, ledger, index or Git writes. */
|
|
51
|
+
export function captureBatchState(store, input, opts = {}) {
|
|
52
|
+
const request = CaptureBatchRequestSchema.parse(input);
|
|
53
|
+
if (!request.principal.grants.some(s => scopePath(s) === scopePath(request.scope)))
|
|
54
|
+
throw new StateRefusal("outside-grants", "capture scope is outside the principal's grants");
|
|
55
|
+
if (!request.observations.length && !request.reviews?.length)
|
|
56
|
+
throw new StateRefusal("malformed", "capture batch must contain observations or reviews");
|
|
57
|
+
const { home, isPrivate } = stateHomeFor(store, request.scope);
|
|
58
|
+
const sourceHashes = new Map();
|
|
59
|
+
const ledgerCache = {};
|
|
60
|
+
const results = [];
|
|
61
|
+
const reviews = [];
|
|
62
|
+
let changed = false;
|
|
63
|
+
try {
|
|
64
|
+
// Review before capture: a replay in the same batch must see the withdrawn state.
|
|
65
|
+
for (const [index, review] of (request.reviews ?? []).entries()) {
|
|
66
|
+
try {
|
|
67
|
+
const record = store.getStateDirect("derived", review.record_id, home);
|
|
68
|
+
if (!record || scopePath(record.scope) !== scopePath(request.scope) || !record.transform_version.startsWith('agent-capture/1:'))
|
|
69
|
+
throw new StateRefusal('conflict', 'captured observation is absent from this partition');
|
|
70
|
+
if (!isCredentialFreeValue(review.reason))
|
|
71
|
+
throw new StateRefusal('malformed', 'review reason contains credential material');
|
|
72
|
+
const evidence = review.evidence.map(e => {
|
|
73
|
+
const source = request.sources[e.source];
|
|
74
|
+
if (!source || !source.source_text.includes(e.excerpt) || !isCredentialFreeValue(e.excerpt))
|
|
75
|
+
throw new StateRefusal('malformed', 'review excerpt must occur exactly in the supplied source');
|
|
76
|
+
const hash = sourceHashes.get(source.source_text) ?? stateHash(source.source_text);
|
|
77
|
+
sourceHashes.set(source.source_text, hash);
|
|
78
|
+
if (source.ref.content_hash && source.ref.content_hash !== hash)
|
|
79
|
+
throw new StateRefusal('malformed', 'review source hash mismatch');
|
|
80
|
+
const ref = { ...source.ref, object_key: canonicalObjectKey(source.ref.object_key), content_hash: hash };
|
|
81
|
+
const original = record.dependencies.find(d => d.kind === 'external' && externalKey(d.ref) === externalKey(ref));
|
|
82
|
+
if (!original || original.kind !== 'external' || original.ref.content_hash === hash)
|
|
83
|
+
throw new StateRefusal('conflict', 'review must cite a changed source the observation actually depends on');
|
|
84
|
+
return { ref, excerpt: e.excerpt };
|
|
85
|
+
});
|
|
86
|
+
const identity = stateHash({ record_id: record.id, expected_hash: review.expected_hash, reason: review.reason, evidence: evidence.map(e => ({ source: externalKey(e.ref), hash: e.ref.content_hash, excerpt: e.excerpt })) });
|
|
87
|
+
// A safe retry returns the same withdrawal without changing its reviewer/time.
|
|
88
|
+
const old = record.review;
|
|
89
|
+
if (record.state === 'stale' && old && old.previous_hash === review.expected_hash && old.reason === review.reason && stateHash(old.evidence.map(e => ({ source: externalKey(e.ref), hash: e.ref.content_hash, excerpt: e.excerpt }))) === stateHash(evidence.map(e => ({ source: externalKey(e.ref), hash: e.ref.content_hash, excerpt: e.excerpt })))) {
|
|
90
|
+
reviews.push({ index, status: 'saved', result: { schema: STATE_WRITE_VERSION, record_id: record.id, record_hash: stateHash(record), record: record, durability: 'local', outcome: 'replayed', conflict: null } });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (record.state !== 'unknown' || record.valid_to != null || stateHash(record) !== review.expected_hash)
|
|
94
|
+
throw new StateRefusal('conflict', 'observation changed since review; read it again');
|
|
95
|
+
const at = (opts.now ?? (() => new Date()))().toISOString();
|
|
96
|
+
const result = writeState(store, { schema: STATE_WRITE_VERSION, principal: request.principal, scope: request.scope, facet: 'derived',
|
|
97
|
+
record: { ...record, state: 'stale', review: { by: request.principal.id, at, previous_hash: review.expected_hash, reason: review.reason, evidence } },
|
|
98
|
+
expected_version: review.expected_hash, idempotency_key: `observation-review:${identity}`, cause: { kind: 'external', ref: evidence[0].ref } }, { now: opts.now, ledgerCache, deferReindex: true });
|
|
99
|
+
changed ||= result.outcome !== 'replayed';
|
|
100
|
+
reviews.push({ index, status: 'saved', result });
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (!(error instanceof StateRefusal))
|
|
104
|
+
throw error;
|
|
105
|
+
reviews.push({ index, status: 'refused', code: error.code, message: error.message });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for (const [index, observation] of request.observations.entries()) {
|
|
109
|
+
try {
|
|
110
|
+
const evidence = observation.evidence.map(e => {
|
|
111
|
+
const source = request.sources[e.source];
|
|
112
|
+
if (!source)
|
|
113
|
+
throw new StateRefusal("malformed", `evidence source index ${e.source} is absent`);
|
|
114
|
+
return { ...source, excerpt: e.excerpt };
|
|
115
|
+
});
|
|
116
|
+
const result = captureState(store, { schema: STATE_CAPTURE_VERSION, principal: request.principal, scope: request.scope, ...observation, evidence }, { now: opts.now, sourceHashes, ledgerCache, deferReindex: true });
|
|
117
|
+
changed ||= result.outcome !== "replayed";
|
|
118
|
+
results.push({ index, status: "saved", result });
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (!(error instanceof StateRefusal))
|
|
122
|
+
throw error;
|
|
123
|
+
results.push({ index, status: "refused", code: error.code, message: error.message });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
// A filesystem failure may occur after an atomic record write but before its
|
|
129
|
+
// result; refresh the derived index before propagating the failure to the caller.
|
|
130
|
+
changed = true;
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
if (changed)
|
|
135
|
+
store.reindex();
|
|
136
|
+
}
|
|
137
|
+
if (changed) {
|
|
138
|
+
const durability = opts.flush?.(isPrivate, `nuryel: capture ${results.filter(r => r.status === "saved" && r.result.outcome === "created").length} observations`) ?? "local";
|
|
139
|
+
for (const item of [...results, ...reviews])
|
|
140
|
+
if (item.status === "saved")
|
|
141
|
+
item.result.durability = durability;
|
|
142
|
+
}
|
|
143
|
+
return { schema: STATE_CAPTURE_BATCH_VERSION, results, ...(request.reviews ? { reviews } : {}) };
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=stateCapture.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const adapterSchema: z.ZodObject<{
|
|
3
|
+
name: z.ZodString;
|
|
4
|
+
command: z.ZodString;
|
|
5
|
+
args: z.ZodArray<z.ZodString>;
|
|
6
|
+
protocol: z.ZodEnum<{
|
|
7
|
+
stdin: "stdin";
|
|
8
|
+
acp: "acp";
|
|
9
|
+
}>;
|
|
10
|
+
probe_args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
11
|
+
timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
12
|
+
}, z.core.$strict>;
|
|
13
|
+
export type AgentCliAdapter = z.infer<typeof adapterSchema>;
|
|
14
|
+
export interface AgentCliWorker {
|
|
15
|
+
name: string;
|
|
16
|
+
draftProse?(prompt: string): Promise<string>;
|
|
17
|
+
}
|
|
18
|
+
export declare function readAgentCliConfig(file: string): AgentCliAdapter[];
|
|
19
|
+
/** ACP (Kimi and other agents) or plain stdin → JSON stdout for any user-configured CLI. */
|
|
20
|
+
export declare function runAgentCli(adapterInput: AgentCliAdapter, prompt: string): Promise<string>;
|
|
21
|
+
export declare function discoverAgentClis(configured?: AgentCliAdapter[], initiator?: string): AgentCliWorker[];
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface AgentInitiator {
|
|
2
|
+
provider: string | null;
|
|
3
|
+
source: "explicit" | "environment" | "client" | "unknown" | "ambiguous";
|
|
4
|
+
}
|
|
5
|
+
export declare function normalizeInitiator(name: string): string;
|
|
6
|
+
export declare function detectInitiator(env?: NodeJS.ProcessEnv): AgentInitiator;
|
|
7
|
+
export declare function currentInitiator(env?: NodeJS.ProcessEnv): AgentInitiator;
|
|
8
|
+
export declare function withInitiator<T>(initiator: AgentInitiator, work: () => T): T;
|
|
9
|
+
/** Bind the MCP client, not the process that happened to start the server. Unknown clients stay unknown. */
|
|
10
|
+
export declare function initiatorFromClient(name: string | undefined): AgentInitiator;
|
|
11
|
+
/** Freeze the operation's origin before spawning Git hooks or other deferred children. */
|
|
12
|
+
export declare function initiatorChildEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
13
|
+
export declare function assertInitiatorProvider(provider: string): void;
|