@lannguyensi/harness 0.56.0 → 0.58.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/CHANGELOG.md +186 -0
- package/README.md +1 -1
- package/dist/cli/apply/apply.js +4 -3
- package/dist/cli/apply/apply.js.map +1 -1
- package/dist/cli/apply/generate-codex-config.js +3 -3
- package/dist/cli/apply/generate-codex-config.js.map +1 -1
- package/dist/cli/apply/generate-memory-index.js +20 -10
- package/dist/cli/apply/generate-memory-index.js.map +1 -1
- package/dist/cli/apply/generate-settings.d.ts +7 -0
- package/dist/cli/apply/generate-settings.js +10 -1
- package/dist/cli/apply/generate-settings.js.map +1 -1
- package/dist/cli/approve/understanding.d.ts +4 -4
- package/dist/cli/doctor/format.js +68 -2
- package/dist/cli/doctor/format.js.map +1 -1
- package/dist/cli/doctor/index.d.ts +6 -0
- package/dist/cli/doctor/index.js +214 -28
- package/dist/cli/doctor/index.js.map +1 -1
- package/dist/cli/doctor/session-start-preflight-setup-version.d.ts +83 -0
- package/dist/cli/doctor/session-start-preflight-setup-version.js +114 -0
- package/dist/cli/doctor/session-start-preflight-setup-version.js.map +1 -0
- package/dist/cli/doctor/types.d.ts +35 -4
- package/dist/cli/doctor/types.js +2 -1
- package/dist/cli/doctor/types.js.map +1 -1
- package/dist/cli/doctor/ug-delegations.js +9 -0
- package/dist/cli/doctor/ug-delegations.js.map +1 -1
- package/dist/cli/explain-policy.d.ts +67 -0
- package/dist/cli/explain-policy.js +140 -3
- package/dist/cli/explain-policy.js.map +1 -1
- package/dist/cli/gc/index.js +28 -14
- package/dist/cli/gc/index.js.map +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +10 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/composer.js +4 -3
- package/dist/cli/init/composer.js.map +1 -1
- package/dist/cli/init/dependencies.js +15 -9
- package/dist/cli/init/dependencies.js.map +1 -1
- package/dist/cli/init/templates.d.ts +1 -0
- package/dist/cli/init/templates.js +30 -4
- package/dist/cli/init/templates.js.map +1 -1
- package/dist/cli/list.js +19 -0
- package/dist/cli/list.js.map +1 -1
- package/dist/cli/loader.js +16 -1
- package/dist/cli/loader.js.map +1 -1
- package/dist/cli/pack/hook-bootstrap.js +2 -1
- package/dist/cli/pack/hook-bootstrap.js.map +1 -1
- package/dist/cli/pack/hook-codex-user-prompt-submit.d.ts +0 -32
- package/dist/cli/pack/hook-codex-user-prompt-submit.js +11 -118
- package/dist/cli/pack/hook-codex-user-prompt-submit.js.map +1 -1
- package/dist/cli/pack/hook-pre-tool-use.js +34 -7
- package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-solution-acceptance.d.ts +55 -0
- package/dist/cli/pack/hook-solution-acceptance.js +222 -16
- package/dist/cli/pack/hook-solution-acceptance.js.map +1 -1
- package/dist/cli/pack/hook-stay-in-scope.d.ts +5 -26
- package/dist/cli/pack/hook-stay-in-scope.js +62 -274
- package/dist/cli/pack/hook-stay-in-scope.js.map +1 -1
- package/dist/cli/pack/hook-track-active-claim.d.ts +1 -0
- package/dist/cli/pack/hook-track-active-claim.js +107 -43
- package/dist/cli/pack/hook-track-active-claim.js.map +1 -1
- package/dist/cli/session-start/index.d.ts +32 -1
- package/dist/cli/session-start/index.js +142 -12
- package/dist/cli/session-start/index.js.map +1 -1
- package/dist/cli/validate/checks.d.ts +0 -2
- package/dist/cli/validate/checks.js +30 -30
- package/dist/cli/validate/checks.js.map +1 -1
- package/dist/io/harness-lock.js +11 -1
- package/dist/io/harness-lock.js.map +1 -1
- package/dist/io/lock.d.ts +26 -0
- package/dist/io/lock.js +25 -0
- package/dist/io/lock.js.map +1 -1
- package/dist/io/project-name.d.ts +115 -0
- package/dist/io/project-name.js +158 -0
- package/dist/io/project-name.js.map +1 -0
- package/dist/io/read-json-dir-entries.js +10 -0
- package/dist/io/read-json-dir-entries.js.map +1 -1
- package/dist/io/version-compare.d.ts +59 -0
- package/dist/io/version-compare.js +67 -0
- package/dist/io/version-compare.js.map +1 -1
- package/dist/policy-packs/builtin/solution-acceptance-reconnect.d.ts +50 -0
- package/dist/policy-packs/builtin/solution-acceptance-reconnect.js +151 -0
- package/dist/policy-packs/builtin/solution-acceptance-reconnect.js.map +1 -0
- package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +43 -4
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js +43 -39
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/solution-acceptance.js +4 -2
- package/dist/policy-packs/builtin/solution-acceptance.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js +15 -0
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js +5 -7
- package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/markers.js +23 -0
- package/dist/policy-packs/builtin/understanding-before-execution/markers.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js +8 -17
- package/dist/policy-packs/builtin/understanding-before-execution/post-tool-use-boundary.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.d.ts +129 -0
- package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.js +166 -0
- package/dist/policy-packs/builtin/understanding-before-execution/stay-in-scope-config.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution.d.ts +129 -0
- package/dist/policy-packs/builtin/understanding-before-execution.js +44 -57
- package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
- package/dist/policy-packs/version-check.d.ts +8 -1
- package/dist/policy-packs/version-check.js +10 -6
- package/dist/policy-packs/version-check.js.map +1 -1
- package/dist/probes/memory.d.ts +34 -0
- package/dist/probes/memory.js +87 -14
- package/dist/probes/memory.js.map +1 -1
- package/dist/runtime/git-context.d.ts +125 -0
- package/dist/runtime/git-context.js +164 -1
- package/dist/runtime/git-context.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/task-providers/agent-tasks.d.ts +90 -0
- package/dist/runtime/task-providers/agent-tasks.js +231 -0
- package/dist/runtime/task-providers/agent-tasks.js.map +1 -0
- package/dist/runtime/workflow-policies.d.ts +1 -0
- package/dist/runtime/workflow-policies.js +8 -8
- package/dist/runtime/workflow-policies.js.map +1 -1
- package/dist/schema/index.d.ts +19 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/session-start-preflight.d.ts +10 -0
- package/dist/schema/session-start-preflight.js +106 -0
- package/dist/schema/session-start-preflight.js.map +1 -0
- package/package.json +7 -2
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// `{project}` value validation and safe display, shared by every
|
|
2
|
+
// `{project}`-shaped interpolation sink.
|
|
3
|
+
//
|
|
4
|
+
// Lives in `src/io/` rather than `src/runtime/` (where `isValidProjectName`
|
|
5
|
+
// and `sanitizeProjectForDisplay` originated, task `c88461c1`) because
|
|
6
|
+
// `buildLockEntries` (`src/io/harness-lock.ts`, task `b5e6ccb0`) is itself
|
|
7
|
+
// one of those sinks, and `io/` may not import from `runtime/`
|
|
8
|
+
// (`io-no-upward-imports`, `.dependency-cruiser.cjs`). This is the same
|
|
9
|
+
// relocation pattern `.dependency-cruiser.cjs`'s header comment already
|
|
10
|
+
// documents for `src/policies/duration.ts`, `src/policies/extract.ts`,
|
|
11
|
+
// `src/runtime/ledger-record.ts` and `src/runtime/expand-home.ts`: a
|
|
12
|
+
// utility needed below its original layer moves down rather than the
|
|
13
|
+
// importing layer's boundary being relaxed. `src/runtime/git-context.ts`
|
|
14
|
+
// re-exports both names so every existing consumer that imports them from
|
|
15
|
+
// `../runtime/git-context.js` keeps working unchanged.
|
|
16
|
+
/**
|
|
17
|
+
* Every C0 control character (U+0000 to U+001F, so NUL, LF, CR and ESC
|
|
18
|
+
* among them), DEL (U+007F), and every C1 control character (U+0080 to
|
|
19
|
+
* U+009F, NEL included). Shared by {@link isValidProjectName}, which
|
|
20
|
+
* rejects a name carrying one, and {@link sanitizeProjectForDisplay},
|
|
21
|
+
* which strips them from a name that was already rejected and is being
|
|
22
|
+
* echoed back.
|
|
23
|
+
*/
|
|
24
|
+
const PROJECT_NAME_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/;
|
|
25
|
+
/**
|
|
26
|
+
* {@link PROJECT_NAME_CONTROL_CHARS} with the `g` flag, for
|
|
27
|
+
* `String.prototype.replace`. Two objects built from one source rather
|
|
28
|
+
* than one shared `g`-flagged regex, because a `g`-flagged regex carries
|
|
29
|
+
* a stateful `lastIndex` that makes `.test()` alternate between hits and
|
|
30
|
+
* misses across calls.
|
|
31
|
+
*/
|
|
32
|
+
const PROJECT_NAME_CONTROL_CHARS_GLOBAL = new RegExp(PROJECT_NAME_CONTROL_CHARS.source, "g");
|
|
33
|
+
/**
|
|
34
|
+
* Is `name` usable as a `{project}` value at all: both as an on-disk
|
|
35
|
+
* directory component (`<home>/projects/<name>/harness.overrides.yaml`,
|
|
36
|
+
* `resolvePaths`, `src/cli/loader.ts`) and as a value that will end up
|
|
37
|
+
* inside rendered CLI output? Two independent screens, both applied here
|
|
38
|
+
* at the SOURCE rather than at each consumer:
|
|
39
|
+
*
|
|
40
|
+
* - PATH ESCAPE. Rejects the empty string, `"."`, and `".."` (the two
|
|
41
|
+
* `path.join` special-cases that either no-op or climb a level,
|
|
42
|
+
* `".."` being exactly the shape a crafted, un-normalized `commondir`
|
|
43
|
+
* used to produce before the fix above), plus any name containing a
|
|
44
|
+
* forward slash or a backslash (Windows separator; POSIX permits a
|
|
45
|
+
* literal backslash IN a directory name, so this is not redundant
|
|
46
|
+
* with the platform's own path parsing).
|
|
47
|
+
* - CONTROL CHARACTERS. Rejects any name containing one of
|
|
48
|
+
* {@link PROJECT_NAME_CONTROL_CHARS}, NUL included (which is why
|
|
49
|
+
* there is no separate NUL clause). A newline would forge an extra,
|
|
50
|
+
* diagnostic-looking line of caller-chosen text wherever the value is
|
|
51
|
+
* rendered, an ESC would write raw terminal escapes into the
|
|
52
|
+
* operator's terminal, and a CR would overwrite the line it landed
|
|
53
|
+
* on. Screening here rather than at each rendering site is what makes
|
|
54
|
+
* "an accepted project name is a plain single-line string" a property
|
|
55
|
+
* of the VALUE: it also covers the surfaces that render the
|
|
56
|
+
* SUBSTITUTED memory directory path rather than the name itself
|
|
57
|
+
* (`harness doctor`'s "memory directory missing" line,
|
|
58
|
+
* `src/cli/doctor/format.ts`; `harness list memories`' `path` row
|
|
59
|
+
* field, `src/cli/list.ts`), which no per-site escaper of the name
|
|
60
|
+
* would ever have reached.
|
|
61
|
+
*
|
|
62
|
+
* A name that passes both screens may still not exist on disk
|
|
63
|
+
* (`resolvePaths` already handles that with `fs.existsSync`); this only
|
|
64
|
+
* guards against the value escaping the single path segment it is meant
|
|
65
|
+
* to occupy and against it carrying control characters anywhere
|
|
66
|
+
* downstream.
|
|
67
|
+
*
|
|
68
|
+
* WHAT A REJECTION LOOKS LIKE. `deriveProjectName` (`src/runtime/git-
|
|
69
|
+
* context.ts`) validates its own return value at every exit, so a
|
|
70
|
+
* checkout directory whose basename contains a control character now
|
|
71
|
+
* takes the same path a `".."` basename already took: the helper returns
|
|
72
|
+
* `null`, no project override layer is looked up for that repository,
|
|
73
|
+
* and the base/machine value applies unchanged. There is no error and no
|
|
74
|
+
* warning; the operator sees the host-wide value instead of a per-repo
|
|
75
|
+
* one (`harness doctor`'s setup-version finding then carries
|
|
76
|
+
* `projectName: null` and renders no `(project: X)` suffix). An explicit
|
|
77
|
+
* `--project` carrying a control character is instead reported:
|
|
78
|
+
* `resolvePaths` resolves no project layer for it, and
|
|
79
|
+
* `substituteProject` (`src/probes/memory.ts`) degrades to the "no
|
|
80
|
+
* project supplied" branch, which `harness doctor` surfaces as its
|
|
81
|
+
* rejected-`--project` warn line.
|
|
82
|
+
*
|
|
83
|
+
* Exported (task c88461c1, review round 3 residual; task `1c4eb3ea`
|
|
84
|
+
* of the batch-44 follow-up run) so `resolvePaths` (`src/cli/loader.ts`)
|
|
85
|
+
* can apply the SAME guard at its own `path.join` sink, defense in
|
|
86
|
+
* depth: this function already rejects an unsafe name at every
|
|
87
|
+
* `deriveProjectName` exit, but an `opts.project` reaching
|
|
88
|
+
* `resolvePaths`' OWN sink from anywhere else (a caller building
|
|
89
|
+
* `LoaderOptions` by hand, a future producer) had no equivalent check
|
|
90
|
+
* of its own until now. Applied a THIRD time at `substituteProject`'s
|
|
91
|
+
* own sink (`src/probes/memory.ts`, task `e904f25a`): an invalid name
|
|
92
|
+
* there degrades to the "no project supplied" branch instead of being
|
|
93
|
+
* interpolated into the `{project}` placeholder. Applied a FOURTH time
|
|
94
|
+
* at `buildLockEntries`' own sink (`src/io/harness-lock.ts`, task
|
|
95
|
+
* `b5e6ccb0`): an invalid name there skips the templated directory's
|
|
96
|
+
* lock entry instead of the raw value being interpolated into the path
|
|
97
|
+
* this function stats, hashes and writes into `harness.lock`.
|
|
98
|
+
* `resolvePaths`, `substituteProject` and `buildLockEntries` are now all
|
|
99
|
+
* guarded; one `{project}` sink is NOT: `generate-memory-index.ts`'s own
|
|
100
|
+
* substitution still interpolates an operator-supplied `--project` value
|
|
101
|
+
* into a path unvalidated, left unguarded (reached only via the explicit
|
|
102
|
+
* CLI flag, not this module's derivation; see CHANGELOG.md for the
|
|
103
|
+
* reservation/follow-up). That one call site calls this function
|
|
104
|
+
* nowhere, so it inherits NOTHING from the control-character screen
|
|
105
|
+
* above either: a caller reading this comment should not assume this
|
|
106
|
+
* function guards every `{project}`-shaped sink in the codebase, for
|
|
107
|
+
* path escapes or for control characters.
|
|
108
|
+
*/
|
|
109
|
+
export function isValidProjectName(name) {
|
|
110
|
+
if (name.length === 0)
|
|
111
|
+
return false;
|
|
112
|
+
if (name === "." || name === "..")
|
|
113
|
+
return false;
|
|
114
|
+
if (PROJECT_NAME_CONTROL_CHARS.test(name))
|
|
115
|
+
return false;
|
|
116
|
+
return !name.includes("/") && !name.includes("\\");
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Renders a REJECTED project name safe to interpolate into one line of
|
|
120
|
+
* CLI output. The invariant this helper is half of has two parts, and
|
|
121
|
+
* they are complementary rather than overlapping (task `e904f25a`):
|
|
122
|
+
*
|
|
123
|
+
* - An ACCEPTED name cannot carry a control character at all.
|
|
124
|
+
* {@link isValidProjectName} rejects C0, DEL and C1 at the source, so
|
|
125
|
+
* every value that survives validation is already a plain
|
|
126
|
+
* single-line string wherever it, or a path it was substituted into,
|
|
127
|
+
* is rendered. Nothing downstream of the validator has to enumerate
|
|
128
|
+
* its own rendering sites to stay safe.
|
|
129
|
+
* - A REJECTED name is by definition unvalidated, and it is still
|
|
130
|
+
* echoed back so the operator can see WHICH value was refused. Every
|
|
131
|
+
* such echo goes through this helper: `harness doctor`'s
|
|
132
|
+
* rejected-`--project` warn line and its per-directory note
|
|
133
|
+
* (`src/cli/doctor/format.ts`), doctor's header `project:` clause
|
|
134
|
+
* (which renders the raw `--project` whether it was accepted or
|
|
135
|
+
* rejected), and `harness list memories`' `project_rejected` row
|
|
136
|
+
* field (`src/cli/list.ts`, and with it the text table derived from
|
|
137
|
+
* it).
|
|
138
|
+
*
|
|
139
|
+
* The `session_start_preflight.setup` finding's `(project: X)` suffix is
|
|
140
|
+
* also wrapped, as defense in depth rather than as an echo of a rejected
|
|
141
|
+
* value: `harness doctor` sets that finding's `projectName` only when
|
|
142
|
+
* `resolvePaths` actually resolved a project layer FILE, which requires
|
|
143
|
+
* the name to have passed {@link isValidProjectName} first. The wrap
|
|
144
|
+
* stays so a hand-built finding or a future producer cannot reintroduce
|
|
145
|
+
* a forged line through that one call site.
|
|
146
|
+
*
|
|
147
|
+
* Strips (rather than escapes) every character in
|
|
148
|
+
* {@link PROJECT_NAME_CONTROL_CHARS}. A name with no control character
|
|
149
|
+
* (`..`, `a/b`, every ordinary shape) passes through unchanged. Report
|
|
150
|
+
* DATA keeps the raw value (`DoctorReport.project`,
|
|
151
|
+
* `MemoryReport.projectRejected`), so a `--json` consumer still reads
|
|
152
|
+
* back exactly what the operator passed, carried by JSON's own escaping
|
|
153
|
+
* rather than by a raw control byte.
|
|
154
|
+
*/
|
|
155
|
+
export function sanitizeProjectForDisplay(name) {
|
|
156
|
+
return name.replace(PROJECT_NAME_CONTROL_CHARS_GLOBAL, "");
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=project-name.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-name.js","sourceRoot":"","sources":["../../src/io/project-name.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,yCAAyC;AACzC,EAAE;AACF,4EAA4E;AAC5E,uEAAuE;AACvE,2EAA2E;AAC3E,+DAA+D;AAC/D,wEAAwE;AACxE,wEAAwE;AACxE,uEAAuE;AACvE,qEAAqE;AACrE,qEAAqE;AACrE,yEAAyE;AACzE,0EAA0E;AAC1E,uDAAuD;AAEvD;;;;;;;GAOG;AACH,MAAM,0BAA0B,GAAG,8BAA8B,CAAC;AAElE;;;;;;GAMG;AACH,MAAM,iCAAiC,GAAG,IAAI,MAAM,CAAC,0BAA0B,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE7F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAChD,IAAI,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACxD,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAY;IACpD,OAAO,IAAI,CAAC,OAAO,CAAC,iCAAiC,EAAE,EAAE,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -3,6 +3,16 @@ import * as path from "node:path";
|
|
|
3
3
|
import { readRegularFileRejectingSymlink } from "./read-regular-file.js";
|
|
4
4
|
import { safeJsonParse } from "./safe-json-parse.js";
|
|
5
5
|
export function readJsonDirEntriesRejectingSymlinks(dir, opts) {
|
|
6
|
+
let dirStat;
|
|
7
|
+
try {
|
|
8
|
+
dirStat = fs.lstatSync(dir);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return { dirPresent: false, entries: [], unreadableCount: 0 };
|
|
12
|
+
}
|
|
13
|
+
if (!dirStat.isDirectory()) {
|
|
14
|
+
return { dirPresent: false, entries: [], unreadableCount: 0 };
|
|
15
|
+
}
|
|
6
16
|
let dirents;
|
|
7
17
|
try {
|
|
8
18
|
dirents = fs.readdirSync(dir, { withFileTypes: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-json-dir-entries.js","sourceRoot":"","sources":["../../src/io/read-json-dir-entries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAuCrD,MAAM,UAAU,mCAAmC,CACjD,GAAW,EACX,IAAkC;IAElC,IAAI,OAAoB,CAAC;IACzB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;IAChE,CAAC;IAED,MAAM,OAAO,GAAQ,EAAE,CAAC;IACxB,IAAI,eAAe,GAAG,CAAC,CAAC;IAExB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;QACpB,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC;YAAE,SAAS;QAEhC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,+BAA+B,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACtF,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAC/B,eAAe,EAAE,CAAC;YAClB,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACvC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,eAAe,EAAE,CAAC;YAClB,SAAS;QACX,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC;AACxD,CAAC"}
|
|
1
|
+
{"version":3,"file":"read-json-dir-entries.js","sourceRoot":"","sources":["../../src/io/read-json-dir-entries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAuCrD,MAAM,UAAU,mCAAmC,CACjD,GAAW,EACX,IAAkC;IAElC,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;IAChE,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;QAC3B,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;IAChE,CAAC;IACD,IAAI,OAAoB,CAAC;IACzB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;IAChE,CAAC;IAED,MAAM,OAAO,GAAQ,EAAE,CAAC;IACxB,IAAI,eAAe,GAAG,CAAC,CAAC;IAExB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC;QACpB,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC;YAAE,SAAS;QAEhC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,+BAA+B,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACtF,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAC/B,eAAe,EAAE,CAAC;YAClB,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACvC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,eAAe,EAAE,CAAC;YAClB,SAAS;QACX,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC;AACxD,CAAC"}
|
|
@@ -22,3 +22,62 @@
|
|
|
22
22
|
export declare const NUMERIC_VERSION_PATTERN: RegExp;
|
|
23
23
|
export declare const NUMERIC_VERSION_MESSAGE = "min_version must be numeric semver-shape: digits separated by up to three dots (e.g. \"1\", \"1.2\", \"1.2.3\", \"1.2.3.4\"). Pre-release suffixes and leading \"v\" are rejected.";
|
|
24
24
|
export declare function compareNumericVersions(a: string, b: string): number;
|
|
25
|
+
/**
|
|
26
|
+
* Extracts a numeric-semver-shape version from free-form `--version`
|
|
27
|
+
* probe output (e.g. "preflight 0.6.0-rc.1\n"), plus whether the
|
|
28
|
+
* matched numeric run was immediately followed by a `-` prerelease/
|
|
29
|
+
* build suffix. Matches ONLY the leading numeric run itself (mirrors
|
|
30
|
+
* the pre-existing `/(\d+(?:\.\d+){0,3})/` extraction every version
|
|
31
|
+
* floor check used before task 65952a0c), so `version` here is exactly
|
|
32
|
+
* what a plain, non-prerelease-aware caller would have parsed; the
|
|
33
|
+
* `isPrerelease` flag is the new information a caller can act on.
|
|
34
|
+
*
|
|
35
|
+
* Returns `null` when no numeric run is found at all (unparseable
|
|
36
|
+
* probe output), matching the pre-existing "no match" contract.
|
|
37
|
+
*
|
|
38
|
+
* Two suffix strings are returned, deliberately different:
|
|
39
|
+
* - `raw` is the regex's own match (`m[0]`): the numeric run plus a
|
|
40
|
+
* `-` suffix restricted to `[0-9A-Za-z.]`, so it TRUNCATES a suffix
|
|
41
|
+
* containing any other character (a git-describe suffix's second
|
|
42
|
+
* hyphen, e.g. "0.6.0-4-gabc123" matches only as far as "0.6.0-4";
|
|
43
|
+
* a platform suffix, e.g. "0.6.0-linux-x64" matches only as far as
|
|
44
|
+
* "0.6.0-linux"). Kept only because `isPrerelease` is derived from
|
|
45
|
+
* whether `m[2]` (the restricted suffix group) matched at all.
|
|
46
|
+
* - `token` is the full probed version token as printed: everything
|
|
47
|
+
* from the start of the numeric run up to the next whitespace
|
|
48
|
+
* character (or end of string), with no character-class
|
|
49
|
+
* restriction. This is what a human-facing message should quote;
|
|
50
|
+
* `raw`'s truncation would otherwise misreport what the probe
|
|
51
|
+
* actually printed. See docs/decisions/2026-09-08-preflight-floors.md.
|
|
52
|
+
*/
|
|
53
|
+
export declare function parseProbedVersion(stdout: string): {
|
|
54
|
+
version: string;
|
|
55
|
+
isPrerelease: boolean;
|
|
56
|
+
raw: string;
|
|
57
|
+
token: string;
|
|
58
|
+
} | null;
|
|
59
|
+
/**
|
|
60
|
+
* `compareNumericVersions`, but a prerelease `a` (as reported by
|
|
61
|
+
* `parseProbedVersion`'s `isPrerelease`) is treated as strictly BELOW
|
|
62
|
+
* a numerically-equal `b` floor, matching semver precedence
|
|
63
|
+
* (`0.6.0-rc.1 < 0.6.0`) and the intent every `min_version` floor in
|
|
64
|
+
* this codebase already carries: `NUMERIC_VERSION_PATTERN` rejects a
|
|
65
|
+
* prerelease shape for `b` itself (a `min_version` field can never
|
|
66
|
+
* BE a prerelease), so `aIsPrerelease` is the only side this can ever
|
|
67
|
+
* apply to. When the base numeric comparison is not a tie, the
|
|
68
|
+
* prerelease flag is irrelevant and the numeric result wins outright
|
|
69
|
+
* (a genuinely older release, prerelease or not, is still older).
|
|
70
|
+
*
|
|
71
|
+
* Decision: docs/decisions/2026-09-08-preflight-floors.md (see the
|
|
72
|
+
* Reopen criteria section for task db44ab46, which extended the rule
|
|
73
|
+
* from its original two consumers to all `min_version` floor checks in
|
|
74
|
+
* this codebase). Consumers as of task db44ab46: `checkHookVersion` and
|
|
75
|
+
* `checkCli`'s `tools.cli[]` check plus `checkMcpVersions`'s
|
|
76
|
+
* `tools.mcp[]` check (all in src/cli/doctor/index.ts),
|
|
77
|
+
* `checkSessionStartPreflightSetupVersion`
|
|
78
|
+
* (src/cli/doctor/session-start-preflight-setup-version.ts), validate's
|
|
79
|
+
* `tools.cli[]` check (src/cli/validate/checks.ts), `memory.router`'s
|
|
80
|
+
* version probe (src/probes/memory.ts), and the pack-level floor
|
|
81
|
+
* (src/policy-packs/version-check.ts).
|
|
82
|
+
*/
|
|
83
|
+
export declare function compareVersionFloor(a: string, aIsPrerelease: boolean, b: string): number;
|
|
@@ -37,4 +37,71 @@ export function compareNumericVersions(a, b) {
|
|
|
37
37
|
}
|
|
38
38
|
return 0;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Extracts a numeric-semver-shape version from free-form `--version`
|
|
42
|
+
* probe output (e.g. "preflight 0.6.0-rc.1\n"), plus whether the
|
|
43
|
+
* matched numeric run was immediately followed by a `-` prerelease/
|
|
44
|
+
* build suffix. Matches ONLY the leading numeric run itself (mirrors
|
|
45
|
+
* the pre-existing `/(\d+(?:\.\d+){0,3})/` extraction every version
|
|
46
|
+
* floor check used before task 65952a0c), so `version` here is exactly
|
|
47
|
+
* what a plain, non-prerelease-aware caller would have parsed; the
|
|
48
|
+
* `isPrerelease` flag is the new information a caller can act on.
|
|
49
|
+
*
|
|
50
|
+
* Returns `null` when no numeric run is found at all (unparseable
|
|
51
|
+
* probe output), matching the pre-existing "no match" contract.
|
|
52
|
+
*
|
|
53
|
+
* Two suffix strings are returned, deliberately different:
|
|
54
|
+
* - `raw` is the regex's own match (`m[0]`): the numeric run plus a
|
|
55
|
+
* `-` suffix restricted to `[0-9A-Za-z.]`, so it TRUNCATES a suffix
|
|
56
|
+
* containing any other character (a git-describe suffix's second
|
|
57
|
+
* hyphen, e.g. "0.6.0-4-gabc123" matches only as far as "0.6.0-4";
|
|
58
|
+
* a platform suffix, e.g. "0.6.0-linux-x64" matches only as far as
|
|
59
|
+
* "0.6.0-linux"). Kept only because `isPrerelease` is derived from
|
|
60
|
+
* whether `m[2]` (the restricted suffix group) matched at all.
|
|
61
|
+
* - `token` is the full probed version token as printed: everything
|
|
62
|
+
* from the start of the numeric run up to the next whitespace
|
|
63
|
+
* character (or end of string), with no character-class
|
|
64
|
+
* restriction. This is what a human-facing message should quote;
|
|
65
|
+
* `raw`'s truncation would otherwise misreport what the probe
|
|
66
|
+
* actually printed. See docs/decisions/2026-09-08-preflight-floors.md.
|
|
67
|
+
*/
|
|
68
|
+
export function parseProbedVersion(stdout) {
|
|
69
|
+
const m = stdout.match(/(\d+(?:\.\d+){0,3})(-[0-9A-Za-z.]+)?/);
|
|
70
|
+
if (!m || !m[1] || m.index === undefined)
|
|
71
|
+
return null;
|
|
72
|
+
const rest = stdout.slice(m.index);
|
|
73
|
+
const wsIndex = rest.search(/\s/);
|
|
74
|
+
const token = wsIndex === -1 ? rest : rest.slice(0, wsIndex);
|
|
75
|
+
return { version: m[1], isPrerelease: m[2] !== undefined, raw: m[0], token };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* `compareNumericVersions`, but a prerelease `a` (as reported by
|
|
79
|
+
* `parseProbedVersion`'s `isPrerelease`) is treated as strictly BELOW
|
|
80
|
+
* a numerically-equal `b` floor, matching semver precedence
|
|
81
|
+
* (`0.6.0-rc.1 < 0.6.0`) and the intent every `min_version` floor in
|
|
82
|
+
* this codebase already carries: `NUMERIC_VERSION_PATTERN` rejects a
|
|
83
|
+
* prerelease shape for `b` itself (a `min_version` field can never
|
|
84
|
+
* BE a prerelease), so `aIsPrerelease` is the only side this can ever
|
|
85
|
+
* apply to. When the base numeric comparison is not a tie, the
|
|
86
|
+
* prerelease flag is irrelevant and the numeric result wins outright
|
|
87
|
+
* (a genuinely older release, prerelease or not, is still older).
|
|
88
|
+
*
|
|
89
|
+
* Decision: docs/decisions/2026-09-08-preflight-floors.md (see the
|
|
90
|
+
* Reopen criteria section for task db44ab46, which extended the rule
|
|
91
|
+
* from its original two consumers to all `min_version` floor checks in
|
|
92
|
+
* this codebase). Consumers as of task db44ab46: `checkHookVersion` and
|
|
93
|
+
* `checkCli`'s `tools.cli[]` check plus `checkMcpVersions`'s
|
|
94
|
+
* `tools.mcp[]` check (all in src/cli/doctor/index.ts),
|
|
95
|
+
* `checkSessionStartPreflightSetupVersion`
|
|
96
|
+
* (src/cli/doctor/session-start-preflight-setup-version.ts), validate's
|
|
97
|
+
* `tools.cli[]` check (src/cli/validate/checks.ts), `memory.router`'s
|
|
98
|
+
* version probe (src/probes/memory.ts), and the pack-level floor
|
|
99
|
+
* (src/policy-packs/version-check.ts).
|
|
100
|
+
*/
|
|
101
|
+
export function compareVersionFloor(a, aIsPrerelease, b) {
|
|
102
|
+
const cmp = compareNumericVersions(a, b);
|
|
103
|
+
if (cmp !== 0)
|
|
104
|
+
return cmp;
|
|
105
|
+
return aIsPrerelease ? -1 : 0;
|
|
106
|
+
}
|
|
40
107
|
//# sourceMappingURL=version-compare.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-compare.js","sourceRoot":"","sources":["../../src/io/version-compare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,qBAAqB,CAAC;AAE7D,MAAM,CAAC,MAAM,uBAAuB,GAClC,0KAA0K,CAAC;AAE7K,MAAM,UAAU,sBAAsB,CAAC,CAAS,EAAE,CAAS;IACzD,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3D,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAAE,OAAO,CAAC,CAAC;QACnD,IAAI,EAAE,GAAG,EAAE;YAAE,OAAO,CAAC,CAAC;QACtB,IAAI,EAAE,GAAG,EAAE;YAAE,OAAO,CAAC,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
1
|
+
{"version":3,"file":"version-compare.js","sourceRoot":"","sources":["../../src/io/version-compare.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,qBAAqB,CAAC;AAE7D,MAAM,CAAC,MAAM,uBAAuB,GAClC,0KAA0K,CAAC;AAE7K,MAAM,UAAU,sBAAsB,CAAC,CAAS,EAAE,CAAS;IACzD,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3D,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAAE,OAAO,CAAC,CAAC;QACnD,IAAI,EAAE,GAAG,EAAE;YAAE,OAAO,CAAC,CAAC;QACtB,IAAI,EAAE,GAAG,EAAE;YAAE,OAAO,CAAC,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAc;IAEd,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC/D,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACtD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC7D,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;AAC/E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAAS,EAAE,aAAsB,EAAE,CAAS;IAC9E,MAAM,GAAG,GAAG,sBAAsB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACzC,IAAI,GAAG,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IAC1B,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** The grounding-mcp version this reconnect lifecycle was verified against. */
|
|
2
|
+
export declare const RECONNECT_PRODUCER_FLOOR = "grounding-mcp >= 0.11.0";
|
|
3
|
+
/** Rendered qualifier prefix both surfaces open the guidance with. */
|
|
4
|
+
export declare const RECONNECT_VERSION_QUALIFIER = "With grounding-mcp >= 0.11.0:";
|
|
5
|
+
export declare const RECONNECT_STATUS_TOOL = "mcp__grounding-mcp__solution_evaluate_status";
|
|
6
|
+
export declare const RECONNECT_RESULT_TOOL = "mcp__grounding-mcp__solution_evaluate_result";
|
|
7
|
+
export declare const RECONNECT_TASK_ID_PLACEHOLDER = "<task-id>";
|
|
8
|
+
export declare const RECONNECT_POLL_MS_ADVERTISED = "5000ms";
|
|
9
|
+
export declare const RECONNECT_RETENTION = "24h";
|
|
10
|
+
export declare const RECONNECT_RETENTION_FLOOR = "100x pollAfterMs";
|
|
11
|
+
/**
|
|
12
|
+
* The three readings `gate.verdict === null` covers. The hook
|
|
13
|
+
* (`classifyNullVerdictReading` / `readAttemptLockLiveness` in
|
|
14
|
+
* `../../cli/pack/hook-solution-acceptance.ts`) reads the documented
|
|
15
|
+
* attempt-lock anchor to rule two of the three out and select the one it
|
|
16
|
+
* detected; this module only owns the wording listing them, consumed by
|
|
17
|
+
* `renderReconnectDenyParagraph` below for its reading-(2)-only paragraph.
|
|
18
|
+
*/
|
|
19
|
+
export declare const RECONNECT_THREE_READINGS_LABELS: readonly ["`solution_evaluate` was never called for \"<task-id>\"", "a `solution_evaluate` call for it is still running in the background", "a marker path exists but what is there was not accepted as a verdict"];
|
|
20
|
+
/** Fact 1: reconnect by attempt id, not by starting a fresh call. */
|
|
21
|
+
export declare const RECONNECT_FACT_RECONNECT_BY_ID: string;
|
|
22
|
+
/**
|
|
23
|
+
* Fact 2: never retry while the lock is held; a second call JOINS, it does
|
|
24
|
+
* not refuse. It deliberately begins lowercase because each renderer owns
|
|
25
|
+
* its own lead-in sentence.
|
|
26
|
+
*/
|
|
27
|
+
export declare const RECONNECT_FACT_JOIN_NOT_RETRY: string;
|
|
28
|
+
/**
|
|
29
|
+
* Fact 3: poll interval and retention bounds, from the released
|
|
30
|
+
* grounding-mcp version. Both renderers use this fact as a fresh sentence
|
|
31
|
+
* (after a period), so it is capitalized.
|
|
32
|
+
*/
|
|
33
|
+
export declare const RECONNECT_FACT_POLL_AND_RETENTION: string;
|
|
34
|
+
/**
|
|
35
|
+
* Compact paragraph appended to the completion-gate's deny reason
|
|
36
|
+
* (`blockJson` in hook-solution-acceptance.ts) when `gate.verdict ===
|
|
37
|
+
* null` AND the hook detected reading (2), a live attempt-lock
|
|
38
|
+
* (`reconnectGuidanceFor`). Renders the SAME fact constants as
|
|
39
|
+
* `renderReconnectInstructionsSection` below.
|
|
40
|
+
*/
|
|
41
|
+
export declare function renderReconnectDenyParagraph(taskId: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* "Reconnecting vs. retrying" section body rendered into the pack's
|
|
44
|
+
* `instructions.md` operator audit copy (`buildInstructions` in
|
|
45
|
+
* solution-acceptance.ts). Renders the SAME fact constants as
|
|
46
|
+
* `renderReconnectDenyParagraph` above; a large repo can outlive a single
|
|
47
|
+
* `solution_evaluate` call, so this section documents reconnecting to an
|
|
48
|
+
* in-flight attempt rather than treating a timeout as a stall to retry.
|
|
49
|
+
*/
|
|
50
|
+
export declare function renderReconnectInstructionsSection(): string;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Reconnect-vs-retry facts for the `solution-acceptance` pack (task
|
|
2
|
+
// 5c9cad05), owned as DATA in exactly one place so the two rendered
|
|
3
|
+
// surfaces cannot drift apart the way they did across review rounds 1-2:
|
|
4
|
+
//
|
|
5
|
+
// 1. `buildInstructions` (solution-acceptance.ts) renders the operator
|
|
6
|
+
// audit copy's "Reconnecting vs. retrying" section from
|
|
7
|
+
// `renderReconnectInstructionsSection`.
|
|
8
|
+
// 2. `blockJson` (../../cli/pack/hook-solution-acceptance.ts) renders the
|
|
9
|
+
// agent-facing deny paragraph from `renderReconnectDenyParagraph`,
|
|
10
|
+
// shown only when `gate.verdict === null` AND the hook detected
|
|
11
|
+
// reading (2), a live attempt-lock (see `reconnectGuidanceFor` in
|
|
12
|
+
// hook-solution-acceptance.ts; the three readings are listed in
|
|
13
|
+
// `RECONNECT_THREE_READINGS_LABELS`
|
|
14
|
+
// below).
|
|
15
|
+
//
|
|
16
|
+
// Both renderers are built from the SAME fact constants below (not just
|
|
17
|
+
// the same values restated in prose twice), and
|
|
18
|
+
// `tests/policy-packs/solution-acceptance-reconnect.test.ts` asserts each
|
|
19
|
+
// fact constant appears verbatim in both rendered surfaces, so a future
|
|
20
|
+
// edit that updates one surface but not the other fails that test instead
|
|
21
|
+
// of shipping a silent drift.
|
|
22
|
+
//
|
|
23
|
+
// Scope: this module documents grounding-mcp's ATTEMPT lifecycle
|
|
24
|
+
// (`solution_evaluate_status` / `solution_evaluate_result`, `attemptId`,
|
|
25
|
+
// `pollAfterMs`, retention), which the pack's producer floor (>= 0.3.2)
|
|
26
|
+
// does not itself guarantee (that lifecycle shipped in grounding-mcp
|
|
27
|
+
// 0.11.0), so every rendering below is explicitly qualified with
|
|
28
|
+
// `RECONNECT_VERSION_QUALIFIER` rather than stated as if it held
|
|
29
|
+
// unconditionally under the pack's own (older) producer floor.
|
|
30
|
+
//
|
|
31
|
+
// grounding-mcp's README also documents an attempt-lock anchor
|
|
32
|
+
// (`<verdict dir>/<id>.attempt-lock`, beside the
|
|
33
|
+
// `<id>.attempt-lock.lock` directory `proper-lockfile` manages, from
|
|
34
|
+
// which the producer itself derives its own `running-unconfirmed`
|
|
35
|
+
// status). The anchor IS read: `classifyNullVerdictReading` /
|
|
36
|
+
// `readAttemptLockLiveness` in `../../cli/pack/hook-solution-acceptance.ts`
|
|
37
|
+
// read it to rule two of the three readings below out and select the
|
|
38
|
+
// one it detected (harness/799de976). This module does not touch the
|
|
39
|
+
// filesystem itself; it only owns the WORDING both rendered surfaces
|
|
40
|
+
// share, including `renderReconnectDenyParagraph`'s reading-(2)-only
|
|
41
|
+
// paragraph below, which the hook appends solely when it has detected
|
|
42
|
+
// the live-attempt reading.
|
|
43
|
+
/** The grounding-mcp version this reconnect lifecycle was verified against. */
|
|
44
|
+
export const RECONNECT_PRODUCER_FLOOR = "grounding-mcp >= 0.11.0";
|
|
45
|
+
/** Rendered qualifier prefix both surfaces open the guidance with. */
|
|
46
|
+
export const RECONNECT_VERSION_QUALIFIER = `With ${RECONNECT_PRODUCER_FLOOR}:`;
|
|
47
|
+
export const RECONNECT_STATUS_TOOL = "mcp__grounding-mcp__solution_evaluate_status";
|
|
48
|
+
export const RECONNECT_RESULT_TOOL = "mcp__grounding-mcp__solution_evaluate_result";
|
|
49
|
+
export const RECONNECT_TASK_ID_PLACEHOLDER = "<task-id>";
|
|
50
|
+
export const RECONNECT_POLL_MS_ADVERTISED = "5000ms";
|
|
51
|
+
export const RECONNECT_RETENTION = "24h";
|
|
52
|
+
export const RECONNECT_RETENTION_FLOOR = "100x pollAfterMs";
|
|
53
|
+
/**
|
|
54
|
+
* The three readings `gate.verdict === null` covers. The hook
|
|
55
|
+
* (`classifyNullVerdictReading` / `readAttemptLockLiveness` in
|
|
56
|
+
* `../../cli/pack/hook-solution-acceptance.ts`) reads the documented
|
|
57
|
+
* attempt-lock anchor to rule two of the three out and select the one it
|
|
58
|
+
* detected; this module only owns the wording listing them, consumed by
|
|
59
|
+
* `renderReconnectDenyParagraph` below for its reading-(2)-only paragraph.
|
|
60
|
+
*/
|
|
61
|
+
export const RECONNECT_THREE_READINGS_LABELS = [
|
|
62
|
+
`\`solution_evaluate\` was never called for "${RECONNECT_TASK_ID_PLACEHOLDER}"`,
|
|
63
|
+
"a `solution_evaluate` call for it is still running in the background",
|
|
64
|
+
"a marker path exists but what is there was not accepted as a verdict",
|
|
65
|
+
];
|
|
66
|
+
/** Fact 1: reconnect by attempt id, not by starting a fresh call. */
|
|
67
|
+
export const RECONNECT_FACT_RECONNECT_BY_ID = `poll \`${RECONNECT_STATUS_TOOL}\` /\n` +
|
|
68
|
+
`\`${RECONNECT_RESULT_TOOL}\` for the SAME \`id\`,\n` +
|
|
69
|
+
`passing the \`attemptId\` you were given (or omitting it to resolve the latest attempt,\n` +
|
|
70
|
+
`the recovery path when your own call timed out before it ever returned one)`;
|
|
71
|
+
/**
|
|
72
|
+
* Fact 2: never retry while the lock is held; a second call JOINS, it does
|
|
73
|
+
* not refuse. It deliberately begins lowercase because each renderer owns
|
|
74
|
+
* its own lead-in sentence.
|
|
75
|
+
*/
|
|
76
|
+
export const RECONNECT_FACT_JOIN_NOT_RETRY = `a second \`solution_evaluate\` call for an \`id\` whose attempt is still live just joins\n` +
|
|
77
|
+
`that attempt and returns its \`attemptId\`, never starting a second \`preflight\` run;\n` +
|
|
78
|
+
`only \`forceNewAttempt\` is refused while that attempt's lock holds`;
|
|
79
|
+
/**
|
|
80
|
+
* Fact 3: poll interval and retention bounds, from the released
|
|
81
|
+
* grounding-mcp version. Both renderers use this fact as a fresh sentence
|
|
82
|
+
* (after a period), so it is capitalized.
|
|
83
|
+
*/
|
|
84
|
+
export const RECONNECT_FACT_POLL_AND_RETENTION = `Wait at least the returned \`pollAfterMs\` (advertised as \`${RECONNECT_POLL_MS_ADVERTISED}\`)\n` +
|
|
85
|
+
`between polls; attempt records are retained \`${RECONNECT_RETENTION}\` by default (always at\n` +
|
|
86
|
+
`least \`${RECONNECT_RETENTION_FLOOR}\`), and a pruned terminal attempt\n` +
|
|
87
|
+
`reads \`expired\``;
|
|
88
|
+
/** The three readings, as one clause, with the task id interpolated. */
|
|
89
|
+
function threeReadingsClause(taskId) {
|
|
90
|
+
const [neverCalled, stillRunning, unreadable] = RECONNECT_THREE_READINGS_LABELS;
|
|
91
|
+
return [
|
|
92
|
+
neverCalled.replace(RECONNECT_TASK_ID_PLACEHOLDER, taskId),
|
|
93
|
+
stillRunning,
|
|
94
|
+
`or ${unreadable}`,
|
|
95
|
+
].join(",\n");
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Compact paragraph appended to the completion-gate's deny reason
|
|
99
|
+
* (`blockJson` in hook-solution-acceptance.ts) when `gate.verdict ===
|
|
100
|
+
* null` AND the hook detected reading (2), a live attempt-lock
|
|
101
|
+
* (`reconnectGuidanceFor`). Renders the SAME fact constants as
|
|
102
|
+
* `renderReconnectInstructionsSection` below.
|
|
103
|
+
*/
|
|
104
|
+
export function renderReconnectDenyParagraph(taskId) {
|
|
105
|
+
return `\n${[
|
|
106
|
+
`Reconnecting vs. retrying. ${RECONNECT_VERSION_QUALIFIER} this same`,
|
|
107
|
+
`"no readable verdict marker" case covers three readings:`,
|
|
108
|
+
threeReadingsClause(taskId),
|
|
109
|
+
`(the verdict marker only appears once an attempt finishes, and is validated on read;`,
|
|
110
|
+
`this hook read the documented attempt-lock anchor and is showing you this paragraph`,
|
|
111
|
+
`because it detected reading (2): an attempt is still live). If you already called`,
|
|
112
|
+
`\`solution_evaluate\` for this \`id\`, do not call it again:`,
|
|
113
|
+
`${RECONNECT_FACT_RECONNECT_BY_ID}.`,
|
|
114
|
+
`Never re-call \`solution_evaluate\`:`,
|
|
115
|
+
`${RECONNECT_FACT_JOIN_NOT_RETRY}.`,
|
|
116
|
+
`${RECONNECT_FACT_POLL_AND_RETENTION}.`,
|
|
117
|
+
].join("\n")}\n`;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* "Reconnecting vs. retrying" section body rendered into the pack's
|
|
121
|
+
* `instructions.md` operator audit copy (`buildInstructions` in
|
|
122
|
+
* solution-acceptance.ts). Renders the SAME fact constants as
|
|
123
|
+
* `renderReconnectDenyParagraph` above; a large repo can outlive a single
|
|
124
|
+
* `solution_evaluate` call, so this section documents reconnecting to an
|
|
125
|
+
* in-flight attempt rather than treating a timeout as a stall to retry.
|
|
126
|
+
*/
|
|
127
|
+
export function renderReconnectInstructionsSection() {
|
|
128
|
+
return `## Reconnecting vs. retrying (${RECONNECT_PRODUCER_FLOOR})
|
|
129
|
+
|
|
130
|
+
${RECONNECT_VERSION_QUALIFIER} a large repo can outlive the call: \`solution_evaluate\` may
|
|
131
|
+
hand back \`{status: "running", attemptId, id, pollAfterMs}\` instead of a verdict, or your
|
|
132
|
+
own call may time out with nothing at all. Either way the \`preflight\` run keeps going in
|
|
133
|
+
the background; reconnect as follows:
|
|
134
|
+
${RECONNECT_FACT_RECONNECT_BY_ID}.
|
|
135
|
+
|
|
136
|
+
Never re-call \`solution_evaluate\` as a stall workaround:
|
|
137
|
+
${RECONNECT_FACT_JOIN_NOT_RETRY}.
|
|
138
|
+
A prior attempt's reported status (\`completed\`,
|
|
139
|
+
\`failed\`, \`unknown\`, or \`expired\`) is informational, not the gate: \`unknown\`/
|
|
140
|
+
\`expired\` never license a new attempt by themselves while another process still holds the
|
|
141
|
+
id's lock. A genuinely new attempt becomes possible only once the previous one is terminal and
|
|
142
|
+
the id's lock is free again, at which point an ordinary \`solution_evaluate\` call starts
|
|
143
|
+
one.
|
|
144
|
+
|
|
145
|
+
${RECONNECT_FACT_POLL_AND_RETENTION}. \`running-unconfirmed\` still means keep
|
|
146
|
+
polling, not stall or escalate: it resolves by itself, into \`running\`
|
|
147
|
+
or once the lock is reclaimed as stale. Never escalate to a human before
|
|
148
|
+
the poll hint has elapsed.
|
|
149
|
+
`;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=solution-acceptance-reconnect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solution-acceptance-reconnect.js","sourceRoot":"","sources":["../../../src/policy-packs/builtin/solution-acceptance-reconnect.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,oEAAoE;AACpE,yEAAyE;AACzE,EAAE;AACF,yEAAyE;AACzE,6DAA6D;AAC7D,6CAA6C;AAC7C,4EAA4E;AAC5E,wEAAwE;AACxE,qEAAqE;AACrE,uEAAuE;AACvE,qEAAqE;AACrE,yCAAyC;AACzC,eAAe;AACf,EAAE;AACF,wEAAwE;AACxE,gDAAgD;AAChD,0EAA0E;AAC1E,wEAAwE;AACxE,0EAA0E;AAC1E,8BAA8B;AAC9B,EAAE;AACF,iEAAiE;AACjE,yEAAyE;AACzE,wEAAwE;AACxE,qEAAqE;AACrE,iEAAiE;AACjE,iEAAiE;AACjE,+DAA+D;AAC/D,EAAE;AACF,+DAA+D;AAC/D,iDAAiD;AACjD,qEAAqE;AACrE,kEAAkE;AAClE,8DAA8D;AAC9D,4EAA4E;AAC5E,qEAAqE;AACrE,qEAAqE;AACrE,qEAAqE;AACrE,qEAAqE;AACrE,sEAAsE;AACtE,4BAA4B;AAE5B,+EAA+E;AAC/E,MAAM,CAAC,MAAM,wBAAwB,GAAG,yBAAyB,CAAC;AAElE,sEAAsE;AACtE,MAAM,CAAC,MAAM,2BAA2B,GAAG,QAAQ,wBAAwB,GAAG,CAAC;AAE/E,MAAM,CAAC,MAAM,qBAAqB,GAAG,8CAA8C,CAAC;AACpF,MAAM,CAAC,MAAM,qBAAqB,GAAG,8CAA8C,CAAC;AACpF,MAAM,CAAC,MAAM,6BAA6B,GAAG,WAAW,CAAC;AAEzD,MAAM,CAAC,MAAM,4BAA4B,GAAG,QAAQ,CAAC;AACrD,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,CAAC;AACzC,MAAM,CAAC,MAAM,yBAAyB,GAAG,kBAAkB,CAAC;AAE5D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG;IAC7C,+CAA+C,6BAA6B,GAAG;IAC/E,sEAAsE;IACtE,sEAAsE;CAC9D,CAAC;AAEX,qEAAqE;AACrE,MAAM,CAAC,MAAM,8BAA8B,GACzC,UAAU,qBAAqB,QAAQ;IACvC,KAAK,qBAAqB,2BAA2B;IACrD,2FAA2F;IAC3F,6EAA6E,CAAC;AAEhF;;;;GAIG;AACH,MAAM,CAAC,MAAM,6BAA6B,GACxC,4FAA4F;IAC5F,0FAA0F;IAC1F,qEAAqE,CAAC;AAExE;;;;GAIG;AACH,MAAM,CAAC,MAAM,iCAAiC,GAC5C,+DAA+D,4BAA4B,OAAO;IAClG,iDAAiD,mBAAmB,4BAA4B;IAChG,WAAW,yBAAyB,sCAAsC;IAC1E,mBAAmB,CAAC;AAEtB,wEAAwE;AACxE,SAAS,mBAAmB,CAAC,MAAc;IACzC,MAAM,CAAC,WAAW,EAAE,YAAY,EAAE,UAAU,CAAC,GAAG,+BAA+B,CAAC;IAChF,OAAO;QACL,WAAW,CAAC,OAAO,CAAC,6BAA6B,EAAE,MAAM,CAAC;QAC1D,YAAY;QACZ,MAAM,UAAU,EAAE;KACnB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAAC,MAAc;IACzD,OAAO,KAAK;QACV,8BAA8B,2BAA2B,YAAY;QACrE,0DAA0D;QAC1D,mBAAmB,CAAC,MAAM,CAAC;QAC3B,sFAAsF;QACtF,qFAAqF;QACrF,mFAAmF;QACnF,8DAA8D;QAC9D,GAAG,8BAA8B,GAAG;QACpC,sCAAsC;QACtC,GAAG,6BAA6B,GAAG;QACnC,GAAG,iCAAiC,GAAG;KACxC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACnB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kCAAkC;IAChD,OAAO,iCAAiC,wBAAwB;;EAEhE,2BAA2B;;;;EAI3B,8BAA8B;;;EAG9B,6BAA6B;;;;;;;;EAQ7B,iCAAiC;;;;CAIlC,CAAC;AACF,CAAC"}
|
|
@@ -170,12 +170,51 @@ export declare function verdictPathFor(dir: string, id: string): string;
|
|
|
170
170
|
* active-claim is present.
|
|
171
171
|
*/
|
|
172
172
|
export declare function resolveExplicitVerdictId(env?: NodeJS.ProcessEnv): string | null;
|
|
173
|
+
/**
|
|
174
|
+
* WHY the verdict marker for an id yielded no verdict, as ONE observation.
|
|
175
|
+
* `readVerdict` below answers `null` for every non-`ok` kind, which is all
|
|
176
|
+
* the gate DECISION needs; the deny TEXT needs to say which one, and must
|
|
177
|
+
* not learn it from a second look at the path. A second look can disagree
|
|
178
|
+
* with the first (a background attempt finishing in between is exactly the
|
|
179
|
+
* case this pack exists for), and a note rendered from the later one then
|
|
180
|
+
* describes a rejection the gate never made: it would tell the agent its
|
|
181
|
+
* fresh marker was refused when the gate had simply not seen it yet
|
|
182
|
+
* (review finding, harness/799de976, round 6). Both consumers therefore
|
|
183
|
+
* read through this one function.
|
|
184
|
+
*/
|
|
185
|
+
export type VerdictReadOutcome = {
|
|
186
|
+
kind: "ok";
|
|
187
|
+
verdict: Verdict;
|
|
188
|
+
} | {
|
|
189
|
+
kind: "invalid-id";
|
|
190
|
+
} | {
|
|
191
|
+
kind: "missing";
|
|
192
|
+
} | {
|
|
193
|
+
kind: "symlink";
|
|
194
|
+
} | {
|
|
195
|
+
kind: "not-regular";
|
|
196
|
+
} | {
|
|
197
|
+
kind: "unreadable";
|
|
198
|
+
} | {
|
|
199
|
+
kind: "invalid-record";
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Read + validate the verdict marker for `id` in ONE filesystem
|
|
203
|
+
* observation, reporting the outcome instead of collapsing it. The
|
|
204
|
+
* symlink-rejecting read is the shared `src/io/read-regular-file.ts`
|
|
205
|
+
* helper (same defense-in-depth as `checkApprovalMarker` against a symlink
|
|
206
|
+
* planted at the marker path pointing at agent-controlled content), and
|
|
207
|
+
* its four non-ok kinds pass straight through, so this outcome set cannot
|
|
208
|
+
* drift from the reader's own. `invalid-record` is the one kind added on
|
|
209
|
+
* top: a regular file that WAS read but is not a verdict (unparseable
|
|
210
|
+
* JSON, or JSON missing one of the three required fields).
|
|
211
|
+
*/
|
|
212
|
+
export declare function readVerdictDetailed(dir: string, id: string): VerdictReadOutcome;
|
|
173
213
|
/**
|
|
174
214
|
* Read + validate the verdict marker for `id`, or null when it is absent,
|
|
175
|
-
* unparseable, a symlink, or not a regular file
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
* pointing at agent-controlled content).
|
|
215
|
+
* unparseable, a symlink, or not a regular file: `readVerdictDetailed`
|
|
216
|
+
* above with the reason dropped. Behaviour is unchanged for every caller
|
|
217
|
+
* that only needs the gate decision.
|
|
179
218
|
*/
|
|
180
219
|
export declare function readVerdict(dir: string, id: string): Verdict | null;
|
|
181
220
|
export interface GateResult {
|