session-orchestrator 5.3.0 → 5.5.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/claude-md-drift-check/SKILL.md +1 -1
- package/.agents/skills/test-audit/SKILL.md +21 -0
- package/.agents/skills/test-audit/agents/openai.yaml +5 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
- package/.codex-plugin/skills/test-audit/agents/openai.yaml +5 -0
- package/.cursor/commands/test-audit.md +14 -0
- package/.cursor/rules/060-evolve.mdc +28 -13
- package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
- package/.cursor/skills/test-audit/SKILL.md +12 -0
- package/.cursor-plugin/plugin.json +1 -1
- package/.orchestrator/policy/blocked-commands.json +1 -1
- package/AGENTS.md +9 -31
- package/CHANGELOG.md +162 -0
- package/README.md +14 -10
- package/docs/agent-authoring.md +2 -2
- package/docs/baseline.md +10 -1
- package/docs/ci-setup.md +154 -4
- package/docs/components.md +11 -11
- package/docs/events-schema.md +25 -19
- package/docs/install.md +14 -2
- package/docs/migration-v4.md +24 -17
- package/docs/persona-panel.md +5 -4
- package/docs/plugin-architecture-v3.md +1 -1
- package/docs/rule-authoring.md +27 -12
- package/docs/scope-collision-guard.md +40 -16
- package/docs/session-config-reference.md +61 -12
- package/docs/session-config-template.md +11 -2
- package/docs/vault-docs-architecture.md +8 -4
- package/hooks/_lib/hook-import-set.json +42 -29
- package/hooks/_lib/lock-bootstrap.mjs +118 -26
- package/hooks/config-protection.mjs +1 -1
- package/hooks/enforce-commands.mjs +28 -13
- package/hooks/enforce-scope.mjs +201 -23
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks-cursor.json +5 -0
- package/hooks/hooks-pi.json +5 -0
- package/hooks/hooks.json +6 -1
- package/hooks/on-session-start.mjs +57 -2
- package/hooks/on-stop.mjs +70 -168
- package/hooks/post-bash-write-verify.mjs +98 -17
- package/hooks/post-edit-validate.mjs +15 -5
- package/hooks/post-tool-batch-wave-signal.mjs +32 -152
- package/hooks/pre-bash-memory-propose-audit.mjs +71 -10
- package/hooks/pre-bash-sessions-ledger-guard.mjs +28 -2
- package/hooks/pre-bash-staging-fence.mjs +29 -8
- package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
- package/hooks/pre-task-scope-disjoint.mjs +1143 -279
- package/hooks/subagent-telemetry.mjs +96 -19
- package/hooks/wave-scope-commit-guard.mjs +168 -72
- package/package.json +2 -6
- package/pi/prompts/test-audit.md +12 -0
- package/rules/README.md +6 -1
- package/rules/_index.md +3 -3
- package/rules/always-on/bash-harness-pitfalls.md +7 -1
- package/rules/opt-in-domain/prompt-caching.md +5 -1
- package/rules/opt-in-stack/backend-data.md +9 -1
- package/rules/opt-in-stack/backend.md +7 -1
- package/rules/opt-in-stack/frontend.md +6 -1
- package/rules/opt-in-stack/security-web.md +7 -1
- package/rules/opt-in-stack/swift.md +5 -1
- package/scripts/apply-session-learnings.mjs +513 -0
- package/scripts/archive-closed-prds.mjs +40 -12
- package/scripts/autopilot.mjs +57 -54
- package/scripts/backfill-abandoned-sessions.mjs +81 -6
- package/scripts/backfill-learnings-from-vault.mjs +49 -22
- package/scripts/baseline-archetypes.mjs +1 -1
- package/scripts/check-package-manager.mjs +62 -5
- package/scripts/codex-install.mjs +3 -0
- package/scripts/cursor-install.mjs +47 -6
- package/scripts/emit-session.mjs +298 -2
- package/scripts/export-hw-learnings.mjs +40 -10
- package/scripts/fleet-checkin.mjs +170 -0
- package/scripts/lib/agent-frontmatter.mjs +3 -3
- package/scripts/lib/auto-dream.mjs +7 -6
- package/scripts/lib/autopilot/flags.mjs +6 -1
- package/scripts/lib/autopilot/loop.mjs +32 -13
- package/scripts/lib/autopilot/stall-sampler.mjs +13 -9
- package/scripts/lib/baseline-archetypes.mjs +41 -22
- package/scripts/lib/blocked-commands-policy.mjs +40 -0
- package/scripts/lib/common.mjs +41 -0
- package/scripts/lib/config/host-paths.mjs +79 -0
- package/scripts/lib/config/persona-gate-wave.mjs +3 -3
- package/scripts/lib/config/reaper.mjs +7 -6
- package/scripts/lib/config/vault-integration.mjs +151 -16
- package/scripts/lib/config-schema.mjs +13 -0
- package/scripts/lib/config.mjs +75 -10
- package/scripts/lib/eval/engine.mjs +13 -0
- package/scripts/lib/events-retention-banner.mjs +393 -0
- package/scripts/lib/events-rotation.mjs +21 -6
- package/scripts/lib/events.mjs +72 -5
- package/scripts/lib/fleet-protocol.mjs +288 -0
- package/scripts/lib/gates/gate-baseline.mjs +13 -3
- package/scripts/lib/gates/gate-full.mjs +7 -2
- package/scripts/lib/gates/gate-helpers.mjs +93 -2
- package/scripts/lib/gates/gate-incremental.mjs +23 -22
- package/scripts/lib/gates/gate-per-file.mjs +16 -7
- package/scripts/lib/hardening.mjs +1 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +8 -6
- package/scripts/lib/instruction-budget-guard.mjs +77 -11
- package/scripts/lib/learnings/expiry-sweep.mjs +420 -22
- package/scripts/lib/learnings/io.mjs +274 -37
- package/scripts/lib/learnings/schema.mjs +163 -15
- package/scripts/lib/learnings/sizing-subject.mjs +2 -2
- package/scripts/lib/lock-reaper.mjs +38 -14
- package/scripts/lib/maintenance-due-banner.mjs +23 -8
- package/scripts/lib/memory-proposals/schema.mjs +11 -5
- package/scripts/lib/named-baseline-resolver.mjs +53 -28
- package/scripts/lib/named-vault-resolver.mjs +147 -5
- package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
- package/scripts/lib/orphan-reaper/no-follow-append.mjs +87 -0
- package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
- package/scripts/lib/orphan-reaper/reaper-audit.mjs +323 -0
- package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
- package/scripts/lib/orphan-reaper/scan-throttle.mjs +144 -0
- package/scripts/lib/orphan-reaper/scan.mjs +510 -0
- package/scripts/lib/orphan-reaper/trigger.mjs +173 -0
- package/scripts/lib/orphan-reaper.mjs +47 -1315
- package/scripts/lib/owner-yaml.mjs +104 -6
- package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
- package/scripts/lib/platform.mjs +206 -2
- package/scripts/lib/plugin-update-banner.mjs +3 -1
- package/scripts/lib/process-group.mjs +134 -22
- package/scripts/lib/project-hygiene.mjs +21 -1
- package/scripts/lib/quality-gate.mjs +45 -5
- package/scripts/lib/resource-probe.mjs +1 -1
- package/scripts/lib/rule-loader.mjs +100 -62
- package/scripts/lib/rules-sync.mjs +27 -12
- package/scripts/lib/scope-baseline.mjs +119 -29
- package/scripts/lib/scope-echo.mjs +7 -0
- package/scripts/lib/scope-gate.mjs +194 -27
- package/scripts/lib/session-close-backfill.mjs +129 -4
- package/scripts/lib/session-end/tail-runner.mjs +10 -5
- package/scripts/lib/session-lock-shape.mjs +6 -3
- package/scripts/lib/session-lock.mjs +320 -67
- package/scripts/lib/session-record-repair.mjs +183 -26
- package/scripts/lib/session-registry.mjs +67 -17
- package/scripts/lib/session-schema/constants.mjs +30 -4
- package/scripts/lib/session-schema/validator.mjs +28 -1
- package/scripts/lib/session-start-probes.mjs +242 -68
- package/scripts/lib/session-token-rollup.mjs +252 -54
- package/scripts/lib/sessions-canonical.mjs +100 -9
- package/scripts/lib/sessions-staleness-banner.mjs +3 -1
- package/scripts/lib/sh-quote.mjs +29 -0
- package/scripts/lib/subagents-schema.mjs +30 -3
- package/scripts/lib/tail-window.mjs +27 -3
- package/scripts/lib/telemetry/pricing.mjs +121 -71
- package/scripts/lib/telemetry/sync.mjs +17 -0
- package/scripts/lib/tests-src-ratio.mjs +215 -23
- package/scripts/lib/tmux-layout/layouts.mjs +1 -11
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +12 -0
- package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
- package/scripts/lib/ux-grill/reconcile.mjs +1 -1
- package/scripts/lib/validate/check-agents.mjs +1 -1
- package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
- package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
- package/scripts/lib/validate/check-test-value-bans.mjs +388 -27
- package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
- package/scripts/lib/validate/check-unwired-features.mjs +1 -1
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +1 -1
- package/scripts/lib/validate-vendored-rules.mjs +206 -81
- package/scripts/lib/vault-mirror/auto-commit.mjs +5 -3
- package/scripts/lib/vault-mirror/namespace.mjs +47 -26
- package/scripts/lib/vault-mirror/process.mjs +350 -62
- package/scripts/lib/vault-mirror/pseudonym-map.mjs +14 -5
- package/scripts/lib/vault-mirror/render-learnings.mjs +66 -3
- package/scripts/lib/vault-mirror/render-sessions.mjs +28 -4
- package/scripts/lib/vault-mirror/telemetry.mjs +25 -11
- package/scripts/lib/vault-status/board-writer.mjs +53 -12
- package/scripts/lib/vault-status/narrative-mirror.mjs +40 -9
- package/scripts/lib/vcs-repo-spec.mjs +8 -0
- package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
- package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
- package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
- package/scripts/lib/wave-resource-gate.mjs +34 -8
- package/scripts/materialize-wave-scope.mjs +7 -4
- package/scripts/memory-propose.mjs +18 -5
- package/scripts/release-session-lock.mjs +2 -1
- package/scripts/release.mjs +11 -3
- package/scripts/repair-invalid-sessions.mjs +37 -4
- package/scripts/run-migrate-v2-cross-repo.mjs +171 -31
- package/scripts/run-quality-gate.mjs +331 -307
- package/scripts/self-update.mjs +229 -0
- package/scripts/site-numbers.mjs +163 -5
- package/scripts/sweep-expired-learnings.mjs +603 -20
- package/scripts/sweep-expired-rules.mjs +2 -1
- package/scripts/validate-plugin.mjs +7 -0
- package/scripts/validate-wave-scope.mjs +21 -2
- package/scripts/vault-backfill.mjs +50 -37
- package/scripts/vault-integration-watcher.mjs +2 -1
- package/scripts/vault-mirror.mjs +238 -75
- package/scripts/wave-scope-binding.mjs +2 -1
- package/skills/_shared/config-reading.md +3 -3
- package/skills/_shared/fleet-protocol.md +134 -0
- package/skills/_shared/state-ownership.md +41 -12
- package/skills/autopilot/SKILL.md +25 -11
- package/skills/bootstrap/SKILL.md +6 -4
- package/skills/bootstrap/_shared-template.md +97 -2
- package/skills/bootstrap/fast-template.md +4 -1
- package/skills/bootstrap/private-contract.md +2 -1
- package/skills/bootstrap/public-fallback.md +6 -3
- package/skills/bootstrap/references/bootstrap-refresh-lock-flow.md +5 -3
- package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
- package/skills/bootstrap/standard-template.md +2 -0
- package/skills/claude-md-drift-check/SKILL.md +10 -10
- package/skills/claude-md-drift-check/checker.mjs +26 -18
- package/skills/discovery/probes/ssot-code-diff.mjs +12 -6
- package/skills/discovery/probes/vault-narrative-staleness.mjs +6 -2
- package/skills/discovery/probes/vault-staleness.mjs +9 -3
- package/skills/docs-orchestrator/SKILL.md +12 -5
- package/skills/docs-orchestrator/audience-mapping.md +5 -3
- package/skills/evolve/SKILL.md +13 -4
- package/skills/evolve/references/evolve-analyze-mode.md +42 -18
- package/skills/memory-cleanup/SKILL.md +1 -1
- package/skills/persona-panel/SKILL.md +2 -2
- package/skills/persona-panel/persona-format.md +2 -2
- package/skills/persona-panel/presets/designer-lens.md +1 -1
- package/skills/persona-panel/presets/engineer-lens.md +1 -1
- package/skills/persona-panel/presets/pm-lens.md +1 -1
- package/skills/plan/mode-retro.md +19 -4
- package/skills/quality-gates/SKILL.md +6 -4
- package/skills/remote-offload/SKILL.md +4 -2
- package/skills/session-end/learning-patterns.md +23 -13
- package/skills/session-end/metrics-collection.md +6 -7
- package/skills/session-end/phase-3-2-docs-verification.md +7 -6
- package/skills/session-end/plan-verification.md +25 -9
- package/skills/session-end/references/phase-3-documentation-updates.md +13 -3
- package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
- package/skills/session-end/references/session-summary-template.md +1 -1
- package/skills/session-end/session-metrics-write.md +65 -68
- package/skills/session-plan/SKILL.md +0 -3
- package/skills/session-start/SKILL.md +8 -2
- package/skills/session-start/references/operations-contract.md +6 -0
- package/skills/session-start/references/phase-1-2-session-lock.md +32 -7
- package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
- package/skills/session-start/references/phase-4-ssot-environment-check.md +10 -3
- package/skills/test-audit/SKILL.md +152 -0
- package/skills/test-audit/references/campaign.md +111 -0
- package/skills/test-audit/references/mutate.sh +50 -0
- package/skills/vault-mirror/SKILL.md +21 -11
- package/skills/wave-executor/SKILL.md +2 -2
- package/skills/wave-executor/circuit-breaker.md +1 -1
- package/skills/wave-executor/references/wave-executor-state-init.md +3 -0
- package/skills/wave-executor/references/wave-loop-dispatch.md +11 -1
- package/skills/wave-executor/references/wave-loop-review.md +4 -6
- package/skills/wave-executor/references/wave-loop-scope-manifest.md +13 -5
- package/templates/personas/README.md +5 -5
- package/templates/personas/accounting-compliance.v1.md +1 -1
- package/templates/personas/accounting-tax-advisor.v1.md +1 -1
- package/templates/personas/buyer-p1-cto.v1.md +1 -1
- package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
- package/templates/personas/buyer-p3-build.v1.md +1 -1
- package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
- package/templates/personas/buyer-p5-solo.v1.md +1 -1
- package/templates/personas/buyer-p6-ld.v1.md +1 -1
- package/templates/personas/klima-ai-expert.v1.md +1 -1
- package/templates/personas/klima-physicist.v1.md +1 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Check: no instruction prose tells an agent to write `learnings.jsonl` with a
|
|
4
|
+
* shell redirect (GitLab #1446 / GitHub #69). BLOCKING.
|
|
5
|
+
*
|
|
6
|
+
* ## Why
|
|
7
|
+
*
|
|
8
|
+
* The store is gitignored, so a bad write has no VCS restore. The sanctioned
|
|
9
|
+
* write paths — `scripts/apply-session-learnings.mjs` (session-end, retro) and
|
|
10
|
+
* `scripts/sweep-expired-learnings.mjs --prune` (`/evolve`) — validate every
|
|
11
|
+
* record, snapshot `.bak-<ISO>`, and archive what leaves the store. A shell
|
|
12
|
+
* `>` / `>>` does none of that. The prose producers this change closed, all
|
|
13
|
+
* measured at ebf63bd1:
|
|
14
|
+
*
|
|
15
|
+
* - `skills/session-end/learning-patterns.md` Phase 3.6 step 4g ("atomic
|
|
16
|
+
* rewrite with `>`") — the path that wrote a file PATH into `scope` (GH#69);
|
|
17
|
+
* - `skills/plan/mode-retro.md` § 3.3 — an unnamed "Append new patterns to
|
|
18
|
+
* `learnings.jsonl`" with no mechanism at all;
|
|
19
|
+
* - `.cursor/rules/060-evolve.mdc` — three sites ("Write entire result back
|
|
20
|
+
* with `>`", "write all with `>`" twice). None of the three names
|
|
21
|
+
* `learnings.jsonl` on its own line, which is why R2 is windowed.
|
|
22
|
+
*
|
|
23
|
+
* ## Rules
|
|
24
|
+
*
|
|
25
|
+
* R1 shell redirect — `>`/`>>` directly in front of a `…learnings.jsonl`
|
|
26
|
+
* target on the same line. The leading boundary (start,
|
|
27
|
+
* whitespace, `|`, `;`, `&`, `(`) is load-bearing:
|
|
28
|
+
* without it 9 of 12 hits were `<state-dir>/…`
|
|
29
|
+
* placeholders. A Markdown blockquote marker (`> ` at
|
|
30
|
+
* line start, outside a fence) is not a redirect and is
|
|
31
|
+
* stripped first; inside a fence it IS a truncation.
|
|
32
|
+
* R3 tee — `tee [-a] …learnings.jsonl` on the same line.
|
|
33
|
+
* R2 prose operator — a backticked `` `>` `` / `` `>>` `` within ±3 lines
|
|
34
|
+
* (WINDOW) of an anchor: `learnings.jsonl`, "the store",
|
|
35
|
+
* "learnings store", "result back", "atomic rewrite".
|
|
36
|
+
* The last two are the retired producers' own wording.
|
|
37
|
+
* An operator inside an ASCII double-quoted span is a
|
|
38
|
+
* quotation of former prose, not an instruction.
|
|
39
|
+
*
|
|
40
|
+
* Exempt (per operator): a prohibition keyword (NEVER / do not / FORBIDDEN /
|
|
41
|
+
* verboten …) BEFORE the operator in the same sentence — a keyword after it
|
|
42
|
+
* (`… > learnings.jsonl # never run this twice`) or in an earlier sentence
|
|
43
|
+
* (`Never skip review. Then run: … >> learnings.jsonl`) does not count.
|
|
44
|
+
* Exempt (whole line): the last sentence of the previous non-empty line states
|
|
45
|
+
* a prohibition before any operator, an empty-file create (`: > file`), or the
|
|
46
|
+
* marker `<!-- learnings-write-check: prohibition -->` on the line or the
|
|
47
|
+
* previous non-empty line. Fenced blocks ARE scanned — a fenced shell snippet
|
|
48
|
+
* is exactly a shell write.
|
|
49
|
+
*
|
|
50
|
+
* @module scripts/lib/validate/check-learnings-shell-writes
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
import { readFileSync } from 'node:fs';
|
|
54
|
+
import path from 'node:path';
|
|
55
|
+
import { enumerateRepoFiles } from './enumerate-repo-files.mjs';
|
|
56
|
+
import { isMainModule } from '../is-main-module.mjs';
|
|
57
|
+
|
|
58
|
+
/** Instruction corpora that tell an agent what to run. */
|
|
59
|
+
export const SCAN_DIRS = Object.freeze(['skills', 'commands', 'agents', 'docs', '.claude/rules', '.cursor/rules']);
|
|
60
|
+
|
|
61
|
+
/** Lines on either side of an R2 operator searched for an anchor. */
|
|
62
|
+
const WINDOW = 3;
|
|
63
|
+
|
|
64
|
+
const MENTION_RE = /learnings\.jsonl/;
|
|
65
|
+
const ANCHOR_RE = /learnings\.jsonl|\b(?:the|learnings) store\b|\bresult back\b|\batomic rewrite\b/i;
|
|
66
|
+
const SHELL_REDIRECT_RE = /(?:^|[\s|;&(])(>{1,2})[ \t]*["'`]?[^\s"'`<>]*learnings\.jsonl\b/dg;
|
|
67
|
+
const TEE_RE = /\btee\b(?:[ \t]+-{1,2}[\w-]+)*[ \t]+["'`]?[^\s"'`|]*learnings\.jsonl\b/g;
|
|
68
|
+
const PROSE_OPERATOR_RE = /`>{1,2}`/g;
|
|
69
|
+
const ANY_OPERATOR_RE = /`>{1,2}`|(?:^|[\s|;&(])>{1,2}|\btee\b/;
|
|
70
|
+
const PROHIBITION_RE = /\b(NEVER|[Nn]ever|DO NOT|[Dd]o not|[Dd]on't|FORBIDDEN|forbidden|verboten)\b/;
|
|
71
|
+
// A sentence ends at `.`/`!`/`?` + whitespace + a capital (markup allowed):
|
|
72
|
+
// `jq . next` and `jq | ... >` are not sentence breaks.
|
|
73
|
+
const SENTENCE_BREAK_RE = /[.!?]\s+(?=[*_`"']*[A-Z])/;
|
|
74
|
+
const EMPTY_CREATE_RE = /^\s*:\s*>/;
|
|
75
|
+
const BLOCKQUOTE_RE = /^\s*(?:>[ \t])+/;
|
|
76
|
+
const FENCE_RE = /^\s*(?:```|~~~)/;
|
|
77
|
+
const MARKER = '<!-- learnings-write-check: prohibition -->';
|
|
78
|
+
|
|
79
|
+
/** The sentence a position sits in, cut off at that position. */
|
|
80
|
+
function clauseBefore(text, pos) {
|
|
81
|
+
return text.slice(0, pos).split(SENTENCE_BREAK_RE).pop();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** True when a prohibition keyword precedes `pos` within its own sentence. */
|
|
85
|
+
function prohibitedBefore(text, pos) {
|
|
86
|
+
return PROHIBITION_RE.test(clauseBefore(text, pos));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** True when the last sentence of `line` states a prohibition before any operator. */
|
|
90
|
+
function isProhibitionSentence(line) {
|
|
91
|
+
const last = line.replace(BLOCKQUOTE_RE, '').trimEnd().split(SENTENCE_BREAK_RE).pop();
|
|
92
|
+
const k = last.search(PROHIBITION_RE);
|
|
93
|
+
if (k < 0) return false;
|
|
94
|
+
const o = last.search(ANY_OPERATOR_RE);
|
|
95
|
+
return o < 0 || k < o;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** True when `pos` sits inside an ASCII double-quoted span of `text`. */
|
|
99
|
+
function insideDoubleQuotes(text, pos) {
|
|
100
|
+
return (text.slice(0, pos).match(/"/g) ?? []).length % 2 === 1;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Every operator on the line as `{rule, pos}`, positions relative to `body`.
|
|
105
|
+
* R2 candidates are returned only when the ±WINDOW neighbourhood holds an anchor.
|
|
106
|
+
*/
|
|
107
|
+
function operatorsOf(body, lines, i) {
|
|
108
|
+
const ops = [];
|
|
109
|
+
if (MENTION_RE.test(body)) {
|
|
110
|
+
for (const m of body.matchAll(SHELL_REDIRECT_RE)) ops.push({ rule: 'R1', pos: m.indices[1][0] });
|
|
111
|
+
for (const m of body.matchAll(TEE_RE)) ops.push({ rule: 'R3', pos: m.index });
|
|
112
|
+
}
|
|
113
|
+
const prose = [...body.matchAll(PROSE_OPERATOR_RE)];
|
|
114
|
+
if (prose.length > 0) {
|
|
115
|
+
const from = Math.max(0, i - WINDOW);
|
|
116
|
+
const anchored = lines.slice(from, i + WINDOW + 1).some((l) => ANCHOR_RE.test(l));
|
|
117
|
+
if (anchored) {
|
|
118
|
+
for (const m of prose) {
|
|
119
|
+
if (!insideDoubleQuotes(body, m.index)) ops.push({ rule: 'R2', pos: m.index });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return ops;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Scan the instruction corpora for shell writes to `learnings.jsonl`.
|
|
128
|
+
*
|
|
129
|
+
* @param {{ pluginRoot: string, dirs?: readonly string[] }} opts
|
|
130
|
+
* @returns {{ ok: boolean, findings: {file: string, line: number, rule: string, text: string}[],
|
|
131
|
+
* summary: { filesScanned: number, mentions: number, exempt: number } }}
|
|
132
|
+
*/
|
|
133
|
+
export function scanLearningsShellWrites({ pluginRoot, dirs = SCAN_DIRS }) {
|
|
134
|
+
const files = enumerateRepoFiles({ repoRoot: pluginRoot, dirs: [...dirs], exts: ['.md', '.mdc'] });
|
|
135
|
+
const findings = [];
|
|
136
|
+
let mentions = 0;
|
|
137
|
+
let exempt = 0;
|
|
138
|
+
for (const abs of files) {
|
|
139
|
+
const lines = readFileSync(abs, 'utf8').split('\n');
|
|
140
|
+
const rel = path.relative(pluginRoot, abs);
|
|
141
|
+
let inFence = false;
|
|
142
|
+
let prev = '';
|
|
143
|
+
lines.forEach((text, i) => {
|
|
144
|
+
const prevNonEmpty = prev;
|
|
145
|
+
if (text.trim().length > 0) prev = text;
|
|
146
|
+
if (FENCE_RE.test(text)) {
|
|
147
|
+
inFence = !inFence;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (MENTION_RE.test(text)) mentions += 1;
|
|
151
|
+
const body = inFence ? text : text.replace(BLOCKQUOTE_RE, '');
|
|
152
|
+
const ops = operatorsOf(body, lines, i);
|
|
153
|
+
if (ops.length === 0) return;
|
|
154
|
+
const lineExempt =
|
|
155
|
+
EMPTY_CREATE_RE.test(body) ||
|
|
156
|
+
text.includes(MARKER) ||
|
|
157
|
+
prevNonEmpty.includes(MARKER) ||
|
|
158
|
+
isProhibitionSentence(prevNonEmpty);
|
|
159
|
+
const failing = lineExempt ? [] : ops.filter((op) => !prohibitedBefore(body, op.pos));
|
|
160
|
+
if (failing.length === 0) {
|
|
161
|
+
exempt += 1;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const rule = ['R1', 'R3', 'R2'].find((r) => failing.some((op) => op.rule === r));
|
|
165
|
+
findings.push({ file: rel, line: i + 1, rule, text: text.trim() });
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return { ok: findings.length === 0, findings, summary: { filesScanned: files.length, mentions, exempt } };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const RULE_TEXT = Object.freeze({
|
|
172
|
+
R1: 'shell redirect into learnings.jsonl',
|
|
173
|
+
R2: 'redirect operator near a learnings.jsonl / store-write mention',
|
|
174
|
+
R3: 'tee into learnings.jsonl',
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Run the human-readable validator CLI.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} pluginRoot absolute plugin root
|
|
181
|
+
* @returns {number} 0 = clean, 1 = findings
|
|
182
|
+
*/
|
|
183
|
+
export function runCheckLearningsShellWrites(pluginRoot) {
|
|
184
|
+
console.log('--- Check: no shell-redirect writes to learnings.jsonl in instruction prose ---');
|
|
185
|
+
const inspection = scanLearningsShellWrites({ pluginRoot });
|
|
186
|
+
for (const f of inspection.findings) {
|
|
187
|
+
console.log(
|
|
188
|
+
` FAIL: [${f.rule}] ${f.file}:${f.line} ${RULE_TEXT[f.rule]} — route the write through ` +
|
|
189
|
+
'`scripts/apply-session-learnings.mjs` or `sweep-expired-learnings.mjs --prune`',
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const s = inspection.summary;
|
|
193
|
+
if (inspection.ok) {
|
|
194
|
+
console.log(
|
|
195
|
+
` PASS: ${s.mentions} learnings.jsonl mention(s) in ${s.filesScanned} file(s), ` +
|
|
196
|
+
`${s.exempt} exempt as prohibition`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
console.log('');
|
|
200
|
+
console.log(`Results: ${inspection.ok ? 1 : 0} passed, ${inspection.findings.length} failed`);
|
|
201
|
+
return inspection.ok ? 0 : 1;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (isMainModule(import.meta.url)) {
|
|
205
|
+
const args = process.argv.slice(2).filter((arg) => arg !== '--json');
|
|
206
|
+
const root = path.resolve(args[0] || process.cwd());
|
|
207
|
+
if (process.argv.includes('--json')) {
|
|
208
|
+
const inspection = scanLearningsShellWrites({ pluginRoot: root });
|
|
209
|
+
process.stdout.write(`${JSON.stringify(inspection, null, 2)}\n`);
|
|
210
|
+
process.exitCode = inspection.ok ? 0 : 1;
|
|
211
|
+
} else {
|
|
212
|
+
process.exitCode = runCheckLearningsShellWrites(root);
|
|
213
|
+
}
|
|
214
|
+
}
|