session-orchestrator 3.22.0 → 3.23.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-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/commands/autopilot-multi.md +14 -0
- package/.cursor/commands/autopilot.md +14 -0
- package/.cursor/commands/bootstrap.md +14 -0
- package/.cursor/commands/brainstorm.md +14 -0
- package/.cursor/commands/close.md +13 -0
- package/.cursor/commands/contract-version-bump.md +14 -0
- package/.cursor/commands/debug.md +14 -0
- package/.cursor/commands/discovery.md +14 -0
- package/.cursor/commands/dispatcher.md +14 -0
- package/.cursor/commands/eli5.md +14 -0
- package/.cursor/commands/eval.md +14 -0
- package/.cursor/commands/evolve.md +14 -0
- package/.cursor/commands/go.md +14 -0
- package/.cursor/commands/grill.md +14 -0
- package/.cursor/commands/harness-audit.md +13 -0
- package/.cursor/commands/journey-audit.md +14 -0
- package/.cursor/commands/memory-cleanup.md +14 -0
- package/.cursor/commands/persona-panel.md +14 -0
- package/.cursor/commands/plan.md +14 -0
- package/.cursor/commands/portfolio.md +14 -0
- package/.cursor/commands/reconcile.md +14 -0
- package/.cursor/commands/release.md +14 -0
- package/.cursor/commands/repo-audit.md +13 -0
- package/.cursor/commands/session.md +14 -0
- package/.cursor/commands/spinout.md +14 -0
- package/.cursor/commands/sunset-review.md +14 -0
- package/.cursor/commands/templates-ack.md +14 -0
- package/.cursor/commands/test.md +14 -0
- package/.cursor/hooks.json +60 -0
- package/.cursor/rules/000-session-orchestrator.mdc +8 -0
- package/.cursor/rules/010-session-workflow.mdc +9 -1
- package/.cursor/rules/020-quality-gates.mdc +1 -1
- package/.cursor/rules/030-wave-execution.mdc +1 -1
- package/.cursor/rules/050-plan.mdc +2 -2
- package/.cursor/rules/070-gitlab-ops.mdc +73 -57
- package/.cursor/rules/080-ecosystem-health.mdc +7 -7
- package/.cursor/skills/architecture/SKILL.md +13 -0
- package/.cursor/skills/autopilot/SKILL.md +12 -0
- package/.cursor/skills/bootstrap/SKILL.md +12 -0
- package/.cursor/skills/brainstorm/SKILL.md +13 -0
- package/.cursor/skills/claude-md-drift-check/SKILL.md +13 -0
- package/.cursor/skills/contract-version-bump/SKILL.md +12 -0
- package/.cursor/skills/convergence-monitoring/SKILL.md +12 -0
- package/.cursor/skills/daily/SKILL.md +12 -0
- package/.cursor/skills/debug/SKILL.md +13 -0
- package/.cursor/skills/discovery/SKILL.md +13 -0
- package/.cursor/skills/dispatcher/SKILL.md +13 -0
- package/.cursor/skills/docs-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/domain-model/SKILL.md +13 -0
- package/.cursor/skills/ecosystem-health/SKILL.md +13 -0
- package/.cursor/skills/eli5/SKILL.md +13 -0
- package/.cursor/skills/eval/SKILL.md +12 -0
- package/.cursor/skills/evolve/SKILL.md +13 -0
- package/.cursor/skills/frontmatter-guard/SKILL.md +13 -0
- package/.cursor/skills/gitlab-ops/SKILL.md +13 -0
- package/.cursor/skills/gitlab-portfolio/SKILL.md +13 -0
- package/.cursor/skills/grill/SKILL.md +13 -0
- package/.cursor/skills/hook-development/SKILL.md +13 -0
- package/.cursor/skills/journey-audit/SKILL.md +13 -0
- package/.cursor/skills/mcp-builder/SKILL.md +13 -0
- package/.cursor/skills/memory-cleanup/SKILL.md +12 -0
- package/.cursor/skills/mode-selector/SKILL.md +13 -0
- package/.cursor/skills/npm-publish/SKILL.md +12 -0
- package/.cursor/skills/peekaboo-driver/SKILL.md +13 -0
- package/.cursor/skills/persona-panel/SKILL.md +12 -0
- package/.cursor/skills/plan/SKILL.md +13 -0
- package/.cursor/skills/playwright-driver/SKILL.md +13 -0
- package/.cursor/skills/quality-gates/SKILL.md +13 -0
- package/.cursor/skills/reconcile/SKILL.md +12 -0
- package/.cursor/skills/repo-audit/SKILL.md +13 -0
- package/.cursor/skills/session-end/SKILL.md +13 -0
- package/.cursor/skills/session-plan/SKILL.md +13 -0
- package/.cursor/skills/session-start/SKILL.md +13 -0
- package/.cursor/skills/skill-creator/SKILL.md +13 -0
- package/.cursor/skills/spinout/SKILL.md +12 -0
- package/.cursor/skills/sunset-review/SKILL.md +13 -0
- package/.cursor/skills/test-runner/SKILL.md +13 -0
- package/.cursor/skills/tmux-layout/SKILL.md +13 -0
- package/.cursor/skills/ubiquitous-language/SKILL.md +13 -0
- package/.cursor/skills/using-orchestrator/SKILL.md +13 -0
- package/.cursor/skills/vault-mirror/SKILL.md +13 -0
- package/.cursor/skills/vault-sync/SKILL.md +13 -0
- package/.cursor/skills/wave-executor/SKILL.md +13 -0
- package/.cursor/skills/write-executable-plan/SKILL.md +13 -0
- package/.mcp.json +4 -1
- package/CHANGELOG.md +168 -0
- package/README.md +18 -15
- package/agents/AGENTS.md +23 -4
- package/agents/code-implementer.md +2 -1
- package/agents/db-specialist.md +2 -1
- package/agents/docs-writer.md +3 -1
- package/agents/eval-judge.md +1 -1
- package/agents/session-reviewer.md +7 -1
- package/agents/test-writer.md +2 -1
- package/agents/ui-developer.md +2 -1
- package/commands/bootstrap.md +2 -2
- package/commands/close.md +3 -1
- package/commands/go.md +1 -1
- package/commands/journey-audit.md +43 -0
- package/docs/USER-GUIDE.md +2 -2
- package/docs/ci-setup.md +14 -0
- package/docs/codex-setup.md +64 -0
- package/docs/components.md +6 -6
- package/docs/cursor-setup.md +26 -47
- package/docs/events-schema.md +76 -4
- package/docs/github-mirror-protection.md +197 -0
- package/docs/pi-setup.md +2 -0
- package/docs/rule-authoring.md +3 -1
- package/docs/scope-collision-guard.md +49 -2
- package/docs/session-config-reference.md +26 -4
- package/docs/session-config-template.md +4 -3
- package/docs/telemetry.md +22 -0
- package/hooks/_lib/lock-bootstrap.mjs +8 -4
- package/hooks/_lib/vcs-create-matcher.mjs +397 -38
- package/hooks/enforce-scope.mjs +64 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +201 -20
- package/hooks/hooks-pi.json +1 -1
- package/hooks/hooks.json +2 -2
- package/hooks/on-session-end.mjs +211 -10
- package/hooks/on-session-start.mjs +214 -11
- package/hooks/on-stop.mjs +48 -9
- package/hooks/post-subagent-discovery-validator.mjs +34 -3
- package/hooks/post-tool-batch-wave-signal.mjs +11 -2
- package/hooks/pre-bash-issue-budget.mjs +117 -4
- package/hooks/pre-bash-sessions-ledger-guard.mjs +159 -0
- package/hooks/pre-bash-staging-fence.mjs +4 -0
- package/hooks/pre-task-scope-disjoint.mjs +368 -35
- package/hooks/skill-invocation-telemetry.mjs +21 -10
- package/monitors/monitors.json +6 -0
- package/package.json +1 -1
- package/pi/prompts/journey-audit.md +12 -0
- package/rules/_index.md +9 -1
- package/rules/always-on/ask-via-tool.md +62 -0
- package/rules/always-on/bash-harness-pitfalls.md +168 -0
- package/rules/always-on/build-value.md +47 -0
- package/rules/always-on/cross-session-messaging.md +59 -0
- package/rules/always-on/loop-and-monitor.md +221 -0
- package/rules/always-on/parallel-sessions.md +142 -12
- package/rules/always-on/receiving-review.md +108 -0
- package/rules/always-on/test-value.md +40 -0
- package/rules/always-on/verification-before-completion.md +77 -0
- package/scripts/archive-closed-prds.mjs +258 -18
- package/scripts/autopilot.mjs +5 -0
- package/scripts/backfill-evidence-digest.mjs +376 -0
- package/scripts/cursor-install.mjs +89 -48
- package/scripts/export-hw-learnings.mjs +143 -2
- package/scripts/express-path.mjs +299 -0
- package/scripts/generate-cursor-adapter.mjs +253 -0
- package/scripts/github-protection-audit.mjs +358 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +240 -16
- package/scripts/lib/build-live-signals.mjs +24 -5
- package/scripts/lib/ci-status-banner.mjs +158 -11
- package/scripts/lib/command-blocker.mjs +70 -0
- package/scripts/lib/config/reconcile.mjs +79 -4
- package/scripts/lib/config/section-extractor.mjs +235 -36
- package/scripts/lib/config-schema.mjs +9 -1
- package/scripts/lib/config.mjs +57 -6
- package/scripts/lib/convergence-monitor.mjs +13 -2
- package/scripts/lib/cursor-hook-bridge.mjs +443 -0
- package/scripts/lib/dispatcher/cli.mjs +2 -2
- package/scripts/lib/express-path.mjs +327 -0
- package/scripts/lib/file-lock.mjs +22 -4
- package/scripts/lib/gates/gate-full.mjs +81 -8
- package/scripts/lib/gates/gate-helpers.mjs +76 -15
- package/scripts/lib/git-config-drift.mjs +134 -5
- package/scripts/lib/host-identity.mjs +247 -2
- package/scripts/lib/instruction-budget-guard.mjs +31 -1
- package/scripts/lib/issue-budget.mjs +229 -30
- package/scripts/lib/learnings/io.mjs +55 -10
- package/scripts/lib/learnings/schema.mjs +95 -28
- package/scripts/lib/lock-reaper.mjs +7 -1
- package/scripts/lib/locks/staging-fence-lock.mjs +5 -1
- package/scripts/lib/locks/state-md-lock.mjs +8 -1
- package/scripts/lib/memory-banner.mjs +5 -2
- package/scripts/lib/memory-paths.mjs +15 -6
- package/scripts/lib/mode-selector/scoring.mjs +53 -6
- package/scripts/lib/platform.mjs +72 -9
- package/scripts/lib/plugin-root.mjs +143 -19
- package/scripts/lib/project-hygiene.mjs +43 -3
- package/scripts/lib/quality-gate.mjs +271 -13
- package/scripts/lib/reconcile/emitter.mjs +87 -19
- package/scripts/lib/reconcile/engine.mjs +281 -13
- package/scripts/lib/reconcile/idempotency.mjs +102 -1
- package/scripts/lib/reconcile/renderer.mjs +148 -3
- package/scripts/lib/reconcile/sanitize.mjs +40 -17
- package/scripts/lib/reconcile/writer.mjs +415 -84
- package/scripts/lib/rule-loader.mjs +37 -2
- package/scripts/lib/rules-sync.mjs +51 -8
- package/scripts/lib/scope-gate.mjs +90 -0
- package/scripts/lib/session-close-backfill.mjs +369 -28
- package/scripts/lib/session-discovery.mjs +13 -3
- package/scripts/lib/session-end/phase-skip.mjs +37 -4
- package/scripts/lib/session-end/worktree-cleanup.mjs +154 -7
- package/scripts/lib/session-id.mjs +30 -14
- package/scripts/lib/session-identity/own-session.mjs +159 -0
- package/scripts/lib/session-lock.mjs +85 -30
- package/scripts/lib/session-schema/normalizer.mjs +70 -3
- package/scripts/lib/session-schema/validator.mjs +40 -0
- package/scripts/lib/session-start-probes.mjs +608 -0
- package/scripts/lib/session-transition.mjs +277 -0
- package/scripts/lib/sessions-staleness-banner.mjs +124 -57
- package/scripts/lib/spiral-carryover.mjs +90 -9
- package/scripts/lib/state-md/frontmatter-mutators.mjs +41 -8
- package/scripts/lib/state-md/mission-status.mjs +350 -52
- package/scripts/lib/state-md/yaml-parser.mjs +145 -16
- package/scripts/lib/state-md.mjs +12 -2
- package/scripts/lib/telemetry/sync.mjs +46 -8
- package/scripts/lib/validate/check-agents.mjs +66 -0
- package/scripts/lib/validate/check-cursor-adapter.mjs +102 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +24 -2
- package/scripts/lib/validate/check-doc-cli-commands.mjs +16 -32
- package/scripts/lib/validate/check-hooks-symmetry.mjs +29 -63
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +13 -22
- package/scripts/lib/validate/check-plugin-monitors.mjs +10 -4
- package/scripts/lib/validate/check-test-value-bans.mjs +165 -17
- package/scripts/lib/validate/check-unwired-features.mjs +340 -32
- package/scripts/lib/validate/repo-files.mjs +275 -0
- package/scripts/lib/validate-vendored-rules.mjs +229 -7
- package/scripts/lib/vault-mirror/process.mjs +99 -43
- package/scripts/lib/vault-mirror/telemetry.mjs +210 -0
- package/scripts/lib/vault-staleness-banner.mjs +76 -6
- package/scripts/lib/vault-status/board-writer.mjs +211 -10
- package/scripts/lib/vault-status/narrative-mirror.mjs +188 -8
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +832 -0
- package/scripts/lib/wave-transcript-tail.mjs +869 -0
- package/scripts/materialize-wave-scope.mjs +209 -12
- package/scripts/mcp-server.sh +11 -2
- package/scripts/parse-config.mjs +65 -0
- package/scripts/token-audit.sh +9 -2
- package/scripts/validate-plugin.mjs +3 -0
- package/scripts/validate-wave-scope.mjs +67 -0
- package/scripts/vault-mirror.mjs +203 -34
- package/skills/_shared/monitor-patterns.md +31 -5
- package/skills/_shared/parallel-aware-auq.md +1 -1
- package/skills/_shared/parallel-aware-preamble.md +4 -2
- package/skills/_shared/platform-tools.md +11 -5
- package/skills/_shared/state-ownership.md +29 -2
- package/skills/autopilot/SKILL.md +5 -1
- package/skills/bootstrap/SKILL.md +3 -3
- package/skills/bootstrap/_shared-template.md +18 -10
- package/skills/bootstrap/deep-template.md +10 -6
- package/skills/bootstrap/fast-template.md +15 -8
- package/skills/bootstrap/standard-template.md +10 -6
- package/skills/claude-md-drift-check/checker.mjs +39 -11
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/journey-audit/SKILL.md +269 -0
- package/skills/peekaboo-driver/SKILL.md +15 -3
- package/skills/persona-panel/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +41 -1
- package/skills/session-end/SKILL.md +17 -4
- package/skills/session-end/metrics-collection.md +7 -4
- package/skills/session-end/phase-3-6-tail.md +11 -3
- package/skills/session-end/phase-3-7a-recommendations.md +16 -2
- package/skills/session-plan/SKILL.md +6 -1
- package/skills/session-plan/wave-template.md +1 -0
- package/skills/session-start/SKILL.md +30 -16
- package/skills/session-start/phase-7-5-mode-selector.md +15 -3
- package/skills/session-start/phase-8-5-express-path.md +77 -12
- package/skills/vault-sync/validator.mjs +31 -0
- package/skills/wave-executor/SKILL.md +4 -2
- package/skills/wave-executor/circuit-breaker.md +34 -9
- package/skills/wave-executor/wave-loop.md +102 -19
- package/templates/_shared/journey-manifest.md +110 -0
- package/templates/_shared/rules/parallel-sessions.md +0 -77
|
@@ -47,11 +47,35 @@
|
|
|
47
47
|
* - a `.git/config` written AFTER this probe ran. It is a session-start
|
|
48
48
|
* snapshot, not a watcher.
|
|
49
49
|
*
|
|
50
|
-
* **Revisit trigger
|
|
51
|
-
* deliberate local override
|
|
50
|
+
* **Revisit trigger (identity / remote / gpgsign only):** if an operator
|
|
51
|
+
* reports a standing false positive on a deliberate local override of one of
|
|
52
|
+
* those three, add an explicit allow-list (a `[sessionOrchestrator]`
|
|
52
53
|
* local-config key naming the accepted keys) — do NOT widen or delete a rule,
|
|
53
54
|
* which would re-open the exact hole the probe was built to close.
|
|
54
55
|
*
|
|
56
|
+
* `hooks-path` is no longer in that bucket (#1158, 2026-08-28): a
|
|
57
|
+
* `core.hooksPath` outside `.husky/_` is accepted when the repo DECLARES it —
|
|
58
|
+
* `git ls-files -- <hooksPath>` returns at least one TRACKED file DIRECTLY
|
|
59
|
+
* under the hooksPath whose BASENAME is a real git hook name (see
|
|
60
|
+
* `GIT_HOOK_NAMES`, sourced from `git help hooks`), the same "declared beats
|
|
61
|
+
* guessed" shape `isFixtureHost` already applies to the remote finding. A
|
|
62
|
+
* tracked `.githooks/pre-commit` with `core.hooksPath=.githooks` is a
|
|
63
|
+
* deliberate setup, not drift, and stops flagging without any config key. An
|
|
64
|
+
* untracked or out-of-repo hooksPath still flags exactly as before — that is
|
|
65
|
+
* still the incident class this probe exists for (a fixture that rewrote
|
|
66
|
+
* hooksPath to somewhere nothing is tracked).
|
|
67
|
+
*
|
|
68
|
+
* **Narrowed 2026-08-28 (review, #1158/#1159 N1):** the first cut of this
|
|
69
|
+
* acceptance rule asked only "does `git ls-files` return anything at all
|
|
70
|
+
* under the hooksPath" — which accepted `core.hooksPath=scripts` the moment
|
|
71
|
+
* ANY file under `scripts/` was tracked, including an UNTRACKED executable
|
|
72
|
+
* literally named `pre-commit` planted alongside ordinary tracked source
|
|
73
|
+
* (measured exploit: `git ls-files -- scripts` returns 456 tracked files in
|
|
74
|
+
* this repo, none of them a hook, and the old check accepted it on count
|
|
75
|
+
* alone). `hooksPathIsTracked` now requires a tracked file DIRECTLY under the
|
|
76
|
+
* hooksPath whose basename git itself would invoke as a hook — a tracked
|
|
77
|
+
* directory of unrelated source no longer counts as a declaration.
|
|
78
|
+
*
|
|
55
79
|
* ## Fail-open is forbidden here
|
|
56
80
|
*
|
|
57
81
|
* A failed query is NOT a clean repo. That fold — `null` meaning both "asked
|
|
@@ -77,6 +101,7 @@
|
|
|
77
101
|
*/
|
|
78
102
|
|
|
79
103
|
import { spawnSync } from 'node:child_process';
|
|
104
|
+
import { isAbsolute, relative } from 'node:path';
|
|
80
105
|
|
|
81
106
|
/** Default timeout in ms for each `git config` invocation. */
|
|
82
107
|
export const DEFAULT_TIMEOUT_MS = 5000;
|
|
@@ -112,7 +137,9 @@ export const DEGRADED_REASONS = Object.freeze([
|
|
|
112
137
|
* global identity. The costliest entry of the incident.
|
|
113
138
|
* - `local-gpgsign` — a local `commit.gpgsign` differing from the global.
|
|
114
139
|
* - `fixture-remote` — a remote URL on a reserved/fixture host.
|
|
115
|
-
* - `hooks-path` — a local `core.hooksPath` not pointing at `.husky/_
|
|
140
|
+
* - `hooks-path` — a local `core.hooksPath` not pointing at `.husky/_`
|
|
141
|
+
* and not DECLARED by the repo — no tracked file directly under it is a
|
|
142
|
+
* real git hook name; see `hooksPathIsTracked` / `GIT_HOOK_NAMES`.
|
|
116
143
|
*
|
|
117
144
|
* @type {readonly ['ambient-git-env','local-identity','local-gpgsign','fixture-remote','hooks-path']}
|
|
118
145
|
*/
|
|
@@ -181,6 +208,45 @@ const FIXTURE_HOSTS = Object.freeze([
|
|
|
181
208
|
/** The only `core.hooksPath` this repo expects, matched as a path tail. */
|
|
182
209
|
const EXPECTED_HOOKS_PATH_TAIL = '.husky/_';
|
|
183
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Every hook name git itself recognizes under a `core.hooksPath` directory.
|
|
213
|
+
* Source: `git help hooks`. A tracked file below a declared hooksPath whose
|
|
214
|
+
* BASENAME is not one of these is not a hook git will ever invoke — accepting
|
|
215
|
+
* it as a "declaration" would accept an arbitrary tracked file, which is the
|
|
216
|
+
* #1158 review finding this list closes (see the module header's Narrowed
|
|
217
|
+
* note).
|
|
218
|
+
*/
|
|
219
|
+
const GIT_HOOK_NAMES = Object.freeze([
|
|
220
|
+
'applypatch-msg',
|
|
221
|
+
'pre-applypatch',
|
|
222
|
+
'post-applypatch',
|
|
223
|
+
'pre-commit',
|
|
224
|
+
'pre-merge-commit',
|
|
225
|
+
'prepare-commit-msg',
|
|
226
|
+
'commit-msg',
|
|
227
|
+
'post-commit',
|
|
228
|
+
'pre-rebase',
|
|
229
|
+
'post-checkout',
|
|
230
|
+
'post-merge',
|
|
231
|
+
'pre-push',
|
|
232
|
+
'pre-receive',
|
|
233
|
+
'update',
|
|
234
|
+
'proc-receive',
|
|
235
|
+
'post-receive',
|
|
236
|
+
'post-update',
|
|
237
|
+
'reference-transaction',
|
|
238
|
+
'push-to-checkout',
|
|
239
|
+
'pre-auto-gc',
|
|
240
|
+
'post-rewrite',
|
|
241
|
+
'sendemail-validate',
|
|
242
|
+
'fsmonitor-watchman',
|
|
243
|
+
'p4-changelist',
|
|
244
|
+
'p4-prepare-changelist',
|
|
245
|
+
'p4-post-changelist',
|
|
246
|
+
'p4-pre-submit',
|
|
247
|
+
'post-index-change',
|
|
248
|
+
]);
|
|
249
|
+
|
|
184
250
|
/**
|
|
185
251
|
* Build the filtered child environment. See {@link GIT_ENV_ALLOWLIST}.
|
|
186
252
|
*
|
|
@@ -310,6 +376,67 @@ function hooksPathIsExpected(value) {
|
|
|
310
376
|
return normalized === EXPECTED_HOOKS_PATH_TAIL || normalized.endsWith(`/${EXPECTED_HOOKS_PATH_TAIL}`);
|
|
311
377
|
}
|
|
312
378
|
|
|
379
|
+
/**
|
|
380
|
+
* @param {string} pathspec repo-relative hooksPath (posix separators, no
|
|
381
|
+
* trailing slash) already resolved to sit inside `repoRoot`
|
|
382
|
+
* @param {string} trackedLine one line of `git ls-files` output (repo-relative;
|
|
383
|
+
* git prints `/`-separated paths even on Windows, but this normalizes `\`
|
|
384
|
+
* too in case a future caller feeds it something else)
|
|
385
|
+
* @returns {boolean} true when `trackedLine` sits DIRECTLY under `pathspec`
|
|
386
|
+
* (not in a nested subdirectory) and its basename is a recognized git hook
|
|
387
|
+
* name — see {@link GIT_HOOK_NAMES}. A tracked file two levels deep, or a
|
|
388
|
+
* tracked file with an unrelated name, is deliberately NOT a declaration.
|
|
389
|
+
*/
|
|
390
|
+
function isDeclaredHookFile(pathspec, trackedLine) {
|
|
391
|
+
const line = String(trackedLine ?? '').replace(/\\/g, '/');
|
|
392
|
+
const slash = line.lastIndexOf('/');
|
|
393
|
+
const dir = slash === -1 ? '' : line.slice(0, slash);
|
|
394
|
+
const base = slash === -1 ? line : line.slice(slash + 1);
|
|
395
|
+
return dir === pathspec && GIT_HOOK_NAMES.includes(base);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* @param {string} value a `core.hooksPath` value that already failed
|
|
400
|
+
* {@link hooksPathIsExpected}
|
|
401
|
+
* @param {{repoRoot: string, env: Record<string,string|undefined>, timeoutMs: number, spawn: Function}} ctx
|
|
402
|
+
* the same context {@link runGitConfig} uses — same filtered env, same `-C`
|
|
403
|
+
* seam, so this query cannot be redirected any more than the config read can
|
|
404
|
+
* @returns {boolean} true when the path is DECLARED as a hook by the repo:
|
|
405
|
+
* `git ls-files -- <path>` returns at least one TRACKED file DIRECTLY under
|
|
406
|
+
* it whose basename is a real git hook name (#1158 review — a tracked
|
|
407
|
+
* directory of ordinary source files is not itself a declaration; see
|
|
408
|
+
* {@link GIT_HOOK_NAMES} / {@link isDeclaredHookFile}). An absolute value is
|
|
409
|
+
* resolved relative to `repoRoot` first; a value outside the repo root can
|
|
410
|
+
* never be tracked and returns `false` without spawning git. Any spawn
|
|
411
|
+
* failure (missing git, timeout, non-zero exit) also returns `false` — this
|
|
412
|
+
* helper only ever WIDENS acceptance, so a query it cannot answer must fall
|
|
413
|
+
* back to "not declared", never the reverse.
|
|
414
|
+
*/
|
|
415
|
+
function hooksPathIsTracked(value, ctx) {
|
|
416
|
+
const raw = String(value ?? '').trim();
|
|
417
|
+
if (raw === '') return false;
|
|
418
|
+
|
|
419
|
+
let pathspec = raw.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
420
|
+
if (isAbsolute(raw)) {
|
|
421
|
+
const rel = relative(ctx.repoRoot, raw);
|
|
422
|
+
if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) return false; // outside repo root
|
|
423
|
+
pathspec = rel.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const res = ctx.spawn('git', ['-C', ctx.repoRoot, 'ls-files', '--', pathspec], {
|
|
427
|
+
encoding: 'utf8',
|
|
428
|
+
timeout: ctx.timeoutMs,
|
|
429
|
+
env: filteredGitEnv(ctx.env),
|
|
430
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
if (res?.error || res?.signal) return false;
|
|
434
|
+
if (typeof res?.status !== 'number' || res.status !== 0) return false;
|
|
435
|
+
|
|
436
|
+
const lines = String(res?.stdout ?? '').split('\n').filter((l) => l.length > 0);
|
|
437
|
+
return lines.some((line) => isDeclaredHookFile(pathspec, line));
|
|
438
|
+
}
|
|
439
|
+
|
|
313
440
|
/**
|
|
314
441
|
* Build the degraded result. Distinct from `null` on purpose — see the header.
|
|
315
442
|
*
|
|
@@ -439,12 +566,14 @@ export function checkGitConfigDrift(opts = {}, deps = {}) {
|
|
|
439
566
|
continue;
|
|
440
567
|
}
|
|
441
568
|
|
|
442
|
-
if (lower === 'core.hookspath' && !hooksPathIsExpected(value)) {
|
|
569
|
+
if (lower === 'core.hookspath' && !hooksPathIsExpected(value) && !hooksPathIsTracked(value, ctx)) {
|
|
443
570
|
findings.push({
|
|
444
571
|
kind: 'hooks-path',
|
|
445
572
|
key,
|
|
446
573
|
value,
|
|
447
|
-
detail:
|
|
574
|
+
detail:
|
|
575
|
+
`core.hooksPath=${value} zeigt nicht auf ${EXPECTED_HOOKS_PATH_TAIL} und ist im Repo ` +
|
|
576
|
+
`nicht als getrackter Pfad deklariert — Hooks laufen möglicherweise nicht.`,
|
|
448
577
|
});
|
|
449
578
|
}
|
|
450
579
|
}
|
|
@@ -30,11 +30,26 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import { readFile, writeFile, mkdir, chmod } from 'node:fs/promises';
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
existsSync,
|
|
35
|
+
readFileSync,
|
|
36
|
+
writeFileSync,
|
|
37
|
+
renameSync,
|
|
38
|
+
chmodSync,
|
|
39
|
+
mkdirSync,
|
|
40
|
+
unlinkSync,
|
|
41
|
+
} from 'node:fs';
|
|
34
42
|
import { digestSha256WithSalt } from './crypto-digest-utils.mjs';
|
|
35
43
|
import path from 'node:path';
|
|
36
44
|
import os from 'node:os';
|
|
37
|
-
|
|
45
|
+
// NOTE (#1072 gate fix): platform.mjs is deliberately NOT imported statically.
|
|
46
|
+
// Its module level computes SO_PLATFORM/SO_PLUGIN_ROOT via detectPlatform()/
|
|
47
|
+
// resolvePluginRoot(), which walk the filesystem (existsSync + readFileSync of
|
|
48
|
+
// package.json up the tree). session-lock.mjs now imports this module, which
|
|
49
|
+
// put those import-time reads into every graph that imports session-lock —
|
|
50
|
+
// including fs-mocked test graphs (tests/lib/vault-mirror/process.test.mjs went
|
|
51
|
+
// red: the walk consumed the mocks' sequenced return values). The only two
|
|
52
|
+
// consumers (SO_OS/SO_PLATFORM in collectFingerprint) are async — load lazily.
|
|
38
53
|
|
|
39
54
|
const FINGERPRINT_TTL_MS = 24 * 60 * 60 * 1000; // 24h
|
|
40
55
|
const PLACEHOLDER_SALT = 'env-aware-v1-default-salt-replaced-by-owner-yaml';
|
|
@@ -97,6 +112,235 @@ export function hashHostname(hostname, salt) {
|
|
|
97
112
|
return digestSha256WithSalt(hostname, { salt: String(salt) });
|
|
98
113
|
}
|
|
99
114
|
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Stable host identity (GitLab #1072)
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
//
|
|
119
|
+
// `os.hostname()` is NOT stable on a single machine. Measured on the reference
|
|
120
|
+
// host 2026-08-24: two readings ten minutes apart returned `Mac.home` and
|
|
121
|
+
// `Ferdinands-MacBook-Pro.local`, and `.orchestrator/metrics/events.jsonl`
|
|
122
|
+
// carries both spellings (106× / 27×). Every `lock.host === os.hostname()`
|
|
123
|
+
// comparison in the lock family therefore fails against the machine's OWN lock
|
|
124
|
+
// after a flip: stale detection reports `cross-host` (and cross-host locks are
|
|
125
|
+
// never reaped or overridden by design, PSA-003), the reaper refuses with
|
|
126
|
+
// `cross-host-requires-operator`, and release owner-match returns `not-owner`.
|
|
127
|
+
//
|
|
128
|
+
// Two independent layers, because one is not enough:
|
|
129
|
+
// 1. `stableHostname()` normalises the SUFFIX difference (`Mac.home` vs
|
|
130
|
+
// `Mac.local` → `mac`). It cannot bridge `mac` vs `ferdinands-macbook-pro`
|
|
131
|
+
// — those differ before the suffix.
|
|
132
|
+
// 2. The self-alias ledger records every spelling THIS machine has presented
|
|
133
|
+
// itself under, so `hostnamesMatch()` can bridge the remaining gap. Only
|
|
134
|
+
// names the local machine wrote about itself ever enter it, which is what
|
|
135
|
+
// keeps a genuinely foreign host from ever matching.
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Suffixes stripped by {@link stableHostname}. mDNS (`.local`), the common
|
|
139
|
+
* router-assigned search domains (`.home`, `.lan`), and the POSIX default
|
|
140
|
+
* (`.localdomain`). Deliberately NOT a generic "strip the last label" rule:
|
|
141
|
+
* `a.b.example.com` is a real FQDN whose labels carry meaning.
|
|
142
|
+
*/
|
|
143
|
+
const LOCAL_HOST_SUFFIXES = ['.local', '.home', '.lan', '.localdomain'];
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Normalise a hostname into a comparable form: trimmed, lowercased, with ONE
|
|
147
|
+
* trailing local-network suffix removed.
|
|
148
|
+
*
|
|
149
|
+
* Never throws. A non-string, empty, or whitespace-only input yields `''`
|
|
150
|
+
* (which {@link hostnamesMatch} treats as "no identity" — it never matches).
|
|
151
|
+
*
|
|
152
|
+
* NOTE on the default: `stableHostname()` and `stableHostname(undefined)` both
|
|
153
|
+
* normalise `os.hostname()`, because that is JavaScript default-parameter
|
|
154
|
+
* semantics. Pass `''` or `null` to get the empty result.
|
|
155
|
+
*
|
|
156
|
+
* @param {string} [raw=os.hostname()]
|
|
157
|
+
* @returns {string} normalised hostname, or '' when there is nothing to normalise.
|
|
158
|
+
*/
|
|
159
|
+
export function stableHostname(raw = os.hostname()) {
|
|
160
|
+
if (typeof raw !== 'string') return '';
|
|
161
|
+
let name = raw.trim().toLowerCase();
|
|
162
|
+
if (name === '') return '';
|
|
163
|
+
for (const suffix of LOCAL_HOST_SUFFIXES) {
|
|
164
|
+
// `name.length > suffix.length` keeps a bare `.local` from normalising to ''.
|
|
165
|
+
if (name.endsWith(suffix) && name.length > suffix.length) {
|
|
166
|
+
name = name.slice(0, -suffix.length);
|
|
167
|
+
break; // ONE suffix only — `foo.lan.local` keeps its inner label.
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return name;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Host-alias ledger path. `SO_HOST_ALIASES_FILE` overrides it — MANDATORY for
|
|
175
|
+
* tests, which must never write into the operator's real `~/.config`.
|
|
176
|
+
*
|
|
177
|
+
* The `.trim() || fallback` shape is deliberate: a whitespace-only env var is
|
|
178
|
+
* truthy and would otherwise short-circuit the `||` (see `.claude/rules/
|
|
179
|
+
* development.md` § Error Handling, env-var fallback whitespace trap).
|
|
180
|
+
*
|
|
181
|
+
* @returns {string}
|
|
182
|
+
*/
|
|
183
|
+
function _hostAliasesFile() {
|
|
184
|
+
const override = (process.env.SO_HOST_ALIASES_FILE || '').trim();
|
|
185
|
+
if (override) return override;
|
|
186
|
+
return path.join(_privateDir(), 'host-aliases.json');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Named ceiling (BV-004): a machine realistically presents 2–4 spellings of
|
|
191
|
+
* itself (`os.hostname()`, the mDNS variant, a DHCP-assigned name). 16 bounds
|
|
192
|
+
* the pathological DHCP-churn case while leaving ample headroom. Revisit if a
|
|
193
|
+
* real host is ever observed writing more than a handful of distinct names —
|
|
194
|
+
* beyond that the ledger stops being an identity record and becomes a history.
|
|
195
|
+
*/
|
|
196
|
+
const HOST_ALIASES_MAX = 16;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Synchronous atomic JSON write with an explicit mode. The async twin
|
|
200
|
+
* `_writeJsonAtomic` below cannot serve the alias ledger: every caller of
|
|
201
|
+
* {@link recordHostAlias} sits on a SYNCHRONOUS lock path. `io.mjs`'s
|
|
202
|
+
* `writeJsonAtomicSync` is not reused because it offers no mode argument, and
|
|
203
|
+
* 0o600 is the whole point of a file that lives beside `host-private.json`.
|
|
204
|
+
*
|
|
205
|
+
* @param {string} filePath
|
|
206
|
+
* @param {*} data
|
|
207
|
+
* @param {number} mode
|
|
208
|
+
*/
|
|
209
|
+
function _writeJsonAtomicSync(filePath, data, mode) {
|
|
210
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
211
|
+
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
212
|
+
try {
|
|
213
|
+
// `flag: 'wx'` + `mode` on the CREATE, never a chmod after the rename.
|
|
214
|
+
// Two distinct holes closed: `wx` fails with EEXIST rather than following a
|
|
215
|
+
// pre-planted symlink at the (predictable) tmp path, so an attacker cannot
|
|
216
|
+
// redirect the write; and the mode is applied at creation, so the file is
|
|
217
|
+
// never briefly world-readable between `writeFileSync` and `chmodSync` —
|
|
218
|
+
// the window in which the ledger beside `host-private.json` was readable.
|
|
219
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', { encoding: 'utf8', mode, flag: 'wx' });
|
|
220
|
+
renameSync(tmp, filePath);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
try { unlinkSync(tmp); } catch { /* best effort */ }
|
|
223
|
+
throw err;
|
|
224
|
+
}
|
|
225
|
+
// Belt-and-braces for the pre-existing-target case: `rename` keeps the
|
|
226
|
+
// SOURCE inode, so the mode above already governs — this only repairs a
|
|
227
|
+
// umask-widened mode on platforms that ignore the create mode.
|
|
228
|
+
try { chmodSync(filePath, mode); } catch { /* best effort */ }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Read the self-alias ledger. Returns normalised names.
|
|
233
|
+
*
|
|
234
|
+
* Fail-CLOSED by construction: any failure (absent file, unreadable, malformed
|
|
235
|
+
* JSON, wrong shape) yields `[]`, which reduces {@link hostnamesMatch} to plain
|
|
236
|
+
* normalised equality. A broken ledger can therefore only ever REFUSE a match
|
|
237
|
+
* that would otherwise have been made — it can never manufacture one.
|
|
238
|
+
*
|
|
239
|
+
* @returns {string[]} normalised alias names; `[]` on any failure.
|
|
240
|
+
*/
|
|
241
|
+
export function readHostAliases() {
|
|
242
|
+
try {
|
|
243
|
+
const parsed = JSON.parse(readFileSync(_hostAliasesFile(), 'utf8'));
|
|
244
|
+
if (!Array.isArray(parsed)) return [];
|
|
245
|
+
const seen = new Set();
|
|
246
|
+
for (const entry of parsed) {
|
|
247
|
+
const norm = typeof entry === 'string' ? stableHostname(entry) : '';
|
|
248
|
+
if (norm) seen.add(norm);
|
|
249
|
+
}
|
|
250
|
+
return [...seen];
|
|
251
|
+
} catch {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Record the name this machine currently presents itself under, so a later
|
|
258
|
+
* reading under a DIFFERENT spelling can still be recognised as the same host.
|
|
259
|
+
*
|
|
260
|
+
* Best-effort and idempotent: never throws, never duplicates, and writes
|
|
261
|
+
* nothing when the name is already present or normalises away to ''. Callers
|
|
262
|
+
* invoke it once per session-lock acquisition — the one moment we know for a
|
|
263
|
+
* fact that the current `os.hostname()` belongs to THIS machine.
|
|
264
|
+
*
|
|
265
|
+
* @param {string} [name=stableHostname()] — raw or normalised hostname.
|
|
266
|
+
* @returns {string[]} the ledger contents after the call (`[]` on failure).
|
|
267
|
+
*/
|
|
268
|
+
export function recordHostAlias(name = stableHostname()) {
|
|
269
|
+
try {
|
|
270
|
+
const norm = stableHostname(typeof name === 'string' ? name : '');
|
|
271
|
+
if (!norm) return readHostAliases();
|
|
272
|
+
const current = readHostAliases();
|
|
273
|
+
if (current.includes(norm)) return current;
|
|
274
|
+
// Keep the MOST RECENT names when the cap bites — an old spelling this
|
|
275
|
+
// machine has not used in 16 renames is the safest one to forget.
|
|
276
|
+
const next = [...current, norm].slice(-HOST_ALIASES_MAX);
|
|
277
|
+
_writeJsonAtomicSync(_hostAliasesFile(), next, 0o600);
|
|
278
|
+
return next;
|
|
279
|
+
} catch {
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Decide whether two hostnames name the SAME machine.
|
|
286
|
+
*
|
|
287
|
+
* Match when the normalised forms are equal, or when BOTH appear in the
|
|
288
|
+
* self-alias set (see {@link recordHostAlias}). Requiring both sides to be in
|
|
289
|
+
* the set is what preserves the cross-host invariants: a foreign host's name
|
|
290
|
+
* was never written by this machine, so it cannot be in the ledger, so it can
|
|
291
|
+
* never match — `cross-host` stays `cross-host` (PSA-003).
|
|
292
|
+
*
|
|
293
|
+
* The ledger is read ONLY when normalised equality already failed, so the
|
|
294
|
+
* common same-host path costs no filesystem access even inside a poll loop.
|
|
295
|
+
*
|
|
296
|
+
* @param {string} a
|
|
297
|
+
* @param {string} b
|
|
298
|
+
* @param {{ aliases?: string[] }} [opts] — inject the alias set (tests, and
|
|
299
|
+
* callers that already hold it); omitted → read the ledger.
|
|
300
|
+
* @returns {boolean}
|
|
301
|
+
*/
|
|
302
|
+
export function hostnamesMatch(a, b, { aliases } = {}) {
|
|
303
|
+
const normA = stableHostname(typeof a === 'string' ? a : '');
|
|
304
|
+
const normB = stableHostname(typeof b === 'string' ? b : '');
|
|
305
|
+
if (!normA || !normB) return false;
|
|
306
|
+
if (normA === normB) return true;
|
|
307
|
+
|
|
308
|
+
const set = Array.isArray(aliases)
|
|
309
|
+
? aliases.map((x) => (typeof x === 'string' ? stableHostname(x) : '')).filter(Boolean)
|
|
310
|
+
: readHostAliases();
|
|
311
|
+
return set.includes(normA) && set.includes(normB);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Pick the host identity to compare a lock body against: the normalised
|
|
316
|
+
* `host_id` when it carries one, else the raw pre-#1072 `host`.
|
|
317
|
+
*
|
|
318
|
+
* `||`, not `??`, and that is the whole point. `??` falls back on `null` and
|
|
319
|
+
* `undefined` only, so a lock whose `host_id` is the EMPTY STRING — what a
|
|
320
|
+
* writer produces when `os.hostname()` momentarily returns '' or whitespace —
|
|
321
|
+
* short-circuits to `''`, and `hostnamesMatch('', host)` is false by contract
|
|
322
|
+
* ("no identity never matches"). The machine then reads its OWN lock as
|
|
323
|
+
* cross-host: stale detection is disabled, the reaper refuses with
|
|
324
|
+
* `cross-host-requires-operator`, and release returns `not-owner` — the exact
|
|
325
|
+
* #1072 failure the `host_id` field was added to prevent, re-entered through
|
|
326
|
+
* the fallback operator. Falling back on '' costs nothing: an empty `host` too
|
|
327
|
+
* yields '' here, and `hostnamesMatch` still refuses it.
|
|
328
|
+
*
|
|
329
|
+
* The `.trim()` extends the same fix one step: a whitespace-only `host_id` is
|
|
330
|
+
* TRUTHY, so a bare `||` would short-circuit on it exactly as `??` does on `''`
|
|
331
|
+
* (`.claude/rules/development.md` § Error Handling, env-var fallback whitespace
|
|
332
|
+
* trap — the same shape, one domain over).
|
|
333
|
+
*
|
|
334
|
+
* @param {{ host_id?: unknown, host?: unknown }|null|undefined} lock
|
|
335
|
+
* @returns {string} raw (un-normalised, but trimmed) candidate name; '' when
|
|
336
|
+
* neither field carries a usable value. Never throws.
|
|
337
|
+
*/
|
|
338
|
+
export function lockHostCandidate(lock) {
|
|
339
|
+
const id = typeof lock?.host_id === 'string' ? lock.host_id.trim() : '';
|
|
340
|
+
if (id) return id;
|
|
341
|
+
return typeof lock?.host === 'string' ? lock.host.trim() : '';
|
|
342
|
+
}
|
|
343
|
+
|
|
100
344
|
// ---------------------------------------------------------------------------
|
|
101
345
|
// Paths
|
|
102
346
|
// ---------------------------------------------------------------------------
|
|
@@ -149,6 +393,7 @@ export async function resolveSalt() {
|
|
|
149
393
|
* @param {string} [opts.salt] — override salt (tests)
|
|
150
394
|
*/
|
|
151
395
|
export async function collectFingerprint(opts = {}) {
|
|
396
|
+
const { SO_OS, SO_PLATFORM } = await import('./platform.mjs');
|
|
152
397
|
const osName = SO_OS;
|
|
153
398
|
const arch = process.arch;
|
|
154
399
|
const cpus = os.cpus() || [];
|
|
@@ -107,8 +107,38 @@ export const DEFAULT_CEILING = 480;
|
|
|
107
107
|
* the live repo sits at 471/480 directives (98.1% of ceiling) but 108589/114000
|
|
108
108
|
* bytes (95.3%). That asymmetry is inherited from the pre-existing directive
|
|
109
109
|
* ratchet, not introduced here.
|
|
110
|
+
*
|
|
111
|
+
* RE-BASELINED 2026-08-22: 114000 -> 121000. The ratchet fired for exactly the
|
|
112
|
+
* reason stated above — "a genuinely NEW always-on surface is added". Since the
|
|
113
|
+
* 2026-07-30 calibration this repo's corpus grew 108589 -> 115730 B, and the
|
|
114
|
+
* largest single cause is a rule file the operator adopted:
|
|
115
|
+
* .claude/rules/host-resources.md (7457 B, #1089, live 2026-08-21). Headroom
|
|
116
|
+
* unchanged at +5%: 115730 x 1.05 = 121516, rounded DOWN to 121000. Same
|
|
117
|
+
* relative slack, measured against the corpus that actually exists.
|
|
118
|
+
*
|
|
119
|
+
* Consumer impact was CHECKED, not assumed. This module ships inside the npm
|
|
120
|
+
* package, so the first instinct was to keep the shared default fixed and set a
|
|
121
|
+
* repo-local `instruction-budget.byte-ceiling` override instead. Two
|
|
122
|
+
* measurements killed that plan: (a) the package ships THREE always-on rules
|
|
123
|
+
* totalling 9261 B — both ceilings sit ~12x above any consumer's inherited
|
|
124
|
+
* corpus, so the delta is numerically inert downstream; and (b) the number was
|
|
125
|
+
* never a shipped-corpus figure in the first place — the calibration above says
|
|
126
|
+
* "measured against this repo's own always-on corpus". A repo-local override
|
|
127
|
+
* also could not have worked: tests/rules/receiving-review.test.mjs calls
|
|
128
|
+
* computeInstructionBudget({repoRoot}), which does not read Session Config, so
|
|
129
|
+
* it pins this constant by construction.
|
|
130
|
+
*
|
|
131
|
+
* Not a licence to raise the number whenever it is hit — a ratchet that yields
|
|
132
|
+
* on contact measures nothing, and the NEXT breach belongs to a diet. 4499 B of
|
|
133
|
+
* the always-on security.md still describes surfaces this repo does not have
|
|
134
|
+
* (SSRF via an http-client package, an OWASP table, RLS, bcrypt/JWT), vendored
|
|
135
|
+
* from the baseline and never adapted — the same class as the Semgrep claim
|
|
136
|
+
* fixed today. That cut is real and nameable and is tracked in #1126; it was
|
|
137
|
+
* deliberately NOT taken in the same pass that raised the ceiling, because
|
|
138
|
+
* deleting security prose to hit a number is the failure build-value.md BV-002
|
|
139
|
+
* names.
|
|
110
140
|
*/
|
|
111
|
-
export const DEFAULT_BYTE_CEILING =
|
|
141
|
+
export const DEFAULT_BYTE_CEILING = 121000;
|
|
112
142
|
|
|
113
143
|
/**
|
|
114
144
|
* Read the `instruction-budget:` nested block from the `## Session Config`
|