automated-qa 0.11.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 +22 -0
- package/NOTICE +9 -0
- package/README.md +100 -0
- package/SKILL.md +65 -0
- package/agent/prebuilt/darwin-universal/qa-agent +0 -0
- package/agent/prebuilt/linux-arm64/qa-agent +0 -0
- package/agent/prebuilt/linux-x64/qa-agent +0 -0
- package/agent/release-manifest.json +31 -0
- package/bin/autonomous-cache.mjs +2 -0
- package/bin/bench-generate.mjs +232 -0
- package/bin/bench-graph-query.mjs +2 -0
- package/bin/bench-score.mjs +10 -0
- package/bin/classify.mjs +15 -0
- package/bin/coherence-playwright.mjs +1 -0
- package/bin/coherence-runner.mjs +6 -0
- package/bin/crud-runner.mjs +12 -0
- package/bin/experiment-executor.mjs +10 -0
- package/bin/experiment-runner.mjs +3 -0
- package/bin/inventory.mjs +17 -0
- package/bin/probe.js +879 -0
- package/bin/qa.mjs +16 -0
- package/bin/rules.mjs +5 -0
- package/bin/run-benchmarks.mjs +2 -0
- package/bin/runtime-packs.mjs +3 -0
- package/bin/semantic-explorer.mjs +3 -0
- package/bin/shadow-collector.mjs +3 -0
- package/bin/shadow-replay.mjs +6 -0
- package/bin/soak-marathon.mjs +15 -0
- package/bin/sweep.mjs +43 -0
- package/bin/validate-priors.mjs +3 -0
- package/bin/verify-artifacts.mjs +3 -0
- package/bin/verify-manifest.mjs +2 -0
- package/install-skill.sh +45 -0
- package/lib/qa/agent-availability.mjs +1 -0
- package/lib/qa/agent-result.mjs +1 -0
- package/lib/qa/analysis.mjs +5 -0
- package/lib/qa/atlas-coverage.mjs +11 -0
- package/lib/qa/atlas-v1.mjs +1 -0
- package/lib/qa/atlas.mjs +2 -0
- package/lib/qa/auth/coordinator.mjs +1 -0
- package/lib/qa/auth/launcher.mjs +3 -0
- package/lib/qa/auth/lease.mjs +1 -0
- package/lib/qa/auth/oracle.mjs +1 -0
- package/lib/qa/auth/personas.mjs +3 -0
- package/lib/qa/autonomous-cache.mjs +3 -0
- package/lib/qa/block-ledger.mjs +1 -0
- package/lib/qa/capture-report.mjs +63 -0
- package/lib/qa/ci.mjs +11 -0
- package/lib/qa/cli.mjs +538 -0
- package/lib/qa/coherence/analyze.mjs +1 -0
- package/lib/qa/coherence/cache-key.mjs +6 -0
- package/lib/qa/coherence/causes.mjs +1 -0
- package/lib/qa/coherence/concurrency.mjs +1 -0
- package/lib/qa/coherence/contracts.mjs +1 -0
- package/lib/qa/coherence/entity-aliases.mjs +1 -0
- package/lib/qa/coherence/enums.mjs +1 -0
- package/lib/qa/coherence/family-report.mjs +1 -0
- package/lib/qa/coherence/flow-pool.mjs +1 -0
- package/lib/qa/coherence/graph.mjs +1 -0
- package/lib/qa/coherence/identity.mjs +1 -0
- package/lib/qa/coherence/ir.mjs +4 -0
- package/lib/qa/coherence/navigation.mjs +1 -0
- package/lib/qa/coherence/observe.mjs +2 -0
- package/lib/qa/coherence/offline.mjs +1 -0
- package/lib/qa/coherence/plan-join.mjs +1 -0
- package/lib/qa/coherence/provenance.mjs +2 -0
- package/lib/qa/coherence/report.mjs +1 -0
- package/lib/qa/coherence/representations.mjs +1 -0
- package/lib/qa/coherence/risk.mjs +2 -0
- package/lib/qa/coherence/structural-js.mjs +11 -0
- package/lib/qa/coherence/symbols.mjs +6 -0
- package/lib/qa/coherence/tanstack.mjs +4 -0
- package/lib/qa/coherence/views.mjs +1 -0
- package/lib/qa/coherence-planner.mjs +1 -0
- package/lib/qa/command-catalog.mjs +2 -0
- package/lib/qa/commands/dev.mjs +1 -0
- package/lib/qa/commands/fix.mjs +1 -0
- package/lib/qa/commands/impl/analysis.mjs +1 -0
- package/lib/qa/commands/impl/atlas.mjs +1 -0
- package/lib/qa/commands/impl/coherence.mjs +1 -0
- package/lib/qa/commands/impl/crud.mjs +2 -0
- package/lib/qa/commands/impl/doctor.mjs +1 -0
- package/lib/qa/commands/impl/experiments.mjs +1 -0
- package/lib/qa/commands/impl/fp.mjs +1 -0
- package/lib/qa/commands/impl/graph.mjs +1 -0
- package/lib/qa/commands/impl/invariants.mjs +1 -0
- package/lib/qa/commands/impl/lanes.mjs +7 -0
- package/lib/qa/commands/impl/ledger.mjs +11 -0
- package/lib/qa/commands/impl/loop.mjs +1 -0
- package/lib/qa/commands/impl/marathon.mjs +1 -0
- package/lib/qa/commands/impl/next.mjs +38 -0
- package/lib/qa/commands/impl/report.mjs +4 -0
- package/lib/qa/commands/impl/research.mjs +4 -0
- package/lib/qa/commands/impl/selftest.mjs +1 -0
- package/lib/qa/commands/impl/serve.mjs +1 -0
- package/lib/qa/commands/impl/shadow.mjs +1 -0
- package/lib/qa/commands/impl/stability.mjs +1 -0
- package/lib/qa/commands/impl/triage.mjs +1 -0
- package/lib/qa/commands/impl/validate.mjs +4 -0
- package/lib/qa/commands/impl/version.mjs +1 -0
- package/lib/qa/commands/impl/watch.mjs +1 -0
- package/lib/qa/commands/init.mjs +6 -0
- package/lib/qa/commands/measure.mjs +1 -0
- package/lib/qa/commands/run.mjs +1 -0
- package/lib/qa/commands/show.mjs +1 -0
- package/lib/qa/commands/verify.mjs +6 -0
- package/lib/qa/deepening.mjs +1 -0
- package/lib/qa/denominators.mjs +1 -0
- package/lib/qa/depth.mjs +4 -0
- package/lib/qa/doctor.mjs +2 -0
- package/lib/qa/experiments-default.mjs +1 -0
- package/lib/qa/fix-lanes.mjs +3 -0
- package/lib/qa/forced-states.mjs +1 -0
- package/lib/qa/fp.mjs +2 -0
- package/lib/qa/fs.mjs +6 -0
- package/lib/qa/graph-schema.mjs +2 -0
- package/lib/qa/graph.mjs +3 -0
- package/lib/qa/harness-plan.mjs +3 -0
- package/lib/qa/init.mjs +5 -0
- package/lib/qa/integrity.mjs +1 -0
- package/lib/qa/invariants.mjs +84 -0
- package/lib/qa/jcs.mjs +1 -0
- package/lib/qa/lanes.mjs +1 -0
- package/lib/qa/loop.mjs +7 -0
- package/lib/qa/marathon-coherence.mjs +1 -0
- package/lib/qa/marathon-phases.mjs +1 -0
- package/lib/qa/marathon-wait.mjs +3 -0
- package/lib/qa/marathon.mjs +5 -0
- package/lib/qa/mode-coverage.mjs +1 -0
- package/lib/qa/mutation-policy.mjs +1 -0
- package/lib/qa/pattern-ledger.mjs +3 -0
- package/lib/qa/pending.mjs +1 -0
- package/lib/qa/proc.mjs +4 -0
- package/lib/qa/recommend.mjs +1 -0
- package/lib/qa/release-manifest.mjs +1 -0
- package/lib/qa/report-html.mjs +178 -0
- package/lib/qa/reports.mjs +2 -0
- package/lib/qa/request-evidence.mjs +1 -0
- package/lib/qa/research.mjs +20 -0
- package/lib/qa/routing.mjs +1 -0
- package/lib/qa/runs.mjs +1 -0
- package/lib/qa/schema.mjs +1 -0
- package/lib/qa/serve.mjs +4 -0
- package/lib/qa/server.mjs +1 -0
- package/lib/qa/source-snapshot.mjs +3 -0
- package/lib/qa/stability.mjs +3 -0
- package/lib/qa/state-evidence.mjs +1 -0
- package/lib/qa/sweep-incremental.mjs +1 -0
- package/lib/qa/triage.mjs +2 -0
- package/lib/qa/verdict.mjs +1 -0
- package/lib/qa/watch.mjs +2 -0
- package/package.json +62 -0
- package/references/ATLAS-COVERAGE.md +231 -0
- package/references/atlas-v1.md +318 -0
- package/references/atlas.md +338 -0
- package/references/verify.crud.example.json +1 -0
- package/references/verify.impact.example.json +1 -0
- package/references/verify.shadow.example.json +1 -0
- package/schemas/agent-result.schema.json +135 -0
- package/schemas/analysis.schema.json +15 -0
- package/schemas/classify.schema.json +61 -0
- package/schemas/coherence-ir.schema.json +1585 -0
- package/schemas/coherence-observed.schema.json +1171 -0
- package/schemas/coherence-plan.schema.json +77 -0
- package/schemas/coherence-risk.schema.json +46 -0
- package/schemas/crud-observed.schema.json +118 -0
- package/schemas/experiment-execution.schema.json +82 -0
- package/schemas/graph-edge.schema.json +22 -0
- package/schemas/graph-node.schema.json +81 -0
- package/schemas/graph.schema.json +2493 -0
- package/schemas/identity-oracle.schema.json +19 -0
- package/schemas/inventory.schema.json +2167 -0
- package/schemas/lanes-plan.schema.json +52 -0
- package/schemas/next.schema.json +54 -0
- package/schemas/personas.schema.json +52 -0
- package/schemas/research.schema.json +192 -0
- package/schemas/run.schema.json +160 -0
- package/schemas/shadow-replay.schema.json +68 -0
- package/schemas/stability.schema.json +92 -0
- package/schemas/sweep.schema.json +239 -0
- package/schemas/v1/agent-result.schema.json +106 -0
- package/schemas/v1/run.schema.json +96 -0
- package/schemas/verify-crud.schema.json +37 -0
- package/schemas/verify-read-only.schema.json +37 -0
- package/templates/github-workflow.yml +80 -0
- package/templates/pre-commit.sample +22 -0
- package/verify.sh +385 -0
package/lib/qa/cli.mjs
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import x from"node:path";import{fileURLToPath as k}from"node:url";import{commandCatalog as q,groupedCatalog as j,nearest as p}from"./command-catalog.mjs";const f=Object.freeze({OK:0,FINDINGS:1,INVALID:2}),m=new Set(["base","auth","width","widths","parallel","routes","run","from","to","runs","fixer","fixer-timeout","max","nav-timeout","warm-timeout","settle","text-floor","integrity-share","label","keep","inventory","json-out","timeout","note","evidence","expires","by","approve","revoke","out","format","kind","limit","budget-cells","wall-ms","families","flow","max-mutations","update-selector","delete-selector","trace","policy","ids","port","build-cmd","start-cmd","debounce","seed","count","corpus","oracle","oracle-out","min-precision","min-recall","capture-dir","discard-first","layer","queue","search","flush-every","delay","height","launcher","launcher-timeout","oracle-path","auth-lease-fd","theme-storage-key","repo","around","depth","link-adapters","across","hours","stability-runs","checkpoint-minutes","shadow-trace","lane","lanes","runid","flow-workers","plan","evidence-envelope"]),h=new Set(["states","exercise"]),E=new Set(["static","cli","coherence","runtime","atlas","agent","tournament","e2e"]),A=new Set(["install","leak-check","no-install","no-journeys","no-warm","help","json","prod","autonomous","no-archive","mutate","ratchet","resume","fresh","strict","write","all","isolated","roles","ignore","quiet","once","verbose","fast","capture","capture-full","views","workflow","hook","crud","shadow","experiments","force","list","md","open","fix","clean","commit","serve","dry-run","keep-serving","stop","status","warmup","version","no-states","no-dark","dark","stdout","strict-json","no-color","phases","measure-only","fixer-protocol","no-autonomous","i-know-this-is-not-production","study","analysis","preflight"]);function U(e){const a=new Set([...m,...h,...A,...E,...Object.keys(u)]),t={command:null,positional:[],flags:{},passthru:[],json:!1};for(let r=0;r<e.length;r++){const n=e[r];if(n==="--"){t.passthru.push(...e.slice(r+1));break}if(!n.startsWith("--")){if(n==="-h"){t.flags.help=!0;continue}if(n.length>1&&n[0]==="-"){const i=p(n.slice(1),a);throw new d(`unknown flag ${n}`+(i?`; flags take two dashes -- did you mean --${i}?`:"; flags take two dashes")+". Use `qa <command> --help` for the flags this command takes, or `--` to pass arguments through verbatim.")}t.command===null?t.command=n:t.positional.push(n);continue}const s=n.indexOf("="),o=s>0?n.slice(2,s):n.slice(2);if(!o)throw new d(`${n} has no flag name before the "=". Use \`qa <command> --help\` for the flags this command takes.`);if(!a.has(o)){const i=p(o,a);throw new d(`unknown flag --${o}`+(i?`; did you mean --${i}?`:"")+". Use `qa <command> --help` for the flags this command takes, or `--` to pass arguments through verbatim.")}const c=m.has(o)||o==="version"&&t.command==="dev"&&t.positional[0]==="atlas";if(s>0){if(!c&&!h.has(o)&&!Object.hasOwn(u,o))throw new d(`--${o} does not take a value; use --${o} or omit it`);if(!n.slice(s+1))throw new d(`--${o} needs a value`);t.flags[o]=n.slice(s+1);continue}if(o==="json"){t.json=!0;continue}if(c){const i=e[r+1];if(i===void 0||/^-(?!\d)/.test(i))throw new d(`--${o} needs a value`);t.flags[o]=i,r++;continue}if(h.has(o)){const i=e[r+1];i!==void 0&&!/^-(?!\d)/.test(i)?(t.flags[o]=i,r++):t.flags[o]=!0;continue}t.flags[o]=!0}return t}class d extends Error{constructor(a){super(a),this.name="UsageError",this.exit=f.INVALID}}class R extends Error{constructor(a,t={}){super(a),this.name="InvalidError",this.exit=f.INVALID,Object.assign(this,t)}}function P(e,{required:a=!0}={}){const t=e.positional[0];if(!t){if(!a)return null;throw new d("missing <repo> \u2014 pass the repository path (absolute paths are safest)")}return x.resolve(process.cwd(),t)}const v=new Set(["login","login-user","login-pass"]),l="<redacted>",u=Object.freeze({login:"use the host-harness launcher, invoked by persona name","login-user":"use the host-harness launcher, invoked by persona name","login-pass":"use the host-harness launcher, invoked by persona name","login-path":"handled by the target-owned launcher itself",auth:"use the host-harness launcher and the per-run lease -- there is no shared auth-state file any more",fixer:"the host agent owns source fixes; run with --once to write a prioritized brief, then apply and verify the change from the host","fixer-protocol":"the CLI never launches an agent; run with --once to write a prioritized brief for the host agent","fixer-timeout":"there is no CLI fixer process to time; run with --once to hand the brief to the host agent","measure-only":"the no-fixer marathon is already measurement-only; run with --once for a bounded host handoff"});function C(e,{command:a,flags:t=Object.keys(u)}){for(const r of t)if(e.flags[r]!==void 0)throw new R(`qa ${a}: --${r} was removed -- ${u[r]}`)}const g=new Set(["base"]),w=new Set(["fixer","start-cmd","build-cmd"]),y=/pass|secret|token|key|credential|auth/i;function S(e){let a;try{a=new URL(e)}catch{return e}let t=!1;(a.username||a.password)&&(a.username=l,a.password="",t=!0);for(const r of[...a.searchParams.keys()])y.test(r)&&(a.searchParams.set(r,l),t=!0);return t?a.toString().replaceAll(encodeURIComponent(l),l):e}function I(e){return e.replace(/(^|\s)(--?[A-Za-z][\w-]*|[A-Za-z_]\w*)=(\S+)/g,(a,t,r,n)=>y.test(r)?`${t}${r}=${l}`:a)}function b(e,a){return v.has(e)?l:g.has(e)?S(a):w.has(e)?I(a):a}const N=new Set([...v,...g,...w]);function _(e){const a=[];for(let t=0;t<e.length;t++){const r=e[t],n=r.startsWith("--")?r.indexOf("="):-1,s=r.startsWith("--")?n>0?r.slice(2,n):r.slice(2):null;if(s===null||!N.has(s)){a.push(r);continue}if(n>0){a.push(`--${s}=${b(s,r.slice(n+1))}`);continue}a.push(r),t+1<e.length&&(a.push(b(s,e[t+1])),t++)}return a}function M(e,a){const t=[];for(const[r,n]of Object.entries(e.flags))if(!a.has(r))if(n===!0)t.push("--"+r);else{if(n===!1||n==null)continue;t.push("--"+r,String(n))}return t.push(...e.passthru),t}function F(e){return{say:(...n)=>{const s=n.join(`
|
|
2
|
+
`);(e?process.stderr:process.stdout).write(s+`
|
|
3
|
+
`)},warn:(...n)=>process.stderr.write(n.join(`
|
|
4
|
+
`)+`
|
|
5
|
+
`),emit:n=>{e&&process.stdout.write(JSON.stringify(n,null,2)+`
|
|
6
|
+
`)},json:e}}function L(e,a){return e=String(e),e.length>=a?e:e+" ".repeat(a-e.length)}function V(e,a){return e=String(e),e.length>=a?e:" ".repeat(a-e.length)+e}function W(e,{indent:a=" "}={}){if(!e.length)return"";const t=[];for(const r of e)r.forEach((n,s)=>{t[s]=Math.max(t[s]??0,String(n??"").length)});return e.map(r=>a+r.map((n,s)=>s===r.length-1?String(n??""):L(n??"",t[s])).join(" ")).join(`
|
|
7
|
+
`)}const G={"":`qa \u2014 evidence-first frontend verification
|
|
8
|
+
|
|
9
|
+
Seven commands. The one long-running "do everything" command:
|
|
10
|
+
qa run <repo> --base URL --once
|
|
11
|
+
measures, prioritizes, and hands one brief to the host agent
|
|
12
|
+
qa run <repo> --base URL --hours 10
|
|
13
|
+
resumable measurement; waits for host-owned source changes
|
|
14
|
+
|
|
15
|
+
COPY-PASTE FOR YOUR CURRENT CODEX/CLAUDE AGENT:
|
|
16
|
+
Use frontend-verify in this host worktree. Run \`qa run <repo> --base URL --once\`, read
|
|
17
|
+
the generated \`.verify/lanes/<class>/brief.md\`, own the source fixes and commits, then rerun
|
|
18
|
+
the same command to recheck.
|
|
19
|
+
|
|
20
|
+
Then, for one check at a time:
|
|
21
|
+
qa verify <repo> static analysis, archived and triaged
|
|
22
|
+
qa verify <repo> --base URL also measure the running app
|
|
23
|
+
qa verify <repo> --preflight check readiness and explain blockers
|
|
24
|
+
|
|
25
|
+
Read the evidence:
|
|
26
|
+
qa show <repo> ranked findings and coverage (triage)
|
|
27
|
+
qa show next <repo> the next finding to fix
|
|
28
|
+
qa show report <repo> self-contained HTML report
|
|
29
|
+
|
|
30
|
+
qa help --phases all commands, grouped by task
|
|
31
|
+
qa help --search TERM find a command by task, artifact, or flag
|
|
32
|
+
qa help <command> usage and options
|
|
33
|
+
qa help [command] --json machine-readable help
|
|
34
|
+
qa dev version installed version
|
|
35
|
+
|
|
36
|
+
Every pre-consolidation top-level name is retired (see RETIRED_COMMANDS in
|
|
37
|
+
lib/qa/command-catalog.mjs for the full list): typing one exits 2 and names
|
|
38
|
+
its replacement (qa help <old-name> does the same).
|
|
39
|
+
|
|
40
|
+
Generic guessed mutation replay is disabled.
|
|
41
|
+
Service-worker or cache-dependent protected journeys are unmeasured.
|
|
42
|
+
|
|
43
|
+
Exit codes: 0 clean/done \xB7 1 findings or regression \xB7 2 could not run (never a pass).
|
|
44
|
+
Relative <repo> paths resolve against your shell's working directory.`,get phases(){const e=q({commandsDir:k(new URL("./commands",import.meta.url)),help:this});return`qa commands \u2014 grouped by task
|
|
45
|
+
|
|
46
|
+
`+j(e).map(({group:a,commands:t})=>a+`
|
|
47
|
+
`+t.map(r=>` ${this[r]?.split(`
|
|
48
|
+
`)[0]??`qa ${r}`}`).join(`
|
|
49
|
+
`)).join(`
|
|
50
|
+
|
|
51
|
+
`)+`
|
|
52
|
+
|
|
53
|
+
Use qa help <command> for options and examples.`},"verify preflight":`qa verify <repo> --preflight [--base URL] [--prod] [--strict]
|
|
54
|
+
|
|
55
|
+
Preflight. Hard failures exit 2 and name what to fix: unreadable repo, node < 18,
|
|
56
|
+
zero routes, unreachable --base, --prod against a detectable dev server, a lock
|
|
57
|
+
held by another sweep, a malformed verify.roles.json. Warnings exit 0 (or 1 with
|
|
58
|
+
--strict): Playwright/chromium missing, a build process running, stale auth
|
|
59
|
+
state, dirty git tree, journeys absent while mutation routes exist, roles file
|
|
60
|
+
absent while more than one principal was detected.`,"init study":`qa init <repo> --study [--json]
|
|
61
|
+
|
|
62
|
+
Studies the repo and writes, under <repo>/.verify/:
|
|
63
|
+
research.json everything below, with a confidence on every heuristic
|
|
64
|
+
research.md the study a marathon's Phase 1 asks for
|
|
65
|
+
ledger.md exactly inventory.counts.routes rows; both numbers printed
|
|
66
|
+
journeys.draft.mjs one draft journey per mutation route, fields from source
|
|
67
|
+
research.json's harness field and research.md's handoff group source mutations
|
|
68
|
+
by exact entity identity, retain shared-hook importing routes, and rank affected
|
|
69
|
+
routes/surfaces. Each item names source-derived assertion candidates and missing
|
|
70
|
+
persona, declaration, fixture/cleanup, readback, driver, and provenance work.
|
|
71
|
+
Validated declarations and present drivers are not execution: every handoff is
|
|
72
|
+
source-derived/non-executed. No selectors, payloads, or runtime proof are guessed.
|
|
73
|
+
The draft is never loaded by the sweep. Promote it to <repo>/verify.journeys.mjs
|
|
74
|
+
after replacing every TODO selector.`,verify:`qa verify <repo> [any verify.sh flag] [--lane LANE] [--autonomous] [--json]
|
|
75
|
+
qa verify <repo> [--no-archive] [--keep N] [--link-adapters crud,shadow,experiments]
|
|
76
|
+
|
|
77
|
+
Runs verify.sh with the flags given, unchanged. Then opens a private pending
|
|
78
|
+
run directory and atomically writes immutable report snapshots plus normalized
|
|
79
|
+
roles and the exact validated CRUD snapshots, with their sha256 digests. The
|
|
80
|
+
agent receives only those explicit paths and never reads the live role or CRUD files.
|
|
81
|
+
It always attempts a compatible qa-agent
|
|
82
|
+
(a verified prebuilt binary, or a \`cargo build\` fallback) with those explicit
|
|
83
|
+
paths, the run ID, lane and mode via --emit-result -- optional evidence by
|
|
84
|
+
default (unmeasured when no compatible binary exists), never gating unless
|
|
85
|
+
--autonomous makes it required. Only after the returned digests and schema
|
|
86
|
+
are verified does the pending directory publish to .verify/runs/<id>/: a
|
|
87
|
+
reader sees no run or a complete run, never a partial one. run.json carries
|
|
88
|
+
the embedded result under \`agent\`, plus \`lane\` and \`runId\`; the archive
|
|
89
|
+
also gets agent-result.json (its canonical bytes). Adapter evidence (qa measure crud,
|
|
90
|
+
qa measure shadow replay, qa measure experiments) is NEVER folded in automatically: name the
|
|
91
|
+
adapters explicitly with --link-adapters, and their findings travel with their
|
|
92
|
+
own denominator rather than entering this run's counts. Appends to
|
|
93
|
+
.verify/trajectory.jsonl and prints the triage. Exit is derived: 2 dominates
|
|
94
|
+
(the gate itself invalid, OR --autonomous with an absent/invalid agent), else
|
|
95
|
+
1 if the gate found gating evidence, else 0. --json output separates \`gate\`
|
|
96
|
+
(verify.sh's own exit) from \`agent\` (the embedded result) so a consumer
|
|
97
|
+
never conflates the two verdicts. --no-archive runs the same snapshot and
|
|
98
|
+
agent sequence into a private pending directory and still emits gate/agent
|
|
99
|
+
on stdout (--no-archive --autonomous still enforces require-agent) but
|
|
100
|
+
publishes nothing: the pending directory is discarded, only after the
|
|
101
|
+
result is validated, so a failure still leaves a diagnosis on stdout.
|
|
102
|
+
|
|
103
|
+
--autonomous also produces the canonical three-file graph contract
|
|
104
|
+
(nodes.jsonl, edges.jsonl, run-manifest.json) via the cargo-built qa-agent
|
|
105
|
+
into .verify/autonomous/ and validates its manifest. This is reported under
|
|
106
|
+
the JSON output's \`autonomous\` field, distinct from \`agent\`. Needs cargo;
|
|
107
|
+
absent cargo is reported unmeasured. A required kernel or manifest failure
|
|
108
|
+
is a hard exit 2. The earlier 42-file surface is retired.`,"show triage":`qa show <repo> [--run ID] [--json]
|
|
109
|
+
|
|
110
|
+
Reads an archived run (latest by default). Groups runtime findings by (kind,
|
|
111
|
+
selector) across routes, static by rule; ranks by severity, leverage, and
|
|
112
|
+
determinism; prints every denominator; flags structural tells that mean the run
|
|
113
|
+
measured something other than the app.`,"show next":`qa show next <repo> [--run ID] [--json]
|
|
114
|
+
|
|
115
|
+
Picks the top finding from triage (skipping fids blocked by qa run loop), writes a
|
|
116
|
+
brief to .verify/next.md and .verify/next.json: where, what, the atlas entry,
|
|
117
|
+
affected routes, how to reproduce, how to waive with proof. Exit 1 when a finding
|
|
118
|
+
was selected, 0 when there is nothing to fix, 2 when the run is invalid.`,"verify runs":`qa verify <repo> --base URL --runs 3 [--routes /a,/b] [--warmup] [sweep flags] [--json]
|
|
119
|
+
|
|
120
|
+
Runs sweep.mjs N times serially (--parallel 1 forced) into .verify/stability/<id>/,
|
|
121
|
+
plus .verify/stability.json (the latest). --warmup runs one extra, UNCOUNTED sweep
|
|
122
|
+
first to prime the dev server compile; it is not one of the N. Any run that exits
|
|
123
|
+
2, or whose own integrity gate is not ok, makes the WHOLE measurement INVALID
|
|
124
|
+
(exit 2) -- partial results are still written, labelled invalid. Per finding:
|
|
125
|
+
stable (N of N, gates), intermittent (>= ceil(2N/3), gates), ghost (< ceil(2N/3);
|
|
126
|
+
listed under "needs reproduction", never gating). Exit 1 only when a gating
|
|
127
|
+
stable/intermittent P0/P1 finding exists. Also reported: routes whose surface
|
|
128
|
+
fingerprint differs between runs (informational), and per-metric min/median/max
|
|
129
|
+
with env-sensitive marked when max/min > 3. Runs, routes, and cells are printed
|
|
130
|
+
as denominators beside every count.`,"show diff":`qa show diff <repo> [--from ID] [--to ID] [--json]
|
|
131
|
+
|
|
132
|
+
Fixed / new / persisting findings between two archived runs (default: the two
|
|
133
|
+
newest). Identity is the fid, so a line shift does not read as fixed-and-new.`,"show ledger":`qa show ledger <repo> [--json]
|
|
134
|
+
|
|
135
|
+
Prints the ledger: one row per inventory route with entities, mutations, whether
|
|
136
|
+
a journey covers it, whether the latest run swept it, and its status. The row
|
|
137
|
+
count is asserted equal to inventory.counts.routes.`,"show runs":`qa show runs <repo> [--json]
|
|
138
|
+
|
|
139
|
+
Lists archived runs with mode, exit, counts and integrity.`,"run loop":`qa run loop <repo> [verify flags]
|
|
140
|
+
|
|
141
|
+
Runs one ratcheted gate, ranks the next actionable finding, and writes
|
|
142
|
+
.verify/next.md plus .verify/next.json for the host agent. Exit 0 means the
|
|
143
|
+
measured scope is clean; exit 1 means the brief was handed off; exit 2 means
|
|
144
|
+
the measurement was invalid. The CLI never launches an agent, executes a
|
|
145
|
+
fixer command, or commits source. Use qa run <repo> --once for the normal
|
|
146
|
+
bounded host-agent handoff.`,run:`qa run <repo> [--base URL | --serve] [--hours N]
|
|
147
|
+
[--lane LANE] [--lanes N] [--widths 390,1440] [--no-states] [--no-dark]
|
|
148
|
+
[--stability-runs 3] [--checkpoint-minutes 45] [--resume] [--fresh] [--keep-serving] [--no-autonomous]
|
|
149
|
+
[--once] [--dry-run] [--json] [passthrough verify flags]
|
|
150
|
+
qa run [kind] <repo> ... kind: loop, watch, serve (default: below)
|
|
151
|
+
|
|
152
|
+
The one long-running "do everything" command. With no kind, every phase below is
|
|
153
|
+
a child \`qa <cmd>\` process; this command reads what each one wrote to .verify/
|
|
154
|
+
and decides whether the next one runs:
|
|
155
|
+
0 --base is used as given. Otherwise this command builds and starts a
|
|
156
|
+
production server itself (qa run serve --prod; its base becomes --base,
|
|
157
|
+
stopped at the end unless --keep-serving). If that fails without an
|
|
158
|
+
explicit --serve, the run is static-only and the report says so; with
|
|
159
|
+
--serve the failure is INVALID.
|
|
160
|
+
1 qa verify --preflight -- exit 2 stops everything and prints its fix lines.
|
|
161
|
+
When a --base is known, its probe of it decides --prod automatically for
|
|
162
|
+
every phase below: prod -> add --prod, dev -> continue without it and
|
|
163
|
+
say timing findings will be P3, unknown -> continue without it.
|
|
164
|
+
2 qa init --study; qa init --roles --ignore -- init never overwrites. With
|
|
165
|
+
more than one principal and no roles file, init scaffolds one role per
|
|
166
|
+
principal (each owning "/") and the run continues, saying so.
|
|
167
|
+
2b Writes turn on when verify.crud.json declares every flow
|
|
168
|
+
"production": false and the base is loopback: the crud and coherence
|
|
169
|
+
phases pass --mutate themselves. The sweep gets --mutate only when an
|
|
170
|
+
app-owned journey declares mutates: true -- generic guessed replay is
|
|
171
|
+
disabled, so without such a journey the flag could only invalidate the
|
|
172
|
+
run, and the decision log says it was withheld. An explicit
|
|
173
|
+
--lane wins; otherwise real-integration is selected when
|
|
174
|
+
verify.auth-launcher.mjs exists and every persona in verify.roles.json
|
|
175
|
+
declares "expect". Draft journeys whose every field
|
|
176
|
+
and submit control resolved from source are promoted to
|
|
177
|
+
.verify/journeys.mjs under that same mutation opt-in. Each decision and
|
|
178
|
+
its reason is printed and lands in the exit report.
|
|
179
|
+
3 qa verify --ratchet (the baseline) -- exit 2 stops. No --base means a
|
|
180
|
+
static-only run, said plainly in the report.
|
|
181
|
+
3b deep measurement, once, in two groups: Group A (concurrent -- read-only
|
|
182
|
+
against the app, or writing only to an isolated target) is static
|
|
183
|
+
coherence, qa measure invariants mine + status (review/enforce stay a human
|
|
184
|
+
call), and qa measure shadow replay (when verify.shadow-trace.json or
|
|
185
|
+
.verify/shadow-trace.json exists -- a trace of real traffic cannot be
|
|
186
|
+
manufactured by the run). Group B (strictly serial -- these can all
|
|
187
|
+
write to the SAME dev database) is qa measure crud (under the mutation opt-in),
|
|
188
|
+
qa measure experiments (qa run generates the Node-owned default queue from
|
|
189
|
+
the baseline's Rust route priority when none exists, so it needs cargo;
|
|
190
|
+
pass --queue <path> for a supplied plan; wall time is a
|
|
191
|
+
quarter of what is left, at most 30 minutes), then runtime coherence
|
|
192
|
+
(under its own repo-declared opt-in). Whatever could not run is a named
|
|
193
|
+
line under "what was not measured" with how to enable it. Whichever
|
|
194
|
+
adapter left measured evidence is folded into the very next qa verify
|
|
195
|
+
via --link-adapters automatically.
|
|
196
|
+
4 qa verify --runs N on the baseline's runtime P0/P1 routes (skipped
|
|
197
|
+
when there are none) -- ghost findings are written into .verify/loop.json's
|
|
198
|
+
blocked list so the fix loop never chases them.
|
|
199
|
+
5 qa fix plan --lanes N (one worktree + brief per class, disjoint batches),
|
|
200
|
+
then waits for the host agent to execute the briefs. --once hands off
|
|
201
|
+
immediately with exit 1.
|
|
202
|
+
6 every chunk: qa show ledger, qa show report, one line appended to
|
|
203
|
+
.verify/trajectory.jsonl. If something was fixed and time remains,
|
|
204
|
+
qa verify runs again and qa show diff compares it -- new findings send it
|
|
205
|
+
back to step 4 with whatever time is left. After the depth ladder is
|
|
206
|
+
exhausted, runtime measurements repeat at --checkpoint-minutes cadence
|
|
207
|
+
without --resume; source changes wake the coordinator sooner. Static-only
|
|
208
|
+
runs wait for source changes. --once finishes after the first full pass.
|
|
209
|
+
7 exit report: .verify/marathon-report.md (routes total/clean/fixed with
|
|
210
|
+
commit hashes/open fids/needs-reproduction ghost fids, the ratchet
|
|
211
|
+
trajectory, every phase's command and exit code verbatim, what stayed
|
|
212
|
+
unmeasured, which adapters got linked, the depth ladder's rungs with
|
|
213
|
+
their --resume reused/rejected counts) plus a qa show report HTML. Its first
|
|
214
|
+
word is PASS, FAIL, or INVALID, matching this command's own exit code.
|
|
215
|
+
--status reads .verify/marathon.json and prints phase, running/stopped/finished,
|
|
216
|
+
minutes left, base, lane, writes on/off, depth rung, last command and decision,
|
|
217
|
+
the last checkpoint counts, wait state, and the report path -- for an agent polling a
|
|
218
|
+
background run. Exit 0 running or finished, 1 stopped mid-run (resume it), 2
|
|
219
|
+
nothing has run here.
|
|
220
|
+
A bare \`qa run <repo>\` on a repo whose .verify/marathon.json is not
|
|
221
|
+
"done" resumes it -- this covers both a killed process and a --once handoff to
|
|
222
|
+
open lanes; --fresh discards that state and starts over. A separate marathon.lock
|
|
223
|
+
refuses a second live coordinator, including --fresh. --resume is still
|
|
224
|
+
accepted and is a no-op. A recorded server that died is restarted only if
|
|
225
|
+
--serve was given, otherwise that is INVALID. --dry-run prints the plan
|
|
226
|
+
above with this run's actual flags and runs nothing. --mutate is added only
|
|
227
|
+
by the declaration rule in step 2b, never by --hours or any other flag.
|
|
228
|
+
Default --hours is 10, default --lanes is 4. Exit: 0 the final gate is
|
|
229
|
+
clean, 1 it has findings (or lanes are ready/open, or time ran out with
|
|
230
|
+
findings open), 2 a phase could not run.
|
|
231
|
+
|
|
232
|
+
--fixer, --fixer-protocol, --fixer-timeout, --measure-only, and --commit are
|
|
233
|
+
retired for qa run: the host agent owns source changes and commits. Use --once
|
|
234
|
+
for a copy-paste-ready brief.
|
|
235
|
+
loop/watch/serve are reachable by name (\`qa run loop ...\`, \`qa run watch ...\`,
|
|
236
|
+
\`qa run serve ...\`) -- see qa help run loop / run watch / run serve for their
|
|
237
|
+
own flags and exit codes.`,"fix fp":`qa fix fp add <repo> <fid> --note "why" [--evidence REF --expires YYYY-MM-DD] | qa fix fp list | qa fix fp rm <id>
|
|
238
|
+
|
|
239
|
+
Source entries retain their content anchor. Runtime entries require evidence
|
|
240
|
+
and expiry, and match only the same repository, rule, route, persona, selector
|
|
241
|
+
and measurement identity -- lane, phase, width and rule version, the identity of
|
|
242
|
+
the observation. The run's measurement modes are recorded on the entry, not part
|
|
243
|
+
of that key, so climbing a depth-ladder rung does not retire the adjudication.
|
|
244
|
+
Raw observations are never removed. Stored in
|
|
245
|
+
~/.config/qa/known-false-positives.json (QA_HOME overrides).`,"dev selftest":`qa dev selftest [--parallel N] [--static] [--cli] [--coherence] [--runtime] [--atlas] [--e2e] [--agent] [--tournament] [--fast] [--all]
|
|
246
|
+
|
|
247
|
+
Runs the selected suites. Node test files use four workers by default;
|
|
248
|
+
--parallel N controls that limit. Child processes can spawn their own children.
|
|
249
|
+
Choose --all, --fast, or individual suites, not a mixture. Default: static + cli, the two that need nothing but
|
|
250
|
+
node. --fast runs every suite that does not drive a browser (static, cli,
|
|
251
|
+
coherence, agent, tournament) -- the per-change gate, minutes not a quarter of
|
|
252
|
+
an hour. --all runs every suite, and is what a release is graded on: --fast
|
|
253
|
+
cannot see a probe rule that stopped matching, because every rule that needs a
|
|
254
|
+
browser to fire lives in runtime/atlas/e2e.
|
|
255
|
+
|
|
256
|
+
static selftest-static.sh -- every classifier rule, planted and asserted
|
|
257
|
+
cli the unit suites plus selftest-cli.sh against fixture repos
|
|
258
|
+
coherence selftest-coherence.sh -- the entity chain end to end: families,
|
|
259
|
+
the sparse obligation universe, a real mutation and authoritative
|
|
260
|
+
readback through a fake driver, the per-obligation join, the
|
|
261
|
+
confirmed root cause and its affected surfaces
|
|
262
|
+
runtime selftest.sh -- the probes, differentially (needs Playwright)
|
|
263
|
+
atlas selftest-atlas.sh -- every runtime finding kind still fires (needs Playwright)
|
|
264
|
+
e2e selftest-e2e.sh -- one real run of the whole chain, inventory
|
|
265
|
+
through classify, sweep, the rust kernel, the artifact contract,
|
|
266
|
+
triage, report and SARIF, asserting the JOINS between them and
|
|
267
|
+
that two identical runs are byte-identical (needs Playwright and cargo)
|
|
268
|
+
agent selftest-agent.sh -- the artifact contract through a symlinked install (needs cargo)
|
|
269
|
+
tournament cargo test plus the adapter, privacy and benchmark gates (needs cargo)
|
|
270
|
+
|
|
271
|
+
A suite that cannot run (no Playwright, no cargo) is reported as skipped, never
|
|
272
|
+
as passed. Exit 1 if a suite failed, 2 if one could not run.`,"dev atlas":`qa dev atlas [--json] [--write] [--layer N] [--strict]
|
|
273
|
+
|
|
274
|
+
Cross-references references/atlas.md (the 11-layer failure taxonomy) against
|
|
275
|
+
the real detectors: bin/classify.mjs's static rules, bin/probe.js's and
|
|
276
|
+
bin/sweep.mjs's runtime finding kinds, and lib/qa/atlas.mjs's KIND_ATLAS map.
|
|
277
|
+
Classifies every S/R class as implemented (lists the rule/kind ids),
|
|
278
|
+
deliberately-not-implemented (the reason from the "Evaluated and deliberately
|
|
279
|
+
not implemented" table), or gap (nothing implements it); every G/H/A class as
|
|
280
|
+
not-automatable (says which: a generated journey, a human question, or an
|
|
281
|
+
architecture choice) -- and separately notes when a detector exists anyway for
|
|
282
|
+
a class atlas.md itself marks not-automatable, since that usually means the
|
|
283
|
+
detector is a partial proxy or the atlas's own Det column is stale. Also
|
|
284
|
+
reports the reverse drift: every finding kind with no atlas id, and every
|
|
285
|
+
atlas id a detector references that is not a row in atlas.md -- both are bugs.
|
|
286
|
+
--layer N narrows the printed table to one layer (accepts "--layer N" or
|
|
287
|
+
"--layer=N"; "--layer" alone is not a value flag in this CLI's shared parser).
|
|
288
|
+
--write regenerates references/ATLAS-COVERAGE.md from the same data. Current
|
|
289
|
+
producer-derived coverage:
|
|
290
|
+
v2: 77 of 105 S/R classes; 79 of 108 mode obligations
|
|
291
|
+
v1: 72 of 96 S/R classes; 74 of 99 mode obligations
|
|
292
|
+
Both have 30 not-automatable classes. Producers are discovered recursively from
|
|
293
|
+
every JavaScript finding producer under bin/ and lib/. Exit 1 under --strict when
|
|
294
|
+
any drift was found, 0 otherwise -- gaps alone never fail the command.`,"show report":`qa show report <repo> [--run ID] [--out FILE] [--format html|sarif|github|junit]
|
|
295
|
+
qa show runs <repo> [--json]
|
|
296
|
+
qa show diff <repo> [--from ID] [--to ID] [--json]
|
|
297
|
+
|
|
298
|
+
HTML is the default. Export formats preserve the archived gate exit.
|
|
299
|
+
diff/runs/ci are also reachable directly under show (qa show diff/runs/ci <repo>),
|
|
300
|
+
not only nested under report.
|
|
301
|
+
|
|
302
|
+
Renders one self-contained HTML file (inline CSS/SVG/JS, no external URLs) from
|
|
303
|
+
an archived run: verdict banner with exit code and denominators, integrity
|
|
304
|
+
gauges, the trajectory as a line chart, the cell matrix heatmap, ranked
|
|
305
|
+
findings, the mutation -> entity -> route sync graph, structural tells, and
|
|
306
|
+
stability classes / the ledger when those have been written. Default output:
|
|
307
|
+
<repo>/.verify/report.html.`,"show graph":`qa show graph <repo> --format dot|mermaid|json [--kind sync|transition]
|
|
308
|
+
qa show graph <repo> --run ID [--around RECORD_ID] [--depth N] [--limit N] [--json]
|
|
309
|
+
|
|
310
|
+
Prints the mutation -> entity -> route sync graph from .verify/inventory.json
|
|
311
|
+
(--kind sync, default), or the route -> route transition graph projected from
|
|
312
|
+
.verify/autonomous/edges.jsonl (--kind transition) -- unmeasured and empty,
|
|
313
|
+
exit 0, if that file has not been written yet.
|
|
314
|
+
|
|
315
|
+
With --run, verifies nodes.jsonl/edges.jsonl against the archived run.json graph
|
|
316
|
+
SHA-256 digests, VALIDATES graph integrity, and returns a bounded neighbourhood.
|
|
317
|
+
--around must name an existing record id; --depth is a nonnegative integer
|
|
318
|
+
(default 1), --limit a positive integer (default 200). --kind must name a finite
|
|
319
|
+
node kind: finding, route, component, query, mutation, entity, endpoint, role,
|
|
320
|
+
transition, or evidence. Unknown kinds/centres and invalid numbers exit 2.
|
|
321
|
+
Collections carry returned/total/truncated. A requested graph that is absent or
|
|
322
|
+
fails integrity validation exits 2 -- never an empty successful neighbourhood.`,"show ci":`qa show ci <repo> [--run ID] --format sarif|github|junit [--out file]
|
|
323
|
+
|
|
324
|
+
Renders an archived run (latest by default) for a CI system:
|
|
325
|
+
sarif SARIF 2.1.0, one result per finding, rules from distinct kinds,
|
|
326
|
+
partialFingerprints.fid, atlas ids as helpUri fragments
|
|
327
|
+
github GitHub Actions workflow commands (::error/::warning/::notice), one per line
|
|
328
|
+
junit JUnit XML: one testsuite per phase, one testcase per group
|
|
329
|
+
Without --out the rendered content is written straight to stdout (redirect it);
|
|
330
|
+
with --out it is written to that file (resolved against your shell's cwd) and a
|
|
331
|
+
short summary is printed instead. Exit code mirrors the archived run's own exit.`,init:`qa init <repo> [--roles] [--workflow] [--hook] [--ignore] [--all] [--crud]
|
|
332
|
+
|
|
333
|
+
Scaffolds the per-repo wiring. With no flags, does everything below (same as
|
|
334
|
+
--all); a specific flag narrows it to just that one:
|
|
335
|
+
--roles verify.roles.json \u2014 from .verify/research.json principals if
|
|
336
|
+
present, else one default role owning "/"
|
|
337
|
+
--ignore .verifyignore (design-lab defaults noted, commented) and appends
|
|
338
|
+
.verify/ to .gitignore if it is not already ignored
|
|
339
|
+
--workflow .github/workflows/frontend-verify.yml \u2014 static gate on every PR;
|
|
340
|
+
runtime gate + SARIF upload only when FRONTEND_VERIFY_BASE is set
|
|
341
|
+
--hook prints (does not write) the Stop-hook JSON for ~/.claude/settings.json
|
|
342
|
+
and a pre-commit sample
|
|
343
|
+
--crud verify.crud.json \u2014 one flow per entity qa init --study saw written,
|
|
344
|
+
every flow "production": false. Never implied by --all or by no
|
|
345
|
+
flags: this file is the opt-in that lets qa run write to the
|
|
346
|
+
app (on a loopback base), so write it only against a disposable DB
|
|
347
|
+
Never overwrites an existing file; prints created / skipped-exists / appended
|
|
348
|
+
for each action, so a second run is visibly a no-op.`,"run serve":`qa run serve <repo> [--prod] [--port N] [--build-cmd CMD] [--start-cmd CMD] [--timeout S]
|
|
349
|
+
qa run serve <repo> --stop
|
|
350
|
+
qa run serve <repo> --status
|
|
351
|
+
|
|
352
|
+
Builds (with --prod, using the repo's own build script) then starts the app in
|
|
353
|
+
the background -- start script for --prod, dev script otherwise -- detached in
|
|
354
|
+
its own process group so this command returns while the app keeps running.
|
|
355
|
+
Output goes to .verify/serve.log; state to .verify/serve.json. Waits for
|
|
356
|
+
liveness (any HTTP response at --port, default 3000) up to --timeout seconds
|
|
357
|
+
(default 180), then prints the exact "qa verify ... --base ..." line to run
|
|
358
|
+
next. Refuses a second start while a recorded pid is still alive, and refuses
|
|
359
|
+
if the port is already in use. A build failure or a liveness timeout exits 2
|
|
360
|
+
with the log path. --stop kills the process group and clears the state file
|
|
361
|
+
(always exit 0, idempotent). --status probes the recorded server and reports
|
|
362
|
+
liveness plus dev/prod detection: exit 0 live, 1 not running.`,"run watch":`qa run watch <repo> [--debounce MS] [--once]
|
|
363
|
+
|
|
364
|
+
Watches source files -- recursive fs.watch where the platform supports it
|
|
365
|
+
(darwin, win32, linux on node >= 20), mtime polling every 2s otherwise --
|
|
366
|
+
ignoring .verify, node_modules, .git, .next, dist, build, out. On change,
|
|
367
|
+
re-runs the static gate (\`qa verify <repo>\`, a real child process) and
|
|
368
|
+
prints fixed / new / persisting findings by fid since the previous archived
|
|
369
|
+
run. --debounce sets the quiet period before a cycle runs (default 400ms);
|
|
370
|
+
changes that arrive while a cycle is already running coalesce into exactly
|
|
371
|
+
one follow-up cycle, never one per file. --once runs a single cycle after
|
|
372
|
+
the first change and exits with that cycle's own gate exit code (for
|
|
373
|
+
scripting and tests). Ctrl-C stops cleanly: the watcher stops taking new
|
|
374
|
+
changes immediately, and an in-flight cycle is allowed to finish archiving
|
|
375
|
+
before the process exits.`,"measure shadow":`qa measure shadow replay <repo> --trace FILE --base URL --isolated [--policy verify.shadow.json] [--ids /route/:id=123,...] [--auth state.json] [--json]
|
|
376
|
+
|
|
377
|
+
Read-only replay of a collected shadow trace (bin/shadow-collector.mjs output)
|
|
378
|
+
against an isolated target. Refuses (exit 2) without --isolated, against a
|
|
379
|
+
target that looks production-like, without a policy declaring
|
|
380
|
+
replay.isolated === true and replay.production === false, when the trace uses
|
|
381
|
+
a route/endpoint template the policy's allow_routes/allow_endpoints does not
|
|
382
|
+
cover, or when there is nothing replayable. Only replays GET navigations, in
|
|
383
|
+
transition_index order; a non-GET trace row is always skipped, never issued.
|
|
384
|
+
Compares same-origin traffic observed during each navigation to the trace's
|
|
385
|
+
expectation for the same endpoint. Writes
|
|
386
|
+
.verify/autonomous/shadow-replay.json. Exit 1 when a measured endpoint moved
|
|
387
|
+
from 2xx to 4xx/5xx (finding shadow.drift), 0 when replayed clean.`,"measure invariants":`qa measure invariants mine <repo>
|
|
388
|
+
qa measure invariants review <repo> --approve ID [--by NAME]
|
|
389
|
+
qa measure invariants review <repo> --revoke ID
|
|
390
|
+
qa measure invariants enforce <repo>
|
|
391
|
+
qa measure invariants status <repo>
|
|
392
|
+
|
|
393
|
+
The independent invariant authority, separate from the Rust output directory
|
|
394
|
+
(agent/src/invariants/*.rs fail-closes Reviewed -> Enforced on purpose; this
|
|
395
|
+
is the authority that can actually promote one). Proofs are sha256 digests of
|
|
396
|
+
archived run files under .verify/runs/<id>/, recomputed at approve time and
|
|
397
|
+
again at pack-load time -- a tampered archive makes approve refuse and makes
|
|
398
|
+
the generated pack throw (infrastructure exit 2), never a finding.
|
|
399
|
+
|
|
400
|
+
mine mines route-level candidates over archived runtime runs, writes
|
|
401
|
+
.verify/invariants/candidates.json
|
|
402
|
+
review --approve ID promotes a candidate into the committed policy file
|
|
403
|
+
<repo>/verify.invariants.json (refused below support 2, above 0
|
|
404
|
+
counterexamples, or on any proof mismatch); --revoke ID removes one
|
|
405
|
+
enforce regenerates .verify/rules/invariants.mjs, a runtime rule pack that
|
|
406
|
+
re-verifies every enforced proof on load and files
|
|
407
|
+
invariant.violated P1 findings for route-reachable / renders-text
|
|
408
|
+
(the only kinds a pack can independently re-check per cell; other
|
|
409
|
+
approved kinds still get proof-tamper protection, see \`status\`)
|
|
410
|
+
status lifecycle table: candidate / approved / enforced, with support,
|
|
411
|
+
counterexamples, proof validity, and whether a kind is runtime-checked`,"fix plan":`qa fix plan <repo> [--run ID] [--lanes N]
|
|
412
|
+
qa fix gate <class> <repo>
|
|
413
|
+
qa fix close <class> <repo>
|
|
414
|
+
qa fix rebase <repo>
|
|
415
|
+
qa fix board <repo>
|
|
416
|
+
|
|
417
|
+
Parallel class-level fixes. plan groups the latest archived run's findings
|
|
418
|
+
into classes ((rule) or (kind, selector)), resolves the files each would
|
|
419
|
+
touch, packs classes into batches whose files are disjoint (--lanes N wide,
|
|
420
|
+
default 4), opens one git worktree + branch qa/<class> per lane of the first
|
|
421
|
+
batch under .verify/lanes/<class>/tree, writes .verify/lanes/<class>/brief.md
|
|
422
|
+
and .verify/board.md. Exit 1 lanes ready, 0 nothing to plan, 2 invalid.
|
|
423
|
+
gate runs qa verify in that worktree with the plan's base and flags under one
|
|
424
|
+
lock; exit 0 every finding of the class gone and no regression, 1 red (the
|
|
425
|
+
worktree is reverted and the attempt recorded), 2 could not gate. close needs
|
|
426
|
+
a passing gate: commits, pushes qa/<class>, opens the PR with the numbers,
|
|
427
|
+
records it; never merges. rebase rebases every open lane onto main and
|
|
428
|
+
re-gates; a conflict marks the lane conflict. board regenerates
|
|
429
|
+
.verify/board.md. qa run plans the lanes and leaves source fixes and commits to
|
|
430
|
+
the host agent; it never executes a fixer.`,"measure crud":`qa measure crud <repo> --base URL --mutate --max-mutations N [--flow NAME] [--auth state.json]
|
|
431
|
+
|
|
432
|
+
Executable CRUD reconciliation: performs and observes create/read/update/delete
|
|
433
|
+
plus reload and navigation reconciliation through a real browser, per flow x
|
|
434
|
+
persona declared in <repo>/verify.crud.json (see references/verify.crud.example.json).
|
|
435
|
+
Every browser context is intercepted before transmission: same-origin writes
|
|
436
|
+
must match the selected declared flow and shared budget, including popups and
|
|
437
|
+
workers; service workers are blocked. An authorization gap invalidates the run.
|
|
438
|
+
Each persona's storage state is minted through <repo>/verify.auth-launcher.mjs
|
|
439
|
+
(the same launcher and lease the sweep uses; verify.roles.json carries no
|
|
440
|
+
credentials) and checked against the identity oracle where the app serves
|
|
441
|
+
one; a persona that cannot be provisioned refuses the run rather than
|
|
442
|
+
measuring the login page. --auth <state.json> overrides with one file for
|
|
443
|
+
every persona; --launcher <path> and --oracle-path <path> override the launcher
|
|
444
|
+
and the oracle endpoint exactly as they do for the sweep (defaults
|
|
445
|
+
<repo>/verify.auth-launcher.mjs and /__qa/identity).
|
|
446
|
+
Refuses without --mutate, refuses if verify.crud.json is absent or any flow's
|
|
447
|
+
"production" is not literally false, without a positive --max-mutations,
|
|
448
|
+
or against a non-loopback base (unless the explicit non-production override is
|
|
449
|
+
used; production-looking names are always refused). A persona listed under a flow's "denied" is exercised for
|
|
450
|
+
authorization only, never the full cycle. Mutations are capped at
|
|
451
|
+
--max-mutations; a create this run performs is always affordable
|
|
452
|
+
to clean up too, and cleanup is verified, never assumed. Writes
|
|
453
|
+
.verify/crud-observed.json. Exit 0 measured clean, 1 on any P0/P1 finding
|
|
454
|
+
(crud.create-failed, crud.write-lost, crud.update-lost, crud.delete-ineffective,
|
|
455
|
+
crud.unauthorized-allowed, crud.cleanup-failed), 2 invalid (refused, or the run
|
|
456
|
+
measured nothing).
|
|
457
|
+
|
|
458
|
+
The create surface is found as \`form:visible\` on the flow's route, waited for
|
|
459
|
+
(--form-timeout, default 10000ms). A surface that is not there at load -- a
|
|
460
|
+
dialog behind a quick action, a form gated on a filter -- needs the flow to
|
|
461
|
+
name an entry in <repo>/verify.journeys.mjs via "journey": its run() is
|
|
462
|
+
called after landing on the route and must leave the surface mounted (the
|
|
463
|
+
sweep drives the same entry, so write nothing in run()). "form_selector"
|
|
464
|
+
names the surface when it is not a <form> (a dialog), "submit_selector" the
|
|
465
|
+
control inside it that performs the write (clicked instead of
|
|
466
|
+
requestSubmit()). A journey entry may carry create(page, { sentinel }) to
|
|
467
|
+
perform the create itself where the generic filler cannot (a <select>-only
|
|
468
|
+
form), returning { sentinel } when the text that proves the write is not
|
|
469
|
+
what was typed. "update_selector" / "delete_selector" set to false declare
|
|
470
|
+
the surface has no such control (step stays unmeasured, with that reason);
|
|
471
|
+
"cleanup_required": false skips the direct DELETE for a record type the API
|
|
472
|
+
cannot delete. Templates interpolate only {fixtureId} and {sentinel}; unresolved
|
|
473
|
+
tokens are unmeasured, never guessed. Schema-v2 declarations may also name
|
|
474
|
+
bounded canonical entity aliases for coherence.`,"measure coherence":`qa measure coherence <repo> [--views] [--base URL --mutate --flow NAME --max-mutations N]
|
|
475
|
+
[--plan agent-result.json] [--flow-workers N] [--runid ID]
|
|
476
|
+
|
|
477
|
+
Without --mutate, prints the coherence-IR denominators (writes, reads,
|
|
478
|
+
surfaces per route component, candidates with their affected surfaces) and the
|
|
479
|
+
fan-out view for every candidate mutation; --views adds the full fan-out
|
|
480
|
+
(mutation -> surfaces) and fan-in (surface -> writers) tables and the
|
|
481
|
+
obligation universe (mutation x surface x principal x context). No browser. Runtime mode is fail-closed: every
|
|
482
|
+
selected flow must be schema-v2 with a crosscheck and the repository must
|
|
483
|
+
provide a provenance adapter (verify.crud.json schema_version 2 with a
|
|
484
|
+
crosscheck, verify.coherence.mjs, verify.coherence-provenance.mjs).
|
|
485
|
+
--plan <agent-result.json> executes the obligations the Rust kernel selected
|
|
486
|
+
(auto-detected from the latest archived run when its coherenceObligations
|
|
487
|
+
dimension is measured) and reports planned/reached/compared/agreed;
|
|
488
|
+
--flow-workers N runs isolated flows in parallel (distinct entities, or both
|
|
489
|
+
flows declaring isolation: per-flow). Schema-v2 canonical entity aliases join
|
|
490
|
+
declared endpoint families while retaining the original endpoint evidence.
|
|
491
|
+
Incomplete evidence exits 2, never clean.`,"measure experiments":`qa measure experiments <repo> --base URL [--budget-cells N] [--wall-ms MS] [--families a,b] [--mutate] [sweep flags] [--json]
|
|
492
|
+
|
|
493
|
+
Executes the accepted candidates from .verify/autonomous/experiment-queue.json,
|
|
494
|
+
or --queue <path> naming one explicitly (a ranked plan -- never auto-executed
|
|
495
|
+
elsewhere), as targeted sweeps, one control/treatment pair per (route, family):
|
|
496
|
+
latency-baseline-vs-injection plain sweep vs the same route with --states slow
|
|
497
|
+
role-pair the two owning roles' cells from one sweep
|
|
498
|
+
(needs two roles in verify.roles.json)
|
|
499
|
+
mutation-vs-no-mutation unmeasured: generic guessed replay is disabled and
|
|
500
|
+
fails closed; use declared CRUD or coherence instead
|
|
501
|
+
navigation-vs-reload and cache-enabled-vs-disabled are also not executable by
|
|
502
|
+
this tool and are always reported unmeasured with the reason. A trial whose sweep
|
|
503
|
+
exits 2 invalidates that family for that route -- never averaged. Writes
|
|
504
|
+
.verify/experiment.json, .verify/experiment-observations.json,
|
|
505
|
+
.verify/autonomous/experiment-observation.json (via bin/experiment-runner.mjs)
|
|
506
|
+
and .verify/autonomous/experiment-execution.json. Exit 0 measured clean, 1 a
|
|
507
|
+
measured treatment introduced a P0/P1 the control lacked, 2 nothing was
|
|
508
|
+
measured (missing queue, budget exhausted before anything ran, or every
|
|
509
|
+
combination was ineligible/invalid).`,init:`qa init <repo> [--study|--analysis] [--roles|--crud|--ignore|--workflow|--hook|--all]
|
|
510
|
+
|
|
511
|
+
Scaffolds declarations without overwriting them. --study writes source-derived research; --analysis additionally writes .verify/analysis.json and analysis.md with an evidence ledger, feature/context/risk/story/mutation/proof/fix view. It never invents business requirements or executes mutations.`,show:`qa show [kind] <repo> ... kind: triage (default), next, report, ledger, graph, analysis
|
|
512
|
+
|
|
513
|
+
Reads results. \u2018show analysis\u2019 renders the generated analysis and \u2018--format json\u2019 emits its structured artifact.`,"dev validate":`qa dev validate <repo> [--run ID]
|
|
514
|
+
|
|
515
|
+
Validates every present artifact under <repo>/.verify/ (inventory.json,
|
|
516
|
+
classify.json, sweep.json, research.json, stability.json, next.json,
|
|
517
|
+
crud-observed.json, autonomous/shadow-replay.json,
|
|
518
|
+
autonomous/experiment-execution.json) against the JSON Schemas in schemas/,
|
|
519
|
+
using a dependency-free draft 2020-12 subset validator. Also validates the
|
|
520
|
+
archived copies (inventory.json, classify.json, sweep.json, run.json) under
|
|
521
|
+
.verify/runs/<id>/ -- <id> is --run when given, else the latest archived run
|
|
522
|
+
if one exists; an explicit --run that does not resolve is an error. Prints a
|
|
523
|
+
table of artifact -> ok/absent/VIOLATIONS/ERROR with the path and message of
|
|
524
|
+
each violation. Absent is not a failure -- most artifacts are produced by
|
|
525
|
+
commands nobody has run yet in a given repo. Exit 0 when every present
|
|
526
|
+
artifact validates, 2 on any schema violation or unreadable/malformed file.`,measure:`qa measure <kind> <repo> ... kind: coherence, crud, experiments, shadow, invariants
|
|
527
|
+
|
|
528
|
+
Dispatches to one of the five deep-measurement adapters. Each kind keeps its
|
|
529
|
+
own flags and subwords -- \`qa measure shadow replay <repo> ...\`, \`qa measure
|
|
530
|
+
invariants mine|review|enforce|status <repo>\`. crud and coherence --mutate
|
|
531
|
+
perform real, declared mutation writes under the repo's own opt-in
|
|
532
|
+
(verify.crud.json); every other kind is read-only. See qa help measure
|
|
533
|
+
coherence / measure crud / measure experiments / measure shadow / measure
|
|
534
|
+
invariants for what each one measures and its exit codes.`,show:"qa show [kind] <repo> ... kind: triage (default), next, report, ledger, graph, analysis\n\nReads results. With no kind, prints ranked findings and coverage (triage).\nreport's own sub-verbs stay reachable both nested (`qa show report\ndiff|runs|ci <repo>`) and directly (`qa show diff|runs|ci <repo>`). See qa\nhelp show triage / show next / show report / show ledger / show graph for\neach one's flags, output, and exit codes. `qa show analysis <repo>` renders the generated analysis; `--format json` emits its structured artifact.",fix:"qa fix <sub> ... sub: plan, gate, close, rebase, board (lanes) \xB7 fp add|list|rm\n\nParallel class-level remediation (`qa fix plan <repo>`, `qa fix gate <class>\n<repo>`, ...) and the cross-repo false-positive memory (`qa fix fp\nadd|list|rm ...`). See qa help fix plan / fix fp for flags, output, and exit\ncodes.",dev:"qa dev <kind> ... kind: atlas, selftest, validate, version\n\nMaintaining the harness itself, not the target repo under test: `qa dev atlas\n--strict`, `qa dev selftest --fast`, `qa dev validate <repo>`, `qa dev\nversion --json`. See qa help dev atlas / dev selftest / dev validate / dev\nversion for flags and exit codes.","dev version":`qa dev version
|
|
535
|
+
|
|
536
|
+
Prints the installed name, semver, short commit, node version, and install
|
|
537
|
+
root -- both as one line and, with --json, as {name, version, commit, node,
|
|
538
|
+
root}.`};export{A as BOOLEAN_FLAGS,w as COMMAND_VALUE_FLAGS,f as EXIT,G as HELP,R as InvalidError,h as OPTIONAL_VALUE_FLAGS,l as REDACTED,u as REMOVED_FLAGS,v as SECRET_VALUE_FLAGS,E as SELFTEST_SUITES,g as URL_VALUE_FLAGS,d as UsageError,m as VALUE_FLAGS,M as forwardFlags,V as lpad,F as makeOutput,L as pad,U as parseArgs,_ as redactArgs,b as redactValue,C as rejectRemovedFlags,P as resolveRepo,W as table};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash as k}from"node:crypto";import{canonicalize as b}from"../jcs.mjs";import{deriveOperationReadPairs as x,keyCoverage as P,reduceCoverage as E}from"./cache-key.mjs";function K(t,n){const o=P(t,n);return o==="covered"?!0:o==="not-covered"?!1:"unresolved"}const M=E;function N(t,n){return x(new Map((t??[]).map(o=>[o.id,o])),new Map((n??[]).map(o=>[o.id,o])))}function z(t){const n={kind:t.kind,mutationId:t.mutationId??t.mutation?.id??null,family:t.family??null,readIds:[...t.readIds??[]].sort()};return"crc:"+k("sha256").update(b(n)).digest("hex")}function C(t){return(t.writeCandidates??0)===0||(t.writesResolved??0)===0?"unmeasured":t.writesUnresolved||t.readsUnresolved||t.keysUnresolved||t.operationsUnresolved||t.operationReadPairsUnresolved||t.invalidationsUnresolved||t.ownershipEdgesUnresolved||t.writersWithNoReaderSurface||t.coverageUndecidable?"partial":"measured"}function R(t,n){return n!=="unmeasured"?null:(t.writeCandidates??0)===0?"no-write-candidates":"no-writes-resolved"}const _=Object.freeze(["no-write-candidates","no-writes-resolved"]);function $(t,{waivers:n=[]}={}){const o={...t?.coverage?.counts??{}},v=C(o),i={status:v,reason:R(o,v),counts:o,unresolved:[...t?.coverage?.unresolved??[]]},l=new Map;for(const e of t?.surfaces??[])for(const s of e.readIds??[])l.has(s)||l.set(s,[]),l.get(s).push(e);const u=[],S=new Map((t?.cacheKeys??[]).map(e=>[e.id,e])),U=new Map((t?.cacheOperations??[]).map(e=>[e.id,{...e,material:{...e.material,key:S.get(e.material.keyId)?.material??null}}]));for(const e of t?.mutations??[]){if(!e.family||!(e.writes??(e.pattern?[{pattern:e.pattern}]:[])).length)continue;const f=(t.reads??[]).filter(r=>r.family&&r.family===e.family);if(!f.length)continue;const g=(e.operationIds??[]).map(r=>U.get(r)).filter(Boolean),O=N(g,f),m=[],c=[];for(const r of f){const a=O.filter(p=>p.readId===r.id).map(p=>p.effectCoverage),I=M(a);I!=="covered"&&(I==="unresolved"?c.push(r):m.push(r))}if(!m.length){c.length&&(o.coverageUndecidable=(o.coverageUndecidable??0)+1,i.unresolved.push({kind:"coherence.coverage-undecidable",mutationId:e.id,family:e.family??null,source:{file:e.file,line:e.line},readIds:c.map(r=>r.id).sort()}));continue}const h=new Set,w=new Set,y=new Set;for(const r of m)for(const a of l.get(r.id)??[])h.add(a.id),a.route&&w.add(a.route),a.route&&a.component&&y.add(`${a.route}:${a.component}`);const d={kind:g.length===0?"coherence.no-invalidation-candidate":"coherence.partial-invalidation-candidate",severity:"P3",confirmed:!1,mutationId:e.id,source:{file:e.file,line:e.line},family:e.family??null,readIds:m.map(r=>r.id).sort(),unresolvedReadIds:c.map(r=>r.id).sort(),surfaceIds:[...h].sort(),routes:[...w].sort(),components:[...y].sort(),coverage:c.length?"partial":i.status};(e.familySource==="declaration"||f.some(r=>r.familySource==="declaration"))&&(d.confidence="uncertain",d.basis="declaration-backed"),d.rootCauseId=z(d),n.includes(d.rootCauseId)&&(d.waived=!0),u.push(d)}return u.sort((e,s)=>e.rootCauseId.localeCompare(s.rootCauseId)),i.status=C(o),i.reason=R(o,i.status),i.unresolved.sort((e,s)=>JSON.stringify(e).localeCompare(JSON.stringify(s))),{coverage:i,candidates:u,waived:u.filter(e=>e.waived)}}export{_ as UNSUPPORTED_COVERAGE_REASONS,$ as analyzeCoherence,z as coherenceRootCauseId,N as operationReadPairs,K as patternCovers,M as reduceOperationCoverage};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{createCoherenceIdentity as Q}from"./identity.mjs";import{canonicalize as E}from"../jcs.mjs";import{deriveOperationReadPairs as ce,effectCoverage as fe,keyCoverage as pe,reduceCoverage as me}from"./contracts.mjs";const $=/^[A-Za-z_$][\w$]*$/,U=new Set(["dynamic-key","unknown-helper","unsupported-client","unsupported-expression","unsafe-number","non-json-value","contract-conflict","dynamic-import","namespace-spread","export-star","computed-member","ambiguous-alias","unresolved-module","unsupported-syntax"]);function y(n,e=0,t=n.length){for(;e<t&&/\s/.test(n[e]);)e++;for(;t>e&&/\s/.test(n[t-1]);)t--;return{start:e,end:t,text:n.slice(e,t)}}function S(n,e=","){const t=[];let s=0,r=null,l=!1;const o=[],a={"(":")","[":"]","{":"}"};for(let d=0;d<n.length;d++){const c=n[d];if(r){!l&&c===r&&(r=null),l=!l&&c==="\\",c!=="\\"&&(l=!1);continue}if(c==="'"||c==='"'||c==="`"){r=c;continue}if(a[c])o.push(a[c]);else if(c===o.at(-1))o.pop();else if(c===e&&o.length===0){const k=y(n,s,d);k.text&&t.push(k),s=d+1}}const h=y(n,s);return h.text&&t.push(h),t}function T(n){let e=null,t=!1;const s=[],r={"(":")","[":"]","{":"}"};for(let l=0;l<n.length;l++){const o=n[l];if(e){!t&&o===e&&(e=null),t=!t&&o==="\\",o!=="\\"&&(t=!1);continue}if(o==="'"||o==='"'||o==="`"){e=o;continue}if(r[o])s.push(r[o]);else if(o===s.at(-1))s.pop();else if(o===":"&&s.length===0)return l}return-1}function q(n,e,t=0){let s=null,r=!1;const l=[],o={"(":")","[":"]","{":"}"};for(let a=t;a<=n.length-e.length;a++){const h=n[a];if(s){!r&&h===s&&(s=null),r=!r&&h==="\\",h!=="\\"&&(r=!1);continue}if(h==="'"||h==='"'||h==="`"){s=h;continue}if(o[h]){l.push(o[h]);continue}if(h===l.at(-1)){l.pop();continue}if(l.length===0&&n.startsWith(e,a))return a}return-1}function _(n){if(n[0]==="`"&&/\$\{/.test(n))return null;if(n[0]==='"')try{return JSON.parse(n)}catch{return null}const e=n[0];if(e!=="'"&&e!=="`"||n.at(-1)!==e)return null;let t="";const s={n:`
|
|
2
|
+
`,r:"\r",t:" ",b:"\b",f:"\f",v:"\v",0:"\0"};for(let r=1;r<n.length-1;r++){const l=n[r];if(l!=="\\"){t+=l;continue}const o=n[++r];if(o==null)return null;if(o==="x"&&/^[0-9a-fA-F]{2}$/.test(n.slice(r+1,r+3))){t+=String.fromCodePoint(Number.parseInt(n.slice(r+1,r+3),16)),r+=2;continue}if(o==="u"&&/^[0-9a-fA-F]{4}$/.test(n.slice(r+1,r+5))){t+=String.fromCodePoint(Number.parseInt(n.slice(r+1,r+5),16)),r+=4;continue}t+=s[o]??o}return t}function N(n){const e=n.trim();if(/^(?:true|false|null)$/.test(e))return{ok:!0,value:e==="null"?null:e==="true"};if(/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(e)){const t=Number(e);return Number.isFinite(t)&&Math.abs(t)<=Number.MAX_SAFE_INTEGER?{ok:!0,value:t}:{ok:!1,reason:"unsafe-number"}}if(/^['"`]/.test(e)){const t=_(e);return t===null?{ok:!1,reason:"dynamic-key"}:{ok:!0,value:t}}if(e.startsWith("[")&&e.endsWith("]")){const t=[];for(const s of S(e.slice(1,-1))){if(s.text.startsWith("..."))return{ok:!1,reason:"unsupported-expression"};const r=N(s.text);if(!r.ok)return r;t.push(r.value)}return{ok:!0,value:t}}if(e.startsWith("{")&&e.endsWith("}")){const t={};for(const s of S(e.slice(1,-1))){const r=T(s.text);if(r<0||s.text.startsWith("...")||s.text.startsWith("["))return{ok:!1,reason:"unsupported-expression"};const l=s.text.slice(0,r).trim(),o=$.test(l)?l:_(l);if(o===null)return{ok:!1,reason:"unsupported-expression"};const a=N(s.text.slice(r+1));if(!a.ok)return a;t[o]=a.value}return{ok:!0,value:t}}return{ok:!1,reason:"unsupported-expression"}}function O(n,e,t){if(!n.file)return null;const s=n.source??"",r=(n.start??0)+e,l=(n.start??0)+t,o=s.slice(0,r);return{file:n.file,start:r,end:l,line:o.split(`
|
|
3
|
+
`).length,column:r-o.lastIndexOf(`
|
|
4
|
+
`)}}function I(n,e,t,s,r,l,o={}){return U.has(t)||(t="unsupported-expression"),{kind:n,expression:e,ownerSymbol:s.ownerSymbol??null,targetSymbol:o.targetSymbol??null,origin:O(s,r,l)??{file:s.file??"<unknown>",start:0,end:Math.max(1,e.length),line:1,column:1},resolutionPath:o.resolutionPath??[],resolution:"unresolved",reason:t}}function V(n,e,t,s){const r=N(n);if(r.ok)return{kind:"literal",value:r.value,...O(e,t,s)?{origin:O(e,t,s)}:{}};const l=n.trim(),o=$.test(l)||/^`[\s\S]*\$\{/.test(l)?"parameter":/\(/.test(l)?"call":"unknown",a=r.reason==="unsafe-number"?r.reason:o==="unknown"?"unsupported-expression":"dynamic-key";return I(o,l,a,e,t,s)}function Y(n,e={}){const t=String(n??""),s=y(t);if(!s.text)return{segments:[I("unknown","","unsupported-expression",e,s.start,Math.max(s.end,s.start+1))]};if(s.text.startsWith("[")&&s.text.endsWith("]")){const l=s.start+1;return{segments:S(s.text.slice(1,-1)).map(o=>V(o.text,e,l+o.start,l+o.end))}}const r=$.test(s.text)?e.bindings?.get?.(s.text)??e.bindings?.[s.text]:null;return Array.isArray(r)&&r.length===1?Y(r[0].expression,e):{segments:[V(s.text,e,s.start,s.end)]}}function j(n){const e=(n?.segments??[]).map(t=>t.kind==="literal"?{kind:"literal",value:t.value}:Object.fromEntries(["kind","expression","ownerSymbol","targetSymbol","origin","resolutionPath","resolution","reason"].filter(s=>Object.hasOwn(t,s)).map(s=>[s,t[s]])));return Q("cache-key",{segments:e})}function Z(n){return(n?.segments??[]).every(e=>e.kind==="literal"||e.kind==="wildcard"||e.resolution==="resolved")?"resolved":"unresolved"}function ee(n,e){return(n?.declarations??[]).find(t=>t.key===e)??null}function te(n,e,t){const s=(n?.declarations??[]).filter(l=>l.file===e&&l.name===t);if(s.length===1)return s[0].key;const r=(n?.imports??[]).filter(l=>l.file===e&&l.local===t&&l.resolution==="resolved");return r.length===1?r[0].target:null}function L(n){const e=f=>{let i=f;for(;i<n.length;){if(/\s/.test(n[i])){i++;continue}if(n[i]==="/"&&n[i+1]==="/"){for(i+=2;i<n.length&&n[i]!==`
|
|
5
|
+
`;)i++;continue}if(n[i]==="/"&&n[i+1]==="*"){const x=n.indexOf("*/",i+2);if(x<0)return-1;i=x+2;continue}break}return i},t=e(0);if(n.slice(t,t+6)!=="return"||/[\w$]/.test(n[t+6]??""))return null;let s=t+6;for(;n[s]===" "||n[s]===" ";)s++;if(s>=n.length||/[\r\n\u2028\u2029]/.test(n[s]))return null;const r={"(":")","[":"]","{":"}"},l=new Set(Object.values(r)),o=[];let a=null,h=!1,d=null,c=s,k=-1;for(let f=s;f<n.length;f++){const i=n[f],x=n[f+1];if(d==="line"){i===`
|
|
6
|
+
`&&(d=null);continue}if(d==="block"){i==="*"&&x==="/"&&(d=null,f++);continue}if(a){c=f+1,h?h=!1:i==="\\"?h=!0:i===a&&(a=null);continue}if(i==="/"&&x==="/"){d="line",f++;continue}if(i==="/"&&x==="*"){d="block",f++;continue}if(i==="'"||i==='"'||i==="`"){a=i,c=f+1;continue}if(r[i])o.push(r[i]);else if(l.has(i)){if(o.pop()!==i)return null}else if(i===";"&&o.length===0){k=f;break}/\s/.test(i)||(c=f+1)}if(a||d==="block"||o.length||c<=s||k>=0&&e(k+1)!==n.length||k<0&&e(c)!==n.length)return null;const u=y(n,s,c);return u.text?{text:u.text,start:u.start}:null}function ne(n,e){const t=e.find(l=>l.file===n.file)?.source??"";if(n.kind==="const"&&t[n.bodyStart-1]!=="{"){const l=t.slice(n.bodyStart,n.end);let o=y(l);return o.text.endsWith(";")&&(o=y(l,o.start,o.end-1)),{source:t,expression:o.text,start:n.bodyStart+o.start}}const s=t.slice(n.bodyStart,Math.max(n.bodyStart,n.end-1)),r=L(s);return r?{source:t,expression:r.text,start:n.bodyStart+r.start}:{source:t,expression:"",start:n.bodyStart}}function re(n,e){const t=y(n);if(!t.text.startsWith("{")||!t.text.endsWith("}"))return null;for(const s of S(t.text.slice(1,-1))){const r=T(s.text);if(r<0)continue;const l=s.text.slice(0,r).trim();if(($.test(l)?l:_(l))===e){const a=y(s.text,r+1);return{text:a.text,start:t.start+1+s.start+a.start}}}return null}function se(n){const e=q(n,"=>");if(e>=0){let t=n.slice(0,e).trim().replace(/^async\s+/,"");t.startsWith("(")&&t.endsWith(")")&&(t=t.slice(1,-1));const s=S(t).map(l=>l.text.split(/[:=]/)[0].trim()).filter(l=>$.test(l));let r=y(n,e+2);if(r.text.startsWith("{")&&r.text.endsWith("}")){const l=L(r.text.slice(1,-1));if(!l)return{params:s,body:"",start:r.start+1};r={text:l.text,start:r.start+1+l.start}}return{params:s,body:r.text,start:r.start}}return null}function D(n,e,t){const s=y(String(n??"")),r={text:s.text,options:{...e,start:(e.start??0)+s.start}};return t.get(r.text)??r}function b(n,e,t="dynamic-key",s=null,r=[]){return{patterns:[{segments:[I(/\(/.test(n)?"call":"parameter",n,t,e,0,Math.max(1,n.length),{targetSymbol:s,resolutionPath:r})]}],provenance:s?[{method:"symbol-evaluation",targetSymbol:s,resolutionPath:r}]:[],resolution:"unresolved",reason:t}}function B(n){const e=new Map;for(const t of n)e.set(E(j(t).material),t);return[...e.entries()].sort(([t],[s])=>t.localeCompare(s)).map(([,t])=>t)}function W(n,e,t){const s=D(n,e,t.env),r=s.text.replace(/\s+as\s+const\s*$/,"");e=s.options;const l=q(r,"?");if(l>=0){const p=q(r,":",l+1);if(p>l){const m=W(r.slice(l+1,p),{...e,start:(e.start??0)+l+1},t),g=W(r.slice(p+1),{...e,start:(e.start??0)+p+1},t),v=B([...m.patterns,...g.patterns]);return{patterns:v,provenance:[...m.provenance,...g.provenance],resolution:v.every(K=>Z(K)==="resolved")?"resolved":"unresolved",reason:m.reason??g.reason??null}}}if(r.startsWith("[")&&r.endsWith("]")){let p=[{segments:[]}];for(const m of S(r.slice(1,-1))){const g=m.text.match(/^\.\.\.([\s\S]+)$/),v={...e,start:(e.start??0)+1+m.start+(g?3:0)},X=W(g?g[1]:m.text,v,t).patterns.map(A=>g||A.segments.length===1?A.segments:[I("unknown",m.text,"unsupported-expression",v,0,m.text.length)]);p=p.flatMap(A=>X.map(H=>({segments:[...A.segments,...H]})))}return p=B(p),{patterns:p,provenance:[],resolution:p.every(m=>Z(m)==="resolved")?"resolved":"unresolved",reason:null}}const o=N(r);if(o.ok)return{patterns:[{segments:[{kind:"literal",value:o.value,...O(e,0,r.length)?{origin:O(e,0,r.length)}:{}}]}],provenance:[],resolution:"resolved",reason:null};const a=r.match(/^([A-Za-z_$][\w$]*)(?:\.([A-Za-z_$][\w$]*))?\s*\(([\s\S]*)\)$/),h=r.match(/^([A-Za-z_$][\w$]*)$/);if(!a&&!h)return b(r,e,o.reason??"unsupported-expression");const d=(a??h)[1],c=a?.[2]??null,k=e.bindings?.get?.(d);if(!a&&k?.length===1&&!(e.units??[]).some(p=>p.file===e.file))return W(k[0].expression,e,t);const u=te(e.symbolIndex,e.file,d);if(!u)return b(r,e,"dynamic-key");if(t.seen.has(u)||t.depth>=24)return b(r,e,"dynamic-key",u,[...t.path,u]);const f=ee(e.symbolIndex,u);if(!f)return b(r,e,"dynamic-key",u,[...t.path,u]);const i=ne(f,e.units??[]),x=c?re(i.expression,c):{text:i.expression,start:0};if(x==null||x.text==="")return b(r,e,"unsupported-expression",u,[...t.path,u]);let R=x.text,F=i.start+x.start;const P=a?se(R):null,G=a?r.indexOf("(")+1:0,C=a?S(a[3]).map(p=>D(p.text,{...e,start:(e.start??0)+G+p.start},t.env)):[],z=new Map(t.env);if(P)P.params.forEach((p,m)=>{C[m]&&z.set(p,C[m])}),R=P.body,F+=P.start;else if(a&&!c&&(f.kind==="function"||f.kind==="const"&&f.supported))i.source.slice(f.paramsStart,f.paramsEnd).split(",").map(m=>m.split(/[:=]/)[0].trim()).forEach((m,g)=>{$.test(m)&&C[g]&&z.set(m,C[g])});else if(a)return b(r,e,"unsupported-expression",u,[...t.path,u]);const J={...e,file:f.file,source:i.source,start:F,ownerSymbol:u},w=W(R,J,{depth:t.depth+1,seen:new Set([...t.seen,u]),path:[...t.path,u],env:z});w.provenance.push({method:"symbol-evaluation",targetSymbol:u,resolutionPath:[...t.path,u]});const M=e.contracts?.factories?.[u];if(M!=null){if(!Array.isArray(M)||M.some(v=>!Array.isArray(v?.segments)))return b(r,e,"contract-conflict",u,[...t.path,u]);const p=B(M),m=p.map(v=>E(j(v).material)),g=w.patterns.map(v=>E(j(v).material));return w.resolution==="resolved"&&E(g)!==E(m)?{...b(r,e,"contract-conflict",u,[...t.path,u]),provenance:[...w.provenance,{method:"explicit-contract",targetSymbol:u,resolutionPath:[...t.path,u]}]}:{patterns:p,provenance:[...w.provenance,{method:"explicit-contract",targetSymbol:u,resolutionPath:[...t.path,u]}],resolution:"resolved",reason:null}}return w}function ie(n,e={}){return W(String(n??""),e,{depth:0,seen:new Set,path:[e.ownerSymbol].filter(Boolean),env:new Map})}export{j as cacheKeyIdentity,ce as deriveOperationReadPairs,fe as effectCoverage,ie as evaluateCacheKeyExpression,pe as keyCoverage,Z as keyResolution,Y as parseTypedKeyExpression,me as reduceCoverage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{parseObligationId as b}from"./plan-join.mjs";const y={P0:0,P1:1,P2:2,P3:3},j=(n,r)=>(y[n]??9)<=(y[r]??9)?n:r;function V(n){const r=[3,4].includes(n?.schemaVersion),u=new Map;for(const c of n?.plan?.journeys??[])for(const s of c.targets??[])for(const i of s.obligations??[]){const d=r?i.mutationId&&i.surfaceId?i:null:b(i.id);if(!d)continue;const f=`${d.mutationId}|${d.surfaceId}`;u.has(f)||u.set(f,[]),u.get(f).push({...i,...d,route:s.route??null,journeyId:c.journeyId,flow:c.flow??null})}return u}function v(n,r=[]){return r.length?{runtime:"disagreed",reason:null}:n.length?n.filter(s=>s.status==="disagreed").length?{runtime:"disagreed",reason:null}:n.filter(s=>s.status==="agreed").length?{runtime:"agreed",reason:null}:{runtime:"unmeasured",reason:[...new Set(n.map(i=>i.reason??i.status))].sort().join(", ")}:{runtime:"unmeasured",reason:"no-planned-obligation-for-this-cause"}}function A(n,r){return n.waived?"waived":r==="disagreed"?"confirmed":r==="agreed"?"not-reproduced":"unmeasured"}function L({candidates:n=[],observed:r=null,surfaces:u=[],mutations:c=[]}){const s=V(r),i=new Map(u.map(e=>[e.id,e])),d=new Map(c.map(e=>[e.id,e])),f=r?.status==="measured"||r?.status==="partial",w=new Map;for(const e of r?.findings??[])e.rootCauseId&&(w.has(e.rootCauseId)||w.set(e.rootCauseId,[]),w.get(e.rootCauseId).push(e));const m=n.map(e=>{const o=(e.surfaceIds??[]).flatMap(t=>s.get(`${e.mutationId}|${t}`)??[]),a=w.get(e.rootCauseId)??[],l=r?v(o,a):{runtime:"unmeasured",reason:"no-runtime-evidence"},M=new Set(a.map(t=>t.route).filter(Boolean)),R=new Set(a.map(t=>t.surfaceId).filter(Boolean)),P=(e.surfaceIds??[]).map(t=>{const g=i.get(t),C=o.filter(k=>k.surfaceId===t);let p;return C.length?p=v(C):[3,4].includes(r?.schemaVersion)&&R.has(t)?p={runtime:"disagreed",reason:"finding-on-this-surface"}:![3,4].includes(r?.schemaVersion)&&g?.route&&M.has(g.route)?p={runtime:"disagreed",reason:"finding-on-this-route"}:p={runtime:"unmeasured",reason:r?"no-planned-obligation-for-this-surface":"no-runtime-evidence"},{surfaceId:t,route:g?.route??C[0]?.route??null,component:g?.component??null,runtime:p.runtime,reason:p.reason}}).sort((t,g)=>t.surfaceId.localeCompare(g.surfaceId)),h=d.get(e.mutationId)??null;return{rootCauseId:e.rootCauseId,kind:e.kind,mutationId:e.mutationId,mutation:h?.hook?`${h.hook}()`:null,source:e.source??(h?{file:h.file,line:h.line}:null),family:e.family??null,label:A(e,l.runtime),runtime:l.runtime,reason:l.reason,affectedSurfaces:P,routes:[...new Set(P.map(t=>t.route).filter(Boolean))].sort(),obligations:{planned:o.length,agreed:o.filter(t=>t.status==="agreed").length,disagreed:o.filter(t=>t.status==="disagreed").length,unmeasured:o.filter(t=>t.status==="unmeasured"||t.status==="reached").length},severity:"P3"}}),B=new Set(m.map(e=>e.rootCauseId)),$=new Map;if(f)for(const e of r?.findings??[]){const o=e.rootCauseId;if(!o||B.has(o))continue;const a=$.get(o)??{rootCauseId:o,kind:"coherence.unpredicted-runtime-cause",label:"confirmed",runtime:"disagreed",reason:null,mutationId:null,mutation:null,source:null,family:null,affectedSurfaces:[],routes:new Set,obligations:{planned:0,agreed:0,disagreed:0,unmeasured:0},severity:e.severity??"P3",surfaceIds:new Set};a.severity=j(e.severity??"P3",a.severity);const l=e.surfaceId??e.route??null;l&&!a.surfaceIds.has(l)&&(a.surfaceIds.add(l),a.affectedSurfaces.push({surfaceId:e.surfaceId??null,route:e.route??null,component:null,runtime:"disagreed",reason:null})),e.route&&a.routes.add(e.route),$.set(o,a)}for(const e of $.values())e.routes=[...e.routes].sort(),e.affectedSurfaces.sort((o,a)=>String(o.surfaceId).localeCompare(String(a.surfaceId))),delete e.surfaceIds,m.push(e);const S={confirmed:0,unmeasured:1,"not-reproduced":2,waived:3};m.sort((e,o)=>(S[e.label]??9)-(S[o.label]??9)||(y[e.severity]??9)-(y[o.severity]??9)||String(e.rootCauseId).localeCompare(String(o.rootCauseId)));const I={causes:m.length,confirmed:0,notReproduced:0,unmeasured:0,waived:0};for(const e of m)e.label==="confirmed"?I.confirmed++:e.label==="not-reproduced"?I.notReproduced++:e.label==="waived"?I.waived++:I.unmeasured++;return{status:r&&f?"measured":"unmeasured",reason:r?null:"no-runtime-evidence",causes:m,counts:I}}const E=n=>n.component?`${n.route}:${n.component}`:n.route??n.surfaceId;function N(n,{max:r=10}={}){const u=[],c=n?.causes??[];if(!c.length)return u;u.push(`causes: ${n.counts.confirmed} confirmed / ${n.counts.notReproduced} not reproduced / ${n.counts.unmeasured} unmeasured of ${n.counts.causes}`);for(const s of c.slice(0,r)){const i=s.source?.file?` (${s.source.file}:${s.source.line})`:"";u.push(` [${s.label}] ${s.mutation??s.mutationId??s.rootCauseId}${i} writes ${s.family??"(unknown)"}`);for(const d of s.affectedSurfaces)u.push(` ${E(d)} -- ${d.runtime}${d.reason?` (${d.reason})`:""}`)}return c.length>r&&u.push(` ... ${c.length-r} more`),u}export{L as linkCauses,N as renderCauses};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function d(t=[]){const u=new Set(t),a=new Map([...u].map(e=>[e,{released:!1,waiters:[]}])),s=[],n=(e,r,o)=>s.push({sequence:s.length,type:e,actor:r??null,barrier:o});function c(e){if(!a.has(e))throw new Error(`unknown barrier: ${e}`);return a.get(e)}return{async arrive(e,r){c(r),n("arrive",e,r)},release(e){const r=c(e);if(!r.released){r.released=!0,n("release",null,e);for(const o of r.waiters.splice(0))o()}},wait(e,r,o){const i=c(r);return n("wait",e,r),i.released?(n("continue",e,r),Promise.resolve()):new Promise((l,f)=>{const w=setTimeout(()=>f(new Error(`barrier timeout: ${r}`)),o);i.waiters.push(()=>{clearTimeout(w),n("continue",e,r),l()})})},events:()=>s.map(e=>({...e}))}}function m(t){return!Array.isArray(t?.actors)||t.actors.length<2||new Set(t.actors).size!==t.actors.length?{ok:!1,reason:"concurrency requires distinct actors"}:Array.isArray(t.schedule)?!t.accept||typeof t.accept.kind!="string"?{ok:!1,reason:"correctness case requires an accepted outcome"}:["conflict","merge","last-writer-wins","observe-only"].includes(t.accept.kind)?{ok:!0}:{ok:!1,reason:"unsupported accepted outcome"}:{ok:!1,reason:"concurrency requires a schedule"}}export{d as createBarrierController,m as validateConcurrencySpec};
|