@sentropic/harness 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/dist/artifacts/verification-run.d.ts +48 -0
- package/dist/artifacts/verification-run.d.ts.map +1 -0
- package/dist/artifacts/verification-run.js +10 -0
- package/dist/artifacts/verification-run.js.map +1 -0
- package/dist/bin/harness.d.ts +3 -0
- package/dist/bin/harness.d.ts.map +1 -0
- package/dist/bin/harness.js +5 -0
- package/dist/bin/harness.js.map +1 -0
- package/dist/branch-md/parse.d.ts +19 -0
- package/dist/branch-md/parse.d.ts.map +1 -0
- package/dist/branch-md/parse.js +95 -0
- package/dist/branch-md/parse.js.map +1 -0
- package/dist/checks/branch-check.d.ts +15 -0
- package/dist/checks/branch-check.d.ts.map +1 -0
- package/dist/checks/branch-check.js +23 -0
- package/dist/checks/branch-check.js.map +1 -0
- package/dist/checks/scope-check.d.ts +23 -0
- package/dist/checks/scope-check.d.ts.map +1 -0
- package/dist/checks/scope-check.js +34 -0
- package/dist/checks/scope-check.js.map +1 -0
- package/dist/cli/run.d.ts +9 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +116 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/profile/profile.d.ts +13 -0
- package/dist/profile/profile.d.ts.map +1 -0
- package/dist/profile/profile.js +10 -0
- package/dist/profile/profile.js.map +1 -0
- package/dist/profile/sentropic.d.ts +3 -0
- package/dist/profile/sentropic.d.ts.map +1 -0
- package/dist/profile/sentropic.js +12 -0
- package/dist/profile/sentropic.js.map +1 -0
- package/dist/profile/stub.d.ts +3 -0
- package/dist/profile/stub.d.ts.map +1 -0
- package/dist/profile/stub.js +13 -0
- package/dist/profile/stub.js.map +1 -0
- package/dist/run/emit.d.ts +24 -0
- package/dist/run/emit.d.ts.map +1 -0
- package/dist/run/emit.js +31 -0
- package/dist/run/emit.js.map +1 -0
- package/dist/scope/scope-boundary.d.ts +25 -0
- package/dist/scope/scope-boundary.d.ts.map +1 -0
- package/dist/scope/scope-boundary.js +50 -0
- package/dist/scope/scope-boundary.js.map +1 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Fabien Antoine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @sentropic/harness
|
|
2
|
+
|
|
3
|
+
Neutral, host-agnostic **code-work / PR-workflow** tooling for AI-driven development:
|
|
4
|
+
branch / scope / lot / verify discipline, behind a pluggable **profile** SPI.
|
|
5
|
+
|
|
6
|
+
- **Tooling-only**: zero product-runtime coupling (no Drizzle / Hono / Svelte / Mistral),
|
|
7
|
+
zero `@sentropic/*` deps. Node built-ins + TypeScript only.
|
|
8
|
+
- **Emit-only**: checks produce a neutral `VerificationRun` artifact; harness never writes
|
|
9
|
+
into `@sentropic/track` (a track-side adapter ingests the artifact).
|
|
10
|
+
- **Policy as data**: the Sentropic rules (scope paths, exception grammar, thresholds) live
|
|
11
|
+
in a `sentropic` profile module behind the `HarnessProfile` interface. The check **kernel**
|
|
12
|
+
is profile-driven (a second `stub` profile exercises it with divergent policy); full
|
|
13
|
+
plugin-surface genericity (plan-adapter SPI, profile resolution, `harness init`/`audit`) is
|
|
14
|
+
the G1–G4 follow-on tracked in `SPEC_STUDY_HARNESS_GENERICITY_AUDIT.md`.
|
|
15
|
+
|
|
16
|
+
> Status: **BR-42h** — published to npm (D7 lifted 2026-06-08). Installs like every other
|
|
17
|
+
> `@sentropic` CLI; `stp harness` subcommand federation is the scale lane (BR-42i).
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm i -g @sentropic/harness # same as @sentropic/h2a, track, remote, …
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Use
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Right branch?
|
|
29
|
+
harness check branch --current-branch "$(git branch --show-current)" --expected-branch feat/x
|
|
30
|
+
# Changed files within the BRANCH.md declared scope?
|
|
31
|
+
harness check scope --branch-md BRANCH.md --staged-files "$(git diff --cached --name-only | paste -sd,)" [--json]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`--json` emits a neutral `VerificationRun`. `--profile sentropic|stub` selects the policy
|
|
35
|
+
pack (default `sentropic`). In an `@sentropic` repo, `make scope-check` wraps the staged+
|
|
36
|
+
unstaged scope check; agents (Claude/Codex/Gemini) are told to use harness — not a generic
|
|
37
|
+
verification skill — for branch/scope discipline (see `AGENTS.md`/`GEMINI.md`).
|
|
38
|
+
|
|
39
|
+
## This slice
|
|
40
|
+
`BRANCH.md` parser · `ScopeBoundary` · **C1** branch-check · **C2** scope-check (advisory,
|
|
41
|
+
BR25 **D5** Layer A) · neutral `VerificationRun`.
|
|
42
|
+
|
|
43
|
+
See `BRANCH.md` (branch root) for the lot plan and the design articulation specs
|
|
44
|
+
(`SPEC_STUDY_HARNESS_WORKFLOW_ARTICULATION.md`, `SPEC_STUDY_CLI_ECOSYSTEM_GAPS.md`).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type VerificationCategory = 'none' | 'static' | 'unit' | 'integration' | 'e2e' | 'ci' | 'uat';
|
|
2
|
+
export type ViolationSeverity = 'advisory' | 'blocking';
|
|
3
|
+
/** A single policy violation found by a check. */
|
|
4
|
+
export interface Violation {
|
|
5
|
+
/** Check code, e.g. 'C1' (branch), 'C2' (scope). */
|
|
6
|
+
code: string;
|
|
7
|
+
/** Offending path, when the violation is path-scoped (C2). */
|
|
8
|
+
path?: string;
|
|
9
|
+
message: string;
|
|
10
|
+
/** D5 Layer A checks emit `advisory`; `blocking` is reserved (e.g. C8). */
|
|
11
|
+
severity: ViolationSeverity;
|
|
12
|
+
}
|
|
13
|
+
/** The return shape of an individual check function. */
|
|
14
|
+
export interface CheckResult {
|
|
15
|
+
pass: boolean;
|
|
16
|
+
violations: Violation[];
|
|
17
|
+
/** Set when the check was deliberately bypassed (documented exception). */
|
|
18
|
+
bypass?: {
|
|
19
|
+
reason: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** One check's contribution inside a VerificationRun. */
|
|
23
|
+
export interface VerificationCheck {
|
|
24
|
+
code: string;
|
|
25
|
+
category: VerificationCategory;
|
|
26
|
+
pass: boolean;
|
|
27
|
+
violations: Violation[];
|
|
28
|
+
}
|
|
29
|
+
export interface VerificationRun {
|
|
30
|
+
schemaVersion: 1;
|
|
31
|
+
runId: string;
|
|
32
|
+
commit: string;
|
|
33
|
+
branch: string;
|
|
34
|
+
env: string;
|
|
35
|
+
runner: string;
|
|
36
|
+
category: VerificationCategory;
|
|
37
|
+
command: string;
|
|
38
|
+
result: 'pass' | 'fail';
|
|
39
|
+
/** ISO-8601 timestamps. */
|
|
40
|
+
startedAt: string;
|
|
41
|
+
finishedAt: string;
|
|
42
|
+
checks: VerificationCheck[];
|
|
43
|
+
/** Flattened union of all check violations (convenience for ingesters). */
|
|
44
|
+
violations: Violation[];
|
|
45
|
+
/** Opaque references to produced artifacts (paths/urls). */
|
|
46
|
+
artifacts: string[];
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=verification-run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verification-run.d.ts","sourceRoot":"","sources":["../../src/artifacts/verification-run.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,oBAAoB,GAC5B,MAAM,GACN,QAAQ,GACR,MAAM,GACN,aAAa,GACb,KAAK,GACL,IAAI,GACJ,KAAK,CAAC;AAEV,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,UAAU,CAAC;AAExD,kDAAkD;AAClD,MAAM,WAAW,SAAS;IACxB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,QAAQ,EAAE,iBAAiB,CAAC;CAC7B;AAED,wDAAwD;AACxD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,2EAA2E;IAC3E,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7B;AAED,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,CAAC,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,2EAA2E;IAC3E,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Neutral verification artifact emitted by harness checks.
|
|
2
|
+
//
|
|
3
|
+
// `VerificationRun` is the host-agnostic JSON a check run produces. harness EMITS it
|
|
4
|
+
// and never writes into `@sentropic/track`; a track-side adapter ingests it. It is a
|
|
5
|
+
// superset of track's `TestRun{ commit, env, runner, result, at }` so the seam holds.
|
|
6
|
+
//
|
|
7
|
+
// `category` is the BR25 **D3** verification taxonomy (APPROVED 2026-06-04), bound to this
|
|
8
|
+
// field. (`security` is added when the security verify-hook lands — out of this slice.)
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=verification-run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verification-run.js","sourceRoot":"","sources":["../../src/artifacts/verification-run.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,EAAE;AACF,qFAAqF;AACrF,qFAAqF;AACrF,sFAAsF;AACtF,EAAE;AACF,2FAA2F;AAC3F,wFAAwF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../src/bin/harness.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/bin/harness.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;AACzF,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ParsedLotItem {
|
|
2
|
+
text: string;
|
|
3
|
+
checked: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface ParsedLot {
|
|
6
|
+
title: string;
|
|
7
|
+
checked: boolean;
|
|
8
|
+
items: ParsedLotItem[];
|
|
9
|
+
}
|
|
10
|
+
export interface ParsedBranchMd {
|
|
11
|
+
title: string;
|
|
12
|
+
allowedPaths: string[];
|
|
13
|
+
forbiddenPaths: string[];
|
|
14
|
+
conditionalPaths: string[];
|
|
15
|
+
exceptions: string[];
|
|
16
|
+
lots: ParsedLot[];
|
|
17
|
+
}
|
|
18
|
+
export declare function parseBranchMd(text: string): ParsedBranchMd;
|
|
19
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/branch-md/parse.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,aAAa,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE,SAAS,EAAE,CAAC;CACnB;AAWD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAuF1D"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Minimal, tolerant BRANCH.md parser. Extracts the structure C1/C2 need:
|
|
2
|
+
// title, scope buckets (Allowed/Forbidden/Conditional path globs), exception ids, and lots.
|
|
3
|
+
// Malformed/partial input yields empty fields — never throws.
|
|
4
|
+
const BACKTICK = /`([^`]+)`/g;
|
|
5
|
+
const EXCEPTION = /\bBR\d+[a-z]?-EX\d+\b/g;
|
|
6
|
+
function backtickGlobs(line) {
|
|
7
|
+
const out = [];
|
|
8
|
+
for (const m of line.matchAll(BACKTICK))
|
|
9
|
+
out.push(m[1]);
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
export function parseBranchMd(text) {
|
|
13
|
+
const lines = text.split(/\r?\n/);
|
|
14
|
+
const result = {
|
|
15
|
+
title: '',
|
|
16
|
+
allowedPaths: [],
|
|
17
|
+
forbiddenPaths: [],
|
|
18
|
+
conditionalPaths: [],
|
|
19
|
+
exceptions: [],
|
|
20
|
+
lots: [],
|
|
21
|
+
};
|
|
22
|
+
// Title: first `# ` heading (single hash), stripped of a leading "Feature:".
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
const m = /^#\s+(.+)$/.exec(line);
|
|
25
|
+
if (m) {
|
|
26
|
+
result.title = m[1].replace(/^Feature:\s*/i, '').trim();
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
let bucket = null;
|
|
31
|
+
let inPlan = false;
|
|
32
|
+
let currentLot = null;
|
|
33
|
+
const pushBucket = (globs) => {
|
|
34
|
+
if (bucket === 'allowed')
|
|
35
|
+
result.allowedPaths.push(...globs);
|
|
36
|
+
else if (bucket === 'forbidden')
|
|
37
|
+
result.forbiddenPaths.push(...globs);
|
|
38
|
+
else if (bucket === 'conditional')
|
|
39
|
+
result.conditionalPaths.push(...globs);
|
|
40
|
+
};
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
for (const m of line.matchAll(EXCEPTION))
|
|
43
|
+
result.exceptions.push(m[0]);
|
|
44
|
+
if (/^##\s+/.test(line)) {
|
|
45
|
+
bucket = null;
|
|
46
|
+
inPlan = /plan\s*\/\s*todo/i.test(line);
|
|
47
|
+
currentLot = null;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (/\*\*Allowed Paths\*\*/i.test(line)) {
|
|
51
|
+
bucket = 'allowed';
|
|
52
|
+
pushBucket(backtickGlobs(line));
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (/\*\*Forbidden Paths\*\*/i.test(line)) {
|
|
56
|
+
bucket = 'forbidden';
|
|
57
|
+
pushBucket(backtickGlobs(line));
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (/\*\*Conditional Paths/i.test(line)) {
|
|
61
|
+
bucket = 'conditional';
|
|
62
|
+
pushBucket(backtickGlobs(line));
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (bucket && /^\s+-\s/.test(line)) {
|
|
66
|
+
pushBucket(backtickGlobs(line));
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (bucket && /^-\s/.test(line)) {
|
|
70
|
+
bucket = null;
|
|
71
|
+
}
|
|
72
|
+
if (inPlan) {
|
|
73
|
+
const lotMatch = /^-\s*\[( |x)\]\s*\*\*(Lot[^*]+)\*\*/i.exec(line);
|
|
74
|
+
if (lotMatch) {
|
|
75
|
+
currentLot = {
|
|
76
|
+
title: lotMatch[2].trim(),
|
|
77
|
+
checked: lotMatch[1].toLowerCase() === 'x',
|
|
78
|
+
items: [],
|
|
79
|
+
};
|
|
80
|
+
result.lots.push(currentLot);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const itemMatch = /^\s+-\s*\[( |x)\]\s*(.+)$/.exec(line);
|
|
84
|
+
if (itemMatch && currentLot) {
|
|
85
|
+
currentLot.items.push({
|
|
86
|
+
text: itemMatch[2].trim(),
|
|
87
|
+
checked: itemMatch[1].toLowerCase() === 'x',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
result.exceptions = [...new Set(result.exceptions)];
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/branch-md/parse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HarnessProfile } from '../profile/profile.js';
|
|
2
|
+
import type { CheckResult } from '../artifacts/verification-run.js';
|
|
3
|
+
export interface BranchCheckInput {
|
|
4
|
+
currentBranch: string;
|
|
5
|
+
/** Caller-supplied this slice (parsing a BRANCH.md identity block is a follow-on). */
|
|
6
|
+
expectedBranch: string;
|
|
7
|
+
profile: HarnessProfile;
|
|
8
|
+
/** Documented reason to skip the check (advisory bypass). */
|
|
9
|
+
bypass?: {
|
|
10
|
+
reason: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** C1 — verify the working branch is the expected one (per the profile's `branchMatch`). */
|
|
14
|
+
export declare function checkBranch(input: BranchCheckInput): CheckResult;
|
|
15
|
+
//# sourceMappingURL=branch-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branch-check.d.ts","sourceRoot":"","sources":["../../src/checks/branch-check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAC;AAEpE,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,cAAc,CAAC;IACxB,6DAA6D;IAC7D,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7B;AAED,4FAA4F;AAC5F,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,WAAW,CAoBhE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** C1 — verify the working branch is the expected one (per the profile's `branchMatch`). */
|
|
2
|
+
export function checkBranch(input) {
|
|
3
|
+
if (input.bypass) {
|
|
4
|
+
return { pass: true, violations: [], bypass: input.bypass };
|
|
5
|
+
}
|
|
6
|
+
const { currentBranch, expectedBranch, profile } = input;
|
|
7
|
+
const ok = profile.branchMatch === 'prefix'
|
|
8
|
+
? currentBranch.startsWith(expectedBranch)
|
|
9
|
+
: currentBranch === expectedBranch;
|
|
10
|
+
if (ok)
|
|
11
|
+
return { pass: true, violations: [] };
|
|
12
|
+
return {
|
|
13
|
+
pass: false,
|
|
14
|
+
violations: [
|
|
15
|
+
{
|
|
16
|
+
code: 'C1',
|
|
17
|
+
message: `on branch '${currentBranch}', expected '${expectedBranch}' (${profile.branchMatch})`,
|
|
18
|
+
severity: 'advisory',
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=branch-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branch-check.js","sourceRoot":"","sources":["../../src/checks/branch-check.ts"],"names":[],"mappings":"AAYA,4FAA4F;AAC5F,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IAC9D,CAAC;IACD,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IACzD,MAAM,EAAE,GACN,OAAO,CAAC,WAAW,KAAK,QAAQ;QAC9B,CAAC,CAAC,aAAa,CAAC,UAAU,CAAC,cAAc,CAAC;QAC1C,CAAC,CAAC,aAAa,KAAK,cAAc,CAAC;IACvC,IAAI,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;IAC9C,OAAO;QACL,IAAI,EAAE,KAAK;QACX,UAAU,EAAE;YACV;gBACE,IAAI,EAAE,IAAI;gBACV,OAAO,EAAE,cAAc,aAAa,gBAAgB,cAAc,MAAM,OAAO,CAAC,WAAW,GAAG;gBAC9F,QAAQ,EAAE,UAAU;aACrB;SACF;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HarnessProfile } from '../profile/profile.js';
|
|
2
|
+
import type { CheckResult } from '../artifacts/verification-run.js';
|
|
3
|
+
import { type ScopeBoundary } from '../scope/scope-boundary.js';
|
|
4
|
+
export interface ScopeCheckInput {
|
|
5
|
+
/** Repo-relative staged file paths. */
|
|
6
|
+
stagedFiles: string[];
|
|
7
|
+
boundary: ScopeBoundary;
|
|
8
|
+
profile: HarnessProfile;
|
|
9
|
+
/** Exception ids declared in the BRANCH.md (e.g. parser `exceptions`). */
|
|
10
|
+
declaredExceptions?: string[];
|
|
11
|
+
bypass?: {
|
|
12
|
+
reason: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* C2 — verify staged files stay within the declared scope.
|
|
17
|
+
* - `forbidden` (explicit or profile-default) → violation;
|
|
18
|
+
* - `conditional` → violation unless a declared exception matches the profile's
|
|
19
|
+
* `exceptionIdPattern` (grammar binding) AND `conditionalRequiresException` is set;
|
|
20
|
+
* - `unknown` (outside Allowed/Forbidden/Conditional) → violation.
|
|
21
|
+
*/
|
|
22
|
+
export declare function checkScope(input: ScopeCheckInput): CheckResult;
|
|
23
|
+
//# sourceMappingURL=scope-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-check.d.ts","sourceRoot":"","sources":["../../src/checks/scope-check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAa,MAAM,kCAAkC,CAAC;AAC/E,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE9E,MAAM,WAAW,eAAe;IAC9B,uCAAuC;IACvC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,aAAa,CAAC;IACxB,OAAO,EAAE,cAAc,CAAC;IACxB,0EAA0E;IAC1E,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,WAAW,CAqB9D"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { classifyPath } from '../scope/scope-boundary.js';
|
|
2
|
+
/**
|
|
3
|
+
* C2 — verify staged files stay within the declared scope.
|
|
4
|
+
* - `forbidden` (explicit or profile-default) → violation;
|
|
5
|
+
* - `conditional` → violation unless a declared exception matches the profile's
|
|
6
|
+
* `exceptionIdPattern` (grammar binding) AND `conditionalRequiresException` is set;
|
|
7
|
+
* - `unknown` (outside Allowed/Forbidden/Conditional) → violation.
|
|
8
|
+
*/
|
|
9
|
+
export function checkScope(input) {
|
|
10
|
+
if (input.bypass)
|
|
11
|
+
return { pass: true, violations: [], bypass: input.bypass };
|
|
12
|
+
const { stagedFiles, boundary, profile, declaredExceptions = [] } = input;
|
|
13
|
+
const hasValidException = declaredExceptions.some((id) => profile.exceptionIdPattern.test(id));
|
|
14
|
+
const violations = [];
|
|
15
|
+
for (const path of stagedFiles) {
|
|
16
|
+
const cls = classifyPath(path, boundary, profile);
|
|
17
|
+
if (cls === 'forbidden') {
|
|
18
|
+
violations.push({ code: 'C2', path, message: `forbidden path touched: ${path}`, severity: 'advisory' });
|
|
19
|
+
}
|
|
20
|
+
else if (cls === 'conditional' && profile.conditionalRequiresException && !hasValidException) {
|
|
21
|
+
violations.push({
|
|
22
|
+
code: 'C2',
|
|
23
|
+
path,
|
|
24
|
+
message: `conditional path touched without a matching exception: ${path}`,
|
|
25
|
+
severity: 'advisory',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
else if (cls === 'unknown') {
|
|
29
|
+
violations.push({ code: 'C2', path, message: `path outside declared scope (unknown): ${path}`, severity: 'advisory' });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { pass: violations.length === 0, violations };
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=scope-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-check.js","sourceRoot":"","sources":["../../src/checks/scope-check.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAsB,MAAM,4BAA4B,CAAC;AAY9E;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,IAAI,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IAC9E,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,kBAAkB,GAAG,EAAE,EAAE,GAAG,KAAK,CAAC;IAC1E,MAAM,iBAAiB,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/F,MAAM,UAAU,GAAgB,EAAE,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;QAClD,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;YACxB,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,2BAA2B,IAAI,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC;QAC1G,CAAC;aAAM,IAAI,GAAG,KAAK,aAAa,IAAI,OAAO,CAAC,4BAA4B,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC/F,UAAU,CAAC,IAAI,CAAC;gBACd,IAAI,EAAE,IAAI;gBACV,IAAI;gBACJ,OAAO,EAAE,0DAA0D,IAAI,EAAE;gBACzE,QAAQ,EAAE,UAAU;aACrB,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YAC7B,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,0CAA0C,IAAI,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,CAAC;QACzH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, arg-based CLI driver — reads everything from `argv`, writes via `out`, returns an exit code.
|
|
3
|
+
* No internal git calls (the caller supplies branch + staged files), no `process.exit`.
|
|
4
|
+
* Advisory (D5 Layer A): a failing check returns 0; only a usage error returns non-zero.
|
|
5
|
+
* Acquiring git state under Docker-first (the `make scope-check` passthrough) is a separate,
|
|
6
|
+
* to-be-double-reviewed concern.
|
|
7
|
+
*/
|
|
8
|
+
export declare function runHarnessCli(argv: string[], out: (s: string) => void): number;
|
|
9
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AA8CA;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,CAsE9E"}
|
package/dist/cli/run.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { parseBranchMd } from '../branch-md/parse.js';
|
|
3
|
+
import { checkScope } from '../checks/scope-check.js';
|
|
4
|
+
import { checkBranch } from '../checks/branch-check.js';
|
|
5
|
+
import { toVerificationRun } from '../run/emit.js';
|
|
6
|
+
import { sentropicProfile } from '../profile/sentropic.js';
|
|
7
|
+
import { stubProfile } from '../profile/stub.js';
|
|
8
|
+
function parseFlags(args) {
|
|
9
|
+
const positionals = [];
|
|
10
|
+
const flags = {};
|
|
11
|
+
for (let i = 0; i < args.length; i++) {
|
|
12
|
+
const a = args[i];
|
|
13
|
+
if (a.startsWith('--')) {
|
|
14
|
+
const key = a.slice(2);
|
|
15
|
+
const next = args[i + 1];
|
|
16
|
+
if (next === undefined || next.startsWith('--')) {
|
|
17
|
+
flags[key] = true;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
flags[key] = next;
|
|
21
|
+
i++;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
positionals.push(a);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return { positionals, flags };
|
|
29
|
+
}
|
|
30
|
+
function str(v) {
|
|
31
|
+
return typeof v === 'string' ? v : undefined;
|
|
32
|
+
}
|
|
33
|
+
function list(v) {
|
|
34
|
+
const s = str(v);
|
|
35
|
+
return s ? s.split(/[\s,]+/).filter(Boolean) : [];
|
|
36
|
+
}
|
|
37
|
+
const USAGE = 'usage: harness check <scope|branch> [--current-branch <b>] [--expected-branch <b>] ' +
|
|
38
|
+
'[--staged-files <list>] [--branch-md <path>] [--profile sentropic|stub] [--json]';
|
|
39
|
+
/**
|
|
40
|
+
* Pure, arg-based CLI driver — reads everything from `argv`, writes via `out`, returns an exit code.
|
|
41
|
+
* No internal git calls (the caller supplies branch + staged files), no `process.exit`.
|
|
42
|
+
* Advisory (D5 Layer A): a failing check returns 0; only a usage error returns non-zero.
|
|
43
|
+
* Acquiring git state under Docker-first (the `make scope-check` passthrough) is a separate,
|
|
44
|
+
* to-be-double-reviewed concern.
|
|
45
|
+
*/
|
|
46
|
+
export function runHarnessCli(argv, out) {
|
|
47
|
+
const { positionals, flags } = parseFlags(argv);
|
|
48
|
+
const command = positionals[0];
|
|
49
|
+
const sub = positionals[1];
|
|
50
|
+
if (command !== 'check' || (sub !== 'scope' && sub !== 'branch')) {
|
|
51
|
+
out(USAGE);
|
|
52
|
+
return command === undefined ? 0 : 2;
|
|
53
|
+
}
|
|
54
|
+
const profile = str(flags.profile) === 'stub' ? stubProfile : sentropicProfile;
|
|
55
|
+
const category = 'static';
|
|
56
|
+
let result;
|
|
57
|
+
let code;
|
|
58
|
+
let commandLabel;
|
|
59
|
+
if (sub === 'scope') {
|
|
60
|
+
code = 'C2';
|
|
61
|
+
commandLabel = 'harness check scope';
|
|
62
|
+
const branchMdPath = str(flags['branch-md']);
|
|
63
|
+
let parsed;
|
|
64
|
+
if (branchMdPath) {
|
|
65
|
+
try {
|
|
66
|
+
parsed = parseBranchMd(readFileSync(branchMdPath, 'utf8'));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
out(`harness: cannot read plan file: ${branchMdPath}`);
|
|
70
|
+
return 2;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
result = checkScope({
|
|
74
|
+
stagedFiles: list(flags['staged-files']),
|
|
75
|
+
boundary: {
|
|
76
|
+
allowed: parsed?.allowedPaths ?? [],
|
|
77
|
+
forbidden: parsed?.forbiddenPaths ?? [],
|
|
78
|
+
conditional: parsed?.conditionalPaths ?? [],
|
|
79
|
+
},
|
|
80
|
+
profile,
|
|
81
|
+
declaredExceptions: parsed?.exceptions ?? [],
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
code = 'C1';
|
|
86
|
+
commandLabel = 'harness check branch';
|
|
87
|
+
result = checkBranch({
|
|
88
|
+
currentBranch: str(flags['current-branch']) ?? '',
|
|
89
|
+
expectedBranch: str(flags['expected-branch']) ?? '',
|
|
90
|
+
profile,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (flags.json === true) {
|
|
94
|
+
// Pure: a deterministic placeholder timestamp; a real run injects commit/env/clock context.
|
|
95
|
+
const ts = '1970-01-01T00:00:00.000Z';
|
|
96
|
+
const run = toVerificationRun([{ code, category, result }], {
|
|
97
|
+
runId: 'cli',
|
|
98
|
+
commit: str(flags.commit) ?? 'unknown',
|
|
99
|
+
branch: str(flags['current-branch']) ?? 'unknown',
|
|
100
|
+
env: str(flags.env) ?? 'cli',
|
|
101
|
+
runner: 'harness',
|
|
102
|
+
category,
|
|
103
|
+
command: commandLabel,
|
|
104
|
+
startedAt: ts,
|
|
105
|
+
finishedAt: ts,
|
|
106
|
+
});
|
|
107
|
+
out(JSON.stringify(run, null, 2));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
out(`${result.pass ? 'PASS' : 'FAIL'} ${code} (${profile.id})`);
|
|
111
|
+
for (const v of result.violations)
|
|
112
|
+
out(` - ${v.severity}: ${v.message}`);
|
|
113
|
+
}
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const HARNESS_PACKAGE: "@sentropic/harness";
|
|
2
|
+
export type { VerificationRun, VerificationCheck, VerificationCategory, Violation, ViolationSeverity, CheckResult, } from './artifacts/verification-run.js';
|
|
3
|
+
export type { HarnessProfile } from './profile/profile.js';
|
|
4
|
+
export { sentropicProfile } from './profile/sentropic.js';
|
|
5
|
+
export { stubProfile } from './profile/stub.js';
|
|
6
|
+
export { parseBranchMd } from './branch-md/parse.js';
|
|
7
|
+
export type { ParsedBranchMd, ParsedLot, ParsedLotItem } from './branch-md/parse.js';
|
|
8
|
+
export { classifyPath, matchGlob, globToRegExp } from './scope/scope-boundary.js';
|
|
9
|
+
export type { ScopeBoundary, PathClass } from './scope/scope-boundary.js';
|
|
10
|
+
export { checkBranch } from './checks/branch-check.js';
|
|
11
|
+
export type { BranchCheckInput } from './checks/branch-check.js';
|
|
12
|
+
export { checkScope } from './checks/scope-check.js';
|
|
13
|
+
export type { ScopeCheckInput } from './checks/scope-check.js';
|
|
14
|
+
export { toVerificationRun } from './run/emit.js';
|
|
15
|
+
export type { NamedCheck, VerificationContext } from './run/emit.js';
|
|
16
|
+
export { runHarnessCli } from './cli/run.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,eAAe,sBAAgC,CAAC;AAG7D,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,SAAS,EACT,iBAAiB,EACjB,WAAW,GACZ,MAAM,iCAAiC,CAAC;AAGzC,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrF,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAClF,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAG1E,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAGrE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @sentropic/harness — neutral, host-agnostic code-work / PR-workflow tooling.
|
|
2
|
+
//
|
|
3
|
+
// Tooling-only: no product-runtime imports (no Drizzle/Hono/Svelte/Mistral), no
|
|
4
|
+
// `@sentropic/*` deps, and NO track import — harness only EMITS neutral artifacts.
|
|
5
|
+
export const HARNESS_PACKAGE = '@sentropic/harness';
|
|
6
|
+
export { sentropicProfile } from './profile/sentropic.js';
|
|
7
|
+
export { stubProfile } from './profile/stub.js';
|
|
8
|
+
// BRANCH.md parser.
|
|
9
|
+
export { parseBranchMd } from './branch-md/parse.js';
|
|
10
|
+
// Scope classification.
|
|
11
|
+
export { classifyPath, matchGlob, globToRegExp } from './scope/scope-boundary.js';
|
|
12
|
+
// Checks (C1 branch, C2 scope) + neutral run assembly.
|
|
13
|
+
export { checkBranch } from './checks/branch-check.js';
|
|
14
|
+
export { checkScope } from './checks/scope-check.js';
|
|
15
|
+
export { toVerificationRun } from './run/emit.js';
|
|
16
|
+
// Pure, arg-based CLI driver (the `harness` bin is a thin wrapper over this).
|
|
17
|
+
export { runHarnessCli } from './cli/run.js';
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,gFAAgF;AAChF,mFAAmF;AAEnF,MAAM,CAAC,MAAM,eAAe,GAAG,oBAA6B,CAAC;AAc7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,oBAAoB;AACpB,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,wBAAwB;AACxB,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAGlF,uDAAuD;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGlD,8EAA8E;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface HarnessProfile {
|
|
2
|
+
/** Profile identifier, e.g. 'sentropic' | 'stub'. */
|
|
3
|
+
id: string;
|
|
4
|
+
/** Globs forbidden by default unless a BRANCH.md explicitly Allows them (C2). */
|
|
5
|
+
forbiddenPathDefaults: string[];
|
|
6
|
+
/** Grammar of a valid scope-exception id in BRANCH.md (C2 conditional binding). */
|
|
7
|
+
exceptionIdPattern: RegExp;
|
|
8
|
+
/** When true, a Conditional path may be touched only if a matching exception id exists (C2). */
|
|
9
|
+
conditionalRequiresException: boolean;
|
|
10
|
+
/** How the current branch is compared to the expected branch (C1). */
|
|
11
|
+
branchMatch: 'exact' | 'prefix';
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../src/profile/profile.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,EAAE,EAAE,MAAM,CAAC;IACX,iFAAiF;IACjF,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,mFAAmF;IACnF,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gGAAgG;IAChG,4BAA4B,EAAE,OAAO,CAAC;IACtC,sEAAsE;IACtE,WAAW,EAAE,OAAO,GAAG,QAAQ,CAAC;CACjC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// HarnessProfile — the policy SPI.
|
|
2
|
+
//
|
|
3
|
+
// The engine (parser, C1/C2 checks) is generic; all Sentropic-specific policy is data in a
|
|
4
|
+
// profile module. This slice exposes ONLY the fields C1/C2 actually consume — deferred
|
|
5
|
+
// checks (C4/C5/C6/C8) add their fields with their lots, so there is no dead config.
|
|
6
|
+
//
|
|
7
|
+
// Genericity is proven, not asserted: a second `stub` profile differs on a C2-consumed
|
|
8
|
+
// dimension and the Lot 4 test asserts DIVERGENT outcomes on the same input.
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/profile/profile.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,EAAE;AACF,2FAA2F;AAC3F,uFAAuF;AACvF,qFAAqF;AACrF,EAAE;AACF,uFAAuF;AACvF,6EAA6E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentropic.d.ts","sourceRoot":"","sources":["../../src/profile/sentropic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAInD,eAAO,MAAM,gBAAgB,EAAE,cAQ9B,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The Sentropic policy, as data (from rules/MASTER.md). The engine never branches on
|
|
2
|
+
// `id` — it only reads these fields.
|
|
3
|
+
export const sentropicProfile = {
|
|
4
|
+
id: 'sentropic',
|
|
5
|
+
// MASTER.md "Default forbidden": Makefile, docker-compose*.yml, .cursor/rules/**.
|
|
6
|
+
forbiddenPathDefaults: ['Makefile', 'docker-compose*.yml', '.cursor/rules/**'],
|
|
7
|
+
// Scope exceptions look like `BR42h-EX1` / `BR04-EX2` (MASTER.md `BRxx-EXn`).
|
|
8
|
+
exceptionIdPattern: /^BR\d+[a-z]?-EX\d+$/,
|
|
9
|
+
conditionalRequiresException: true,
|
|
10
|
+
branchMatch: 'exact',
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=sentropic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentropic.js","sourceRoot":"","sources":["../../src/profile/sentropic.ts"],"names":[],"mappings":"AAEA,qFAAqF;AACrF,qCAAqC;AACrC,MAAM,CAAC,MAAM,gBAAgB,GAAmB;IAC9C,EAAE,EAAE,WAAW;IACf,kFAAkF;IAClF,qBAAqB,EAAE,CAAC,UAAU,EAAE,qBAAqB,EAAE,kBAAkB,CAAC;IAC9E,8EAA8E;IAC9E,kBAAkB,EAAE,qBAAqB;IACzC,4BAA4B,EAAE,IAAI;IAClC,WAAW,EAAE,OAAO;CACrB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stub.d.ts","sourceRoot":"","sources":["../../src/profile/stub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAOnD,eAAO,MAAM,WAAW,EAAE,cAMzB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// A deliberately-different 2nd profile that PROVES the engine is profile-generic.
|
|
2
|
+
// It differs from `sentropic` on dimensions C2 actually consumes — a different
|
|
3
|
+
// exception-id grammar, a different forbidden-default set, and conditional-without-
|
|
4
|
+
// exception allowed — so the Lot 4 genericity test can assert DIVERGENT outcomes on the
|
|
5
|
+
// same BRANCH.md + staged-file input (not "both pass", which would prove nothing).
|
|
6
|
+
export const stubProfile = {
|
|
7
|
+
id: 'stub',
|
|
8
|
+
forbiddenPathDefaults: ['secret/**'],
|
|
9
|
+
exceptionIdPattern: /^EXC-\d+$/,
|
|
10
|
+
conditionalRequiresException: false,
|
|
11
|
+
branchMatch: 'prefix',
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=stub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stub.js","sourceRoot":"","sources":["../../src/profile/stub.ts"],"names":[],"mappings":"AAEA,kFAAkF;AAClF,+EAA+E;AAC/E,oFAAoF;AACpF,wFAAwF;AACxF,mFAAmF;AACnF,MAAM,CAAC,MAAM,WAAW,GAAmB;IACzC,EAAE,EAAE,MAAM;IACV,qBAAqB,EAAE,CAAC,WAAW,CAAC;IACpC,kBAAkB,EAAE,WAAW;IAC/B,4BAA4B,EAAE,KAAK;IACnC,WAAW,EAAE,QAAQ;CACtB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CheckResult, VerificationRun, VerificationCategory } from '../artifacts/verification-run.js';
|
|
2
|
+
export interface NamedCheck {
|
|
3
|
+
code: string;
|
|
4
|
+
category: VerificationCategory;
|
|
5
|
+
result: CheckResult;
|
|
6
|
+
}
|
|
7
|
+
export interface VerificationContext {
|
|
8
|
+
runId: string;
|
|
9
|
+
commit: string;
|
|
10
|
+
branch: string;
|
|
11
|
+
env: string;
|
|
12
|
+
runner: string;
|
|
13
|
+
category: VerificationCategory;
|
|
14
|
+
command: string;
|
|
15
|
+
startedAt: string;
|
|
16
|
+
finishedAt: string;
|
|
17
|
+
artifacts?: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Assemble a NEUTRAL `VerificationRun` from check results. harness EMITS this artifact and
|
|
21
|
+
* never writes into `@sentropic/track` — a track-side adapter ingests it (no track import here).
|
|
22
|
+
*/
|
|
23
|
+
export declare function toVerificationRun(checks: NamedCheck[], ctx: VerificationContext): VerificationRun;
|
|
24
|
+
//# sourceMappingURL=emit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/run/emit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EAEf,oBAAoB,EAErB,MAAM,kCAAkC,CAAC;AAE1C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,mBAAmB,GAAG,eAAe,CAyBjG"}
|
package/dist/run/emit.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assemble a NEUTRAL `VerificationRun` from check results. harness EMITS this artifact and
|
|
3
|
+
* never writes into `@sentropic/track` — a track-side adapter ingests it (no track import here).
|
|
4
|
+
*/
|
|
5
|
+
export function toVerificationRun(checks, ctx) {
|
|
6
|
+
const verificationChecks = checks.map((c) => ({
|
|
7
|
+
code: c.code,
|
|
8
|
+
category: c.category,
|
|
9
|
+
pass: c.result.pass,
|
|
10
|
+
violations: c.result.violations,
|
|
11
|
+
}));
|
|
12
|
+
const violations = checks.flatMap((c) => c.result.violations);
|
|
13
|
+
const pass = verificationChecks.every((c) => c.pass);
|
|
14
|
+
return {
|
|
15
|
+
schemaVersion: 1,
|
|
16
|
+
runId: ctx.runId,
|
|
17
|
+
commit: ctx.commit,
|
|
18
|
+
branch: ctx.branch,
|
|
19
|
+
env: ctx.env,
|
|
20
|
+
runner: ctx.runner,
|
|
21
|
+
category: ctx.category,
|
|
22
|
+
command: ctx.command,
|
|
23
|
+
result: pass ? 'pass' : 'fail',
|
|
24
|
+
startedAt: ctx.startedAt,
|
|
25
|
+
finishedAt: ctx.finishedAt,
|
|
26
|
+
checks: verificationChecks,
|
|
27
|
+
violations,
|
|
28
|
+
artifacts: ctx.artifacts ?? [],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=emit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit.js","sourceRoot":"","sources":["../../src/run/emit.ts"],"names":[],"mappings":"AA2BA;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAoB,EAAE,GAAwB;IAC9E,MAAM,kBAAkB,GAAwB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACjE,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI;QACnB,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,UAAU;KAChC,CAAC,CAAC,CAAC;IACJ,MAAM,UAAU,GAAgB,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC3E,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACrD,OAAO;QACL,aAAa,EAAE,CAAC;QAChB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;QAC9B,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,MAAM,EAAE,kBAAkB;QAC1B,UAAU;QACV,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,EAAE;KAC/B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { HarnessProfile } from '../profile/profile.js';
|
|
2
|
+
export type PathClass = 'allowed' | 'forbidden' | 'conditional' | 'unknown';
|
|
3
|
+
/** The Allowed / Forbidden / Conditional path globs declared by a BRANCH.md. */
|
|
4
|
+
export interface ScopeBoundary {
|
|
5
|
+
allowed: string[];
|
|
6
|
+
forbidden: string[];
|
|
7
|
+
conditional: string[];
|
|
8
|
+
}
|
|
9
|
+
/** Translate a path glob into an anchored RegExp. `**` spans `/`; `*` stays within a segment. */
|
|
10
|
+
export declare function globToRegExp(glob: string): RegExp;
|
|
11
|
+
export declare function matchGlob(glob: string, path: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Classify a repo-relative path against a branch ScopeBoundary + the profile's default
|
|
14
|
+
* forbidden globs (C2 scope-check core).
|
|
15
|
+
*
|
|
16
|
+
* Precedence (first match wins):
|
|
17
|
+
* explicit allowed > explicit forbidden > explicit conditional > profile default-forbidden > unknown
|
|
18
|
+
*
|
|
19
|
+
* `allowed` wins over `forbidden` so an explicitly-granted subtree (e.g. `packages/harness/**`)
|
|
20
|
+
* is `allowed` even when a broader `packages/**` is forbidden — the Allowed list is the granted
|
|
21
|
+
* implementation scope. `conditional` is checked before profile defaults so a Conditional listing
|
|
22
|
+
* (e.g. `Makefile` with an exception) overrides the profile's default-forbidden classification.
|
|
23
|
+
*/
|
|
24
|
+
export declare function classifyPath(path: string, boundary: ScopeBoundary, profile: HarnessProfile): PathClass;
|
|
25
|
+
//# sourceMappingURL=scope-boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-boundary.d.ts","sourceRoot":"","sources":["../../src/scope/scope-boundary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,aAAa,GAAG,SAAS,CAAC;AAE5E,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,iGAAiG;AACjG,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBjD;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,aAAa,EACvB,OAAO,EAAE,cAAc,GACtB,SAAS,CAMX"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Translate a path glob into an anchored RegExp. `**` spans `/`; `*` stays within a segment. */
|
|
2
|
+
export function globToRegExp(glob) {
|
|
3
|
+
let re = '';
|
|
4
|
+
for (let i = 0; i < glob.length; i++) {
|
|
5
|
+
const c = glob[i];
|
|
6
|
+
if (c === '*') {
|
|
7
|
+
if (glob[i + 1] === '*') {
|
|
8
|
+
re += '.*';
|
|
9
|
+
i++;
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
re += '[^/]*';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
else if ('\\^$+?.()|[]{}/'.includes(c)) {
|
|
16
|
+
re += '\\' + c;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
re += c;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return new RegExp('^' + re + '$');
|
|
23
|
+
}
|
|
24
|
+
export function matchGlob(glob, path) {
|
|
25
|
+
return globToRegExp(glob).test(path);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Classify a repo-relative path against a branch ScopeBoundary + the profile's default
|
|
29
|
+
* forbidden globs (C2 scope-check core).
|
|
30
|
+
*
|
|
31
|
+
* Precedence (first match wins):
|
|
32
|
+
* explicit allowed > explicit forbidden > explicit conditional > profile default-forbidden > unknown
|
|
33
|
+
*
|
|
34
|
+
* `allowed` wins over `forbidden` so an explicitly-granted subtree (e.g. `packages/harness/**`)
|
|
35
|
+
* is `allowed` even when a broader `packages/**` is forbidden — the Allowed list is the granted
|
|
36
|
+
* implementation scope. `conditional` is checked before profile defaults so a Conditional listing
|
|
37
|
+
* (e.g. `Makefile` with an exception) overrides the profile's default-forbidden classification.
|
|
38
|
+
*/
|
|
39
|
+
export function classifyPath(path, boundary, profile) {
|
|
40
|
+
if (boundary.allowed.some((g) => matchGlob(g, path)))
|
|
41
|
+
return 'allowed';
|
|
42
|
+
if (boundary.forbidden.some((g) => matchGlob(g, path)))
|
|
43
|
+
return 'forbidden';
|
|
44
|
+
if (boundary.conditional.some((g) => matchGlob(g, path)))
|
|
45
|
+
return 'conditional';
|
|
46
|
+
if (profile.forbiddenPathDefaults.some((g) => matchGlob(g, path)))
|
|
47
|
+
return 'forbidden';
|
|
48
|
+
return 'unknown';
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=scope-boundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope-boundary.js","sourceRoot":"","sources":["../../src/scope/scope-boundary.ts"],"names":[],"mappings":"AAWA,iGAAiG;AACjG,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBACxB,EAAE,IAAI,IAAI,CAAC;gBACX,CAAC,EAAE,CAAC;YACN,CAAC;iBAAM,CAAC;gBACN,EAAE,IAAI,OAAO,CAAC;YAChB,CAAC;QACH,CAAC;aAAM,IAAI,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACzC,EAAE,IAAI,IAAI,GAAG,CAAC,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,EAAE,IAAI,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IACD,OAAO,IAAI,MAAM,CAAC,GAAG,GAAG,EAAE,GAAG,GAAG,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,IAAY;IAClD,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,QAAuB,EACvB,OAAuB;IAEvB,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACvE,IAAI,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAAE,OAAO,WAAW,CAAC;IAC3E,IAAI,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAAE,OAAO,aAAa,CAAC;IAC/E,IAAI,OAAO,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAAE,OAAO,WAAW,CAAC;IACtF,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sentropic/harness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Neutral, host-agnostic code-work / PR-workflow tooling for AI dev: branch/scope/lot/verify discipline. Tooling-only, zero product-runtime deps. (BR-42h)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/rhanka/sentropic.git",
|
|
10
|
+
"directory": "packages/harness"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"bin": {
|
|
15
|
+
"harness": "./dist/bin/harness.js"
|
|
16
|
+
},
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc -p tsconfig.json",
|
|
31
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^20.12.0",
|
|
35
|
+
"typescript": "5.4.5",
|
|
36
|
+
"vitest": "4.0.18"
|
|
37
|
+
}
|
|
38
|
+
}
|