shapeup-sdlc 1.7.0 → 3.0.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/.claude/settings.local.example.json +5 -5
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +17 -3
- package/README.md +124 -103
- package/SECURITY.md +37 -30
- package/bin/init.mjs +77 -47
- package/bin/lib/grant.mjs +145 -0
- package/commands/build.md +7 -6
- package/commands/ship.md +29 -16
- package/commands/wire.md +1 -1
- package/hooks/dispatch-receipt.mjs +195 -0
- package/hooks/gate-intake.mjs +3 -3
- package/hooks/gate-zerowork.mjs +25 -24
- package/hooks/hooks.json +9 -48
- package/hooks/lib/decision.mjs +19 -8
- package/hooks/safety-spine.mjs +3 -3
- package/hooks/sandbox-guard.mjs +84 -40
- package/{skills/tech-lead/scripts/compile-order.mjs → kernel/compile.mjs} +283 -41
- package/{skills/tech-lead/scripts/gate-answers.mjs → kernel/gate.mjs} +58 -14
- package/kernel/harness.mjs +134 -0
- package/{skills/tech-lead/scripts/fit-check.mjs → kernel/init/fit.mjs} +15 -12
- package/{skills/tech-lead/scripts/init-run.mjs → kernel/init/run.mjs} +152 -31
- package/{skills/tech-lead/scripts → kernel}/lib/argv.mjs +52 -31
- package/{skills/tech-lead/scripts/lib/contract-md.mjs → kernel/lib/contract.mjs} +251 -7
- package/kernel/lib/paths.mjs +491 -0
- package/kernel/probe/concurrency.mjs +510 -0
- package/{skills/tech-lead/scripts/aegis-digest.mjs → kernel/probe/digest.mjs} +9 -10
- package/kernel/probe/eval.mjs +77 -0
- package/kernel/probe/leg.mjs +125 -0
- package/{skills/tech-lead/scripts/resume-state.mjs → kernel/probe/resume.mjs} +204 -63
- package/{skills/tech-lead/scripts → kernel/probe}/stats.mjs +93 -11
- package/kernel/probe/t0.mjs +66 -0
- package/{skills/ba-pitch-analyzer/scripts/board-derive.mjs → kernel/reduce/board.mjs} +99 -23
- package/kernel/reduce/graph.mjs +437 -0
- package/{skills/tech-lead/scripts/hill-derive.mjs → kernel/reduce/hill.mjs} +28 -9
- package/kernel/reduce/ingest.mjs +633 -0
- package/{hooks/slop-cleaner.mjs → kernel/reduce/leftovers.mjs} +40 -57
- package/{skills/tech-lead/scripts/ship-report.mjs → kernel/reduce/ship.mjs} +61 -13
- package/{skills/tech-lead/scripts/run-snapshot.mjs → kernel/reduce/snapshot.mjs} +21 -15
- package/{skills/spec-evaluator/scripts/verdict-ledger.mjs → kernel/reduce/verdict.mjs} +13 -7
- package/kernel/report/export.mjs +325 -0
- package/kernel/report/facts.mjs +347 -0
- package/{skills/tech-lead/scripts/budget-check.mjs → kernel/verify/budget.mjs} +15 -12
- package/kernel/verify/dispatch.mjs +114 -0
- package/{skills/tech-lead/scripts/validate-envelope.mjs → kernel/verify/envelope.mjs} +20 -15
- package/{skills/tech-lead/scripts/lib → kernel/verify}/ratchet-tree.mjs +36 -14
- package/kernel/verify/skills.mjs +125 -0
- package/kernel/verify/spec.mjs +559 -0
- package/{skills/tech-lead/scripts/t0-verify.mjs → kernel/verify/t0.mjs} +134 -24
- package/{skills/tech-lead/scripts/trace-lint.mjs → kernel/verify/trace.mjs} +26 -11
- package/oracles/http-oracle.mjs +1 -1
- package/oracles/process-oracle.mjs +1 -1
- package/oracles/snapshot-oracle.mjs +1 -1
- package/oracles/test-oracle.mjs +1 -1
- package/package.json +11 -9
- package/skills/ba-pitch-analyzer/SKILL.md +5 -5
- package/skills/ba-pitch-analyzer/assets/templates/_index.tmpl.md +1 -1
- package/skills/ba-pitch-analyzer/assets/templates/api-feasibility.tmpl.md +12 -7
- package/skills/ba-pitch-analyzer/assets/templates/contracts/third-party-api.contract.tmpl.md +6 -3
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/migration-plan.tmpl.md +5 -4
- package/skills/ba-pitch-analyzer/assets/templates/cross-context/team-handoff.tmpl.md +23 -13
- package/skills/ba-pitch-analyzer/assets/templates/scope-summary.tmpl.md +17 -12
- package/skills/ba-pitch-analyzer/assets/templates/synthesis.tmpl.md +17 -38
- package/skills/ba-pitch-analyzer/assets/templates/task.tmpl.md +3 -0
- package/skills/ba-pitch-analyzer/references/contract-patterns.md +8 -4
- package/skills/ba-pitch-analyzer/references/doc-schemas.md +2 -0
- package/skills/ba-pitch-analyzer/references/task-generation.md +2 -2
- package/skills/qa-edge-hunter/SKILL.md +7 -3
- package/skills/scope-architect/SKILL.md +40 -6
- package/skills/solution-architect/SKILL.md +19 -4
- package/skills/spec-evaluator/SKILL.md +12 -2
- package/skills/task-executor/SKILL.md +7 -6
- package/skills/tech-lead/SKILL.md +50 -38
- package/skills/tech-lead/references/gates.md +69 -20
- package/skills/tech-lead/references/protocol.md +832 -0
- package/skills/tech-lead/schemas/domain.schema.json +341 -66
- package/skills/tech-lead/schemas/work-order.schema.json +11 -2
- package/skills/tech-lead/schemas/work-result.schema.json +1 -1
- package/skills/tech-lead/workflows/shapeup-run.js +1313 -750
- package/hooks/anti-rationalization.mjs +0 -238
- package/hooks/compact-snapshot.mjs +0 -47
- package/hooks/gate-deadline.mjs +0 -147
- package/hooks/gate-l2.mjs +0 -161
- package/hooks/session-rehydrate.mjs +0 -108
- package/skills/ba-pitch-analyzer/scripts/spec-lint.mjs +0 -239
- package/skills/spec-evaluator/README.md +0 -93
- package/skills/tech-lead/README.md +0 -71
- package/skills/tech-lead/references/delegation.md +0 -261
- package/skills/tech-lead/references/hard-rules.md +0 -34
- package/skills/tech-lead/references/invocation.md +0 -45
- package/skills/tech-lead/references/ledger-schema.md +0 -213
- package/skills/tech-lead/references/round-protocol.md +0 -234
- package/skills/tech-lead/references/state-model.md +0 -66
- package/skills/tech-lead/scripts/ingest-result.mjs +0 -258
- package/skills/tech-lead/scripts/lib/is-main.mjs +0 -81
- package/skills/tech-lead/scripts/lib/paths.mjs +0 -280
- package/skills/tech-lead/scripts/run-workflow.mjs +0 -381
- package/skills/translator/README.md +0 -66
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// harness — the kernel's single entry point.
|
|
3
|
+
//
|
|
4
|
+
// WHAT THIS IS. Everything in the pipeline that must be DETERMINISTIC — measured rather than
|
|
5
|
+
// claimed, single-writer rather than negotiated, an answer file rather than a vibe — is a
|
|
6
|
+
// subcommand of this one script:
|
|
7
|
+
//
|
|
8
|
+
// node ${CLAUDE_PLUGIN_ROOT}/kernel/harness.mjs <verb> [<action>] [flags]
|
|
9
|
+
//
|
|
10
|
+
// WHY ONE ENTRY POINT. A permission rule is matched against a command string. With one executable
|
|
11
|
+
// the entire grant is two lines a person can read (`bin/lib/grant.mjs`); with twenty it is forty
|
|
12
|
+
// rules that must be regenerated on every add, rename or removal — and a rule that silently
|
|
13
|
+
// matches nothing is indistinguishable from a rule that works until the first dispatch fails.
|
|
14
|
+
// Subcommands are free; entry points are not.
|
|
15
|
+
//
|
|
16
|
+
// WHO CALLS IT. Worker skills, from their own shells, and the hooks. The control plane — the
|
|
17
|
+
// Workflow script — calls no shell at all: it branches on the structured return of an `agent()`
|
|
18
|
+
// and never parses stdout.
|
|
19
|
+
//
|
|
20
|
+
// THE VERBS, and the invariant each one exists to keep:
|
|
21
|
+
//
|
|
22
|
+
// verify t0 · budget · envelope · Measured, not claimed. A model verifying itself is
|
|
23
|
+
// trace · spec · skills · claiming; these read artifacts and re-hash them.
|
|
24
|
+
// dispatch `skills` reads the roster off disk; `dispatch` reads
|
|
25
|
+
// the hook layer's evidence that a skill really resolved
|
|
26
|
+
// in this session — the half a file check cannot answer.
|
|
27
|
+
// reduce ingest · hill · snapshot · Single writer. Shared state has exactly one author.
|
|
28
|
+
// ship · board · verdict · graph
|
|
29
|
+
// gate An answer file with a source, not a vibe.
|
|
30
|
+
// probe resume · t0 · stats · digest · Read-only queries over run state. `concurrency`
|
|
31
|
+
// concurrency · leg · eval answers how many legs ran at once and what the
|
|
32
|
+
// fan-out bought, and refuses a figure the record set
|
|
33
|
+
// cannot support rather than printing a plausible one.
|
|
34
|
+
// `leg` answers whether a scope's work reached the
|
|
35
|
+
// board — a green T0 says its fixtures passed, not that
|
|
36
|
+
// its result was ever applied, and a leg that skipped
|
|
37
|
+
// its own ingest step is indistinguishable from one that
|
|
38
|
+
// did not until something reads the writer's own record.
|
|
39
|
+
// `eval` answers what an EVAL round's WorkResult
|
|
40
|
+
// actually said, mechanically — the round-loop branch
|
|
41
|
+
// reads this instead of trusting a dispatching agent's
|
|
42
|
+
// own end-of-turn summary of its own verdict.
|
|
43
|
+
// init run · fit Opens a run, or refuses it (exit 3).
|
|
44
|
+
// report export Projects the run's records as fact tables.
|
|
45
|
+
// compile The WorkOrder: schema-valid or nothing is dispatched.
|
|
46
|
+
//
|
|
47
|
+
// EXIT CODES are the subcommand's own and are part of its contract: 0 success · 1 ran, answer is
|
|
48
|
+
// no · 2 malformed input, nothing ran (`lib/argv.mjs`) · 3 `init run` refused to open · 4/5 gate
|
|
49
|
+
// stop/abort · 6 budget tripped. This dispatcher adds exactly one of its own: 2 for an unknown
|
|
50
|
+
// verb, which is the same "the input was malformed" case.
|
|
51
|
+
|
|
52
|
+
import { isMain } from "./lib/argv.mjs";
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* verb → action → module, the whole routing table.
|
|
56
|
+
*
|
|
57
|
+
* A verb whose value is a string takes no action word (`gate`, `compile`); a verb whose value is an
|
|
58
|
+
* object requires one, and `_default` names the action taken when it is omitted.
|
|
59
|
+
*
|
|
60
|
+
* Exported so the structural suite enumerates the subcommands from THIS table rather than from a
|
|
61
|
+
* hand-kept list beside it — a second copy of the routing is a second thing to forget.
|
|
62
|
+
*/
|
|
63
|
+
export const ROUTES = {
|
|
64
|
+
verify: {
|
|
65
|
+
t0: "./verify/t0.mjs", budget: "./verify/budget.mjs", envelope: "./verify/envelope.mjs",
|
|
66
|
+
trace: "./verify/trace.mjs", spec: "./verify/spec.mjs", skills: "./verify/skills.mjs",
|
|
67
|
+
dispatch: "./verify/dispatch.mjs",
|
|
68
|
+
},
|
|
69
|
+
reduce: {
|
|
70
|
+
ingest: "./reduce/ingest.mjs", hill: "./reduce/hill.mjs", snapshot: "./reduce/snapshot.mjs",
|
|
71
|
+
ship: "./reduce/ship.mjs", board: "./reduce/board.mjs", verdict: "./reduce/verdict.mjs",
|
|
72
|
+
graph: "./reduce/graph.mjs",
|
|
73
|
+
},
|
|
74
|
+
probe: {
|
|
75
|
+
resume: "./probe/resume.mjs", t0: "./probe/t0.mjs", stats: "./probe/stats.mjs",
|
|
76
|
+
digest: "./probe/digest.mjs", concurrency: "./probe/concurrency.mjs",
|
|
77
|
+
leg: "./probe/leg.mjs", eval: "./probe/eval.mjs",
|
|
78
|
+
},
|
|
79
|
+
init: { run: "./init/run.mjs", fit: "./init/fit.mjs" },
|
|
80
|
+
report: { export: "./report/export.mjs", _default: "export" },
|
|
81
|
+
gate: "./gate.mjs",
|
|
82
|
+
compile: "./compile.mjs",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** Every `<verb> <action>` pair, for the usage text and the unknown-verb rejection. */
|
|
86
|
+
function usage() {
|
|
87
|
+
const lines = [];
|
|
88
|
+
for (const [verb, target] of Object.entries(ROUTES)) {
|
|
89
|
+
if (typeof target === "string") lines.push(` ${verb}`);
|
|
90
|
+
else lines.push(` ${verb} ${Object.keys(target).filter((a) => a !== "_default").join(" | ")}`);
|
|
91
|
+
}
|
|
92
|
+
return `usage: harness.mjs <verb> [<action>] [flags]\n${lines.join("\n")}\n\nEvery subcommand accepts --help through its own argv spec.`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Reject an invocation before anything runs, the same way `lib/argv.mjs` rejects a bad flag.
|
|
97
|
+
* @param {object} detail - Machine-readable rejection record.
|
|
98
|
+
* @returns {never} Exits 2.
|
|
99
|
+
*/
|
|
100
|
+
function reject(detail) {
|
|
101
|
+
process.stderr.write(JSON.stringify(detail) + "\n");
|
|
102
|
+
process.stderr.write(usage() + "\n");
|
|
103
|
+
process.exit(2);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Resolve `<verb> [<action>]` to a module path and the argv the subcommand should see.
|
|
108
|
+
*
|
|
109
|
+
* @param {string[]} argv - `process.argv.slice(2)`.
|
|
110
|
+
* @returns {{module: string, rest: string[]}} The module to import and its own arguments.
|
|
111
|
+
*/
|
|
112
|
+
export function route(argv) {
|
|
113
|
+
const [verb, ...rest] = argv;
|
|
114
|
+
if (!verb || verb === "--help" || verb === "-h") { process.stdout.write(usage() + "\n"); process.exit(0); }
|
|
115
|
+
const target = ROUTES[verb];
|
|
116
|
+
if (!target) reject({ error: "unknown_verb", verb, expected: Object.keys(ROUTES).join(" | ") });
|
|
117
|
+
if (typeof target === "string") return { module: target, rest };
|
|
118
|
+
|
|
119
|
+
const action = rest[0] && !rest[0].startsWith("-") ? rest[0] : target._default;
|
|
120
|
+
const module = action ? target[action] : null;
|
|
121
|
+
if (!module) {
|
|
122
|
+
reject({
|
|
123
|
+
error: action ? "unknown_action" : "missing_action", verb, action: action ?? null,
|
|
124
|
+
expected: Object.keys(target).filter((a) => a !== "_default").join(" | "),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return { module, rest: rest[0] === action ? rest.slice(1) : rest };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (isMain(import.meta.url)) {
|
|
131
|
+
const { module, rest } = route(process.argv.slice(2));
|
|
132
|
+
const mod = await import(module);
|
|
133
|
+
await mod.cli(rest);
|
|
134
|
+
}
|
|
@@ -34,17 +34,16 @@
|
|
|
34
34
|
// symmetric, and the tie goes to the gates.
|
|
35
35
|
//
|
|
36
36
|
// USAGE
|
|
37
|
-
// node fit
|
|
38
|
-
// node fit
|
|
37
|
+
// node `harness init fit` --intake-file <path> [--cwd <root>] [--json]
|
|
38
|
+
// node `harness init fit` --intake-text "<requirement>" [--cwd <root>]
|
|
39
39
|
//
|
|
40
40
|
// Exit 0 always — this informs a decision, it does not deny a tool call. The orchestrator reads
|
|
41
|
-
// `lane` and acts on it;
|
|
41
|
+
// `lane` and acts on it; ``harness init run`` records it in the receipt.
|
|
42
42
|
|
|
43
43
|
import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
|
|
44
44
|
import { join } from "node:path";
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import { runArgs } from "./lib/argv.mjs";
|
|
45
|
+
import { LOCAL } from "../lib/paths.mjs";
|
|
46
|
+
import { runArgs } from "../lib/argv.mjs";
|
|
48
47
|
|
|
49
48
|
/** Directories that are never part of "the tree being changed". */
|
|
50
49
|
const IGNORE_DIRS = new Set([".git", "node_modules", LOCAL, "dist", "build", ".next", "coverage", ".claude"]);
|
|
@@ -160,15 +159,22 @@ export function decideLane({ intake, files }) {
|
|
|
160
159
|
|
|
161
160
|
/** The typed argv contract (see `./lib/argv.mjs`). */
|
|
162
161
|
export const ARGV_SPEC = {
|
|
163
|
-
usage: '
|
|
162
|
+
usage: 'harness.mjs init fit (--intake-file <path> | --intake-text "<requirement>") [--cwd <dir>]',
|
|
164
163
|
_: { arity: 0, max: 0, name: "(no positional operands)" },
|
|
165
164
|
cwd: { type: "path" },
|
|
166
165
|
"intake-text": { type: "str" },
|
|
167
166
|
"intake-file": { type: "path" },
|
|
168
167
|
};
|
|
169
168
|
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Decide the lane (full or tiny) for an intake and the tree it will build in.
|
|
171
|
+
*
|
|
172
|
+
* @param {string[]} rawArgv - The subcommand's own arguments (harness.mjs strips the verb words).
|
|
173
|
+
* @returns {(Promise<void>|void)} Settles when the subcommand has written its output; most paths
|
|
174
|
+
* call `process.exit()` with the subcommand's documented code rather than returning.
|
|
175
|
+
*/
|
|
176
|
+
export function cli(rawArgv) {
|
|
177
|
+
const args = runArgs(ARGV_SPEC, rawArgv);
|
|
172
178
|
const cwd = args.cwd || process.cwd();
|
|
173
179
|
let intake = args.intakeText ?? null;
|
|
174
180
|
const f = args.intakeFile ?? null;
|
|
@@ -191,6 +197,3 @@ export function main() {
|
|
|
191
197
|
}, null, 2));
|
|
192
198
|
}
|
|
193
199
|
|
|
194
|
-
if (isMain(import.meta.url)) {
|
|
195
|
-
main();
|
|
196
|
-
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
//
|
|
16
16
|
// Two guards existed and neither could see it:
|
|
17
17
|
// • `gate-intake.mjs` (L0.0) fires on an EMPTY intake. Intake was valid here. Correct no-op.
|
|
18
|
-
// •
|
|
18
|
+
// • the ship report's census fires when a completion claim contradicts run facts. It is
|
|
19
19
|
// scoped to an ACTIVE run — and a run that never started produces none of the files it
|
|
20
20
|
// reads — and its claim detector matches past-tense completion ("done", "shipped"), while
|
|
21
21
|
// narration is future-tense ("it will"). Two independent misses on the same transcript.
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
// verbatim next to its digest, so "the spec was dropped" is checkable, not arguable.
|
|
40
40
|
//
|
|
41
41
|
// USAGE
|
|
42
|
-
// node init
|
|
43
|
-
// node init
|
|
44
|
-
// cat spec.md | node init
|
|
42
|
+
// node `harness init run` --slug <slug> --intake-file <path> [options] <- prefer this
|
|
43
|
+
// node `harness init run` --slug <slug> --intake-text "<requirement>" [options]
|
|
44
|
+
// cat spec.md | node `harness init run` --slug <slug> --intake-stdin [options]
|
|
45
45
|
//
|
|
46
46
|
// PREFER --intake-file. A multi-line requirement inlined into a shell argument is where this step
|
|
47
47
|
// goes wrong: quoting breaks, a `#` after a newline trips path validation, and the run spends six
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
// --attempts N inner per-scope T0 budget (default: 5)
|
|
54
54
|
// --spec-folder SHARED spec deliverable path (default: shapeup/<slug>/spec/)
|
|
55
55
|
// --dimensions comma-separated eval dimensions (default: spec-conformance)
|
|
56
|
-
// --gate-answers path | preset name (see gate
|
|
57
|
-
// --wall-clock-budget N deadline breaker, seconds (off by default; see budget
|
|
56
|
+
// --gate-answers path | preset name (see `harness gate`; recorded, not read)
|
|
57
|
+
// --wall-clock-budget N deadline breaker, seconds (off by default; see `harness verify budget`)
|
|
58
58
|
// --cwd project root (default: process.cwd())
|
|
59
59
|
// --force re-init over an existing run receipt
|
|
60
60
|
//
|
|
@@ -68,15 +68,16 @@
|
|
|
68
68
|
// that takes a phase, not an init-run flag that takes a slug: the one instruction available at the
|
|
69
69
|
// one moment it mattered named a mechanism that does not parse.
|
|
70
70
|
|
|
71
|
-
import { mkdirSync, writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
72
|
-
import { join, dirname } from "node:path";
|
|
71
|
+
import { mkdirSync, writeFileSync, readFileSync, readdirSync, existsSync } from "node:fs";
|
|
72
|
+
import { join, dirname, resolve } from "node:path";
|
|
73
73
|
import { createHash } from "node:crypto";
|
|
74
|
-
import { decideLane, treeSize } from "./fit
|
|
75
|
-
import {
|
|
76
|
-
import {
|
|
77
|
-
import {
|
|
78
|
-
import {
|
|
79
|
-
import { localRoot, activeScope, globLocal, globShared } from "
|
|
74
|
+
import { decideLane, treeSize } from "./fit.mjs";
|
|
75
|
+
import { runArgs } from "../lib/argv.mjs";
|
|
76
|
+
import { uncoerce } from "../lib/contract.mjs";
|
|
77
|
+
import { deriveSnapshot } from "../reduce/snapshot.mjs";
|
|
78
|
+
import { mintRunId } from "../lib/paths.mjs";
|
|
79
|
+
import { localRoot, activeScope, globLocal, globShared, ordersDir, resultsDir } from "../lib/paths.mjs";
|
|
80
|
+
import { resolveWorkers } from "../verify/skills.mjs";
|
|
80
81
|
|
|
81
82
|
export const RECEIPT_VERSION = 1;
|
|
82
83
|
|
|
@@ -129,24 +130,43 @@ export function digest(text) {
|
|
|
129
130
|
* Build the receipt record. Pure — takes resolved inputs, returns the object that gets written.
|
|
130
131
|
* Kept separate from I/O so the structural tests can assert its shape without a filesystem.
|
|
131
132
|
*/
|
|
132
|
-
export function buildReceipt({ slug, intake, config, startedAt }) {
|
|
133
|
+
export function buildReceipt({ slug, intake, config, startedAt, plugin = null }) {
|
|
133
134
|
const intakeText = String(intake ?? "");
|
|
135
|
+
const intakeSha256 = digest(intakeText);
|
|
134
136
|
return {
|
|
135
137
|
receipt_version: RECEIPT_VERSION,
|
|
136
138
|
type: "harness-run-receipt",
|
|
137
139
|
slug,
|
|
140
|
+
// THE JOIN KEY, minted here because this is where a run acquires an identity at all.
|
|
141
|
+
//
|
|
142
|
+
// `order_id` was the nearest thing the harness had, and it is `<slug>/r<N>-a<M>`: unique
|
|
143
|
+
// WITHIN a run and identical across every run of the same slug. So every record the pipeline
|
|
144
|
+
// writes — orders, results, journal rows, trial rows, hook receipts — could be grouped by
|
|
145
|
+
// feature and never by RUN, which makes "compare this run against the last one" and "what did
|
|
146
|
+
// this run cost" both unanswerable from data that was otherwise all present.
|
|
147
|
+
//
|
|
148
|
+
// Derived, not drawn (see `mintRunId` in lib/paths.mjs): a pure function of the three fields below
|
|
149
|
+
// it, so any writer holding this receipt recomputes the same id without being handed it, and a
|
|
150
|
+
// receipt written before this field existed still yields the id it would have been given.
|
|
151
|
+
run_id: mintRunId({ slug, startedAt, intakeSha256 }),
|
|
138
152
|
started_at: startedAt,
|
|
139
|
-
intake_sha256:
|
|
153
|
+
intake_sha256: intakeSha256,
|
|
140
154
|
intake_chars: intakeText.length,
|
|
141
155
|
intake_lines: intakeText ? intakeText.split("\n").length : 0,
|
|
142
156
|
// The single fact that separates "the harness ran" from "the harness described itself".
|
|
143
157
|
// Written before any gate, so its ABSENCE at Stop is unambiguous.
|
|
144
158
|
started: true,
|
|
159
|
+
// WHICH COPY OF THE PLUGIN PRODUCED THIS RUN. Recorded while it is in hand, because afterwards
|
|
160
|
+
// it is unrecoverable: every artifact a run leaves looks identical whether it came from this
|
|
161
|
+
// version, a stale marketplace install, or a sub-agent improvising past a failed dispatch. A
|
|
162
|
+
// run whose trace cannot name its own plugin cannot be compared with another run, and cannot be
|
|
163
|
+
// cleared of the wrong-version failure the roster check exists to catch.
|
|
164
|
+
plugin: plugin ? { name: plugin.name, version: plugin.version, root: plugin.root } : null,
|
|
145
165
|
config,
|
|
146
166
|
};
|
|
147
167
|
}
|
|
148
168
|
|
|
149
|
-
/** The `harness-run.md` frontmatter block, per references/
|
|
169
|
+
/** The `harness-run.md` frontmatter block, per references/protocol.md Part 4 — State. */
|
|
150
170
|
export function runFrontmatter({ slug, config, startedAt }) {
|
|
151
171
|
return [
|
|
152
172
|
"---",
|
|
@@ -175,7 +195,7 @@ export function runFrontmatter({ slug, config, startedAt }) {
|
|
|
175
195
|
"",
|
|
176
196
|
`# Harness run — ${slug}`,
|
|
177
197
|
"",
|
|
178
|
-
"Opened by
|
|
198
|
+
"Opened by ``harness init run`` (GATE L0.1). The tech lead is the sole writer from here on.",
|
|
179
199
|
"",
|
|
180
200
|
"## Rounds",
|
|
181
201
|
"",
|
|
@@ -191,15 +211,62 @@ export function runFrontmatter({ slug, config, startedAt }) {
|
|
|
191
211
|
].join("\n");
|
|
192
212
|
}
|
|
193
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Unwedge `--force`: resolve every order left LIVE by a run nobody is continuing.
|
|
216
|
+
*
|
|
217
|
+
* `hooks/sandbox-guard.mjs`'s `liveOrders()` treats any file under `orders/` with no SAME-NAMED
|
|
218
|
+
* file under `results/` as live, and constrains every later Edit/Write to what some live order's
|
|
219
|
+
* substrate permits. `--force` used to `mkdirSync(..., {recursive:true})` over the same directories
|
|
220
|
+
* and stop — a no-op on dirs that already exist and already hold the stale order, so the wedge
|
|
221
|
+
* survived it exactly as before. This is the real unwedge path: for every order abandoned by the run
|
|
222
|
+
* being forced over, write a same-named record under `results/` so `liveOrders()` no longer counts
|
|
223
|
+
* it, without deleting the order file itself (`orders/` is this codebase's own audit trail of what
|
|
224
|
+
* was dispatched, not a rolling buffer — losing the file loses the record that a dispatch happened).
|
|
225
|
+
*
|
|
226
|
+
* Not a real WorkResult: `work-result.schema.json`'s `status` enum (done/partial/escalated/failed)
|
|
227
|
+
* has no member that honestly means "no worker ever answered" — every one of those values would
|
|
228
|
+
* misrepresent an abandoned dispatch as an attempt that actually ran. So this writes a plainly
|
|
229
|
+
* self-labelled, intentionally non-conforming marker instead of forcing a lie into a schema-valid
|
|
230
|
+
* shape. `liveOrders()` only checks filename presence under `results/`, never content, so this is
|
|
231
|
+
* sufficient to unwedge on its own.
|
|
232
|
+
*
|
|
233
|
+
* @param {string} cwd - Project root.
|
|
234
|
+
* @param {string} slug - The run being forced over.
|
|
235
|
+
* @returns {string[]} Order-id suffixes (filenames minus `.json`) resolved as abandoned.
|
|
236
|
+
*/
|
|
237
|
+
export function resolveAbandonedOrders(cwd, slug) {
|
|
238
|
+
const oDir = ordersDir(cwd, slug);
|
|
239
|
+
const rDir = resultsDir(cwd, slug);
|
|
240
|
+
if (!existsSync(oDir)) return [];
|
|
241
|
+
const done = new Set(existsSync(rDir) ? readdirSync(rDir) : []);
|
|
242
|
+
const resolvedAt = new Date().toISOString();
|
|
243
|
+
const resolved = [];
|
|
244
|
+
for (const f of readdirSync(oDir)) {
|
|
245
|
+
if (!f.endsWith(".json") || done.has(f)) continue;
|
|
246
|
+
mkdirSync(rDir, { recursive: true });
|
|
247
|
+
const marker = {
|
|
248
|
+
synthetic: true,
|
|
249
|
+
status: "abandoned", // not in work-result.schema.json's enum — deliberately: see banner above
|
|
250
|
+
order_id: f.slice(0, -".json".length),
|
|
251
|
+
reason: "dispatched, never answered — resolved by `harness init run --force`",
|
|
252
|
+
resolved_at: resolvedAt,
|
|
253
|
+
};
|
|
254
|
+
writeFileSync(join(rDir, f), JSON.stringify(marker, null, 2) + "\n", "utf8");
|
|
255
|
+
resolved.push(marker.order_id);
|
|
256
|
+
}
|
|
257
|
+
return resolved;
|
|
258
|
+
}
|
|
259
|
+
|
|
194
260
|
// ---- CLI -------------------------------------------------------------------
|
|
195
261
|
|
|
196
262
|
/** The typed argv contract (see `./lib/argv.mjs`). */
|
|
197
263
|
export const ARGV_SPEC = {
|
|
198
|
-
usage: '
|
|
264
|
+
usage: 'harness.mjs init run (--intake-file <path> | --intake-text "<req>" | --intake-stdin) ' +
|
|
199
265
|
"[--slug <slug>] [--auto-level interactive|auto|unattended] [--lens <lens>] " +
|
|
200
266
|
"[--max-rounds N] [--attempts N] [--spec-folder <dir>] [--dimensions <a,b>] " +
|
|
201
267
|
"[--gate-answers <preset|path>] " +
|
|
202
|
-
"[--lane full|tiny] [--tiny] [--wall-clock-budget <seconds>] [--cwd <dir>]
|
|
268
|
+
"[--lane full|tiny] [--tiny] [--wall-clock-budget <seconds>] [--cwd <dir>] " +
|
|
269
|
+
"[--plugin-root <dir>] [--force]",
|
|
203
270
|
_: { arity: 0, max: 0, name: "(no positional operands)" },
|
|
204
271
|
cwd: { type: "path" },
|
|
205
272
|
"intake-text": { type: "str" },
|
|
@@ -216,6 +283,11 @@ export const ARGV_SPEC = {
|
|
|
216
283
|
lane: { type: "str" },
|
|
217
284
|
tiny: { type: "flag" },
|
|
218
285
|
"wall-clock-budget": { type: "int", min: 1 },
|
|
286
|
+
// Which copy of the plugin this run is opened against. Defaults to the one this kernel is part
|
|
287
|
+
// of, which is right for every ordinary invocation; it is nameable because a machine can carry
|
|
288
|
+
// several installs, and because the roster refusal below is otherwise unreachable for a test —
|
|
289
|
+
// a check whose failure path cannot be exercised is a check nobody has seen work.
|
|
290
|
+
"plugin-root": { type: "path" },
|
|
219
291
|
force: { type: "flag" },
|
|
220
292
|
};
|
|
221
293
|
|
|
@@ -224,10 +296,45 @@ function fail(code, msg) {
|
|
|
224
296
|
process.exit(code);
|
|
225
297
|
}
|
|
226
298
|
|
|
227
|
-
|
|
228
|
-
|
|
299
|
+
/**
|
|
300
|
+
* Open a run: mint the receipt, or refuse (exit 3) when one is already live.
|
|
301
|
+
*
|
|
302
|
+
* @param {string[]} rawArgv - The subcommand's own arguments (harness.mjs strips the verb words).
|
|
303
|
+
* @returns {(Promise<void>|void)} Settles when the subcommand has written its output; most paths
|
|
304
|
+
* call `process.exit()` with the subcommand's documented code rather than returning.
|
|
305
|
+
*/
|
|
306
|
+
export function cli(rawArgv) {
|
|
307
|
+
const args = runArgs(ARGV_SPEC, rawArgv);
|
|
229
308
|
const cwd = args.cwd || process.cwd();
|
|
230
309
|
|
|
310
|
+
// GATE L0 — the worker roster, before any spend.
|
|
311
|
+
//
|
|
312
|
+
// HERE AND NOT IN THE WORKFLOW SCRIPT, for two reasons. This is what GATE L0 actually executes,
|
|
313
|
+
// and it covers both lanes rather than only the orchestrated one — the prose/`--tiny` lane opens
|
|
314
|
+
// its run through exactly this call. And it is genuinely before any spend: the workflow script's
|
|
315
|
+
// own helpers reach the kernel by spawning a sub-agent first, so a check placed there has already
|
|
316
|
+
// paid for a model call before it can refuse.
|
|
317
|
+
//
|
|
318
|
+
// What it can and cannot prove is stated in verify/skills.mjs and is worth repeating where it is
|
|
319
|
+
// enforced: this says the files exist at this root at this version. It does not say the SESSION
|
|
320
|
+
// will resolve that copy. The orchestrator's canary dispatch answers that, and its receipt is the
|
|
321
|
+
// evidence; this refusal is the cheap half that costs nothing to run on every single run.
|
|
322
|
+
const plugin = resolveWorkers(args.pluginRoot ? resolve(args.pluginRoot) : undefined);
|
|
323
|
+
if (plugin.missing.length) {
|
|
324
|
+
fail(3, [
|
|
325
|
+
`✋ init-run: refusing to open a run — ${plugin.missing.length} of ${plugin.workers.length} worker skills are missing.`,
|
|
326
|
+
"",
|
|
327
|
+
` plugin: ${plugin.name ?? "unknown"} ${plugin.version ?? "unknown version"}`,
|
|
328
|
+
` root: ${plugin.root}`,
|
|
329
|
+
` missing: ${plugin.missing.join(", ")}`,
|
|
330
|
+
"",
|
|
331
|
+
"A run against this copy would dispatch workers that cannot resolve, and a failed dispatch is",
|
|
332
|
+
"answered by the sub-agent improvising the craft itself — phases reporting complete with none",
|
|
333
|
+
"of the shipped craft applied. Load the working copy (`claude --plugin-dir <repo>`) or install",
|
|
334
|
+
"and enable the plugin, then retry. `harness verify skills` reports the same thing on demand.",
|
|
335
|
+
].join("\n"));
|
|
336
|
+
}
|
|
337
|
+
|
|
231
338
|
let intake = args.intakeText ?? null;
|
|
232
339
|
const intakeFile = args.intakeFile ?? null;
|
|
233
340
|
// `--intake-file -` is the shape everyone reaches for; accept it rather than erroring on a
|
|
@@ -271,7 +378,7 @@ export function main() {
|
|
|
271
378
|
spec_folder: args.specFolder ?? `${globShared(slug, "spec")}/`,
|
|
272
379
|
gate_answers: args.gateAnswers ?? null,
|
|
273
380
|
tiny_lane: !!args.tiny,
|
|
274
|
-
// GATE L0.3 — the lane, computed rather than judged (see fit
|
|
381
|
+
// GATE L0.3 — the lane, computed rather than judged (see `harness init fit`). Recorded with its
|
|
275
382
|
// evidence so a heavy lane on a small change is visible instead of accidental. An explicit
|
|
276
383
|
// --lane or --tiny is honoured and marked as an override, because a measured recommendation
|
|
277
384
|
// fitted on three features must not outrank a human who knows the codebase.
|
|
@@ -282,7 +389,7 @@ export function main() {
|
|
|
282
389
|
? { ...auto, lane: forced, overridden_from: auto.lane, override_source: args.tiny ? "--tiny" : "--lane" }
|
|
283
390
|
: auto;
|
|
284
391
|
})(),
|
|
285
|
-
// The third breaker (see
|
|
392
|
+
// The third breaker (see `harness verify budget`). Null = off, which is the default and
|
|
286
393
|
// keeps every existing run behaving exactly as before. Set it in any lane with a hard clock
|
|
287
394
|
// — CI, an overnight run — so the harness trips its own breaker and ships what
|
|
288
395
|
// is green, instead of being killed from outside and shipping nothing.
|
|
@@ -298,14 +405,14 @@ export function main() {
|
|
|
298
405
|
// <slug>`)". `--from` is not an init-run flag at all; it is a `/tech-lead` flag, and it takes a
|
|
299
406
|
// PHASE (`--from build`), not a slug. So at the one moment the orchestrator most needs a next
|
|
300
407
|
// step, the runtime named a mechanism that does not exist, on a script whose failure mode was
|
|
301
|
-
// already invisible
|
|
408
|
+
// already invisible — under a symlinked install an entry-point guard once skipped this whole body.
|
|
302
409
|
//
|
|
303
410
|
// Observed consequence on a handoff: a fresh session in a workspace with an open run burns most
|
|
304
411
|
// of its budget before its first write, largely on forensics against this step, and closes none
|
|
305
412
|
// of the gap.
|
|
306
413
|
//
|
|
307
414
|
// So the refusal now DOES the resume work instead of describing it. It emits the derived snapshot
|
|
308
|
-
// — the same file-only derivation `
|
|
415
|
+
// — the same file-only derivation `harness reduce graph --subgraph run` injects — so the orchestrator gets
|
|
309
416
|
// slug, status, round, attempt, board counts and pending orders in THIS tool call rather than
|
|
310
417
|
// needing to discover that it needs another one. Exit 3 still means "do not proceed as if you
|
|
311
418
|
// opened a run"; it now also means "here is the run you are actually in".
|
|
@@ -328,13 +435,28 @@ export function main() {
|
|
|
328
435
|
].join("\n"),
|
|
329
436
|
"",
|
|
330
437
|
"To re-derive this at any time:",
|
|
331
|
-
" node <plugin>/
|
|
438
|
+
" node <plugin>/kernel/harness.mjs reduce snapshot --cwd <dir>",
|
|
332
439
|
"To abandon the open run and start over, deliberately: --force",
|
|
333
440
|
].join("\n"));
|
|
334
441
|
}
|
|
335
442
|
|
|
443
|
+
// THE REAL UNWEDGE PATH. `--force` used to `mkdirSync(..., {recursive:true})` over orders/,
|
|
444
|
+
// results/ and discovery/ and stop — a no-op on directories that already exist and already hold a
|
|
445
|
+
// dispatched-but-unanswered order, so `sandbox-guard.mjs`'s `liveOrders()` kept constraining every
|
|
446
|
+
// later write to that stale order's substrate regardless of `--force`. Resolve every such order
|
|
447
|
+
// BEFORE the fresh run starts writing (see `resolveAbandonedOrders()` above for why this writes a
|
|
448
|
+
// marker under `results/` rather than deleting the order file).
|
|
449
|
+
if (args.force) {
|
|
450
|
+
const abandoned = resolveAbandonedOrders(cwd, slug);
|
|
451
|
+
if (abandoned.length) {
|
|
452
|
+
console.error(
|
|
453
|
+
`⚠ init-run --force: resolved ${abandoned.length} dispatched-but-unanswered order(s) as abandoned — ${abandoned.join(", ")}`,
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
336
458
|
const startedAt = new Date().toISOString();
|
|
337
|
-
const receipt = buildReceipt({ slug, intake, config, startedAt });
|
|
459
|
+
const receipt = buildReceipt({ slug, intake, config, startedAt, plugin });
|
|
338
460
|
|
|
339
461
|
mkdirSync(runRoot, { recursive: true });
|
|
340
462
|
mkdirSync(join(runRoot, "orders"), { recursive: true });
|
|
@@ -354,6 +476,8 @@ export function main() {
|
|
|
354
476
|
console.log(JSON.stringify({
|
|
355
477
|
ok: true,
|
|
356
478
|
slug,
|
|
479
|
+
// Echoed so the orchestrator can put it in RunArgs without re-reading the receipt.
|
|
480
|
+
run_id: receipt.run_id,
|
|
357
481
|
run_root: globLocal(slug),
|
|
358
482
|
receipt: globLocal(slug, "receipt.json"),
|
|
359
483
|
intake_sha256: receipt.intake_sha256,
|
|
@@ -363,6 +487,3 @@ export function main() {
|
|
|
363
487
|
}, null, 2));
|
|
364
488
|
}
|
|
365
489
|
|
|
366
|
-
if (isMain(import.meta.url)) {
|
|
367
|
-
main();
|
|
368
|
-
}
|
|
@@ -1,44 +1,27 @@
|
|
|
1
|
-
//
|
|
1
|
+
// argv — the typed CLI boundary: who is running, and with what.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// This project's envelope boundary is rigorously typed: 38 `$defs` in `domain.schema.json`,
|
|
6
|
-
// both directions validated, and a `PreToolUse` hook that DENIES a malformed WorkOrder before a
|
|
7
|
-
// worker ever sees it. That discipline stopped dead at `process.argv` — which is where the
|
|
8
|
-
// pipeline actually executes.
|
|
9
|
-
//
|
|
10
|
-
// The reproduced defect. `t0-verify.mjs` parsed its own flags with
|
|
11
|
-
//
|
|
12
|
-
// if (a === "--round") out.round = Number(argv[++i]); // then: args.round ?? 1
|
|
13
|
-
//
|
|
14
|
-
// `Number(undefined)` is `NaN`, and `??` does not catch `NaN`. So passing a flag WITHOUT a value
|
|
15
|
-
// wrote a real verdict artifact to `t0/verdicts/rNaN-a1.json` and **exited 0**:
|
|
16
|
-
//
|
|
17
|
-
// $ t0-verify.mjs contract.json --round --attempt 1
|
|
18
|
-
// { "path": "t0/verdicts/rNaN-a1.json", "overall": "green", ... } exit=0
|
|
19
|
-
//
|
|
20
|
-
// The orchestrator then looks for `r1-a1.json`, finds nothing, and the evaluator's MANDATORY T0
|
|
21
|
-
// citation cannot resolve — on the single artifact the judge is structurally required to cite.
|
|
22
|
-
// A green verdict at an address nobody will look up is the same failure class as a silent no-op:
|
|
23
|
-
// indistinguishable from working.
|
|
24
|
-
//
|
|
25
|
-
// THE CONTRACT, mirroring `validate-envelope`'s: reject BEFORE anything runs, exit 2, and put a
|
|
3
|
+
// CONTRACT, mirroring the envelope's: reject BEFORE anything runs, exit 2, and put a
|
|
26
4
|
// machine-readable reason on stderr. Nothing here touches the filesystem, so a rejected parse
|
|
27
|
-
// cannot leave a half-written artifact behind.
|
|
5
|
+
// cannot leave a half-written artifact behind. Exit 2 is this plugin's "the input was malformed,
|
|
6
|
+
// nothing ran" code — deliberately NOT 1, which every oracle uses for "ran, and the answer is no".
|
|
28
7
|
//
|
|
29
8
|
// const SPEC = {
|
|
30
9
|
// _: { arity: 1, name: "scope-contract.json" },
|
|
31
10
|
// round: { type: "int", min: 1, required: true },
|
|
32
11
|
// "no-seesaw": { type: "flag" },
|
|
33
12
|
// };
|
|
34
|
-
// const args = runArgs(SPEC,
|
|
13
|
+
// const args = runArgs(SPEC, argv); // args.round, args.noSeesaw, args._
|
|
35
14
|
//
|
|
36
|
-
// Flag names reach the caller camelCased (`--no-seesaw` → `noSeesaw`)
|
|
37
|
-
//
|
|
38
|
-
// a
|
|
15
|
+
// Flag names reach the caller camelCased (`--no-seesaw` → `noSeesaw`). Unknown flags are rejected
|
|
16
|
+
// rather than swallowed as positionals: a typo'd `--rounds 2` landing in `_` is the same defect
|
|
17
|
+
// wearing a different hat. Untyped coercion is the failure this guards — `Number(undefined)` is
|
|
18
|
+
// `NaN`, `??` does not catch `NaN`, and a verdict written to `r NaN-a1.json` with exit 0 is
|
|
19
|
+
// indistinguishable from working.
|
|
39
20
|
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
21
|
+
// {@link isMain} answers the other half of the boundary question: was this module executed, or
|
|
22
|
+
// imported? Comparing `import.meta.url` to a raw `file://${process.argv[1]}` is false under a
|
|
23
|
+
// symlinked install path (macOS `/var`, nvm, pnpm, Homebrew) and under any path containing a space,
|
|
24
|
+
// which silently turns an entry point into a no-op that still exits 0.
|
|
42
25
|
|
|
43
26
|
/** Thrown by {@link parseArgs} when argv does not satisfy the spec. `detail` is the wire shape. */
|
|
44
27
|
export class ArgvError extends Error {
|
|
@@ -222,3 +205,41 @@ export function runArgs(spec, argv = process.argv.slice(2)) {
|
|
|
222
205
|
process.exit(2);
|
|
223
206
|
}
|
|
224
207
|
}
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// isMain — "was this module executed directly, or imported?"
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
import { realpathSync } from "node:fs";
|
|
214
|
+
import { pathToFileURL } from "node:url";
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* True when `moduleUrl` belongs to the module Node was asked to execute.
|
|
218
|
+
*
|
|
219
|
+
* Resolves both sides through `pathToFileURL` (so percent-encoding matches) and `realpathSync` (so
|
|
220
|
+
* symlinks match), which is what makes the guard hold where the naive string compare does not.
|
|
221
|
+
*
|
|
222
|
+
* @param {string} moduleUrl - The caller's `import.meta.url`.
|
|
223
|
+
* @returns {boolean} true if executed directly, false if imported (or if there is no entry point,
|
|
224
|
+
* e.g. `node --eval`, where nothing should auto-run).
|
|
225
|
+
*/
|
|
226
|
+
export function isMain(moduleUrl) {
|
|
227
|
+
const entry = process.argv[1];
|
|
228
|
+
if (!entry || !moduleUrl) return false;
|
|
229
|
+
|
|
230
|
+
// Cheap path first: correct encoding, no filesystem access. Handles spaces and unicode.
|
|
231
|
+
let entryUrl;
|
|
232
|
+
try { entryUrl = pathToFileURL(entry).href; } catch { return false; }
|
|
233
|
+
if (entryUrl === moduleUrl) return true;
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const realEntry = pathToFileURL(realpathSync(entry)).href;
|
|
237
|
+
if (realEntry === moduleUrl) return true;
|
|
238
|
+
return realEntry === pathToFileURL(realpathSync(new URL(moduleUrl))).href;
|
|
239
|
+
} catch {
|
|
240
|
+
// An unreadable or deleted entry point is not this function's problem to report. Returning
|
|
241
|
+
// false means "do not auto-run", which is the safe direction for an imported module and is
|
|
242
|
+
// never the direction that silently skips a gate.
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|