session-orchestrator 3.22.0 → 3.24.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/remote-offload/SKILL.md +13 -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 +446 -0
- package/README.md +22 -17
- package/agents/AGENTS.md +23 -4
- package/agents/code-implementer.md +2 -1
- package/agents/db-specialist.md +2 -2
- 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 +194 -25
- package/docs/codex-setup.md +64 -0
- package/docs/components.md +7 -7
- package/docs/cursor-setup.md +26 -47
- package/docs/events-schema.md +120 -10
- 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 +89 -9
- package/docs/session-config-template.md +38 -7
- package/docs/telemetry/telemetry-claims.md +11 -10
- package/docs/telemetry.md +52 -1
- package/hooks/_lib/atomic-json.mjs +111 -0
- package/hooks/_lib/lock-bootstrap.mjs +8 -4
- package/hooks/_lib/subagent-paths.mjs +143 -0
- package/hooks/_lib/vcs-create-matcher.mjs +397 -38
- package/hooks/cwd-change-restore.mjs +9 -29
- package/hooks/enforce-scope.mjs +93 -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 +486 -19
- package/hooks/on-session-start.mjs +263 -12
- package/hooks/on-stop.mjs +392 -24
- package/hooks/post-bash-write-verify.mjs +104 -4
- package/hooks/post-subagent-discovery-validator.mjs +182 -21
- package/hooks/post-tool-batch-wave-signal.mjs +165 -42
- package/hooks/post-tool-failure-corrective-context.mjs +9 -32
- package/hooks/pre-bash-issue-budget.mjs +117 -4
- package/hooks/pre-bash-memory-propose-audit.mjs +13 -7
- 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/hooks/subagent-telemetry.mjs +11 -26
- 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 +31 -12
- package/scripts/backfill-abandoned-sessions.mjs +80 -11
- package/scripts/backfill-evidence-digest.mjs +376 -0
- package/scripts/cursor-install.mjs +89 -48
- package/scripts/emit-event.mjs +10 -2
- 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/auq/parse.mjs +5 -29
- package/scripts/lib/auto-dialectic.mjs +68 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +318 -18
- package/scripts/lib/build-live-signals.mjs +49 -27
- package/scripts/lib/ci-status-banner.mjs +158 -11
- package/scripts/lib/cold-start-detector.mjs +23 -14
- package/scripts/lib/command-blocker.mjs +70 -0
- package/scripts/lib/config/block-header.mjs +55 -0
- package/scripts/lib/config/discovery-validator.mjs +7 -2
- package/scripts/lib/config/health-endpoints.mjs +383 -0
- package/scripts/lib/config/reconcile.mjs +79 -4
- package/scripts/lib/config/remote-hosts.mjs +233 -0
- package/scripts/lib/config/section-extractor.mjs +235 -36
- package/scripts/lib/config-schema.mjs +9 -1
- package/scripts/lib/config.mjs +87 -8
- 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/dispatcher/enumerate.mjs +2 -17
- package/scripts/lib/events-schema.mjs +48 -0
- package/scripts/lib/events.mjs +238 -5
- package/scripts/lib/evolve/autonomy-verdict.mjs +9 -4
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +18 -1
- 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/gitlab-portfolio/cli.mjs +3 -15
- package/scripts/lib/harness-audit/categories/category1.mjs +17 -6
- 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 +25 -10
- package/scripts/lib/memory-paths.mjs +15 -6
- package/scripts/lib/mode-selector/scoring.mjs +53 -6
- package/scripts/lib/peer-discovery.mjs +20 -2
- 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 +517 -18
- 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 +126 -0
- package/scripts/lib/session-close-backfill.mjs +427 -37
- package/scripts/lib/session-discovery.mjs +69 -5
- package/scripts/lib/session-end/phase-skip.mjs +38 -5
- 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 +220 -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-canonical.mjs +446 -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/schema.mjs +74 -8
- package/scripts/lib/telemetry/sync.mjs +91 -16
- package/scripts/lib/tmux-layout/telemetry.mjs +14 -2
- 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 +25 -65
- package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +370 -0
- 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-skill-script-paths.mjs +436 -0
- package/scripts/lib/validate/check-test-value-bans.mjs +165 -17
- package/scripts/lib/validate/check-untracked-test-deps.mjs +10 -0
- package/scripts/lib/validate/check-unwired-features.mjs +333 -32
- package/scripts/lib/validate/check-validator-registration.mjs +248 -0
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +6 -28
- package/scripts/lib/validate/markdown-fences.mjs +196 -0
- 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-lock.mjs +185 -0
- package/scripts/lib/vault-status/board-writer.mjs +381 -141
- package/scripts/lib/vault-status/narrative-mirror.mjs +190 -27
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +832 -0
- package/scripts/lib/wave-executor/remote-dispatch.mjs +504 -0
- package/scripts/lib/wave-resource-gate.mjs +127 -7
- package/scripts/lib/wave-transcript-tail.mjs +889 -0
- package/scripts/materialize-wave-scope.mjs +228 -15
- package/scripts/mcp-server.sh +11 -2
- package/scripts/memory-propose.mjs +132 -8
- package/scripts/parse-config.mjs +65 -0
- package/scripts/promote-vault-strict.mjs +4 -15
- package/scripts/site-numbers.mjs +36 -4
- package/scripts/token-audit.sh +9 -2
- package/scripts/validate-plugin.mjs +29 -0
- package/scripts/validate-wave-scope.mjs +67 -0
- package/scripts/vault-consolidate.mjs +3 -11
- package/scripts/vault-integration-watcher.mjs +2 -4
- package/scripts/vault-mirror.mjs +305 -51
- package/skills/_shared/monitor-patterns.md +31 -5
- package/skills/_shared/parallel-aware-auq.md +31 -2
- package/skills/_shared/parallel-aware-preamble.md +19 -4
- 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/contract-version-bump/SKILL.md +1 -1
- package/skills/dispatcher/SKILL.md +1 -1
- package/skills/ecosystem-health/SKILL.md +4 -1
- package/skills/ecosystem-health/wizard.md +5 -0
- package/skills/evolve/SKILL.md +38 -1
- package/skills/journey-audit/SKILL.md +270 -0
- package/skills/peekaboo-driver/SKILL.md +15 -3
- package/skills/persona-panel/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +46 -3
- package/skills/remote-offload/SKILL.md +89 -0
- 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 +20 -9
- 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 +54 -17
- 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 +5 -3
- package/skills/wave-executor/circuit-breaker.md +34 -9
- package/skills/wave-executor/wave-loop.md +143 -22
- package/templates/_shared/journey-manifest.md +110 -0
- package/templates/_shared/rules/parallel-sessions.md +0 -77
|
@@ -122,6 +122,7 @@
|
|
|
122
122
|
import { readFileSync, existsSync, statSync, readdirSync } from 'node:fs';
|
|
123
123
|
import { join, isAbsolute, relative } from 'node:path';
|
|
124
124
|
import { execFileSync } from 'node:child_process';
|
|
125
|
+
import { isGitToplevel } from './repo-files.mjs';
|
|
125
126
|
|
|
126
127
|
// ---------------------------------------------------------------------------
|
|
127
128
|
// argv
|
|
@@ -184,9 +185,32 @@ const CARVE_OUT_MARKER = '// integrity-anchor:';
|
|
|
184
185
|
const MAGIC_COMMENT = '// @test-value-bans-allowed';
|
|
185
186
|
const MAGIC_COMMENT_SCAN_LINES = 5;
|
|
186
187
|
|
|
187
|
-
/** B2 heuristic: how many toContain/toMatch calls make a .md
|
|
188
|
+
/** B2 heuristic: how many toContain/toMatch calls make a .md read suspect. */
|
|
188
189
|
const PROSE_ASSERT_THRESHOLD = 3;
|
|
189
190
|
|
|
191
|
+
/** A line that reads a `.md` file — the B2 trigger. */
|
|
192
|
+
const MD_READ_CALL = /readFileSync\(/;
|
|
193
|
+
const MD_READ_TARGET = /\.md\b/;
|
|
194
|
+
|
|
195
|
+
/** A prose assertion — the B2 population. */
|
|
196
|
+
const PROSE_ASSERT = /\.(?:toContain|toMatch)\(/g;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Count prose assertions in `lines[from, to)`.
|
|
200
|
+
* @param {string[]} lines
|
|
201
|
+
* @param {number} from inclusive
|
|
202
|
+
* @param {number} to exclusive
|
|
203
|
+
* @returns {number}
|
|
204
|
+
*/
|
|
205
|
+
function countProseAsserts(lines, from, to) {
|
|
206
|
+
let n = 0;
|
|
207
|
+
for (let i = from; i < to; i++) {
|
|
208
|
+
const m = lines[i].match(PROSE_ASSERT);
|
|
209
|
+
if (m) n += m.length;
|
|
210
|
+
}
|
|
211
|
+
return n;
|
|
212
|
+
}
|
|
213
|
+
|
|
190
214
|
const B1_PATTERNS = [
|
|
191
215
|
{
|
|
192
216
|
name: 'toHaveLength',
|
|
@@ -343,6 +367,62 @@ function trackedTestFiles() {
|
|
|
343
367
|
.filter((rel) => /^tests\/.*\.test\.mjs$/.test(rel));
|
|
344
368
|
}
|
|
345
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Line ranges this commit actually ADDS or CHANGES, per repo-relative path,
|
|
372
|
+
* from `git diff --cached -U0`.
|
|
373
|
+
*
|
|
374
|
+
* Why (#1148, HR-101): `--stdin` scopes the scan to staged FILES, not staged
|
|
375
|
+
* LINES. Touching one line of a 1400-line test file therefore surfaced every
|
|
376
|
+
* pre-existing finding in it — a warning that fires on work the committer did
|
|
377
|
+
* not do, which teaches the committer to ignore the warning. The repo-wide
|
|
378
|
+
* census is not lost by this: the CI job `test-value-bans` runs the scanner
|
|
379
|
+
* unscoped over `$CI_PROJECT_DIR` (.gitlab-ci.yml), so an old prose pin is
|
|
380
|
+
* still reported there — just not at the moment someone edits a neighbouring
|
|
381
|
+
* line.
|
|
382
|
+
*
|
|
383
|
+
* Returns `null` when there is no index to consult (not a git top level, git
|
|
384
|
+
* absent, command failed). A null gate is INERT, never empty: failing open is
|
|
385
|
+
* the only safe direction for an advisory that must not invent silence.
|
|
386
|
+
*
|
|
387
|
+
* @returns {Map<string, Array<[number, number]>> | null}
|
|
388
|
+
*/
|
|
389
|
+
function stagedLineRanges() {
|
|
390
|
+
if (!isGitToplevel(repoRoot)) return null;
|
|
391
|
+
let out;
|
|
392
|
+
try {
|
|
393
|
+
out = execFileSync('git', ['diff', '--cached', '-U0', '--no-color'], {
|
|
394
|
+
cwd: repoRoot,
|
|
395
|
+
encoding: 'utf8',
|
|
396
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
397
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
398
|
+
});
|
|
399
|
+
} catch {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
/** @type {Map<string, Array<[number, number]>>} */
|
|
403
|
+
const ranges = new Map();
|
|
404
|
+
/** @type {string | null} */
|
|
405
|
+
let current = null;
|
|
406
|
+
for (const line of out.split('\n')) {
|
|
407
|
+
if (line.startsWith('+++ ')) {
|
|
408
|
+
// The POST-image path: a rename stages under its new name, and a
|
|
409
|
+
// deletion stages as /dev/null (no post-image lines to judge).
|
|
410
|
+
const target = line.slice(4).trim();
|
|
411
|
+
current = target === '/dev/null' ? null : target.replace(/^b\//, '').replace(/\\/g, '/');
|
|
412
|
+
if (current && !ranges.has(current)) ranges.set(current, []);
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (!current || !line.startsWith('@@')) continue;
|
|
416
|
+
const m = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(line);
|
|
417
|
+
if (!m) continue;
|
|
418
|
+
const start = Number(m[1]);
|
|
419
|
+
const count = m[2] === undefined ? 1 : Number(m[2]);
|
|
420
|
+
if (count === 0) continue; // pure deletion — no post-image line exists
|
|
421
|
+
ranges.get(current).push([start, start + count - 1]);
|
|
422
|
+
}
|
|
423
|
+
return ranges;
|
|
424
|
+
}
|
|
425
|
+
|
|
346
426
|
/** Newline-separated paths from stdin (staged-only mode). */
|
|
347
427
|
function stdinFiles() {
|
|
348
428
|
let raw;
|
|
@@ -641,18 +721,44 @@ function scanContent(relPath, content, denyHooks = new Set()) {
|
|
|
641
721
|
});
|
|
642
722
|
|
|
643
723
|
// --- B2: suspected prose pin -------------------------------------------
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
724
|
+
// ONE finding per .md read, counted over the population that read can reach
|
|
725
|
+
// (#1148). v1 reported the line of the FIRST .md read beside a WHOLE-FILE
|
|
726
|
+
// assert count — two different populations with no causal relation, so the
|
|
727
|
+
// reported location was never the location of anything counted.
|
|
728
|
+
// Live proof: tests/unit/vault-mirror.test.mjs was filed at :1077 on
|
|
729
|
+
// 2026-08-23 and reported :1415 three days later with the same 52 asserts.
|
|
730
|
+
// Nothing happened at line 1077; the first .md read simply moved 338 lines.
|
|
731
|
+
//
|
|
732
|
+
// A read's population = its enclosing it()/test() block when it has one,
|
|
733
|
+
// else the whole file — a module-scope read IS visible file-wide, so there
|
|
734
|
+
// the file is the causally correct scope, not a fallback.
|
|
735
|
+
// Ceiling (BV-004): testBlocks() segments it()/test() only, so a read at
|
|
736
|
+
// describe() scope also counts file-wide. That over-includes in the same
|
|
737
|
+
// direction v1 always did; revisit if a describe-scoped .md read is ever
|
|
738
|
+
// reported with a count no single test could produce.
|
|
739
|
+
{
|
|
740
|
+
const blocks = testBlocks(lines);
|
|
741
|
+
/** Segments already reported — a block holding two .md reads reports once. */
|
|
742
|
+
const reportedSegments = new Set();
|
|
743
|
+
lines.forEach((line, idx) => {
|
|
744
|
+
if (!MD_READ_CALL.test(line) || !MD_READ_TARGET.test(line)) return;
|
|
745
|
+
const block = blocks.find((b) => idx >= b.start && idx < b.end);
|
|
746
|
+
const key = block ? `block:${block.start}` : 'file';
|
|
747
|
+
if (reportedSegments.has(key)) return;
|
|
748
|
+
const [from, to] = block ? [block.start, block.end] : [0, lines.length];
|
|
749
|
+
const proseAsserts = countProseAsserts(lines, from, to);
|
|
750
|
+
if (proseAsserts < PROSE_ASSERT_THRESHOLD) return;
|
|
751
|
+
reportedSegments.add(key);
|
|
648
752
|
findings.push({
|
|
649
753
|
file: relPath,
|
|
650
|
-
line:
|
|
754
|
+
line: idx + 1,
|
|
651
755
|
ban: 'B2-prose-pin-suspected',
|
|
652
|
-
match: `readFileSync on .md + ${proseAsserts} toContain/toMatch asserts
|
|
756
|
+
match: `readFileSync on .md + ${proseAsserts} toContain/toMatch asserts in ${
|
|
757
|
+
block ? 'this test' : 'this file'
|
|
758
|
+
}`,
|
|
653
759
|
hint: B2_HINT,
|
|
654
760
|
});
|
|
655
|
-
}
|
|
761
|
+
});
|
|
656
762
|
}
|
|
657
763
|
|
|
658
764
|
// --- B3: bare exit-code allow assertion on a deny-capable hook ----------
|
|
@@ -748,23 +854,59 @@ for (const rel of candidates) {
|
|
|
748
854
|
findings.push(...scanContent(rel, content, denyHooks));
|
|
749
855
|
}
|
|
750
856
|
|
|
857
|
+
// Under --stdin (the pre-commit path) judge only the lines this commit
|
|
858
|
+
// touches; without it (the CI path) keep the repo-wide census. See
|
|
859
|
+
// stagedLineRanges() for why the two paths differ.
|
|
860
|
+
const stagedScope = flags.has('--stdin') ? stagedLineRanges() : null;
|
|
861
|
+
let suppressed = 0;
|
|
862
|
+
const reported = stagedScope
|
|
863
|
+
? findings.filter((f) => {
|
|
864
|
+
const ranges = stagedScope.get(f.file);
|
|
865
|
+
const inHunk = Boolean(ranges) && ranges.some(([a, b]) => f.line >= a && f.line <= b);
|
|
866
|
+
if (!inHunk) suppressed++;
|
|
867
|
+
return inHunk;
|
|
868
|
+
})
|
|
869
|
+
: findings;
|
|
870
|
+
|
|
751
871
|
const counts = {
|
|
752
|
-
'B1-exact-count':
|
|
753
|
-
'B2-prose-pin-suspected':
|
|
754
|
-
'B3-bare-hook-exit-code':
|
|
872
|
+
'B1-exact-count': reported.filter((f) => f.ban === 'B1-exact-count').length,
|
|
873
|
+
'B2-prose-pin-suspected': reported.filter((f) => f.ban === 'B2-prose-pin-suspected').length,
|
|
874
|
+
'B3-bare-hook-exit-code': reported.filter((f) => f.ban === 'B3-bare-hook-exit-code').length,
|
|
755
875
|
'B4-hook-decision-contract-copy': findings.filter(
|
|
756
876
|
(f) => f.ban === 'B4-hook-decision-contract-copy',
|
|
757
877
|
).length,
|
|
758
|
-
'B5-date-time-bomb':
|
|
878
|
+
'B5-date-time-bomb': reported.filter((f) => f.ban === 'B5-date-time-bomb').length,
|
|
759
879
|
};
|
|
760
880
|
|
|
761
881
|
if (jsonMode) {
|
|
762
|
-
console.log(
|
|
763
|
-
|
|
764
|
-
|
|
882
|
+
console.log(
|
|
883
|
+
JSON.stringify(
|
|
884
|
+
{
|
|
885
|
+
advisory: true,
|
|
886
|
+
scanned,
|
|
887
|
+
counts,
|
|
888
|
+
stagedScope: { applied: stagedScope !== null, suppressed },
|
|
889
|
+
findings: reported,
|
|
890
|
+
},
|
|
891
|
+
null,
|
|
892
|
+
2,
|
|
893
|
+
),
|
|
894
|
+
);
|
|
895
|
+
} else if (reported.length === 0) {
|
|
896
|
+
if (!quiet) {
|
|
897
|
+
console.log(`check-test-value-bans: 0 findings across ${scanned} test file(s) — advisory`);
|
|
898
|
+
// Never suppress silently: an empty result that had findings behind it is
|
|
899
|
+
// a different fact from an empty result that did not (HR-106).
|
|
900
|
+
if (stagedScope !== null && suppressed > 0) {
|
|
901
|
+
console.log(
|
|
902
|
+
` (${suppressed} pre-existing finding(s) outside this commit's staged hunks not shown — ` +
|
|
903
|
+
'the CI job test-value-bans reports the repo-wide census)',
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
765
907
|
} else {
|
|
766
|
-
console.log(`check-test-value-bans: ${
|
|
767
|
-
for (const f of
|
|
908
|
+
console.log(`check-test-value-bans: ${reported.length} advisory finding(s) across ${scanned} test file(s)`);
|
|
909
|
+
for (const f of reported) {
|
|
768
910
|
console.log(` ${f.ban} ${f.file}:${f.line} ${f.match}`);
|
|
769
911
|
}
|
|
770
912
|
if (counts['B1-exact-count'] > 0) console.log(` B1 hint: ${B1_HINT}`);
|
|
@@ -772,6 +914,12 @@ if (jsonMode) {
|
|
|
772
914
|
if (counts['B3-bare-hook-exit-code'] > 0) console.log(` B3 hint: ${B3_HINT}`);
|
|
773
915
|
if (counts['B4-hook-decision-contract-copy'] > 0) console.log(` B4 hint: ${B4_HINT}`);
|
|
774
916
|
if (counts['B5-date-time-bomb'] > 0) console.log(` B5 hint: ${B5_HINT}`);
|
|
917
|
+
if (stagedScope !== null && suppressed > 0) {
|
|
918
|
+
console.log(
|
|
919
|
+
` (${suppressed} pre-existing finding(s) outside this commit's staged hunks not shown — ` +
|
|
920
|
+
'the CI job test-value-bans reports the repo-wide census)',
|
|
921
|
+
);
|
|
922
|
+
}
|
|
775
923
|
console.log(' (advisory — this check never blocks; see .claude/rules/testing.md § Lint-Enforceable Test Bans)');
|
|
776
924
|
}
|
|
777
925
|
|
|
@@ -695,9 +695,19 @@ export function resolveUntrackedOracle(repoRoot, candidates) {
|
|
|
695
695
|
// directory candidates (`.git`, `.claude`, `docs/prd`, `skills/bootstrap`, …)
|
|
696
696
|
// were all false. A directory can therefore only be condemned by
|
|
697
697
|
// `check-ignore`, which does judge directories correctly.
|
|
698
|
+
//
|
|
699
|
+
// `.git` ALSO needs an explicit exemption from THIS FILES-ONLY branch, not
|
|
700
|
+
// just from the directory-only reasoning above. In a normal checkout `.git`
|
|
701
|
+
// is a directory and the `isFile()` guard already excludes it — but in a
|
|
702
|
+
// LINKED WORKTREE (`git worktree add`) `.git` is a FILE containing
|
|
703
|
+
// `gitdir: <path>`, which makes `existsSync() && isFile()` true and
|
|
704
|
+
// re-opens exactly the false-positive the comment above says is closed.
|
|
705
|
+
// `.git` is present in every clone/worktree by construction regardless of
|
|
706
|
+
// which shape it takes, so it is never a "never git-add-ed" candidate.
|
|
698
707
|
for (const c of all) {
|
|
699
708
|
if (untracked.has(c)) continue;
|
|
700
709
|
if (tracked.has(c)) continue;
|
|
710
|
+
if (c === '.git') continue;
|
|
701
711
|
try {
|
|
702
712
|
const abs = path.join(repoRoot, c);
|
|
703
713
|
if (existsSync(abs) && statSync(abs).isFile()) untracked.add(c);
|