martin-loop 0.1.4 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CODE_OF_CONDUCT.md +32 -0
- package/README.md +172 -227
- package/demo/seeded-workspace/README.md +35 -0
- package/demo/seeded-workspace/TASKS.md +29 -0
- package/demo/seeded-workspace/martin.config.yaml +11 -0
- package/demo/seeded-workspace/package.json +8 -0
- package/demo/seeded-workspace/src/invoice-summary.js +11 -0
- package/demo/seeded-workspace/test/invoice-summary.test.js +20 -0
- package/dist/bin/martin-loop.js +0 -0
- package/dist/vendor/adapters/claude-cli.d.ts +19 -4
- package/dist/vendor/adapters/claude-cli.js +55 -24
- package/dist/vendor/adapters/cli-bridge.d.ts +1 -0
- package/dist/vendor/adapters/cli-bridge.js +154 -28
- package/dist/vendor/adapters/counter.d.ts +1 -0
- package/dist/vendor/adapters/counter.js +4 -0
- package/dist/vendor/adapters/git-baseline.d.ts +50 -0
- package/dist/vendor/adapters/git-baseline.js +233 -0
- package/dist/vendor/adapters/index.d.ts +1 -0
- package/dist/vendor/adapters/index.js +1 -0
- package/dist/vendor/adapters/openrouter-adapter.d.ts +15 -0
- package/dist/vendor/adapters/openrouter-adapter.js +302 -0
- package/dist/vendor/adapters/usage.d.ts +48 -0
- package/dist/vendor/adapters/usage.js +66 -0
- package/dist/vendor/adapters/verifier-only.d.ts +7 -0
- package/dist/vendor/adapters/verifier-only.js +57 -0
- package/dist/vendor/cli/bin/exit.d.ts +12 -0
- package/dist/vendor/cli/bin/exit.js +28 -0
- package/dist/vendor/cli/commands/analyze.d.ts +5 -0
- package/dist/vendor/cli/commands/analyze.js +58 -0
- package/dist/vendor/cli/commands/audit-log-verify.d.ts +34 -0
- package/dist/vendor/cli/commands/audit-log-verify.js +99 -0
- package/dist/vendor/cli/commands/audit.d.ts +8 -0
- package/dist/vendor/cli/commands/audit.js +199 -0
- package/dist/vendor/cli/commands/corpus.d.ts +5 -0
- package/dist/vendor/cli/commands/corpus.js +60 -0
- package/dist/vendor/cli/commands/doctor.d.ts +8 -0
- package/dist/vendor/cli/commands/doctor.js +219 -0
- package/dist/vendor/cli/commands/explain.d.ts +17 -0
- package/dist/vendor/cli/commands/explain.js +176 -0
- package/dist/vendor/cli/commands/export.d.ts +5 -0
- package/dist/vendor/cli/commands/export.js +60 -0
- package/dist/vendor/cli/commands/governance.d.ts +8 -0
- package/dist/vendor/cli/commands/governance.js +95 -0
- package/dist/vendor/cli/commands/improve.d.ts +18 -0
- package/dist/vendor/cli/commands/improve.js +396 -0
- package/dist/vendor/cli/commands/init.d.ts +8 -0
- package/dist/vendor/cli/commands/init.js +281 -0
- package/dist/vendor/cli/commands/migration.d.ts +8 -0
- package/dist/vendor/cli/commands/migration.js +67 -0
- package/dist/vendor/cli/commands/prior.d.ts +23 -0
- package/dist/vendor/cli/commands/prior.js +145 -0
- package/dist/vendor/cli/commands/resume.d.ts +21 -0
- package/dist/vendor/cli/commands/resume.js +73 -0
- package/dist/vendor/cli/commands/verify.d.ts +6 -0
- package/dist/vendor/cli/commands/verify.js +43 -0
- package/dist/vendor/cli/index.d.ts +6 -1
- package/dist/vendor/cli/index.js +124 -7
- package/dist/vendor/cli/research/public-corpus.d.ts +43 -0
- package/dist/vendor/cli/research/public-corpus.js +151 -0
- package/dist/vendor/cli/ui/error-card.d.ts +38 -0
- package/dist/vendor/cli/ui/error-card.js +103 -0
- package/dist/vendor/cli/ui/mission-brief.d.ts +41 -0
- package/dist/vendor/cli/ui/mission-brief.js +173 -0
- package/dist/vendor/cli/ui/summary-card.d.ts +34 -0
- package/dist/vendor/cli/ui/summary-card.js +102 -0
- package/dist/vendor/contracts/audit.d.ts +46 -0
- package/dist/vendor/contracts/audit.js +360 -0
- package/dist/vendor/contracts/index.d.ts +3 -1
- package/dist/vendor/contracts/post-phase15.d.ts +240 -0
- package/dist/vendor/contracts/post-phase15.js +166 -0
- package/dist/vendor/core/agent/mandates.d.ts +46 -0
- package/dist/vendor/core/agent/mandates.js +178 -0
- package/dist/vendor/core/agent/receipts.d.ts +38 -0
- package/dist/vendor/core/agent/receipts.js +131 -0
- package/dist/vendor/core/agent/signing.d.ts +17 -0
- package/dist/vendor/core/agent/signing.js +91 -0
- package/dist/vendor/core/attestation/sign.d.ts +25 -0
- package/dist/vendor/core/attestation/sign.js +216 -0
- package/dist/vendor/core/autonomy/autonomous-promotion.d.ts +120 -0
- package/dist/vendor/core/autonomy/autonomous-promotion.js +346 -0
- package/dist/vendor/core/autonomy/envelope-v2.d.ts +29 -0
- package/dist/vendor/core/autonomy/envelope-v2.js +60 -0
- package/dist/vendor/core/autonomy/envelope.d.ts +17 -0
- package/dist/vendor/core/autonomy/envelope.js +27 -0
- package/dist/vendor/core/autonomy/escalation-ledger.d.ts +20 -0
- package/dist/vendor/core/autonomy/escalation-ledger.js +18 -0
- package/dist/vendor/core/autonomy/resume.d.ts +15 -0
- package/dist/vendor/core/autonomy/resume.js +23 -0
- package/dist/vendor/core/circuit/circuit-breaker.d.ts +60 -0
- package/dist/vendor/core/circuit/circuit-breaker.js +143 -0
- package/dist/vendor/core/compiler.d.ts +2 -0
- package/dist/vendor/core/compiler.js +10 -4
- package/dist/vendor/core/context-distillation.d.ts +3 -0
- package/dist/vendor/core/context-distillation.js +44 -0
- package/dist/vendor/core/context-flow/compile-context.d.ts +8 -0
- package/dist/vendor/core/context-flow/compile-context.js +111 -0
- package/dist/vendor/core/context-flow/entities.d.ts +2 -0
- package/dist/vendor/core/context-flow/entities.js +44 -0
- package/dist/vendor/core/context-flow/evaluate-policy.d.ts +2 -0
- package/dist/vendor/core/context-flow/evaluate-policy.js +42 -0
- package/dist/vendor/core/context-flow/index.d.ts +11 -0
- package/dist/vendor/core/context-flow/index.js +24 -0
- package/dist/vendor/core/context-flow/labels.d.ts +3 -0
- package/dist/vendor/core/context-flow/labels.js +17 -0
- package/dist/vendor/core/context-flow/normalizer.d.ts +9 -0
- package/dist/vendor/core/context-flow/normalizer.js +69 -0
- package/dist/vendor/core/context-flow/profiles.d.ts +33 -0
- package/dist/vendor/core/context-flow/profiles.js +36 -0
- package/dist/vendor/core/context-flow/redaction.d.ts +1 -0
- package/dist/vendor/core/context-flow/redaction.js +6 -0
- package/dist/vendor/core/context-flow/sensitivity.d.ts +2 -0
- package/dist/vendor/core/context-flow/sensitivity.js +27 -0
- package/dist/vendor/core/context-flow/sync-preview.d.ts +2 -0
- package/dist/vendor/core/context-flow/sync-preview.js +22 -0
- package/dist/vendor/core/context-flow/token-estimator.d.ts +3 -0
- package/dist/vendor/core/context-flow/token-estimator.js +13 -0
- package/dist/vendor/core/context-flow/types.d.ts +91 -0
- package/dist/vendor/core/context-flow/types.js +2 -0
- package/dist/vendor/core/context-integrity.d.ts +26 -0
- package/dist/vendor/core/context-integrity.js +56 -0
- package/dist/vendor/core/context-utility.d.ts +47 -0
- package/dist/vendor/core/context-utility.js +405 -0
- package/dist/vendor/core/cost/pipeline.d.ts +92 -0
- package/dist/vendor/core/cost/pipeline.js +141 -0
- package/dist/vendor/core/cost/tagged-cost.d.ts +27 -0
- package/dist/vendor/core/cost/tagged-cost.js +55 -0
- package/dist/vendor/core/cost-governor.d.ts +2 -0
- package/dist/vendor/core/cost-governor.js +50 -0
- package/dist/vendor/core/cve/cve-check.d.ts +80 -0
- package/dist/vendor/core/cve/cve-check.js +172 -0
- package/dist/vendor/core/digital-twin/index.d.ts +27 -0
- package/dist/vendor/core/digital-twin/index.js +90 -0
- package/dist/vendor/core/drift/drift-graph.d.ts +47 -0
- package/dist/vendor/core/drift/drift-graph.js +100 -0
- package/dist/vendor/core/drift/objective-lock.d.ts +69 -0
- package/dist/vendor/core/drift/objective-lock.js +88 -0
- package/dist/vendor/core/drift/scope.d.ts +46 -0
- package/dist/vendor/core/drift/scope.js +102 -0
- package/dist/vendor/core/drift/signature-lock.d.ts +48 -0
- package/dist/vendor/core/drift/signature-lock.js +202 -0
- package/dist/vendor/core/drift/stale-proof-gate.d.ts +21 -0
- package/dist/vendor/core/drift/stale-proof-gate.js +19 -0
- package/dist/vendor/core/eval/known-bad-world-runner.d.ts +24 -0
- package/dist/vendor/core/eval/known-bad-world-runner.js +256 -0
- package/dist/vendor/core/evidence/claim-audit.d.ts +18 -0
- package/dist/vendor/core/evidence/claim-audit.js +89 -0
- package/dist/vendor/core/exit-intelligence.d.ts +2 -0
- package/dist/vendor/core/exit-intelligence.js +58 -0
- package/dist/vendor/core/explain/formatter.d.ts +42 -0
- package/dist/vendor/core/explain/formatter.js +171 -0
- package/dist/vendor/core/explain/timeline.d.ts +29 -0
- package/dist/vendor/core/explain/timeline.js +213 -0
- package/dist/vendor/core/failure-taxonomy.d.ts +2 -0
- package/dist/vendor/core/failure-taxonomy.js +76 -0
- package/dist/vendor/core/gateway/index.d.ts +10 -0
- package/dist/vendor/core/gateway/index.js +12 -0
- package/dist/vendor/core/gateway/registry.d.ts +40 -0
- package/dist/vendor/core/gateway/registry.js +97 -0
- package/dist/vendor/core/gateway/transport.d.ts +31 -0
- package/dist/vendor/core/gateway/transport.js +82 -0
- package/dist/vendor/core/gateway/vault.d.ts +19 -0
- package/dist/vendor/core/gateway/vault.js +29 -0
- package/dist/vendor/core/graph/adapters.d.ts +43 -0
- package/dist/vendor/core/graph/adapters.js +91 -0
- package/dist/vendor/core/graph/hotspots.d.ts +22 -0
- package/dist/vendor/core/graph/hotspots.js +30 -0
- package/dist/vendor/core/graph/index.d.ts +1 -0
- package/dist/vendor/core/graph/index.js +2 -0
- package/dist/vendor/core/honey/honey-tokens.d.ts +32 -0
- package/dist/vendor/core/honey/honey-tokens.js +44 -0
- package/dist/vendor/core/index.d.ts +7 -4
- package/dist/vendor/core/index.js +222 -64
- package/dist/vendor/core/learning/bayesian-update.d.ts +31 -0
- package/dist/vendor/core/learning/bayesian-update.js +60 -0
- package/dist/vendor/core/learning/prior-sets.d.ts +42 -0
- package/dist/vendor/core/learning/prior-sets.js +111 -0
- package/dist/vendor/core/learning/promotion-gate.d.ts +17 -0
- package/dist/vendor/core/learning/promotion-gate.js +23 -0
- package/dist/vendor/core/leash/blast-radius.d.ts +42 -0
- package/dist/vendor/core/leash/blast-radius.js +156 -0
- package/dist/vendor/core/leash/policy-leash.d.ts +31 -0
- package/dist/vendor/core/leash/policy-leash.js +117 -0
- package/dist/vendor/core/memo/memo.d.ts +63 -0
- package/dist/vendor/core/memo/memo.js +97 -0
- package/dist/vendor/core/memory/learning-pipeline.d.ts +154 -0
- package/dist/vendor/core/memory/learning-pipeline.js +391 -0
- package/dist/vendor/core/memory/palace.d.ts +84 -0
- package/dist/vendor/core/memory/palace.js +379 -0
- package/dist/vendor/core/merge/ast-merge.d.ts +22 -0
- package/dist/vendor/core/merge/ast-merge.js +350 -0
- package/dist/vendor/core/merge/text-merge.d.ts +12 -0
- package/dist/vendor/core/merge/text-merge.js +182 -0
- package/dist/vendor/core/otel/tracer.d.ts +45 -0
- package/dist/vendor/core/otel/tracer.js +116 -0
- package/dist/vendor/core/parallel/parallel-attempts.d.ts +28 -0
- package/dist/vendor/core/parallel/parallel-attempts.js +41 -0
- package/dist/vendor/core/parallel/scorer.d.ts +24 -0
- package/dist/vendor/core/parallel/scorer.js +65 -0
- package/dist/vendor/core/pattern-detection.d.ts +64 -0
- package/dist/vendor/core/pattern-detection.js +108 -0
- package/dist/vendor/core/persistence/checkpoint.d.ts +44 -0
- package/dist/vendor/core/persistence/checkpoint.js +156 -0
- package/dist/vendor/core/persistence/cleanup.d.ts +22 -0
- package/dist/vendor/core/persistence/cleanup.js +131 -0
- package/dist/vendor/core/persistence/index.d.ts +2 -0
- package/dist/vendor/core/persistence/index.js +1 -0
- package/dist/vendor/core/persistence/runs-reader.d.ts +52 -0
- package/dist/vendor/core/persistence/runs-reader.js +84 -0
- package/dist/vendor/core/persistence/store.d.ts +6 -1
- package/dist/vendor/core/persistence/store.js +5 -0
- package/dist/vendor/core/policy/file-touch-quota.d.ts +60 -0
- package/dist/vendor/core/policy/file-touch-quota.js +105 -0
- package/dist/vendor/core/policy/policy-loader.d.ts +30 -0
- package/dist/vendor/core/policy/policy-loader.js +170 -0
- package/dist/vendor/core/policy/policy-schema.d.ts +55 -0
- package/dist/vendor/core/policy/policy-schema.js +78 -0
- package/dist/vendor/core/policy.d.ts +6 -0
- package/dist/vendor/core/probe/probe.d.ts +49 -0
- package/dist/vendor/core/probe/probe.js +115 -0
- package/dist/vendor/core/proof/patch-proof.d.ts +58 -0
- package/dist/vendor/core/proof/patch-proof.js +84 -0
- package/dist/vendor/core/proof/semantic-probe.d.ts +25 -0
- package/dist/vendor/core/proof/semantic-probe.js +82 -0
- package/dist/vendor/core/recovery/failure-mode-runner.d.ts +29 -0
- package/dist/vendor/core/recovery/failure-mode-runner.js +39 -0
- package/dist/vendor/core/red-blue/red-phase.d.ts +64 -0
- package/dist/vendor/core/red-blue/red-phase.js +141 -0
- package/dist/vendor/core/red-blue/risk-tiers.d.ts +22 -0
- package/dist/vendor/core/red-blue/risk-tiers.js +33 -0
- package/dist/vendor/core/replay/replay.d.ts +85 -0
- package/dist/vendor/core/replay/replay.js +109 -0
- package/dist/vendor/core/router/engine.d.ts +54 -0
- package/dist/vendor/core/router/engine.js +131 -0
- package/dist/vendor/core/router/index.d.ts +1 -0
- package/dist/vendor/core/router/index.js +2 -0
- package/dist/vendor/core/router/trust-calibration.d.ts +57 -0
- package/dist/vendor/core/router/trust-calibration.js +127 -0
- package/dist/vendor/core/run-martin.d.ts +2 -0
- package/dist/vendor/core/run-martin.js +287 -0
- package/dist/vendor/core/security/cve-scanner.d.ts +62 -0
- package/dist/vendor/core/security/cve-scanner.js +178 -0
- package/dist/vendor/core/sentinel/efficiency-sentinel.d.ts +29 -0
- package/dist/vendor/core/sentinel/efficiency-sentinel.js +30 -0
- package/dist/vendor/core/sentinel/progress-guard.d.ts +35 -0
- package/dist/vendor/core/sentinel/progress-guard.js +46 -0
- package/dist/vendor/core/siem/siem-emitter.d.ts +49 -0
- package/dist/vendor/core/siem/siem-emitter.js +157 -0
- package/dist/vendor/core/strategy/attempt-brief.d.ts +22 -0
- package/dist/vendor/core/strategy/attempt-brief.js +89 -0
- package/dist/vendor/core/summarize/diff-summary.d.ts +35 -0
- package/dist/vendor/core/summarize/diff-summary.js +204 -0
- package/dist/vendor/core/surface-signals.d.ts +21 -0
- package/dist/vendor/core/surface-signals.js +139 -0
- package/dist/vendor/core/truth/truth-wall.d.ts +51 -0
- package/dist/vendor/core/truth/truth-wall.js +69 -0
- package/dist/vendor/core/truth-spine.d.ts +26 -0
- package/dist/vendor/core/truth-spine.js +62 -0
- package/dist/vendor/core/types.d.ts +115 -0
- package/dist/vendor/core/types.js +2 -0
- package/dist/vendor/core/verification/tiered-verify.d.ts +17 -0
- package/dist/vendor/core/verification/tiered-verify.js +29 -0
- package/dist/vendor/core/verifier-pyramid.d.ts +32 -0
- package/dist/vendor/core/verifier-pyramid.js +111 -0
- package/dist/vendor/core/workflow-artifacts.d.ts +99 -0
- package/dist/vendor/core/workflow-artifacts.js +668 -0
- package/dist/vendor/core/wrap/supervised-run.d.ts +96 -0
- package/dist/vendor/core/wrap/supervised-run.js +178 -0
- package/docs/assets/cli-animated.svg +139 -0
- package/docs/assets/cli-static.svg +34 -0
- package/docs/assets/github-hero-v2.svg +23 -0
- package/docs/assets/martin-raplph.png.jpg +0 -0
- package/docs/assets/martinloop-logo.png +0 -0
- package/docs/assets/nvidia-inception-program-light.png +0 -0
- package/docs/assets/nvidia-inception-program.png +0 -0
- package/docs/assets/phase3c-sidesidebyside-demo.html +228 -0
- package/docs/assets/side-by-side.svg +134 -0
- package/docs/oss/CLAUDE-CODE-WALKTHROUGH.md +142 -0
- package/docs/oss/EXAMPLES.md +9 -1
- package/docs/oss/OSS-BOUNDARY-REPORT.json +109 -113
- package/docs/oss/OSS-BOUNDARY-REPORT.md +48 -48
- package/docs/oss/QUICKSTART.md +39 -4
- package/docs/oss/RALPH-LOOP-SAFETY.md +113 -0
- package/docs/oss/README.md +7 -4
- package/docs/oss/RELEASE-SURFACE-REPORT.json +46 -45
- package/docs/oss/RELEASE-SURFACE-REPORT.md +36 -35
- package/package.json +129 -49
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* diff-summary.ts
|
|
3
|
+
*
|
|
4
|
+
* Produces a one-line human-readable summary of a unified diff relative to the
|
|
5
|
+
* loop objective. Uses a cheap haiku-tier LLM call when an adapter is available;
|
|
6
|
+
* falls back to a deterministic heuristic when no adapter is configured or the
|
|
7
|
+
* model call fails.
|
|
8
|
+
*
|
|
9
|
+
* Quality gate: the generated one-liner MUST contain at least one concrete
|
|
10
|
+
* identifier (file name, function name, symbol) extracted from the diff itself.
|
|
11
|
+
* If the model output fails that gate the fallback is used instead.
|
|
12
|
+
*/
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Public entry point
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/**
|
|
17
|
+
* Summarize a unified diff in the context of a loop objective.
|
|
18
|
+
*
|
|
19
|
+
* @param input Diff text + objective string.
|
|
20
|
+
* @param callModelFn Optional async function that accepts a prompt string and
|
|
21
|
+
* returns a model response string. When omitted, or when
|
|
22
|
+
* the call throws, the deterministic heuristic is used.
|
|
23
|
+
*/
|
|
24
|
+
export async function summarizeDiff(input, callModelFn) {
|
|
25
|
+
const anchor = extractAnchorIdentifier(input.diff);
|
|
26
|
+
if (callModelFn && input.diff.trim().length > 0) {
|
|
27
|
+
try {
|
|
28
|
+
const raw = await callModelFn(buildPrompt(input, anchor));
|
|
29
|
+
const cleaned = cleanModelOutput(raw);
|
|
30
|
+
if (isQualityGatePassing(cleaned, anchor)) {
|
|
31
|
+
return { oneLiner: cleaned, method: "model", anchorIdentifier: anchor };
|
|
32
|
+
}
|
|
33
|
+
// Model output failed quality gate — fall through to heuristic
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Non-fatal — fall through to heuristic
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
oneLiner: buildHeuristicSummary(input, anchor),
|
|
41
|
+
method: "heuristic",
|
|
42
|
+
anchorIdentifier: anchor
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Prompt construction
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
function buildPrompt(input, anchor) {
|
|
49
|
+
// Keep the diff excerpt short — we only need enough for the model to name
|
|
50
|
+
// the primary change. Hard cap at 3000 chars to stay within haiku context.
|
|
51
|
+
const diffExcerpt = input.diff.length > 3000
|
|
52
|
+
? `${input.diff.slice(0, 2900)}\n... (truncated)`
|
|
53
|
+
: input.diff;
|
|
54
|
+
return [
|
|
55
|
+
"You are a precise technical writer generating a single-line git commit summary.",
|
|
56
|
+
"",
|
|
57
|
+
`Objective: ${input.objective}`,
|
|
58
|
+
"",
|
|
59
|
+
"Diff:",
|
|
60
|
+
"```",
|
|
61
|
+
diffExcerpt,
|
|
62
|
+
"```",
|
|
63
|
+
"",
|
|
64
|
+
`The summary MUST mention "${anchor}" or a closely related identifier.`,
|
|
65
|
+
"Respond with ONLY the one-line summary — no preamble, no punctuation at the end.",
|
|
66
|
+
"Keep it under 72 characters.",
|
|
67
|
+
"Format: <verb in imperative mood> <what changed> in <where>",
|
|
68
|
+
"Example: add retry guard to fetchWithTimeout in http-client.ts"
|
|
69
|
+
].join("\n");
|
|
70
|
+
}
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Quality gate
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
/**
|
|
75
|
+
* Returns true when the model output contains the anchor identifier or a
|
|
76
|
+
* plausible stem of it (e.g. "retry" from "retryCount").
|
|
77
|
+
*/
|
|
78
|
+
function isQualityGatePassing(summary, anchor) {
|
|
79
|
+
if (summary.trim().length === 0)
|
|
80
|
+
return false;
|
|
81
|
+
if (summary.length > 120)
|
|
82
|
+
return false; // unreasonably long
|
|
83
|
+
const lowerSummary = summary.toLowerCase();
|
|
84
|
+
const lowerAnchor = anchor.toLowerCase();
|
|
85
|
+
// Direct match
|
|
86
|
+
if (lowerSummary.includes(lowerAnchor))
|
|
87
|
+
return true;
|
|
88
|
+
// Stem match: try the first 6+ chars of the anchor
|
|
89
|
+
const stem = lowerAnchor.slice(0, Math.max(6, Math.floor(lowerAnchor.length * 0.6)));
|
|
90
|
+
if (stem.length >= 4 && lowerSummary.includes(stem))
|
|
91
|
+
return true;
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Deterministic heuristic
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
/**
|
|
98
|
+
* Builds a summary without a model call by inspecting the diff structure.
|
|
99
|
+
* Always passes the quality gate because we embed the anchor directly.
|
|
100
|
+
*/
|
|
101
|
+
function buildHeuristicSummary(input, anchor) {
|
|
102
|
+
const stats = parseDiffStats(input.diff);
|
|
103
|
+
const verb = selectVerb(stats);
|
|
104
|
+
const filesChanged = stats.files.length;
|
|
105
|
+
if (filesChanged === 0) {
|
|
106
|
+
return `${verb} changes related to ${anchor}`;
|
|
107
|
+
}
|
|
108
|
+
if (filesChanged === 1) {
|
|
109
|
+
const file = stats.files[0] ?? "change";
|
|
110
|
+
const location = stripPath(file);
|
|
111
|
+
return `${verb} ${anchor} in ${location}`;
|
|
112
|
+
}
|
|
113
|
+
const primary = stripPath(stats.files[0] ?? "change");
|
|
114
|
+
return `${verb} ${anchor} across ${filesChanged} files (primary: ${primary})`;
|
|
115
|
+
}
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Anchor identifier extraction
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
/**
|
|
120
|
+
* Extracts the most informative concrete identifier from a unified diff.
|
|
121
|
+
*
|
|
122
|
+
* Priority order:
|
|
123
|
+
* 1. First function/method name from a hunk header (@@...@@ <name>)
|
|
124
|
+
* 2. First added symbol-looking token from a + line
|
|
125
|
+
* 3. First changed file base name (no extension)
|
|
126
|
+
* 4. Literal "change" as a safe fallback
|
|
127
|
+
*/
|
|
128
|
+
function extractAnchorIdentifier(diff) {
|
|
129
|
+
// 1. Hunk header function hint:
|
|
130
|
+
// @@ -l,n +l,n @@ function validateToken(
|
|
131
|
+
const hunkHeaderFn = diff.match(/@@[^@]+@@\s+(?:(?:export|async)\s+)*(?:function\s+)?(\w[\w.]*)\s*\(/u);
|
|
132
|
+
if (hunkHeaderFn?.[1] && hunkHeaderFn[1].length >= 3) {
|
|
133
|
+
return hunkHeaderFn[1];
|
|
134
|
+
}
|
|
135
|
+
// 2. First added function/method-like declaration. This intentionally
|
|
136
|
+
// precedes local const extraction so a method body's first local variable
|
|
137
|
+
// does not become the summary anchor.
|
|
138
|
+
const addedCallable = diff.match(/^\+[^+].*?\b(?:export\s+)?(?:async\s+)?(?:function\s+)?(\w[\w]*)\s*\(/mu);
|
|
139
|
+
if (addedCallable?.[1] && addedCallable[1].length >= 3) {
|
|
140
|
+
return addedCallable[1];
|
|
141
|
+
}
|
|
142
|
+
// 3. First export/const/class/type on an added line
|
|
143
|
+
const addedSymbol = diff.match(/^\+[^+].*?\b(?:export\s+)?(?:const|let|var|class|interface|type)\s+(\w[\w]*)/mu);
|
|
144
|
+
if (addedSymbol?.[1] && addedSymbol[1].length >= 3) {
|
|
145
|
+
return addedSymbol[1];
|
|
146
|
+
}
|
|
147
|
+
// 4. First changed file name from diff --git a/<path> b/<path>
|
|
148
|
+
const fileMatch = diff.match(/^diff --git a\/(.+?) b\//mu);
|
|
149
|
+
if (fileMatch?.[1]) {
|
|
150
|
+
const base = fileMatch[1].split("/").pop() ?? fileMatch[1];
|
|
151
|
+
return base.replace(/\.\w+$/, ""); // strip extension
|
|
152
|
+
}
|
|
153
|
+
// 5. First +++ b/ file name
|
|
154
|
+
const plusFile = diff.match(/^\+\+\+ b\/(.+)$/mu);
|
|
155
|
+
if (plusFile?.[1]) {
|
|
156
|
+
const base = plusFile[1].split("/").pop() ?? plusFile[1];
|
|
157
|
+
return base.replace(/\.\w+$/, "");
|
|
158
|
+
}
|
|
159
|
+
return "change";
|
|
160
|
+
}
|
|
161
|
+
function parseDiffStats(diff) {
|
|
162
|
+
const files = [];
|
|
163
|
+
let addedLines = 0;
|
|
164
|
+
let removedLines = 0;
|
|
165
|
+
for (const line of diff.split("\n")) {
|
|
166
|
+
if (line.startsWith("diff --git ")) {
|
|
167
|
+
const match = line.match(/^diff --git a\/(.+?) b\//u);
|
|
168
|
+
if (match?.[1])
|
|
169
|
+
files.push(match[1]);
|
|
170
|
+
}
|
|
171
|
+
else if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
172
|
+
addedLines += 1;
|
|
173
|
+
}
|
|
174
|
+
else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
175
|
+
removedLines += 1;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { files, addedLines, removedLines };
|
|
179
|
+
}
|
|
180
|
+
function selectVerb(stats) {
|
|
181
|
+
if (stats.addedLines > 0 && stats.removedLines === 0)
|
|
182
|
+
return "add";
|
|
183
|
+
if (stats.removedLines > 0 && stats.addedLines === 0)
|
|
184
|
+
return "remove";
|
|
185
|
+
if (stats.addedLines > stats.removedLines * 3)
|
|
186
|
+
return "add";
|
|
187
|
+
if (stats.removedLines > stats.addedLines * 3)
|
|
188
|
+
return "remove";
|
|
189
|
+
return "update";
|
|
190
|
+
}
|
|
191
|
+
function stripPath(filePath) {
|
|
192
|
+
return filePath.split("/").pop() ?? filePath;
|
|
193
|
+
}
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// Model output cleanup
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
function cleanModelOutput(raw) {
|
|
198
|
+
return raw
|
|
199
|
+
.trim()
|
|
200
|
+
.replace(/^["']|["']$/g, "") // strip surrounding quotes
|
|
201
|
+
.replace(/\.+$/, "") // strip trailing dots
|
|
202
|
+
.replace(/\s+/g, " "); // collapse whitespace
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=diff-summary.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LoopAttempt } from "../contracts/index.js";
|
|
2
|
+
export interface SurfaceSignalInput {
|
|
3
|
+
objective?: string;
|
|
4
|
+
verificationPlan?: string[];
|
|
5
|
+
summary?: string;
|
|
6
|
+
failureMessage?: string;
|
|
7
|
+
diff?: string;
|
|
8
|
+
changedFiles?: string[];
|
|
9
|
+
previousAttempts?: Array<Pick<LoopAttempt, "summary">>;
|
|
10
|
+
}
|
|
11
|
+
export interface SurfaceSignals {
|
|
12
|
+
dependencyTouchedFileCount: number;
|
|
13
|
+
migrationTouchedFileCount: number;
|
|
14
|
+
mergeConflictSignalCount: number;
|
|
15
|
+
ambiguitySignalCount: number;
|
|
16
|
+
observabilityNoiseSignalCount: number;
|
|
17
|
+
workspaceGraphRiskScore: number;
|
|
18
|
+
crossBoundaryRiskScore: number;
|
|
19
|
+
}
|
|
20
|
+
export declare function analyzeLoopSurface(input: SurfaceSignalInput): SurfaceSignals;
|
|
21
|
+
export declare function buildSurfaceGuidance(input: SurfaceSignalInput): string[];
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
export function analyzeLoopSurface(input) {
|
|
2
|
+
const changedFiles = (input.changedFiles ?? []).map(normalizePath);
|
|
3
|
+
const narrative = [
|
|
4
|
+
input.objective,
|
|
5
|
+
input.summary,
|
|
6
|
+
input.failureMessage,
|
|
7
|
+
input.diff,
|
|
8
|
+
...(input.verificationPlan ?? []),
|
|
9
|
+
...(input.previousAttempts?.map((attempt) => attempt.summary) ?? [])
|
|
10
|
+
]
|
|
11
|
+
.filter(Boolean)
|
|
12
|
+
.join("\n")
|
|
13
|
+
.toLowerCase();
|
|
14
|
+
const dependencyTouchedFileCount = changedFiles.filter(isDependencySurface).length;
|
|
15
|
+
const migrationTouchedFileCount = changedFiles.filter(isMigrationSurface).length;
|
|
16
|
+
const mergeConflictSignalCount = countPhraseMatches(narrative, [
|
|
17
|
+
"merge conflict",
|
|
18
|
+
"conflict marker",
|
|
19
|
+
"conflict markers",
|
|
20
|
+
"rebase",
|
|
21
|
+
"cherry-pick",
|
|
22
|
+
"<<<<<<<",
|
|
23
|
+
">>>>>>>"
|
|
24
|
+
]) + (input.diff?.match(/^(<{7}|={7}|>{7})/gmu)?.length ?? 0);
|
|
25
|
+
const workspaceGraphSignals = countPhraseMatches(narrative, [
|
|
26
|
+
"monorepo",
|
|
27
|
+
"workspace graph",
|
|
28
|
+
"dependency graph",
|
|
29
|
+
"graph-wide",
|
|
30
|
+
"cross-package",
|
|
31
|
+
"package boundaries",
|
|
32
|
+
"downstream packages",
|
|
33
|
+
"transitive dependency",
|
|
34
|
+
"transitive toolchain",
|
|
35
|
+
"pnpm -r",
|
|
36
|
+
"workspace:",
|
|
37
|
+
"package manager",
|
|
38
|
+
"lockfile"
|
|
39
|
+
]) + dependencyTouchedFileCount;
|
|
40
|
+
const crossBoundarySignals = countPhraseMatches(narrative, [
|
|
41
|
+
"cross-system",
|
|
42
|
+
"cross-package",
|
|
43
|
+
"cross-service",
|
|
44
|
+
"service boundary",
|
|
45
|
+
"producer-consumer",
|
|
46
|
+
"contract boundary",
|
|
47
|
+
"data contract",
|
|
48
|
+
"schema drift",
|
|
49
|
+
"external dependency",
|
|
50
|
+
"package boundaries"
|
|
51
|
+
]) + migrationTouchedFileCount;
|
|
52
|
+
const ambiguitySignalCount = countPhraseMatches(narrative, [
|
|
53
|
+
"ambiguous",
|
|
54
|
+
"acceptance target",
|
|
55
|
+
"moving target",
|
|
56
|
+
"moving expectation",
|
|
57
|
+
"underspecified",
|
|
58
|
+
"seed-data divergence",
|
|
59
|
+
"oauth callback",
|
|
60
|
+
"callback loop",
|
|
61
|
+
"unclear ground truth",
|
|
62
|
+
"requirement boundary"
|
|
63
|
+
]);
|
|
64
|
+
const observabilityNoiseSignalCount = countPhraseMatches(narrative, [
|
|
65
|
+
"alert-threshold",
|
|
66
|
+
"alert threshold",
|
|
67
|
+
"threshold-driven",
|
|
68
|
+
"flapping alert",
|
|
69
|
+
"flapping alerts",
|
|
70
|
+
"alert storm",
|
|
71
|
+
"signal-to-noise",
|
|
72
|
+
"noisy alert",
|
|
73
|
+
"noise"
|
|
74
|
+
]);
|
|
75
|
+
return {
|
|
76
|
+
dependencyTouchedFileCount,
|
|
77
|
+
migrationTouchedFileCount,
|
|
78
|
+
mergeConflictSignalCount,
|
|
79
|
+
ambiguitySignalCount,
|
|
80
|
+
observabilityNoiseSignalCount,
|
|
81
|
+
workspaceGraphRiskScore: roundScore(Math.min(1, workspaceGraphSignals / 4)),
|
|
82
|
+
crossBoundaryRiskScore: roundScore(Math.min(1, crossBoundarySignals / 4))
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function buildSurfaceGuidance(input) {
|
|
86
|
+
const signals = analyzeLoopSurface(input);
|
|
87
|
+
const guidance = [];
|
|
88
|
+
if (signals.dependencyTouchedFileCount > 0 || signals.workspaceGraphRiskScore >= 0.5) {
|
|
89
|
+
guidance.push("Treat this as a dependency or workspace-graph boundary issue. Prefer one package or contract seam at a time, verify install/typecheck before broad build fan-out, and avoid graph-wide surgery unless the verifier proves the boundary is local.");
|
|
90
|
+
}
|
|
91
|
+
if (signals.migrationTouchedFileCount > 0 || signals.crossBoundaryRiskScore >= 0.5) {
|
|
92
|
+
guidance.push("Treat migration and data-contract edits as boundary work. Change one producer/consumer or migration seam at a time and verify the contract boundary before broad cleanup.");
|
|
93
|
+
}
|
|
94
|
+
if (signals.mergeConflictSignalCount > 0) {
|
|
95
|
+
guidance.push("Resolve the conflict surface first. Do not stack new edits on top of unresolved conflict markers; refresh the base, resolve the minimal conflicting files, then rerun verification.");
|
|
96
|
+
}
|
|
97
|
+
if (signals.ambiguitySignalCount > 0) {
|
|
98
|
+
guidance.push("Treat this as an acceptance-boundary issue. Freeze one explicit expected behavior, verify the smallest canonical case, and escalate once the target keeps moving.");
|
|
99
|
+
}
|
|
100
|
+
if (signals.observabilityNoiseSignalCount > 0) {
|
|
101
|
+
guidance.push("Treat this as an alert-noise issue. Adjust the smallest threshold seam or noise filter first, then re-verify signal quality before widening the observability patch.");
|
|
102
|
+
}
|
|
103
|
+
return guidance;
|
|
104
|
+
}
|
|
105
|
+
function normalizePath(file) {
|
|
106
|
+
return file.replace(/\\/gu, "/").toLowerCase();
|
|
107
|
+
}
|
|
108
|
+
function isDependencySurface(file) {
|
|
109
|
+
return (file === "package.json" ||
|
|
110
|
+
file.endsWith("/package.json") ||
|
|
111
|
+
file.endsWith("/pnpm-lock.yaml") ||
|
|
112
|
+
file === "pnpm-lock.yaml" ||
|
|
113
|
+
file.endsWith("/package-lock.json") ||
|
|
114
|
+
file === "package-lock.json" ||
|
|
115
|
+
file.endsWith("/yarn.lock") ||
|
|
116
|
+
file === "yarn.lock" ||
|
|
117
|
+
file.endsWith("/turbo.json") ||
|
|
118
|
+
file === "turbo.json");
|
|
119
|
+
}
|
|
120
|
+
function isMigrationSurface(file) {
|
|
121
|
+
return (file.includes("/migrations/") ||
|
|
122
|
+
file.startsWith("migrations/") ||
|
|
123
|
+
file.includes("prisma/migrations/") ||
|
|
124
|
+
file.endsWith("/schema.prisma") ||
|
|
125
|
+
file.endsWith("/supabase/config.toml"));
|
|
126
|
+
}
|
|
127
|
+
function countPhraseMatches(haystack, phrases) {
|
|
128
|
+
return phrases.reduce((count, phrase) => {
|
|
129
|
+
const matches = haystack.match(new RegExp(escapeRegExp(phrase), "gu"));
|
|
130
|
+
return count + (matches?.length ?? 0);
|
|
131
|
+
}, 0);
|
|
132
|
+
}
|
|
133
|
+
function escapeRegExp(value) {
|
|
134
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
135
|
+
}
|
|
136
|
+
function roundScore(value) {
|
|
137
|
+
return Math.round(value * 100) / 100;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=surface-signals.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface SettledFact {
|
|
2
|
+
factId: string;
|
|
3
|
+
content: string;
|
|
4
|
+
settledAt: string;
|
|
5
|
+
}
|
|
6
|
+
export interface TruthWall {
|
|
7
|
+
readonly runId: string;
|
|
8
|
+
readonly builtAt: string;
|
|
9
|
+
readonly facts: readonly SettledFact[];
|
|
10
|
+
}
|
|
11
|
+
export interface Hypothesis {
|
|
12
|
+
hypothesisId: string;
|
|
13
|
+
runId: string;
|
|
14
|
+
rootCause: string;
|
|
15
|
+
recordedAt: string;
|
|
16
|
+
testResult: "validated" | "invalidated" | "pending";
|
|
17
|
+
confidenceDelta: number;
|
|
18
|
+
}
|
|
19
|
+
export interface HypothesisLedger {
|
|
20
|
+
hypotheses: Hypothesis[];
|
|
21
|
+
}
|
|
22
|
+
export interface TruthWallInput {
|
|
23
|
+
runId: string;
|
|
24
|
+
facts: SettledFact[];
|
|
25
|
+
runDir: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Builds an immutable TruthWall from settled ledger facts at run start.
|
|
29
|
+
* Writes truth-wall.json to the run directory and freezes the returned object.
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildTruthWall(input: TruthWallInput): Promise<TruthWall>;
|
|
32
|
+
/**
|
|
33
|
+
* Creates an ephemeral mutable Map for scratch-space state during a run.
|
|
34
|
+
* Writes do NOT propagate to the truth wall.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createWorkingMemory(): Map<string, unknown>;
|
|
37
|
+
/**
|
|
38
|
+
* Loads an existing hypothesis ledger from the run directory, or returns an empty one.
|
|
39
|
+
*/
|
|
40
|
+
export declare function loadHypothesisLedger(runDir: string): Promise<HypothesisLedger>;
|
|
41
|
+
/**
|
|
42
|
+
* Records a hypothesis in the ledger and persists hypothesis-ledger.json.
|
|
43
|
+
*/
|
|
44
|
+
export declare function recordHypothesis(ledger: HypothesisLedger, input: Omit<Hypothesis, "recordedAt">, runDir: string): Promise<Hypothesis>;
|
|
45
|
+
/**
|
|
46
|
+
* Emits all three required run artifacts:
|
|
47
|
+
* - truth-wall.json (immutable settled facts)
|
|
48
|
+
* - hypothesis-ledger.json (per-attempt hypotheses)
|
|
49
|
+
* - working-memory.json (mutable scratch, never promoted to truth wall)
|
|
50
|
+
*/
|
|
51
|
+
export declare function emitRunArtifacts(runDir: string, wall: TruthWall, ledger: HypothesisLedger, workingMemory: Map<string, unknown>): Promise<void>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
// ─── Truth Wall ───────────────────────────────────────────────────────────────
|
|
5
|
+
/**
|
|
6
|
+
* Builds an immutable TruthWall from settled ledger facts at run start.
|
|
7
|
+
* Writes truth-wall.json to the run directory and freezes the returned object.
|
|
8
|
+
*/
|
|
9
|
+
export async function buildTruthWall(input) {
|
|
10
|
+
const wall = Object.freeze({
|
|
11
|
+
runId: input.runId,
|
|
12
|
+
builtAt: new Date().toISOString(),
|
|
13
|
+
facts: Object.freeze([...input.facts.map(f => Object.freeze({ ...f }))])
|
|
14
|
+
});
|
|
15
|
+
await writeFile(join(input.runDir, "truth-wall.json"), JSON.stringify(wall, null, 2), "utf8");
|
|
16
|
+
return wall;
|
|
17
|
+
}
|
|
18
|
+
// ─── Working Memory ───────────────────────────────────────────────────────────
|
|
19
|
+
/**
|
|
20
|
+
* Creates an ephemeral mutable Map for scratch-space state during a run.
|
|
21
|
+
* Writes do NOT propagate to the truth wall.
|
|
22
|
+
*/
|
|
23
|
+
export function createWorkingMemory() {
|
|
24
|
+
return new Map();
|
|
25
|
+
}
|
|
26
|
+
// ─── Hypothesis Ledger ────────────────────────────────────────────────────────
|
|
27
|
+
/**
|
|
28
|
+
* Loads an existing hypothesis ledger from the run directory, or returns an empty one.
|
|
29
|
+
*/
|
|
30
|
+
export async function loadHypothesisLedger(runDir) {
|
|
31
|
+
const ledgerPath = join(runDir, "hypothesis-ledger.json");
|
|
32
|
+
if (existsSync(ledgerPath)) {
|
|
33
|
+
const raw = await readFile(ledgerPath, "utf8");
|
|
34
|
+
return JSON.parse(raw);
|
|
35
|
+
}
|
|
36
|
+
return { hypotheses: [] };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Records a hypothesis in the ledger and persists hypothesis-ledger.json.
|
|
40
|
+
*/
|
|
41
|
+
export async function recordHypothesis(ledger, input, runDir) {
|
|
42
|
+
const hypothesis = {
|
|
43
|
+
...input,
|
|
44
|
+
recordedAt: new Date().toISOString()
|
|
45
|
+
};
|
|
46
|
+
ledger.hypotheses.push(hypothesis);
|
|
47
|
+
await writeFile(join(runDir, "hypothesis-ledger.json"), JSON.stringify(ledger, null, 2), "utf8");
|
|
48
|
+
return hypothesis;
|
|
49
|
+
}
|
|
50
|
+
// ─── Run Artifacts Emitter ────────────────────────────────────────────────────
|
|
51
|
+
/**
|
|
52
|
+
* Emits all three required run artifacts:
|
|
53
|
+
* - truth-wall.json (immutable settled facts)
|
|
54
|
+
* - hypothesis-ledger.json (per-attempt hypotheses)
|
|
55
|
+
* - working-memory.json (mutable scratch, never promoted to truth wall)
|
|
56
|
+
*/
|
|
57
|
+
export async function emitRunArtifacts(runDir, wall, ledger, workingMemory) {
|
|
58
|
+
// truth-wall.json was already written by buildTruthWall — only write if missing
|
|
59
|
+
if (!existsSync(join(runDir, "truth-wall.json"))) {
|
|
60
|
+
await writeFile(join(runDir, "truth-wall.json"), JSON.stringify(wall, null, 2), "utf8");
|
|
61
|
+
}
|
|
62
|
+
await writeFile(join(runDir, "hypothesis-ledger.json"), JSON.stringify(ledger, null, 2), "utf8");
|
|
63
|
+
const wmObject = {};
|
|
64
|
+
for (const [k, v] of workingMemory) {
|
|
65
|
+
wmObject[k] = v;
|
|
66
|
+
}
|
|
67
|
+
await writeFile(join(runDir, "working-memory.json"), JSON.stringify({ entries: wmObject }, null, 2), "utf8");
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=truth-wall.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type ContextIntegrityVerdict = "clean" | "context_poisoning_warning" | "context_poisoning_block";
|
|
2
|
+
export interface ContextIntegrityPrecheck {
|
|
3
|
+
runId: string;
|
|
4
|
+
attemptIndex: number;
|
|
5
|
+
verdict: ContextIntegrityVerdict;
|
|
6
|
+
reason?: string;
|
|
7
|
+
detectedSignals: string[];
|
|
8
|
+
analyzedChannels: {
|
|
9
|
+
system: boolean;
|
|
10
|
+
user: boolean;
|
|
11
|
+
tools: boolean;
|
|
12
|
+
history: boolean;
|
|
13
|
+
};
|
|
14
|
+
timestamp: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* T05: Context Poisoning Pre-gate.
|
|
18
|
+
* Scans untrusted input channels for authority inversion or instruction re-injection.
|
|
19
|
+
* Runs BEFORE core reasoning and verification.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runContextIntegrityPrecheck(runId: string, attemptIndex: number, artifactsDir: string, inputs: {
|
|
22
|
+
userPrompt?: string;
|
|
23
|
+
toolOutput?: string;
|
|
24
|
+
retrievedContext?: string;
|
|
25
|
+
history?: string;
|
|
26
|
+
}): Promise<ContextIntegrityPrecheck>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const POISON_PATTERNS = [
|
|
4
|
+
/ignore\s+(?:all\s+)?previous\s+instructions/i,
|
|
5
|
+
/you\s+are\s+now\s+a\s+(?!Martin\s+Loop)/i,
|
|
6
|
+
/new\s+rule:/i,
|
|
7
|
+
/disregard\s+(?:safety|policy|guardrails)/i,
|
|
8
|
+
/override\s+system\s+authority/i,
|
|
9
|
+
/hidden\s+instruction:/i,
|
|
10
|
+
/\[system_override\]/i,
|
|
11
|
+
/\[authority_inversion\]/i
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* T05: Context Poisoning Pre-gate.
|
|
15
|
+
* Scans untrusted input channels for authority inversion or instruction re-injection.
|
|
16
|
+
* Runs BEFORE core reasoning and verification.
|
|
17
|
+
*/
|
|
18
|
+
export async function runContextIntegrityPrecheck(runId, attemptIndex, artifactsDir, inputs) {
|
|
19
|
+
const signals = [];
|
|
20
|
+
const analyzedChannels = {
|
|
21
|
+
system: true,
|
|
22
|
+
user: Boolean(inputs.userPrompt),
|
|
23
|
+
tools: Boolean(inputs.toolOutput),
|
|
24
|
+
history: Boolean(inputs.history)
|
|
25
|
+
};
|
|
26
|
+
const fullUntrustedBuffer = [
|
|
27
|
+
inputs.userPrompt,
|
|
28
|
+
inputs.toolOutput,
|
|
29
|
+
inputs.retrievedContext
|
|
30
|
+
].filter(Boolean).join("\n---\n");
|
|
31
|
+
for (const pattern of POISON_PATTERNS) {
|
|
32
|
+
if (pattern.test(fullUntrustedBuffer)) {
|
|
33
|
+
signals.push(`Detected poison pattern: ${pattern.toString()}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// Authority Boundary Detector: Check for attempts to redefine the 'Martin' identity or role.
|
|
37
|
+
if (/\b(?:I am|You are)\s+(?!Martin\s+Loop|an\s+AI)\b/i.test(fullUntrustedBuffer)) {
|
|
38
|
+
signals.push("Identity redefinition attempt detected.");
|
|
39
|
+
}
|
|
40
|
+
const verdict = signals.length > 0 ? "context_poisoning_block" : "clean";
|
|
41
|
+
const precheck = {
|
|
42
|
+
runId,
|
|
43
|
+
attemptIndex,
|
|
44
|
+
verdict,
|
|
45
|
+
reason: signals.length > 0 ? `Detected ${signals.length} poisoning signals.` : undefined,
|
|
46
|
+
detectedSignals: signals,
|
|
47
|
+
analyzedChannels,
|
|
48
|
+
timestamp: new Date().toISOString()
|
|
49
|
+
};
|
|
50
|
+
// Persist artifact: context-integrity-precheck.json
|
|
51
|
+
const artifactPath = join(artifactsDir, "context-integrity-precheck.json");
|
|
52
|
+
try {
|
|
53
|
+
const { mkdir } = await import("node:fs/promises");
|
|
54
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
55
|
+
await writeFile(artifactPath, JSON.stringify(precheck, null, 2), "utf8");
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.error(`Failed to persist context-integrity-precheck: ${err instanceof Error ? err.message : String(err)}`);
|
|
59
|
+
}
|
|
60
|
+
return precheck;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=truth-spine.js.map
|