audit-tools 0.49.0 → 0.50.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/audit/cli/args.d.ts +27 -0
- package/dist/audit/cli/args.d.ts.map +1 -1
- package/dist/audit/cli/args.js +32 -2
- package/dist/audit/cli/args.js.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.d.ts +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.js +32 -55
- package/dist/audit/cli/dispatch/hostHandoff.js.map +1 -1
- package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
- package/dist/audit/cli/nextStepHelpers.js +8 -8
- package/dist/audit/cli/nextStepHelpers.js.map +1 -1
- package/dist/audit/io/artifacts.d.ts +0 -28
- package/dist/audit/io/artifacts.d.ts.map +1 -1
- package/dist/audit/io/artifacts.js +1 -65
- package/dist/audit/io/artifacts.js.map +1 -1
- package/dist/audit/orchestrator/advance.d.ts.map +1 -1
- package/dist/audit/orchestrator/advance.js +12 -10
- package/dist/audit/orchestrator/advance.js.map +1 -1
- package/dist/audit/orchestrator/designReviewSnapshot.d.ts.map +1 -1
- package/dist/audit/orchestrator/designReviewSnapshot.js +2 -5
- package/dist/audit/orchestrator/designReviewSnapshot.js.map +1 -1
- package/dist/audit/reporting/mergeFindings.d.ts.map +1 -1
- package/dist/audit/reporting/mergeFindings.js +1 -2
- package/dist/audit/reporting/mergeFindings.js.map +1 -1
- package/dist/remediate/contractPipeline/cyclicSeamResolution.d.ts +25 -10
- package/dist/remediate/contractPipeline/cyclicSeamResolution.d.ts.map +1 -1
- package/dist/remediate/contractPipeline/cyclicSeamResolution.js +35 -88
- package/dist/remediate/contractPipeline/cyclicSeamResolution.js.map +1 -1
- package/dist/remediate/contractPipeline/idRegistry.d.ts +10 -20
- package/dist/remediate/contractPipeline/idRegistry.d.ts.map +1 -1
- package/dist/remediate/contractPipeline/idRegistry.js +10 -24
- package/dist/remediate/contractPipeline/idRegistry.js.map +1 -1
- package/dist/remediate/contractPipeline/obligationKinds.d.ts +27 -0
- package/dist/remediate/contractPipeline/obligationKinds.d.ts.map +1 -0
- package/dist/remediate/contractPipeline/obligationKinds.js +18 -0
- package/dist/remediate/contractPipeline/obligationKinds.js.map +1 -0
- package/dist/remediate/dedup/crossLensDedup.d.ts +2 -3
- package/dist/remediate/dedup/crossLensDedup.d.ts.map +1 -1
- package/dist/remediate/dedup/crossLensDedup.js +2 -4
- package/dist/remediate/dedup/crossLensDedup.js.map +1 -1
- package/dist/remediate/index.d.ts +38 -8
- package/dist/remediate/index.d.ts.map +1 -1
- package/dist/remediate/index.js +75 -27
- package/dist/remediate/index.js.map +1 -1
- package/dist/remediate/steps/contractPipeline.d.ts +6 -62
- package/dist/remediate/steps/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/steps/contractPipeline.js +11 -80
- package/dist/remediate/steps/contractPipeline.js.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.js +57 -48
- package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
- package/dist/remediate/steps/nextStep.d.ts +83 -2
- package/dist/remediate/steps/nextStep.d.ts.map +1 -1
- package/dist/remediate/steps/nextStep.js +79 -11
- package/dist/remediate/steps/nextStep.js.map +1 -1
- package/dist/remediate/steps/types.d.ts +1 -1
- package/dist/remediate/steps/types.d.ts.map +1 -1
- package/dist/remediate/steps/types.js.map +1 -1
- package/dist/remediate/utils/fileIntegrity.d.ts.map +1 -1
- package/dist/remediate/utils/fileIntegrity.js +123 -30
- package/dist/remediate/utils/fileIntegrity.js.map +1 -1
- package/dist/remediate/validation/artifacts.d.ts.map +1 -1
- package/dist/remediate/validation/artifacts.js +3 -9
- package/dist/remediate/validation/artifacts.js.map +1 -1
- package/dist/remediate/validation/contractPipeline.d.ts +1 -1
- package/dist/remediate/validation/contractPipeline.d.ts.map +1 -1
- package/dist/remediate/validation/contractPipeline.js +8 -46
- package/dist/remediate/validation/contractPipeline.js.map +1 -1
- package/dist/remediate/validation/contractPipelineGates.d.ts +12 -20
- package/dist/remediate/validation/contractPipelineGates.d.ts.map +1 -1
- package/dist/remediate/validation/contractPipelineGates.js +107 -135
- package/dist/remediate/validation/contractPipelineGates.js.map +1 -1
- package/dist/shared/engine/obligationEngine.d.ts +32 -0
- package/dist/shared/engine/obligationEngine.d.ts.map +1 -1
- package/dist/shared/engine/obligationEngine.js +29 -0
- package/dist/shared/engine/obligationEngine.js.map +1 -1
- package/dist/shared/findings/dedupe.d.ts +4 -11
- package/dist/shared/findings/dedupe.d.ts.map +1 -1
- package/dist/shared/findings/dedupe.js +196 -163
- package/dist/shared/findings/dedupe.js.map +1 -1
- package/dist/shared/graph/directedCycles.d.ts +61 -0
- package/dist/shared/graph/directedCycles.d.ts.map +1 -0
- package/dist/shared/graph/directedCycles.js +190 -0
- package/dist/shared/graph/directedCycles.js.map +1 -0
- package/dist/shared/index.d.ts +6 -3
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +4 -2
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/io/repoRoot.d.ts +45 -0
- package/dist/shared/io/repoRoot.d.ts.map +1 -1
- package/dist/shared/io/repoRoot.js +91 -1
- package/dist/shared/io/repoRoot.js.map +1 -1
- package/dist/shared/submission/submissionScan.d.ts +86 -0
- package/dist/shared/submission/submissionScan.d.ts.map +1 -0
- package/dist/shared/submission/submissionScan.js +93 -0
- package/dist/shared/submission/submissionScan.js.map +1 -0
- package/dist/shared/types/obligationLedger.d.ts +7 -3
- package/dist/shared/types/obligationLedger.d.ts.map +1 -1
- package/dist/shared/types/obligationLedger.js +9 -37
- package/dist/shared/types/obligationLedger.js.map +1 -1
- package/package.json +3 -2
- package/skills/audit-code/audit-code.prompt.md +5 -2
- package/wrapper/audit-code-wrapper-install-hosts.mjs +8 -3
- package/wrapper/audit-code-wrapper-lib.mjs +10 -3
- package/wrapper/installer-verb-help.mjs +3 -1
- package/wrapper/remediate-code-wrapper-install-hosts.mjs +8 -3
- package/wrapper/repo-root.mjs +80 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE per-item host-submission scan both ingests draw from.
|
|
3
|
+
*
|
|
4
|
+
* Every submitted work item crosses the same four-step gate before any draw's
|
|
5
|
+
* domain work begins: resolve the bound path inside the boundary, read the
|
|
6
|
+
* bytes, classify a missing / unparseable / contract-invalid document, and
|
|
7
|
+
* refuse a `result_id` the caller has already seen. Both twins hand-rolled that
|
|
8
|
+
* sequence — audit in `readSubmittedResult` plus an inline duplicate check,
|
|
9
|
+
* remediate inline in its ingest loop — and the two copies had already drifted
|
|
10
|
+
* on CONTAINMENT: remediate re-checked the resolved path under the artifacts
|
|
11
|
+
* dir, audit checked the root only. This module resolves that in the stricter
|
|
12
|
+
* direction for BOTH draws.
|
|
13
|
+
*
|
|
14
|
+
* What it deliberately does NOT own — because these are the places the draws
|
|
15
|
+
* genuinely differ, not places they drifted:
|
|
16
|
+
*
|
|
17
|
+
* - the MESSAGE TEXT of each refusal (a per-draw renderer supplies it; the two
|
|
18
|
+
* vocabularies are test-pinned and address different readers);
|
|
19
|
+
* - the DOMAIN PARSER (a callback; each draw's parser stays standalone, so the
|
|
20
|
+
* hand-recovery lane can wrap `parseResult` directly and stay parity-pinned);
|
|
21
|
+
* - identity enforcement, which lives INSIDE those parsers — hoisting it would
|
|
22
|
+
* change refusal precedence for a doubly-bad document;
|
|
23
|
+
* - loop iteration, aggregation order, seen-set seeding, and the TIMING of the
|
|
24
|
+
* duplicate consume. Audit seeds from its persisted accepted ledger and
|
|
25
|
+
* consumes only after conversion, validation and grounding; remediate uses a
|
|
26
|
+
* fresh per-call set and consumes at parse. This scan only CHECKS.
|
|
27
|
+
*/
|
|
28
|
+
import { type SubmissionIssue } from "./submissionClassifier.js";
|
|
29
|
+
/**
|
|
30
|
+
* The domain parser's answer, normalized. `detail` is handed straight to the
|
|
31
|
+
* draw's `contractInvalid` renderer — the scan never composes refusal prose of
|
|
32
|
+
* its own.
|
|
33
|
+
*/
|
|
34
|
+
export type SubmissionParse<TParsed> = {
|
|
35
|
+
readonly ok: true;
|
|
36
|
+
readonly parsed: TParsed;
|
|
37
|
+
} | {
|
|
38
|
+
readonly ok: false;
|
|
39
|
+
readonly detail: string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The per-draw refusal vocabulary. Text is a DRAW concern: the audit ingest
|
|
43
|
+
* addresses a host repairing a bound result file, remediate addresses one
|
|
44
|
+
* repairing a pending work item, and several strings are pinned by tests.
|
|
45
|
+
*/
|
|
46
|
+
export interface SubmissionScanMessages {
|
|
47
|
+
/** Nothing exists at the bound path. */
|
|
48
|
+
readonly missing: () => string;
|
|
49
|
+
/** Bytes exist but are not JSON; `detail` is the parse/IO reason. */
|
|
50
|
+
readonly malformed: (detail: string) => string;
|
|
51
|
+
/** JSON that the domain parser refused; `detail` is its stated reason. */
|
|
52
|
+
readonly contractInvalid: (detail: string) => string;
|
|
53
|
+
/** The parsed result re-uses an identity the caller has already seen. */
|
|
54
|
+
readonly duplicate: (resultId: string) => string;
|
|
55
|
+
}
|
|
56
|
+
/** Either the parsed submission, or the ONE issue that names why not. */
|
|
57
|
+
export type SubmissionScanOutcome<TParsed> = {
|
|
58
|
+
readonly ok: true;
|
|
59
|
+
readonly parsed: TParsed;
|
|
60
|
+
} | {
|
|
61
|
+
readonly ok: false;
|
|
62
|
+
readonly issue: SubmissionIssue;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Scan one bound submission. Throws only where the twins already threw: a bound
|
|
66
|
+
* path that escapes the repository root or the artifacts dir is a boundary
|
|
67
|
+
* violation, not a classified submission failure.
|
|
68
|
+
*/
|
|
69
|
+
export declare function scanBoundSubmission<TParsed>(params: {
|
|
70
|
+
/** Absolute repository root the bound path is expressed relative to. */
|
|
71
|
+
readonly root: string;
|
|
72
|
+
/** Absolute artifacts dir the submission must also stay beneath. */
|
|
73
|
+
readonly artifactsDir: string;
|
|
74
|
+
/** Names the item in the containment refusal and in both issue locators. */
|
|
75
|
+
readonly workItemId: string;
|
|
76
|
+
/** Repository-relative bound path, as the persisted contract declares it. */
|
|
77
|
+
readonly resultPath: string;
|
|
78
|
+
/** The draw's standalone contract gate. */
|
|
79
|
+
readonly parse: (value: unknown) => SubmissionParse<TParsed>;
|
|
80
|
+
/** The submitted identity the duplicate check is asked about. */
|
|
81
|
+
readonly resultId: (parsed: TParsed) => string;
|
|
82
|
+
/** Caller-owned predicate; seeding and consume timing stay per-draw. */
|
|
83
|
+
readonly seen: (resultId: string) => boolean;
|
|
84
|
+
readonly messages: SubmissionScanMessages;
|
|
85
|
+
}): Promise<SubmissionScanOutcome<TParsed>>;
|
|
86
|
+
//# sourceMappingURL=submissionScan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submissionScan.d.ts","sourceRoot":"","sources":["../../../src/shared/submission/submissionScan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,2BAA2B,CAAC;AAGnC;;;;GAIG;AACH,MAAM,MAAM,eAAe,CAAC,OAAO,IAC/B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAC/C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,wCAAwC;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,MAAM,CAAC;IAC/B,qEAAqE;IACrE,QAAQ,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C,0EAA0E;IAC1E,QAAQ,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACrD,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;CAClD;AAED,yEAAyE;AACzE,MAAM,MAAM,qBAAqB,CAAC,OAAO,IACrC;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAC/C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAA;CAAE,CAAC;AAE5D;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE;IACzD,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,eAAe,CAAC,OAAO,CAAC,CAAC;IAC7D,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,MAAM,CAAC;IAC/C,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;CAC3C,GAAG,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CA2D1C"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE per-item host-submission scan both ingests draw from.
|
|
3
|
+
*
|
|
4
|
+
* Every submitted work item crosses the same four-step gate before any draw's
|
|
5
|
+
* domain work begins: resolve the bound path inside the boundary, read the
|
|
6
|
+
* bytes, classify a missing / unparseable / contract-invalid document, and
|
|
7
|
+
* refuse a `result_id` the caller has already seen. Both twins hand-rolled that
|
|
8
|
+
* sequence — audit in `readSubmittedResult` plus an inline duplicate check,
|
|
9
|
+
* remediate inline in its ingest loop — and the two copies had already drifted
|
|
10
|
+
* on CONTAINMENT: remediate re-checked the resolved path under the artifacts
|
|
11
|
+
* dir, audit checked the root only. This module resolves that in the stricter
|
|
12
|
+
* direction for BOTH draws.
|
|
13
|
+
*
|
|
14
|
+
* What it deliberately does NOT own — because these are the places the draws
|
|
15
|
+
* genuinely differ, not places they drifted:
|
|
16
|
+
*
|
|
17
|
+
* - the MESSAGE TEXT of each refusal (a per-draw renderer supplies it; the two
|
|
18
|
+
* vocabularies are test-pinned and address different readers);
|
|
19
|
+
* - the DOMAIN PARSER (a callback; each draw's parser stays standalone, so the
|
|
20
|
+
* hand-recovery lane can wrap `parseResult` directly and stay parity-pinned);
|
|
21
|
+
* - identity enforcement, which lives INSIDE those parsers — hoisting it would
|
|
22
|
+
* change refusal precedence for a doubly-bad document;
|
|
23
|
+
* - loop iteration, aggregation order, seen-set seeding, and the TIMING of the
|
|
24
|
+
* duplicate consume. Audit seeds from its persisted accepted ledger and
|
|
25
|
+
* consumes only after conversion, validation and grounding; remediate uses a
|
|
26
|
+
* fresh per-call set and consumes at parse. This scan only CHECKS.
|
|
27
|
+
*/
|
|
28
|
+
// Sibling modules imported DIRECTLY, never through ../index.js: the barrel
|
|
29
|
+
// re-exports this module, so importing the barrel here would close a cycle
|
|
30
|
+
// (index → submissionScan → index) that check:depgraph refuses.
|
|
31
|
+
import { readSubmissionDocument, } from "./submissionClassifier.js";
|
|
32
|
+
import { resolveContainedPath } from "./submissionIdentity.js";
|
|
33
|
+
/**
|
|
34
|
+
* Scan one bound submission. Throws only where the twins already threw: a bound
|
|
35
|
+
* path that escapes the repository root or the artifacts dir is a boundary
|
|
36
|
+
* violation, not a classified submission failure.
|
|
37
|
+
*/
|
|
38
|
+
export async function scanBoundSubmission(params) {
|
|
39
|
+
const locators = {
|
|
40
|
+
work_item_id: params.workItemId,
|
|
41
|
+
result_path: params.resultPath,
|
|
42
|
+
};
|
|
43
|
+
const label = `result path for ${params.workItemId}`;
|
|
44
|
+
const absolutePath = resolveContainedPath(params.root, params.resultPath, label);
|
|
45
|
+
// Both boundaries, both draws: a submission lives under the artifacts dir,
|
|
46
|
+
// and root containment alone would admit any other tracked file in the repo.
|
|
47
|
+
resolveContainedPath(params.artifactsDir, absolutePath, label);
|
|
48
|
+
const read = await readSubmissionDocument(absolutePath);
|
|
49
|
+
if (read.kind === "missing") {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
issue: {
|
|
53
|
+
code: "submission_missing",
|
|
54
|
+
message: params.messages.missing(),
|
|
55
|
+
...locators,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (read.kind === "malformed") {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
issue: {
|
|
63
|
+
code: "submission_malformed",
|
|
64
|
+
message: params.messages.malformed(read.detail),
|
|
65
|
+
...locators,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const parsed = params.parse(read.value);
|
|
70
|
+
if (!parsed.ok) {
|
|
71
|
+
return {
|
|
72
|
+
ok: false,
|
|
73
|
+
issue: {
|
|
74
|
+
code: "submission_contract_invalid",
|
|
75
|
+
message: params.messages.contractInvalid(parsed.detail),
|
|
76
|
+
...locators,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const resultId = params.resultId(parsed.parsed);
|
|
81
|
+
if (params.seen(resultId)) {
|
|
82
|
+
return {
|
|
83
|
+
ok: false,
|
|
84
|
+
issue: {
|
|
85
|
+
code: "duplicate_submission_id",
|
|
86
|
+
message: params.messages.duplicate(resultId),
|
|
87
|
+
...locators,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return { ok: true, parsed: parsed.parsed };
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=submissionScan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submissionScan.js","sourceRoot":"","sources":["../../../src/shared/submission/submissionScan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,2EAA2E;AAC3E,2EAA2E;AAC3E,gEAAgE;AAChE,OAAO,EACL,sBAAsB,GAEvB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAgC/D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAU,MAgBlD;IACC,MAAM,QAAQ,GAAG;QACf,YAAY,EAAE,MAAM,CAAC,UAAU;QAC/B,WAAW,EAAE,MAAM,CAAC,UAAU;KACtB,CAAC;IACX,MAAM,KAAK,GAAG,mBAAmB,MAAM,CAAC,UAAU,EAAE,CAAC;IACrD,MAAM,YAAY,GAAG,oBAAoB,CACvC,MAAM,CAAC,IAAI,EACX,MAAM,CAAC,UAAU,EACjB,KAAK,CACN,CAAC;IACF,2EAA2E;IAC3E,6EAA6E;IAC7E,oBAAoB,CAAC,MAAM,CAAC,YAAY,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;IAE/D,MAAM,IAAI,GAAG,MAAM,sBAAsB,CAAC,YAAY,CAAC,CAAC;IACxD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE;gBACL,IAAI,EAAE,oBAAoB;gBAC1B,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE;gBAClC,GAAG,QAAQ;aACZ;SACF,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC9B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE;gBACL,IAAI,EAAE,sBAAsB;gBAC5B,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;gBAC/C,GAAG,QAAQ;aACZ;SACF,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE;gBACL,IAAI,EAAE,6BAA6B;gBACnC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC;gBACvD,GAAG,QAAQ;aACZ;SACF,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAChD,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE;gBACL,IAAI,EAAE,yBAAyB;gBAC/B,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,CAAC;gBAC5C,GAAG,QAAQ;aACZ;SACF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;AAC7C,CAAC"}
|
|
@@ -8,9 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { ObligationEntry, ObligationLedger } from "./contractPipeline.js";
|
|
10
10
|
/**
|
|
11
|
-
* Detect cycles in an obligation dependency graph
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Detect cycles in an obligation dependency graph. Returns the IDs of
|
|
12
|
+
* obligations forming the first cycle detected as a witness path with its start
|
|
13
|
+
* repeated at the end (`["A","B","A"]`), or null when the graph is acyclic.
|
|
14
|
+
*
|
|
15
|
+
* A thin draw over the shared directed-cycle core: an obligation that depends
|
|
16
|
+
* on ITSELF is a cycle here (self-loops included), and dependencies naming an
|
|
17
|
+
* id outside the ledger are external references the core ignores.
|
|
14
18
|
*
|
|
15
19
|
* INV-shared-core-07: called at construction time so a cycle is caught early
|
|
16
20
|
* rather than causing an infinite loop or confusing error at scheduling time.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"obligationLedger.d.ts","sourceRoot":"","sources":["../../../src/shared/types/obligationLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EACjB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"obligationLedger.d.ts","sourceRoot":"","sources":["../../../src/shared/types/obligationLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EACjB,MAAM,uBAAuB,CAAC;AAM/B;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,SAAS,eAAe,EAAE,GACtC,MAAM,EAAE,GAAG,IAAI,CAEjB;AAED,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,gBAAgB,CAgBlB"}
|
|
@@ -7,49 +7,21 @@
|
|
|
7
7
|
* if a dependency cycle is present.
|
|
8
8
|
*/
|
|
9
9
|
import { CONTRACT_PIPELINE_OBLIGATION_LEDGER_VERSION, } from "./contractPipeline.js";
|
|
10
|
+
import { findFirstCycleWitness } from "../graph/directedCycles.js";
|
|
10
11
|
/**
|
|
11
|
-
* Detect cycles in an obligation dependency graph
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Detect cycles in an obligation dependency graph. Returns the IDs of
|
|
13
|
+
* obligations forming the first cycle detected as a witness path with its start
|
|
14
|
+
* repeated at the end (`["A","B","A"]`), or null when the graph is acyclic.
|
|
15
|
+
*
|
|
16
|
+
* A thin draw over the shared directed-cycle core: an obligation that depends
|
|
17
|
+
* on ITSELF is a cycle here (self-loops included), and dependencies naming an
|
|
18
|
+
* id outside the ledger are external references the core ignores.
|
|
14
19
|
*
|
|
15
20
|
* INV-shared-core-07: called at construction time so a cycle is caught early
|
|
16
21
|
* rather than causing an infinite loop or confusing error at scheduling time.
|
|
17
22
|
*/
|
|
18
23
|
export function detectObligationCycle(obligations) {
|
|
19
|
-
|
|
20
|
-
const depMap = new Map();
|
|
21
|
-
for (const o of obligations) {
|
|
22
|
-
depMap.set(o.id, o.depends_on.filter((dep) => idSet.has(dep)));
|
|
23
|
-
}
|
|
24
|
-
// Three-color DFS: 0 = unvisited, 1 = in-stack (gray), 2 = done (black).
|
|
25
|
-
const color = new Map();
|
|
26
|
-
const path = [];
|
|
27
|
-
function dfs(id) {
|
|
28
|
-
const c = color.get(id) ?? 0;
|
|
29
|
-
if (c === 2)
|
|
30
|
-
return null; // already done
|
|
31
|
-
if (c === 1) {
|
|
32
|
-
// Cycle detected: return the cycle slice.
|
|
33
|
-
const cycleStart = path.indexOf(id);
|
|
34
|
-
return path.slice(cycleStart).concat(id);
|
|
35
|
-
}
|
|
36
|
-
color.set(id, 1);
|
|
37
|
-
path.push(id);
|
|
38
|
-
for (const dep of depMap.get(id) ?? []) {
|
|
39
|
-
const cycle = dfs(dep);
|
|
40
|
-
if (cycle)
|
|
41
|
-
return cycle;
|
|
42
|
-
}
|
|
43
|
-
path.pop();
|
|
44
|
-
color.set(id, 2);
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
for (const o of obligations) {
|
|
48
|
-
const cycle = dfs(o.id);
|
|
49
|
-
if (cycle)
|
|
50
|
-
return cycle;
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
24
|
+
return findFirstCycleWitness(obligations, { includeSelfLoops: true });
|
|
53
25
|
}
|
|
54
26
|
/**
|
|
55
27
|
* Build a validated ObligationLedger.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"obligationLedger.js","sourceRoot":"","sources":["../../../src/shared/types/obligationLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,EACL,2CAA2C,GAC5C,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"obligationLedger.js","sourceRoot":"","sources":["../../../src/shared/types/obligationLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,EACL,2CAA2C,GAC5C,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CACnC,WAAuC;IAEvC,OAAO,qBAAqB,CAAC,WAAW,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC;AACxE,CAAC;AAQD;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAqC;IAErC,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAErD,MAAM,KAAK,GAAG,qBAAqB,CAAC,WAAW,CAAC,CAAC;IACjD,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACb,wFAAwF,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAC5G,CAAC;IACJ,CAAC;IAED,OAAO;QACL,gBAAgB,EAAE,2CAA2C;QAC7D,OAAO;QACP,WAAW;QACX,UAAU,EAAE,UAAU,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACnD,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "audit-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"description": "Provider-neutral code auditing and remediation workflows for arbitrary repositories.",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"test": "npm run build && node scripts/shared/run-vitest-gate.mjs",
|
|
52
52
|
"test:single": "npm run build && node scripts/shared/run-vitest-gate.mjs",
|
|
53
53
|
"verify:guards": "node scripts/shared/run-vitest-gate.mjs --retry=2 --exclude \"**/audit-code-completion-*.test.ts\" --exclude \"**/audit-code-wrapper-*.test.ts\" --exclude \"**/audit/next-step-core-*.test.ts\" --exclude \"**/seam-host-only-next-step.test.ts\" --exclude \"**/*-e2e.test.*\" --exclude \"**/next-step-implement-dispatch.test.ts\" --exclude \"**/next-step-pipeline-dispatch.test.ts\"",
|
|
54
|
-
"verify:checks": "node scripts/shared/profile-run.mjs verify-checks check:control-bytes check:shared-primitives check:version-gates check:guard-reach check:ci-trigger-paths check:offload-lanes check:loop-core-patterns check:constitutional-doc-paths check:runtime-artifact-names check:executor-producers check:cli-surface check:deadcode check:lint check:dup check:depgraph check:doc-manifest check:doc-links check:doc-code-citations check:gate-enumeration check:philosophy-brief check:readme-sample-report check:proposal-red-at check:nightly-routine-prompt check:handoff-roadmap check:backlog-index check:memory-citations check:backlog-budget check:backlog-status check:backlog-line-numbers check:tests build check:scripts verify:hosts verify:remediate-hosts pack:smoke smoke:packaged-audit-code smoke:packaged-remediate-code",
|
|
54
|
+
"verify:checks": "node scripts/shared/profile-run.mjs verify-checks check:control-bytes check:shared-primitives check:version-gates check:guard-reach check:ci-trigger-paths check:offload-lanes check:loop-core-patterns check:constitutional-doc-paths check:runtime-artifact-names check:executor-producers check:spec-mirrors check:cli-surface check:deadcode check:lint check:dup check:depgraph check:doc-manifest check:doc-links check:doc-code-citations check:gate-enumeration check:philosophy-brief check:readme-sample-report check:proposal-red-at check:nightly-routine-prompt check:handoff-roadmap check:backlog-index check:memory-citations check:backlog-budget check:backlog-status check:backlog-line-numbers check:tests build check:scripts verify:hosts verify:remediate-hosts pack:smoke smoke:packaged-audit-code smoke:packaged-remediate-code",
|
|
55
55
|
"verify:release": "npm run verify:checks && node scripts/shared/run-vitest-gate.mjs && npm run smoke:linked-audit-code && npm run smoke:linked-remediate-code",
|
|
56
56
|
"check:control-bytes": "node scripts/check-control-bytes.mjs",
|
|
57
57
|
"check:shared-primitives": "node scripts/check-shared-primitives.mjs",
|
|
@@ -101,6 +101,7 @@
|
|
|
101
101
|
"check:backlog-line-numbers": "node scripts/check-backlog-line-numbers.mjs",
|
|
102
102
|
"check:runtime-artifact-names": "node scripts/shared/generate-runtime-artifact-names.mjs --check",
|
|
103
103
|
"check:executor-producers": "node scripts/shared/generate-executor-producers.mjs --check",
|
|
104
|
+
"check:spec-mirrors": "node scripts/shared/generate-spec-mirrors.mjs --check",
|
|
104
105
|
"check:cli-surface": "node scripts/shared/generate-cli-surface.mjs --check",
|
|
105
106
|
"generate:shard-baseline": "node scripts/shared/generate-vitest-shard-baseline.mjs"
|
|
106
107
|
},
|
|
@@ -21,8 +21,11 @@ repository root.
|
|
|
21
21
|
|
|
22
22
|
Preserve user arguments:
|
|
23
23
|
|
|
24
|
-
-
|
|
25
|
-
|
|
24
|
+
- run `audit-code` from inside the target repository; every command resolves
|
|
25
|
+
that repository's root from the working directory on its own, so normal usage
|
|
26
|
+
passes no `--root`
|
|
27
|
+
- pass the user-supplied target directory with `--root <path>` only when running
|
|
28
|
+
from outside that repository
|
|
26
29
|
|
|
27
30
|
This is the one full statement of the target-directory rule; the `audit-code`
|
|
28
31
|
skill points here instead of restating it.
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
findLegacyAuditCodeSurfaceFiles,
|
|
20
20
|
removeLegacyAuditCodeSurfaceFiles,
|
|
21
21
|
} from './audit-code-wrapper-legacy.mjs';
|
|
22
|
+
import { resolveWrapperRoot } from './repo-root.mjs';
|
|
22
23
|
import {
|
|
23
24
|
renderVSCodeAgentFile,
|
|
24
25
|
renderCodexAutomationRecipe,
|
|
@@ -603,7 +604,11 @@ export async function writeAntigravityAssets(assetPaths, promptBody, skillSource
|
|
|
603
604
|
|
|
604
605
|
export async function installBootstrap(argv, options = {}) {
|
|
605
606
|
const host = (getFlag(argv, '--host') ?? DEFAULT_INSTALL_HOST).toLowerCase();
|
|
606
|
-
|
|
607
|
+
// An absent --root is the repository the caller is standing in, discovered
|
|
608
|
+
// the same way `next-step` discovers it — never the cwd verbatim, which would
|
|
609
|
+
// deposit a full set of host assets in whatever subdirectory the operator
|
|
610
|
+
// happened to be in.
|
|
611
|
+
const root = resolveWrapperRoot(getFlag(argv, '--root'));
|
|
607
612
|
await assertDirectoryExists(root, 'Target repository root');
|
|
608
613
|
const profile = getInstallProfile(host);
|
|
609
614
|
const promptSource = await readFile(promptAssetPath, 'utf8');
|
|
@@ -828,7 +833,7 @@ export async function verifyHostsIsolated(options = {}) {
|
|
|
828
833
|
}
|
|
829
834
|
|
|
830
835
|
export async function verifyInstalledBootstrap(argv) {
|
|
831
|
-
const root =
|
|
836
|
+
const root = resolveWrapperRoot(getFlag(argv, '--root'));
|
|
832
837
|
const requestedHost = getFlag(argv, '--host')?.toLowerCase() ?? null;
|
|
833
838
|
const installManifestPath = join(
|
|
834
839
|
root,
|
|
@@ -1118,7 +1123,7 @@ export async function detectBootstrapRefreshReason(root, host) {
|
|
|
1118
1123
|
|
|
1119
1124
|
export async function ensureBootstrap(argv) {
|
|
1120
1125
|
const host = (getFlag(argv, '--host') ?? DEFAULT_INSTALL_HOST).toLowerCase();
|
|
1121
|
-
const root =
|
|
1126
|
+
const root = resolveWrapperRoot(getFlag(argv, '--root'));
|
|
1122
1127
|
const quiet = hasFlag(argv, '--quiet');
|
|
1123
1128
|
const force = hasFlag(argv, '--force');
|
|
1124
1129
|
await assertDirectoryExists(root, 'Target repository root');
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
installerVerbHelp,
|
|
21
21
|
installerVerbSummaries,
|
|
22
22
|
} from './installer-verb-help.mjs';
|
|
23
|
+
import { resolveWrapperRoot } from './repo-root.mjs';
|
|
23
24
|
|
|
24
25
|
export { shouldBuildDistForPaths, assertWorkspaceInstalled };
|
|
25
26
|
export { _INSTALL_HOST_ORDER, _INSTALL_HOST_DEFINITIONS, _getInstallHostKeys, _getInstallProfile, _renderGeminiCommandToml };
|
|
@@ -263,7 +264,7 @@ function printHelp({ usageName, preferredEntrypoint }) {
|
|
|
263
264
|
'- status summarizes deterministic audit state and pending review work',
|
|
264
265
|
'',
|
|
265
266
|
'Defaults:',
|
|
266
|
-
'- --root .',
|
|
267
|
+
'- --root the repository the working directory is in (nearest ancestor owning .audit-tools/ or .git); pass it only to target a repository you are not inside',
|
|
267
268
|
'- --artifacts-dir <root>/.audit-tools/audit',
|
|
268
269
|
];
|
|
269
270
|
|
|
@@ -284,7 +285,11 @@ async function printPromptPath() {
|
|
|
284
285
|
|
|
285
286
|
async function runDistCommand(commandName, argv, { ensureArtifactsDir = false } = {}) {
|
|
286
287
|
const commandArgs = [...argv];
|
|
287
|
-
|
|
288
|
+
// An absent --root is DISCOVERED (nearest ancestor owning .audit-tools/.git),
|
|
289
|
+
// not defaulted to the caller's cwd verbatim: the wrapper normalizes the root
|
|
290
|
+
// to an absolute path before the child sees it (CE-001), so a cwd-verbatim
|
|
291
|
+
// default here would pin the wrong root no matter what dist resolves.
|
|
292
|
+
const rootValue = resolveWrapperRoot(getFlag(commandArgs, '--root'));
|
|
288
293
|
const artifactsDir = resolve(getFlag(commandArgs, '--artifacts-dir') ?? join(rootValue, '.audit-tools', 'audit'));
|
|
289
294
|
|
|
290
295
|
// Overwrite (not default) so a user-supplied relative value is normalized to
|
|
@@ -304,7 +309,9 @@ async function runDistCommand(commandName, argv, { ensureArtifactsDir = false }
|
|
|
304
309
|
|
|
305
310
|
async function runDistCommandInline(commandName, argv, { ensureArtifactsDir = false } = {}) {
|
|
306
311
|
const commandArgs = [...argv];
|
|
307
|
-
|
|
312
|
+
// Same discovery as runDistCommand — the two paths must not answer "which
|
|
313
|
+
// repository is this?" differently.
|
|
314
|
+
const rootValue = resolveWrapperRoot(getFlag(commandArgs, '--root'));
|
|
308
315
|
const artifactsDir = resolve(getFlag(commandArgs, '--artifacts-dir') ?? join(rootValue, '.audit-tools', 'audit'));
|
|
309
316
|
|
|
310
317
|
setFlag(commandArgs, '--root', rootValue);
|
|
@@ -97,7 +97,9 @@ export function installerVerbHelp(verb, { usageName, product }) {
|
|
|
97
97
|
...VERB_DETAIL[verb],
|
|
98
98
|
'',
|
|
99
99
|
'Common options:',
|
|
100
|
-
|
|
100
|
+
// NOT "(default: .)" — an absent --root is DISCOVERED (nearest ancestor
|
|
101
|
+
// owning .audit-tools/ or .git), not the cwd verbatim, on both bins.
|
|
102
|
+
'- --root PATH repository root (default: the repository the working directory is in)',
|
|
101
103
|
'- --quiet suppress output',
|
|
102
104
|
].join('\n');
|
|
103
105
|
}
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
findLegacyRemediateCodeSurfaceFiles,
|
|
22
22
|
removeLegacyRemediateCodeSurfaceFiles,
|
|
23
23
|
} from './remediate-code-wrapper-legacy.mjs';
|
|
24
|
+
import { resolveWrapperRoot } from './repo-root.mjs';
|
|
24
25
|
import {
|
|
25
26
|
renderVSCodeAgentFile,
|
|
26
27
|
renderCodexAutomationRecipe,
|
|
@@ -597,7 +598,11 @@ export async function writeAntigravityAssets(assetPaths, promptBody, skillSource
|
|
|
597
598
|
|
|
598
599
|
export async function installBootstrap(argv, options = {}) {
|
|
599
600
|
const host = (getFlag(argv, '--host') ?? DEFAULT_INSTALL_HOST).toLowerCase();
|
|
600
|
-
|
|
601
|
+
// An absent --root is the repository the caller is standing in, discovered
|
|
602
|
+
// the same way `next-step` discovers it — never the cwd verbatim, which would
|
|
603
|
+
// deposit a full set of host assets in whatever subdirectory the operator
|
|
604
|
+
// happened to be in.
|
|
605
|
+
const root = resolveWrapperRoot(getFlag(argv, '--root'));
|
|
601
606
|
await assertDirectoryExists(root, 'Target repository root');
|
|
602
607
|
const profile = getInstallProfile(host);
|
|
603
608
|
const promptSource = (await readFile(promptAssetPath, 'utf8')).replace(/\r\n/g, '\n');
|
|
@@ -864,7 +869,7 @@ export async function verifyHostsIsolated(options = {}) {
|
|
|
864
869
|
}
|
|
865
870
|
|
|
866
871
|
export async function verifyInstalledBootstrap(argv) {
|
|
867
|
-
const root =
|
|
872
|
+
const root = resolveWrapperRoot(getFlag(argv, '--root'));
|
|
868
873
|
const requestedHost = getFlag(argv, '--host')?.toLowerCase() ?? null;
|
|
869
874
|
const installManifestPath = join(
|
|
870
875
|
root,
|
|
@@ -1154,7 +1159,7 @@ export async function detectBootstrapRefreshReason(root, host) {
|
|
|
1154
1159
|
|
|
1155
1160
|
export async function ensureBootstrap(argv) {
|
|
1156
1161
|
const host = (getFlag(argv, '--host') ?? DEFAULT_INSTALL_HOST).toLowerCase();
|
|
1157
|
-
const root =
|
|
1162
|
+
const root = resolveWrapperRoot(getFlag(argv, '--root'));
|
|
1158
1163
|
const quiet = hasFlag(argv, '--quiet');
|
|
1159
1164
|
const force = hasFlag(argv, '--force');
|
|
1160
1165
|
await assertDirectoryExists(root, 'Target repository root');
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
4
|
+
|
|
5
|
+
// Canonical repo-root discovery for the WRAPPER side of BOTH bins.
|
|
6
|
+
//
|
|
7
|
+
// Bin-neutral by name and by ownership, like `installer-verb-help.mjs`: audit-code
|
|
8
|
+
// and remediate-code answer their installer verbs from this one module, so the two
|
|
9
|
+
// wrappers cannot resolve "no --root" differently (one core, two draws — never a
|
|
10
|
+
// per-bin fork of the same algorithm).
|
|
11
|
+
//
|
|
12
|
+
// This is a deliberate, pinned MIRROR of `discoverRepoRoot` / `climbOutOfAuditTools`
|
|
13
|
+
// in src/shared/io/repoRoot.ts, kept for the same bootstrap constraint that
|
|
14
|
+
// forces `quoteForCmd` to be mirrored here: the wrapper runs BEFORE dist exists
|
|
15
|
+
// (the installer verbs `ensure` / `install` / `verify-install` are answered
|
|
16
|
+
// entirely in the wrapper and must never trigger a build just to learn where
|
|
17
|
+
// they are), so it cannot import the TypeScript source or the built module.
|
|
18
|
+
// `tests/shared/wrapper-repo-root-parity.test.ts` runs BOTH implementations over
|
|
19
|
+
// the same fixture trees and fails if they ever disagree — the two copies cannot
|
|
20
|
+
// drift silently.
|
|
21
|
+
//
|
|
22
|
+
// Behaviour, stated once here and pinned by that parity test: climb out of any
|
|
23
|
+
// `.audit-tools/` the start dir sits inside, then walk up to the nearest
|
|
24
|
+
// ancestor carrying `.audit-tools` or `.git` (either may be a file — a linked
|
|
25
|
+
// worktree's `.git` is one), stopping BELOW the home directory because
|
|
26
|
+
// `~/.audit-tools/` is the tool's machine-wide cache home and would otherwise
|
|
27
|
+
// match on every box. No marker → the start dir unchanged.
|
|
28
|
+
|
|
29
|
+
export const AUDIT_TOOLS_DIRNAME = '.audit-tools';
|
|
30
|
+
|
|
31
|
+
export const REPO_ROOT_MARKERS = [AUDIT_TOOLS_DIRNAME, '.git'];
|
|
32
|
+
|
|
33
|
+
/** @param {string} p */
|
|
34
|
+
export function climbOutOfAuditTools(p) {
|
|
35
|
+
const resolved = resolve(p);
|
|
36
|
+
const segments = resolved.split(sep);
|
|
37
|
+
const idx = segments.indexOf(AUDIT_TOOLS_DIRNAME);
|
|
38
|
+
if (idx <= 0) return resolved;
|
|
39
|
+
return segments.slice(0, idx).join(sep) || resolved;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** @returns {string | null} */
|
|
43
|
+
function repoRootCeiling() {
|
|
44
|
+
try {
|
|
45
|
+
const home = homedir();
|
|
46
|
+
return home ? resolve(home) : null;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @param {string} [startDir] @returns {string} */
|
|
53
|
+
export function discoverRepoRoot(startDir = process.cwd()) {
|
|
54
|
+
const start = climbOutOfAuditTools(resolve(startDir));
|
|
55
|
+
const ceiling = repoRootCeiling();
|
|
56
|
+
let current = start;
|
|
57
|
+
for (;;) {
|
|
58
|
+
if (ceiling !== null && current === ceiling) return start;
|
|
59
|
+
if (REPO_ROOT_MARKERS.some((marker) => existsSync(join(current, marker)))) {
|
|
60
|
+
return current;
|
|
61
|
+
}
|
|
62
|
+
const parent = dirname(current);
|
|
63
|
+
if (parent === current) return start;
|
|
64
|
+
current = parent;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The root a wrapper-side command acts on: an explicitly supplied `--root` is
|
|
70
|
+
* honored verbatim (resolved against the caller's cwd), an absent one is
|
|
71
|
+
* discovered. The ONE place either wrapper turns "no --root" into a directory,
|
|
72
|
+
* so `next-step`, `ensure`, `install` and `verify-install` cannot answer it
|
|
73
|
+
* differently — across both bins.
|
|
74
|
+
*
|
|
75
|
+
* @param {string | undefined} rawRoot
|
|
76
|
+
* @returns {string}
|
|
77
|
+
*/
|
|
78
|
+
export function resolveWrapperRoot(rawRoot) {
|
|
79
|
+
return rawRoot === undefined ? discoverRepoRoot() : resolve(rawRoot);
|
|
80
|
+
}
|