@meetless/mla 0.1.4

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 (202) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +81 -0
  3. package/dist/build-info.json +9 -0
  4. package/dist/bundles/ask-core.js +396 -0
  5. package/dist/bundles/mcp.js +16592 -0
  6. package/dist/bundles/trace-core.js +263 -0
  7. package/dist/cli.js +828 -0
  8. package/dist/commands/activate.js +781 -0
  9. package/dist/commands/adoption.js +130 -0
  10. package/dist/commands/ask.js +290 -0
  11. package/dist/commands/context.js +114 -0
  12. package/dist/commands/debug.js +313 -0
  13. package/dist/commands/doctor.js +1021 -0
  14. package/dist/commands/enrich.js +427 -0
  15. package/dist/commands/evidence.js +229 -0
  16. package/dist/commands/flush.js +184 -0
  17. package/dist/commands/graph.js +104 -0
  18. package/dist/commands/init.js +272 -0
  19. package/dist/commands/internal-active-review.js +322 -0
  20. package/dist/commands/internal-auto-index.js +188 -0
  21. package/dist/commands/internal-capture-decisions.js +320 -0
  22. package/dist/commands/internal-evidence-correlate.js +239 -0
  23. package/dist/commands/internal-evidence-hooks.js +240 -0
  24. package/dist/commands/internal-evidence-inject.js +231 -0
  25. package/dist/commands/internal-finalize.js +221 -0
  26. package/dist/commands/internal-pretool-observe.js +225 -0
  27. package/dist/commands/internal-refresh.js +136 -0
  28. package/dist/commands/internal-session-nudge.js +120 -0
  29. package/dist/commands/internal-steer-sync.js +117 -0
  30. package/dist/commands/internal-turn-recap.js +140 -0
  31. package/dist/commands/kb.js +375 -0
  32. package/dist/commands/kb_add.js +681 -0
  33. package/dist/commands/kb_forget.js +283 -0
  34. package/dist/commands/kb_move.js +45 -0
  35. package/dist/commands/kb_pending.js +410 -0
  36. package/dist/commands/kb_personal.js +149 -0
  37. package/dist/commands/kb_promote.js +188 -0
  38. package/dist/commands/kb_purge.js +168 -0
  39. package/dist/commands/kb_reingest.js +335 -0
  40. package/dist/commands/kb_retime.js +170 -0
  41. package/dist/commands/kb_review.js +391 -0
  42. package/dist/commands/kb_revision.js +179 -0
  43. package/dist/commands/kb_show.js +385 -0
  44. package/dist/commands/label.js +226 -0
  45. package/dist/commands/login.js +295 -0
  46. package/dist/commands/logout.js +108 -0
  47. package/dist/commands/mcp-supervisor.js +93 -0
  48. package/dist/commands/mcp.js +227 -0
  49. package/dist/commands/queue-prune.js +98 -0
  50. package/dist/commands/review.js +358 -0
  51. package/dist/commands/rewire.js +124 -0
  52. package/dist/commands/rules.js +728 -0
  53. package/dist/commands/scan-context.js +67 -0
  54. package/dist/commands/session.js +347 -0
  55. package/dist/commands/stats.js +479 -0
  56. package/dist/commands/status.js +61 -0
  57. package/dist/commands/summary.js +250 -0
  58. package/dist/commands/turn.js +114 -0
  59. package/dist/commands/uninstall.js +222 -0
  60. package/dist/commands/whoami.js +102 -0
  61. package/dist/commands/workspace.js +130 -0
  62. package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
  63. package/dist/hooks-template/ce0-session-start.sh +49 -0
  64. package/dist/hooks-template/ce0-stop.sh +29 -0
  65. package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
  66. package/dist/hooks-template/common.sh +934 -0
  67. package/dist/hooks-template/event-batch-filter.jq +67 -0
  68. package/dist/hooks-template/flush.sh +503 -0
  69. package/dist/hooks-template/post-tool-use.sh +423 -0
  70. package/dist/hooks-template/pre-tool-use.sh +69 -0
  71. package/dist/hooks-template/session-start.sh +140 -0
  72. package/dist/hooks-template/stop.sh +308 -0
  73. package/dist/hooks-template/user-prompt-submit.sh +1162 -0
  74. package/dist/lib/activation.js +79 -0
  75. package/dist/lib/active-conflict-cache.js +141 -0
  76. package/dist/lib/active-memory.js +59 -0
  77. package/dist/lib/active-review-runner.js +26 -0
  78. package/dist/lib/agent-decision/index.js +25 -0
  79. package/dist/lib/agent-decision/keys.js +49 -0
  80. package/dist/lib/agent-decision/normalize-claude.js +183 -0
  81. package/dist/lib/agent-decision/types.js +21 -0
  82. package/dist/lib/agent-decision/validate.js +216 -0
  83. package/dist/lib/analytics/capture.js +96 -0
  84. package/dist/lib/analytics/command-event.js +267 -0
  85. package/dist/lib/analytics/consent.js +58 -0
  86. package/dist/lib/analytics/coverage-gap.js +96 -0
  87. package/dist/lib/analytics/envelope.js +236 -0
  88. package/dist/lib/analytics/event-id.js +86 -0
  89. package/dist/lib/analytics/evidence.js +150 -0
  90. package/dist/lib/analytics/followthrough.js +194 -0
  91. package/dist/lib/analytics/forwarder.js +109 -0
  92. package/dist/lib/analytics/logs.js +78 -0
  93. package/dist/lib/analytics/metrics.js +78 -0
  94. package/dist/lib/analytics/recorder.js +92 -0
  95. package/dist/lib/analytics/review-analytics.js +75 -0
  96. package/dist/lib/analytics/sequence.js +77 -0
  97. package/dist/lib/analytics/store.js +131 -0
  98. package/dist/lib/analytics/turn-recap.js +279 -0
  99. package/dist/lib/artifact_id.js +108 -0
  100. package/dist/lib/auth-breaker.js +161 -0
  101. package/dist/lib/auto-index.js +112 -0
  102. package/dist/lib/classifier.js +88 -0
  103. package/dist/lib/config.js +298 -0
  104. package/dist/lib/conflict-advisory.js +64 -0
  105. package/dist/lib/debug-bundle.js +520 -0
  106. package/dist/lib/enrichment/ingest.js +301 -0
  107. package/dist/lib/enrichment/plan.js +253 -0
  108. package/dist/lib/enrichment/protocol.js +359 -0
  109. package/dist/lib/enrichment/scout-brief.js +176 -0
  110. package/dist/lib/failure-telemetry.js +444 -0
  111. package/dist/lib/git.js +200 -0
  112. package/dist/lib/governance-cache.js +77 -0
  113. package/dist/lib/governed-path-cache.js +76 -0
  114. package/dist/lib/http.js +677 -0
  115. package/dist/lib/identity-envelope.js +23 -0
  116. package/dist/lib/kb-candidate.js +65 -0
  117. package/dist/lib/kb_acl.js +98 -0
  118. package/dist/lib/login.js +353 -0
  119. package/dist/lib/mcp-fetchers.js +130 -0
  120. package/dist/lib/mcp-restart.js +47 -0
  121. package/dist/lib/observability.js +805 -0
  122. package/dist/lib/open-url.js +33 -0
  123. package/dist/lib/orphan-guard.js +70 -0
  124. package/dist/lib/packaged.js +21 -0
  125. package/dist/lib/reconcile-sessions.js +171 -0
  126. package/dist/lib/redactor.js +89 -0
  127. package/dist/lib/relationship-candidate-query.js +27 -0
  128. package/dist/lib/render.js +611 -0
  129. package/dist/lib/rules/applicability.js +64 -0
  130. package/dist/lib/rules/attest-code-rule-version.js +47 -0
  131. package/dist/lib/rules/attest-notes-location.js +217 -0
  132. package/dist/lib/rules/attest-rule-version.js +69 -0
  133. package/dist/lib/rules/canonical-json.js +97 -0
  134. package/dist/lib/rules/ce0-emit.js +64 -0
  135. package/dist/lib/rules/ce0-evidence.js +281 -0
  136. package/dist/lib/rules/ce0-recall-sample.js +82 -0
  137. package/dist/lib/rules/ce0-rule.js +55 -0
  138. package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
  139. package/dist/lib/rules/ce0-store.js +683 -0
  140. package/dist/lib/rules/ce0-telemetry-project.js +93 -0
  141. package/dist/lib/rules/ce0-telemetry.js +158 -0
  142. package/dist/lib/rules/code-rule-registry.js +17 -0
  143. package/dist/lib/rules/command-match.js +185 -0
  144. package/dist/lib/rules/consult-evidence-binding.js +27 -0
  145. package/dist/lib/rules/consultation-capture-adapter.js +193 -0
  146. package/dist/lib/rules/content-match.js +56 -0
  147. package/dist/lib/rules/deny-admission.js +99 -0
  148. package/dist/lib/rules/durable-observation.js +190 -0
  149. package/dist/lib/rules/enforce-notes-version.js +421 -0
  150. package/dist/lib/rules/evaluation-input-hash.js +126 -0
  151. package/dist/lib/rules/evaluator.js +108 -0
  152. package/dist/lib/rules/inert-rule-families.js +51 -0
  153. package/dist/lib/rules/input-authority-resolver.js +241 -0
  154. package/dist/lib/rules/interception-schema.js +170 -0
  155. package/dist/lib/rules/interception-store.js +267 -0
  156. package/dist/lib/rules/live-input-authority.js +66 -0
  157. package/dist/lib/rules/local-matcher.js +108 -0
  158. package/dist/lib/rules/local-observe.js +79 -0
  159. package/dist/lib/rules/local-rule-version-repo.js +214 -0
  160. package/dist/lib/rules/memory-requirement.js +109 -0
  161. package/dist/lib/rules/notes-observe.js +39 -0
  162. package/dist/lib/rules/notes-path.js +261 -0
  163. package/dist/lib/rules/notes-rule.js +75 -0
  164. package/dist/lib/rules/observe-adapter.js +114 -0
  165. package/dist/lib/rules/observed-rule-hash.js +119 -0
  166. package/dist/lib/rules/prompt-submit-adapter.js +132 -0
  167. package/dist/lib/rules/requirement-subject.js +240 -0
  168. package/dist/lib/rules/rule-activity.js +67 -0
  169. package/dist/lib/rules/rule-version-hash.js +151 -0
  170. package/dist/lib/rules/runtime-scope.js +55 -0
  171. package/dist/lib/rules/stop-adapter.js +116 -0
  172. package/dist/lib/rules/stop-response-snapshot.js +174 -0
  173. package/dist/lib/rules/types.js +10 -0
  174. package/dist/lib/rules/ulid.js +46 -0
  175. package/dist/lib/rules/version-evaluation.js +156 -0
  176. package/dist/lib/scanner/agent-memory.js +99 -0
  177. package/dist/lib/scanner/bootstrap-summary.js +87 -0
  178. package/dist/lib/scanner/cache.js +59 -0
  179. package/dist/lib/scanner/frontmatter.js +42 -0
  180. package/dist/lib/scanner/parse-directives.js +69 -0
  181. package/dist/lib/scanner/parse-structured.js +72 -0
  182. package/dist/lib/scanner/render.js +73 -0
  183. package/dist/lib/scanner/scan.js +132 -0
  184. package/dist/lib/scanner/score.js +38 -0
  185. package/dist/lib/scanner/scout-mission.js +126 -0
  186. package/dist/lib/scanner/types.js +7 -0
  187. package/dist/lib/session-scope.js +195 -0
  188. package/dist/lib/spool.js +355 -0
  189. package/dist/lib/staleness.js +100 -0
  190. package/dist/lib/steer-cache.js +87 -0
  191. package/dist/lib/tagged-reference.js +20 -0
  192. package/dist/lib/temporal.js +109 -0
  193. package/dist/lib/turn-recap-emit.js +67 -0
  194. package/dist/lib/unwire.js +253 -0
  195. package/dist/lib/update-check.js +469 -0
  196. package/dist/lib/update-notifier.js +217 -0
  197. package/dist/lib/upgrade-apply.js +643 -0
  198. package/dist/lib/wire.js +1087 -0
  199. package/dist/lib/workspace.js +96 -0
  200. package/dist/lib/zip.js +154 -0
  201. package/dist/pretool-entry.js +37 -0
  202. package/package.json +75 -0
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ // CE0 offline telemetry projection: the CE0 durable store -> the §6.4 analytics events the harness
3
+ // measures precision/recall and outcome rates with. Pure: a store record in, an analytics
4
+ // RecordInput out; no I/O, no clock (the `mla evidence ce0-emit-telemetry` sweep supplies the store
5
+ // and the recorder supplies the envelope).
6
+ //
7
+ // Brutal honesty about scope. The CE0 store is a GRADING store, not a complete telemetry source, so
8
+ // only the two events it honestly backs are projected:
9
+ //
10
+ // memory_requirement_assessed one per assessment row (the precision/recall denominator)
11
+ // evidence_obligation_finalized one per FINALIZED obligation row
12
+ //
13
+ // The other two §6.4 events are live-only and deliberately NOT projected here: a fabricated
14
+ // latency_ms (evidence_consultation_completed) or per-hook duration (evidence_hook_health) has no
15
+ // honest offline value, and zero would be a false measurement.
16
+ //
17
+ // work_type rides as an accurate "not recorded" constant (CE0 does not classify the turn), as does
18
+ // the finalized event's answer_disposition (a human label set offline, never on the live obligation,
19
+ // so null is honest here). The sampling_bucket is NOT a constant: every assessment row carries its
20
+ // own deterministic bucket (R3 P0.9), projected verbatim so the offline unflagged-recall sample is
21
+ // reconstructible. satisfied_by_sources is ALSO not a constant: the live obligation's
22
+ // subjectSatisfaction is always [] (the runtime only records facts), so the projector recomputes the
23
+ // proof set offline exactly as ce0-export does, then resolves each proof's consultation back to the
24
+ // §1.6 source that initiated it. The recompute is bounded by the same frozen deadline the first Stop
25
+ // claimed, so a late consultation can never manufacture an on-time source.
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.CE0_WORK_TYPE_UNKNOWN = void 0;
28
+ exports.ce0TurnId = ce0TurnId;
29
+ exports.projectAssessedEvent = projectAssessedEvent;
30
+ exports.projectFinalizedEvent = projectFinalizedEvent;
31
+ const ce0_store_1 = require("./ce0-store");
32
+ const requirement_subject_1 = require("./requirement-subject");
33
+ const ce0_telemetry_1 = require("./ce0-telemetry");
34
+ /** CE0 does not classify the turn's work type. */
35
+ exports.CE0_WORK_TYPE_UNKNOWN = "unknown";
36
+ /** The CE0 turn coordinate carried in telemetry: the (session, sequence) pair the analytics side
37
+ * joins on. The assessment/obligation rows store both halves; this is their canonical rendering. */
38
+ function ce0TurnId(sessionId, localTurnSequence) {
39
+ return `${sessionId}:${localTurnSequence}`;
40
+ }
41
+ /** Project one assessment row into a memory_requirement_assessed event. EVERY assessment is a
42
+ * telemetry fact (REQUIRED or not): the negative half is the precision/recall denominator. */
43
+ function projectAssessedEvent(rec) {
44
+ return (0, ce0_telemetry_1.buildMemoryRequirementAssessedEvent)({
45
+ assessmentId: rec.assessmentId,
46
+ turnId: ce0TurnId(rec.sessionId, rec.localTurnSequence),
47
+ localTurnSequence: rec.localTurnSequence,
48
+ memoryRequirement: rec.requirement,
49
+ workType: exports.CE0_WORK_TYPE_UNKNOWN,
50
+ classifierVersion: rec.classifierVersion,
51
+ markerSetVersion: rec.markerSetVersion,
52
+ markersMatched: rec.markersMatched,
53
+ samplingBucket: rec.samplingBucket,
54
+ });
55
+ }
56
+ /**
57
+ * Recompute the distinct §1.6 sources that proved a required subject for this obligation, in
58
+ * canonical CONSULTATION_SOURCES order. The live subjectSatisfaction is always [] in CE0, so this
59
+ * mirrors ce0-export: map the turn's consultations onto the reducer input, take the eligible set
60
+ * bounded by the frozen deadline, recompute the proof set, then resolve each proof's consultation
61
+ * back to its source. Deduped (the tuple lists each source once) and stably ordered.
62
+ */
63
+ function resolveSatisfiedSources(obl, consultations) {
64
+ const eligible = (0, requirement_subject_1.selectEligibleConsultations)(consultations.map(ce0_store_1.consultationRecordToReducerInput), obl.deadlineClaimedAt);
65
+ const proofs = (0, requirement_subject_1.recomputeSubjectSatisfaction)(obl.requiredSubjects, eligible);
66
+ const sourceById = new Map(consultations.map((c) => [c.consultationId, c.source]));
67
+ const proven = new Set();
68
+ for (const proof of proofs) {
69
+ const source = sourceById.get(proof.consultationId);
70
+ if (source)
71
+ proven.add(source);
72
+ }
73
+ return requirement_subject_1.CONSULTATION_SOURCES.filter((s) => proven.has(s));
74
+ }
75
+ /** Project one FINALIZED obligation row into an evidence_obligation_finalized event. The DB
76
+ * invariant ties FINALIZED to a non-null outcome, so a null outcome means the caller handed in a
77
+ * non-finalized row; reject it loudly rather than emit a malformed event. The turn's consultations
78
+ * are passed alongside the obligation so the projector can recompute which sources proved a subject
79
+ * (the live subjectSatisfaction is always [] in CE0). */
80
+ function projectFinalizedEvent(obl, consultations) {
81
+ if (obl.outcome === null) {
82
+ throw new Error(`projectFinalizedEvent: obligation ${obl.obligationId} has no outcome (not FINALIZED)`);
83
+ }
84
+ return (0, ce0_telemetry_1.buildEvidenceObligationFinalizedEvent)({
85
+ obligationId: obl.obligationId,
86
+ localTurnSequence: obl.localTurnSequence,
87
+ ruleVersionId: obl.ruleVersionId,
88
+ stateVersion: obl.stateVersion,
89
+ outcome: obl.outcome,
90
+ satisfiedBySources: resolveSatisfiedSources(obl, consultations),
91
+ answerDisposition: null,
92
+ });
93
+ }
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ // CE0 minimal telemetry: the EXACTLY four PostHog events the harness needs to measure
3
+ // precision, recall, the consultation rate, and hook health
4
+ // (notes/20260617-evidence-consultation-forcing-function-proposal.md §6.4, R3 P1.6):
5
+ //
6
+ // memory_requirement_assessed every classified turn (the precision/recall denominator)
7
+ // evidence_consultation_completed every governed-memory consultation, keyed by the consultation
8
+ // evidence_obligation_finalized emitted offline by the label importer (§2.3)
9
+ // evidence_hook_health the latency/health watchdog
10
+ //
11
+ // These are PURE projections: a CE0 record -> the analytics RecordInput the recorder ships. No I/O,
12
+ // no clock; the recorder supplies the envelope (workspace, session, run, trace, timestamps). The
13
+ // privacy boundary (INV-POSTHOG-PII-1) holds field by field: ids, enums, counts, booleans,
14
+ // durations, and hashes only. markersMatched is hashed here, never emitted verbatim.
15
+ //
16
+ // eventId strategy mirrors event-id.ts's server-recomputable family. ALL FOUR events carry a
17
+ // deterministic id so a hook re-firing across processes dedupes on (businessKey, version): the
18
+ // assessment on (assessmentId, 0), the consultation on (consultationId, 0), the finalization on
19
+ // (obligationId, stateVersion), and the health watchdog on (hook + operationIdentity, 0). The health
20
+ // event's operationIdentity is the stable per-hook coordinate the hook acted on (§6.4 P0.2), so a
21
+ // retried hook hashes to the same id rather than minting a fresh CLI-origin id and double-counting.
22
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ var desc = Object.getOwnPropertyDescriptor(m, k);
25
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
26
+ desc = { enumerable: true, get: function() { return m[k]; } };
27
+ }
28
+ Object.defineProperty(o, k2, desc);
29
+ }) : (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ o[k2] = m[k];
32
+ }));
33
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
35
+ }) : function(o, v) {
36
+ o["default"] = v;
37
+ });
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.hashMarkerSet = hashMarkerSet;
57
+ exports.buildMemoryRequirementAssessedEvent = buildMemoryRequirementAssessedEvent;
58
+ exports.buildEvidenceConsultationCompletedEvent = buildEvidenceConsultationCompletedEvent;
59
+ exports.buildEvidenceObligationFinalizedEvent = buildEvidenceObligationFinalizedEvent;
60
+ exports.buildEvidenceHookHealthEvent = buildEvidenceHookHealthEvent;
61
+ const crypto = __importStar(require("crypto"));
62
+ const event_id_1 = require("../analytics/event-id");
63
+ const envelope_1 = require("../analytics/envelope");
64
+ const OBLIGATION_OUTCOME_SET = new Set(envelope_1.OBLIGATION_OUTCOME_LABELS);
65
+ /** Hash a marker set to a stable hex digest. Order-independent (the SET, not the sequence, is the
66
+ * identity) and one-way, so the matched markers never leave the device as raw text (§6.4 privacy). */
67
+ function hashMarkerSet(markers) {
68
+ const canonical = [...new Set(markers)].sort().join("\n");
69
+ return crypto.createHash("sha256").update(canonical).digest("hex");
70
+ }
71
+ /** One event per classified turn (REQUIRED or not). Without it the precision/recall denominator
72
+ * (the turns you did NOT flag) is unobservable. Carries no obligationId by design. */
73
+ function buildMemoryRequirementAssessedEvent(input) {
74
+ return {
75
+ eventType: "memory_requirement_assessed",
76
+ eventId: (0, event_id_1.deterministicEventId)(input.assessmentId, 0),
77
+ payload: {
78
+ assessment_id: input.assessmentId,
79
+ turn_id: input.turnId,
80
+ local_turn_sequence: input.localTurnSequence,
81
+ memory_requirement: input.memoryRequirement,
82
+ work_type: input.workType,
83
+ classifier_version: input.classifierVersion,
84
+ marker_set_version: input.markerSetVersion,
85
+ markers_matched_hashed: hashMarkerSet(input.markersMatched),
86
+ sampling_bucket: input.samplingBucket,
87
+ },
88
+ };
89
+ }
90
+ /** One event per governed-memory consultation, keyed by the consultation (NOT the obligation). No
91
+ * FULL/PARTIAL/NONE coverage summary in CE0: coverage grading is offline (§1.6). `ruleVersionId` and
92
+ * `latencyMs` are optional (§6.4 P1.2 / P0.2) and carried only when the turn supplies them, mirroring
93
+ * the health event's optional `turnId`. */
94
+ function buildEvidenceConsultationCompletedEvent(input) {
95
+ // §6.4 / P0.3: result is present IFF the consultation is COMPLETE.
96
+ const complete = input.execution === "COMPLETE";
97
+ if (complete !== (input.result !== null)) {
98
+ throw new Error(`evidence_consultation_completed: result must be present IFF execution is COMPLETE ` +
99
+ `(execution=${input.execution}, result=${input.result})`);
100
+ }
101
+ const payload = {
102
+ consultation_id: input.consultationId,
103
+ local_turn_sequence: input.localTurnSequence,
104
+ source: input.source,
105
+ execution: input.execution,
106
+ result: input.result,
107
+ delivered_to_answering_context: input.deliveredToAnsweringContext,
108
+ };
109
+ if (input.ruleVersionId !== undefined)
110
+ payload.rule_version_id = input.ruleVersionId;
111
+ if (input.latencyMs !== undefined)
112
+ payload.latency_ms = input.latencyMs;
113
+ return {
114
+ eventType: "evidence_consultation_completed",
115
+ eventId: (0, event_id_1.deterministicEventId)(input.consultationId, 0),
116
+ payload,
117
+ };
118
+ }
119
+ /** Emitted by the offline label importer (§2.3) when an obligation finalizes. Server-recomputable on
120
+ * (obligationId, stateVersion): a re-import at the same stateVersion dedupes; a later finalization
121
+ * (a new stateVersion) is a distinct row. */
122
+ function buildEvidenceObligationFinalizedEvent(input) {
123
+ if (!OBLIGATION_OUTCOME_SET.has(input.outcome)) {
124
+ throw new Error(`evidence_obligation_finalized: outcome "${input.outcome}" is not a §6.4 ObligationOutcome`);
125
+ }
126
+ return {
127
+ eventType: "evidence_obligation_finalized",
128
+ eventId: (0, event_id_1.deterministicEventId)(input.obligationId, input.stateVersion),
129
+ payload: {
130
+ obligation_id: input.obligationId,
131
+ local_turn_sequence: input.localTurnSequence,
132
+ rule_version_id: input.ruleVersionId,
133
+ state_version: input.stateVersion,
134
+ outcome: input.outcome,
135
+ satisfied_by_sources: input.satisfiedBySources,
136
+ answer_disposition: input.answerDisposition,
137
+ },
138
+ };
139
+ }
140
+ /** The latency/health watchdog: one row per hook invocation. Keyed by (hook, operationIdentity) so a
141
+ * hook that re-fires across processes (a Stop continuation, a retry) re-appends the SAME deterministic
142
+ * eventId and the projection dedups it instead of double-counting (§6.4 P0.2). */
143
+ function buildEvidenceHookHealthEvent(input) {
144
+ const payload = {
145
+ hook: input.hook,
146
+ operation_identity: input.operationIdentity,
147
+ duration_ms: input.durationMs,
148
+ failed: input.failed,
149
+ reason: input.reason,
150
+ };
151
+ if (input.turnId !== undefined)
152
+ payload.turn_id = input.turnId;
153
+ return {
154
+ eventType: "evidence_hook_health",
155
+ eventId: (0, event_id_1.deterministicEventId)(`${input.hook}:${input.operationIdentity}`, 0),
156
+ payload,
157
+ };
158
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCodeRule = getCodeRule;
4
+ const canonical_json_1 = require("./canonical-json");
5
+ const ce0_rule_1 = require("./ce0-rule");
6
+ /** The closed registry, keyed by code-rule name. Adding a rule here is the only way to enroll one. */
7
+ const CODE_RULES = {
8
+ [ce0_rule_1.CONSULT_EVIDENCE_RULE_ID]: {
9
+ ruleId: ce0_rule_1.CONSULT_EVIDENCE_RULE_ID,
10
+ serializedPayload: (0, canonical_json_1.canonicalize)(ce0_rule_1.CONSULT_EVIDENCE_RULE_PAYLOAD),
11
+ canonicalPayloadHash: ce0_rule_1.CONSULT_EVIDENCE_CANONICAL_PAYLOAD_HASH,
12
+ },
13
+ };
14
+ /** Resolve a code-rule by name; null when no such rule ships in source (never fabricates one). */
15
+ function getCodeRule(name) {
16
+ return CODE_RULES[name] ?? null;
17
+ }
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ // COMMAND matcher: the pure tokenizer + classifier for the git/prisma rule class
3
+ // (GAP2).
4
+ //
5
+ // The proposal declares Bash PATH enforcement out of v1 (§552-556 / §3167) because
6
+ // a shell string is opaque: cp/mv/python/redirection/eval can perform an effect
7
+ // without the literal tokens appearing, so you can never prove a command is SAFE
8
+ // for a path rule. This module covers the decidable HALF the proposal left out:
9
+ // the POSITIVE literal match. If the forbidden tokens (e.g. "git push") appear as
10
+ // a contiguous run of unquoted, uncommented words, the command performs that
11
+ // operation. Opacity can only ADD effects, never remove the literal one, so a
12
+ // positive match is a sound VIOLATION.
13
+ //
14
+ // The asymmetry is deliberate and is the whole reason this matcher exists:
15
+ // forbidden token run present -> MATCHES_FORBIDDEN -> VIOLATION
16
+ // no run found -> NO_MATCH -> UNKNOWN (NOT compliant)
17
+ // non-string / no usable needle -> INDETERMINATE -> UNKNOWN
18
+ // There is NO command COMPLIANT. A non-match cannot prove the command will not push
19
+ // (an alias, a wrapper script, eval, or $VAR expansion could), so the absence of
20
+ // the run is UNKNOWN, never proof of compliance. That is the inverse of the CONTENT
21
+ // matcher, whose field is fully observable and so CAN produce a real COMPLIANT.
22
+ //
23
+ // Soundness of a positive match rests on three tokenizer guarantees:
24
+ // 1. quotes collapse a run into ONE token, so `echo "git push"` is not a match;
25
+ // 2. a `#` at a word boundary starts a comment, so `ls # git push` is not a match;
26
+ // 3. statement separators (newline, ; | &, parens) break a segment, so `git ;
27
+ // push` is two statements, not the `git push` invocation.
28
+ // Known, ACCEPTED limitation: a command reached indirectly (an absolute path like
29
+ // `/usr/bin/git push`, a subshell `(git push)` with no inner spaces, an alias) will
30
+ // MISS. A miss is a false negative that degrades to UNKNOWN, which is the safe,
31
+ // non-denying state. This matcher is OBSERVE-ONLY in this slice; it must never deny
32
+ // until a tokenized pattern is human-attested, which is the safety valve for the
33
+ // residual risk that a contrived redirect target places the tokens consecutively.
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.tokenizeCommand = tokenizeCommand;
36
+ exports.classifyCommand = classifyCommand;
37
+ // Statement separators that break a token run. A forbidden sequence can never
38
+ // match ACROSS one of these, so two adjacent statements cannot be read as a single
39
+ // command invocation. Redirections (< >) are deliberately NOT here: they leave
40
+ // their operator as its own token, which already breaks contiguity.
41
+ const SEPARATORS = new Set([";", "|", "&", "(", ")", "\n"]);
42
+ const DQUOTE_ESCAPABLE = new Set(['"', "\\", "$", "`", "\n"]);
43
+ /**
44
+ * A deliberately small POSIX-ish tokenizer, scoped to what a SOUND positive match
45
+ * needs: single quotes (literal), double quotes (with the POSIX backslash escapes),
46
+ * backslash escaping and line continuation, `#` comments at a word boundary, and
47
+ * the statement separators above. It does not expand variables, globs, aliases, or
48
+ * substitutions: those only ever cause a MISS (UNKNOWN), never a false match.
49
+ */
50
+ function tokenizeCommand(raw) {
51
+ const segments = [];
52
+ let segment = [];
53
+ let token = "";
54
+ let inToken = false;
55
+ const endToken = () => {
56
+ if (inToken) {
57
+ segment.push(token);
58
+ token = "";
59
+ inToken = false;
60
+ }
61
+ };
62
+ const endSegment = () => {
63
+ endToken();
64
+ if (segment.length > 0) {
65
+ segments.push(segment);
66
+ segment = [];
67
+ }
68
+ };
69
+ let i = 0;
70
+ while (i < raw.length) {
71
+ const c = raw[i];
72
+ if (c === "'") {
73
+ inToken = true;
74
+ i++;
75
+ while (i < raw.length && raw[i] !== "'") {
76
+ token += raw[i];
77
+ i++;
78
+ }
79
+ i++; // consume the closing quote (or run off the end on an unbalanced quote)
80
+ continue;
81
+ }
82
+ if (c === '"') {
83
+ inToken = true;
84
+ i++;
85
+ while (i < raw.length && raw[i] !== '"') {
86
+ if (raw[i] === "\\" && i + 1 < raw.length && DQUOTE_ESCAPABLE.has(raw[i + 1])) {
87
+ token += raw[i + 1];
88
+ i += 2;
89
+ continue;
90
+ }
91
+ token += raw[i];
92
+ i++;
93
+ }
94
+ i++;
95
+ continue;
96
+ }
97
+ if (c === "\\") {
98
+ if (i + 1 < raw.length && raw[i + 1] === "\n") {
99
+ i += 2; // line continuation: both chars vanish
100
+ continue;
101
+ }
102
+ if (i + 1 < raw.length) {
103
+ token += raw[i + 1];
104
+ inToken = true;
105
+ i += 2;
106
+ continue;
107
+ }
108
+ token += c;
109
+ inToken = true;
110
+ i++;
111
+ continue;
112
+ }
113
+ // A hash starts a comment only at a word boundary (not mid-token), matching
114
+ // shell semantics: `abc#def` is one word, ` # ...` is a comment.
115
+ if (c === "#" && !inToken) {
116
+ while (i < raw.length && raw[i] !== "\n") {
117
+ i++;
118
+ }
119
+ continue;
120
+ }
121
+ if (c === " " || c === "\t" || c === "\r") {
122
+ endToken();
123
+ i++;
124
+ continue;
125
+ }
126
+ if (SEPARATORS.has(c)) {
127
+ endSegment();
128
+ i++;
129
+ continue;
130
+ }
131
+ token += c;
132
+ inToken = true;
133
+ i++;
134
+ }
135
+ endSegment();
136
+ return segments;
137
+ }
138
+ /** True iff `needle` occurs as a contiguous run inside `haystack`. */
139
+ function containsRun(haystack, needle) {
140
+ if (needle.length === 0 || needle.length > haystack.length) {
141
+ return false;
142
+ }
143
+ for (let start = 0; start + needle.length <= haystack.length; start++) {
144
+ let all = true;
145
+ for (let k = 0; k < needle.length; k++) {
146
+ if (haystack[start + k] !== needle[k]) {
147
+ all = false;
148
+ break;
149
+ }
150
+ }
151
+ if (all) {
152
+ return true;
153
+ }
154
+ }
155
+ return false;
156
+ }
157
+ /**
158
+ * Classify a candidate command string against a set of forbidden token sequences.
159
+ *
160
+ * INDETERMINATE when the value is not a string, or when no usable forbidden
161
+ * sequence remains after dropping sequences that are empty or contain an empty
162
+ * token (an empty token would degenerate matching). Otherwise MATCHES_FORBIDDEN
163
+ * iff some forbidden sequence is a contiguous run within some statement segment;
164
+ * NO_MATCH if none are. NO_MATCH is NOT compliance (see module header).
165
+ */
166
+ function classifyCommand(rawCommand, forbiddenSequences) {
167
+ if (typeof rawCommand !== "string") {
168
+ return "INDETERMINATE";
169
+ }
170
+ const needles = forbiddenSequences.filter((seq) => Array.isArray(seq) &&
171
+ seq.length > 0 &&
172
+ seq.every((t) => typeof t === "string" && t.length > 0));
173
+ if (needles.length === 0) {
174
+ return "INDETERMINATE";
175
+ }
176
+ const segments = tokenizeCommand(rawCommand);
177
+ for (const segment of segments) {
178
+ for (const needle of needles) {
179
+ if (containsRun(segment, needle)) {
180
+ return "MATCHES_FORBIDDEN";
181
+ }
182
+ }
183
+ }
184
+ return "NO_MATCH";
185
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveConsultEvidenceRuleBinding = resolveConsultEvidenceRuleBinding;
4
+ const ce0_rule_1 = require("./ce0-rule");
5
+ const local_rule_version_repo_1 = require("./local-rule-version-repo");
6
+ /**
7
+ * Resolve the consult-evidence obligation binding for a runtime scope. Returns the LIVE attested version's
8
+ * id + stored hash when one exists for the scope; otherwise the frozen compile-time identity (the unarmed
9
+ * measurement default). The two branches differ only in the version id unless a seed bump rotated the hash.
10
+ */
11
+ function resolveConsultEvidenceRuleBinding(store, runtimeScopeId) {
12
+ const live = (0, local_rule_version_repo_1.getLiveLocalRuleVersion)(store, runtimeScopeId, ce0_rule_1.CONSULT_EVIDENCE_RULE_ID);
13
+ if (live) {
14
+ return {
15
+ ruleId: live.ruleId,
16
+ ruleVersionId: live.versionId,
17
+ canonicalPayloadHash: live.canonicalPayloadHash,
18
+ attested: true,
19
+ };
20
+ }
21
+ return {
22
+ ruleId: ce0_rule_1.CONSULT_EVIDENCE_RULE_ID,
23
+ ruleVersionId: ce0_rule_1.CONSULT_EVIDENCE_RULE_VERSION_ID,
24
+ canonicalPayloadHash: ce0_rule_1.CONSULT_EVIDENCE_CANONICAL_PAYLOAD_HASH,
25
+ attested: false,
26
+ };
27
+ }
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.classifyConsultationTool = classifyConsultationTool;
4
+ exports.classifyRetrievalEnvelope = classifyRetrievalEnvelope;
5
+ exports.parsePostToolUseInput = parsePostToolUseInput;
6
+ exports.captureMemoryConsultation = captureMemoryConsultation;
7
+ const crypto_1 = require("crypto");
8
+ const ce0_store_1 = require("./ce0-store");
9
+ const requirement_subject_1 = require("./requirement-subject");
10
+ function isPlainObject(value) {
11
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12
+ }
13
+ function describeError(err) {
14
+ return err instanceof Error ? err.message : String(err);
15
+ }
16
+ function defaultNewId() {
17
+ return `con:${(0, crypto_1.randomUUID)()}`;
18
+ }
19
+ const CONSULTATION_TOOL_PATTERN = /(?:^|__)meetless__(retrieve_knowledge|kb_doc_detail|query)$/;
20
+ /**
21
+ * Recognize the governed-memory pull surface from the real (double-prefixed) hook tool name,
22
+ * e.g. `mcp__meetless__meetless__retrieve_knowledge`. Returns null for everything else,
23
+ * including the verdict write and a same-named tool on a foreign server.
24
+ */
25
+ function classifyConsultationTool(toolName) {
26
+ const match = CONSULTATION_TOOL_PATTERN.exec(toolName);
27
+ return match ? match[1] : null;
28
+ }
29
+ /** The subject text of a pull: the query for retrieve_knowledge / query, the document id
30
+ * for kb_doc_detail. Null (a missing or blank field) makes the event uncapturable. */
31
+ function extractConsultationText(tool, toolInput) {
32
+ const field = tool === "kb_doc_detail" ? "document_id" : "query";
33
+ const value = toolInput[field];
34
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
35
+ }
36
+ function extractResultPayload(response) {
37
+ const content = response.content;
38
+ if (!Array.isArray(content) || content.length === 0)
39
+ return null;
40
+ const first = content[0];
41
+ if (!isPlainObject(first) || typeof first.text !== "string")
42
+ return null;
43
+ try {
44
+ const parsed = JSON.parse(first.text);
45
+ return isPlainObject(parsed) ? parsed : null;
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ /** A clean empty governed result (count 0, or an empty candidates/results array) is a
52
+ * NO_MATCH; any other parseable success carries a result. kb_doc_detail and ask answers
53
+ * have no emptiness signal, so they read as RESULTS_RETURNED. */
54
+ function payloadCarriesResults(payload) {
55
+ if (typeof payload.count === "number")
56
+ return payload.count > 0;
57
+ if (Array.isArray(payload.candidates))
58
+ return payload.candidates.length > 0;
59
+ if (Array.isArray(payload.results))
60
+ return payload.results.length > 0;
61
+ return true;
62
+ }
63
+ /**
64
+ * Classify the MCP CallToolResult envelope into the §1.6 execution / result pair. Only an
65
+ * explicit handler error is FAILED; a valid parseable response is COMPLETE (with a result
66
+ * subtype that is telemetry only and does not gate satisfaction); anything malformed,
67
+ * missing, or uncorrelatable is UNKNOWN. result is present iff COMPLETE.
68
+ */
69
+ function classifyRetrievalEnvelope(toolResponse) {
70
+ if (!isPlainObject(toolResponse)) {
71
+ return { execution: "UNKNOWN" };
72
+ }
73
+ if (toolResponse.isError === true) {
74
+ return { execution: "FAILED" };
75
+ }
76
+ const payload = extractResultPayload(toolResponse);
77
+ if (payload === null) {
78
+ return { execution: "UNKNOWN" };
79
+ }
80
+ return {
81
+ execution: "COMPLETE",
82
+ result: payloadCarriesResults(payload) ? "RESULTS_RETURNED" : "NO_MATCH",
83
+ };
84
+ }
85
+ /**
86
+ * Parse and minimally validate the raw PostToolUse payload. Accepts either an already
87
+ * parsed object or the raw JSON string. Returns null for any shape that is not a usable
88
+ * PostToolUse event (a non-empty string tool_name and an object tool_input); the adapter
89
+ * maps that null to INFRA. The session coordinate is checked separately so its absence
90
+ * gets a distinct diagnostic.
91
+ */
92
+ function parsePostToolUseInput(raw) {
93
+ let obj = raw;
94
+ if (typeof raw === "string") {
95
+ try {
96
+ obj = JSON.parse(raw);
97
+ }
98
+ catch {
99
+ return null;
100
+ }
101
+ }
102
+ if (!isPlainObject(obj))
103
+ return null;
104
+ const toolName = obj.tool_name;
105
+ if (typeof toolName !== "string" || toolName.length === 0)
106
+ return null;
107
+ if (!isPlainObject(obj.tool_input))
108
+ return null;
109
+ const str = (v) => (typeof v === "string" ? v : undefined);
110
+ return {
111
+ session_id: str(obj.session_id),
112
+ transcript_path: str(obj.transcript_path),
113
+ cwd: str(obj.cwd),
114
+ hook_event_name: str(obj.hook_event_name),
115
+ tool_name: toolName,
116
+ tool_input: obj.tool_input,
117
+ tool_response: obj.tool_response,
118
+ };
119
+ }
120
+ /**
121
+ * Capture one governed-memory consultation. Records a ConsultationAttempt fact under the
122
+ * turn's LocalTurnIdentity and returns an empty (injection-free) hook response.
123
+ */
124
+ function captureMemoryConsultation(rawInput, config) {
125
+ const NO_INJECTION = {};
126
+ const parsed = parsePostToolUseInput(rawInput);
127
+ if (!parsed) {
128
+ return { response: NO_INJECTION, outcome: { kind: "INFRA", diagnostic: "malformed hook input" } };
129
+ }
130
+ const tool = classifyConsultationTool(parsed.tool_name);
131
+ if (!tool) {
132
+ return { response: NO_INJECTION, outcome: { kind: "NOT_APPLICABLE" } };
133
+ }
134
+ if (!parsed.session_id) {
135
+ return {
136
+ response: NO_INJECTION,
137
+ outcome: { kind: "INFRA", diagnostic: "missing session_id coordinate" },
138
+ };
139
+ }
140
+ const queryText = extractConsultationText(tool, parsed.tool_input);
141
+ if (queryText === null) {
142
+ return {
143
+ response: NO_INJECTION,
144
+ outcome: { kind: "INFRA", diagnostic: "missing consultation query" },
145
+ };
146
+ }
147
+ const identity = (0, ce0_store_1.resolveLatestTurnIdentity)(config.store, {
148
+ workspaceId: config.workspaceId,
149
+ sessionId: parsed.session_id,
150
+ });
151
+ if (!identity) {
152
+ return {
153
+ response: NO_INJECTION,
154
+ outcome: { kind: "INFRA", diagnostic: "no turn identity for session" },
155
+ };
156
+ }
157
+ const now = config.now ?? Date.now;
158
+ const newId = config.newId ?? defaultNewId;
159
+ const envelope = classifyRetrievalEnvelope(parsed.tool_response);
160
+ try {
161
+ const draft = {
162
+ consultationId: newId(),
163
+ workspaceId: config.workspaceId,
164
+ sessionId: parsed.session_id,
165
+ localTurnSequence: identity.localTurnSequence,
166
+ source: "AGENT_PULL",
167
+ consultationSubjects: [(0, requirement_subject_1.buildConsultationSubjectFromQuery)(queryText)],
168
+ execution: envelope.execution,
169
+ result: envelope.result ?? null,
170
+ deliveredToAnsweringContext: true,
171
+ createdAt: now(),
172
+ };
173
+ const rec = (0, ce0_store_1.appendConsultationAttempt)(config.store, draft);
174
+ return {
175
+ response: NO_INJECTION,
176
+ outcome: {
177
+ kind: "CAPTURED",
178
+ consultationId: rec.consultationId,
179
+ source: "AGENT_PULL",
180
+ execution: rec.execution,
181
+ result: rec.result,
182
+ localTurnSequence: rec.localTurnSequence,
183
+ orderingToken: rec.orderingToken,
184
+ },
185
+ };
186
+ }
187
+ catch (err) {
188
+ return {
189
+ response: NO_INJECTION,
190
+ outcome: { kind: "INFRA", diagnostic: `persistence failure: ${describeError(err)}` },
191
+ };
192
+ }
193
+ }