@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.
- package/LICENSE +201 -0
- package/README.md +81 -0
- package/dist/build-info.json +9 -0
- package/dist/bundles/ask-core.js +396 -0
- package/dist/bundles/mcp.js +16592 -0
- package/dist/bundles/trace-core.js +263 -0
- package/dist/cli.js +828 -0
- package/dist/commands/activate.js +781 -0
- package/dist/commands/adoption.js +130 -0
- package/dist/commands/ask.js +290 -0
- package/dist/commands/context.js +114 -0
- package/dist/commands/debug.js +313 -0
- package/dist/commands/doctor.js +1021 -0
- package/dist/commands/enrich.js +427 -0
- package/dist/commands/evidence.js +229 -0
- package/dist/commands/flush.js +184 -0
- package/dist/commands/graph.js +104 -0
- package/dist/commands/init.js +272 -0
- package/dist/commands/internal-active-review.js +322 -0
- package/dist/commands/internal-auto-index.js +188 -0
- package/dist/commands/internal-capture-decisions.js +320 -0
- package/dist/commands/internal-evidence-correlate.js +239 -0
- package/dist/commands/internal-evidence-hooks.js +240 -0
- package/dist/commands/internal-evidence-inject.js +231 -0
- package/dist/commands/internal-finalize.js +221 -0
- package/dist/commands/internal-pretool-observe.js +225 -0
- package/dist/commands/internal-refresh.js +136 -0
- package/dist/commands/internal-session-nudge.js +120 -0
- package/dist/commands/internal-steer-sync.js +117 -0
- package/dist/commands/internal-turn-recap.js +140 -0
- package/dist/commands/kb.js +375 -0
- package/dist/commands/kb_add.js +681 -0
- package/dist/commands/kb_forget.js +283 -0
- package/dist/commands/kb_move.js +45 -0
- package/dist/commands/kb_pending.js +410 -0
- package/dist/commands/kb_personal.js +149 -0
- package/dist/commands/kb_promote.js +188 -0
- package/dist/commands/kb_purge.js +168 -0
- package/dist/commands/kb_reingest.js +335 -0
- package/dist/commands/kb_retime.js +170 -0
- package/dist/commands/kb_review.js +391 -0
- package/dist/commands/kb_revision.js +179 -0
- package/dist/commands/kb_show.js +385 -0
- package/dist/commands/label.js +226 -0
- package/dist/commands/login.js +295 -0
- package/dist/commands/logout.js +108 -0
- package/dist/commands/mcp-supervisor.js +93 -0
- package/dist/commands/mcp.js +227 -0
- package/dist/commands/queue-prune.js +98 -0
- package/dist/commands/review.js +358 -0
- package/dist/commands/rewire.js +124 -0
- package/dist/commands/rules.js +728 -0
- package/dist/commands/scan-context.js +67 -0
- package/dist/commands/session.js +347 -0
- package/dist/commands/stats.js +479 -0
- package/dist/commands/status.js +61 -0
- package/dist/commands/summary.js +250 -0
- package/dist/commands/turn.js +114 -0
- package/dist/commands/uninstall.js +222 -0
- package/dist/commands/whoami.js +102 -0
- package/dist/commands/workspace.js +130 -0
- package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
- package/dist/hooks-template/ce0-session-start.sh +49 -0
- package/dist/hooks-template/ce0-stop.sh +29 -0
- package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
- package/dist/hooks-template/common.sh +934 -0
- package/dist/hooks-template/event-batch-filter.jq +67 -0
- package/dist/hooks-template/flush.sh +503 -0
- package/dist/hooks-template/post-tool-use.sh +423 -0
- package/dist/hooks-template/pre-tool-use.sh +69 -0
- package/dist/hooks-template/session-start.sh +140 -0
- package/dist/hooks-template/stop.sh +308 -0
- package/dist/hooks-template/user-prompt-submit.sh +1162 -0
- package/dist/lib/activation.js +79 -0
- package/dist/lib/active-conflict-cache.js +141 -0
- package/dist/lib/active-memory.js +59 -0
- package/dist/lib/active-review-runner.js +26 -0
- package/dist/lib/agent-decision/index.js +25 -0
- package/dist/lib/agent-decision/keys.js +49 -0
- package/dist/lib/agent-decision/normalize-claude.js +183 -0
- package/dist/lib/agent-decision/types.js +21 -0
- package/dist/lib/agent-decision/validate.js +216 -0
- package/dist/lib/analytics/capture.js +96 -0
- package/dist/lib/analytics/command-event.js +267 -0
- package/dist/lib/analytics/consent.js +58 -0
- package/dist/lib/analytics/coverage-gap.js +96 -0
- package/dist/lib/analytics/envelope.js +236 -0
- package/dist/lib/analytics/event-id.js +86 -0
- package/dist/lib/analytics/evidence.js +150 -0
- package/dist/lib/analytics/followthrough.js +194 -0
- package/dist/lib/analytics/forwarder.js +109 -0
- package/dist/lib/analytics/logs.js +78 -0
- package/dist/lib/analytics/metrics.js +78 -0
- package/dist/lib/analytics/recorder.js +92 -0
- package/dist/lib/analytics/review-analytics.js +75 -0
- package/dist/lib/analytics/sequence.js +77 -0
- package/dist/lib/analytics/store.js +131 -0
- package/dist/lib/analytics/turn-recap.js +279 -0
- package/dist/lib/artifact_id.js +108 -0
- package/dist/lib/auth-breaker.js +161 -0
- package/dist/lib/auto-index.js +112 -0
- package/dist/lib/classifier.js +88 -0
- package/dist/lib/config.js +298 -0
- package/dist/lib/conflict-advisory.js +64 -0
- package/dist/lib/debug-bundle.js +520 -0
- package/dist/lib/enrichment/ingest.js +301 -0
- package/dist/lib/enrichment/plan.js +253 -0
- package/dist/lib/enrichment/protocol.js +359 -0
- package/dist/lib/enrichment/scout-brief.js +176 -0
- package/dist/lib/failure-telemetry.js +444 -0
- package/dist/lib/git.js +200 -0
- package/dist/lib/governance-cache.js +77 -0
- package/dist/lib/governed-path-cache.js +76 -0
- package/dist/lib/http.js +677 -0
- package/dist/lib/identity-envelope.js +23 -0
- package/dist/lib/kb-candidate.js +65 -0
- package/dist/lib/kb_acl.js +98 -0
- package/dist/lib/login.js +353 -0
- package/dist/lib/mcp-fetchers.js +130 -0
- package/dist/lib/mcp-restart.js +47 -0
- package/dist/lib/observability.js +805 -0
- package/dist/lib/open-url.js +33 -0
- package/dist/lib/orphan-guard.js +70 -0
- package/dist/lib/packaged.js +21 -0
- package/dist/lib/reconcile-sessions.js +171 -0
- package/dist/lib/redactor.js +89 -0
- package/dist/lib/relationship-candidate-query.js +27 -0
- package/dist/lib/render.js +611 -0
- package/dist/lib/rules/applicability.js +64 -0
- package/dist/lib/rules/attest-code-rule-version.js +47 -0
- package/dist/lib/rules/attest-notes-location.js +217 -0
- package/dist/lib/rules/attest-rule-version.js +69 -0
- package/dist/lib/rules/canonical-json.js +97 -0
- package/dist/lib/rules/ce0-emit.js +64 -0
- package/dist/lib/rules/ce0-evidence.js +281 -0
- package/dist/lib/rules/ce0-recall-sample.js +82 -0
- package/dist/lib/rules/ce0-rule.js +55 -0
- package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
- package/dist/lib/rules/ce0-store.js +683 -0
- package/dist/lib/rules/ce0-telemetry-project.js +93 -0
- package/dist/lib/rules/ce0-telemetry.js +158 -0
- package/dist/lib/rules/code-rule-registry.js +17 -0
- package/dist/lib/rules/command-match.js +185 -0
- package/dist/lib/rules/consult-evidence-binding.js +27 -0
- package/dist/lib/rules/consultation-capture-adapter.js +193 -0
- package/dist/lib/rules/content-match.js +56 -0
- package/dist/lib/rules/deny-admission.js +99 -0
- package/dist/lib/rules/durable-observation.js +190 -0
- package/dist/lib/rules/enforce-notes-version.js +421 -0
- package/dist/lib/rules/evaluation-input-hash.js +126 -0
- package/dist/lib/rules/evaluator.js +108 -0
- package/dist/lib/rules/inert-rule-families.js +51 -0
- package/dist/lib/rules/input-authority-resolver.js +241 -0
- package/dist/lib/rules/interception-schema.js +170 -0
- package/dist/lib/rules/interception-store.js +267 -0
- package/dist/lib/rules/live-input-authority.js +66 -0
- package/dist/lib/rules/local-matcher.js +108 -0
- package/dist/lib/rules/local-observe.js +79 -0
- package/dist/lib/rules/local-rule-version-repo.js +214 -0
- package/dist/lib/rules/memory-requirement.js +109 -0
- package/dist/lib/rules/notes-observe.js +39 -0
- package/dist/lib/rules/notes-path.js +261 -0
- package/dist/lib/rules/notes-rule.js +75 -0
- package/dist/lib/rules/observe-adapter.js +114 -0
- package/dist/lib/rules/observed-rule-hash.js +119 -0
- package/dist/lib/rules/prompt-submit-adapter.js +132 -0
- package/dist/lib/rules/requirement-subject.js +240 -0
- package/dist/lib/rules/rule-activity.js +67 -0
- package/dist/lib/rules/rule-version-hash.js +151 -0
- package/dist/lib/rules/runtime-scope.js +55 -0
- package/dist/lib/rules/stop-adapter.js +116 -0
- package/dist/lib/rules/stop-response-snapshot.js +174 -0
- package/dist/lib/rules/types.js +10 -0
- package/dist/lib/rules/ulid.js +46 -0
- package/dist/lib/rules/version-evaluation.js +156 -0
- package/dist/lib/scanner/agent-memory.js +99 -0
- package/dist/lib/scanner/bootstrap-summary.js +87 -0
- package/dist/lib/scanner/cache.js +59 -0
- package/dist/lib/scanner/frontmatter.js +42 -0
- package/dist/lib/scanner/parse-directives.js +69 -0
- package/dist/lib/scanner/parse-structured.js +72 -0
- package/dist/lib/scanner/render.js +73 -0
- package/dist/lib/scanner/scan.js +132 -0
- package/dist/lib/scanner/score.js +38 -0
- package/dist/lib/scanner/scout-mission.js +126 -0
- package/dist/lib/scanner/types.js +7 -0
- package/dist/lib/session-scope.js +195 -0
- package/dist/lib/spool.js +355 -0
- package/dist/lib/staleness.js +100 -0
- package/dist/lib/steer-cache.js +87 -0
- package/dist/lib/tagged-reference.js +20 -0
- package/dist/lib/temporal.js +109 -0
- package/dist/lib/turn-recap-emit.js +67 -0
- package/dist/lib/unwire.js +253 -0
- package/dist/lib/update-check.js +469 -0
- package/dist/lib/update-notifier.js +217 -0
- package/dist/lib/upgrade-apply.js +643 -0
- package/dist/lib/wire.js +1087 -0
- package/dist/lib/workspace.js +96 -0
- package/dist/lib/zip.js +154 -0
- package/dist/pretool-entry.js +37 -0
- 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
|
+
}
|