@ornexus/neocortex 4.60.61 → 4.60.62
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/dist/release-source.json +1 -0
- package/dist/sbom.cdx.json +150 -47
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/package.json +9 -4
- package/packages/client/dist/cli.js +52 -2
- package/packages/client/dist/commands/invoke.d.ts +1 -0
- package/packages/client/dist/commands/invoke.js +80 -73
- package/packages/client/dist/commands/legacy-epic-terminal.d.ts +9 -0
- package/packages/client/dist/commands/legacy-epic-terminal.js +1 -0
- package/packages/client/dist/commands/legacy-story-pr.d.ts +2 -1
- package/packages/client/dist/commands/legacy-story-pr.js +1 -1
- package/packages/client/dist/commands/mutation-qa-checkpoint.d.ts +39 -0
- package/packages/client/dist/commands/mutation-qa-checkpoint.js +5 -0
- package/packages/client/dist/commands/mutation-qa-gate.d.ts +35 -0
- package/packages/client/dist/commands/mutation-qa-gate.js +1 -0
- package/packages/client/dist/commands/mutation-qa-git.d.ts +2 -0
- package/packages/client/dist/commands/mutation-qa-git.js +1 -0
- package/packages/client/dist/commands/mutation-qa-policy.d.ts +15 -0
- package/packages/client/dist/commands/mutation-qa-policy.js +1 -0
- package/packages/client/dist/commands/mutation-qa-run.d.ts +14 -0
- package/packages/client/dist/commands/mutation-qa-run.js +3 -0
- package/packages/client/dist/commands/mutation-qa-verdict.d.ts +21 -0
- package/packages/client/dist/commands/mutation-qa-verdict.js +1 -0
- package/packages/client/dist/commands/mutation-qa.d.ts +22 -0
- package/packages/client/dist/commands/mutation-qa.js +1 -0
- package/packages/client/dist/commands/property-qa.d.ts +1 -0
- package/packages/client/dist/commands/property-qa.js +1 -0
- package/packages/client/dist/commands/qa-scan-capture.d.ts +52 -0
- package/packages/client/dist/commands/qa-scan-capture.js +11 -0
- package/packages/client/dist/commands/qa-scan-discovery.d.ts +36 -0
- package/packages/client/dist/commands/qa-scan-discovery.js +3 -0
- package/packages/client/dist/commands/qa-scan-review.d.ts +119 -0
- package/packages/client/dist/commands/qa-scan-review.js +5 -0
- package/packages/client/dist/commands/refactor-characterization.d.ts +47 -0
- package/packages/client/dist/commands/refactor-characterization.js +12 -0
- package/packages/client/dist/commands/refactor-commit.d.ts +45 -0
- package/packages/client/dist/commands/refactor-commit.js +13 -0
- package/packages/client/dist/commands/refactor-review.d.ts +37 -0
- package/packages/client/dist/commands/refactor-review.js +5 -0
- package/packages/client/dist/commands/refactor-root.d.ts +85 -0
- package/packages/client/dist/commands/refactor-root.js +7 -0
- package/packages/client/dist/generated/command-registry.generated.d.ts +9 -9
- package/packages/client/dist/generated/command-registry.generated.js +1 -1
- package/packages/client/dist/generated/public-projections.generated.d.ts +20 -20
- package/packages/client/dist/generated/public-projections.generated.js +1 -1
- package/packages/client/dist/git/legacy-story-pr.d.ts +137 -6
- package/packages/client/dist/git/legacy-story-pr.js +51 -43
- package/packages/client/dist/license/license-client.js +1 -1
- package/packages/client/dist/private-execution/delivery.d.ts +49 -4
- package/packages/client/dist/private-execution/delivery.js +4 -2
- package/packages/client/dist/private-execution/isolated-admission.d.ts +7 -0
- package/packages/client/dist/private-execution/isolated-admission.js +10 -10
- package/packages/client/dist/private-execution/private-report-store.d.ts +17 -0
- package/packages/client/dist/private-execution/private-report-store.js +1 -0
- package/packages/client/dist/private-execution/property-test-candidate.d.ts +14 -0
- package/packages/client/dist/private-execution/property-test-candidate.js +1 -0
- package/packages/client/dist/private-execution/property-test-choices.d.ts +15 -0
- package/packages/client/dist/private-execution/property-test-choices.js +3 -0
- package/packages/client/dist/private-execution/property-test-plan.d.ts +65 -0
- package/packages/client/dist/private-execution/property-test-plan.js +7 -0
- package/packages/client/dist/private-execution/property-test-review.d.ts +58 -0
- package/packages/client/dist/private-execution/property-test-review.js +4 -0
- package/packages/client/dist/private-execution/property-test-runtime.d.ts +18 -0
- package/packages/client/dist/private-execution/property-test-runtime.js +9 -0
- package/packages/client/dist/private-execution/property-test-source.d.ts +8 -0
- package/packages/client/dist/private-execution/property-test-source.js +1 -0
- package/packages/client/dist/state/project-state-snapshot.js +1 -1
- package/packages/shared/dist/generated/command-registry.generated.js +9 -9
- package/packages/shared/dist/generated/invocation-schema.generated.js +2 -2
- package/packages/shared/dist/generated/public-projections.generated.js +18 -18
- package/packages/shared/dist/invocation/index.d.ts +1 -1
- package/packages/shared/dist/invocation/index.js +13 -0
- package/packages/shared/dist/merge-readiness/c12-registry-projection.js +111 -0
- package/packages/shared/dist/merge-readiness/c12-remote-observation.js +60 -0
- package/targets-stubs/antigravity/gemini.md +1 -1
- package/targets-stubs/antigravity/skill/SKILL.md +1 -1
- package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex-root.md +2 -2
- package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex.md +2 -2
- package/targets-stubs/codex/AGENTS.md +1 -1
- package/targets-stubs/cursor/agent.md +2 -2
- package/targets-stubs/gemini-cli/agent.md +2 -2
- package/targets-stubs/opencode/neocortex-root.md +1 -1
- package/targets-stubs/vscode/neocortex.agent.md +2 -2
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface LegacyEpicTerminalCliOptions {
|
|
2
|
+
readonly action: 'plan' | 'accept' | 'status';
|
|
3
|
+
readonly epicId: string;
|
|
4
|
+
readonly projectRoot: string;
|
|
5
|
+
readonly format: 'json' | 'plain';
|
|
6
|
+
readonly authorize: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseLegacyEpicTerminalArgs(args: readonly string[]): LegacyEpicTerminalCliOptions;
|
|
9
|
+
export declare function legacyEpicTerminalCliHandler(args: readonly string[]): Promise<number>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{resolve as g}from"node:path";import{acceptLegacyEpicFinalization as u,planLegacyEpicFinalization as h,readLegacyEpicTerminalAcceptance as y}from"../git/legacy-story-pr.js";function w(i){const e=i[0];if(e!=="plan"&&e!=="accept"&&e!=="status")throw new Error("legacy-epic-terminal-action-invalid");let r="",a=process.cwd(),s="plain",o=!1,p=!1,f=!1,m=!1;for(let n=1;n<i.length;n++){const t=i[n],c=()=>i[++n]??"";if(t==="--epic"||t.startsWith("--epic=")){if(p)throw new Error("legacy-epic-terminal-argument-invalid");p=!0,r=t==="--epic"?c():t.slice(7)}else if(t==="--project-root"||t.startsWith("--project-root=")){if(f)throw new Error("legacy-epic-terminal-argument-invalid");f=!0,a=t==="--project-root"?c():t.slice(15)}else if(t==="--format"||t.startsWith("--format=")){if(m)throw new Error("legacy-epic-terminal-argument-invalid");m=!0;const l=t==="--format"?c():t.slice(9);if(l!=="json"&&l!=="plain")throw new Error("legacy-epic-terminal-format-invalid");s=l}else if(t==="--authorize-root-qa"&&!o)o=!0;else throw new Error("legacy-epic-terminal-argument-invalid")}if(!/^P[0-9]+$/.test(r)||!a)throw new Error("legacy-epic-terminal-target-invalid");if(e==="accept"!==o)throw new Error("legacy-epic-terminal-authorization-invalid");return{action:e,epicId:r,projectRoot:g(a),format:s,authorize:o}}async function v(i){let e;try{e=w(i)}catch(a){return console.error(a instanceof Error?a.message:"legacy-epic-terminal-arguments-invalid"),1}const r=e.action==="plan"?await h(e):e.action==="status"?await y(e):await u(e);return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-epic-terminal ${e.action}: physical-stories=${r.value.storyCount} quota-release=unauthorized epic=${r.value.epicId}`):console.error(`legacy-epic-terminal: ${r.reasonCode}`),r.ok?0:1}export{v as legacyEpicTerminalCliHandler,w as parseLegacyEpicTerminalArgs};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface LegacyStoryPrCliOptions {
|
|
2
|
-
readonly action: 'commit-plan' | 'commit-accept' | 'sync-plan' | 'sync-accept' | 'plan' | 'create' | 'accept' | 'review-plan' | 'review-accept' | 'merge-plan' | 'merge-prepare' | 'merge-decision' | 'merge-debt-record' | 'merge-execute' | 'merge-reconcile' | 'post-merge-plan' | 'post-merge-accept';
|
|
2
|
+
readonly action: 'commit-plan' | 'commit-accept' | 'sync-plan' | 'sync-accept' | 'plan' | 'create' | 'accept' | 'review-plan' | 'review-observe' | 'review-accept' | 'merge-plan' | 'merge-prepare' | 'merge-debt-plan' | 'merge-decision' | 'merge-debt-record' | 'merge-execute' | 'merge-reconcile' | 'post-merge-plan' | 'post-merge-accept';
|
|
3
3
|
readonly storyId: string;
|
|
4
4
|
readonly projectRoot: string;
|
|
5
5
|
readonly bodyFile?: string;
|
|
@@ -8,6 +8,7 @@ export interface LegacyStoryPrCliOptions {
|
|
|
8
8
|
readonly authorize: boolean;
|
|
9
9
|
readonly authorizeReview: boolean;
|
|
10
10
|
readonly authorizePrUpdate: boolean;
|
|
11
|
+
readonly recordRemoteDebt: boolean;
|
|
11
12
|
readonly authorizeMerge: boolean;
|
|
12
13
|
readonly authorizePostMerge: boolean;
|
|
13
14
|
readonly authorizeSync: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{resolve as w}from"node:path";import{acceptLegacyStoryPrC10 as S,acceptLegacyStoryPrPostMergeC12 as $,acceptLegacyStoryPrReviewC11 as k,acceptLegacyStoryCommitC08 as C,acceptLegacyStorySyncC09 as z,createLegacyStoryPr as E,mergeLegacyStoryPrC12 as b,planLegacyStoryCommitC08 as P,planLegacyStoryPr as j,planLegacyStoryPrMergeC12 as L,planLegacyStoryPrMergeDecisionC12 as N,planLegacyStoryPrReview as M,planLegacyStoryPrPostMergeC12 as I,planLegacyStorySyncC09 as J,prepareLegacyStoryPrMergeC12 as O,recordLegacyStoryPrRemoteDebtC12 as x}from"../git/legacy-story-pr.js";function F(y){const e=y[0];if(e!=="commit-plan"&&e!=="commit-accept"&&e!=="sync-plan"&&e!=="sync-accept"&&e!=="plan"&&e!=="create"&&e!=="accept"&&e!=="review-plan"&&e!=="review-accept"&&e!=="merge-plan"&&e!=="merge-prepare"&&e!=="merge-decision"&&e!=="merge-debt-record"&&e!=="merge-execute"&&e!=="merge-reconcile"&&e!=="post-merge-plan"&&e!=="post-merge-accept")throw new Error("legacy-story-pr-action-invalid");let c="",r=process.cwd(),o="",a="",i=0,n=!1,s=!1,l=!1,p=!1,m=!1,f=!1,d=!1,v="plain";for(let h=1;h<y.length;h++){const t=y[h],g=()=>y[++h]??"";if(t==="--story")c=g();else if(t.startsWith("--story="))c=t.slice(8);else if(t==="--project-root")r=g();else if(t.startsWith("--project-root="))r=t.slice(15);else if(t==="--body-file")o=g();else if(t.startsWith("--body-file="))o=t.slice(12);else if(t==="--report-file")a=g();else if(t.startsWith("--report-file="))a=t.slice(14);else if(t==="--pr-number"||t.startsWith("--pr-number=")){const u=t==="--pr-number"?g():t.slice(12);if(!/^[1-9][0-9]*$/.test(u))throw new Error("legacy-story-pr-number-invalid");if(i=Number(u),!Number.isSafeInteger(i))throw new Error("legacy-story-pr-number-invalid")}else if(t==="--authorize-pr-create")n=!0;else if(t==="--authorize-review")s=!0;else if(t==="--authorize-pr-update")l=!0;else if(t==="--authorize-merge")p=!0;else if(t==="--authorize-post-merge")m=!0;else if(t==="--authorize-sync")f=!0;else if(t==="--authorize-commit")d=!0;else if(t==="--format"||t.startsWith("--format=")){const u=t==="--format"?g():t.slice(9);if(u!=="json"&&u!=="plain")throw new Error("legacy-story-pr-format-invalid");v=u}else throw new Error("legacy-story-pr-argument-invalid")}if(!c||!r)throw new Error("legacy-story-pr-target-required");if(e!=="post-merge-accept"&&m)throw new Error("legacy-story-pr-argument-invalid");if(e!=="sync-accept"&&f)throw new Error("legacy-story-pr-argument-invalid");if(e!=="commit-accept"&&d)throw new Error("legacy-story-pr-argument-invalid");if(e==="commit-plan"&&(n||s||l||p||o||a||i))throw new Error("legacy-story-pr-commit-plan-options-invalid");if(e==="commit-accept"&&(n||s||l||p||!d||o||a||i))throw new Error("legacy-story-pr-commit-accept-options-invalid");if(e==="sync-plan"&&(n||s||l||p||o||a||i))throw new Error("legacy-story-pr-sync-plan-options-invalid");if(e==="sync-accept"&&(n||s||l||p||!f||o||a||i))throw new Error("legacy-story-pr-sync-accept-options-invalid");if(e==="plan"&&(n||s||l||p||o||a||i))throw new Error("legacy-story-pr-plan-options-invalid");if(e==="create"&&(!n||s||l||p||!o||a||i))throw new Error("legacy-story-pr-create-options-invalid");if(e==="accept"&&(n||s||l||p||o||a||!i))throw new Error("legacy-story-pr-accept-options-invalid");if(e==="review-plan"&&(n||s||l||p||o||a||!i))throw new Error("legacy-story-pr-review-plan-options-invalid");if(e==="review-accept"&&(n||!s||l||p||o||!a||!i))throw new Error("legacy-story-pr-review-accept-options-invalid");if(e==="merge-plan"&&(n||s||l||p||o||a||!i))throw new Error("legacy-story-pr-merge-plan-options-invalid");if(e==="merge-prepare"&&(n||s||!l||p||o||a||!i))throw new Error("legacy-story-pr-merge-prepare-options-invalid");if(e==="merge-decision"&&(n||s||l||p||o||a||!i))throw new Error("legacy-story-pr-merge-decision-options-invalid");if(e==="merge-debt-record"&&(n||s||l||p||o||a||!i))throw new Error("legacy-story-pr-merge-debt-record-options-invalid");if(e==="merge-execute"&&(n||s||l||!p||o||a||!i))throw new Error("legacy-story-pr-merge-execute-options-invalid");if(e==="merge-reconcile"&&(n||s||l||p||o||a||!i))throw new Error("legacy-story-pr-merge-reconcile-options-invalid");if(e==="post-merge-plan"&&(n||s||l||p||o||a||!i))throw new Error("legacy-story-pr-post-merge-plan-options-invalid");if(e==="post-merge-accept"&&(n||s||l||p||!m||o||a||!i))throw new Error("legacy-story-pr-post-merge-accept-options-invalid");return{action:e,storyId:c,projectRoot:w(r),authorize:n,authorizeReview:s,authorizePrUpdate:l,authorizeMerge:p,authorizePostMerge:m,authorizeSync:f,authorizeCommit:d,format:v,...o?{bodyFile:w(o)}:{},...a?{reportFile:w(a)}:{},...i?{prNumber:i}:{}}}async function q(y){let e;try{e=F(y)}catch(r){return console.error(r instanceof Error?r.message:"legacy-story-pr-arguments-invalid"),1}if(e.action==="commit-plan"){const r=P(e);return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr commit-plan: staged-basis-confirmed checkpoint=unaccepted story=${r.value.storyId} paths=${r.value.changedPaths.length}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}if(e.action==="commit-accept"){const r=await C({...e,authorize:e.authorizeCommit});return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr commit-accept: checkpoint=accepted story=${r.value.storyId} head=${r.value.checkpointHeadSha}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}if(e.action==="sync-plan"){const r=J(e);return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr sync-plan: physical-basis-confirmed checkpoint=unaccepted story=${r.value.storyId} base=${r.value.baseSha}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}if(e.action==="sync-accept"){const r=await z({...e,authorize:e.authorizeSync});return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr sync-accept: checkpoint=accepted story=${r.value.storyId} head=${r.value.checkpointHeadSha}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}if(e.action==="merge-execute"){const r=await N({...e,prNumber:e.prNumber});if(!r.ok||!r.value.canMerge){const a=r.ok?"legacy-story-pr-merge-remote-debt-required":r.reasonCode;return e.format==="json"?console.log(JSON.stringify({action:e.action,ok:!1,reasonCode:a})):console.error(`legacy-story-pr: ${a}`),1}const o=await b({...e,prNumber:e.prNumber,authorize:e.authorizeMerge,rootDecision:r.value});return e.format==="json"?console.log(JSON.stringify({action:e.action,...o})):o.ok?console.log(`legacy-story-pr merge-execute: physical-merge-confirmed checkpoint=unaccepted story=${o.value.storyId} pr=${o.value.prNumber}`):console.error(`legacy-story-pr: ${o.reasonCode}`),o.ok?0:1}if(e.action==="merge-reconcile"){const r=await b({...e,prNumber:e.prNumber,authorize:!0});return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr merge-reconcile: physical-merge-confirmed checkpoint=unaccepted story=${r.value.storyId} pr=${r.value.prNumber}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}if(e.action==="post-merge-plan"){const r=await I({...e,prNumber:e.prNumber});return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr post-merge-plan: physical-basis-confirmed checkpoint=unaccepted story=${r.value.storyId} pr=${r.value.prNumber}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}if(e.action==="post-merge-accept"){const r=await $({...e,prNumber:e.prNumber,authorize:e.authorizePostMerge});return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr post-merge-accept: checkpoint=accepted story=${r.value.storyId} pr=${r.value.prNumber} issue=${r.value.issueNumber}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}const c=e.action==="plan"?j(e):e.action==="create"?await E({...e,bodyFile:e.bodyFile}):e.action==="accept"?await S({...e,prNumber:e.prNumber}):e.action==="review-plan"?await M({...e,prNumber:e.prNumber}):e.action==="review-accept"?await k({...e,prNumber:e.prNumber,reportFile:e.reportFile,authorize:e.authorizeReview}):e.action==="merge-plan"?await L({...e,prNumber:e.prNumber}):e.action==="merge-decision"?await N({...e,prNumber:e.prNumber}):e.action==="merge-debt-record"?await x({...e,prNumber:e.prNumber}):await O({...e,prNumber:e.prNumber,authorize:e.authorizePrUpdate});if(e.format==="json")console.log(JSON.stringify({action:e.action,...c}));else if(c.ok){const r="canMerge"in c.value&&c.value.canMerge===!1;console.log(`legacy-story-pr ${e.action}: ${e.action==="merge-debt-record"?"journaled-not-registry":r?"debt-required":"ok"}`),console.log(`story=${c.value.storyId}${"branch"in c.value?` branch=${c.value.branch}`:""}`),r&&"reasonCodes"in c.value&&console.log(`reasons=${c.value.reasonCodes.join(",")}`),"number"in c.value&&console.log(`pr=${c.value.number} checkpoint=${c.value.checkpointAccepted?"accepted":"unaccepted"}`)}else console.error(`legacy-story-pr: ${c.reasonCode}`);return c.ok?0:1}export{q as legacyStoryPrCliHandler,F as parseLegacyStoryPrArgs};
|
|
1
|
+
import{resolve as v}from"node:path";import{acceptLegacyStoryPrC10 as S,acceptLegacyStoryPrPostMergeC12 as $,acceptLegacyStoryPrReviewC11 as C,acceptLegacyStoryCommitC08 as E,acceptLegacyStorySyncC09 as z,createLegacyStoryPr as P,mergeLegacyStoryPrC12 as N,planLegacyStoryCommitC08 as j,planLegacyStoryPr as L,planLegacyStoryPrMergeC12 as R,planLegacyStoryPrMergeDecisionC12 as k,planLegacyStoryPrReview as M,planLegacyStoryPrPostMergeC12 as I,planLegacyStoryPrRemoteDebtBeforePrepareC12 as J,observeLegacyStoryPrReviewC11 as O,planLegacyStorySyncC09 as x,prepareLegacyStoryPrMergeC12 as F,recordLegacyStoryPrRemoteDebtC12 as D}from"../git/legacy-story-pr.js";function W(y){const e=y[0];if(e!=="commit-plan"&&e!=="commit-accept"&&e!=="sync-plan"&&e!=="sync-accept"&&e!=="plan"&&e!=="create"&&e!=="accept"&&e!=="review-plan"&&e!=="review-observe"&&e!=="review-accept"&&e!=="merge-plan"&&e!=="merge-prepare"&&e!=="merge-debt-plan"&&e!=="merge-decision"&&e!=="merge-debt-record"&&e!=="merge-execute"&&e!=="merge-reconcile"&&e!=="post-merge-plan"&&e!=="post-merge-accept")throw new Error("legacy-story-pr-action-invalid");let i="",r=process.cwd(),o="",t="",c=0,n=!1,s=!1,l=!1,w=!1,p=!1,m=!1,f=!1,d=!1,b="plain";for(let h=1;h<y.length;h++){const a=y[h],g=()=>y[++h]??"";if(a==="--story")i=g();else if(a.startsWith("--story="))i=a.slice(8);else if(a==="--project-root")r=g();else if(a.startsWith("--project-root="))r=a.slice(15);else if(a==="--body-file")o=g();else if(a.startsWith("--body-file="))o=a.slice(12);else if(a==="--report-file")t=g();else if(a.startsWith("--report-file="))t=a.slice(14);else if(a==="--pr-number"||a.startsWith("--pr-number=")){const u=a==="--pr-number"?g():a.slice(12);if(!/^[1-9][0-9]*$/.test(u))throw new Error("legacy-story-pr-number-invalid");if(c=Number(u),!Number.isSafeInteger(c))throw new Error("legacy-story-pr-number-invalid")}else if(a==="--authorize-pr-create")n=!0;else if(a==="--authorize-review")s=!0;else if(a==="--authorize-pr-update")l=!0;else if(a==="--record-remote-debt")w=!0;else if(a==="--authorize-merge")p=!0;else if(a==="--authorize-post-merge")m=!0;else if(a==="--authorize-sync")f=!0;else if(a==="--authorize-commit")d=!0;else if(a==="--format"||a.startsWith("--format=")){const u=a==="--format"?g():a.slice(9);if(u!=="json"&&u!=="plain")throw new Error("legacy-story-pr-format-invalid");b=u}else throw new Error("legacy-story-pr-argument-invalid")}if(!i||!r)throw new Error("legacy-story-pr-target-required");if(e!=="post-merge-accept"&&m)throw new Error("legacy-story-pr-argument-invalid");if(e!=="sync-accept"&&f)throw new Error("legacy-story-pr-argument-invalid");if(e!=="commit-accept"&&d)throw new Error("legacy-story-pr-argument-invalid");if(e!=="merge-prepare"&&w)throw new Error("legacy-story-pr-argument-invalid");if(e==="commit-plan"&&(n||s||l||p||o||t||c))throw new Error("legacy-story-pr-commit-plan-options-invalid");if(e==="commit-accept"&&(n||s||l||p||!d||o||t||c))throw new Error("legacy-story-pr-commit-accept-options-invalid");if(e==="sync-plan"&&(n||s||l||p||o||t||c))throw new Error("legacy-story-pr-sync-plan-options-invalid");if(e==="sync-accept"&&(n||s||l||p||!f||o||t||c))throw new Error("legacy-story-pr-sync-accept-options-invalid");if(e==="plan"&&(n||s||l||p||o||t||c))throw new Error("legacy-story-pr-plan-options-invalid");if(e==="create"&&(!n||s||l||p||!o||t||c))throw new Error("legacy-story-pr-create-options-invalid");if(e==="accept"&&(n||s||l||p||o||t||!c))throw new Error("legacy-story-pr-accept-options-invalid");if(e==="review-plan"&&(n||s||l||p||o||t||!c))throw new Error("legacy-story-pr-review-plan-options-invalid");if(e==="review-observe"&&(n||s||l||p||o||t||!c))throw new Error("legacy-story-pr-review-observe-options-invalid");if(e==="review-accept"&&(n||!s||l||p||o||!t||!c))throw new Error("legacy-story-pr-review-accept-options-invalid");if(e==="merge-plan"&&(n||s||l||p||o||t||!c))throw new Error("legacy-story-pr-merge-plan-options-invalid");if(e==="merge-prepare"&&(n||s||!l||p||o||t||!c))throw new Error("legacy-story-pr-merge-prepare-options-invalid");if(e==="merge-debt-plan"&&(n||s||l||p||o||t||!c))throw new Error("legacy-story-pr-merge-debt-plan-options-invalid");if(e==="merge-decision"&&(n||s||l||p||o||t||!c))throw new Error("legacy-story-pr-merge-decision-options-invalid");if(e==="merge-debt-record"&&(n||s||l||p||o||t||!c))throw new Error("legacy-story-pr-merge-debt-record-options-invalid");if(e==="merge-execute"&&(n||s||l||!p||o||t||!c))throw new Error("legacy-story-pr-merge-execute-options-invalid");if(e==="merge-reconcile"&&(n||s||l||p||o||t||!c))throw new Error("legacy-story-pr-merge-reconcile-options-invalid");if(e==="post-merge-plan"&&(n||s||l||p||o||t||!c))throw new Error("legacy-story-pr-post-merge-plan-options-invalid");if(e==="post-merge-accept"&&(n||s||l||p||!m||o||t||!c))throw new Error("legacy-story-pr-post-merge-accept-options-invalid");return{action:e,storyId:i,projectRoot:v(r),authorize:n,authorizeReview:s,authorizePrUpdate:l,recordRemoteDebt:w,authorizeMerge:p,authorizePostMerge:m,authorizeSync:f,authorizeCommit:d,format:b,...o?{bodyFile:v(o)}:{},...t?{reportFile:v(t)}:{},...c?{prNumber:c}:{}}}async function H(y){let e;try{e=W(y)}catch(r){return console.error(r instanceof Error?r.message:"legacy-story-pr-arguments-invalid"),1}if(e.action==="commit-plan"){const r=j(e);return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr commit-plan: staged-basis-confirmed checkpoint=unaccepted story=${r.value.storyId} paths=${r.value.changedPaths.length}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}if(e.action==="commit-accept"){const r=await E({...e,authorize:e.authorizeCommit});return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr commit-accept: checkpoint=accepted story=${r.value.storyId} head=${r.value.checkpointHeadSha}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}if(e.action==="sync-plan"){const r=x(e);return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr sync-plan: physical-basis-confirmed checkpoint=unaccepted story=${r.value.storyId} base=${r.value.baseSha}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}if(e.action==="sync-accept"){const r=await z({...e,authorize:e.authorizeSync});return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr sync-accept: checkpoint=accepted story=${r.value.storyId} head=${r.value.checkpointHeadSha}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}if(e.action==="merge-execute"){const r=await k({...e,prNumber:e.prNumber});if(!r.ok||!r.value.canMerge){const t=r.ok?"legacy-story-pr-merge-remote-debt-required":r.reasonCode;return e.format==="json"?console.log(JSON.stringify({action:e.action,ok:!1,reasonCode:t})):console.error(`legacy-story-pr: ${t}`),1}const o=await N({...e,prNumber:e.prNumber,authorize:e.authorizeMerge,rootDecision:r.value});return e.format==="json"?console.log(JSON.stringify({action:e.action,...o})):o.ok?console.log(`legacy-story-pr merge-execute: physical-merge-confirmed checkpoint=unaccepted story=${o.value.storyId} pr=${o.value.prNumber}`):console.error(`legacy-story-pr: ${o.reasonCode}`),o.ok?0:1}if(e.action==="merge-reconcile"){const r=await N({...e,prNumber:e.prNumber,authorize:!0});return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr merge-reconcile: physical-merge-confirmed checkpoint=unaccepted story=${r.value.storyId} pr=${r.value.prNumber}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}if(e.action==="post-merge-plan"){const r=await I({...e,prNumber:e.prNumber});return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr post-merge-plan: physical-basis-confirmed checkpoint=unaccepted story=${r.value.storyId} pr=${r.value.prNumber}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}if(e.action==="post-merge-accept"){const r=await $({...e,prNumber:e.prNumber,authorize:e.authorizePostMerge});return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr post-merge-accept: checkpoint=accepted story=${r.value.storyId} pr=${r.value.prNumber} issue=${r.value.issueNumber}`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}if(e.action==="review-observe"){const r=await O({...e,prNumber:e.prNumber});return e.format==="json"?console.log(JSON.stringify({action:e.action,...r})):r.ok?console.log(`legacy-story-pr review-observe: categorical-reasons=${r.value.observation.reasonCodes.join(",")||"none"} checkpoint=unaccepted merge=unauthorized`):console.error(`legacy-story-pr: ${r.reasonCode}`),r.ok?0:1}const i=e.action==="plan"?L(e):e.action==="create"?await P({...e,bodyFile:e.bodyFile}):e.action==="accept"?await S({...e,prNumber:e.prNumber}):e.action==="review-plan"?await M({...e,prNumber:e.prNumber}):e.action==="review-accept"?await C({...e,prNumber:e.prNumber,reportFile:e.reportFile,authorize:e.authorizeReview}):e.action==="merge-plan"?await R({...e,prNumber:e.prNumber}):e.action==="merge-debt-plan"?await J({...e,prNumber:e.prNumber}):e.action==="merge-decision"?await k({...e,prNumber:e.prNumber}):e.action==="merge-debt-record"?await D({...e,prNumber:e.prNumber}):await F({...e,prNumber:e.prNumber,authorize:e.authorizePrUpdate});if(e.format==="json")console.log(JSON.stringify({action:e.action,...i}));else if(i.ok){const r="canMerge"in i.value&&i.value.canMerge===!1,o="registrySha256"in i.value;console.log(`legacy-story-pr ${e.action}: ${e.action==="merge-debt-record"?"journaled-not-registry":r?"debt-required":o?"recorded-debt-merge-attempt-authorized":"ok"}`),console.log(`story=${i.value.storyId}${"branch"in i.value?` branch=${i.value.branch}`:""}`),r&&"reasonCodes"in i.value&&console.log(`reasons=${i.value.reasonCodes.join(",")}`),o&&"nonBlockingChecks"in i.value&&console.log(`nonBlockingReasons=${i.value.nonBlockingChecks.map(t=>t.checkName).join(",")}`),"registryRecorded"in i.value&&console.log(`registryRecorded=${i.value.registryRecorded}`),"number"in i.value&&console.log(`pr=${i.value.number} checkpoint=${i.value.checkpointAccepted?"accepted":"unaccepted"}`)}else console.error(`legacy-story-pr: ${i.reasonCode}`);return i.ok?0:1}export{H as legacyStoryPrCliHandler,W as parseLegacyStoryPrArgs};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type MutationQaRunV1 } from './mutation-qa-run.js';
|
|
2
|
+
import type { MutationQaVerdictV1 } from './mutation-qa-verdict.js';
|
|
3
|
+
export interface MutationQaCheckpointV1 {
|
|
4
|
+
readonly schemaVersion: 1;
|
|
5
|
+
readonly kind: 'mutation-qa-checkpoint';
|
|
6
|
+
readonly planSha256: string;
|
|
7
|
+
readonly headSha: string;
|
|
8
|
+
readonly baseSha: string;
|
|
9
|
+
readonly reportSha256: string;
|
|
10
|
+
readonly verdict: MutationQaVerdictV1['outcome'];
|
|
11
|
+
readonly mutationScore: number;
|
|
12
|
+
readonly checkpointAccepted: true;
|
|
13
|
+
readonly qaPassed: boolean;
|
|
14
|
+
readonly localOnly: true;
|
|
15
|
+
readonly checksumSha256: string;
|
|
16
|
+
}
|
|
17
|
+
export interface MutationQaFollowUpDebtV1 {
|
|
18
|
+
readonly schemaVersion: 1;
|
|
19
|
+
readonly kind: 'mutation-qa-follow-up-debt';
|
|
20
|
+
readonly planSha256: string;
|
|
21
|
+
readonly headSha: string;
|
|
22
|
+
readonly status: 'warned' | 'unobserved';
|
|
23
|
+
readonly reasonCode: 'mutation-qa-warned' | 'mutation-qa-observation-missing';
|
|
24
|
+
readonly runOutcome: MutationQaRunV1['outcome'] | 'not-run';
|
|
25
|
+
readonly owner: 'root-qa';
|
|
26
|
+
readonly severity: 'P2' | 'P3';
|
|
27
|
+
readonly publicSafe: true;
|
|
28
|
+
readonly localOnly: true;
|
|
29
|
+
readonly checksumSha256: string;
|
|
30
|
+
}
|
|
31
|
+
/** Persist optional-QA debt separately from the verdict before allowing root continuation. */
|
|
32
|
+
export declare function recordMutationQaFollowUpDebt(projectRoot: string, baseRef: string, planSha256: string, status: 'warned' | 'unobserved', runOutcome: MutationQaRunV1['outcome'] | 'not-run', durableRoot?: string): MutationQaFollowUpDebtV1;
|
|
33
|
+
/** An authorized root run is the only producer; skips never create a checkpoint. */
|
|
34
|
+
export declare function runAndRecordMutationQa(projectRoot: string, baseRef: string, planSha256: string): {
|
|
35
|
+
readonly result: MutationQaRunV1;
|
|
36
|
+
readonly checkpoint?: MutationQaCheckpointV1;
|
|
37
|
+
};
|
|
38
|
+
/** Revalidate the exact physical plan before projecting a private checkpoint. */
|
|
39
|
+
export declare function readMutationQaCheckpoint(projectRoot: string, baseRef: string, planSha256: string): MutationQaCheckpointV1 | undefined;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{createHash as b,randomUUID as J}from"node:crypto";import{closeSync as S,fsyncSync as w,lstatSync as h,mkdirSync as D,openSync as k,readFileSync as x,realpathSync as A,linkSync as I,unlinkSync as j,writeFileSync as P}from"node:fs";import{isAbsolute as F,join as y,resolve as q}from"node:path";import{mutationQaGit as g}from"./mutation-qa-git.js";import{planMutationQa as v}from"./mutation-qa.js";import{runMutationQa as H}from"./mutation-qa-run.js";function E(n,o){const e=q(n),r=g(e,["rev-parse","--show-toplevel"]).toString("utf8").trim(),t=g(e,["rev-parse","--git-common-dir"]).toString("utf8").trim(),a=F(t)?q(t):q(e,t);if(A(e)!==e||r!==e||A(a)!==a||h(a).isSymbolicLink()||h(a).uid!==process.getuid?.())throw new Error("mutation-qa-checkpoint-store-invalid");const i=y(a,"neocortex-mutation-qa");let c=!1;if(o&&!h(i,{throwIfNoEntry:!1}))try{D(i,{mode:448}),c=!0}catch(s){if(s.code!=="EEXIST")throw s}const d=h(i,{throwIfNoEntry:!1});if(d&&(!d.isDirectory()||d.isSymbolicLink()||d.uid!==process.getuid?.()||(d.mode&63)!==0))throw new Error("mutation-qa-checkpoint-store-invalid");if(c){const s=k(a,"r");try{w(s)}finally{S(s)}}return{root:e,directory:i}}function O(n){const o=h(n);if(!o.isFile()||o.isSymbolicLink()||o.uid!==process.getuid?.()||(o.mode&63)!==0||o.size<1||o.size>4096)throw new Error("mutation-qa-checkpoint-record-invalid");const e=x(n,"utf8");let r;try{r=JSON.parse(e)}catch{throw new Error("mutation-qa-checkpoint-record-invalid")}if(!r||typeof r!="object"||Array.isArray(r))throw new Error("mutation-qa-checkpoint-record-invalid");const t=r,{checksumSha256:a,...i}=t,c=b("sha256").update(`mutation-qa-checkpoint-v1\0${JSON.stringify(i)}`).digest("hex");if(Object.keys(t).sort().join(",")!==["schemaVersion","kind","planSha256","headSha","baseSha","reportSha256","verdict","mutationScore","checkpointAccepted","qaPassed","localOnly","checksumSha256"].sort().join(",")||t.schemaVersion!==1||t.kind!=="mutation-qa-checkpoint"||typeof t.planSha256!="string"||!/^[a-f0-9]{64}$/.test(t.planSha256)||typeof t.headSha!="string"||!/^[a-f0-9]{40,64}$/.test(t.headSha)||typeof t.baseSha!="string"||!/^[a-f0-9]{40,64}$/.test(t.baseSha)||typeof t.reportSha256!="string"||!/^[a-f0-9]{64}$/.test(t.reportSha256)||!["passed","warned","failed"].includes(String(t.verdict))||typeof t.mutationScore!="number"||!Number.isFinite(t.mutationScore)||t.mutationScore<0||t.mutationScore>100||t.checkpointAccepted!==!0||t.qaPassed!==(t.verdict==="passed")||t.localOnly!==!0||a!==c||e!==`${JSON.stringify(t)}
|
|
2
|
+
`)throw new Error("mutation-qa-checkpoint-record-invalid");return t}function Q(n){const o=h(n);if(!o.isFile()||o.isSymbolicLink()||o.uid!==process.getuid?.()||(o.mode&63)!==0||o.size<1||o.size>4096)throw new Error("mutation-qa-debt-record-invalid");const e=x(n,"utf8");let r;try{r=JSON.parse(e)}catch{throw new Error("mutation-qa-debt-record-invalid")}if(!r||typeof r!="object"||Array.isArray(r))throw new Error("mutation-qa-debt-record-invalid");const t=r,{checksumSha256:a,...i}=t,c=b("sha256").update(`mutation-qa-debt-v1\0${JSON.stringify(i)}`).digest("hex");if(Object.keys(t).sort().join(",")!==["schemaVersion","kind","planSha256","headSha","status","reasonCode","runOutcome","owner","severity","publicSafe","localOnly","checksumSha256"].sort().join(",")||t.schemaVersion!==1||t.kind!=="mutation-qa-follow-up-debt"||typeof t.planSha256!="string"||!/^[a-f0-9]{64}$/.test(t.planSha256)||typeof t.headSha!="string"||!/^[a-f0-9]{40,64}$/.test(t.headSha)||!["warned","unobserved"].includes(String(t.status))||t.reasonCode!==(t.status==="warned"?"mutation-qa-warned":"mutation-qa-observation-missing")||!["not-run","skipped-no-files","skipped-stryker-missing","skipped-dependencies-unavailable","skipped-host-isolation-unavailable","skipped-stryker-error","verified"].includes(String(t.runOutcome))||t.owner!=="root-qa"||t.severity!==(t.status==="warned"?"P2":"P3")||t.publicSafe!==!0||t.localOnly!==!0||a!==c||e!==`${JSON.stringify(t)}
|
|
3
|
+
`)throw new Error("mutation-qa-debt-record-invalid");return t}function B(n,o,e,r,t,a){const i=v(n,o);if(i.planSha256!==e)throw new Error("mutation-qa-debt-plan-drift");const c=V(n,o,e);if(r==="warned"&&(t!=="verified"||c?.verdict!=="warned"))throw new Error("mutation-qa-debt-run-invalid");if(r==="unobserved"&&(i.outcome!=="ready"||c||!["skipped-stryker-missing","skipped-dependencies-unavailable","skipped-stryker-error"].includes(t)))throw new Error("mutation-qa-debt-run-invalid");const{root:d,directory:s}=E(a??n,!0);if(a&&g(d,["rev-parse","--verify","HEAD"]).toString("utf8").trim()!==i.baseSha)throw new Error("mutation-qa-debt-root-drift");const l={schemaVersion:1,kind:"mutation-qa-follow-up-debt",planSha256:e,headSha:i.headSha,status:r,reasonCode:r==="warned"?"mutation-qa-warned":"mutation-qa-observation-missing",runOutcome:t,owner:"root-qa",severity:r==="warned"?"P2":"P3",publicSafe:!0,localOnly:!0},f=Object.freeze({...l,checksumSha256:b("sha256").update(`mutation-qa-debt-v1\0${JSON.stringify(l)}`).digest("hex")}),p=y(s,`debt-${e}.json`);if(h(p,{throwIfNoEntry:!1})){const m=Q(p);if(JSON.stringify(m)!==JSON.stringify(f))throw new Error("mutation-qa-debt-conflict");return m}const u=y(s,`debt-${J()}.tmp`);try{const m=k(u,"wx",384);try{P(m,`${JSON.stringify(f)}
|
|
4
|
+
`),w(m)}finally{S(m)}if(v(n,o).planSha256!==e||a&&g(d,["rev-parse","--verify","HEAD"]).toString("utf8").trim()!==i.baseSha)throw new Error("mutation-qa-debt-plan-drift");try{I(u,p)}catch($){if($.code!=="EEXIST")throw $;const z=Q(p);if(JSON.stringify(z)!==JSON.stringify(f))throw new Error("mutation-qa-debt-conflict")}const N=k(s,"r");try{w(N)}finally{S(N)}}finally{h(u,{throwIfNoEntry:!1})&&j(u)}return f}function M(n,o,e){if(e.outcome!=="verified"||!e.verdict||e.verdict.planSha256!==e.planSha256||e.verdict.checkpointAccepted!==!1)throw new Error("mutation-qa-checkpoint-verdict-unavailable");const r=v(n,o);if(r.outcome!=="ready"||r.planSha256!==e.planSha256||r.headSha!==e.headSha)throw new Error("mutation-qa-checkpoint-plan-drift");const{root:t,directory:a}=E(n,!0),i={schemaVersion:1,kind:"mutation-qa-checkpoint",planSha256:r.planSha256,headSha:r.headSha,baseSha:r.baseSha,reportSha256:e.verdict.reportSha256,verdict:e.verdict.outcome,mutationScore:e.verdict.mutationScore,checkpointAccepted:!0,qaPassed:e.verdict.outcome==="passed",localOnly:!0},c=Object.freeze({...i,checksumSha256:b("sha256").update(`mutation-qa-checkpoint-v1\0${JSON.stringify(i)}`).digest("hex")}),d=y(a,`${r.planSha256}.json`);if(h(d,{throwIfNoEntry:!1})){const l=O(d);if(JSON.stringify(l)!==JSON.stringify(c))throw new Error("mutation-qa-checkpoint-conflict");return l}const s=y(a,`checkpoint-${J()}.tmp`);try{const l=k(s,"wx",384);try{P(l,`${JSON.stringify(c)}
|
|
5
|
+
`),w(l)}finally{S(l)}if(v(t,o).planSha256!==r.planSha256)throw new Error("mutation-qa-checkpoint-plan-drift");try{I(s,d)}catch(p){if(p.code!=="EEXIST")throw p;const u=O(d);if(JSON.stringify(u)!==JSON.stringify(c))throw new Error("mutation-qa-checkpoint-conflict")}const f=k(a,"r");try{w(f)}finally{S(f)}}finally{h(s,{throwIfNoEntry:!1})&&j(s)}return c}function K(n,o,e){const r=H(n,o,e);return r.outcome==="verified"?{result:r,checkpoint:M(n,o,r)}:{result:r}}function V(n,o,e){if(!/^[a-f0-9]{64}$/.test(e))throw new Error("mutation-qa-plan-digest-invalid");const r=v(n,o);if(r.planSha256!==e)throw new Error("mutation-qa-checkpoint-plan-drift");const{directory:t}=E(n,!1),a=y(t,`${e}.json`);if(!h(a,{throwIfNoEntry:!1}))return;const i=O(a);if(i.planSha256!==e||i.headSha!==r.headSha||i.baseSha!==r.baseSha)throw new Error("mutation-qa-checkpoint-plan-drift");return i}export{V as readMutationQaCheckpoint,B as recordMutationQaFollowUpDebt,K as runAndRecordMutationQa};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Root-only classification of a physically bound mutation observation. */
|
|
2
|
+
import { type MutationQaCheckpointV1, type MutationQaFollowUpDebtV1 } from './mutation-qa-checkpoint.js';
|
|
3
|
+
import type { MutationQaRunV1 } from './mutation-qa-run.js';
|
|
4
|
+
export interface MutationQaGateDecisionV1 {
|
|
5
|
+
readonly schemaVersion: 1;
|
|
6
|
+
readonly kind: 'mutation-qa-gate-decision';
|
|
7
|
+
readonly planSha256: string;
|
|
8
|
+
readonly headSha: string;
|
|
9
|
+
readonly status: 'passed' | 'warned' | 'failed' | 'unobserved' | 'not-applicable';
|
|
10
|
+
readonly reasonCode: 'mutation-qa-passed' | 'mutation-qa-warned' | 'mutation-qa-failed' | 'mutation-qa-observation-missing' | 'mutation-qa-no-eligible-files';
|
|
11
|
+
readonly checkpointAccepted: boolean;
|
|
12
|
+
readonly qaPassed: boolean;
|
|
13
|
+
readonly blocksLocalQa: boolean;
|
|
14
|
+
readonly followUpDebtRequired: boolean;
|
|
15
|
+
readonly safeToContinueLocalQa: boolean;
|
|
16
|
+
readonly publicSafe: true;
|
|
17
|
+
readonly localOnly: true;
|
|
18
|
+
}
|
|
19
|
+
/** A skip is never a passing verdict; the caller must persist required debt separately. */
|
|
20
|
+
export declare function classifyMutationQaGate(projectRoot: string, baseRef: string, expectedPlanSha256: string): MutationQaGateDecisionV1;
|
|
21
|
+
export interface MutationQaExecutionV1 {
|
|
22
|
+
readonly schemaVersion: 1;
|
|
23
|
+
readonly kind: 'mutation-qa-root-execution';
|
|
24
|
+
readonly planSha256: string;
|
|
25
|
+
readonly headSha: string;
|
|
26
|
+
readonly runOutcome: MutationQaRunV1['outcome'] | 'not-run';
|
|
27
|
+
readonly decision: MutationQaGateDecisionV1;
|
|
28
|
+
readonly checkpoint?: MutationQaCheckpointV1;
|
|
29
|
+
readonly followUpDebt?: MutationQaFollowUpDebtV1;
|
|
30
|
+
readonly continuationAuthorized: boolean;
|
|
31
|
+
readonly publicSafe: true;
|
|
32
|
+
readonly localOnly: true;
|
|
33
|
+
}
|
|
34
|
+
/** One root transaction from a reviewed physical plan to verdict or durable debt. */
|
|
35
|
+
export declare function executeMutationQaPlan(projectRoot: string, baseRef: string, expectedPlanSha256: string): MutationQaExecutionV1;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readMutationQaCheckpoint as l,recordMutationQaFollowUpDebt as s,runAndRecordMutationQa as c}from"./mutation-qa-checkpoint.js";import{planMutationQa as u}from"./mutation-qa.js";function d(n,t,o){const i=u(n,t);if(i.planSha256!==o)throw new Error("mutation-qa-gate-plan-drift");const e=l(n,t,o);if(u(n,t).planSha256!==i.planSha256)throw new Error("mutation-qa-gate-plan-drift");const a=e?.verdict??(i.outcome==="skipped-no-files"?"not-applicable":"unobserved"),r=a==="passed"?"mutation-qa-passed":a==="warned"?"mutation-qa-warned":a==="failed"?"mutation-qa-failed":a==="not-applicable"?"mutation-qa-no-eligible-files":"mutation-qa-observation-missing";return Object.freeze({schemaVersion:1,kind:"mutation-qa-gate-decision",planSha256:i.planSha256,headSha:i.headSha,status:a,reasonCode:r,checkpointAccepted:e!==void 0,qaPassed:a==="passed",blocksLocalQa:a==="failed",followUpDebtRequired:a==="warned"||a==="unobserved",safeToContinueLocalQa:a==="passed"||a==="not-applicable",publicSafe:!0,localOnly:!0})}function f(n,t,o){const i=u(n,t);if(i.planSha256!==o)throw new Error("mutation-qa-plan-drift");const e=i.outcome==="ready"?c(n,t,o):void 0;if(e?.result.outcome==="skipped-host-isolation-unavailable")throw new Error("mutation-qa-host-isolation-unavailable");const a=d(n,t,o),r=a.followUpDebtRequired?s(n,t,o,a.status,e?.result.outcome??"not-run"):void 0;if(u(n,t).planSha256!==o)throw new Error("mutation-qa-root-execution-drift");return Object.freeze({schemaVersion:1,kind:"mutation-qa-root-execution",planSha256:o,headSha:i.headSha,runOutcome:e?.result.outcome??"not-run",decision:a,...e?.checkpoint?{checkpoint:e.checkpoint}:{},...r?{followUpDebt:r}:{},continuationAuthorized:a.safeToContinueLocalQa||!!r&&!a.blocksLocalQa,publicSafe:!0,localOnly:!0})}export{d as classifyMutationQaGate,f as executeMutationQaPlan};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{execFileSync as r}from"node:child_process";import{existsSync as i,lstatSync as n,realpathSync as s}from"node:fs";import{isAbsolute as c,join as u}from"node:path";function l(){for(const t of(process.env.PATH??"").split(":")){if(!c(t))continue;const e=u(t,"git");if(i(e)){try{const o=s(e);if((o.startsWith("/usr/")||o.startsWith("/home/linuxbrew/.linuxbrew/"))&&n(o).isFile())return o}catch{}break}}throw new Error("mutation-qa-git-untrusted")}function G(t,e){return r(l(),["-c","core.fsmonitor=false","-c","core.hooksPath=/dev/null","-c","protocol.ext.allow=never",...e],{cwd:t,env:{PATH:"/usr/bin:/bin",GIT_CONFIG_NOSYSTEM:"1",GIT_CONFIG_GLOBAL:"/dev/null",GIT_CONFIG_SYSTEM:"/dev/null",GIT_TERMINAL_PROMPT:"0",GIT_OPTIONAL_LOCKS:"0",GIT_LFS_SKIP_SMUDGE:"1"},stdio:["ignore","pipe","pipe"],timeout:15e3,maxBuffer:8*1024*1024})}export{G as mutationQaGit};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface MutationQaThresholds {
|
|
2
|
+
readonly defaultThreshold: number;
|
|
3
|
+
readonly blockThreshold: number;
|
|
4
|
+
readonly perAreaThreshold: Readonly<Record<string, number>>;
|
|
5
|
+
}
|
|
6
|
+
export interface MutationQaPolicyV1 {
|
|
7
|
+
readonly schemaVersion: 1;
|
|
8
|
+
readonly source: 'default' | 'project';
|
|
9
|
+
readonly sha256: string;
|
|
10
|
+
readonly thresholds: MutationQaThresholds;
|
|
11
|
+
readonly excludePaths: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function policyExcludesPath(policy: MutationQaPolicyV1, path: string): boolean;
|
|
14
|
+
/** Accept a bounded subset of the published policy schema, failing closed on unknown semantics. */
|
|
15
|
+
export declare function loadMutationQaPolicy(projectRoot: string): MutationQaPolicyV1;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash as m}from"node:crypto";import{lstatSync as y,readFileSync as b,realpathSync as w}from"node:fs";import{join as x}from"node:path";const g=Object.freeze({schema_version:1,default_threshold:75,block_threshold:50,exclude_paths:["**/*.test.ts","**/*.spec.ts","**/__tests__/**","**/migrations/**","**/*.d.ts"],per_area_threshold:{domain:90,infrastructure:60}}),h=16*1024;function c(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0&&e<=100}function d(e){if(e.length===0||e.length>240||e.startsWith("/")||e.includes("..")||e.includes("//")||!/^[A-Za-z0-9_./*?-]+$/.test(e))throw new Error("mutation-qa-policy-pattern-invalid");let t="^";for(let o=0;o<e.length;o++){const s=e[o];s==="*"?e[o+1]==="*"?e[o+2]==="/"?(t+="(?:.*/)?",o+=2):(t+=".*",o++):t+="[^/]*":s==="?"?t+="[^/]":s==="/"?t+="/":t+=s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}return new RegExp(`${t}$`)}function O(e,t){return e.excludePaths.some(o=>d(o).test(t))}function A(e){const t=x(e,".neocortex","mutation-policy.json");let o="default",s=Buffer.from(JSON.stringify(g));const n=y(t,{throwIfNoEntry:!1});if(n){if(w(t)!==t)throw new Error("mutation-qa-policy-path-unsafe");if(!n.isFile()||n.size===0||n.size>h)throw new Error("mutation-qa-policy-size-invalid");if(s=b(t),s.byteLength===0||s.byteLength>h)throw new Error("mutation-qa-policy-size-invalid");o="project"}let a;try{a=JSON.parse(s.toString("utf8").replace(/^\uFEFF/,""))}catch{throw new Error("mutation-qa-policy-invalid")}if(!a||typeof a!="object"||Array.isArray(a))throw new Error("mutation-qa-policy-invalid");const r=a,f=new Set(["$schema","_comment","schema_version","default_threshold","block_threshold","exclude_paths","per_area_threshold"]);if(Object.keys(r).some(i=>!f.has(i))||r.schema_version!==1||!c(r.default_threshold)||!c(r.block_threshold)||r.block_threshold>r.default_threshold||!Array.isArray(r.exclude_paths)||r.exclude_paths.length>32||r.exclude_paths.some(i=>typeof i!="string"))throw new Error("mutation-qa-policy-invalid");const u=r.exclude_paths.map(i=>(d(i),i)),l=r.per_area_threshold??{};if(!l||typeof l!="object"||Array.isArray(l)||Object.keys(l).length>32||Object.entries(l).some(([i,_])=>!/^[a-z][a-z0-9-]{0,63}$/.test(i)||!c(_)))throw new Error("mutation-qa-policy-invalid");const p=Object.freeze({...l});return Object.freeze({schemaVersion:1,source:o,sha256:m("sha256").update(s).digest("hex"),thresholds:Object.freeze({defaultThreshold:r.default_threshold,blockThreshold:r.block_threshold,perAreaThreshold:p}),excludePaths:Object.freeze(u)})}export{A as loadMutationQaPolicy,O as policyExcludesPath};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type MutationQaVerdictV1 } from './mutation-qa-verdict.js';
|
|
2
|
+
export interface MutationQaRunV1 {
|
|
3
|
+
readonly schemaVersion: 1;
|
|
4
|
+
readonly kind: 'mutation-qa-run';
|
|
5
|
+
readonly planSha256: string;
|
|
6
|
+
readonly headSha: string;
|
|
7
|
+
readonly outcome: 'skipped-no-files' | 'skipped-stryker-missing' | 'skipped-dependencies-unavailable' | 'skipped-host-isolation-unavailable' | 'skipped-stryker-error' | 'verified';
|
|
8
|
+
readonly executionEnvironment: 'os-sandbox-clone';
|
|
9
|
+
readonly verdict?: MutationQaVerdictV1;
|
|
10
|
+
readonly checkpointAccepted: false;
|
|
11
|
+
readonly localOnly: true;
|
|
12
|
+
}
|
|
13
|
+
/** Installs only from the local npm cache in a disposable clone; never mutates the source checkout. */
|
|
14
|
+
export declare function runMutationQa(projectRoot: string, baseRef: string, expectedPlanSha256: string): MutationQaRunV1;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{spawnSync as E}from"node:child_process";import{createHash as q}from"node:crypto";import{existsSync as p,lstatSync as k,mkdirSync as G,mkdtempSync as V,readFileSync as Y,realpathSync as l,rmSync as J,writeFileSync as K}from"node:fs";import{homedir as Z,tmpdir as ee}from"node:os";import{isAbsolute as M,join as u,relative as F,resolve as C,sep as B}from"node:path";import{mutationQaGit as f}from"./mutation-qa-git.js";import{planMutationQa as I}from"./mutation-qa.js";import{evaluateMutationQaReport as te}from"./mutation-qa-verdict.js";const re="reports/mutation/mutation.json",oe=8*1024*1024,ne=1800*1e3,ie=300*1e3;function x(n,t){const a=F(n,t);return a!==".."&&!a.startsWith(`..${B}`)&&!M(a)}function $(n){const t=u(n,re);if(!p(t))return null;const a=l(t);if(a!==t||!x(n,a))throw new Error("mutation-qa-report-path-unsafe");const r=k(t);if(!r.isFile()||r.size===0||r.size>oe)throw new Error("mutation-qa-report-size-invalid");return Y(t)}function se(n,t){const a=f(n,["rev-parse","--verify","HEAD"]).toString("utf8").trim(),r=t.baseRef.startsWith("sha:")?t.baseSha:t.baseRef,i=f(n,["rev-parse","--verify","--end-of-options",`${r}^{commit}`]).toString("utf8").trim();if(a!==t.headSha||i!==t.baseSha)throw new Error("mutation-qa-physical-drift");if(f(n,["status","--porcelain=v1","-z","--untracked-files=all"]).toString("utf8").split("\0").filter(Boolean).some(o=>!o.startsWith("?? ")||!(o.slice(3).startsWith("node_modules/")||o.slice(3).startsWith("reports/mutation/")||o.slice(3).startsWith(".stryker-tmp/"))))throw new Error("mutation-qa-checkout-drift");for(const o of t.eligibleFiles){const e=C(n,o.path);if(!x(n,e)||l(e)!==e||!k(e).isFile()||f(n,["hash-object","--",o.path]).toString("utf8").trim()!==o.blobSha)throw new Error("mutation-qa-source-drift")}}function N(n){for(const t of(process.env.PATH??"").split(":")){if(!M(t))continue;const a=u(t,n);if(p(a)){try{const r=l(a);if((r.startsWith("/usr/")||r.startsWith("/home/linuxbrew/.linuxbrew/"))&&k(r).isFile()&&(n!=="npm"||r.endsWith(".js")))return r}catch{}return null}}return null}function Q(n,t,a,r,i){const s=l(process.execPath),o="/home/linuxbrew/.linuxbrew";if(!s.startsWith("/usr/")&&!s.startsWith(`${o}/`))return null;const e=["--die-with-parent","--unshare-net","--unshare-pid","--new-session","--ro-bind","/usr","/usr"];for(const c of["/bin","/lib","/lib64"])p(c)&&e.push("--ro-bind",c,c);if(s.startsWith(`${o}/`)){if(!p(o))return null;e.push("--dir","/home","--dir","/home/linuxbrew","--ro-bind",o,o)}else e.push("--dir","/home");e.push("--bind",t,"/home/worker"),i&&e.push("--dir","/npm-cache","--ro-bind",i,"/npm-cache"),e.push("--dir","/etc","--ro-bind",a,"/etc/hosts","--proc","/proc","--dev","/dev","--tmpfs","/tmp","--dir","/work","--bind",n,"/work","--chdir","/work","--clearenv");for(const[c,m]of Object.entries(r))e.push("--setenv",c,m);return e}function de(n,t,a){if(!/^[a-f0-9]{64}$/.test(a))throw new Error("mutation-qa-plan-digest-invalid");const r=C(n),i=I(r,t);if(i.planSha256!==a)throw new Error("mutation-qa-plan-drift");const s={schemaVersion:1,kind:"mutation-qa-run",planSha256:i.planSha256,headSha:i.headSha,checkpointAccepted:!1,localOnly:!0,executionEnvironment:"os-sandbox-clone"};if(i.outcome==="skipped-no-files")return Object.freeze({...s,outcome:"skipped-no-files"});if(!p(u(r,"package-lock.json")))return Object.freeze({...s,outcome:"skipped-dependencies-unavailable"});const o=V(u(ee(),"mutation-qa-run-")),e=u(o,"checkout"),c=u(o,"home");G(c,{mode:448});const m=u(o,"hosts");K(m,`127.0.0.1 localhost
|
|
2
|
+
::1 localhost
|
|
3
|
+
`,{mode:384});const A={PATH:process.env.PATH??"/usr/bin:/bin",HOME:"/home/worker",TMPDIR:"/tmp",CI:"true",LANG:"C.UTF-8",NO_COLOR:"1",npm_config_offline:"true",npm_config_yes:"false",npm_config_ignore_scripts:"true",npm_config_audit:"false",npm_config_fund:"false",npm_config_userconfig:"/dev/null",npm_config_globalconfig:"/tmp/npm-global.npmrc"};try{f(r,["clone","--local","--no-hardlinks","--no-checkout",r,e]),f(e,["checkout","--detach",i.headSha]),i.baseRef.startsWith("sha:")||f(e,["update-ref",i.baseRef,i.baseSha]);const O=N("npm"),d=N("bwrap"),j=u(Z(),".npm");if(!O||!d||!p(j))return Object.freeze({...s,outcome:"skipped-host-isolation-unavailable"});const w=Q(e,c,m,A),R=Q(e,c,m,{...A,npm_config_cache:"/npm-cache",npm_config_logs_dir:"/tmp/npm-logs"},l(j));if(!w||!R)return Object.freeze({...s,outcome:"skipped-host-isolation-unavailable"});const v=l(process.execPath),g=E(d,[...w,"--",v,"-e","process.exit(0)"],{cwd:e,timeout:1e4,maxBuffer:4096,stdio:["ignore","pipe","pipe"],env:{PATH:process.env.PATH??"/usr/bin:/bin"}});if(g.error||g.signal||g.status!==0)return Object.freeze({...s,outcome:"skipped-host-isolation-unavailable"});const S=E(d,[...R,"--",v,O,"ci","--offline","--ignore-scripts","--no-audit","--no-fund"],{cwd:e,timeout:ie,maxBuffer:2*1024*1024,stdio:["ignore","pipe","pipe"],env:{PATH:process.env.PATH??"/usr/bin:/bin"}});if(S.error||S.signal||S.status!==0)return Object.freeze({...s,outcome:"skipped-dependencies-unavailable"});const P=u(e,"node_modules/.bin/stryker");if(!p(P))return Object.freeze({...s,outcome:"skipped-stryker-missing"});const y=l(P);if(!x(e,y)||!k(y).isFile())throw new Error("mutation-qa-stryker-path-unsafe");const D=`/work/${F(e,y).split(B).join("/")}`,T=$(e),L=T?q("sha256").update(T).digest("hex"):null,U=process.umask(63);let h=null,z=!1,H=!1;try{const b=E(d,[...w,"--",v,D,"run","--mutate",i.eligibleFiles.map(X=>X.path).join(","),"--reporters","json","--timeoutMS","600000","--concurrency","4","--coverageAnalysis","off"],{cwd:e,timeout:ne,maxBuffer:2097152,stdio:["ignore","pipe","pipe"],env:{PATH:process.env.PATH??"/usr/bin:/bin"}});h=b.status,z=!!b.error||!!b.signal,H=b.error?.code==="ENOENT"}finally{process.umask(U)}if(se(e,i),I(r,t).planSha256!==i.planSha256)throw new Error("mutation-qa-physical-drift");if(H)return Object.freeze({...s,outcome:"skipped-host-isolation-unavailable"});if(z||h!==0&&h!==1)return Object.freeze({...s,outcome:"skipped-stryker-error"});const _=$(e);if(!_||q("sha256").update(_).digest("hex")===L){if(h===1)return Object.freeze({...s,outcome:"skipped-stryker-error"});throw new Error("mutation-qa-report-stale")}const W=te(i,_);return h===1&&!W.runnerBreakCrossed?Object.freeze({...s,outcome:"skipped-stryker-error"}):Object.freeze({...s,outcome:"verified",verdict:W})}finally{J(o,{recursive:!0,force:!0})}}export{de as runMutationQa};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { MutationQaPlanV1 } from './mutation-qa.js';
|
|
2
|
+
export interface MutationQaVerdictV1 {
|
|
3
|
+
readonly schemaVersion: 1;
|
|
4
|
+
readonly kind: 'mutation-qa-verdict';
|
|
5
|
+
readonly planSha256: string;
|
|
6
|
+
readonly reportSha256: string;
|
|
7
|
+
readonly runnerBreakThreshold: number | null;
|
|
8
|
+
readonly runnerBreakCrossed: boolean;
|
|
9
|
+
readonly outcome: 'passed' | 'warned' | 'failed';
|
|
10
|
+
readonly mutationScore: number;
|
|
11
|
+
readonly killed: number;
|
|
12
|
+
readonly survived: number;
|
|
13
|
+
readonly noCoverage: number;
|
|
14
|
+
readonly timeout: number;
|
|
15
|
+
readonly invalid: number;
|
|
16
|
+
readonly belowAreaThresholdCount: number;
|
|
17
|
+
readonly checkpointAccepted: false;
|
|
18
|
+
readonly localOnly: true;
|
|
19
|
+
}
|
|
20
|
+
/** Reject partial/foreign reports; never project file names, source, mutants or test details. */
|
|
21
|
+
export declare function evaluateMutationQaReport(plan: MutationQaPlanV1, rawReport: Buffer): MutationQaVerdictV1;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash as F}from"node:crypto";function C(t,r,e,i){const o=t+r+e+i;return o===0?0:Math.round((t+i)/o*1e4)/100}function O(t,r){const e=Buffer.from(t,"utf8");return F(r).update(`blob ${e.byteLength}\0`).update(e).digest("hex")}function L(t,r){const e=t.match(/^src\/([^/]+)\//)?.[1];return e&&r.perAreaThreshold[e]!==void 0?r.perAreaThreshold[e]:e==="adapters"&&r.perAreaThreshold.infrastructure!==void 0?r.perAreaThreshold.infrastructure:r.defaultThreshold}function z(t,r){const e=t.policy.thresholds;if(t.outcome!=="ready"||t.eligibleFiles.length===0)throw new Error("mutation-qa-plan-not-ready");if(r.byteLength===0||r.byteLength>8*1024*1024)throw new Error("mutation-qa-report-size-invalid");if([e.defaultThreshold,e.blockThreshold,...Object.values(e.perAreaThreshold)].some(a=>!Number.isFinite(a)||a<0||a>100)||e.blockThreshold>e.defaultThreshold)throw new Error("mutation-qa-thresholds-invalid");let i;try{i=JSON.parse(r.toString("utf8"))}catch{throw new Error("mutation-qa-report-invalid")}if(!i||typeof i!="object"||Array.isArray(i))throw new Error("mutation-qa-report-invalid");const o=i;if(typeof o.schemaVersion!="string"||!/^[12](?:\.\d+){0,2}$/.test(o.schemaVersion)||!o.thresholds||typeof o.thresholds!="object"||Array.isArray(o.thresholds)||!o.files||typeof o.files!="object"||Array.isArray(o.files))throw new Error("mutation-qa-report-invalid");const u=o.thresholds,c=u.high,d=u.low,s=u.break===void 0||u.break===null?null:u.break;if(typeof c!="number"||!Number.isFinite(c)||c<0||c>100||typeof d!="number"||!Number.isFinite(d)||d<0||d>c||s!==null&&(typeof s!="number"||!Number.isFinite(s)||s<0||s>d))throw new Error("mutation-qa-report-invalid");const j=o.files,y=t.eligibleFiles.map(a=>a.path).sort(),N=Object.keys(j).sort();if(y.length!==N.length||y.some((a,n)=>a!==N[n]))throw new Error("mutation-qa-report-scope-mismatch");let f=0,p=0,b=0,m=0,w=0,g=0;for(const a of y){const n=j[a];if(!n||typeof n!="object"||Array.isArray(n)||typeof n.language!="string"||typeof n.source!="string"||!Array.isArray(n.mutants))throw new Error("mutation-qa-report-invalid");const A=t.eligibleFiles.find(l=>l.path===a)?.blobSha;if(!A||O(n.source,A.length===64?"sha256":"sha1")!==A)throw new Error("mutation-qa-report-source-mismatch");const T=n.mutants;if(T.length===0||T.length>1e5)throw new Error("mutation-qa-report-invalid");let k=0,E=0,q=0,S=0;for(const l of T){if(!l||typeof l!="object"||Array.isArray(l))throw new Error("mutation-qa-report-invalid");const h=l;if(typeof h.id!="string"||!h.id||typeof h.mutatorName!="string"||!h.mutatorName||!h.location||typeof h.location!="object"||Array.isArray(h.location))throw new Error("mutation-qa-report-invalid");switch(l.status){case"Killed":f++,k++;break;case"Survived":p++,E++;break;case"NoCoverage":b++,q++;break;case"Timeout":m++,S++;break;case"RuntimeError":case"CompileError":w++;break;case"Ignored":break;default:throw new Error("mutation-qa-report-status-unsupported")}}if(k+E+q+S===0)throw new Error("mutation-qa-report-no-scored-mutants");C(k,E,q,S)<L(a,e)&&g++}const v=C(f,p,b,m),x=s!==null&&(f+m)*100<s*(f+p+b+m),B=v<e.blockThreshold?"failed":v<e.defaultThreshold||g>0||w>0?"warned":"passed";return Object.freeze({schemaVersion:1,kind:"mutation-qa-verdict",planSha256:t.planSha256,reportSha256:F("sha256").update(r).digest("hex"),runnerBreakThreshold:s,runnerBreakCrossed:x,outcome:B,mutationScore:v,killed:f,survived:p,noCoverage:b,timeout:m,invalid:w,belowAreaThresholdCount:g,checkpointAccepted:!1,localOnly:!0})}export{z as evaluateMutationQaReport};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type MutationQaPolicyV1 } from './mutation-qa-policy.js';
|
|
2
|
+
export interface MutationQaPlanV1 {
|
|
3
|
+
readonly schemaVersion: 1;
|
|
4
|
+
readonly kind: 'mutation-qa-plan';
|
|
5
|
+
readonly outcome: 'ready' | 'skipped-no-files';
|
|
6
|
+
/** A verified remote ref or `sha:<exact-ancestor-commit>` for a private snapshot. */
|
|
7
|
+
readonly baseRef: string;
|
|
8
|
+
readonly baseSha: string;
|
|
9
|
+
readonly headSha: string;
|
|
10
|
+
readonly mergeBaseSha: string;
|
|
11
|
+
readonly policy: MutationQaPolicyV1;
|
|
12
|
+
readonly eligibleFiles: readonly {
|
|
13
|
+
readonly path: string;
|
|
14
|
+
readonly blobSha: string;
|
|
15
|
+
}[];
|
|
16
|
+
readonly planSha256: string;
|
|
17
|
+
readonly checkpointAccepted: false;
|
|
18
|
+
readonly localOnly: true;
|
|
19
|
+
}
|
|
20
|
+
/** No test execution, package installation, network, project write or QA verdict. */
|
|
21
|
+
export declare function planMutationQa(projectRoot: string, baseRef: string): MutationQaPlanV1;
|
|
22
|
+
export declare function mutationQaCliHandler(args: readonly string[]): Promise<number>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createHash as q}from"node:crypto";import{lstatSync as y,realpathSync as $}from"node:fs";import{resolve as v}from"node:path";import{mutationQaGit as w}from"./mutation-qa-git.js";import{loadMutationQaPolicy as E,policyExcludesPath as S}from"./mutation-qa-policy.js";function p(o,n){return w(o,n).toString("utf8").trim()}function k(o){return o.length>0&&o.length<=240&&/^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)*$/.test(o)&&!o.split("/").some(n=>n==="."||n==="..")&&/\.(?:ts|tsx|js|jsx)$/.test(o)&&!/\.d\.ts$|\.(?:test|spec)\.[jt]sx?$|(?:^|\/)__tests__\/|(?:^|\/)migrations\//.test(o)}function b(o){return/\.(?:ts|tsx|js|jsx)$/.test(o)&&!/\.d\.ts$|\.(?:test|spec)\.[jt]sx?$|(?:^|\/)__tests__\/|(?:^|\/)migrations\//.test(o)}function x(o,n){const f=/^sha:([a-f0-9]{40}|[a-f0-9]{64})$/.exec(n)?.[1],m=/^refs\/remotes\/[A-Za-z0-9][A-Za-z0-9_-]{0,63}\/[A-Za-z0-9][A-Za-z0-9/_-]{0,127}$/.test(n)&&!n.includes("..")&&!n.includes("//")&&!n.endsWith("/");if(!f&&!m)throw new Error("mutation-qa-base-ref-invalid");const e=v(o);try{if($(e)!==e||!y(e).isDirectory()||p(e,["rev-parse","--show-toplevel"])!==e)throw new Error("mutation-qa-project-root-invalid");const a=p(e,["rev-parse","--verify","HEAD"]),h=f??n,i=p(e,["rev-parse","--verify","--end-of-options",`${h}^{commit}`]);if(f)try{w(e,["merge-base","--is-ancestor",i,a])}catch{throw new Error("mutation-qa-base-not-ancestor")}const d=p(e,["merge-base",i,a]);if(![a,i,d].every(c=>/^[a-f0-9]{40,64}$/.test(c)))throw new Error("mutation-qa-revision-invalid");if(f&&(i!==f||d!==i))throw new Error("mutation-qa-base-not-ancestor");if(w(e,["status","--porcelain=v1","-z","--untracked-files=all"]).byteLength!==0)throw new Error("mutation-qa-checkout-dirty");const s=E(e),u=w(e,["diff","--name-only","-z","--diff-filter=ACMR",d,a]).toString("utf8").split("\0").filter(Boolean);if(u.length>512)throw new Error("mutation-qa-scope-too-large");const r=u.filter(b);if(r.some(c=>!k(c)))throw new Error("mutation-qa-source-path-unsafe");if(r.length>128)throw new Error("mutation-qa-scope-too-large");const t=r.filter(c=>!S(s,c)).sort().map(c=>{const g=p(e,["ls-tree","HEAD","--",c]).match(/^(100644|100755) blob ([a-f0-9]{40,64})\t(.+)$/);if(!g||g[3]!==c)throw new Error("mutation-qa-source-not-regular");return Object.freeze({path:c,blobSha:g[2]})});if(p(e,["rev-parse","--verify","HEAD"])!==a||p(e,["rev-parse","--verify","--end-of-options",`${h}^{commit}`])!==i||w(e,["status","--porcelain=v1","-z","--untracked-files=all"]).byteLength!==0)throw new Error("mutation-qa-physical-drift");const l={schemaVersion:1,kind:"mutation-qa-plan",outcome:t.length>0?"ready":"skipped-no-files",baseRef:n,baseSha:i,headSha:a,mergeBaseSha:d,policy:s,eligibleFiles:t,checkpointAccepted:!1,localOnly:!0};return Object.freeze({...l,planSha256:q("sha256").update("mutation-qa-plan-v1\0").update(JSON.stringify(l)).digest("hex")})}catch(a){throw a instanceof Error&&a.message.startsWith("mutation-qa-")?a:new Error("mutation-qa-plan-unavailable")}}async function C(o){let n=process.cwd(),f="",m="",e="",a=!1,h=!1,i="plain";const d=new Set;try{if(o[0]!=="plan"&&o[0]!=="run"&&o[0]!=="execute"&&o[0]!=="checkpoint"&&o[0]!=="gate")throw new Error("mutation-qa-action-invalid");for(let r=1;r<o.length;r++){const t=o[r];if(d.has(t))throw new Error("mutation-qa-argument-duplicate");d.add(t);const l=()=>o[++r]??"";if(t==="--project-root")n=l();else if(t==="--base-ref")f=l();else if(t==="--base-sha")m=l();else if(t==="--plan-sha")e=l();else if(t==="--authorize-run")a=!0;else if(t==="--record-checkpoint")h=!0;else if(t==="--format"){const c=l();if(c!=="plain"&&c!=="json")throw new Error("mutation-qa-format-invalid");i=c}else throw new Error("mutation-qa-argument-invalid")}if(!n||!!f==!!m)throw new Error("mutation-qa-target-required");if(m&&!/^([a-f0-9]{40}|[a-f0-9]{64})$/.test(m))throw new Error("mutation-qa-base-ref-invalid");const s=m?`sha:${m}`:f;if(o[0]==="execute"){if(!a||!e||h)throw new Error("mutation-qa-execute-authorization-required");const{executeMutationQaPlan:r}=await import("./mutation-qa-gate.js"),t=r(n,s,e);return console.log(i==="json"?JSON.stringify({ok:!0,execution:t}):`mutation-qa execute: ${t.decision.status} head=${t.headSha} debt=${t.followUpDebt?"recorded":"none"} qaPassed=${t.decision.qaPassed}`),t.decision.safeToContinueLocalQa?0:t.continuationAuthorized?2:1}if(o[0]==="gate"){if(!e||a||h)throw new Error("mutation-qa-gate-options-invalid");const{classifyMutationQaGate:r}=await import("./mutation-qa-gate.js"),t=r(n,s,e);return console.log(i==="json"?JSON.stringify({ok:!0,decision:t}):`mutation-qa gate: ${t.status} head=${t.headSha}`),t.safeToContinueLocalQa?0:1}if(o[0]==="checkpoint"){if(!e||a||h)throw new Error("mutation-qa-checkpoint-options-invalid");const{readMutationQaCheckpoint:r}=await import("./mutation-qa-checkpoint.js"),t=r(n,s,e);return console.log(i==="json"?JSON.stringify({ok:!0,checkpoint:t??null}):`mutation-qa checkpoint: ${t?t.verdict:"none"}`),0}if(o[0]==="run"){if(!a||!e)throw new Error("mutation-qa-run-authorization-required");if(h){const{runAndRecordMutationQa:r}=await import("./mutation-qa-checkpoint.js"),{result:t,checkpoint:l}=r(n,s,e);console.log(i==="json"?JSON.stringify({ok:!0,result:t,checkpoint:l??null}):`mutation-qa run: ${t.outcome} head=${t.headSha} checkpoint=${l?"accepted":"none"}`)}else{const{runMutationQa:r}=await import("./mutation-qa-run.js"),t=r(n,s,e);console.log(i==="json"?JSON.stringify({ok:!0,result:t}):`mutation-qa run: ${t.outcome} head=${t.headSha}`)}return 0}if(a||e||h)throw new Error("mutation-qa-plan-options-invalid");const u=x(n,s);return console.log(i==="json"?JSON.stringify({ok:!0,plan:u}):`mutation-qa plan: ${u.outcome} files=${u.eligibleFiles.length} head=${u.headSha}`),0}catch(s){const u=s instanceof Error&&/^mutation-qa-[a-z0-9-]+$/.test(s.message)?s.message:"mutation-qa-unavailable";return i==="json"?console.log(JSON.stringify({ok:!1,reasonCode:u})):console.error(`mutation-qa: ${u}`),1}}export{C as mutationQaCliHandler,x as planMutationQa};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function propertyQaCliHandler(args: string[]): Promise<number>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{inspectPropertyTestCatalog as d,confirmPropertyTestPlan as w}from"../private-execution/property-test-plan.js";import{savePropertyTestChoices as g}from"../private-execution/property-test-choices.js";async function h(n){const r=n[0];let o=process.cwd(),i="plain",p=!1,c=!1;const f=new Set;for(let e=1;e<n.length;e++){const t=n[e];if(f.has(t))return console.error("property-test-invalid-cli-argument"),1;if(f.add(t),t==="--project-root"&&n[e+1])o=n[++e];else if(t==="--format"&&(n[e+1]==="plain"||n[e+1]==="json"))i=n[++e];else if(t==="--authorize-run")p=!0;else if(t==="--authorize-review")c=!0;else return console.error("property-test-invalid-cli-argument"),1}if(r!=="inspect"&&r!=="confirm"&&r!=="save"&&r!=="candidate"&&r!=="verify"&&r!=="review-plan"&&r!=="review-accept"&&r!=="review-status")return console.error("Usage: neocortex-client property-qa inspect|confirm|save|candidate|verify|review-plan|review-accept|review-status --project-root <path> --format json|plain"),1;if(p!==(r==="verify")||c!==(r==="review-accept"))return console.error(r==="review-accept"||c?"property-test-review-authorization-required":"property-test-run-authorization-required"),1;try{if(r==="inspect"){const e=d(o);if(i==="json")console.log(JSON.stringify(e));else{console.log(`PRD: ${e.prdPath} (${e.prdSha256})`);for(const t of e.aggregates)console.log(`${t.name}: ${t.invariantCount} invariants; candidates: ${t.suggestedFiles.join(", ")||"(none)"}`);console.log("Explicit mapping, language and numRuns are required for confirm. No tests were written.")}}else if(r==="verify"){const{verifyPropertyTestRuntime:e}=await import("../private-execution/property-test-runtime.js"),t=e(o);return console.log(i==="json"?JSON.stringify(t):`property-test runtime ${t.outcome}: ${t.reasonCode}; QA not accepted`),t.executionVerified?0:1}else if(r==="review-plan"){const{inspectPropertyTestReviewPlan:e}=await import("../private-execution/property-test-review.js"),t=e(o);console.log(i==="json"?JSON.stringify(t):`property-test review plan ${t.candidateSha256}: ${t.invariants.length} invariant hashes; no QA accepted`)}else if(r==="review-accept"){let e="";for await(const u of process.stdin)if(e+=String(u),Buffer.byteLength(e)>256*1024)throw new Error("property-test-review-too-large");const{parseDocument:t}=await import("yaml"),s=t(e,{strict:!0,uniqueKeys:!0,schema:"core",customTags:[]});if(s.errors.length||s.warnings.length)throw new Error("property-test-review-unverified");const{acceptPropertyTestSemanticReview:l}=await import("../private-execution/property-test-review.js"),a=l(o,JSON.parse(e));console.log(i==="json"?JSON.stringify(a):`property-test review accepted: ${a.invariantCount} invariants, ${a.passedTests} passed and ${a.causalFailedTests} causal failures`)}else if(r==="review-status"){const{readPropertyTestReviewReceipt:e}=await import("../private-execution/property-test-review.js"),t=e(o);console.log(i==="json"?JSON.stringify(t):`property-test review accepted: ${t.invariantCount} invariants at ${t.candidateSha256}`)}else if(r==="candidate"){const{inspectPropertyTestCandidate:e}=await import("../private-execution/property-test-candidate.js"),t=e(o);console.log(i==="json"?JSON.stringify(t):`property-test candidate ${t.artifactSha256}: ${t.testFileCount} test files; execution and QA unverified`)}else{let e="";for await(const l of process.stdin)if(e+=String(l),Buffer.byteLength(e)>32768)throw new Error("property-test-choices-too-large");const t=JSON.parse(e),s=r==="save"?g(o,t):w(o,t);i==="json"?console.log(JSON.stringify(s)):(console.log(`property-test plan ${s.planSha256}: ${s.writeSet.join(", ")}`),console.log(r==="save"?"Choices saved in the private Git common-dir; no tests were written and no QA checkpoint was accepted.":"Read-only preflight: no tests were written and no QA checkpoint was accepted."))}return 0}catch(e){const t=e instanceof Error&&/^property-test-[a-z-]+$/.test(e.message)?e.message:"property-test-preflight-failed";return console.error(t),1}}export{h as propertyQaCliHandler};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type RootQaScanObservationV1 } from './qa-scan-review.js';
|
|
2
|
+
export interface RootQaScanCaptureReceiptV1 {
|
|
3
|
+
readonly schemaVersion: 1;
|
|
4
|
+
readonly evidenceContractVersion: 2;
|
|
5
|
+
readonly kind: 'root-qa-scan-capture';
|
|
6
|
+
readonly sourceHead: string;
|
|
7
|
+
readonly planSha256: string;
|
|
8
|
+
readonly surface: string;
|
|
9
|
+
readonly targetSha256: string;
|
|
10
|
+
readonly evidenceCount: 8;
|
|
11
|
+
readonly observations: readonly RootQaScanObservationV1[];
|
|
12
|
+
readonly qaAccepted: false;
|
|
13
|
+
readonly checkpointAccepted: false;
|
|
14
|
+
readonly workflowContinuationAuthorized: false;
|
|
15
|
+
readonly localOnly: true;
|
|
16
|
+
readonly publicSafe: true;
|
|
17
|
+
}
|
|
18
|
+
export interface RootQaScanInteractionReceiptV1 {
|
|
19
|
+
readonly schemaVersion: 1;
|
|
20
|
+
readonly evidenceContractVersion: 2;
|
|
21
|
+
readonly kind: 'root-qa-scan-interaction';
|
|
22
|
+
readonly sourceHead: string;
|
|
23
|
+
readonly planSha256: string;
|
|
24
|
+
readonly surface: string;
|
|
25
|
+
readonly targetSha256: string;
|
|
26
|
+
readonly observation: RootQaScanObservationV1;
|
|
27
|
+
readonly qaAccepted: false;
|
|
28
|
+
readonly checkpointAccepted: false;
|
|
29
|
+
readonly workflowContinuationAuthorized: false;
|
|
30
|
+
readonly localOnly: true;
|
|
31
|
+
readonly publicSafe: true;
|
|
32
|
+
}
|
|
33
|
+
/** Observe only loopback GET/HEAD loads, including popups. A page-initiated
|
|
34
|
+
* mutation request or WebSocket is blocked even at the named origin; a fresh
|
|
35
|
+
* browser profile/cookie jar is never borrowed from the user's host. */
|
|
36
|
+
export declare function captureRootQaScan(options: {
|
|
37
|
+
readonly projectRoot: string;
|
|
38
|
+
readonly surface: string;
|
|
39
|
+
readonly targetUrl: string;
|
|
40
|
+
readonly authorizeCapture: boolean;
|
|
41
|
+
}): Promise<RootQaScanCaptureReceiptV1>;
|
|
42
|
+
/** Explicit synthetic-only local-state interaction. The click runs with every
|
|
43
|
+
* subsequent browser network request denied, so it cannot authorize an
|
|
44
|
+
* arbitrary live-app workflow or a server-side business mutation. */
|
|
45
|
+
export declare function captureRootQaScanInteraction(options: {
|
|
46
|
+
readonly projectRoot: string;
|
|
47
|
+
readonly surface: string;
|
|
48
|
+
readonly targetUrl: string;
|
|
49
|
+
readonly actionId: string;
|
|
50
|
+
readonly expectedStatus: string;
|
|
51
|
+
readonly authorizeInteraction: boolean;
|
|
52
|
+
}): Promise<RootQaScanInteractionReceiptV1>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import{createHash as M,randomUUID as P}from"node:crypto";import{closeSync as D,constants as l,fsyncSync as U,openSync as z,writeFileSync as F}from"node:fs";import{join as H,resolve as N}from"node:path";import Q from"axe-core";import{chromium as W}from"playwright-core";import{planRootQaScan as k,rootQaScanEvidenceDirectory as B}from"./qa-scan-review.js";const G=8*1024*1024,X=1e4,$=/^[a-z][a-z0-9-]{0,50}$/;function J(e){return M("sha256").update(e).digest("hex")}function _(e){return Buffer.from(`${JSON.stringify(e)}
|
|
2
|
+
`,"utf8")}function Y(e,u,n,i,d,r){if(i.length<8||i.length>G)throw new Error("qa-scan-review-capture-artifact-invalid");return Object.freeze({kind:e,artifactId:`${e}-${P()}.${n}`,captureMethod:u,sha256:J(i),capturedAt:d,rationale:r})}async function oe(e){if(!e.authorizeCapture)throw new Error("qa-scan-review-authorization-or-target-required");const u=N(e.projectRoot),n=k(u,e.surface,e.targetUrl),i=new URL(e.targetUrl);let d;try{d=await W.launch({channel:"chrome",headless:!0,timeout:15e3})}catch{throw new Error("qa-scan-review-browser-unavailable")}try{const r=await d.newContext({viewport:{width:1440,height:900},serviceWorkers:"block",acceptDownloads:!1,permissions:[],ignoreHTTPSErrors:!1});let w=0,h=0;await r.route("**/*",async a=>{let I=!1;try{const S=new URL(a.request().url());I=S.protocol==="http:"&&S.origin===i.origin&&(a.request().method()==="GET"||a.request().method()==="HEAD")}catch{}I?await a.continue():(w++,await a.abort("blockedbyclient"))}),await r.routeWebSocket(/.*/,async a=>{h++,await a.close({code:1008,reason:"blocked"})});const t=await r.newPage();r.on("page",a=>{a!==t&&a.close()});let o=0;t.on("framenavigated",a=>{a===t.mainFrame()&&a.url()!=="about:blank"&&o++}),t.setDefaultTimeout(15e3);let q=0,x=0,m=0,b=0;t.on("console",a=>{a.type()==="error"&&q++}),t.on("pageerror",()=>{x++}),t.on("request",()=>{b++}),t.on("requestfailed",()=>{m++}),t.on("response",a=>{a.status()>=400&&m++});const g=await t.goto(i.href,{waitUntil:"load",timeout:15e3});if(!g||g.status()>=400||t.url()!==i.href||o!==1)throw new Error("qa-scan-review-capture-target-unavailable");const s=()=>{if(t.url()!==i.href||o!==1)throw new Error("qa-scan-review-capture-target-drift")};s();const E=await t.screenshot({type:"png",animations:"disabled",timeout:15e3});s(),await t.addScriptTag({content:Q.source});const p=await t.evaluate("window.axe.run(document)");if(s(),!p||typeof p!="object")throw new Error("qa-scan-review-capture-accessibility-unavailable");const f=p;if(!Array.isArray(f.violations)||!Array.isArray(f.incomplete)||!Array.isArray(f.passes))throw new Error("qa-scan-review-capture-accessibility-unavailable");await t.keyboard.press("Tab");const C=await t.evaluate(`(() => {
|
|
3
|
+
const active = document.activeElement;
|
|
4
|
+
return !!active && active !== document.body && active !== document.documentElement
|
|
5
|
+
&& active.matches(':focus-visible');
|
|
6
|
+
})()`),v=await t.evaluate(`(() => {
|
|
7
|
+
const entry = performance.getEntriesByType('navigation')[0];
|
|
8
|
+
return entry ? entry.duration : null;
|
|
9
|
+
})()`);s(),await t.setViewportSize({width:375,height:812});const O=await t.screenshot({type:"png",animations:"disabled",timeout:15e3});s(),await t.setViewportSize({width:768,height:1024});const T=await t.screenshot({type:"png",animations:"disabled",timeout:15e3});s(),await t.setViewportSize({width:1024,height:768});const c=await t.screenshot({type:"png",animations:"disabled",timeout:15e3});s();const R=typeof v=="number"&&Number.isFinite(v)?Math.max(0,Math.round(v)):null,A=new Date().toISOString(),y=[{kind:"visual",method:"browser-screenshot",suffix:"png",bytes:Buffer.from(E),rationale:"Chrome headless desktop viewport 1440x900 at the exact local target before QA review."},{kind:"responsive",method:"browser-screenshot",suffix:"png",bytes:Buffer.from(O),rationale:"Chrome headless mobile-width viewport 375x812 at the same local target."},{kind:"responsive-tablet",method:"browser-screenshot",suffix:"png",bytes:Buffer.from(T),rationale:"Chrome headless tablet-width viewport 768x1024 at the same local target."},{kind:"responsive-desktop",method:"browser-screenshot",suffix:"png",bytes:Buffer.from(c),rationale:"Chrome headless compact-desktop viewport 1024x768 at the same local target."},{kind:"accessibility",method:"axe-scan",suffix:"json",bytes:_({schemaVersion:1,engine:"axe-core",violationCount:f.violations.length,incompleteCount:f.incomplete.length,passCount:f.passes.length}),rationale:"Axe-core ran against the rendered local document; counts preserve incomplete and violated rules."},{kind:"interaction",method:"browser-interaction",suffix:"json",bytes:_({schemaVersion:1,checks:[{id:"keyboard-focus",passed:C===!0}]}),rationale:"Non-activating Tab key probe observed a visible focus target without clicking application controls."},{kind:"console-network",method:"console-capture",suffix:"json",bytes:_({schemaVersion:1,initialCapture:!0,consoleErrors:q,pageErrors:x,failedRequests:m+w+h,requestsObserved:b}),rationale:"Console, page-error, HTTP and WebSocket routing listeners were installed before navigation."},{kind:"performance",method:"performance-trace",suffix:"json",bytes:_({schemaVersion:1,metric:"load",observedMs:R,budgetMs:X}),rationale:"Browser Navigation Timing load duration was compared with the fixed 10000 ms local budget."}],L=y.map(a=>Y(a.kind,a.method,a.suffix,a.bytes,A,a.rationale));if(s(),k(u,e.surface,e.targetUrl).planSha256!==n.planSha256)throw new Error("qa-scan-review-capture-source-drift");const V=B(u);for(let a=0;a<y.length;a++){const I=H(V,L[a].artifactId),S=z(I,l.O_WRONLY|l.O_CREAT|l.O_EXCL|(l.O_NOFOLLOW??0),384);try{F(S,y[a].bytes),U(S)}finally{D(S)}}const j=z(V,l.O_RDONLY|(l.O_DIRECTORY??0));try{U(j)}finally{D(j)}if(s(),k(u,e.surface,e.targetUrl).planSha256!==n.planSha256)throw new Error("qa-scan-review-capture-source-drift");return Object.freeze({schemaVersion:1,evidenceContractVersion:2,kind:"root-qa-scan-capture",sourceHead:n.sourceHead,planSha256:n.planSha256,surface:n.surface,targetSha256:n.targetSha256,evidenceCount:8,observations:Object.freeze(L),qaAccepted:!1,checkpointAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}catch(r){throw r instanceof Error&&r.message.startsWith("qa-scan-review-")?r:new Error("qa-scan-review-capture-unavailable")}finally{await d.close().catch(()=>{})}}async function ne(e){if(!e.authorizeInteraction||!$.test(e.actionId)||!$.test(e.expectedStatus))throw new Error("qa-scan-review-interaction-authorization-required");const u=N(e.projectRoot),n=k(u,e.surface,e.targetUrl),i=new URL(e.targetUrl);let d;try{d=await W.launch({channel:"chrome",headless:!0,timeout:15e3})}catch{throw new Error("qa-scan-review-browser-unavailable")}try{const r=await d.newContext({viewport:{width:1280,height:800},serviceWorkers:"block",acceptDownloads:!1,permissions:[],ignoreHTTPSErrors:!1});let w=!1,h=0,t=0;await r.route("**/*",async c=>{let R=!1;try{const A=c.request(),y=new URL(A.url());R=!w&&y.protocol==="http:"&&y.origin===i.origin&&(A.method()==="GET"||A.method()==="HEAD")}catch{}R?await c.continue():(w?h++:t++,await c.abort("blockedbyclient"))}),await r.routeWebSocket(/.*/,async c=>{w?h++:t++,await c.close({code:1008,reason:"blocked"})});const o=await r.newPage();let q=0,x=0,m=0;r.on("page",c=>{c!==o&&(q++,c.close())}),o.on("framenavigated",()=>{w&&x++}),o.on("download",()=>{w&&m++});let b=0,g=0;o.on("pageerror",()=>{b++}),o.on("console",c=>{c.type()==="error"&&g++});const s=await o.goto(i.href,{waitUntil:"load",timeout:15e3});if(!s||s.status()>=400||o.url()!==i.href||t!==0||b!==0||g!==0)throw new Error("qa-scan-review-interaction-target-unavailable");const E=o.locator(`button[type="button"][data-neocortex-qa-safe="true"][data-neocortex-qa-action="${e.actionId}"]`),p=o.locator(`[data-neocortex-qa-status="${e.actionId}"]`);if(await o.locator('body[data-neocortex-qa-synthetic="1"]').count()!==1||await E.count()!==1||await p.count()!==1||!await E.isVisible()||!await p.isVisible()||(await p.textContent())?.trim()===e.expectedStatus)throw new Error("qa-scan-review-interaction-contract-unavailable");if(w=!0,await E.click({timeout:5e3}),await o.waitForFunction(`({ actionId, expectedStatus }) =>
|
|
10
|
+
document.querySelector('[data-neocortex-qa-status="' + actionId + '"]')
|
|
11
|
+
?.textContent?.trim() === expectedStatus`,{actionId:e.actionId,expectedStatus:e.expectedStatus},{timeout:5e3}),await o.waitForTimeout(250),h!==0||q!==0||x!==0||m!==0||b!==0||g!==0||o.url()!==i.href)throw new Error("qa-scan-review-interaction-effects-denied");const f=_({schemaVersion:2,syntheticTarget:!0,networkAttemptsAfterAction:h,checks:[{id:`functional-${e.actionId}`,passed:!0}]}),C=Y("interaction","browser-interaction","json",f,new Date().toISOString(),"Root synthetic action changed one visible local status without browser network effects.");if(k(u,e.surface,e.targetUrl).planSha256!==n.planSha256)throw new Error("qa-scan-review-capture-source-drift");const v=B(u),O=z(H(v,C.artifactId),l.O_WRONLY|l.O_CREAT|l.O_EXCL|(l.O_NOFOLLOW??0),384);try{F(O,f),U(O)}finally{D(O)}const T=z(v,l.O_RDONLY|(l.O_DIRECTORY??0));try{U(T)}finally{D(T)}if(k(u,e.surface,e.targetUrl).planSha256!==n.planSha256)throw new Error("qa-scan-review-capture-source-drift");return Object.freeze({schemaVersion:1,evidenceContractVersion:2,kind:"root-qa-scan-interaction",sourceHead:n.sourceHead,planSha256:n.planSha256,surface:n.surface,targetSha256:n.targetSha256,observation:C,qaAccepted:!1,checkpointAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}catch(r){throw r instanceof Error&&r.message.startsWith("qa-scan-review-")?r:new Error("qa-scan-review-interaction-unavailable")}finally{await d.close().catch(()=>{})}}export{oe as captureRootQaScan,ne as captureRootQaScanInteraction};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface RootQaScanDiscoveryReceiptV2 {
|
|
2
|
+
readonly schemaVersion: 2;
|
|
3
|
+
readonly kind: 'root-qa-scan-link-discovery';
|
|
4
|
+
readonly sourceHead: string;
|
|
5
|
+
readonly seedSha256: string;
|
|
6
|
+
readonly discoverySha256: string;
|
|
7
|
+
readonly targetCount: number;
|
|
8
|
+
readonly inventoryScope: 'reachable-rendered-same-origin-links';
|
|
9
|
+
readonly qaAccepted: false;
|
|
10
|
+
readonly workflowContinuationAuthorized: false;
|
|
11
|
+
readonly localOnly: true;
|
|
12
|
+
readonly publicSafe: true;
|
|
13
|
+
}
|
|
14
|
+
/** Navigation itself may have application-defined GET effects. Opt in only
|
|
15
|
+
* for a purpose-built synthetic app; never borrow a browser profile/cookies. */
|
|
16
|
+
export declare function discoverRootQaScanLinks(options: {
|
|
17
|
+
readonly projectRoot: string;
|
|
18
|
+
readonly seedUrl: string;
|
|
19
|
+
readonly authorizeDiscovery: boolean;
|
|
20
|
+
}): Promise<RootQaScanDiscoveryReceiptV2>;
|
|
21
|
+
export declare function readRootQaScanDiscovery(projectRoot: string, id: string): RootQaScanDiscoveryReceiptV2;
|
|
22
|
+
export interface RootQaScanDiscoveredPhaseV2 {
|
|
23
|
+
readonly schemaVersion: 2;
|
|
24
|
+
readonly kind: 'root-qa-scan-discovered-phase-checkpoint';
|
|
25
|
+
readonly sourceHead: string;
|
|
26
|
+
readonly discoverySha256: string;
|
|
27
|
+
readonly aggregateSha256: string;
|
|
28
|
+
readonly surfaceCount: number;
|
|
29
|
+
readonly inventoryScope: 'reachable-rendered-same-origin-links';
|
|
30
|
+
readonly qaAccepted: boolean;
|
|
31
|
+
readonly workflowContinuationAuthorized: false;
|
|
32
|
+
readonly localOnly: true;
|
|
33
|
+
readonly publicSafe: true;
|
|
34
|
+
}
|
|
35
|
+
export declare function acceptRootQaScanDiscoveredPhase(projectRoot: string, id: string, input: unknown): RootQaScanDiscoveredPhaseV2;
|
|
36
|
+
export declare function readRootQaScanDiscoveredPhase(projectRoot: string, id: string, input: unknown): RootQaScanDiscoveredPhaseV2;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{createHash as M}from"node:crypto";import{performance as O}from"node:perf_hooks";import{closeSync as R,constants as w,fstatSync as F,fsyncSync as H,openSync as k,readFileSync as Q,writeFileSync as V}from"node:fs";import{dirname as P,join as W,resolve as b}from"node:path";import{chromium as X}from"playwright-core";import{aggregateRootQaScanReviews as Y,planRootQaScan as z,rootQaScanEvidenceDirectory as G}from"./qa-scan-review.js";const D=/^[a-f0-9]{64}$/,A=16,U=1024,B=32768,K=60*6e4,Z=500,ee=2e3,re=50;function S(r){return M("sha256").update(r).digest("hex")}function te(r){return r!==null&&typeof r=="object"&&!Array.isArray(r)}function _(r,o){return te(r)&&Object.keys(r).sort().join("\0")===[...o].sort().join("\0")}function L(r,o,e){if(!D.test(e))throw new Error("qa-scan-discovery-unverified");return W(G(r),`${o}-${e}.json`)}function C(r,o){const e=Buffer.from(`${JSON.stringify(o)}
|
|
2
|
+
`),a=k(r,w.O_WRONLY|w.O_CREAT|w.O_EXCL|(w.O_NOFOLLOW??0),384);try{V(a,e),H(a)}finally{R(a)}const s=k(P(r),w.O_RDONLY|(w.O_DIRECTORY??0));try{H(s)}finally{R(s)}}function T(r,o){const e=k(r,w.O_RDONLY|(w.O_NOFOLLOW??0));let a,s;try{const n=F(e);if(!n.isFile()||n.uid!==process.getuid?.()||(n.mode&63)!==0||n.size<1||n.size>o)throw new Error("qa-scan-discovery-unverified");s=n.size,a=Q(e)}finally{R(e)}if(a.length!==s||a.length>o)throw new Error("qa-scan-discovery-unverified");const t=JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(a));if(!a.equals(Buffer.from(`${JSON.stringify(t)}
|
|
3
|
+
`)))throw new Error("qa-scan-discovery-unverified");return t}function I(r,o){return Object.freeze({schemaVersion:2,kind:"root-qa-scan-link-discovery",sourceHead:r.sourceHead,seedSha256:r.seedSha256,discoverySha256:o,targetCount:r.targetUrls.length,inventoryScope:"reachable-rendered-same-origin-links",qaAccepted:!1,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}async function oe(r){let o,e=O.now();const a=e+ee;for(;O.now()<=a;){const s=await r.locator("a[href]").evaluateAll(n=>n.map(f=>f.href)),t=JSON.stringify(s);if(t!==o)o=t,e=O.now();else if(O.now()-e>=Z)return s;await r.waitForTimeout(re)}throw new Error("qa-scan-discovery-scope-unverified")}async function ue(r){if(!r.authorizeDiscovery)throw new Error("qa-scan-discovery-authorization-required");const o=b(r.projectRoot),e=z(o,"discovery",r.seedUrl),a=new URL(r.seedUrl);if(Buffer.byteLength(a.href)>U)throw new Error("qa-scan-discovery-scope-too-large");let s;try{s=await X.launch({channel:"chrome",headless:!0,timeout:15e3})}catch{throw new Error("qa-scan-discovery-browser-unavailable")}try{const t=await s.newContext({serviceWorkers:"block",acceptDownloads:!1,permissions:[],ignoreHTTPSErrors:!1});let n=0,f=0,g=0,h=0;await t.route("**/*",async i=>{let y=!1;try{const p=i.request(),q=new URL(p.url());y=q.protocol==="http:"&&q.origin===a.origin&&(p.method()==="GET"||p.method()==="HEAD")}catch{}y?await i.continue():(n++,await i.abort("blockedbyclient"))}),await t.routeWebSocket(/.*/,async i=>{n++,await i.close({code:1008,reason:"blocked"})});const c=await t.newPage();t.on("page",i=>{i!==c&&(h++,i.close())}),c.on("framenavigated",i=>{i===c.mainFrame()&&i.url()!=="about:blank"&&g++}),c.on("requestfailed",()=>{f++}),c.on("response",i=>{i.status()>=400&&f++});const v=new Set([a.href]),l=new Set;let J=0;for(;l.size<v.size;){const i=[...v].filter(u=>!l.has(u)).sort((u,d)=>Buffer.from(u).compare(Buffer.from(d)))[0];if(!i||v.size>A)throw new Error("qa-scan-discovery-scope-too-large");let y;try{y=await c.goto(i,{waitUntil:"load",timeout:15e3})}catch{throw new Error("qa-scan-discovery-target-unavailable")}if(!y||y.status()>=400||c.url()!==i||g!==l.size+1||n!==0||f!==0||h!==0)throw new Error("qa-scan-discovery-target-unavailable");const p=await oe(c);if(J+=p.length,J>256)throw new Error("qa-scan-discovery-scope-too-large");for(const u of p){if(typeof u!="string"||Buffer.byteLength(u)>U)throw new Error("qa-scan-discovery-scope-too-large");let d;try{d=new URL(u)}catch{throw new Error("qa-scan-discovery-target-unavailable")}if(!(d.origin!==a.origin||d.protocol!=="http:")){if(d.username||d.password||d.search||d.hash)throw new Error("qa-scan-discovery-target-unavailable");if(v.add(d.href),v.size>A)throw new Error("qa-scan-discovery-scope-too-large")}}const q=await c.locator("a[href]").evaluateAll(u=>u.map(d=>d.href));if(JSON.stringify(q)!==JSON.stringify(p)||c.url()!==i||g!==l.size+1||n!==0||f!==0||h!==0)throw new Error("qa-scan-discovery-scope-unverified");l.add(i)}const N=[...v].sort((i,y)=>Buffer.from(i).compare(Buffer.from(y)));if(N.length<2||N.length>A||l.size!==v.size||g!==l.size||n!==0||f!==0||h!==0)throw new Error("qa-scan-discovery-scope-unverified");if(z(o,"discovery",r.seedUrl).planSha256!==e.planSha256)throw new Error("qa-scan-discovery-source-drift");const m={schemaVersion:2,sourceHead:e.sourceHead,seedUrl:a.href,seedSha256:e.targetSha256,capturedAt:new Date().toISOString(),targetUrls:N,pageCount:l.size},E=S(JSON.stringify(m)),j=L(o,"discovery",E);if(Buffer.byteLength(JSON.stringify({...m,discoverySha256:E}))+1>B)throw new Error("qa-scan-discovery-scope-too-large");return C(j,{...m,discoverySha256:E}),I(m,E)}catch(t){throw t instanceof Error&&t.message.startsWith("qa-scan-discovery-")?t:new Error("qa-scan-discovery-unverified")}finally{await s.close().catch(()=>{})}}function x(r,o){const e=T(L(r,"discovery",o),B);if(!_(e,["schemaVersion","sourceHead","seedUrl","seedSha256","capturedAt","targetUrls","pageCount","discoverySha256"])||e.schemaVersion!==2||e.discoverySha256!==o||typeof e.seedUrl!="string"||typeof e.sourceHead!="string"||typeof e.seedSha256!="string"||Buffer.byteLength(e.seedUrl)>U||!D.test(e.seedSha256)||!Array.isArray(e.targetUrls)||e.targetUrls.length<2||e.targetUrls.length>A||e.pageCount!==e.targetUrls.length||typeof e.capturedAt!="string")throw new Error("qa-scan-discovery-unverified");const a=Date.now()-Date.parse(e.capturedAt);if(!Number.isFinite(a)||new Date(e.capturedAt).toISOString()!==e.capturedAt||a<-5*6e4||a>K)throw new Error("qa-scan-discovery-unverified");const s=z(r,"discovery",e.seedUrl);if(e.sourceHead!==s.sourceHead||e.seedSha256!==s.targetSha256)throw new Error("qa-scan-discovery-unverified");const t=new URL(e.seedUrl),n=e.targetUrls;if(!n.every(h=>typeof h=="string"&&Buffer.byteLength(h)<=U&&(()=>{try{const c=new URL(h);return c.origin===t.origin&&c.protocol==="http:"&&!c.username&&!c.password&&!c.search&&!c.hash&&c.href===h}catch{return!1}})())||!n.includes(t.href)||JSON.stringify(n)!==JSON.stringify([...new Set(n)].sort((h,c)=>Buffer.from(String(h)).compare(Buffer.from(String(c))))))throw new Error("qa-scan-discovery-unverified");const{discoverySha256:f,...g}=e;if(S(JSON.stringify(g))!==o)throw new Error("qa-scan-discovery-unverified");return g}function le(r,o){try{return I(x(b(r),o),o)}catch{throw new Error("qa-scan-discovery-unverified")}}function $(r,o,e){if(!_(e,["schemaVersion","targets"])||e.schemaVersion!==1||!Array.isArray(e.targets))throw new Error("qa-scan-discovery-phase-unverified");const a=x(r,o),s=e.targets.map(n=>{if(!_(n,["surface","targetUrl"])||typeof n.targetUrl!="string")throw new Error("qa-scan-discovery-phase-unverified");return n.targetUrl}).sort((n,f)=>Buffer.from(n).compare(Buffer.from(f)));if(JSON.stringify(s)!==JSON.stringify(a.targetUrls))throw new Error("qa-scan-discovery-phase-unverified");const t=Y(r,e);if(t.sourceHead!==a.sourceHead||t.qaAccepted&&(t.verdict!=="approved"||t.issueCount!==0)||!t.qaAccepted&&(t.verdict!=="issues-found"||t.issueCount<1))throw new Error("qa-scan-discovery-phase-unverified");return Object.freeze({schemaVersion:2,kind:"root-qa-scan-discovered-phase-checkpoint",sourceHead:a.sourceHead,discoverySha256:o,aggregateSha256:t.aggregateSha256,surfaceCount:t.surfaceCount,inventoryScope:"reachable-rendered-same-origin-links",qaAccepted:t.qaAccepted,workflowContinuationAuthorized:!1,localOnly:!0,publicSafe:!0})}function ye(r,o,e){try{const a=b(r),s=$(a,o,e),t={receipt:s,checksumSha256:S(`qa-scan-discovered-phase-v2\0${JSON.stringify(s)}`)},n=L(a,"discovered-phase",S(`${o}\0${s.aggregateSha256}`));try{C(n,t)}catch(f){if(f.code!=="EEXIST")throw f}if(JSON.stringify(ae(a,o,e))!==JSON.stringify(s))throw new Error("qa-scan-discovery-phase-unverified");return s}catch{throw new Error("qa-scan-discovery-phase-unverified")}}function ae(r,o,e){try{const a=b(r),s=$(a,o,e),t=T(L(a,"discovered-phase",S(`${o}\0${s.aggregateSha256}`)),4096);if(!_(t,["receipt","checksumSha256"])||t.checksumSha256!==S(`qa-scan-discovered-phase-v2\0${JSON.stringify(t.receipt)}`)||JSON.stringify(t.receipt)!==JSON.stringify(s))throw new Error("qa-scan-discovery-phase-unverified");return s}catch{throw new Error("qa-scan-discovery-phase-unverified")}}export{ye as acceptRootQaScanDiscoveredPhase,ue as discoverRootQaScanLinks,ae as readRootQaScanDiscoveredPhase,le as readRootQaScanDiscovery};
|