session-orchestrator 3.24.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/architecture/SKILL.md +18 -0
- package/.agents/skills/autopilot/SKILL.md +17 -0
- package/.agents/skills/bootstrap/SKILL.md +20 -0
- package/.agents/skills/brainstorm/SKILL.md +22 -0
- package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
- package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
- package/.agents/skills/debug/SKILL.md +22 -0
- package/.agents/skills/discovery/SKILL.md +20 -0
- package/.agents/skills/dispatcher/SKILL.md +15 -0
- package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
- package/.agents/skills/ecosystem-health/SKILL.md +20 -0
- package/.agents/skills/eli5/SKILL.md +20 -0
- package/.agents/skills/eval/SKILL.md +21 -0
- package/.agents/skills/evolve/SKILL.md +21 -0
- package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
- package/.agents/skills/gitlab-ops/SKILL.md +20 -0
- package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
- package/.agents/skills/grill/SKILL.md +22 -0
- package/.agents/skills/hook-development/SKILL.md +15 -0
- package/.agents/skills/mcp-builder/SKILL.md +15 -0
- package/.agents/skills/memory-cleanup/SKILL.md +21 -0
- package/.agents/skills/mode-selector/SKILL.md +17 -0
- package/.agents/skills/npm-publish/SKILL.md +16 -0
- package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
- package/.agents/skills/persona-panel/SKILL.md +17 -0
- package/.agents/skills/plan/SKILL.md +20 -0
- package/.agents/skills/playwright-driver/SKILL.md +20 -0
- package/.agents/skills/quality-gates/SKILL.md +20 -0
- package/.agents/skills/reconcile/SKILL.md +21 -0
- package/.agents/skills/remote-offload/SKILL.md +20 -0
- package/.agents/skills/repo-audit/SKILL.md +16 -0
- package/.agents/skills/session-end/SKILL.md +20 -0
- package/.agents/skills/session-plan/SKILL.md +20 -0
- package/.agents/skills/session-start/SKILL.md +20 -0
- package/.agents/skills/spinout/SKILL.md +16 -0
- package/.agents/skills/sunset-review/SKILL.md +16 -0
- package/.agents/skills/test-runner/SKILL.md +20 -0
- package/.agents/skills/tmux-layout/SKILL.md +21 -0
- package/.agents/skills/using-orchestrator/SKILL.md +17 -0
- package/.agents/skills/vault-mirror/SKILL.md +15 -0
- package/.agents/skills/vault-sync/SKILL.md +15 -0
- package/.agents/skills/wave-executor/SKILL.md +20 -0
- package/.agents/skills/write-executable-plan/SKILL.md +22 -0
- 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.md +2 -2
- package/.cursor/commands/bootstrap.md +1 -1
- package/.cursor/commands/brainstorm.md +1 -1
- package/.cursor/commands/debug.md +1 -1
- package/.cursor/commands/discovery.md +1 -1
- package/.cursor/commands/dispatcher.md +2 -2
- package/.cursor/commands/eli5.md +2 -2
- package/.cursor/commands/eval.md +2 -2
- package/.cursor/commands/evolve.md +1 -1
- package/.cursor/commands/go.md +1 -1
- package/.cursor/commands/grill.md +2 -2
- package/.cursor/commands/memory-cleanup.md +2 -2
- package/.cursor/commands/persona-panel.md +1 -1
- package/.cursor/commands/plan.md +1 -1
- package/.cursor/commands/portfolio.md +1 -1
- package/.cursor/commands/reconcile.md +2 -2
- package/.cursor/commands/release.md +2 -2
- package/.cursor/commands/session.md +2 -2
- package/.cursor/commands/spinout.md +2 -2
- package/.cursor/commands/sunset-review.md +2 -2
- package/.cursor/commands/templates-ack.md +2 -2
- package/.cursor/commands/test.md +2 -2
- package/.cursor/skills/brainstorm/SKILL.md +1 -1
- package/.cursor/skills/eval/SKILL.md +1 -1
- package/.cursor/skills/quality-gates/SKILL.md +1 -1
- package/.cursor/skills/remote-offload/SKILL.md +1 -1
- package/.orchestrator/policy/blocked-commands.json +121 -0
- package/.orchestrator/policy/ecosystem.schema.json +66 -0
- package/.orchestrator/policy/quality-gates.example.json +16 -0
- package/.orchestrator/policy/quality-gates.schema.json +38 -0
- package/.orchestrator/policy/templates-policy.json +27 -0
- package/.orchestrator/policy/test-profiles.json +47 -0
- package/AGENTS.md +225 -0
- package/CHANGELOG.md +1125 -2
- package/NOTICE +11 -6
- package/README.md +127 -94
- package/agents/eval-judge.md +1 -1
- package/agents/skill-applied-judge.md +1 -1
- package/assets/wave-lifecycle.svg +98 -0
- package/commands/release.md +6 -3
- package/commands/session.md +18 -3
- package/docs/README.md +4 -0
- package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
- package/docs/baseline.md +67 -0
- package/docs/ci-setup.md +108 -62
- package/docs/codex-setup.md +65 -21
- package/docs/components.md +36 -15
- package/docs/cursor-setup.md +6 -2
- package/docs/events-schema.md +9 -6
- package/docs/instruction-delivery.md +62 -0
- package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
- package/docs/migration-v4.md +341 -0
- package/docs/pi-setup.md +6 -1
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +85 -19
- package/docs/scope-collision-guard.md +5 -5
- package/docs/session-config-reference.md +57 -56
- package/docs/session-config-template.md +6 -29
- package/docs/telemetry.md +157 -3
- package/docs/vault-docs-architecture.md +50 -11
- package/hooks/_lib/hook-import-set.json +1487 -0
- package/hooks/_lib/subagent-transcript.mjs +562 -0
- package/hooks/config-protection.mjs +2 -2
- package/hooks/cwd-change-restore.mjs +2 -2
- package/hooks/enforce-commands.mjs +69 -0
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +10 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/loop-guard.mjs +3 -3
- package/hooks/on-session-end.mjs +2 -2
- package/hooks/on-session-start.mjs +103 -2
- package/hooks/on-stop.mjs +36 -11
- package/hooks/operator-steer.mjs +2 -2
- package/hooks/post-bash-write-verify.mjs +85 -0
- package/hooks/post-edit-import-probe.mjs +344 -0
- package/hooks/post-subagent-discovery-validator.mjs +187 -431
- package/hooks/post-tool-batch-wave-signal.mjs +118 -4
- package/hooks/post-tool-failure-corrective-context.mjs +2 -2
- package/hooks/post-tooluse-frontend-slop.mjs +3 -3
- package/hooks/pre-bash-destructive-guard.mjs +39 -13
- package/hooks/skill-invocation-telemetry.mjs +17 -5
- package/hooks/subagent-telemetry.mjs +13 -4
- package/monitors/monitors.json +3 -3
- package/package.json +9 -1
- package/pi/prompts/session.md +2 -2
- package/plugin.json +27 -0
- package/scripts/backfill-abandoned-sessions.mjs +50 -4
- package/scripts/backfill-learnings-from-vault.mjs +9 -3
- package/scripts/dialectic-deriver.mjs +73 -8
- package/scripts/export-hw-learnings.mjs +113 -1
- package/scripts/generate-agents-skills.mjs +378 -0
- package/scripts/generate-cursor-adapter.mjs +45 -8
- package/scripts/generate-hook-import-set.mjs +249 -0
- package/scripts/lib/agent-status.mjs +13 -2
- package/scripts/lib/auto-dream.mjs +38 -36
- package/scripts/lib/autonomy/suitability.mjs +6 -0
- package/scripts/lib/autopilot/loop.mjs +2 -2
- package/scripts/lib/ci-status-banner.mjs +220 -75
- package/scripts/lib/codex/plugin-contract.mjs +82 -6
- package/scripts/lib/config/auto-dream.mjs +2 -1
- package/scripts/lib/config/block-header.mjs +8 -0
- package/scripts/lib/config/block-preprocess.mjs +177 -0
- package/scripts/lib/config/broken-window.mjs +2 -1
- package/scripts/lib/config/cold-start.mjs +2 -1
- package/scripts/lib/config/config-protection.mjs +22 -2
- package/scripts/lib/config/context-coverage.mjs +2 -1
- package/scripts/lib/config/cross-repo.mjs +2 -1
- package/scripts/lib/config/custom-phases.mjs +2 -1
- package/scripts/lib/config/dialectic.mjs +2 -1
- package/scripts/lib/config/discovery-validator.mjs +2 -1
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
- package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
- package/scripts/lib/config/docs-orchestrator.mjs +2 -1
- package/scripts/lib/config/docs-staleness.mjs +2 -1
- package/scripts/lib/config/drift-check.mjs +2 -1
- package/scripts/lib/config/eval.mjs +2 -1
- package/scripts/lib/config/events-rotation.mjs +2 -1
- package/scripts/lib/config/evolve.mjs +8 -2
- package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
- package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
- package/scripts/lib/config/handover-gate.mjs +2 -1
- package/scripts/lib/config/health-endpoints.mjs +7 -2
- package/scripts/lib/config/issue-budget.mjs +2 -1
- package/scripts/lib/config/loop-guard.mjs +2 -1
- package/scripts/lib/config/memory.mjs +2 -1
- package/scripts/lib/config/moc-staleness.mjs +2 -1
- package/scripts/lib/config/persona-gate-wave.mjs +2 -1
- package/scripts/lib/config/private-config-dir.mjs +67 -0
- package/scripts/lib/config/reconcile.mjs +2 -1
- package/scripts/lib/config/remote-hosts.mjs +2 -1
- package/scripts/lib/config/section-extractor.mjs +7 -1
- package/scripts/lib/config/skill-evolution.mjs +2 -1
- package/scripts/lib/config/slopcheck.mjs +2 -1
- package/scripts/lib/config/state-md-lock.mjs +2 -1
- package/scripts/lib/config/templates-first.mjs +2 -1
- package/scripts/lib/config/test.mjs +2 -1
- package/scripts/lib/config/vault-integration.mjs +7 -1
- package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
- package/scripts/lib/config/vault-staleness.mjs +2 -1
- package/scripts/lib/config/vault-sync.mjs +2 -1
- package/scripts/lib/config/verification-auto-fix.mjs +2 -1
- package/scripts/lib/config/wave-reviewers.mjs +2 -1
- package/scripts/lib/config/worktree-orphans.mjs +2 -1
- package/scripts/lib/convergence-monitor.mjs +82 -16
- package/scripts/lib/dispatcher/rank.mjs +124 -48
- package/scripts/lib/ecosystem-health.mjs +16 -2
- package/scripts/lib/eval/engine.mjs +9 -1
- package/scripts/lib/eval/session-resolve.mjs +23 -4
- package/scripts/lib/events.mjs +22 -6
- package/scripts/lib/frontmatter-guard.mjs +131 -13
- package/scripts/lib/gates/gate-full.mjs +26 -0
- package/scripts/lib/gates/gate-helpers.mjs +76 -0
- package/scripts/lib/hardware-pattern-detector.mjs +18 -1
- package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
- package/scripts/lib/host-identity.mjs +50 -11
- package/scripts/lib/instruction-budget-guard.mjs +171 -5
- package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
- package/scripts/lib/learnings/io.mjs +60 -6
- package/scripts/lib/memory-proposals/store.mjs +30 -22
- package/scripts/lib/owner-config-banner.mjs +43 -6
- package/scripts/lib/owner-config-loader.mjs +21 -10
- package/scripts/lib/owner-interview.mjs +3 -3
- package/scripts/lib/owner-yaml.mjs +207 -14
- package/scripts/lib/platform.mjs +108 -15
- package/scripts/lib/plugin-update-banner.mjs +406 -0
- package/scripts/lib/project-hygiene.mjs +38 -2
- package/scripts/lib/qg-command-drift-banner.mjs +50 -12
- package/scripts/lib/quality-gate.mjs +133 -44
- package/scripts/lib/reconcile/emitter.mjs +68 -6
- package/scripts/lib/reconcile/engine.mjs +13 -4
- package/scripts/lib/reconcile/idempotency.mjs +37 -4
- package/scripts/lib/reconcile/writer.mjs +40 -18
- package/scripts/lib/session-close-backfill.mjs +67 -9
- package/scripts/lib/session-id.mjs +12 -23
- package/scripts/lib/session-identity/own-session.mjs +125 -10
- package/scripts/lib/session-lock-shape.mjs +43 -0
- package/scripts/lib/session-lock.mjs +5 -10
- package/scripts/lib/session-registry.mjs +25 -9
- package/scripts/lib/session-schema/constants.mjs +36 -2
- package/scripts/lib/session-schema/validator.mjs +38 -4
- package/scripts/lib/session-start-probes.mjs +18 -1
- package/scripts/lib/sessions-staleness-banner.mjs +18 -11
- package/scripts/lib/skill-health/join.mjs +17 -4
- package/scripts/lib/state-md.mjs +78 -0
- package/scripts/lib/sunset/walker.mjs +6 -0
- package/scripts/lib/telemetry/schema.mjs +181 -9
- package/scripts/lib/telemetry/sync.mjs +368 -12
- package/scripts/lib/validate/check-agents-skills.mjs +327 -0
- package/scripts/lib/validate/check-agents.mjs +3 -3
- package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
- package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
- package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
- package/scripts/lib/validate/check-skill-links.mjs +163 -0
- package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
- package/scripts/lib/validate/check-unwired-features.mjs +0 -2
- package/scripts/lib/validate/check-validator-registration.mjs +10 -4
- package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
- package/scripts/lib/vault-backfill/template.mjs +63 -6
- package/scripts/lib/vault-mirror/process.mjs +165 -42
- package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
- package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
- package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
- package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
- package/scripts/lib/wave-resource-gate.mjs +8 -2
- package/scripts/lib/wave-sizing.mjs +4 -1
- package/scripts/lib/wave-transcript-tail.mjs +118 -4
- package/scripts/materialize-wave-scope.mjs +12 -5
- package/scripts/memory-propose.mjs +19 -5
- package/scripts/migrate-cold-start-seed.mjs +4 -1
- package/scripts/parse-config.mjs +60 -3
- package/scripts/release.mjs +337 -29
- package/scripts/repair-invalid-sessions.mjs +3 -3
- package/scripts/run-quality-gate.mjs +128 -11
- package/scripts/sweep-expired-learnings.mjs +90 -0
- package/scripts/sync-vault-schema.mjs +3 -1
- package/scripts/telemetry.mjs +2 -2
- package/scripts/validate-plugin.mjs +161 -0
- package/scripts/validate-wave-scope.mjs +28 -8
- package/scripts/wave-scope-binding.mjs +215 -0
- package/skills/_shared/instruction-file-resolution.md +10 -0
- package/skills/_shared/parallel-aware-preamble.md +1 -0
- package/skills/_shared/platform-tools.md +1 -1
- package/skills/_shared/state-ownership.md +1 -1
- package/skills/architecture/SKILL.md +7 -5
- package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
- package/skills/autopilot/SKILL.md +4 -18
- package/skills/claude-md-drift-check/SKILL.md +5 -1
- package/skills/claude-md-drift-check/checker.mjs +62 -2
- package/skills/convergence-monitoring/SIGNALS.md +55 -0
- package/skills/discovery/probes/vault-staleness.mjs +37 -13
- package/skills/discovery/probes-arch.md +20 -18
- package/skills/dispatcher/SKILL.md +3 -2
- package/skills/evolve/SKILL.md +65 -26
- package/skills/frontmatter-guard/SKILL.md +11 -5
- package/skills/npm-publish/SKILL.md +1 -1
- package/skills/reconcile/SKILL.md +33 -0
- package/skills/remote-offload/SKILL.md +1 -1
- package/skills/session-end/SKILL.md +18 -905
- package/skills/session-end/phase-3-6-tail.md +10 -3
- package/skills/session-end/plan-verification.md +221 -155
- package/skills/session-end/references/phase-2-quality-gate.md +93 -0
- package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
- package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
- package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
- package/skills/session-end/references/session-summary-template.md +62 -0
- package/skills/session-plan/SKILL.md +49 -0
- package/skills/session-start/SKILL.md +22 -904
- package/skills/session-start/phase-8-5-express-path.md +1 -1
- package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
- package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
- package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
- package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
- package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
- package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
- package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
- package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
- package/skills/vault-sync/validator.mjs +21 -27
- package/skills/wave-executor/SKILL.md +15 -1
- package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
- package/skills/wave-executor/references/wave-loop-review.md +570 -0
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
- package/skills/wave-executor/wave-loop.md +14 -1309
- package/templates/_shared/journey-manifest.md +10 -6
- package/.cursor/commands/autopilot-multi.md +0 -14
- package/.cursor/commands/contract-version-bump.md +0 -14
- package/.cursor/commands/journey-audit.md +0 -14
- package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
- package/.cursor/skills/daily/SKILL.md +0 -12
- package/.cursor/skills/domain-model/SKILL.md +0 -13
- package/.cursor/skills/journey-audit/SKILL.md +0 -13
- package/.cursor/skills/skill-creator/SKILL.md +0 -13
- package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
- package/commands/autopilot-multi.md +0 -74
- package/commands/contract-version-bump.md +0 -28
- package/commands/journey-audit.md +0 -43
- package/pi/prompts/autopilot-multi.md +0 -12
- package/pi/prompts/contract-version-bump.md +0 -12
- package/pi/prompts/journey-audit.md +0 -12
- package/scripts/autopilot-multi.mjs +0 -885
- package/scripts/backfill-learnings-expires.mjs +0 -196
- package/scripts/backfill-learnings.mjs +0 -203
- package/scripts/fleet-instruction-scan.mjs +0 -141
- package/scripts/lib/autopilot/dep-graph.mjs +0 -417
- package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
- package/scripts/lib/webhook-url.mjs +0 -105
- package/scripts/lifecycle-sim-v6.mjs +0 -347
- package/scripts/migrate-learnings-jsonl.mjs +0 -189
- package/scripts/migrate-subagents-jsonl.mjs +0 -196
- package/scripts/upload-social-preview.mjs +0 -316
- package/skills/_shared/model-selection.md +0 -64
- package/skills/contract-version-bump/SKILL.md +0 -219
- package/skills/daily/SKILL.md +0 -222
- package/skills/daily/generate.sh +0 -92
- package/skills/daily/templates/daily.md.tpl +0 -36
- package/skills/journey-audit/SKILL.md +0 -270
- package/skills/skill-creator/SKILL.md +0 -168
- package/skills/ubiquitous-language/SKILL.md +0 -97
- package/skills/vault-sync/package-lock.json +0 -40
- /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
- /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* subagent-transcript.mjs — the PSA-006 claim-extraction engine (#1218/#1198).
|
|
3
|
+
*
|
|
4
|
+
* Extracted out of `hooks/post-subagent-discovery-validator.mjs`, which kept
|
|
5
|
+
* the whole scanner inline behind a `process.stdin` read: the only way to
|
|
6
|
+
* exercise a claim string was to spawn the hook with a sandbox repo, a
|
|
7
|
+
* CLAUDE.md (or AGENTS.md — transparent aliases, see skills/_shared/instruction-file-resolution.md)
|
|
8
|
+
* and a transcript on disk. That cost made the precision of the
|
|
9
|
+
* matcher — the property the fleet actually complained about — effectively
|
|
10
|
+
* unmeasurable, so it was never measured. This module is the pure half
|
|
11
|
+
* (string in, findings out); the hook keeps stdin, config gate, sidecar
|
|
12
|
+
* resolution, sentinels and the three output channels.
|
|
13
|
+
*
|
|
14
|
+
* Three responsibilities, in the order the hook uses them:
|
|
15
|
+
* 1. `readTranscriptTail()` — the last N assistant records of ONE transcript
|
|
16
|
+
* JSONL, text blocks concatenated.
|
|
17
|
+
* 2. `findViolations()` — distributional/repo-state claims that carry no
|
|
18
|
+
* adjacent measurement evidence.
|
|
19
|
+
* 3. `normalizeClaim()` / `dedupeViolations()` — one record per distinct
|
|
20
|
+
* claim, with an `occurrences` count (#1198: the fleet's worst repo held
|
|
21
|
+
* 3,360 records over 205 distinct `claim_text` values = duplication
|
|
22
|
+
* factor 16.4, measured 2026-09-06 over
|
|
23
|
+
* `extern/aiat-barrierefrei-engine/.orchestrator/metrics/events.jsonl`).
|
|
24
|
+
*
|
|
25
|
+
* @module hooks/_lib/subagent-transcript
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { promises as fs } from 'node:fs';
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Constants
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/** Number of trailing assistant records to scan. */
|
|
35
|
+
export const TAIL_RECORDS = 8;
|
|
36
|
+
/** Proximity window (in lines) for an adjacent grep transcript. */
|
|
37
|
+
export const GREP_PROXIMITY_LINES = 5;
|
|
38
|
+
/** Max characters of claim text persisted to the event record. */
|
|
39
|
+
export const CLAIM_TEXT_MAX = 200;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Code-distribution context nouns (singular or plural). A distributional claim
|
|
43
|
+
* is only a PSA-006 *code* claim when one of these appears near the trigger —
|
|
44
|
+
* this is what separates "4 of 4 callers" (a real claim) from "Turn 3 of 25"
|
|
45
|
+
* (a turn counter) and "every caller imports X" from "every developer should
|
|
46
|
+
* test" (generic advice). Tightening per W2-review LOW finding (#567).
|
|
47
|
+
*
|
|
48
|
+
* Single optional `\s?` only (call site / callsite) — no nested quantifiers,
|
|
49
|
+
* so the alternation stays linear-time / ReDoS-safe.
|
|
50
|
+
*/
|
|
51
|
+
const CTX = '(?:call\\s?sites?|callers?|sites?|references?|instances?|files?|consumers?|imports?|matches|match|occurrences?|usages?|modules?|tests?|places?|functions?|dependenc(?:y|ies)|endpoints?|hooks?)';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Repo-STATE nouns (#908). The four documented #908 drift cases were counts of
|
|
55
|
+
* repository state, not of code locations: "14 commits", "92 learnings",
|
|
56
|
+
* "5 dirty files", "412 lines". None of them contains a CTX noun, so the
|
|
57
|
+
* original six patterns could not see them.
|
|
58
|
+
*
|
|
59
|
+
* Vocabulary taken from this repo's own artefacts (`.orchestrator/metrics/*.jsonl`
|
|
60
|
+
* record kinds, `.claude/rules/`, `skills/`, `agents/`, `hooks/`) rather than a
|
|
61
|
+
* generic English list — a noun that never names a countable repo artefact here
|
|
62
|
+
* only buys false positives.
|
|
63
|
+
*/
|
|
64
|
+
const STATE = '(?:commits?|learnings?|issues?|branches?|lines?|entries|records?|sessions?|rules?|skills?|probes?|waves?|proposals?|worktrees?)';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The noun class the BARE-CARDINAL pattern may use — deliberately a strict
|
|
68
|
+
* subset of STATE ∪ CTX, restricted to the artefact kinds the #908 drift was
|
|
69
|
+
* actually measured in (commit counts, learnings counts, open-issue counts,
|
|
70
|
+
* branch counts, line counts, dirty-file counts).
|
|
71
|
+
*
|
|
72
|
+
* Measured, not guessed: over 32 real agent-stop windows from this repo's own
|
|
73
|
+
* transcripts, admitting the full CTX ∪ STATE set fired 93 times (2.9 per
|
|
74
|
+
* stop — the "validator gets switched off" zone). Every noun below earns its
|
|
75
|
+
* place by naming one of the documented #908 facts; the ones that only cost
|
|
76
|
+
* false positives (`tests`, `references`, `matches`, `agents`, `files` without
|
|
77
|
+
* a state adjective) are excluded here and remain reachable through the six
|
|
78
|
+
* quantifier-triggered patterns above, which have a lexical anchor.
|
|
79
|
+
*
|
|
80
|
+
* `callers` (#918): PSA-006's own canonical noun — the rule text's worked
|
|
81
|
+
* examples ("4 of 4 callers", "100% of callers opt-in") all count callers, yet
|
|
82
|
+
* the cardinal pattern could not see a bare "14 callers". Re-measured with it
|
|
83
|
+
* admitted: 490 real SubagentStop transcripts (2026-07-31), +0 additional
|
|
84
|
+
* firings from the bare form — the noun is free on this corpus (all +3 delta
|
|
85
|
+
* firings came from the #918 slash pattern below).
|
|
86
|
+
*
|
|
87
|
+
* German extension (#1211): `zeilen?|dateien?|datei|aufrufer|eintr(?:ag|(?:ä|ae)ge)`
|
|
88
|
+
* only — the same D3 measurement that found the quantifier patterns
|
|
89
|
+
* disciplined (7/41, see WIDE_DE below) found the WIDE German noun set
|
|
90
|
+
* (Sessions/Repos/Treffer/Stellen/Module/Tests included) firing 39/41
|
|
91
|
+
* (~57% false-positive rate in the labelled sample) when admitted here.
|
|
92
|
+
*/
|
|
93
|
+
const CARDINAL_NOUN =
|
|
94
|
+
'(?:commits?|learnings?|issues?|branches?|lines?|files?|callers?|zeilen?|dateien?|datei|aufrufer|eintr(?:ag|(?:ä|ae)ge))';
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Wide noun class = code-distribution nouns ∪ repo-state nouns. Used by the six
|
|
98
|
+
* QUANTIFIER-triggered patterns ("N of M", "100% of", "all N", "no remaining",
|
|
99
|
+
* "none of") — each of those carries a strong lexical trigger, so widening the
|
|
100
|
+
* noun set there is low-risk.
|
|
101
|
+
*
|
|
102
|
+
* Deliberately NOT used by the `every <noun>` pattern: `every` has no numeric
|
|
103
|
+
* anchor, so `every commit must be signed` / `every rule is always-on` are
|
|
104
|
+
* ordinary prose, not measured claims. That pattern keeps the narrow CTX.
|
|
105
|
+
*/
|
|
106
|
+
const WIDE = `(?:${CTX}|${STATE})`;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* German counterparts of CTX/STATE/WIDE (#1211). D3 (Discovery, 2026-09-03)
|
|
110
|
+
* measured these against 41 real German claim lines pulled from this repo's
|
|
111
|
+
* own `.orchestrator/metrics/events.jsonl`: the quantifier-triggered patterns
|
|
112
|
+
* built from WIDE_DE below flagged 7/41 — the same "disciplined" order of
|
|
113
|
+
* magnitude as the English CLAIM_PATTERNS.
|
|
114
|
+
*/
|
|
115
|
+
const CTX_DE =
|
|
116
|
+
'(?:Aufrufer|Stellen?|Module?|Tests?|Treffer|Zeilen?|Aufrufstellen?|Referenzen?|Instanzen?|Konsumenten?|Verweise?)';
|
|
117
|
+
const STATE_DE =
|
|
118
|
+
'(?:Commits?|Learnings?|Issues?|Branches?|Zeilen?|Eintr(?:ag|(?:ä|ae)ge)|Sessions?|Repos?|Dateien?|Datei|Waves?|Wellen?)';
|
|
119
|
+
const WIDE_DE = `(?:${CTX_DE}|${STATE_DE})`;
|
|
120
|
+
|
|
121
|
+
/** Bounded same-line gap between a trigger and its context noun. */
|
|
122
|
+
const CTX_GAP = '[^\\n]{0,40}?';
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Distributional-claim patterns (case-insensitive). A match is a PSA-006 claim
|
|
126
|
+
* that requires an adjacent measurement transcript.
|
|
127
|
+
*
|
|
128
|
+
* Each pattern requires a context noun within a small, bounded same-line window
|
|
129
|
+
* of the trigger. The `[^\n]{0,40}?` gap is a bounded lazy character class
|
|
130
|
+
* (linear-time — the ReDoS-safety the W2 reviewer verified is preserved).
|
|
131
|
+
*/
|
|
132
|
+
export const CLAIM_PATTERNS = [
|
|
133
|
+
new RegExp(`\\b\\d+ of \\d+\\b${CTX_GAP}\\b${WIDE}\\b`, 'i'),
|
|
134
|
+
new RegExp(`100% of\\b${CTX_GAP}\\b${WIDE}\\b`, 'i'),
|
|
135
|
+
new RegExp(`\\ball \\d+\\b${CTX_GAP}\\b${WIDE}\\b`, 'i'),
|
|
136
|
+
new RegExp(`no remaining\\b${CTX_GAP}\\b${WIDE}\\b`, 'i'),
|
|
137
|
+
new RegExp(`every ${CTX}\\b`, 'i'),
|
|
138
|
+
new RegExp(`none of\\b${CTX_GAP}\\b${WIDE}\\b`, 'i'),
|
|
139
|
+
|
|
140
|
+
// German equivalents (#1211). Each mirrors one of the six English patterns
|
|
141
|
+
// above, plus three shapes with no direct English counterpart in this list
|
|
142
|
+
// ("davon" subset-claims, "sämtliche", "kein einziger").
|
|
143
|
+
new RegExp(`\\b\\d+\\s+von\\s+\\d+\\b${CTX_GAP}\\b${WIDE_DE}\\b`, 'i'), // "N von M <noun>"
|
|
144
|
+
new RegExp(`\\b100\\s?%\\s*(?:von|der|aller)\\b${CTX_GAP}\\b${WIDE_DE}\\b`, 'i'), // "100 % von/der/aller <noun>"
|
|
145
|
+
new RegExp(`\\balle\\s+\\d+\\b${CTX_GAP}\\b${WIDE_DE}\\b`, 'i'), // "alle N <noun>"
|
|
146
|
+
new RegExp(`\\bkeine\\s+verbleibenden\\b${CTX_GAP}\\b${WIDE_DE}\\b`, 'i'), // "keine verbleibenden <noun>"
|
|
147
|
+
new RegExp(`\\bjed(?:er|e|es)\\s+${CTX_DE}\\b`, 'i'), // "jeder/jede/jedes <CTX_DE>"
|
|
148
|
+
new RegExp(`\\bkein(?:er|e|es)\\s+(?:von|der)\\b${CTX_GAP}\\b${WIDE_DE}\\b`, 'i'), // "keiner/keine/keins von/der <noun>"
|
|
149
|
+
// "N <noun> … davon N" (#1198 anchor).
|
|
150
|
+
new RegExp(`\\b\\d+\\s+${WIDE_DE}\\b${CTX_GAP}\\bdavon\\s+\\d+\\b`, 'i'),
|
|
151
|
+
new RegExp(`\\bs(?:ä|ae)mtliche\\s+${CTX_DE}\\b`, 'i'), // "sämtliche/saemtliche <CTX_DE>"
|
|
152
|
+
new RegExp(`\\bkein\\w{0,2}\\s+einzige[rs]?\\b${CTX_GAP}\\b${WIDE_DE}\\b`, 'i'), // "kein(e)? einzige[rs]? <noun>"
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Pattern 7 (#908) — the BARE CARDINAL repo-state fact. `14 commits` has no
|
|
157
|
+
* quantifier trigger at all, which is exactly why the #908 drift went unseen.
|
|
158
|
+
*
|
|
159
|
+
* Precision is bought three ways: a TRIGGER whose lookbehind/lookahead reject
|
|
160
|
+
* identifier punctuation (`#906`, `v3`, `3.17`, `70%`, `2026-07-29`), a GAP of
|
|
161
|
+
* at most two adjective-like words with no preposition/article/copula, and a
|
|
162
|
+
* SCOPE restricted to prose lines (see `NON_PROSE_LINE_RE` and `scanFences`).
|
|
163
|
+
*
|
|
164
|
+
* All quantifiers are bounded ({1,9}, {0,2}) — linear-time, ReDoS-safe.
|
|
165
|
+
*/
|
|
166
|
+
const CARDINAL_TRIGGER = '(?<![\\w#$:/.-])\\d{1,9}(?![\\d.%:/-])';
|
|
167
|
+
const CARDINAL_STOPWORDS =
|
|
168
|
+
'of|in|on|at|for|to|the|a|an|and|or|is|are|was|were|from|with|by|that|than|per|out|over|into|onto|via|but|as';
|
|
169
|
+
const CARDINAL_GAP = `(?:\\s+(?!(?:${CARDINAL_STOPWORDS})\\b)[A-Za-z][\\w-]*){0,2}`;
|
|
170
|
+
export const CARDINAL_PATTERN = new RegExp(`${CARDINAL_TRIGGER}${CARDINAL_GAP}\\s+${CARDINAL_NOUN}\\b`, 'i');
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Pattern 8 (#918) — the PSA-006 CANONICAL numerator/denominator slash form:
|
|
174
|
+
* "12/14 files", "4/4 callers". Stricter contract than the bare cardinal:
|
|
175
|
+
* exactly two slash-joined numbers, and the noun must follow IMMEDIATELY (no
|
|
176
|
+
* gap), which is what separates a measured ratio from a date or a score
|
|
177
|
+
* ("on 12/14 we shipped", "rated 3/5 overall").
|
|
178
|
+
*/
|
|
179
|
+
export const CARDINAL_RATIO_PATTERN = new RegExp(
|
|
180
|
+
`(?<![\\w#$:/.-])\\d{1,9}/\\d{1,9}(?![\\d.%:/-])\\s+${CARDINAL_NOUN}\\b`,
|
|
181
|
+
'i'
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
/** Inline-code spans are masked before any pattern runs. */
|
|
185
|
+
const INLINE_CODE_RE = /`[^`\n]*`/g;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Gate-summary / STATUS-report lines are the HARNESS's own completion output —
|
|
189
|
+
* tool evidence, not an unverified assertion about the codebase. GitLab #1198
|
|
190
|
+
* (Discovery D8, 2026-09-02): a 400-event sample showed 186/400 (46.5%) firing
|
|
191
|
+
* on this exact class — the single largest false-positive source measured to
|
|
192
|
+
* date. Skipped at the LINE level, before ANY pattern runs.
|
|
193
|
+
*
|
|
194
|
+
* Trade named, not fixed: matching anywhere on the line silences the WHOLE
|
|
195
|
+
* line, so a distributional claim sharing a line with a `Gate N/M` mention goes
|
|
196
|
+
* unflagged too — accepted because a gate-summary line is harness evidence.
|
|
197
|
+
*/
|
|
198
|
+
const GATE_SUMMARY_LINE_RE =
|
|
199
|
+
/\b\d+\s+passed\s*\/\s*\d+\s+failed\b|^\s*STATUS:\s*(?:done|partial|failed)\b|\bFull Gate\b|\bGate:\s*(?:typecheck|grün|gruen|rot)\b|\bGate \d[\d.]*\/\d+\b|\b\d+\s+Wellen?,\s*\d+\s+Agents?\b|\bArbeitsbaum leer\b|\bmit Nachweis geschlossen\b/i;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* NON-PROSE structural lines (#1218 negative-context guard). A PSA-006 claim is
|
|
203
|
+
* an ASSERTION in prose; these four shapes are not prose at all, and every one
|
|
204
|
+
* of them was measured as a live false-positive class.
|
|
205
|
+
*
|
|
206
|
+
* Measured 2026-09-06 over the 205 DISTINCT `claim_text` values in
|
|
207
|
+
* `~/Projects/extern/aiat-barrierefrei-engine/.orchestrator/metrics/events.jsonl`
|
|
208
|
+
* (the fleet's worst-affected repo; command in the module test header):
|
|
209
|
+
* 201/205 re-fire on the pre-#1218 matcher, of which 35 are markdown table
|
|
210
|
+
* rows and 9 are ATX headings — 44 firings, 21.9% of the corpus, with a true
|
|
211
|
+
* positive rate of zero in the independently sampled 60 (2026-09-02).
|
|
212
|
+
*
|
|
213
|
+
* 1. TABLE ROW — `| Lane B | 3 Commits, Baum sauber |`. A status matrix
|
|
214
|
+
* cell is structured data; the number in it is a field value.
|
|
215
|
+
* 2. TABLE SEPARATOR — `|---|---:|`.
|
|
216
|
+
* 3. ATX HEADING — `## Welle 1 abgeschlossen — 10/10 Lanes, 15 Commits`. A
|
|
217
|
+
* heading LABELS a section; the claim, if there is one, is restated in
|
|
218
|
+
* the body below it where it is still caught.
|
|
219
|
+
* NAMED CEILING (BV-004): a claim that appears ONLY in a heading and
|
|
220
|
+
* never in the body escapes. Accepted because the class measured zero
|
|
221
|
+
* true positives; REVISIT TRIGGER: a labelled sample in which a
|
|
222
|
+
* heading-only claim is a genuine PSA-006 violation.
|
|
223
|
+
* 4. PLAN / INTENT item — a task-list checkbox, or a line whose FIRST token
|
|
224
|
+
* declares intent rather than a finding (`TODO`, `Next`, `Geplant`,
|
|
225
|
+
* `Empfehlung`, `Follow-up`, …). An intent is a statement about the
|
|
226
|
+
* future; PSA-006 governs assertions about the present.
|
|
227
|
+
*
|
|
228
|
+
* All four are anchored at line START (after at most one bullet marker), so a
|
|
229
|
+
* mid-sentence "TODO" or a pipe character inside prose cannot silence a line.
|
|
230
|
+
*/
|
|
231
|
+
const TABLE_ROW_RE = /^\s{0,3}\|.*\|\s*$/;
|
|
232
|
+
const TABLE_SEPARATOR_RE = /^\s{0,3}\|?[\s:|-]*-{2,}[\s:|-]*\|?\s*$/;
|
|
233
|
+
const HEADING_RE = /^\s{0,3}#{1,6}\s+\S/;
|
|
234
|
+
const PLAN_INTENT_RE =
|
|
235
|
+
/^\s*(?:[-*+]\s+)?(?:\[[ xX]\]|TODO\b|FIXME\b|Plan\b|Planned\b|Next\b|Geplant\b|N(?:ä|ae)chste[rs]?\b|Vorschlag\b|Empfehlung\b|Recommendation\b|Follow-?ups?\b|Ziel\b|Aufgabe\b)/i;
|
|
236
|
+
|
|
237
|
+
const NON_PROSE_LINE_RE = new RegExp(
|
|
238
|
+
`(${TABLE_ROW_RE.source})|(${TABLE_SEPARATOR_RE.source})|(${HEADING_RE.source})|(${PLAN_INTENT_RE.source})`,
|
|
239
|
+
'i'
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* CONFIG / SCORE / VERSION literal line (#1218). A `key: <scalar>` line is a
|
|
244
|
+
* configuration or JSON fragment, not an assertion — the number is a SETTING.
|
|
245
|
+
*
|
|
246
|
+
* The key must be all-lowercase kebab/snake (`agents-per-wave`, `"session_id"`,
|
|
247
|
+
* `enabled`) — which is exactly what distinguishes a YAML/JSON key from a
|
|
248
|
+
* German or English prose label. `Gemessen: 4 von 4 Aufrufern` keeps its
|
|
249
|
+
* capital G and stays a claim; `quota-per-wave: 5` does not.
|
|
250
|
+
*
|
|
251
|
+
* Version literals (`v0.15.0`, `0.18.0`) need no rule of their own: the
|
|
252
|
+
* cardinal trigger's `(?![\d.%:/-])` lookahead already rejects a digit followed
|
|
253
|
+
* by a dot. Score ratios (`PAC bei 23/29`, `rated 3/5 overall`) are likewise
|
|
254
|
+
* already excluded — CARDINAL_RATIO_PATTERN demands an artefact noun
|
|
255
|
+
* immediately after the denominator. This rule closes the remaining third of
|
|
256
|
+
* the class: the config-block line.
|
|
257
|
+
*
|
|
258
|
+
* Suppresses ONLY the cardinal/ratio branch, never the quantifier patterns: a
|
|
259
|
+
* genuine "all 4 callers" inside a config-shaped line is still a claim.
|
|
260
|
+
*/
|
|
261
|
+
const CONFIG_KEY_LINE_RE =
|
|
262
|
+
/^\s*(?:[-*+]\s+)?["']?[a-z_][a-z0-9_.-]*["']?\s*:\s*(?:["'[{]|-?\d|true\b|false\b|null\b|on\b|off\b|~\s*$)/;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* A YAML/JSON BLOCK line that is not itself a `key: value` pair — a bare list
|
|
266
|
+
* item or a closing brace/bracket inside an object literal. Only recognised
|
|
267
|
+
* when the surrounding lines are config lines (see `scanConfigBlocks`), so a
|
|
268
|
+
* prose bullet is never mistaken for one.
|
|
269
|
+
*/
|
|
270
|
+
const CONFIG_CONTINUATION_RE = /^\s*(?:[-*]\s+\S|[}\]],?\s*$)/;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Commands that count as a MEASUREMENT inside a fenced or inline-code block.
|
|
274
|
+
* `grep|rg|find` (the #567 set) only covers text search; the #908 facts are
|
|
275
|
+
* measured with `git log --oneline | wc -l`, `jq` over a JSONL metrics file,
|
|
276
|
+
* `ls | wc -l`, or a `node`/`npm` script.
|
|
277
|
+
*/
|
|
278
|
+
const MEASUREMENT_CMD_RE = /\b(grep|rg|find|git|wc|jq|ls|node|npm)\b/;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* GERMAN PROSE evidence (#1218). Until now a measurement only counted when it
|
|
282
|
+
* appeared as a COMMAND — fenced or in backticks — so the German evidence
|
|
283
|
+
* forms this repo's own rules prescribe (`Gemessen 2026-09-06 …`,
|
|
284
|
+
* `Verifizierte Zahlen … HEAD <sha>`) were read as unverified prose and the
|
|
285
|
+
* honest author was flagged. PSA-006 item 4 asks for the measurement TIME; a
|
|
286
|
+
* German marker that carries one is evidence by the rule's own definition.
|
|
287
|
+
*
|
|
288
|
+
* The marker alone grants nothing: it must be followed within 80 characters by
|
|
289
|
+
* an ISO date, `HEAD`, or a commit SHA. `Gemessen wurde nichts` therefore does
|
|
290
|
+
* not buy an exemption, while `Gemessen 2026-09-06 @ e4674109: 4 von 4` does.
|
|
291
|
+
* Bounded quantifier ({0,80}) — linear-time, ReDoS-safe.
|
|
292
|
+
*/
|
|
293
|
+
const GERMAN_EVIDENCE_RE =
|
|
294
|
+
/\b(?:gemessen|nachgemessen|nachgez(?:ä|ae)hlt|ausgez(?:ä|ae)hlt|verifizierte\s+zahlen|verifiziert\s+(?:gegen|an|auf))\b[^\n]{0,80}?(?:\b\d{4}-\d{2}-\d{2}\b|\bHEAD\b|\b[0-9a-f]{7,40}\b)/i;
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Markers that date a measurement (#908 Baustein 2 input). ADVISORY: an
|
|
298
|
+
* undated-but-verified claim is counted and reported, never a violation.
|
|
299
|
+
* German markers (#1218) sit alongside the English ones for the same reason
|
|
300
|
+
* the evidence rule above admits them.
|
|
301
|
+
*/
|
|
302
|
+
const TIMESTAMP_MARKER_RE =
|
|
303
|
+
/\b\d{4}-\d{2}-\d{2}\b|\bHEAD\b|\bas of\b|\bmeasured (?:at|on)\b|\brev-parse\b|\bgemessen (?:am|an|auf|gegen)\b|\bStand vom\b/i;
|
|
304
|
+
|
|
305
|
+
/** Leading list-bullet / heading / blockquote markers stripped by `normalizeClaim`. */
|
|
306
|
+
const LEADING_MARKER_RE = /^(?:\s*(?:[-*+•]|\d{1,3}[.)]|#{1,6}|>)\s+)+/;
|
|
307
|
+
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
// transcript reading
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Read the transcript JSONL at `transcriptPath`, take the last `TAIL_RECORDS`
|
|
314
|
+
* `type:"assistant"` records, and concatenate their text content blocks.
|
|
315
|
+
* Returns '' on any failure (missing file, /dev/null, malformed lines).
|
|
316
|
+
*
|
|
317
|
+
* @param {string} transcriptPath
|
|
318
|
+
* @returns {Promise<string>}
|
|
319
|
+
*/
|
|
320
|
+
export async function readTranscriptTail(transcriptPath) {
|
|
321
|
+
if (typeof transcriptPath !== 'string' || !transcriptPath) return '';
|
|
322
|
+
let raw;
|
|
323
|
+
try {
|
|
324
|
+
raw = await fs.readFile(transcriptPath, 'utf8');
|
|
325
|
+
} catch {
|
|
326
|
+
return '';
|
|
327
|
+
}
|
|
328
|
+
if (!raw.trim()) return '';
|
|
329
|
+
|
|
330
|
+
const assistantRecords = [];
|
|
331
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
332
|
+
const trimmed = line.trim();
|
|
333
|
+
if (!trimmed) continue;
|
|
334
|
+
let rec;
|
|
335
|
+
try { rec = JSON.parse(trimmed); } catch { continue; }
|
|
336
|
+
if (rec && rec.type === 'assistant') assistantRecords.push(rec);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const tail = assistantRecords.slice(-TAIL_RECORDS);
|
|
340
|
+
const textBlocks = [];
|
|
341
|
+
for (const rec of tail) {
|
|
342
|
+
const content = rec?.message?.content;
|
|
343
|
+
if (!Array.isArray(content)) continue;
|
|
344
|
+
for (const block of content) {
|
|
345
|
+
if (block && block.type === 'text' && typeof block.text === 'string') {
|
|
346
|
+
textBlocks.push(block.text);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return textBlocks.join('\n');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
// claim scanning
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Single fence walk. Returns two disjoint-purpose index sets:
|
|
359
|
+
* - `measurementLines`: lines belonging to a fenced block whose body contains
|
|
360
|
+
* a MEASUREMENT_CMD_RE invocation (the evidence a claim can lean on), plus
|
|
361
|
+
* lines carrying an inline-code measurement or a German prose measurement.
|
|
362
|
+
* - `fencedLines`: lines belonging to ANY fenced block (evidence or not).
|
|
363
|
+
* Used only to keep the greedy #908 cardinal pattern out of tool output.
|
|
364
|
+
*
|
|
365
|
+
* An unterminated trailing fence is treated as fenced-to-EOF (conservative for
|
|
366
|
+
* false-positive suppression) but never as a measurement block.
|
|
367
|
+
*
|
|
368
|
+
* @param {string[]} lines
|
|
369
|
+
* @returns {{ measurementLines: Set<number>, fencedLines: Set<number> }}
|
|
370
|
+
*/
|
|
371
|
+
export function scanFences(lines) {
|
|
372
|
+
const measurementLines = new Set();
|
|
373
|
+
const fencedLines = new Set();
|
|
374
|
+
let fenceStart = -1;
|
|
375
|
+
let buffer = [];
|
|
376
|
+
let inFence = false;
|
|
377
|
+
|
|
378
|
+
for (let i = 0; i < lines.length; i++) {
|
|
379
|
+
const isFence = /^\s*```/.test(lines[i]);
|
|
380
|
+
if (!inFence) {
|
|
381
|
+
if (isFence) { inFence = true; fenceStart = i; buffer = []; }
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
// inside a fence
|
|
385
|
+
if (isFence) {
|
|
386
|
+
// closing fence — evaluate the buffered body
|
|
387
|
+
const body = buffer.join('\n');
|
|
388
|
+
const isMeasurement = MEASUREMENT_CMD_RE.test(body);
|
|
389
|
+
for (let j = fenceStart; j <= i; j++) {
|
|
390
|
+
fencedLines.add(j);
|
|
391
|
+
if (isMeasurement) measurementLines.add(j);
|
|
392
|
+
}
|
|
393
|
+
inFence = false;
|
|
394
|
+
fenceStart = -1;
|
|
395
|
+
buffer = [];
|
|
396
|
+
} else {
|
|
397
|
+
buffer.push(lines[i]);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Unterminated trailing fence — suppress cardinal matches, grant no evidence.
|
|
402
|
+
if (inFence) {
|
|
403
|
+
for (let j = fenceStart; j < lines.length; j++) fencedLines.add(j);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// INLINE-code evidence: PSA-006 asks for "the exact pattern executed" to be
|
|
407
|
+
// quoted — an inline `git log --oneline | wc -l` satisfies that exactly as
|
|
408
|
+
// well as a fenced block. GERMAN PROSE evidence (#1218) is admitted on the
|
|
409
|
+
// same line-level footing: `Gemessen 2026-09-06 @ e4674109` is a dated
|
|
410
|
+
// measurement declaration, which is what the rule asks for.
|
|
411
|
+
for (let i = 0; i < lines.length; i++) {
|
|
412
|
+
if (fencedLines.has(i)) continue;
|
|
413
|
+
if (GERMAN_EVIDENCE_RE.test(lines[i])) { measurementLines.add(i); continue; }
|
|
414
|
+
for (const span of lines[i].match(INLINE_CODE_RE) ?? []) {
|
|
415
|
+
if (MEASUREMENT_CMD_RE.test(span)) { measurementLines.add(i); break; }
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return { measurementLines, fencedLines };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Index set of lines that belong to a YAML/JSON block (#1218). A `key: scalar`
|
|
424
|
+
* line always qualifies; a bare list item or a closing brace qualifies only
|
|
425
|
+
* when an adjacent line is already a config line, so an ordinary prose bullet
|
|
426
|
+
* is never swept in.
|
|
427
|
+
*
|
|
428
|
+
* @param {string[]} lines
|
|
429
|
+
* @returns {Set<number>}
|
|
430
|
+
*/
|
|
431
|
+
export function scanConfigBlocks(lines) {
|
|
432
|
+
const configLines = new Set();
|
|
433
|
+
for (let i = 0; i < lines.length; i++) {
|
|
434
|
+
if (CONFIG_KEY_LINE_RE.test(lines[i])) configLines.add(i);
|
|
435
|
+
}
|
|
436
|
+
// One widening pass: continuation lines glued to a config line above them.
|
|
437
|
+
for (let i = 1; i < lines.length; i++) {
|
|
438
|
+
if (configLines.has(i)) continue;
|
|
439
|
+
if (configLines.has(i - 1) && CONFIG_CONTINUATION_RE.test(lines[i])) configLines.add(i);
|
|
440
|
+
}
|
|
441
|
+
return configLines;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* True when any line in `indices` sits within ±GREP_PROXIMITY_LINES of `i`.
|
|
446
|
+
*
|
|
447
|
+
* @param {Set<number>} indices
|
|
448
|
+
* @param {number} i
|
|
449
|
+
* @returns {boolean}
|
|
450
|
+
*/
|
|
451
|
+
function nearIndex(indices, i) {
|
|
452
|
+
for (let j = i - GREP_PROXIMITY_LINES; j <= i + GREP_PROXIMITY_LINES; j++) {
|
|
453
|
+
if (indices.has(j)) return true;
|
|
454
|
+
}
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* True when a measurement TIMESTAMP marker appears within the same proximity
|
|
460
|
+
* window used for the measurement block itself.
|
|
461
|
+
*
|
|
462
|
+
* @param {string[]} lines
|
|
463
|
+
* @param {number} i
|
|
464
|
+
* @returns {boolean}
|
|
465
|
+
*/
|
|
466
|
+
function hasMeasurementTimestamp(lines, i) {
|
|
467
|
+
const from = Math.max(0, i - GREP_PROXIMITY_LINES);
|
|
468
|
+
const to = Math.min(lines.length - 1, i + GREP_PROXIMITY_LINES);
|
|
469
|
+
for (let j = from; j <= to; j++) {
|
|
470
|
+
if (TIMESTAMP_MARKER_RE.test(lines[j])) return true;
|
|
471
|
+
}
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Normalise a claim line into its dedup key (#1198): trim, strip leading
|
|
477
|
+
* bullet / ordered-list / heading / blockquote markers, collapse all internal
|
|
478
|
+
* whitespace runs to one space, lowercase.
|
|
479
|
+
*
|
|
480
|
+
* The marker strip is what makes the key stable across the re-formatting an
|
|
481
|
+
* agent does between a bullet in its progress note and the same sentence in
|
|
482
|
+
* its final report — the two shapes that produced the measured duplication.
|
|
483
|
+
*
|
|
484
|
+
* @param {string} text
|
|
485
|
+
* @returns {string}
|
|
486
|
+
*/
|
|
487
|
+
export function normalizeClaim(text) {
|
|
488
|
+
if (typeof text !== 'string') return '';
|
|
489
|
+
return text
|
|
490
|
+
.replace(LEADING_MARKER_RE, '')
|
|
491
|
+
.replace(/\s+/g, ' ')
|
|
492
|
+
.trim()
|
|
493
|
+
.toLowerCase();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Collapse repeated claims into one entry per distinct normalized key,
|
|
498
|
+
* preserving first-seen order and counting `occurrences` (#1198).
|
|
499
|
+
*
|
|
500
|
+
* @param {string[]} claims — raw claim strings in encounter order
|
|
501
|
+
* @returns {{claim: string, normalized: string, occurrences: number}[]}
|
|
502
|
+
*/
|
|
503
|
+
export function dedupeViolations(claims) {
|
|
504
|
+
/** @type {Map<string, {claim: string, normalized: string, occurrences: number}>} */
|
|
505
|
+
const byKey = new Map();
|
|
506
|
+
for (const claim of claims) {
|
|
507
|
+
const normalized = normalizeClaim(claim);
|
|
508
|
+
if (!normalized) continue;
|
|
509
|
+
const hit = byKey.get(normalized);
|
|
510
|
+
if (hit) { hit.occurrences += 1; continue; }
|
|
511
|
+
byKey.set(normalized, { claim, normalized, occurrences: 1 });
|
|
512
|
+
}
|
|
513
|
+
return [...byKey.values()];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Scan concatenated transcript text for claims lacking an adjacent measurement
|
|
518
|
+
* block (within ±GREP_PROXIMITY_LINES).
|
|
519
|
+
*
|
|
520
|
+
* @param {string} text
|
|
521
|
+
* @returns {{ violations: {claim: string, normalized: string, occurrences: number}[], undatedVerified: number }}
|
|
522
|
+
* `violations` — deduplicated, truncated claim snippets with an occurrence
|
|
523
|
+
* count; `undatedVerified` — count of claims that ARE verified but carry no
|
|
524
|
+
* measurement timestamp (advisory).
|
|
525
|
+
*/
|
|
526
|
+
export function findViolations(text) {
|
|
527
|
+
if (!text) return { violations: [], undatedVerified: 0 };
|
|
528
|
+
const lines = text.split(/\r?\n/);
|
|
529
|
+
const { measurementLines, fencedLines } = scanFences(lines);
|
|
530
|
+
const configLines = scanConfigBlocks(lines);
|
|
531
|
+
const raw = [];
|
|
532
|
+
let undatedVerified = 0;
|
|
533
|
+
|
|
534
|
+
for (let i = 0; i < lines.length; i++) {
|
|
535
|
+
const line = lines[i];
|
|
536
|
+
// #1198 FIX 2: gate-summary/STATUS lines are tool OUTPUT, not a claim —
|
|
537
|
+
// skipped before any pattern runs (see GATE_SUMMARY_LINE_RE header).
|
|
538
|
+
if (GATE_SUMMARY_LINE_RE.test(line)) continue;
|
|
539
|
+
// #1218: table rows, headings and plan/intent items are not prose
|
|
540
|
+
// assertions at all — skipped before any pattern runs.
|
|
541
|
+
if (NON_PROSE_LINE_RE.test(line)) continue;
|
|
542
|
+
|
|
543
|
+
// #1198 FIX 3 (masking-order bug): mask inline-code spans ONCE, then test
|
|
544
|
+
// BOTH the CLAIM_PATTERNS and the cardinal/ratio patterns against the
|
|
545
|
+
// masked text, so a claim quoted entirely inside backticks (evidence or
|
|
546
|
+
// example text, not an assertion) cannot trip a pattern.
|
|
547
|
+
const masked = line.replace(INLINE_CODE_RE, ' ');
|
|
548
|
+
let matched = CLAIM_PATTERNS.some((re) => re.test(masked));
|
|
549
|
+
if (!matched && !fencedLines.has(i) && !configLines.has(i)) {
|
|
550
|
+
matched = CARDINAL_PATTERN.test(masked) || CARDINAL_RATIO_PATTERN.test(masked);
|
|
551
|
+
}
|
|
552
|
+
if (!matched) continue;
|
|
553
|
+
|
|
554
|
+
if (nearIndex(measurementLines, i)) {
|
|
555
|
+
if (!hasMeasurementTimestamp(lines, i)) undatedVerified++;
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
raw.push(line.trim().slice(0, CLAIM_TEXT_MAX));
|
|
560
|
+
}
|
|
561
|
+
return { violations: dedupeViolations(raw), undatedVerified };
|
|
562
|
+
}
|
|
@@ -50,7 +50,7 @@ import path from 'node:path';
|
|
|
50
50
|
|
|
51
51
|
import { emitAllow, emitDeny } from '../scripts/lib/io.mjs';
|
|
52
52
|
import { emitEvent } from '../scripts/lib/events.mjs';
|
|
53
|
-
import {
|
|
53
|
+
import { getProjectDir } from '../scripts/lib/platform.mjs';
|
|
54
54
|
import {
|
|
55
55
|
_parseConfigProtection,
|
|
56
56
|
_isConfigWeakeningAllowed,
|
|
@@ -461,7 +461,7 @@ async function main() {
|
|
|
461
461
|
let cfgContent = '';
|
|
462
462
|
for (const file of ['CLAUDE.md', 'AGENTS.md']) {
|
|
463
463
|
try {
|
|
464
|
-
cfgContent = await fs.readFile(path.join(
|
|
464
|
+
cfgContent = await fs.readFile(path.join(getProjectDir(), file), 'utf8');
|
|
465
465
|
if (cfgContent) break;
|
|
466
466
|
} catch { /* try next candidate */ }
|
|
467
467
|
}
|
|
@@ -30,7 +30,7 @@ import { shouldRunHook } from './_lib/profile-gate.mjs';
|
|
|
30
30
|
// Exit 0 immediately when disabled via SO_HOOK_PROFILE / SO_DISABLED_HOOKS.
|
|
31
31
|
if (!shouldRunHook('cwd-change-restore')) process.exit(0);
|
|
32
32
|
|
|
33
|
-
import {
|
|
33
|
+
import { getProjectDir } from '../scripts/lib/platform.mjs';
|
|
34
34
|
import { atomicMutateJson } from './_lib/atomic-json.mjs';
|
|
35
35
|
|
|
36
36
|
// ---------------------------------------------------------------------------
|
|
@@ -89,7 +89,7 @@ async function main() {
|
|
|
89
89
|
new_cwd: newCwd,
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
-
const sessionFile = path.join(
|
|
92
|
+
const sessionFile = path.join(getProjectDir(), '.orchestrator', 'current-session.json');
|
|
93
93
|
|
|
94
94
|
const result = await atomicMutateJson(sessionFile, {}, (current) => {
|
|
95
95
|
const existing = Array.isArray(current.cwd_changes)
|