@spikedpunch/align-agent 0.1.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/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/anthropicFixProvider.d.ts +33 -0
- package/dist/anthropicFixProvider.d.ts.map +1 -0
- package/dist/anthropicFixProvider.js +157 -0
- package/dist/anthropicFixProvider.js.map +1 -0
- package/dist/coverage.d.ts +15 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +28 -0
- package/dist/coverage.js.map +1 -0
- package/dist/effects.d.ts +69 -0
- package/dist/effects.d.ts.map +1 -0
- package/dist/effects.js +2 -0
- package/dist/effects.js.map +1 -0
- package/dist/fixProvider.d.ts +51 -0
- package/dist/fixProvider.d.ts.map +1 -0
- package/dist/fixProvider.js +54 -0
- package/dist/fixProvider.js.map +1 -0
- package/dist/format.d.ts +5 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +47 -0
- package/dist/format.js.map +1 -0
- package/dist/git.d.ts +3 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +91 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/oscillation.d.ts +28 -0
- package/dist/oscillation.d.ts.map +1 -0
- package/dist/oscillation.js +31 -0
- package/dist/oscillation.js.map +1 -0
- package/dist/rails.d.ts +27 -0
- package/dist/rails.d.ts.map +1 -0
- package/dist/rails.js +38 -0
- package/dist/rails.js.map +1 -0
- package/dist/repairDecision.d.ts +23 -0
- package/dist/repairDecision.d.ts.map +1 -0
- package/dist/repairDecision.js +14 -0
- package/dist/repairDecision.js.map +1 -0
- package/dist/run.d.ts +68 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +211 -0
- package/dist/run.js.map +1 -0
- package/dist/symbolDiff.d.ts +16 -0
- package/dist/symbolDiff.d.ts.map +1 -0
- package/dist/symbolDiff.js +13 -0
- package/dist/symbolDiff.js.map +1 -0
- package/dist/symbolTable.d.ts +14 -0
- package/dist/symbolTable.d.ts.map +1 -0
- package/dist/symbolTable.js +10 -0
- package/dist/symbolTable.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 align contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @spikedpunch/align-agent
|
|
2
|
+
|
|
3
|
+
The Stage 4 BYOK (bring-your-own-key) LLM fix loop (ADR 010, `IMPLEMENTATION_PLAN.md` Stage 4).
|
|
4
|
+
Depends only on `@spikedpunch/align-core` and `@anthropic-ai/sdk`; never imports `@spikedpunch/align-plugin-typescript` or
|
|
5
|
+
`@spikedpunch/align-cli` (enforced by align's own dogfooded `align.config.ts` layering rule). `@spikedpunch/align-cli` is
|
|
6
|
+
the composition root that wires the real `TypeScriptPlugin` scanner, `node:fs`, and `git`/`gh`
|
|
7
|
+
into this package's `AgentEffects` interface (see `packages/cli/src/commands/agent.ts`).
|
|
8
|
+
|
|
9
|
+
## Safety bound — read before running against a real repo
|
|
10
|
+
|
|
11
|
+
**Behavioral safety is bounded by the target repo's own test suite.** No gate in align (or in this
|
|
12
|
+
agent) verifies *behavior* — only form (parses, doesn't create a forbidden dependency, doesn't
|
|
13
|
+
introduce a cycle). An LLM can satisfy every form gate by making code do less: the cleanest way to
|
|
14
|
+
remove a forbidden import is often to delete the import *and* the feature that used it. This
|
|
15
|
+
agent's guards reduce that risk but do not eliminate it:
|
|
16
|
+
|
|
17
|
+
- **Exported-symbol surface diff** flags (and by default refuses to commit) any fix that deletes an
|
|
18
|
+
exported symbol, requiring explicit `--allow-symbol-removals` consent.
|
|
19
|
+
- **Zero-coverage refusal** declines to propose a fix for a file with no detected test coverage
|
|
20
|
+
unless `--allow-untested` is passed. The v1 heuristic is **reachability, not real coverage**: a
|
|
21
|
+
file is "covered" if any scanned test file (`**/*.{test,spec}.*`) imports it directly or
|
|
22
|
+
transitively in the dependency graph. A test that imports a module but never exercises the
|
|
23
|
+
specific lines being changed still counts as "covered" — this catches the worst case (nothing
|
|
24
|
+
tests this file at all) cheaply, at the cost of false confidence on partially-exercised code.
|
|
25
|
+
|
|
26
|
+
Neither guard is a substitute for a real test suite. If the target repo has weak or no tests, the
|
|
27
|
+
agent's fixes carry the same behavioral risk any unreviewed automated edit would. Review the diff
|
|
28
|
+
(or the draft PR, which is the default terminal-merge mode) before merging to a protected branch.
|
|
29
|
+
|
|
30
|
+
## Suppressions — dormant machinery
|
|
31
|
+
|
|
32
|
+
`FixProposal.suppressions` is accepted and zod-validated per ADR 010 (the schema is shared with the
|
|
33
|
+
future tool-wrapping gate stack), but **arch-first v1 has no lint gates** — there is no rule
|
|
34
|
+
category a suppression could legitimately silence yet. Any proposal that *uses* `suppressions` is
|
|
35
|
+
rejected with "no suppressible rule categories active" (`rails.ts::usesSuppressions`, enforced in
|
|
36
|
+
`run.ts`). This is tested as dormant machinery (`test/run.test.ts`), not wired to any live
|
|
37
|
+
suppress-and-commit path — it exists now so the schema doesn't need a breaking change when
|
|
38
|
+
tool-wrapping gates (Stage 5) activate it.
|
|
39
|
+
|
|
40
|
+
## Design notes / deviations
|
|
41
|
+
|
|
42
|
+
- **Apply pipeline lives in `@spikedpunch/align-core/fix`, not here** — per ADR 010 ("the LLM proposes, the
|
|
43
|
+
engine applies... this pipeline is entirely engine-side... it introduces no dependency from
|
|
44
|
+
`@spikedpunch/align-core` on any LLM client"). `FixProposal`/`EditBlock` zod schemas and the deterministic
|
|
45
|
+
byte-offset apply algorithm are pure data/algorithm with zero LLM awareness, so they belong in
|
|
46
|
+
core alongside every other IR core validates — `@spikedpunch/align-agent` only supplies the `FixProvider`
|
|
47
|
+
that produces a `FixProposal` and the effects shell that applies one.
|
|
48
|
+
- **`condensedSymbolTable` is component-scoped**, not repo-wide or import-graph-reachability-scoped
|
|
49
|
+
— a repo-wide symbol table would blow the token budget on any non-trivial repo; component scope
|
|
50
|
+
is the cheapest reasonable proxy for "files the target may plausibly import" (`symbolTable.ts`).
|
|
51
|
+
- **VERIFY and the terminal merge both run a FULL, non-scoped `align check`** — v1 does not
|
|
52
|
+
impact-scope verification inside the loop (impact scoping is Design Reserve). This is simpler and
|
|
53
|
+
matches ADR 005's freshness doctrine at the cost of a full rescan per REPAIR attempt; acceptable
|
|
54
|
+
given the measured ~1.4s full-rescan cost on the target repo class (`spike/SPIKE_REPORT.md`).
|
|
55
|
+
Impact-scoped in-loop VERIFY is a future promotion candidate if REPAIR-heavy runs measure slow.
|
|
56
|
+
- **Model default is `claude-sonnet-5`**, not the `claude-api` skill's general-purpose
|
|
57
|
+
`claude-opus-4-8` default — an explicit deviation directed by the Stage 4 task brief: a
|
|
58
|
+
background fix loop that may issue many PLAN+FIX calls per run is exactly the
|
|
59
|
+
cost-sensitive/high-volume workload Sonnet-tier targets. Override via `--model` or
|
|
60
|
+
`ALIGN_AGENT_MODEL`.
|
|
61
|
+
- **No fuzzy/whitespace-normalized apply fallback** (Design Reserve, ADR 010) — exact match +
|
|
62
|
+
`nearLine` disambiguation only, in `@spikedpunch/align-core/fix`.
|
|
63
|
+
|
|
64
|
+
## `align agent run`
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
align agent run [--max-attempts N] [--pr|--auto-merge] [--allow-untested] \
|
|
68
|
+
[--allow-symbol-removals] [--model <id>] [--dry-run]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Requires `ANTHROPIC_API_KEY` in the environment (or any credential source the Anthropic SDK
|
|
72
|
+
resolves automatically — see the `claude-api` skill). `--dry-run` runs DISCOVER+GROUP+PLAN only and
|
|
73
|
+
prints proposed edits without applying or committing anything — a cheap smoke test that still calls
|
|
74
|
+
the real model.
|
|
75
|
+
|
|
76
|
+
## Testing
|
|
77
|
+
|
|
78
|
+
Every test in this package runs with no network access. `FakeFixProvider` (`test/fakeFixProvider.ts`)
|
|
79
|
+
scripts deterministic responses per file; `test/fakeEffects.ts` provides an in-memory `AgentEffects`
|
|
80
|
+
(including a git-revert-accurate undo log) for state-machine tests; `test/e2e-git.test.ts` exercises
|
|
81
|
+
the real `git`/`gh` shell-out effects against a temp repo. One optional live-API smoke test
|
|
82
|
+
(`test/live-smoke.test.ts`) runs only when `ALIGN_LIVE_SMOKE=1` and `ANTHROPIC_API_KEY` are both
|
|
83
|
+
set — it skips (not fails) otherwise, and is never depended on by CI.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type FixProposal } from '@spikedpunch/align-core';
|
|
2
|
+
import type { FixProvider, FixProviderInput } from './fixProvider.js';
|
|
3
|
+
export declare const DEFAULT_MODEL = "claude-sonnet-5";
|
|
4
|
+
export interface AnthropicFixProviderOptions {
|
|
5
|
+
/** Falls back to the SDK's own credential resolution (env/profile) when omitted — never log it. */
|
|
6
|
+
readonly apiKey?: string;
|
|
7
|
+
readonly model?: string;
|
|
8
|
+
readonly maxTokens?: number;
|
|
9
|
+
readonly schemaRetries?: number;
|
|
10
|
+
}
|
|
11
|
+
/** Telemetry's `agent` event usage field (IMPLEMENTATION_PLAN.md's telemetry spec, closing the
|
|
12
|
+
* Kimi-flagged observability gap): "capture whatever `@anthropic-ai/sdk` returns in
|
|
13
|
+
* `response.usage`; if absent, omit the field, don't fabricate." */
|
|
14
|
+
export interface AnthropicUsageTotals {
|
|
15
|
+
readonly inputTokens: number;
|
|
16
|
+
readonly outputTokens: number;
|
|
17
|
+
}
|
|
18
|
+
export declare class AnthropicFixProvider implements FixProvider {
|
|
19
|
+
private readonly client;
|
|
20
|
+
private readonly model;
|
|
21
|
+
private readonly maxTokens;
|
|
22
|
+
private readonly schemaRetries;
|
|
23
|
+
private usageTotals;
|
|
24
|
+
constructor(options?: AnthropicFixProviderOptions);
|
|
25
|
+
/** Accumulated `input_tokens`/`output_tokens` across every real API call this instance made
|
|
26
|
+
* (schema-retry attempts included — a retry is still real spend). Read by
|
|
27
|
+
* `packages/cli/src/commands/agent.ts` after `runAgentLoop` completes to populate the `agent`
|
|
28
|
+
* telemetry event's optional `usage` field. */
|
|
29
|
+
getUsageTotals(): AnthropicUsageTotals | undefined;
|
|
30
|
+
private accumulateUsage;
|
|
31
|
+
proposeFix(input: FixProviderInput): Promise<FixProposal>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=anthropicFixProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropicFixProvider.d.ts","sourceRoot":"","sources":["../src/anthropicFixProvider.ts"],"names":[],"mappings":"AAYA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,eAAO,MAAM,aAAa,oBAAoB,CAAC;AA4C/C,MAAM,WAAW,2BAA2B;IAC1C,mGAAmG;IACnG,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED;;oEAEoE;AACpE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAyDD,qBAAa,oBAAqB,YAAW,WAAW;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAKvC,OAAO,CAAC,WAAW,CAA4D;gBAEnE,OAAO,GAAE,2BAAgC;IAOrD;;;mDAG+C;IAC/C,cAAc,IAAI,oBAAoB,GAAG,SAAS;IAIlD,OAAO,CAAC,eAAe;IAQjB,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC;CAqChE"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one real `FixProvider` implementation — raw Anthropic API tool-use, per the plan's "Inner
|
|
3
|
+
* PLAN+FIX: raw Anthropic API pure function (zod-constrained tool-use, memoizable), not a nested
|
|
4
|
+
* agent." Never imported by the state machine directly (`fixProvider.ts`'s interface is the only
|
|
5
|
+
* thing `run.ts` depends on) — this file is the sole place `@anthropic-ai/sdk` is imported.
|
|
6
|
+
*
|
|
7
|
+
* Model default per the `claude-api` skill reference, per explicit task instruction to default to
|
|
8
|
+
* the latest Sonnet-class model id (not the skill's general-purpose Opus default) — a background
|
|
9
|
+
* fix loop that may run many PLAN+FIX calls per session is exactly the cost-sensitive, high-volume
|
|
10
|
+
* workload Sonnet-tier is for. Config/env-selectable (`ALIGN_AGENT_MODEL` / `--model`).
|
|
11
|
+
*/
|
|
12
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
13
|
+
import { fixProposalSchema } from '@spikedpunch/align-core';
|
|
14
|
+
export const DEFAULT_MODEL = 'claude-sonnet-5';
|
|
15
|
+
const DEFAULT_MAX_TOKENS = 16000;
|
|
16
|
+
const DEFAULT_SCHEMA_RETRIES = 2;
|
|
17
|
+
const TOOL_NAME = 'propose_fix';
|
|
18
|
+
const EDIT_BLOCK_JSON_SCHEMA = {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
search: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Exact, continuous block present in the file — literal, character-for-character match including whitespace/indentation/newlines. Include 1-2 lines of untouched context above/below for uniqueness.',
|
|
24
|
+
},
|
|
25
|
+
replace: { type: 'string', description: 'Replacement text. Empty string means deletion.' },
|
|
26
|
+
nearLine: {
|
|
27
|
+
type: 'integer',
|
|
28
|
+
description: 'Approximate 1-based line number, used ONLY to disambiguate if `search` matches more than once. Never affects file content.',
|
|
29
|
+
},
|
|
30
|
+
forViolations: { type: 'array', items: { type: 'string' }, description: 'Violation ids this edit addresses.' },
|
|
31
|
+
},
|
|
32
|
+
required: ['search', 'replace'],
|
|
33
|
+
};
|
|
34
|
+
const FIX_PROPOSAL_JSON_SCHEMA = {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
files: {
|
|
38
|
+
type: 'array',
|
|
39
|
+
items: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
path: { type: 'string', description: 'Repo-relative path, exactly as given in the violation payload.' },
|
|
43
|
+
edits: { type: 'array', items: EDIT_BLOCK_JSON_SCHEMA, minItems: 1 },
|
|
44
|
+
},
|
|
45
|
+
required: ['path', 'edits'],
|
|
46
|
+
},
|
|
47
|
+
minItems: 1,
|
|
48
|
+
},
|
|
49
|
+
rationale: { type: 'string', description: 'Short explanation — becomes the git commit body.' },
|
|
50
|
+
},
|
|
51
|
+
required: ['files', 'rationale'],
|
|
52
|
+
};
|
|
53
|
+
function renderInputPrompt(input, correction) {
|
|
54
|
+
const parts = [];
|
|
55
|
+
parts.push('You are fixing architecture-conformance violations in a TypeScript monorepo. Respond ONLY by ' +
|
|
56
|
+
`calling the \`${TOOL_NAME}\` tool with a FixProposal. Adhere strictly to the schema: emit only ` +
|
|
57
|
+
'the precise chunks requiring modification (search/replace edit blocks, never full files, never ' +
|
|
58
|
+
'line-number diffs); `search` must match the file exactly including whitespace/indentation/' +
|
|
59
|
+
'newlines; include 1-2 lines of untouched context above/below each block for uniqueness. Do not ' +
|
|
60
|
+
'use the `suppressions` field — no suppressible rule categories are active in this repo.');
|
|
61
|
+
parts.push('\n## Rule explanations\n');
|
|
62
|
+
for (const exp of input.ruleExplanations) {
|
|
63
|
+
parts.push(`- ${exp.ruleId} (${exp.kind})${exp.because ? `: ${exp.because}` : ''}`);
|
|
64
|
+
}
|
|
65
|
+
parts.push('\n## Violations\n');
|
|
66
|
+
for (const v of input.violations) {
|
|
67
|
+
parts.push(`- id=${v.id} rule=${v.ruleId} kind=${v.kind} ${v.file}:${v.range.startLine}-${v.range.endLine}\n snippet: ${v.snippet}`);
|
|
68
|
+
}
|
|
69
|
+
parts.push('\n## Current file contents\n');
|
|
70
|
+
for (const [path, content] of input.fileContents) {
|
|
71
|
+
parts.push(`### ${path}\n\`\`\`\n${content}\n\`\`\``);
|
|
72
|
+
}
|
|
73
|
+
if (input.condensedSymbolTable.length > 0) {
|
|
74
|
+
parts.push('\n## Importable symbols (files this target may import)\n');
|
|
75
|
+
for (const entry of input.condensedSymbolTable) {
|
|
76
|
+
parts.push(`- ${entry.file}: ${entry.exports.join(', ')}`);
|
|
77
|
+
}
|
|
78
|
+
parts.push('Only reference symbols that exist above — do not invent imports.');
|
|
79
|
+
}
|
|
80
|
+
if (input.previousFailure !== undefined) {
|
|
81
|
+
parts.push(`\n## Previous attempt failed to apply\n`);
|
|
82
|
+
parts.push(`reason: ${input.previousFailure.reason}`);
|
|
83
|
+
if (input.previousFailure.nearestCandidate !== undefined) {
|
|
84
|
+
parts.push(`Nearest candidate region (line numbers for your reference only — do NOT include them in \`search\`):\n${input.previousFailure.nearestCandidate.linesWithContext}`);
|
|
85
|
+
}
|
|
86
|
+
parts.push('Re-anchor your `search` block to match the file exactly, character-for-character.');
|
|
87
|
+
}
|
|
88
|
+
if (correction !== undefined) {
|
|
89
|
+
parts.push(`\n## Your previous response was invalid\n${correction}\nRetry, adhering strictly to the schema.`);
|
|
90
|
+
}
|
|
91
|
+
return parts.join('\n');
|
|
92
|
+
}
|
|
93
|
+
export class AnthropicFixProvider {
|
|
94
|
+
client;
|
|
95
|
+
model;
|
|
96
|
+
maxTokens;
|
|
97
|
+
schemaRetries;
|
|
98
|
+
// `undefined` until the first response actually surfaces `usage` — telemetry's `agent.usage`
|
|
99
|
+
// field must be omittable, never a fabricated `{ inputTokens: 0, outputTokens: 0 }` for a
|
|
100
|
+
// provider instance that was never actually called (e.g. `nothing-to-fix`, ADR-consistent with
|
|
101
|
+
// every other "if absent, omit" field in this codebase).
|
|
102
|
+
usageTotals;
|
|
103
|
+
constructor(options = {}) {
|
|
104
|
+
this.client = options.apiKey !== undefined ? new Anthropic({ apiKey: options.apiKey }) : new Anthropic();
|
|
105
|
+
this.model = options.model ?? process.env['ALIGN_AGENT_MODEL'] ?? DEFAULT_MODEL;
|
|
106
|
+
this.maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
107
|
+
this.schemaRetries = options.schemaRetries ?? DEFAULT_SCHEMA_RETRIES;
|
|
108
|
+
}
|
|
109
|
+
/** Accumulated `input_tokens`/`output_tokens` across every real API call this instance made
|
|
110
|
+
* (schema-retry attempts included — a retry is still real spend). Read by
|
|
111
|
+
* `packages/cli/src/commands/agent.ts` after `runAgentLoop` completes to populate the `agent`
|
|
112
|
+
* telemetry event's optional `usage` field. */
|
|
113
|
+
getUsageTotals() {
|
|
114
|
+
return this.usageTotals;
|
|
115
|
+
}
|
|
116
|
+
accumulateUsage(usage) {
|
|
117
|
+
if (usage === undefined)
|
|
118
|
+
return;
|
|
119
|
+
const inputTokens = typeof usage.input_tokens === 'number' ? usage.input_tokens : 0;
|
|
120
|
+
const outputTokens = typeof usage.output_tokens === 'number' ? usage.output_tokens : 0;
|
|
121
|
+
const previous = this.usageTotals ?? { inputTokens: 0, outputTokens: 0 };
|
|
122
|
+
this.usageTotals = { inputTokens: previous.inputTokens + inputTokens, outputTokens: previous.outputTokens + outputTokens };
|
|
123
|
+
}
|
|
124
|
+
async proposeFix(input) {
|
|
125
|
+
let correction;
|
|
126
|
+
for (let attempt = 0; attempt <= this.schemaRetries; attempt++) {
|
|
127
|
+
const message = await this.client.messages.create({
|
|
128
|
+
model: this.model,
|
|
129
|
+
max_tokens: this.maxTokens,
|
|
130
|
+
tools: [
|
|
131
|
+
{
|
|
132
|
+
name: TOOL_NAME,
|
|
133
|
+
description: 'Propose a search/replace edit-block fix for the given violations.',
|
|
134
|
+
input_schema: FIX_PROPOSAL_JSON_SCHEMA,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
tool_choice: { type: 'tool', name: TOOL_NAME },
|
|
138
|
+
messages: [{ role: 'user', content: renderInputPrompt(input, correction) }],
|
|
139
|
+
});
|
|
140
|
+
this.accumulateUsage(message.usage);
|
|
141
|
+
if (message.stop_reason === 'refusal') {
|
|
142
|
+
throw new Error('AnthropicFixProvider: model refused the request');
|
|
143
|
+
}
|
|
144
|
+
const toolUse = message.content.find((block) => block.type === 'tool_use' && block.name === TOOL_NAME);
|
|
145
|
+
if (toolUse === undefined || toolUse.type !== 'tool_use') {
|
|
146
|
+
correction = 'No tool call was returned. You must call the tool with a valid FixProposal.';
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const parsed = fixProposalSchema.safeParse(toolUse.input);
|
|
150
|
+
if (parsed.success)
|
|
151
|
+
return parsed.data;
|
|
152
|
+
correction = `Schema validation failed: ${parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ')}`;
|
|
153
|
+
}
|
|
154
|
+
throw new Error(`AnthropicFixProvider: exceeded ${this.schemaRetries} schema-mismatch retries`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=anthropicFixProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropicFixProvider.js","sourceRoot":"","sources":["../src/anthropicFixProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-coverage refusal heuristic (green≠correct guard (b), IMPLEMENTATION_PLAN.md Stage 4).
|
|
3
|
+
*
|
|
4
|
+
* v1 heuristic, stated honestly: a file is "covered" if any scanned test file (matching
|
|
5
|
+
* `**\/*.{test,spec}.*`) imports it directly or transitively within the graph — computable from
|
|
6
|
+
* the `DependencyGraph` the orchestrator already builds, no separate coverage-instrumentation
|
|
7
|
+
* tool. This is a REACHABILITY proxy, not real coverage: a test file that imports a module but
|
|
8
|
+
* never exercises the specific lines being changed still counts as "covered." It catches the
|
|
9
|
+
* worst case (a file literally nothing imports from a test) cheaply, at the cost of false
|
|
10
|
+
* confidence on files with imported-but-unexercised code. Documented in the agent package README
|
|
11
|
+
* per the plan's requirement to state this plainly.
|
|
12
|
+
*/
|
|
13
|
+
import type { DependencyGraph, RepoRelativePath } from '@spikedpunch/align-core';
|
|
14
|
+
export declare function isFileCovered(target: RepoRelativePath, graph: DependencyGraph, testFilePattern?: RegExp): boolean;
|
|
15
|
+
//# sourceMappingURL=coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../src/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAIjF,wBAAgB,aAAa,CAC3B,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,eAAe,EACtB,eAAe,GAAE,MAAkC,GAClD,OAAO,CAsBT"}
|
package/dist/coverage.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const DEFAULT_TEST_FILE_PATTERN = /\.(test|spec)\./;
|
|
2
|
+
export function isFileCovered(target, graph, testFilePattern = DEFAULT_TEST_FILE_PATTERN) {
|
|
3
|
+
const testFiles = graph.nodes.filter((n) => testFilePattern.test(n.file)).map((n) => n.file);
|
|
4
|
+
if (testFiles.length === 0)
|
|
5
|
+
return false;
|
|
6
|
+
const adjacency = new Map();
|
|
7
|
+
for (const edge of graph.edges) {
|
|
8
|
+
const list = adjacency.get(edge.from) ?? [];
|
|
9
|
+
list.push(edge.to);
|
|
10
|
+
adjacency.set(edge.from, list);
|
|
11
|
+
}
|
|
12
|
+
const visited = new Set();
|
|
13
|
+
const stack = [...testFiles];
|
|
14
|
+
while (stack.length > 0) {
|
|
15
|
+
const current = stack.pop();
|
|
16
|
+
if (current === undefined)
|
|
17
|
+
break;
|
|
18
|
+
if (current === target)
|
|
19
|
+
return true;
|
|
20
|
+
if (visited.has(current))
|
|
21
|
+
continue;
|
|
22
|
+
visited.add(current);
|
|
23
|
+
for (const next of adjacency.get(current) ?? [])
|
|
24
|
+
stack.push(next);
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.js","sourceRoot":"","sources":["../src/coverage.ts"],"names":[],"mappings":"AAcA,MAAM,yBAAyB,GAAG,iBAAiB,CAAC;AAEpD,MAAM,UAAU,aAAa,CAC3B,MAAwB,EACxB,KAAsB,EACtB,kBAA0B,yBAAyB;IAEnD,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC7F,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAEzC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC9C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnB,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,KAAK,GAAa,CAAC,GAAG,SAAS,CAAC,CAAC;IACvC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;QAC5B,IAAI,OAAO,KAAK,SAAS;YAAE,MAAM;QACjC,IAAI,OAAO,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC;QACpC,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,SAAS;QACnC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrB,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injected effects — the imperative shell the state machine (`run.ts`) is built against. Every
|
|
3
|
+
* side effect (fs, git, `align check`, formatting) is an interface here so the state machine can
|
|
4
|
+
* be driven end-to-end by a fake in tests, per CODING_BEST_PRACTICES.md §15 ("if you're reaching
|
|
5
|
+
* for module mocking, the dependency wasn't injected").
|
|
6
|
+
*/
|
|
7
|
+
import type { CheckRun, DependencyGraph, RepoRelativePath } from '@spikedpunch/align-core';
|
|
8
|
+
import type { FixProvider } from './fixProvider.js';
|
|
9
|
+
export interface GitCommitResult {
|
|
10
|
+
readonly sha: string;
|
|
11
|
+
}
|
|
12
|
+
export type RebaseResult = {
|
|
13
|
+
readonly ok: true;
|
|
14
|
+
} | {
|
|
15
|
+
readonly ok: false;
|
|
16
|
+
readonly conflict: true;
|
|
17
|
+
};
|
|
18
|
+
export type PushResult = {
|
|
19
|
+
readonly ok: true;
|
|
20
|
+
} | {
|
|
21
|
+
readonly ok: false;
|
|
22
|
+
readonly reason: 'no-remote' | 'push-failed';
|
|
23
|
+
};
|
|
24
|
+
export type PrResult = {
|
|
25
|
+
readonly ok: true;
|
|
26
|
+
readonly url: string;
|
|
27
|
+
} | {
|
|
28
|
+
readonly ok: false;
|
|
29
|
+
readonly reason: string;
|
|
30
|
+
};
|
|
31
|
+
/** All git operations the agent needs — a thin, testable seam over the real `git`/`gh` binaries. */
|
|
32
|
+
export interface GitEffects {
|
|
33
|
+
isWorktreeClean(): Promise<boolean>;
|
|
34
|
+
currentBranch(): Promise<string>;
|
|
35
|
+
createBranch(name: string): Promise<void>;
|
|
36
|
+
/** Stages exactly `paths` and commits. Returns the new commit sha. */
|
|
37
|
+
commit(message: string, paths: readonly RepoRelativePath[]): Promise<GitCommitResult>;
|
|
38
|
+
/** Hard-reverts the working tree to the state before `sha` (used by REPAIR). */
|
|
39
|
+
revertCommit(sha: string): Promise<void>;
|
|
40
|
+
/** Rebases the current branch onto `ontoBranch`. Never auto-resolves conflicts. */
|
|
41
|
+
rebaseOnto(ontoBranch: string): Promise<RebaseResult>;
|
|
42
|
+
push(branch: string): Promise<PushResult>;
|
|
43
|
+
/** Requires `gh` on PATH and a remote; returns `ok:false` (never throws) when either is absent. */
|
|
44
|
+
createDraftPr(params: {
|
|
45
|
+
readonly branch: string;
|
|
46
|
+
readonly base: string;
|
|
47
|
+
readonly title: string;
|
|
48
|
+
readonly body: string;
|
|
49
|
+
}): Promise<PrResult>;
|
|
50
|
+
ffMergeAndDeleteBranch(branch: string, base: string): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
export interface AgentEffects {
|
|
53
|
+
readonly fixProvider: FixProvider;
|
|
54
|
+
/** Fresh, FULL `align check` — no scoping (DISCOVER and the terminal-merge verify both use this;
|
|
55
|
+
* VERIFY inside the loop uses it too — v1 keeps verification uniformly full-scope, matching
|
|
56
|
+
* ADR 005's "the oracle never answers from state older than the code it judges" doctrine and
|
|
57
|
+
* sidestepping impact-scoping, which is Design Reserve). */
|
|
58
|
+
readonly runCheck: () => Promise<CheckRun>;
|
|
59
|
+
/** Fresh full dependency-graph scan (for condensedSymbolTable, coverage heuristic, symbol diff). */
|
|
60
|
+
readonly scanGraph: () => Promise<DependencyGraph>;
|
|
61
|
+
readonly readFile: (path: RepoRelativePath) => Promise<string>;
|
|
62
|
+
readonly writeFile: (path: RepoRelativePath, content: string) => Promise<void>;
|
|
63
|
+
/** Mechanical post-format: runs the target repo's own prettier on `paths` if the target repo
|
|
64
|
+
* exposes one (checked via its own package.json/devDependencies); skips silently otherwise. */
|
|
65
|
+
readonly formatIfAvailable: (paths: readonly RepoRelativePath[]) => Promise<void>;
|
|
66
|
+
readonly git: GitEffects;
|
|
67
|
+
readonly now: () => number;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=effects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effects.d.ts","sourceRoot":"","sources":["../src/effects.ts"],"names":[],"mappings":"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"}
|
package/dist/effects.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effects.js","sourceRoot":"","sources":["../src/effects.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `FixProvider` — the injected boundary between the state machine and any LLM (CODING_BEST_PRACTICES.md
|
|
3
|
+
* §14/15, per the task's explicit framing): the state machine is a pure decision core that never
|
|
4
|
+
* imports `@anthropic-ai/sdk`; it only ever sees this interface, which any fake can implement in
|
|
5
|
+
* tests. `AnthropicFixProvider` (anthropicFixProvider.ts) is the one real implementation.
|
|
6
|
+
*/
|
|
7
|
+
import { type RepoRelativePath, type RuleId, type Violation } from '@spikedpunch/align-core';
|
|
8
|
+
import type { FailureContext, FixProposal } from '@spikedpunch/align-core';
|
|
9
|
+
export interface RuleExplanation {
|
|
10
|
+
readonly ruleId: RuleId;
|
|
11
|
+
readonly kind: string;
|
|
12
|
+
/** `.because()` / doc-quote provenance (ADR 002/011) — included verbatim so the LLM sees the
|
|
13
|
+
* project's own stated rationale, not a paraphrase. */
|
|
14
|
+
readonly because?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface SymbolTableEntry {
|
|
17
|
+
readonly file: RepoRelativePath;
|
|
18
|
+
readonly exports: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
export interface FixProviderInput {
|
|
21
|
+
/** Structured violations for one GROUP (all of one file's violations, per ADR 010/plan). */
|
|
22
|
+
readonly violations: readonly Violation[];
|
|
23
|
+
/** The grouped file(s)' current content — search blocks must match this exactly. */
|
|
24
|
+
readonly fileContents: ReadonlyMap<RepoRelativePath, string>;
|
|
25
|
+
/** Importable symbols from the graph — exports of files the target may import. */
|
|
26
|
+
readonly condensedSymbolTable: readonly SymbolTableEntry[];
|
|
27
|
+
readonly ruleExplanations: readonly RuleExplanation[];
|
|
28
|
+
/** Present only on a REPAIR retry — the apply pipeline's rejection reason + re-anchoring context. */
|
|
29
|
+
readonly previousFailure?: FailureContext;
|
|
30
|
+
}
|
|
31
|
+
export interface FixProvider {
|
|
32
|
+
proposeFix(input: FixProviderInput): Promise<FixProposal>;
|
|
33
|
+
}
|
|
34
|
+
export declare function hashFixProviderInput(input: FixProviderInput): string;
|
|
35
|
+
/**
|
|
36
|
+
* Wraps any `FixProvider` with in-memory, per-run memoization by `hash(input)`. A `FixProposal` is
|
|
37
|
+
* a pure function of its input in spirit (ADR 010: "preserves pure-function memoization
|
|
38
|
+
* semantics") — an identical GROUP + identical `previousFailure` context should never re-invoke
|
|
39
|
+
* the underlying provider (saves cost, and makes REPAIR retries with unchanged state a no-op
|
|
40
|
+
* instead of an infinite-cost loop).
|
|
41
|
+
*/
|
|
42
|
+
export declare class MemoizingFixProvider implements FixProvider {
|
|
43
|
+
private readonly inner;
|
|
44
|
+
private readonly cache;
|
|
45
|
+
private callCount;
|
|
46
|
+
constructor(inner: FixProvider);
|
|
47
|
+
proposeFix(input: FixProviderInput): Promise<FixProposal>;
|
|
48
|
+
/** Test/observability hook — number of calls that actually reached the inner provider. */
|
|
49
|
+
get providerCallCount(): number;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=fixProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixProvider.d.ts","sourceRoot":"","sources":["../src/fixProvider.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAa,KAAK,gBAAgB,EAAE,KAAK,MAAM,EAAE,KAAK,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACxG,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;2DACuD;IACvD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAED,MAAM,WAAW,gBAAgB;IAC/B,4FAA4F;IAC5F,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;IAC1C,oFAAoF;IACpF,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IAC7D,kFAAkF;IAClF,QAAQ,CAAC,oBAAoB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC3D,QAAQ,CAAC,gBAAgB,EAAE,SAAS,eAAe,EAAE,CAAC;IACtD,qGAAqG;IACrG,QAAQ,CAAC,eAAe,CAAC,EAAE,cAAc,CAAC;CAC3C;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CAC3D;AAeD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAEpE;AAED;;;;;;GAMG;AACH,qBAAa,oBAAqB,YAAW,WAAW;IAI1C,OAAO,CAAC,QAAQ,CAAC,KAAK;IAHlC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2C;IACjE,OAAO,CAAC,SAAS,CAAK;gBAEO,KAAK,EAAE,WAAW;IAEzC,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,CAAC;IAa/D,0FAA0F;IAC1F,IAAI,iBAAiB,IAAI,MAAM,CAE9B;CACF"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `FixProvider` — the injected boundary between the state machine and any LLM (CODING_BEST_PRACTICES.md
|
|
3
|
+
* §14/15, per the task's explicit framing): the state machine is a pure decision core that never
|
|
4
|
+
* imports `@anthropic-ai/sdk`; it only ever sees this interface, which any fake can implement in
|
|
5
|
+
* tests. `AnthropicFixProvider` (anthropicFixProvider.ts) is the one real implementation.
|
|
6
|
+
*/
|
|
7
|
+
import { sha256Hex } from '@spikedpunch/align-core';
|
|
8
|
+
/** Canonical, order-independent serialization of a `FixProviderInput` for memoization hashing. */
|
|
9
|
+
function canonicalize(input) {
|
|
10
|
+
const files = [...input.fileContents.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
11
|
+
const violations = [...input.violations]
|
|
12
|
+
.map((v) => ({ id: v.id, ruleId: v.ruleId, file: v.file, range: v.range, snippet: v.snippet }))
|
|
13
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
14
|
+
const symbols = [...input.condensedSymbolTable]
|
|
15
|
+
.map((s) => ({ file: s.file, exports: [...s.exports].sort() }))
|
|
16
|
+
.sort((a, b) => a.file.localeCompare(b.file));
|
|
17
|
+
const explanations = [...input.ruleExplanations].sort((a, b) => a.ruleId.localeCompare(b.ruleId));
|
|
18
|
+
return JSON.stringify({ files, violations, symbols, explanations, previousFailure: input.previousFailure ?? null });
|
|
19
|
+
}
|
|
20
|
+
export function hashFixProviderInput(input) {
|
|
21
|
+
return sha256Hex(canonicalize(input));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Wraps any `FixProvider` with in-memory, per-run memoization by `hash(input)`. A `FixProposal` is
|
|
25
|
+
* a pure function of its input in spirit (ADR 010: "preserves pure-function memoization
|
|
26
|
+
* semantics") — an identical GROUP + identical `previousFailure` context should never re-invoke
|
|
27
|
+
* the underlying provider (saves cost, and makes REPAIR retries with unchanged state a no-op
|
|
28
|
+
* instead of an infinite-cost loop).
|
|
29
|
+
*/
|
|
30
|
+
export class MemoizingFixProvider {
|
|
31
|
+
inner;
|
|
32
|
+
cache = new Map();
|
|
33
|
+
callCount = 0;
|
|
34
|
+
constructor(inner) {
|
|
35
|
+
this.inner = inner;
|
|
36
|
+
}
|
|
37
|
+
async proposeFix(input) {
|
|
38
|
+
const key = hashFixProviderInput(input);
|
|
39
|
+
const cached = this.cache.get(key);
|
|
40
|
+
if (cached !== undefined)
|
|
41
|
+
return cached;
|
|
42
|
+
this.callCount += 1;
|
|
43
|
+
const promise = this.inner.proposeFix(input);
|
|
44
|
+
this.cache.set(key, promise);
|
|
45
|
+
// Never memoize a rejection — a transient failure shouldn't poison the cache for this run.
|
|
46
|
+
promise.catch(() => this.cache.delete(key));
|
|
47
|
+
return promise;
|
|
48
|
+
}
|
|
49
|
+
/** Test/observability hook — number of calls that actually reached the inner provider. */
|
|
50
|
+
get providerCallCount() {
|
|
51
|
+
return this.callCount;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=fixProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixProvider.js","sourceRoot":"","sources":["../src/fixProvider.ts"],"names":[],"mappings":"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"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Runs the target repo's own prettier binary on `paths` (relative to `rootDir`) if — and only if
|
|
2
|
+
* — the repo both declares prettier as a dependency AND has it installed locally. Never throws:
|
|
3
|
+
* a formatting failure is not fixable by the agent and must not halt the loop. */
|
|
4
|
+
export declare function formatIfAvailable(rootDir: string, paths: readonly string[]): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAiCA;;kFAEkF;AAClF,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhG"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mechanical post-format (APPLY step, IMPLEMENTATION_PLAN.md Stage 4): if the TARGET repo being
|
|
3
|
+
* fixed exposes prettier, run it on touched files before committing; skip silently otherwise.
|
|
4
|
+
* This is a target-repo runtime detection, not align's own repo's tooling — align's own repo has
|
|
5
|
+
* no prettier config (confirmed at Stage-4 research time), so this must never assume prettier is
|
|
6
|
+
* present.
|
|
7
|
+
*/
|
|
8
|
+
import { execFile } from 'node:child_process';
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import { promisify } from 'node:util';
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
13
|
+
function prettierBinaryPath(rootDir) {
|
|
14
|
+
const binPath = path.join(rootDir, 'node_modules', '.bin', process.platform === 'win32' ? 'prettier.cmd' : 'prettier');
|
|
15
|
+
return fs.existsSync(binPath) ? binPath : undefined;
|
|
16
|
+
}
|
|
17
|
+
function targetRepoDeclaresPrettier(rootDir) {
|
|
18
|
+
const pkgPath = path.join(rootDir, 'package.json');
|
|
19
|
+
if (!fs.existsSync(pkgPath))
|
|
20
|
+
return false;
|
|
21
|
+
try {
|
|
22
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
23
|
+
return Boolean(pkg.dependencies?.prettier ?? pkg.devDependencies?.prettier);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Runs the target repo's own prettier binary on `paths` (relative to `rootDir`) if — and only if
|
|
30
|
+
* — the repo both declares prettier as a dependency AND has it installed locally. Never throws:
|
|
31
|
+
* a formatting failure is not fixable by the agent and must not halt the loop. */
|
|
32
|
+
export async function formatIfAvailable(rootDir, paths) {
|
|
33
|
+
if (paths.length === 0)
|
|
34
|
+
return;
|
|
35
|
+
if (!targetRepoDeclaresPrettier(rootDir))
|
|
36
|
+
return;
|
|
37
|
+
const bin = prettierBinaryPath(rootDir);
|
|
38
|
+
if (bin === undefined)
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
await execFileAsync(bin, ['--write', ...paths], { cwd: rootDir });
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Mechanical formatting is best-effort; a prettier crash must not halt the fix loop.
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,SAAS,kBAAkB,CAAC,OAAe;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IACvH,OAAO,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AACtD,CAAC;AAED,SAAS,0BAA0B,CAAC,OAAe;IACjD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IACnD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAGtD,CAAC;QACF,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,IAAI,GAAG,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;kFAEkF;AAClF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAe,EAAE,KAAwB;IAC/E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAC/B,IAAI,CAAC,0BAA0B,CAAC,OAAO,CAAC;QAAE,OAAO;IACjD,MAAM,GAAG,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO;IAC9B,IAAI,CAAC;QACH,MAAM,aAAa,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,qFAAqF;IACvF,CAAC;AACH,CAAC"}
|