@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,359 @@
1
+ "use strict";
2
+ // Onboarding enrichment protocol: the pure, dependency-free core shared by the two
3
+ // CLI bookends (`enrich plan` writes the authoritative run record; `enrich ingest`
4
+ // loads it and validates the scouts' candidates). Everything here is deterministic
5
+ // and side-effect-free: types, the candidate identity hash, the plan digest, and the
6
+ // SHAPE validators. Impure checks (realpath containment, exist-at-HEAD, line-range vs
7
+ // real file length, fs/network) live in ingest.ts; clock + id injection lives in
8
+ // plan.ts. See notes/20260626-mla-agent-onboarding-enrichment-plan.md (§5, §5b, §6, §6b, §8).
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.SCOUT_STATUSES = exports.SCOUT_NAMES = exports.ENRICHMENT_KINDS = exports.MIN_COMMIT_SHA_LENGTH = exports.MAX_EVIDENCE_PER_CANDIDATE = exports.MIN_STATEMENT_LENGTH = exports.MAX_STATEMENT_LENGTH = exports.DEFAULT_BUDGET_MS = exports.MAX_CANDIDATES_TOTAL = exports.MAX_PREPARED_INPUT_BYTES = exports.MAX_HISTORY_COMMITS = exports.MAX_DOCUMENT_TARGETS = exports.PROTOCOL_VERSION = void 0;
11
+ exports.normalizeStatement = normalizeStatement;
12
+ exports.candidateAnchors = candidateAnchors;
13
+ exports.candidateId = candidateId;
14
+ exports.candidateSlug = candidateSlug;
15
+ exports.candidateRelPath = candidateRelPath;
16
+ exports.stableStringify = stableStringify;
17
+ exports.computePlanDigest = computePlanDigest;
18
+ exports.defaultLimits = defaultLimits;
19
+ exports.commitAllowlist = commitAllowlist;
20
+ exports.resolveAllowedCommit = resolveAllowedCommit;
21
+ exports.validateCandidateShape = validateCandidateShape;
22
+ exports.validateScoutResultShape = validateScoutResultShape;
23
+ exports.validateIngestRequestShape = validateIngestRequestShape;
24
+ const crypto_1 = require("crypto");
25
+ exports.PROTOCOL_VERSION = 1;
26
+ // Input bounds (§8). Explicit MVP constants; only the time budget is configurable.
27
+ exports.MAX_DOCUMENT_TARGETS = 20;
28
+ exports.MAX_HISTORY_COMMITS = 40;
29
+ exports.MAX_PREPARED_INPUT_BYTES = 200_000;
30
+ exports.MAX_CANDIDATES_TOTAL = 20; // ceiling, not a target; zero is valid
31
+ exports.DEFAULT_BUDGET_MS = 240_000;
32
+ // Defensive bounds NOT pinned by the plan (§5 says only "max statement length" and
33
+ // "allowed kind"); these are conservative defaults, tune freely.
34
+ exports.MAX_STATEMENT_LENGTH = 500;
35
+ exports.MIN_STATEMENT_LENGTH = 1; // non-empty after normalization; no semantic floor (the human governs durability)
36
+ exports.MAX_EVIDENCE_PER_CANDIDATE = 12;
37
+ exports.MIN_COMMIT_SHA_LENGTH = 7; // git's conventional abbreviation floor
38
+ exports.ENRICHMENT_KINDS = [
39
+ "constraint",
40
+ "decision",
41
+ "convention",
42
+ "boundary",
43
+ "deprecation",
44
+ ];
45
+ exports.SCOUT_NAMES = ["documentation", "history"];
46
+ exports.SCOUT_STATUSES = ["complete", "failed", "timed_out"];
47
+ // --- Identity + digest -----------------------------------------------------------
48
+ // Nothing semantic: no stemming, no punctuation removal, no LLM canonicalization (§6).
49
+ function normalizeStatement(value) {
50
+ return value.trim().replace(/\s+/g, " ");
51
+ }
52
+ // The anchors that define a candidate's identity: file paths (from file evidence) and
53
+ // commit SHAs (from commit evidence). Line numbers are EXCLUDED so identity survives
54
+ // line drift (§6). Anchors are type-tagged ("f:"/"c:") to prevent a path that happens
55
+ // to equal a SHA string from colliding across the two evidence kinds, then deduped and
56
+ // sorted for a stable hash. A commit's optional historical `path` is supplementary
57
+ // context, not identity (the SHA is the anchor).
58
+ function candidateAnchors(candidate) {
59
+ const anchors = new Set();
60
+ for (const ev of candidate.evidence) {
61
+ if (ev.type === "file") {
62
+ anchors.add(`f:${ev.path}`);
63
+ }
64
+ else {
65
+ anchors.add(`c:${ev.commit.toLowerCase()}`);
66
+ }
67
+ }
68
+ return [...anchors].sort();
69
+ }
70
+ // candidateId = sha256( protocolVersion + kind + normalizeStatement(statement) + sortedAnchors )
71
+ // Identical content reuses the same id (idempotent re-ingest); changed content gets a
72
+ // different id. The server's per-revision PENDING default is the real authority backstop;
73
+ // this hash is only for dedup stability (§6).
74
+ function candidateId(candidate) {
75
+ const parts = [
76
+ String(exports.PROTOCOL_VERSION),
77
+ candidate.kind,
78
+ normalizeStatement(candidate.statement),
79
+ candidateAnchors(candidate).join("\n"),
80
+ ];
81
+ // Join with "\n" (not " "): normalizeStatement collapses all whitespace to single
82
+ // spaces so a statement can never contain a newline, which makes "\n" an unambiguous
83
+ // tuple delimiter. A space would be ambiguous (statements contain spaces); a NUL byte
84
+ // would make this file read as binary to grep/diff. Keep it "\n".
85
+ return (0, crypto_1.createHash)("sha256").update(parts.join("\n")).digest("hex");
86
+ }
87
+ // Human-friendly suffix for the persisted path; identity lives in the hash, the slug is
88
+ // cosmetic. Path: onboarding/<candidateId>-<slug>.md (§6).
89
+ function candidateSlug(statement, maxLen = 40) {
90
+ const slug = normalizeStatement(statement)
91
+ .toLowerCase()
92
+ .replace(/[^a-z0-9]+/g, "-")
93
+ .replace(/^-+|-+$/g, "")
94
+ .slice(0, maxLen)
95
+ .replace(/-+$/g, "");
96
+ return slug || "candidate";
97
+ }
98
+ function candidateRelPath(candidate) {
99
+ return `onboarding/${candidateId(candidate)}-${candidateSlug(candidate.statement)}.md`;
100
+ }
101
+ // Deterministic JSON: recursively sort object keys so the digest is stable regardless of
102
+ // property insertion order. Arrays keep their order (it is meaningful here: ranks, etc.).
103
+ function stableStringify(value) {
104
+ if (value === null || typeof value !== "object") {
105
+ return JSON.stringify(value) ?? "null";
106
+ }
107
+ if (Array.isArray(value)) {
108
+ return `[${value.map(stableStringify).join(",")}]`;
109
+ }
110
+ const obj = value;
111
+ const keys = Object.keys(obj).sort();
112
+ const body = keys
113
+ .filter((k) => obj[k] !== undefined)
114
+ .map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`)
115
+ .join(",");
116
+ return `{${body}}`;
117
+ }
118
+ // Digest over the integrity-bearing plan content: everything that defines the plan's
119
+ // commitments. Excludes runId (the lookup key), createdAt/deadlineAt (volatile
120
+ // orchestration), and planDigest itself. ingest recomputes this and rejects on mismatch,
121
+ // catching on-disk corruption of the stored record (§5b step 4).
122
+ function computePlanDigest(run) {
123
+ const canonical = stableStringify({
124
+ protocolVersion: run.protocolVersion,
125
+ workspaceId: run.workspaceId,
126
+ repositoryRoot: run.repositoryRoot,
127
+ limits: run.limits,
128
+ documentationTargets: run.documentationTargets,
129
+ historyEvidence: run.historyEvidence,
130
+ });
131
+ return (0, crypto_1.createHash)("sha256").update(canonical).digest("hex");
132
+ }
133
+ function defaultLimits(budgetMs = exports.DEFAULT_BUDGET_MS) {
134
+ return {
135
+ maxDocumentTargets: exports.MAX_DOCUMENT_TARGETS,
136
+ maxHistoryCommits: exports.MAX_HISTORY_COMMITS,
137
+ maxPreparedInputBytes: exports.MAX_PREPARED_INPUT_BYTES,
138
+ maxCandidatesTotal: exports.MAX_CANDIDATES_TOTAL,
139
+ budgetMs,
140
+ };
141
+ }
142
+ // --- Commit allowlist resolution (pure; membership against the stored plan) ------
143
+ function commitAllowlist(run) {
144
+ return run.historyEvidence.map((e) => e.commit.toLowerCase());
145
+ }
146
+ // Resolve a candidate-cited commit (possibly abbreviated) against the plan's allowlist
147
+ // of full SHAs. Returns the canonical full SHA, or null if it matches none or is
148
+ // ambiguous (a too-short prefix hitting more than one allowlisted commit). ingest uses
149
+ // this to enforce "commit must be in the plan's allowlist" (§5b step 5).
150
+ function resolveAllowedCommit(allowlist, cited) {
151
+ const needle = cited.trim().toLowerCase();
152
+ if (!/^[0-9a-f]+$/.test(needle) || needle.length < exports.MIN_COMMIT_SHA_LENGTH)
153
+ return null;
154
+ const exact = allowlist.find((c) => c === needle);
155
+ if (exact)
156
+ return exact;
157
+ const prefixed = allowlist.filter((c) => c.startsWith(needle));
158
+ return prefixed.length === 1 ? prefixed[0] : null;
159
+ }
160
+ // --- Pure shape validators -------------------------------------------------------
161
+ const CANDIDATE_FIELDS = new Set(["kind", "statement", "evidence", "sourceScout"]);
162
+ const FILE_EVIDENCE_FIELDS = new Set(["type", "path", "startLine", "endLine"]);
163
+ const COMMIT_EVIDENCE_FIELDS = new Set(["type", "commit", "path"]);
164
+ function isPlainObject(v) {
165
+ return typeof v === "object" && v !== null && !Array.isArray(v);
166
+ }
167
+ function isPositiveInt(v) {
168
+ return typeof v === "number" && Number.isInteger(v) && v >= 1;
169
+ }
170
+ // Validates a single untrusted candidate's SHAPE only (§5). Pure: no fs, no git. The
171
+ // caller (ingest) layers on realpath containment, exist-at-HEAD, line-range-vs-file,
172
+ // and commit-allowlist membership. Collects ALL shape errors for one candidate so the
173
+ // scout's report is actionable rather than first-error-only.
174
+ function validateCandidateShape(raw, index) {
175
+ const errors = [];
176
+ const err = (code, message, field) => {
177
+ errors.push({ index, code, message, field });
178
+ };
179
+ if (!isPlainObject(raw)) {
180
+ return { ok: false, errors: [{ index, code: "not_an_object", message: "candidate must be a JSON object" }] };
181
+ }
182
+ for (const key of Object.keys(raw)) {
183
+ if (!CANDIDATE_FIELDS.has(key))
184
+ err("unknown_field", `unknown field "${key}"`, key);
185
+ }
186
+ const kind = raw.kind;
187
+ if (typeof kind !== "string" || !exports.ENRICHMENT_KINDS.includes(kind)) {
188
+ err("bad_kind", `kind must be one of: ${exports.ENRICHMENT_KINDS.join(", ")}`, "kind");
189
+ }
190
+ const sourceScout = raw.sourceScout;
191
+ if (typeof sourceScout !== "string" || !exports.SCOUT_NAMES.includes(sourceScout)) {
192
+ err("bad_source_scout", `sourceScout must be one of: ${exports.SCOUT_NAMES.join(", ")}`, "sourceScout");
193
+ }
194
+ const statement = raw.statement;
195
+ if (typeof statement !== "string") {
196
+ err("bad_statement", "statement must be a string", "statement");
197
+ }
198
+ else {
199
+ const norm = normalizeStatement(statement);
200
+ if (norm.length < exports.MIN_STATEMENT_LENGTH)
201
+ err("empty_statement", "statement is empty", "statement");
202
+ if (norm.length > exports.MAX_STATEMENT_LENGTH) {
203
+ err("statement_too_long", `statement exceeds ${exports.MAX_STATEMENT_LENGTH} chars`, "statement");
204
+ }
205
+ }
206
+ const evidence = raw.evidence;
207
+ const validEvidence = [];
208
+ if (!Array.isArray(evidence) || evidence.length === 0) {
209
+ err("no_evidence", "evidence must be a non-empty array", "evidence");
210
+ }
211
+ else if (evidence.length > exports.MAX_EVIDENCE_PER_CANDIDATE) {
212
+ err("too_much_evidence", `evidence exceeds ${exports.MAX_EVIDENCE_PER_CANDIDATE} items`, "evidence");
213
+ }
214
+ else {
215
+ evidence.forEach((ev, i) => {
216
+ const parsed = validateEvidenceShape(ev, index, i, err);
217
+ if (parsed)
218
+ validEvidence.push(parsed);
219
+ });
220
+ }
221
+ // Anchor-type cross-check (§5): documentation candidates require >= 1 file anchor;
222
+ // history candidates require >= 1 commit anchor.
223
+ if (sourceScout === "documentation" && !validEvidence.some((e) => e.type === "file")) {
224
+ err("missing_file_anchor", "documentation candidate requires at least one file anchor", "evidence");
225
+ }
226
+ if (sourceScout === "history" && !validEvidence.some((e) => e.type === "commit")) {
227
+ err("missing_commit_anchor", "history candidate requires at least one commit anchor", "evidence");
228
+ }
229
+ if (errors.length > 0)
230
+ return { ok: false, errors };
231
+ return {
232
+ ok: true,
233
+ candidate: {
234
+ kind: kind,
235
+ statement: statement,
236
+ evidence: validEvidence,
237
+ sourceScout: sourceScout,
238
+ },
239
+ };
240
+ }
241
+ function validateEvidenceShape(raw, candidateIndex, evidenceIndex, err) {
242
+ const field = `evidence[${evidenceIndex}]`;
243
+ if (!isPlainObject(raw)) {
244
+ err("bad_evidence", "evidence item must be an object", field);
245
+ return null;
246
+ }
247
+ const type = raw.type;
248
+ if (type === "file") {
249
+ for (const key of Object.keys(raw)) {
250
+ if (!FILE_EVIDENCE_FIELDS.has(key))
251
+ err("unknown_field", `unknown field "${key}" on file evidence`, `${field}.${key}`);
252
+ }
253
+ const path = raw.path;
254
+ const startLine = raw.startLine;
255
+ const endLine = raw.endLine;
256
+ let ok = true;
257
+ if (typeof path !== "string" || path.trim().length === 0) {
258
+ err("bad_path", "file evidence requires a non-empty path", `${field}.path`);
259
+ ok = false;
260
+ }
261
+ if (!isPositiveInt(startLine)) {
262
+ err("bad_line", "startLine must be an integer >= 1", `${field}.startLine`);
263
+ ok = false;
264
+ }
265
+ if (!isPositiveInt(endLine)) {
266
+ err("bad_line", "endLine must be an integer >= 1", `${field}.endLine`);
267
+ ok = false;
268
+ }
269
+ if (isPositiveInt(startLine) && isPositiveInt(endLine) && endLine < startLine) {
270
+ err("bad_range", "endLine must be >= startLine", `${field}.endLine`);
271
+ ok = false;
272
+ }
273
+ if (!ok)
274
+ return null;
275
+ return { type: "file", path: path, startLine: startLine, endLine: endLine };
276
+ }
277
+ if (type === "commit") {
278
+ for (const key of Object.keys(raw)) {
279
+ if (!COMMIT_EVIDENCE_FIELDS.has(key))
280
+ err("unknown_field", `unknown field "${key}" on commit evidence`, `${field}.${key}`);
281
+ }
282
+ const commit = raw.commit;
283
+ const path = raw.path;
284
+ let ok = true;
285
+ if (typeof commit !== "string" || !/^[0-9a-f]+$/i.test(commit) || commit.length < exports.MIN_COMMIT_SHA_LENGTH || commit.length > 40) {
286
+ err("bad_commit", `commit must be a hex SHA of ${exports.MIN_COMMIT_SHA_LENGTH}-40 chars`, `${field}.commit`);
287
+ ok = false;
288
+ }
289
+ if (path !== undefined && (typeof path !== "string" || path.trim().length === 0)) {
290
+ err("bad_path", "commit evidence path must be a non-empty string when present", `${field}.path`);
291
+ ok = false;
292
+ }
293
+ if (!ok)
294
+ return null;
295
+ const out = { type: "commit", commit: commit.toLowerCase() };
296
+ if (typeof path === "string")
297
+ out.path = path;
298
+ return out;
299
+ }
300
+ err("bad_evidence_type", 'evidence type must be "file" or "commit"', `${field}.type`);
301
+ return null;
302
+ }
303
+ // Validates the OUTER scout envelope shape (§6b). candidates[] content is intentionally
304
+ // left as unknown[] here; each candidate is validated independently downstream so one bad
305
+ // candidate never discards the rest from the same scout.
306
+ function validateScoutResultShape(raw) {
307
+ if (!isPlainObject(raw))
308
+ return { ok: false, error: "scout result must be an object" };
309
+ const scout = raw.scout;
310
+ if (typeof scout !== "string" || !exports.SCOUT_NAMES.includes(scout)) {
311
+ return { ok: false, error: `scout must be one of: ${exports.SCOUT_NAMES.join(", ")}` };
312
+ }
313
+ const status = raw.status;
314
+ if (typeof status !== "string" || !exports.SCOUT_STATUSES.includes(status)) {
315
+ return { ok: false, error: `status must be one of: ${exports.SCOUT_STATUSES.join(", ")}` };
316
+ }
317
+ if (!Array.isArray(raw.candidates)) {
318
+ return { ok: false, error: "candidates must be an array" };
319
+ }
320
+ if (raw.truncated !== undefined && typeof raw.truncated !== "boolean") {
321
+ return { ok: false, error: "truncated must be a boolean when present" };
322
+ }
323
+ if (raw.error !== undefined && typeof raw.error !== "string") {
324
+ return { ok: false, error: "error must be a string when present" };
325
+ }
326
+ const result = {
327
+ scout: scout,
328
+ status: status,
329
+ candidates: raw.candidates,
330
+ };
331
+ if (typeof raw.truncated === "boolean")
332
+ result.truncated = raw.truncated;
333
+ if (typeof raw.error === "string")
334
+ result.error = raw.error;
335
+ return { ok: true, result };
336
+ }
337
+ // Validates the top-level ingest envelope (§5b). Per-scout envelope and per-candidate
338
+ // validation happen downstream.
339
+ function validateIngestRequestShape(raw) {
340
+ if (!isPlainObject(raw))
341
+ return { ok: false, error: "ingest request must be an object" };
342
+ if (raw.protocolVersion !== exports.PROTOCOL_VERSION) {
343
+ return { ok: false, error: `protocolVersion must be ${exports.PROTOCOL_VERSION}` };
344
+ }
345
+ if (typeof raw.runId !== "string" || raw.runId.trim().length === 0) {
346
+ return { ok: false, error: "runId must be a non-empty string" };
347
+ }
348
+ if (!Array.isArray(raw.results)) {
349
+ return { ok: false, error: "results must be an array" };
350
+ }
351
+ return {
352
+ ok: true,
353
+ request: {
354
+ protocolVersion: exports.PROTOCOL_VERSION,
355
+ runId: raw.runId,
356
+ results: raw.results,
357
+ },
358
+ };
359
+ }
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ // Onboarding scout briefs: the internal subagent prompt for each scout role.
3
+ //
4
+ // `/mla onboard` runs `enrich plan` (writes the authoritative run record), then
5
+ // dispatches one subagent per incomplete scout, then runs `enrich ingest`. This
6
+ // module renders the per-role brief from the run record so EVERY input a scout sees
7
+ // (the exact document targets, the bounded git evidence) is exactly what `enrich
8
+ // ingest` will validate against. The brief is a pure function of (run, role): no
9
+ // clock, no randomness, no IO. `enrich brief --run-id --role` prints it.
10
+ //
11
+ // The shared rules of engagement (role identity, candidate kinds, evidence rule,
12
+ // non-authoritative posture, untrusted-content rule) come from buildScoutPolicy in
13
+ // ../scanner/scout-mission, the SAME source the human copy/paste mission uses, so
14
+ // the two surfaces cannot drift (plan §4).
15
+ //
16
+ // Capability boundary: SCOUT_TOOL_ALLOWLIST is the single source of truth for what
17
+ // each scout may do. The documentation scout gets Read only; the history scout gets
18
+ // nothing (it interprets in-prompt evidence). A static test (gate 7) asserts neither
19
+ // allowlist contains a shell, mutation, or network tool, and the subagent .md
20
+ // definitions' `tools:` frontmatter is cross-checked against this map.
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.SCOUT_AGENT_NAME = exports.SCOUT_TOOL_ALLOWLIST = void 0;
23
+ exports.buildScoutPrompt = buildScoutPrompt;
24
+ const scout_mission_1 = require("../scanner/scout-mission");
25
+ // The capability each scout role is granted. Read-only for documentation; no tools
26
+ // for history (the plan precomputes and inlines its evidence). Deliberately narrow:
27
+ // the deterministic plan already discovered and ranked inputs, so scouts never need
28
+ // Glob/Grep, and they never need shell, write, or network. Widen only if real
29
+ // dogfood proves a need (plan §4).
30
+ exports.SCOUT_TOOL_ALLOWLIST = {
31
+ documentation: ["Read"],
32
+ history: [],
33
+ };
34
+ // The Claude Code subagent `name:` each scout role dispatches as. `/mla onboard`
35
+ // (the mla-onboard skill) reads this to pick the subagent_type per role, and
36
+ // wire.ts installs one ~/.claude/agents/<name>.md per role whose `tools:`
37
+ // frontmatter is rendered from SCOUT_TOOL_ALLOWLIST above. Single source of truth
38
+ // so the skill, the installed agent files, and the contract test cannot drift.
39
+ exports.SCOUT_AGENT_NAME = {
40
+ documentation: "meetless-doc-scout",
41
+ history: "meetless-history-scout",
42
+ };
43
+ function renderCategoryLines(policy) {
44
+ return policy.categories.map((c) => ` • ${c.kind}: ${c.gloss}`);
45
+ }
46
+ function renderDocumentationTargets(targets) {
47
+ if (targets.length === 0) {
48
+ return [
49
+ "(The plan issued no document targets for this run. Return status \"complete\"",
50
+ " with an empty candidates array.)",
51
+ ];
52
+ }
53
+ return [...targets]
54
+ .sort((a, b) => a.rank - b.rank)
55
+ .map((t) => ` ${t.rank}. ${t.path} [${t.tier}]`);
56
+ }
57
+ function renderGitEvidence(evidence) {
58
+ if (evidence.length === 0) {
59
+ return [
60
+ "(The plan issued no commit history for this run. Return status \"complete\"",
61
+ " with an empty candidates array.)",
62
+ ];
63
+ }
64
+ const lines = [];
65
+ for (const c of evidence) {
66
+ lines.push(`commit ${c.commit}`);
67
+ lines.push(` date: ${c.timestamp}`);
68
+ lines.push(` subject: ${c.subject}`);
69
+ if (c.body && c.body.trim().length > 0) {
70
+ lines.push(" message:");
71
+ for (const bodyLine of c.body.split("\n")) {
72
+ lines.push(` ${bodyLine}`);
73
+ }
74
+ }
75
+ if (c.changedFiles.length > 0) {
76
+ lines.push(" files:");
77
+ for (const f of c.changedFiles) {
78
+ const renamed = f.renamedFrom ? ` (from ${f.renamedFrom})` : "";
79
+ lines.push(` ${f.status} ${f.path}${renamed}`);
80
+ }
81
+ }
82
+ if (c.diffExcerpt && c.diffExcerpt.trim().length > 0) {
83
+ lines.push(" diff excerpt:");
84
+ for (const diffLine of c.diffExcerpt.split("\n")) {
85
+ lines.push(` ${diffLine}`);
86
+ }
87
+ }
88
+ lines.push("");
89
+ }
90
+ return lines;
91
+ }
92
+ function toolLine(role) {
93
+ const tools = exports.SCOUT_TOOL_ALLOWLIST[role];
94
+ if (tools.length === 0) {
95
+ return ("You have NO tools. Do not attempt to read files, run commands, or fetch " +
96
+ "anything; everything you need is reproduced in this brief.");
97
+ }
98
+ return `Your only tools are: ${tools.join(", ")}. Do not attempt any other tool.`;
99
+ }
100
+ function renderOutputContract(run, role) {
101
+ const evidenceExample = role === "documentation"
102
+ ? '{ "type": "file", "path": "<one of the documents above>", "startLine": 10, "endLine": 24 }'
103
+ : '{ "type": "commit", "commit": "<one of the commits above>", "path": "optional/historical/path" }';
104
+ const anchorRule = role === "documentation"
105
+ ? "Every candidate needs at least one `file` anchor whose path is exactly one of the documents listed above; the line range must point at the text that states the claim."
106
+ : "Every candidate needs at least one `commit` anchor whose SHA is exactly one of the commits listed above; an optional `path` may name a historical file even if it no longer exists at HEAD.";
107
+ return [
108
+ "Return EXACTLY one JSON object and nothing else (no prose before or after it):",
109
+ "",
110
+ "{",
111
+ ` "scout": "${role}",`,
112
+ ' "status": "complete", // or "timed_out" if you ran out of time, "failed" if you could not proceed',
113
+ ' "candidates": [',
114
+ " {",
115
+ ' "kind": "<one of the kinds listed above>",',
116
+ ' "statement": "<one specific claim, 500 characters or fewer>",',
117
+ ` "evidence": [ ${evidenceExample} ],`,
118
+ ` "sourceScout": "${role}"`,
119
+ " }",
120
+ " ]",
121
+ "}",
122
+ "",
123
+ anchorRule,
124
+ `Surface at most ${run.limits.maxCandidatesTotal} candidates total across all scouts; ` +
125
+ "choose the highest-value ones rather than padding.",
126
+ 'Zero candidates with status "complete" is a valid, successful result: only record a',
127
+ "candidate you can anchor to the evidence above.",
128
+ "Also note any contradictions you see in a short prose summary after the JSON; a",
129
+ "contradiction is a flag for the human, not a candidate of its own.",
130
+ ];
131
+ }
132
+ /**
133
+ * Render the brief for one scout role from the authoritative run record. Pure: the
134
+ * same (run, role) always yields the same string. The documentation brief lists the
135
+ * exact ranked document targets and grants Read; the history brief inlines the
136
+ * bounded git evidence and grants no tools. Both state the shared scout policy and
137
+ * the JSON output contract `enrich ingest` expects.
138
+ */
139
+ function buildScoutPrompt(run, role) {
140
+ const policy = (0, scout_mission_1.buildScoutPolicy)();
141
+ const head = [
142
+ `Onboarding scout: ${role} (run ${run.runId}).`,
143
+ "",
144
+ ...policy.roleIdentity,
145
+ "",
146
+ "Surface these kinds of candidate (use the exact value for the `kind` field):",
147
+ ...renderCategoryLines(policy),
148
+ "",
149
+ ...policy.evidenceRule,
150
+ "",
151
+ ...policy.untrustedContent,
152
+ "",
153
+ ...policy.nonAuthoritative,
154
+ "",
155
+ toolLine(role),
156
+ `Wall-clock deadline: ${run.deadlineAt}. If you approach it, stop and return what you`,
157
+ 'have so far with status "timed_out" rather than working past the deadline.',
158
+ "",
159
+ ];
160
+ const body = role === "documentation"
161
+ ? [
162
+ "Read ONLY these documents, in rank order. The plan already selected and ranked",
163
+ "them; do not search for, glob, or open any other file.",
164
+ "",
165
+ ...renderDocumentationTargets(run.documentationTargets),
166
+ ]
167
+ : [
168
+ "You cannot open files or run git. The relevant history is reproduced below,",
169
+ "bounded to what fits this brief. Interpret it: why a current design exists, what",
170
+ "was reversed or superseded, which mistake keeps reappearing, which approach was",
171
+ "killed.",
172
+ "",
173
+ ...renderGitEvidence(run.historyEvidence),
174
+ ];
175
+ return [...head, ...body, "", ...renderOutputContract(run, role)].join("\n");
176
+ }