@tangle-network/agent-runtime 0.69.0 → 0.70.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/README.md +81 -13
- package/dist/agent.d.ts +4 -5
- package/dist/agent.js +3 -6
- package/dist/agent.js.map +1 -1
- package/dist/chunk-BYZCXQHF.js +474 -0
- package/dist/chunk-BYZCXQHF.js.map +1 -0
- package/dist/{chunk-G6U3GVK2.js → chunk-EDCVUZZC.js} +2 -2
- package/dist/{chunk-RTDI2DHD.js → chunk-L5ZFBVT6.js} +6 -6
- package/dist/chunk-L5ZFBVT6.js.map +1 -0
- package/dist/{chunk-NLRA6434.js → chunk-QXWGSDAQ.js} +716 -535
- package/dist/chunk-QXWGSDAQ.js.map +1 -0
- package/dist/{chunk-QZ6FA5LM.js → chunk-ZNQVMMR5.js} +3 -3
- package/dist/{coordination-Curpzeyc.d.ts → coordination-C7WxwHXq.d.ts} +8 -1
- package/dist/{delegates-CLFNAKyi.d.ts → delegates-DqAgo32T.d.ts} +105 -10
- package/dist/{improvement-adapter-BC4HhuAR.d.ts → improvement-adapter-BVuMragr.d.ts} +1 -1
- package/dist/index.d.ts +439 -100
- package/dist/index.js +583 -125
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +2 -3
- package/dist/{loop-runner-bin-B6dzNZC8.d.ts → loop-runner-bin-B0NeLTRd.d.ts} +4 -4
- package/dist/loop-runner-bin.d.ts +7 -10
- package/dist/loop-runner-bin.js +4 -7
- package/dist/loops.d.ts +3149 -16
- package/dist/loops.js +34 -63
- package/dist/mcp/bin.js +14 -16
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +8 -12
- package/dist/mcp/index.js +16 -19
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-CA2N3-Ak.d.ts → openai-tools-DPx9Gzvn.d.ts} +1 -1
- package/dist/profiles.d.ts +94 -6
- package/dist/profiles.js +320 -9
- package/dist/profiles.js.map +1 -1
- package/dist/{router-client-30Y_pca8.d.ts → router-client-C7kp_ECN.d.ts} +37 -1
- package/dist/{substrate-CUgk7F7s.d.ts → substrate-BoRXgvka.d.ts} +52 -1
- package/dist/{types-Crxftafi.d.ts → types-BYa2ZOAx.d.ts} +83 -2
- package/dist/{types-p8dWBIXL.d.ts → types-DJu6TBGp.d.ts} +2 -16
- package/dist/{worktree-fanout-DUiKPApb.d.ts → worktree-fanout-gNfl0Byj.d.ts} +9 -37
- package/package.json +9 -39
- package/dist/analyst-loop.d.ts +0 -52
- package/dist/analyst-loop.js +0 -11
- package/dist/analyst-loop.js.map +0 -1
- package/dist/audit.d.ts +0 -93
- package/dist/audit.js +0 -312
- package/dist/audit.js.map +0 -1
- package/dist/chunk-4B6U4CVQ.js +0 -15
- package/dist/chunk-4B6U4CVQ.js.map +0 -1
- package/dist/chunk-K4FQSEXQ.js +0 -214
- package/dist/chunk-K4FQSEXQ.js.map +0 -1
- package/dist/chunk-NLRA6434.js.map +0 -1
- package/dist/chunk-O2UPHN7X.js +0 -114
- package/dist/chunk-O2UPHN7X.js.map +0 -1
- package/dist/chunk-P5OKDSLB.js +0 -580
- package/dist/chunk-P5OKDSLB.js.map +0 -1
- package/dist/chunk-RTDI2DHD.js.map +0 -1
- package/dist/chunk-VLF5RHEQ.js +0 -143
- package/dist/chunk-VLF5RHEQ.js.map +0 -1
- package/dist/coder-2leJPOvC.d.ts +0 -52
- package/dist/improvement.d.ts +0 -208
- package/dist/improvement.js +0 -343
- package/dist/improvement.js.map +0 -1
- package/dist/local-harness-BE_h8szs.d.ts +0 -93
- package/dist/platform.d.ts +0 -255
- package/dist/platform.js +0 -229
- package/dist/platform.js.map +0 -1
- package/dist/run-loop-D3PwlG7J.d.ts +0 -112
- package/dist/runtime-hooks-C7JwKb9E.d.ts +0 -70
- package/dist/runtime.d.ts +0 -3120
- package/dist/runtime.js +0 -263
- package/dist/runtime.js.map +0 -1
- package/dist/topology.d.ts +0 -126
- package/dist/topology.js +0 -333
- package/dist/topology.js.map +0 -1
- package/dist/workflow.d.ts +0 -551
- package/dist/workflow.js +0 -1781
- package/dist/workflow.js.map +0 -1
- /package/dist/{chunk-G6U3GVK2.js.map → chunk-EDCVUZZC.js.map} +0 -0
- /package/dist/{chunk-QZ6FA5LM.js.map → chunk-ZNQVMMR5.js.map} +0 -0
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { AgentProfile } from '@tangle-network/agent-interface';
|
|
2
2
|
import { AnalystFinding, DefaultVerdict } from '@tangle-network/agent-eval';
|
|
3
|
-
import { h as AgentSpec, E as ExecutorRegistry, B as Budget, A as Agent, i as SpawnJournal, g as ResultBlobStore, j as RootHandle, k as SupervisedResult, N as NodeId, l as Settled, m as Spend, S as Scope, n as Executor, G as GitRunner } from './delegates-
|
|
4
|
-
import { I as Iteration } from './types-
|
|
3
|
+
import { h as AgentSpec, E as ExecutorRegistry, B as Budget, A as Agent, i as SpawnJournal, g as ResultBlobStore, j as RootHandle, k as SupervisedResult, N as NodeId, l as Settled, m as Spend, S as Scope, n as Executor, L as LocalHarness, G as GitRunner, r as runLocalHarness } from './delegates-DqAgo32T.js';
|
|
4
|
+
import { b as RuntimeHooks, I as Iteration } from './types-BYa2ZOAx.js';
|
|
5
5
|
import { BackendType } from '@tangle-network/sandbox';
|
|
6
|
-
import { R as RuntimeHooks } from './runtime-hooks-C7JwKb9E.js';
|
|
7
|
-
import { L as LocalHarness, r as runLocalHarness } from './local-harness-BE_h8szs.js';
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* @experimental
|
|
@@ -787,6 +785,12 @@ type EqualKOnCost = (arms: ReadonlyArray<EqualKArm>, options?: EqualKOnCostOptio
|
|
|
787
785
|
* fail-closed (not delivered), never a crash.
|
|
788
786
|
*/
|
|
789
787
|
|
|
788
|
+
/**
|
|
789
|
+
* The deployable completion oracle passed to {@link gateOnDeliverable}: a `check` that
|
|
790
|
+
* decides DELIVERED (settles `valid` ⟺ it resolves true) plus an optional `describe` of
|
|
791
|
+
* what the spawn was supposed to produce. The check reads the child's output — never the
|
|
792
|
+
* model judging itself.
|
|
793
|
+
*/
|
|
790
794
|
interface DeliverableSpec<Out = unknown> {
|
|
791
795
|
/** The deployable check that decides DELIVERED. `settled.valid ⟺ this resolves true`. */
|
|
792
796
|
check: (out: Out) => boolean | Promise<boolean>;
|
|
@@ -813,15 +817,6 @@ declare function gateOnDeliverable<Out>(inner: Executor<Out>, deliverable: Deliv
|
|
|
813
817
|
* (`isNonEmptyPatch` / `touchesSecretPath`) for reuse outside the full scorer.
|
|
814
818
|
*/
|
|
815
819
|
|
|
816
|
-
/** @experimental The patch + its derived PASS signals the mechanical gate decides on. */
|
|
817
|
-
interface CoderCheckInput {
|
|
818
|
-
/** The unified diff produced by the run. */
|
|
819
|
-
patch: string;
|
|
820
|
-
/** Did `testCmd` exit clean? */
|
|
821
|
-
testsPassed: boolean;
|
|
822
|
-
/** Did `typecheckCmd` exit clean? */
|
|
823
|
-
typecheckPassed: boolean;
|
|
824
|
-
}
|
|
825
820
|
/** @experimental The per-task constraints the mechanical gate enforces. */
|
|
826
821
|
interface CoderCheckConstraints {
|
|
827
822
|
/** Default 400. Hard cap; gate fails when exceeded. */
|
|
@@ -829,29 +824,6 @@ interface CoderCheckConstraints {
|
|
|
829
824
|
/** Literal path prefixes the patch must not touch. */
|
|
830
825
|
forbiddenPaths?: string[];
|
|
831
826
|
}
|
|
832
|
-
/** The unified-diff paths the patch touches — the `+++`/`---` headers, de-`a/`/`b/`-prefixed,
|
|
833
|
-
* with `/dev/null` (a delete's other side) dropped. */
|
|
834
|
-
declare function touchedPathsFromPatch(patch: string): string[];
|
|
835
|
-
/** Count the added/removed content lines in a unified diff (excludes the `+++`/`---` headers). */
|
|
836
|
-
declare function countDiffLines(patch: string): number;
|
|
837
|
-
/** True when the patch actually changes something — at least one touched path AND non-blank body.
|
|
838
|
-
* An empty patch can trivially "pass" tests/typecheck (nothing changed) yet does no work. */
|
|
839
|
-
declare function isNonEmptyPatch(patch: string): boolean;
|
|
840
|
-
/** The credential-shaped paths the patch touches (always-on floor, independent of `forbiddenPaths`). */
|
|
841
|
-
declare function touchesSecretPath(patch: string): string[];
|
|
842
|
-
/**
|
|
843
|
-
* @experimental
|
|
844
|
-
*
|
|
845
|
-
* The pure mechanical gate — the SINGLE source of the no-op / always-on secret-path floor /
|
|
846
|
-
* diff-size / forbidden-path / test / typecheck checks. No I/O: it scores a patch + its
|
|
847
|
-
* already-derived pass signals.
|
|
848
|
-
*
|
|
849
|
-
* Checks in order: (1) no-op rejection, (2) always-on secret-path floor (independent of
|
|
850
|
-
* `forbiddenPaths`), (3) forbidden-path, (4) diff-size cap, (5) tests, (6) typecheck.
|
|
851
|
-
* Aggregate score: `0.5*tests + 0.3*typecheck + 0.2*(1 - diffLines/maxDiff)`; `valid` is the
|
|
852
|
-
* conjunction of all six.
|
|
853
|
-
*/
|
|
854
|
-
declare function runCoderChecks(input: CoderCheckInput, constraints?: CoderCheckConstraints): DefaultVerdict;
|
|
855
827
|
|
|
856
828
|
/**
|
|
857
829
|
* @experimental
|
|
@@ -1096,4 +1068,4 @@ interface WorktreeFanoutOptions extends PatchDeliverableOptions {
|
|
|
1096
1068
|
*/
|
|
1097
1069
|
declare function worktreeFanout<Task>(options: WorktreeFanoutOptions): CombinatorShape<Task, WorktreePatchArtifact>;
|
|
1098
1070
|
|
|
1099
|
-
export { type
|
|
1071
|
+
export { type Verify as $, type AuthoredHarness as A, type LoopUntilState as B, type CorpusRecord as C, type DefinePersonaInput as D, type EqualKArm as E, type FanoutOptions as F, type Panel as G, type PanelJudge as H, type PanelVerdict as I, type PatchDeliverableOptions as J, type PersonaContext as K, type LoopUntilSpec as L, type PersonaExecutors as M, type Pipeline as N, type Outcome as O, type PanelSpec as P, type RenderCorpusToInstructions as Q, type RenderCorpusToInstructionsOptions as R, type ScopeAnalyzeInput as S, type TrajectoryReportOptions as T, type RunPersonified as U, type VerifySpec as V, type WinnerStrategy as W, type ShapeBudget as X, type ShapeContext as Y, type TrajectoryNode as Z, type TrajectoryReportFn as _, type WorktreeFanoutOptions as a, type Widen as a0, type WidenDecision as a1, type WidenLineage as a2, type WorktreeCliExecutorOptions as a3, type WorktreeCommandResult as a4, createWorktreeCliExecutor as a5, gateOnDeliverable as a6, patchDelivered as a7, worktreeFanout as a8, type WorktreePatchArtifact as b, type Corpus as c, type AssertTraceDerivedFindings as d, type SteerContext as e, type ScopeAnalyst as f, type CombinatorShape as g, type ScopeWidenGate as h, type PipelineStage as i, type FanoutWinnerSelector as j, type WidenSpec as k, type CorpusFilter as l, type Persona as m, type RunPersonifiedOptions as n, type ShapeRegistry as o, type LoopShape as p, type EqualKOnCostOptions as q, type EqualKVerdict as r, type TrajectoryReport as s, type DeliverableSpec as t, type DefinePersona as u, type EqualKOnCost as v, type Fanout as w, type FanoutSynthesis as x, type FlatWidenGate as y, type LoopUntil as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.70.0",
|
|
4
4
|
"description": "Shared task-lifecycle skeleton for agents: a recursive loop kernel for chat turns, one-shot tasks, and multi-attempt loops, with trace capture and eval-gated self-improvement. Domain behavior lives in adapters; scoring and ship-gates in @tangle-network/agent-eval.",
|
|
5
5
|
"homepage": "https://github.com/tangle-network/agent-runtime#readme",
|
|
6
6
|
"repository": {
|
|
@@ -19,21 +19,6 @@
|
|
|
19
19
|
"import": "./dist/index.js",
|
|
20
20
|
"default": "./dist/index.js"
|
|
21
21
|
},
|
|
22
|
-
"./platform": {
|
|
23
|
-
"types": "./dist/platform.d.ts",
|
|
24
|
-
"import": "./dist/platform.js",
|
|
25
|
-
"default": "./dist/platform.js"
|
|
26
|
-
},
|
|
27
|
-
"./analyst-loop": {
|
|
28
|
-
"types": "./dist/analyst-loop.d.ts",
|
|
29
|
-
"import": "./dist/analyst-loop.js",
|
|
30
|
-
"default": "./dist/analyst-loop.js"
|
|
31
|
-
},
|
|
32
|
-
"./improvement": {
|
|
33
|
-
"types": "./dist/improvement.d.ts",
|
|
34
|
-
"import": "./dist/improvement.js",
|
|
35
|
-
"default": "./dist/improvement.js"
|
|
36
|
-
},
|
|
37
22
|
"./agent": {
|
|
38
23
|
"types": "./dist/agent.d.ts",
|
|
39
24
|
"import": "./dist/agent.js",
|
|
@@ -44,36 +29,16 @@
|
|
|
44
29
|
"import": "./dist/intelligence.js",
|
|
45
30
|
"default": "./dist/intelligence.js"
|
|
46
31
|
},
|
|
47
|
-
"./runtime": {
|
|
48
|
-
"types": "./dist/runtime.d.ts",
|
|
49
|
-
"import": "./dist/runtime.js",
|
|
50
|
-
"default": "./dist/runtime.js"
|
|
51
|
-
},
|
|
52
32
|
"./loops": {
|
|
53
33
|
"types": "./dist/loops.d.ts",
|
|
54
34
|
"import": "./dist/loops.js",
|
|
55
35
|
"default": "./dist/loops.js"
|
|
56
36
|
},
|
|
57
|
-
"./topology": {
|
|
58
|
-
"types": "./dist/topology.d.ts",
|
|
59
|
-
"import": "./dist/topology.js",
|
|
60
|
-
"default": "./dist/topology.js"
|
|
61
|
-
},
|
|
62
|
-
"./workflow": {
|
|
63
|
-
"types": "./dist/workflow.d.ts",
|
|
64
|
-
"import": "./dist/workflow.js",
|
|
65
|
-
"default": "./dist/workflow.js"
|
|
66
|
-
},
|
|
67
37
|
"./profiles": {
|
|
68
38
|
"types": "./dist/profiles.d.ts",
|
|
69
39
|
"import": "./dist/profiles.js",
|
|
70
40
|
"default": "./dist/profiles.js"
|
|
71
41
|
},
|
|
72
|
-
"./audit": {
|
|
73
|
-
"types": "./dist/audit.d.ts",
|
|
74
|
-
"import": "./dist/audit.js",
|
|
75
|
-
"default": "./dist/audit.js"
|
|
76
|
-
},
|
|
77
42
|
"./mcp": {
|
|
78
43
|
"types": "./dist/mcp/index.d.ts",
|
|
79
44
|
"import": "./dist/mcp/index.js",
|
|
@@ -102,16 +67,21 @@
|
|
|
102
67
|
"lint:fix": "biome check --write src tests examples",
|
|
103
68
|
"typecheck": "tsc --noEmit && pnpm run typecheck:examples",
|
|
104
69
|
"typecheck:examples": "tsc --noEmit -p tsconfig.examples.json",
|
|
105
|
-
"verify:package": "node scripts/verify-package-exports.mjs"
|
|
70
|
+
"verify:package": "node scripts/verify-package-exports.mjs",
|
|
71
|
+
"docs:api": "typedoc",
|
|
72
|
+
"docs:freshness": "node scripts/check-docs-freshness.mjs",
|
|
73
|
+
"docs:check": "pnpm run docs:api && git diff --exit-code -- docs/api && pnpm run docs:freshness"
|
|
106
74
|
},
|
|
107
75
|
"devDependencies": {
|
|
108
76
|
"@biomejs/biome": "^2.4.15",
|
|
109
|
-
"@tangle-network/agent-eval": ">=0.
|
|
77
|
+
"@tangle-network/agent-eval": ">=0.95.0 <1.0.0",
|
|
110
78
|
"@tangle-network/agent-interface": ">=0.10.0 <1.0.0",
|
|
111
79
|
"@tangle-network/sandbox": ">=0.8.0 <1.0.0",
|
|
112
80
|
"@types/node": "^25.9.3",
|
|
113
81
|
"playwright": "^1.61.0",
|
|
114
82
|
"tsup": "^8.0.0",
|
|
83
|
+
"typedoc": "0.28.19",
|
|
84
|
+
"typedoc-plugin-markdown": "4.12.0",
|
|
115
85
|
"typescript": "^5.7.0",
|
|
116
86
|
"vitest": "^3.0.0"
|
|
117
87
|
},
|
|
@@ -132,7 +102,7 @@
|
|
|
132
102
|
"license": "MIT",
|
|
133
103
|
"packageManager": "pnpm@10.28.0",
|
|
134
104
|
"peerDependencies": {
|
|
135
|
-
"@tangle-network/agent-eval": ">=0.
|
|
105
|
+
"@tangle-network/agent-eval": ">=0.95.0 <1.0.0",
|
|
136
106
|
"@tangle-network/agent-interface": ">=0.10.0 <1.0.0",
|
|
137
107
|
"@tangle-network/agent-knowledge": ">=1.7.0 <2.0.0",
|
|
138
108
|
"@tangle-network/sandbox": ">=0.8.0 <1.0.0",
|
package/dist/analyst-loop.d.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { TraceAnalystByteBudgets, TraceAnalysisStore } from '@tangle-network/agent-eval';
|
|
2
|
-
import { I as Iteration } from './types-Crxftafi.js';
|
|
3
|
-
import { R as RunAnalystLoopOpts, a as RunAnalystLoopResult } from './types-p8dWBIXL.js';
|
|
4
|
-
export { A as AnalystLoopEvent, b as AnalystRegistryLike, c as AnalystRegistryStreamingLike, d as AutoApplyPolicy, F as FindingsStoreLike, I as ImprovementAdapter, e as ImprovementEditBatch, f as ImprovementReport, K as KnowledgeAdapter, g as KnowledgeProposalBatch, h as KnowledgeReport } from './types-p8dWBIXL.js';
|
|
5
|
-
import '@tangle-network/sandbox';
|
|
6
|
-
import './runtime-hooks-C7JwKb9E.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @experimental
|
|
10
|
-
*
|
|
11
|
-
* The read seam that closes the autonomous loop: project a round's `Iteration[]`
|
|
12
|
-
* (each carrying its raw `SandboxEvent[]`) into an in-memory `TraceAnalysisStore`, the
|
|
13
|
-
* read interface the trace analysts query. `runAnalystLoop` has had zero consumers
|
|
14
|
-
* because nothing turned a loop's iterations into a store — this is that bridge.
|
|
15
|
-
*
|
|
16
|
-
* One iteration → one trace. The iteration is the root AGENT span; each `SandboxEvent`
|
|
17
|
-
* becomes a child span (LLM for llm_call events, TOOL for tool events, else SPAN), so an
|
|
18
|
-
* analyst can walk a shot's trace, cluster its errors, and emit findings the driver
|
|
19
|
-
* steers on. Projection is best-effort over the FLAT SandboxEvent shape (no per-event
|
|
20
|
-
* lineage yet — that's the richer-trace gap); it never fabricates — an errored iteration
|
|
21
|
-
* surfaces a real ERROR span carrying the real message.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Build an in-memory `TraceAnalysisStore` over a loop round's iterations. Fail-loud on an
|
|
26
|
-
* empty round — there is nothing for an analyst to read, and a silent empty store would
|
|
27
|
-
* mask a broken capture path.
|
|
28
|
-
*/
|
|
29
|
-
declare function iterationsToTraceStore<Task, Output>(iterations: ReadonlyArray<Iteration<Task, Output>>, budgets?: TraceAnalystByteBudgets): TraceAnalysisStore;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* `runAnalystLoop` — the one call agent apps reach for to close the
|
|
33
|
-
* recursive-self-improvement loop.
|
|
34
|
-
*
|
|
35
|
-
* 1. Load baseline findings (last run, or the slice the caller specifies)
|
|
36
|
-
* 2. Run the analyst registry with priorFindings injected
|
|
37
|
-
* 3. Persist the new run's findings to the ledger
|
|
38
|
-
* 4. Diff the new run against the baseline
|
|
39
|
-
* 5. Hand the findings to the knowledge adapter → proposals (and
|
|
40
|
-
* optionally apply them) → wiki edits
|
|
41
|
-
* 6. Hand the findings to the improvement adapter → prompt / tool /
|
|
42
|
-
* scaffolding edits (review-only by default)
|
|
43
|
-
* 7. Return a single report the consumer renders / persists / acts on.
|
|
44
|
-
*
|
|
45
|
-
* Adapters are optional: the loop works as a "run + diff + report"
|
|
46
|
-
* primitive when no adapters are wired; it closes end-to-end when
|
|
47
|
-
* both adapters are wired.
|
|
48
|
-
*/
|
|
49
|
-
|
|
50
|
-
declare function runAnalystLoop<TProposal = unknown, TEdit = unknown>(opts: RunAnalystLoopOpts): Promise<RunAnalystLoopResult<TProposal, TEdit>>;
|
|
51
|
-
|
|
52
|
-
export { RunAnalystLoopOpts, RunAnalystLoopResult, iterationsToTraceStore, runAnalystLoop };
|
package/dist/analyst-loop.js
DELETED
package/dist/analyst-loop.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/audit.d.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { U as UiFinding, a as UiLens } from './substrate-CUgk7F7s.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @experimental
|
|
5
|
-
*
|
|
6
|
-
* UI-audit issue writer — pure I/O. Takes a workspace dir + `UiFinding[]`
|
|
7
|
-
* and emits:
|
|
8
|
-
* - `<workspace>/issues/NNN--<lens>--<slug>.md` — one self-contained
|
|
9
|
-
* GitHub-issue-ready Markdown per finding, with embedded screenshot
|
|
10
|
-
* references.
|
|
11
|
-
* - `<workspace>/registry.json` — finding index for dedup and audit
|
|
12
|
-
* resume across iterations.
|
|
13
|
-
* - `<workspace>/index.md` — human-readable rollup (severity / lens /
|
|
14
|
-
* route counts plus a sorted finding list).
|
|
15
|
-
*
|
|
16
|
-
* The writer is deterministic, idempotent for `appendFindings()`, and
|
|
17
|
-
* never invokes an LLM. It assigns the next monotonic id to a finding the
|
|
18
|
-
* caller did not pre-id.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/** @experimental */
|
|
22
|
-
interface AuditRegistry {
|
|
23
|
-
schemaVersion: 1;
|
|
24
|
-
findings: UiFinding[];
|
|
25
|
-
/** Route → URL + captures sidecar; preserved across runs. */
|
|
26
|
-
routes: Record<string, {
|
|
27
|
-
url?: string;
|
|
28
|
-
captures: AuditRegistryCapture[];
|
|
29
|
-
}>;
|
|
30
|
-
}
|
|
31
|
-
/** @experimental */
|
|
32
|
-
interface AuditRegistryCapture {
|
|
33
|
-
file: string;
|
|
34
|
-
viewport?: string;
|
|
35
|
-
fullPage?: boolean;
|
|
36
|
-
elementSelector?: string;
|
|
37
|
-
capturedAt: string;
|
|
38
|
-
}
|
|
39
|
-
/** @experimental */
|
|
40
|
-
declare function initAuditWorkspace(workspaceDir: string): Promise<void>;
|
|
41
|
-
/** @experimental */
|
|
42
|
-
declare function readAuditRegistry(workspaceDir: string): Promise<AuditRegistry>;
|
|
43
|
-
/** @experimental */
|
|
44
|
-
interface AppendFindingsResult {
|
|
45
|
-
/** Findings with id + createdAt assigned, in input order. */
|
|
46
|
-
written: UiFinding[];
|
|
47
|
-
/** Workspace-relative path to each issue Markdown file, in input order. */
|
|
48
|
-
files: string[];
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Append findings to a workspace, writing one Markdown file per finding
|
|
52
|
-
* and updating registry.json. Assigns monotonically increasing ids to
|
|
53
|
-
* findings that arrived without one.
|
|
54
|
-
*
|
|
55
|
-
* Findings already carrying an id that collides with the registry are
|
|
56
|
-
* rejected — callers must either freshly mint findings (id undefined) or
|
|
57
|
-
* use a separate update path. This protects against accidental overwrite.
|
|
58
|
-
*
|
|
59
|
-
* @experimental
|
|
60
|
-
*/
|
|
61
|
-
declare function appendFindings(workspaceDir: string, findings: readonly UiFinding[]): Promise<AppendFindingsResult>;
|
|
62
|
-
/** @experimental */
|
|
63
|
-
interface RegisterCapturesOptions {
|
|
64
|
-
route: string;
|
|
65
|
-
url?: string;
|
|
66
|
-
captures: readonly AuditRegistryCapture[];
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Record screenshots taken for a route in the registry, without filing a
|
|
70
|
-
* finding. Useful when the auditor wants to remember which captures
|
|
71
|
-
* exist for resume / dedup purposes.
|
|
72
|
-
*
|
|
73
|
-
* @experimental
|
|
74
|
-
*/
|
|
75
|
-
declare function registerCaptures(workspaceDir: string, options: RegisterCapturesOptions): Promise<void>;
|
|
76
|
-
/** @experimental */
|
|
77
|
-
interface AuditIndex {
|
|
78
|
-
/** Total findings in the workspace. */
|
|
79
|
-
total: number;
|
|
80
|
-
bySeverity: Record<UiFinding['severity'], number>;
|
|
81
|
-
byLens: Partial<Record<UiLens, number>>;
|
|
82
|
-
byRoute: Record<string, number>;
|
|
83
|
-
}
|
|
84
|
-
/** @experimental */
|
|
85
|
-
declare function summarizeRegistry(reg: AuditRegistry): AuditIndex;
|
|
86
|
-
/**
|
|
87
|
-
* Regenerate `<workspace>/index.md` from registry.json.
|
|
88
|
-
*
|
|
89
|
-
* @experimental
|
|
90
|
-
*/
|
|
91
|
-
declare function writeAuditIndex(workspaceDir: string): Promise<string>;
|
|
92
|
-
|
|
93
|
-
export { type AppendFindingsResult, type AuditIndex, type AuditRegistry, type AuditRegistryCapture, type RegisterCapturesOptions, appendFindings, initAuditWorkspace, readAuditRegistry, registerCaptures, summarizeRegistry, writeAuditIndex };
|
package/dist/audit.js
DELETED
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
slugify
|
|
3
|
-
} from "./chunk-4B6U4CVQ.js";
|
|
4
|
-
import {
|
|
5
|
-
UI_FINDING_SEVERITIES,
|
|
6
|
-
UI_LENSES
|
|
7
|
-
} from "./chunk-WIR4HOOJ.js";
|
|
8
|
-
import "./chunk-DGUM43GV.js";
|
|
9
|
-
|
|
10
|
-
// src/audit/issue-writer.ts
|
|
11
|
-
import { promises as fs } from "fs";
|
|
12
|
-
import path from "path";
|
|
13
|
-
var SEVERITY_ORDER = {
|
|
14
|
-
critical: 0,
|
|
15
|
-
high: 1,
|
|
16
|
-
med: 2,
|
|
17
|
-
low: 3
|
|
18
|
-
};
|
|
19
|
-
function assertValidWorkspaceDir(dir) {
|
|
20
|
-
if (typeof dir !== "string" || dir.length === 0) {
|
|
21
|
-
throw new Error(
|
|
22
|
-
`audit-writer: workspaceDir must be a non-empty string (got ${JSON.stringify(dir)})`
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
if (!path.isAbsolute(dir)) {
|
|
26
|
-
throw new Error(
|
|
27
|
-
`audit-writer: workspaceDir must be an absolute path (got ${JSON.stringify(dir)})`
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
if (dir.split(path.sep).includes("..")) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
`audit-writer: workspaceDir must not contain '..' segments (got ${JSON.stringify(dir)})`
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
async function initAuditWorkspace(workspaceDir) {
|
|
37
|
-
assertValidWorkspaceDir(workspaceDir);
|
|
38
|
-
await fs.mkdir(path.join(workspaceDir, "issues"), { recursive: true });
|
|
39
|
-
await fs.mkdir(path.join(workspaceDir, "screenshots"), { recursive: true });
|
|
40
|
-
const regPath = path.join(workspaceDir, "registry.json");
|
|
41
|
-
try {
|
|
42
|
-
await fs.access(regPath);
|
|
43
|
-
} catch {
|
|
44
|
-
const fresh = { schemaVersion: 1, findings: [], routes: {} };
|
|
45
|
-
await fs.writeFile(regPath, JSON.stringify(fresh, null, 2));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
async function readAuditRegistry(workspaceDir) {
|
|
49
|
-
assertValidWorkspaceDir(workspaceDir);
|
|
50
|
-
const regPath = path.join(workspaceDir, "registry.json");
|
|
51
|
-
const text = await fs.readFile(regPath, "utf8");
|
|
52
|
-
const parsed = JSON.parse(text);
|
|
53
|
-
if (!parsed || typeof parsed !== "object") {
|
|
54
|
-
throw new Error(`audit-writer: registry.json at ${regPath} is not a JSON object`);
|
|
55
|
-
}
|
|
56
|
-
const reg = parsed;
|
|
57
|
-
if (reg.schemaVersion !== 1 || !Array.isArray(reg.findings) || typeof reg.routes !== "object") {
|
|
58
|
-
throw new Error(`audit-writer: registry.json at ${regPath} has an unrecognized shape`);
|
|
59
|
-
}
|
|
60
|
-
return reg;
|
|
61
|
-
}
|
|
62
|
-
async function writeAuditRegistry(workspaceDir, reg) {
|
|
63
|
-
const regPath = path.join(workspaceDir, "registry.json");
|
|
64
|
-
const tmpPath = `${regPath}.tmp-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
65
|
-
await fs.writeFile(tmpPath, JSON.stringify(reg, null, 2));
|
|
66
|
-
await fs.rename(tmpPath, regPath);
|
|
67
|
-
}
|
|
68
|
-
var workspaceLocks = /* @__PURE__ */ new Map();
|
|
69
|
-
async function withWorkspaceLock(workspaceDir, fn) {
|
|
70
|
-
const key = path.resolve(workspaceDir);
|
|
71
|
-
const prev = workspaceLocks.get(key) ?? Promise.resolve();
|
|
72
|
-
let signalDone;
|
|
73
|
-
const done = new Promise((resolve) => {
|
|
74
|
-
signalDone = resolve;
|
|
75
|
-
});
|
|
76
|
-
workspaceLocks.set(key, done);
|
|
77
|
-
try {
|
|
78
|
-
await prev;
|
|
79
|
-
return await fn();
|
|
80
|
-
} finally {
|
|
81
|
-
signalDone();
|
|
82
|
-
if (workspaceLocks.get(key) === done) workspaceLocks.delete(key);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function assertFindingShape(f, index) {
|
|
86
|
-
const where = `audit-writer: findings[${index}]`;
|
|
87
|
-
if (!UI_LENSES.includes(f.lens)) {
|
|
88
|
-
throw new Error(
|
|
89
|
-
`${where}: invalid lens ${JSON.stringify(f.lens)}; one of ${UI_LENSES.join("|")}`
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
if (!UI_FINDING_SEVERITIES.includes(f.severity)) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
`${where}: invalid severity ${JSON.stringify(f.severity)}; one of ${UI_FINDING_SEVERITIES.join("|")}`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
for (const field of ["title", "route", "observation", "impact", "suggestedFix"]) {
|
|
98
|
-
const v = f[field];
|
|
99
|
-
if (typeof v !== "string" || v.trim().length === 0) {
|
|
100
|
-
throw new Error(`${where}: ${field} must be a non-empty string`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (!Array.isArray(f.screenshots) || f.screenshots.length === 0) {
|
|
104
|
-
throw new Error(`${where}: screenshots must be a non-empty array`);
|
|
105
|
-
}
|
|
106
|
-
for (let i = 0; i < f.screenshots.length; i += 1) {
|
|
107
|
-
const s = f.screenshots[i];
|
|
108
|
-
if (!s || typeof s.path !== "string" || s.path.length === 0) {
|
|
109
|
-
throw new Error(`${where}.screenshots[${i}].path must be a non-empty string`);
|
|
110
|
-
}
|
|
111
|
-
if (s.path.split(/[/\\]/).includes("..")) {
|
|
112
|
-
throw new Error(`${where}.screenshots[${i}].path must not contain '..' segments`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
function nextFindingId(reg) {
|
|
117
|
-
let max = 0;
|
|
118
|
-
for (const f of reg.findings) {
|
|
119
|
-
if (typeof f.id === "number" && f.id > max) max = f.id;
|
|
120
|
-
}
|
|
121
|
-
return max + 1;
|
|
122
|
-
}
|
|
123
|
-
function slugifyTitle(title) {
|
|
124
|
-
return slugify(title, "title");
|
|
125
|
-
}
|
|
126
|
-
function renderFinding(finding) {
|
|
127
|
-
if (!finding.id) {
|
|
128
|
-
throw new Error("audit-writer: cannot render a finding without an assigned id");
|
|
129
|
-
}
|
|
130
|
-
if (finding.screenshots.length === 0) {
|
|
131
|
-
throw new Error(`audit-writer: finding #${finding.id} has no screenshots`);
|
|
132
|
-
}
|
|
133
|
-
const id = finding.id;
|
|
134
|
-
const numStr = String(id).padStart(3, "0");
|
|
135
|
-
const metaLines = [
|
|
136
|
-
`> **Issue #${numStr}** \xB7 **Severity:** ${finding.severity} \xB7 **Lens:** ${finding.lens}`,
|
|
137
|
-
`> **Route:** \`${finding.route}\`${finding.url ? ` \xB7 **URL:** ${finding.url}` : ""}`
|
|
138
|
-
];
|
|
139
|
-
if (finding.viewport) metaLines.push(`> **Viewport:** ${finding.viewport}`);
|
|
140
|
-
if (finding.selector) metaLines.push(`> **Selector:** \`${finding.selector}\``);
|
|
141
|
-
if (finding.tags && finding.tags.length > 0) {
|
|
142
|
-
metaLines.push(`> **Tags:** ${finding.tags.map((t) => `\`${t}\``).join(", ")}`);
|
|
143
|
-
}
|
|
144
|
-
if (finding.similarTo && finding.similarTo.length > 0) {
|
|
145
|
-
metaLines.push(
|
|
146
|
-
`> **Possible duplicates:** ${finding.similarTo.map((n) => `#${String(n).padStart(3, "0")}`).join(", ")}`
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
metaLines.push(`> _Generated: ${finding.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()}_`);
|
|
150
|
-
const reproSteps = finding.reproSteps ?? `1. Open \`${finding.route}\`${finding.url ? ` (${finding.url})` : ""}
|
|
151
|
-
2. Observe the area shown in the screenshot${finding.selector ? ` (selector: \`${finding.selector}\`)` : ""}.`;
|
|
152
|
-
const evidence = finding.screenshots.map((s) => ``).join("\n\n");
|
|
153
|
-
return [
|
|
154
|
-
`# [UI] ${finding.title}`,
|
|
155
|
-
"",
|
|
156
|
-
metaLines.join("\n"),
|
|
157
|
-
"",
|
|
158
|
-
"## Observation",
|
|
159
|
-
finding.observation.trim(),
|
|
160
|
-
"",
|
|
161
|
-
"## Why it matters",
|
|
162
|
-
finding.impact.trim(),
|
|
163
|
-
"",
|
|
164
|
-
"## Steps to reproduce",
|
|
165
|
-
reproSteps,
|
|
166
|
-
"",
|
|
167
|
-
"## Suggested fix",
|
|
168
|
-
finding.suggestedFix.trim(),
|
|
169
|
-
"",
|
|
170
|
-
"## Evidence",
|
|
171
|
-
evidence,
|
|
172
|
-
"",
|
|
173
|
-
"---",
|
|
174
|
-
`<sub>Generated by agent-runtime/ui-auditor \xB7 lens=\`${finding.lens}\` \xB7 severity=\`${finding.severity}\`</sub>`,
|
|
175
|
-
""
|
|
176
|
-
].join("\n");
|
|
177
|
-
}
|
|
178
|
-
async function appendFindings(workspaceDir, findings) {
|
|
179
|
-
assertValidWorkspaceDir(workspaceDir);
|
|
180
|
-
for (let i = 0; i < findings.length; i += 1) {
|
|
181
|
-
const f = findings[i];
|
|
182
|
-
if (!f) throw new Error(`audit-writer: findings[${i}] is undefined`);
|
|
183
|
-
assertFindingShape(f, i);
|
|
184
|
-
}
|
|
185
|
-
return withWorkspaceLock(workspaceDir, async () => {
|
|
186
|
-
await initAuditWorkspace(workspaceDir);
|
|
187
|
-
const reg = await readAuditRegistry(workspaceDir);
|
|
188
|
-
const usedIds = /* @__PURE__ */ new Set();
|
|
189
|
-
for (const f of reg.findings) {
|
|
190
|
-
if (typeof f.id === "number") usedIds.add(f.id);
|
|
191
|
-
}
|
|
192
|
-
const written = [];
|
|
193
|
-
const files = [];
|
|
194
|
-
let nextId = nextFindingId(reg);
|
|
195
|
-
for (const incoming of findings) {
|
|
196
|
-
let id = incoming.id;
|
|
197
|
-
if (id !== void 0) {
|
|
198
|
-
if (usedIds.has(id)) {
|
|
199
|
-
throw new Error(
|
|
200
|
-
`audit-writer: incoming finding id ${id} (title=${JSON.stringify(incoming.title)}) collides with an existing registry entry`
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
} else {
|
|
204
|
-
id = nextId;
|
|
205
|
-
while (usedIds.has(id)) id += 1;
|
|
206
|
-
nextId = id + 1;
|
|
207
|
-
}
|
|
208
|
-
usedIds.add(id);
|
|
209
|
-
const createdAt = incoming.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
210
|
-
const persisted = { ...incoming, id, createdAt };
|
|
211
|
-
const slug = slugifyTitle(persisted.title);
|
|
212
|
-
const fileName = `${String(id).padStart(3, "0")}--${persisted.lens}--${slug}.md`;
|
|
213
|
-
const filePathAbs = path.join(workspaceDir, "issues", fileName);
|
|
214
|
-
const filePathRel = `issues/${fileName}`;
|
|
215
|
-
await fs.writeFile(filePathAbs, renderFinding(persisted));
|
|
216
|
-
reg.findings.push(persisted);
|
|
217
|
-
written.push(persisted);
|
|
218
|
-
files.push(filePathRel);
|
|
219
|
-
}
|
|
220
|
-
await writeAuditRegistry(workspaceDir, reg);
|
|
221
|
-
return { written, files };
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
async function registerCaptures(workspaceDir, options) {
|
|
225
|
-
assertValidWorkspaceDir(workspaceDir);
|
|
226
|
-
if (typeof options.route !== "string" || options.route.trim().length === 0) {
|
|
227
|
-
throw new Error("audit-writer: registerCaptures: route must be a non-empty string");
|
|
228
|
-
}
|
|
229
|
-
return withWorkspaceLock(workspaceDir, async () => {
|
|
230
|
-
await initAuditWorkspace(workspaceDir);
|
|
231
|
-
const reg = await readAuditRegistry(workspaceDir);
|
|
232
|
-
const slot = reg.routes[options.route] ?? { captures: [] };
|
|
233
|
-
if (options.url) slot.url = options.url;
|
|
234
|
-
slot.captures = [...slot.captures, ...options.captures];
|
|
235
|
-
reg.routes[options.route] = slot;
|
|
236
|
-
await writeAuditRegistry(workspaceDir, reg);
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
function summarizeRegistry(reg) {
|
|
240
|
-
const bySeverity = {
|
|
241
|
-
critical: 0,
|
|
242
|
-
high: 0,
|
|
243
|
-
med: 0,
|
|
244
|
-
low: 0
|
|
245
|
-
};
|
|
246
|
-
const byLens = {};
|
|
247
|
-
const byRoute = {};
|
|
248
|
-
for (const f of reg.findings) {
|
|
249
|
-
bySeverity[f.severity] += 1;
|
|
250
|
-
byLens[f.lens] = (byLens[f.lens] ?? 0) + 1;
|
|
251
|
-
byRoute[f.route] = (byRoute[f.route] ?? 0) + 1;
|
|
252
|
-
}
|
|
253
|
-
return { total: reg.findings.length, bySeverity, byLens, byRoute };
|
|
254
|
-
}
|
|
255
|
-
async function writeAuditIndex(workspaceDir) {
|
|
256
|
-
assertValidWorkspaceDir(workspaceDir);
|
|
257
|
-
const reg = await readAuditRegistry(workspaceDir);
|
|
258
|
-
const summary = summarizeRegistry(reg);
|
|
259
|
-
const sorted = [...reg.findings].sort((a, b) => {
|
|
260
|
-
const sa = SEVERITY_ORDER[a.severity];
|
|
261
|
-
const sb = SEVERITY_ORDER[b.severity];
|
|
262
|
-
if (sa !== sb) return sa - sb;
|
|
263
|
-
return (a.id ?? 0) - (b.id ?? 0);
|
|
264
|
-
});
|
|
265
|
-
const lines = [];
|
|
266
|
-
lines.push("# UI audit index", "");
|
|
267
|
-
lines.push(`_Generated: ${(/* @__PURE__ */ new Date()).toISOString()}_`, "");
|
|
268
|
-
lines.push(`**Total findings:** ${summary.total}`, "");
|
|
269
|
-
lines.push("## By severity");
|
|
270
|
-
for (const s of ["critical", "high", "med", "low"]) {
|
|
271
|
-
const n = summary.bySeverity[s] ?? 0;
|
|
272
|
-
if (n > 0) lines.push(`- **${s}** \u2014 ${n}`);
|
|
273
|
-
}
|
|
274
|
-
lines.push("", "## By lens");
|
|
275
|
-
const lensEntries = Object.entries(summary.byLens).map(([k, v]) => [
|
|
276
|
-
k,
|
|
277
|
-
v ?? 0
|
|
278
|
-
]);
|
|
279
|
-
for (const [lens, n] of lensEntries.sort((a, b) => b[1] - a[1])) {
|
|
280
|
-
lines.push(`- **${lens}** \u2014 ${n}`);
|
|
281
|
-
}
|
|
282
|
-
lines.push("", "## By route");
|
|
283
|
-
const routeEntries = Object.entries(summary.byRoute).map(([k, v]) => [
|
|
284
|
-
k,
|
|
285
|
-
v ?? 0
|
|
286
|
-
]);
|
|
287
|
-
for (const [route, n] of routeEntries.sort((a, b) => b[1] - a[1])) {
|
|
288
|
-
lines.push(`- \`${route}\` \u2014 ${n}`);
|
|
289
|
-
}
|
|
290
|
-
lines.push("", "## Findings", "");
|
|
291
|
-
for (const f of sorted) {
|
|
292
|
-
if (typeof f.id !== "number") continue;
|
|
293
|
-
const num = String(f.id).padStart(3, "0");
|
|
294
|
-
const slug = slugifyTitle(f.title);
|
|
295
|
-
const file = `issues/${num}--${f.lens}--${slug}.md`;
|
|
296
|
-
lines.push(`- [#${num} \`${f.severity}\` \`${f.lens}\` \`${f.route}\` \u2014 ${f.title}](${file})`);
|
|
297
|
-
}
|
|
298
|
-
lines.push("");
|
|
299
|
-
const out = path.join(workspaceDir, "index.md");
|
|
300
|
-
const body = lines.join("\n");
|
|
301
|
-
await fs.writeFile(out, body);
|
|
302
|
-
return out;
|
|
303
|
-
}
|
|
304
|
-
export {
|
|
305
|
-
appendFindings,
|
|
306
|
-
initAuditWorkspace,
|
|
307
|
-
readAuditRegistry,
|
|
308
|
-
registerCaptures,
|
|
309
|
-
summarizeRegistry,
|
|
310
|
-
writeAuditIndex
|
|
311
|
-
};
|
|
312
|
-
//# sourceMappingURL=audit.js.map
|