@webpieces/ai-hook-rules 0.4.609 → 0.4.611
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/README.md +1 -1
- package/package.json +2 -2
- package/src/adapters/claude-code-response.js +6 -2
- package/src/adapters/claude-code-response.js.map +1 -1
- package/src/adapters/hook-core.js +13 -10
- package/src/adapters/hook-core.js.map +1 -1
- package/src/bin/dev-hook-install.js +3 -3
- package/src/bin/dev-hook-install.js.map +1 -1
- package/src/bin/guarantee-root.js +23 -10
- package/src/bin/guarantee-root.js.map +1 -1
- package/src/bin/l0-allowlist.js +1 -1
- package/src/bin/l0-allowlist.js.map +1 -1
- package/src/bin/setup.js +6 -3
- package/src/bin/setup.js.map +1 -1
- package/src/bin/shim-audit-log.d.ts +0 -3
- package/src/bin/shim-audit-log.js +14 -13
- package/src/bin/shim-audit-log.js.map +1 -1
- package/src/bin/shim.js +2 -2
- package/src/bin/shim.js.map +1 -1
- package/src/core/decision-log.d.ts +75 -10
- package/src/core/decision-log.js +91 -28
- package/src/core/decision-log.js.map +1 -1
- package/src/core/l0-fault-codes.d.ts +1 -1
- package/src/core/l0-fault-codes.js +1 -1
- package/src/core/l0-fault-codes.js.map +1 -1
- package/src/core/l1-doc.js +6 -0
- package/src/core/l1-doc.js.map +1 -1
- package/src/core/l1-rows.d.ts +13 -0
- package/src/core/l1-rows.js +24 -11
- package/src/core/l1-rows.js.map +1 -1
- package/src/core/load-rules.js +2 -0
- package/src/core/load-rules.js.map +1 -1
- package/src/core/log-stream.d.ts +32 -18
- package/src/core/log-stream.js +33 -19
- package/src/core/log-stream.js.map +1 -1
- package/src/core/log-streams.d.ts +38 -0
- package/src/core/log-streams.js +45 -0
- package/src/core/log-streams.js.map +1 -0
- package/src/core/main-sync-log.d.ts +2 -2
- package/src/core/main-sync-log.js +22 -15
- package/src/core/main-sync-log.js.map +1 -1
- package/src/core/main-sync-refresh.d.ts +3 -3
- package/src/core/main-sync-refresh.js +4 -4
- package/src/core/main-sync-refresh.js.map +1 -1
- package/src/core/rejection-log.d.ts +1 -1
- package/src/core/rejection-log.js +13 -12
- package/src/core/rejection-log.js.map +1 -1
- package/src/core/rules/feature-branch-guard.d.ts +11 -0
- package/src/core/rules/feature-branch-guard.js +20 -5
- package/src/core/rules/feature-branch-guard.js.map +1 -1
- package/src/core/rules/index.js +1 -0
- package/src/core/rules/index.js.map +1 -1
- package/src/core/rules/merged-branch-bash-guard.d.ts +12 -1
- package/src/core/rules/merged-branch-bash-guard.js +21 -6
- package/src/core/rules/merged-branch-bash-guard.js.map +1 -1
- package/src/core/rules/read-stale-guard.d.ts +11 -0
- package/src/core/rules/read-stale-guard.js +25 -10
- package/src/core/rules/read-stale-guard.js.map +1 -1
- package/src/core/rules/stale-main-bash-guard.d.ts +11 -0
- package/src/core/rules/stale-main-bash-guard.js +22 -7
- package/src/core/rules/stale-main-bash-guard.js.map +1 -1
- package/src/core/rules/whole-repo-build-guard.d.ts +88 -0
- package/src/core/rules/whole-repo-build-guard.js +176 -0
- package/src/core/rules/whole-repo-build-guard.js.map +1 -0
- package/src/core/rules/whole-repo-build-scan.d.ts +35 -0
- package/src/core/rules/whole-repo-build-scan.js +216 -0
- package/src/core/rules/whole-repo-build-scan.js.map +1 -0
- package/src/core/runner.js +34 -15
- package/src/core/runner.js.map +1 -1
- package/src/core/sync-main.d.ts +1 -1
- package/src/core/sync-main.js +2 -2
- package/src/core/sync-main.js.map +1 -1
- package/templates/ai-hook.sh +11 -8
- package/templates/guarantee-root.sh +10 -7
package/src/core/l1-rows.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"l1-rows.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/l1-rows.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TreeKind } from './effective-tree';\n\n// ---------------------------------------------------------------------------\n// L1 — the LOCATION layer, as data.\n//\n// L1 answers three questions: do we govern this call at all, is the WRONG AGENT standing here, and is\n// the agent stranded away from the root? Drawn as a decision matrix that is SIX ordered rows over five\n// dimensions (K/A/R/G/P), first match wins.\n//\n// This module holds those rows, and l1-doc.ts renders them into guards/L1-location.md — the doc a human\n// reads on GitHub. A unit test locks that file byte-identical to the renderer, and the guard itself\n// CONSULTS L1_ROWS to decide which structural block fires (see runner.l1LocationBlock). Doc and code\n// come from the SAME array, so they cannot drift.\n//\n// This module is deliberately import-free at runtime (only a type-only import above), so\n// `pnpm guards:generate` can load it without the package's transitive dependencies.\n// ---------------------------------------------------------------------------\n\n/**\n * The K dimension as a CLASSIFICATION carries — one concrete tree, never the `pw` union.\n *\n * `p` and `w` are the same PROJECT and every rule-scoped guard treats them alike; the doc writes the\n * pair as `pw` in the row's MATCHER (below), which is a different vocabulary on purpose.\n */\nexport type L1Kind = 'f' | 'o' | 'p' | 'w';\n\n/** The K value a ROW matches on. `pw` matches both `p` and `w`; `-` is the wildcard. */\nexport type L1KindMatch = 'f' | 'o' | 'w' | 'pw' | '-';\n\n/** R and G are yes/no, written `y`/`n` in the doc, with `-` for \"does not matter\". */\nexport type L1Flag = 'y' | 'n' | '-';\n\n/** A is the same one boolean wearing the doc's own letters: `c` the coordinator, `s` a subagent. */\nexport type L1Agent = 'c' | 's' | '-';\n\n/** What L1 does with a row. The labels are the doc's own action codebook (see GUARD_MATRIX.md). */\nexport type L1ActionKind = 'exempt' | 'down' | 'block';\n\n/**\n * WHICH structural block a blocking row dispatches to. This is the field that makes the array\n * load-bearing rather than decorative: runner.l1LocationBlock looks the row up and switches on it,\n * so deleting a row from the array removes the block.\n */\nexport type L1BlockId = 'coordinator-in-worktree' | 'force-to-root';\n\n/**\n * One point in the five-dimensional space L1 classifies over. Data-only → a class, per CLAUDE.md.\n *\n * The dimensions are exactly the doc's legend: K (tree kind of the resolved target), A (coordinator or\n * subagent), R (provably read-only inspection), G (invokes git/gh), P (root or subdirectory).\n */\nexport class L1Classification {\n // eslint-disable-next-line @typescript-eslint/max-params -- five dimensions is the matrix's shape\n constructor(\n readonly kind: L1Kind,\n readonly coordinator: boolean,\n readonly readOnly: boolean,\n readonly git: boolean,\n readonly atRoot: boolean,\n ) {}\n\n /**\n * The classification the RUNNER enforces on, built from the resolved tree and the caller.\n *\n * `'outside'` maps to `p`, and that is not a typo. TreeKind `'outside'` is produced by\n * effective-tree.ts (`gitRoot === null`) and consumed NOWHERE, so a command in no git repo is\n * judged against the governed repo exactly as if it stood in it. Row 2 (`o` → L2) describes what\n * SHOULD happen and is deliberately unreachable from here until the \"Not done\" fix in\n * guards/L1-location.md lands — exempting `o` alone opens a `cd /tmp &&` bypass of every L2 guard,\n * so the two ship together or neither does. Mapping it to `p` here is what preserves today's\n * behaviour (a `git` command from /tmp is still force-to-root blocked); it is not an endorsement.\n */\n // eslint-disable-next-line @typescript-eslint/max-params -- mirrors the constructor it delegates to\n // webpieces-disable no-function-outside-class -- a named constructor for this data class, not a service: it takes the runner's TreeKind and returns the same class, so there is nothing to inject\n static forEnforcement(\n treeKind: TreeKind,\n coordinator: boolean,\n readOnly: boolean,\n git: boolean,\n atRoot: boolean,\n ): L1Classification {\n const kind: L1Kind = treeKind === 'foreign' ? 'f' : treeKind === 'worktree' ? 'w' : 'p';\n return new L1Classification(kind, coordinator, readOnly, git, atRoot);\n }\n}\n\n/** The `act` cell of a row: the doc's literal label, plus the machine-readable kind behind it. */\nexport class L1Action {\n constructor(readonly label: string, readonly kind: L1ActionKind) {}\n}\n\nexport const ACT_EXEMPT = new L1Action('2 exempt', 'exempt');\nexport const ACT_DOWN = new L1Action('→ L2', 'down');\nexport const ACT_BLOCK = new L1Action('4 block', 'block');\n\n/**\n * The CURE a blocking row prescribes.\n *\n * `runnable` is the axis that matters to the tests: a cure that is a command must, once applied,\n * actually stop the row from matching (cure reachability). Row 3's cure is an INSTRUCTION — \"spawn a\n * subagent bound to the worktree\" — which no allowlist can accept and no reclassification can model,\n * so it declares `runnable: false` and is asserted only on the deny text.\n */\nexport class L1Cure {\n constructor(\n /** How the doc's `why` column spells it. */\n readonly summary: string,\n /** A substring that MUST appear in the deny text the guard emits for this row. */\n readonly denyMention: string,\n readonly runnable: boolean,\n ) {}\n}\n\n/**\n * One row of the \"L1 use cases\" table: what you SEE, the state it puts you in, the verdict, the fix.\n *\n * The four text fields are rendered VERBATIM into the doc. `classification` is the same case expressed\n * in the matrix's own vocabulary so the tests can run it through the matcher — it is test/enforcement\n * data, never rendered, which is why a use case that exercises the FILTER or the L0 allowlist (neither\n * of which is a row) can carry `null` there.\n */\nexport class L1UseCase {\n // eslint-disable-next-line @typescript-eslint/max-params -- four verbatim doc cells plus the classification behind them\n constructor(\n readonly num: number,\n readonly symptom: string,\n readonly state: string,\n readonly verdict: string,\n readonly fix: string,\n readonly classification: L1Classification | null = null,\n ) {}\n}\n\n/** One row of L1's decision table. Data-only → a class, per CLAUDE.md. */\nexport class L1Row {\n // eslint-disable-next-line @typescript-eslint/max-params -- five dimension cells plus act/why/cure/blockId/useCases\n constructor(\n readonly num: number,\n readonly k: L1KindMatch,\n readonly a: L1Agent,\n readonly r: L1Flag,\n readonly g: L1Flag,\n readonly p: 'root' | 'sub' | '-',\n readonly action: L1Action,\n /** The `why` cell, verbatim. */\n readonly why: string,\n readonly cure: L1Cure | null,\n readonly blockId: L1BlockId | null,\n readonly useCases: readonly L1UseCase[],\n ) {}\n\n matches(c: L1Classification): boolean {\n if (!this.kindMatches(c.kind)) return false;\n if (this.a !== '-' && (this.a === 'c') !== c.coordinator) return false;\n if (!flagMatches(this.r, c.readOnly)) return false;\n if (!flagMatches(this.g, c.git)) return false;\n return this.p === '-' || this.p === (c.atRoot ? 'root' : 'sub');\n }\n\n private kindMatches(kind: L1Kind): boolean {\n if (this.k === '-') return true;\n if (this.k === 'pw') return kind === 'p' || kind === 'w';\n return this.k === kind;\n }\n}\n\n// R and G are one boolean each behind a `y`/`n`/`-` cell, so one helper answers for both. (A is the\n// same shape but spelled `c`/`s`, and is matched inline above so the row literals read like the doc.)\n// webpieces-disable no-function-outside-class -- pure predicate for L1Row.matches above, in this data module\nfunction flagMatches(cell: L1Flag, value: boolean): boolean {\n if (cell === '-') return true;\n return (cell === 'y') === value;\n}\n\n/**\n * THE six L1 rows, in first-match-wins order.\n *\n * Rows 3 and 5 are the two structural blocks and they run as ONE step (runner.l1LocationBlock) so they\n * can never be reordered by accident. Every other row is a hand-down or an exemption, i.e. \"L1 has no\n * objection\" — which is why only rows 3 and 5 carry a blockId.\n */\nexport const L1_ROWS: readonly L1Row[] = [\n new L1Row(1, 'f', '-', '-', '-', '-', ACT_EXEMPT, 'different git repo — hands off', null, null, [\n new L1UseCase(1,\n '`cd repositories/vendored && git commit` goes through untouched',\n '`f` / `y` / - — row 1',\n 'ALLOW_EXEMPT',\n 'none needed — jurisdiction is judged on the RESOLVED target, after the `cd`; a different git repo is hands-off',\n new L1Classification('f', false, false, true, false)),\n ]),\n new L1Row(2, 'o', '-', '-', '-', '-', ACT_DOWN, 'see \"Not done\" below', null, null, []),\n new L1Row(3, 'w', 'c', 'n', '-', '-', ACT_BLOCK,\n 'the coordinator\\'s guards do not follow its `cd` — delegate to a subagent bound to the worktree',\n new L1Cure('delegate to a subagent bound to the worktree', 'Spawn a subagent bound to that worktree', false),\n 'coordinator-in-worktree', [\n new L1UseCase(12,\n 'you are the **coordinator**, you ran `git worktree add ../wt`, and `cd ../wt && pnpm build` is blocked',\n '`w` / `c` / `n` — row 3',\n 'BLOCK',\n 'Option 1 (preferred): spawn a subagent bound to `<worktree>` — the Agent tool with worktree isolation, or have the subagent call `EnterWorktree` with `path: <worktree>` (it accepts a worktree you already created)<br>Do NOT: re-type the command, or conclude the harness ate your `cd` — it did not; your GUARDS did not follow it',\n new L1Classification('w', true, false, false, false)),\n ]),\n new L1Row(4, 'pw', '-', '-', 'n', '-', ACT_DOWN, 'force-to-root has no jurisdiction', null, null, [\n new L1UseCase(5,\n '`ls` from `packages/http/` runs normally',\n '`pw` / `n` / - — row 4',\n '→ L2',\n 'none — force-to-root has no jurisdiction over non-git commands',\n new L1Classification('p', false, true, false, false)),\n new L1UseCase(6,\n '`pnpm test` from `packages/http/` runs normally',\n '`pw` / `n` / - — row 4',\n '→ L2',\n 'none — deliberately untouched, so package-local test runs stay natural',\n new L1Classification('p', false, false, false, false)),\n new L1UseCase(10,\n '`echo \"cd sub && git push\"` passes',\n '`pw` / `n` / `root` — row 4',\n '→ L2',\n 'none — the `cd` is inside quotes, so `ShellSegmentScan` never treats it as a scope escape',\n new L1Classification('p', false, false, false, true)),\n new L1UseCase(13,\n 'the same command from a **subagent** runs normally',\n '`w` / `s` — row 3 does not match',\n '→ L2',\n 'none — a subagent pinned to a worktree is the correct pattern',\n new L1Classification('w', false, false, false, false)),\n new L1UseCase(14,\n 'the coordinator\\'s `cd <worktree> && ls`/`cat`/`grep` still runs',\n '`w` / `c` / `y` — row 3 does not match',\n '→ L2',\n 'none — inspection is always open; so are the `Read` tool, `git -C <worktree> …` and `git show <branch>:<file>`, none of which move you',\n new L1Classification('w', true, true, false, false)),\n ]),\n new L1Row(5, 'pw', '-', '-', 'y', 'sub', ACT_BLOCK, '`cd <root> && <original>`',\n new L1Cure('`cd <root> && <original>`', 'Run git/gh commands from the repo root', true),\n 'force-to-root', [\n new L1UseCase(7,\n '`git status` from `packages/http/` is blocked',\n '`pw` / `y` / `sub` — row 5',\n 'BLOCK',\n 'Option 1 (preferred): `cd <root> && git status`',\n new L1Classification('p', false, false, true, false)),\n new L1UseCase(8,\n '`cd packages/http && git status` **typed from the root** is blocked',\n '`pw` / `y` / `sub` — row 5',\n 'BLOCK',\n 'Option 1 (preferred): `cd <root> && git status`<br>Do NOT: assume it is allowed because you started at the root — the predicate is `effectiveCwd === root`, i.e. the DESTINATION',\n new L1Classification('p', false, false, true, false)),\n new L1UseCase(11,\n '`cd <subdir> && git push` blocked with the force-to-root message, NOT the gated-flow one',\n '`pw` / `y` / `sub` — row 5; force-to-root runs first',\n 'BLOCK',\n 'Option 1 (preferred): `cd <root> && git push`, which then gets the push guard\\'s real answer ← costs one extra turn by design; still blocked',\n new L1Classification('p', false, false, true, false)),\n ]),\n new L1Row(6, 'pw', '-', '-', 'y', 'root', ACT_DOWN, '', null, null, [\n new L1UseCase(9,\n '`cd <root> && git status` passes from anywhere',\n '`pw` / `y` / `root` — row 6',\n '→ L2',\n 'none — this IS the prescribed cure',\n new L1Classification('p', false, false, true, true)),\n ]),\n];\n\n/**\n * The use cases that exercise something that is NOT a row: the excludePaths FILTER (2, 3, 4) and the L0\n * allowlist that runs ahead of L1 (15).\n *\n * They are use cases of L1 all the same — \"exempt\" is what emerges when the filter empties the rule\n * list, and case 15 is the invariant that a cure stays reachable from every tree — so they stay in the\n * doc's one numbered table. They carry no classification because no row classifies them.\n */\nexport const L1_UNROWED_USE_CASES: readonly L1UseCase[] = [\n new L1UseCase(2,\n 'Edit `repositories/vendored/foo.ts` allowed even on stale main',\n 'filter — the path is in `excludePaths`',\n 'ALLOW_EXEMPT',\n 'none needed'),\n new L1UseCase(3,\n 'Edit `packages/http/foo.ts` blocked on stale main',\n 'filter keeps the rules → L2 fires',\n 'BLOCK (at L2)',\n 'that is L2\\'s write-on-main verdict, not L1\\'s — follow the L2 message'),\n new L1UseCase(4,\n 'Edit `packages/http/foo.ts` judged even though the shell is in `/tmp`',\n 'filter, on the TARGET path',\n '→ L2',\n 'none — for file tools the cwd is irrelevant; do NOT `cd` anywhere to \"fix\" it'),\n new L1UseCase(15,\n 'the coordinator\\'s `cd <worktree> && pnpm install` still runs while row 3 is live',\n 'L0 allowlist, ahead of L1',\n 'ALLOW',\n 'none — a cure must stay reachable from every tree'),\n];\n\n/** Every use case, in the doc's numbering — the order the table is rendered and read in. */\n// webpieces-disable no-function-outside-class -- pure accessor over the two arrays above, beside them in this data module\nexport function allL1UseCases(): readonly L1UseCase[] {\n const all = [...L1_ROWS.flatMap((row: L1Row): readonly L1UseCase[] => row.useCases), ...L1_UNROWED_USE_CASES];\n return all.sort((a: L1UseCase, b: L1UseCase): number => a.num - b.num);\n}\n\n/**\n * FIRST MATCH WINS — the one lookup the guard and the tests share.\n *\n * Never null: rows 1, 2 and 4/5/6 between them cover every kind, and rows 4/5/6 partition G × P, so a\n * classification that matched nothing would be a hole in the matrix. The totality test asserts exactly\n * that, which is why this returns L1Row rather than L1Row | null.\n */\n// webpieces-disable no-function-outside-class -- the matcher over L1_ROWS, beside the array it reads\nexport function firstMatchingL1Row(c: L1Classification): L1Row {\n const row = L1_ROWS.find((r: L1Row): boolean => r.matches(c));\n if (row === undefined) throw new Error(`L1 matrix has a hole: no row matches ${JSON.stringify(c)}`);\n return row;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"l1-rows.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/l1-rows.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TreeKind } from './effective-tree';\n\n// ---------------------------------------------------------------------------\n// L1 — the LOCATION layer, as data.\n//\n// L1 answers three questions: do we govern this call at all, is the WRONG AGENT standing here, and is\n// the agent stranded away from the root? Drawn as a decision matrix that is SIX ordered rows over five\n// dimensions (K/A/R/G/P), first match wins.\n//\n// This module holds those rows, and l1-doc.ts renders them into guards/L1-location.md — the doc a human\n// reads on GitHub. A unit test locks that file byte-identical to the renderer, and the guard itself\n// CONSULTS L1_ROWS to decide which structural block fires (see runner.l1LocationBlock). Doc and code\n// come from the SAME array, so they cannot drift.\n//\n// This module is deliberately import-free at runtime (only a type-only import above), so\n// `pnpm guards:generate` can load it without the package's transitive dependencies.\n// ---------------------------------------------------------------------------\n\n/**\n * The K dimension as a CLASSIFICATION carries — one concrete tree, never the `pw` union.\n *\n * `p` and `w` are the same PROJECT and every rule-scoped guard treats them alike; the doc writes the\n * pair as `pw` in the row's MATCHER (below), which is a different vocabulary on purpose.\n */\nexport type L1Kind = 'f' | 'o' | 'p' | 'w';\n\n/** The K value a ROW matches on. `pw` matches both `p` and `w`; `-` is the wildcard. */\nexport type L1KindMatch = 'f' | 'o' | 'w' | 'pw' | '-';\n\n/** R and G are yes/no, written `y`/`n` in the doc, with `-` for \"does not matter\". */\nexport type L1Flag = 'y' | 'n' | '-';\n\n/** A is the same one boolean wearing the doc's own letters: `c` the coordinator, `s` a subagent. */\nexport type L1Agent = 'c' | 's' | '-';\n\n/** What L1 does with a row. The labels are the doc's own action codebook (see GUARD_MATRIX.md). */\nexport type L1ActionKind = 'exempt' | 'down' | 'block';\n\n/**\n * WHICH structural block a blocking row dispatches to. This is the field that makes the array\n * load-bearing rather than decorative: runner.l1LocationBlock looks the row up and switches on it,\n * so deleting a row from the array removes the block.\n */\nexport type L1BlockId = 'coordinator-in-worktree' | 'force-to-root';\n\n/**\n * The row number for L1's PRE-STAGE — `misplacedCdBlock`, which decides from command TEXT before a\n * tree has been resolved, and therefore cannot be classified over the five dimensions rows 1-6 use\n * (asking L1_ROWS to classify it would need the very resolution its answer determines).\n *\n * ZERO rather than a seventh row, deliberately. It has to appear in the table — an L1 block the\n * generated doc did not describe is precisely the drift the table exists to prevent, and it was\n * carrying a `KNOWN GAP` comment saying so. But numbering it 7 would assert it sits in the same\n * first-match scan as the others, which is the one thing that is not true about it. Row 0 says\n * \"decided before the scan\" in the number itself. `renderL1Doc()` PRINTS this row above the six, so\n * `row=0` in the L1 log joins to a line the reader can actually find.\n */\nexport const L1_PRESTAGE_ROW = '0';\n\n/**\n * One point in the five-dimensional space L1 classifies over. Data-only → a class, per CLAUDE.md.\n *\n * The dimensions are exactly the doc's legend: K (tree kind of the resolved target), A (coordinator or\n * subagent), R (provably read-only inspection), G (invokes git/gh), P (root or subdirectory).\n */\nexport class L1Classification {\n // eslint-disable-next-line @typescript-eslint/max-params -- five dimensions is the matrix's shape\n constructor(\n readonly kind: L1Kind,\n readonly coordinator: boolean,\n readonly readOnly: boolean,\n readonly git: boolean,\n readonly atRoot: boolean,\n ) {}\n\n /**\n * The classification the RUNNER enforces on, built from the resolved tree and the caller.\n *\n * `'outside'` maps to `p`, and that is not a typo. TreeKind `'outside'` is produced by\n * effective-tree.ts (`gitRoot === null`) and consumed NOWHERE, so a command in no git repo is\n * judged against the governed repo exactly as if it stood in it. Row 2 (`o` → L2) describes what\n * SHOULD happen and is deliberately unreachable from here until the \"Not done\" fix in\n * guards/L1-location.md lands — exempting `o` alone opens a `cd /tmp &&` bypass of every L2 guard,\n * so the two ship together or neither does. Mapping it to `p` here is what preserves today's\n * behaviour (a `git` command from /tmp is still force-to-root blocked); it is not an endorsement.\n */\n // eslint-disable-next-line @typescript-eslint/max-params -- mirrors the constructor it delegates to\n // webpieces-disable no-function-outside-class -- a named constructor for this data class, not a service: it takes the runner's TreeKind and returns the same class, so there is nothing to inject\n static forEnforcement(\n treeKind: TreeKind,\n coordinator: boolean,\n readOnly: boolean,\n git: boolean,\n atRoot: boolean,\n ): L1Classification {\n const kind: L1Kind = treeKind === 'foreign' ? 'f' : treeKind === 'worktree' ? 'w' : 'p';\n return new L1Classification(kind, coordinator, readOnly, git, atRoot);\n }\n}\n\n/** The `act` cell of a row: the doc's literal label, plus the machine-readable kind behind it. */\nexport class L1Action {\n constructor(readonly label: string, readonly kind: L1ActionKind) {}\n}\n\nexport const ACT_EXEMPT = new L1Action('2 exempt', 'exempt');\nexport const ACT_DOWN = new L1Action('→ L2', 'down');\nexport const ACT_BLOCK = new L1Action('4 block', 'block');\n\n/**\n * The CURE a blocking row prescribes.\n *\n * `runnable` is the axis that matters to the tests: a cure that is a command must, once applied,\n * actually stop the row from matching (cure reachability). Row 3's cure is an INSTRUCTION — \"spawn a\n * subagent bound to the worktree\" — which no allowlist can accept and no reclassification can model,\n * so it declares `runnable: false` and is asserted only on the deny text.\n */\nexport class L1Cure {\n constructor(\n /** How the doc's `why` column spells it. */\n readonly summary: string,\n /** A substring that MUST appear in the deny text the guard emits for this row. */\n readonly denyMention: string,\n readonly runnable: boolean,\n ) {}\n}\n\n/**\n * One row of the \"L1 use cases\" table: what you SEE, the state it puts you in, the verdict, the fix.\n *\n * The four text fields are rendered VERBATIM into the doc. `classification` is the same case expressed\n * in the matrix's own vocabulary so the tests can run it through the matcher — it is test/enforcement\n * data, never rendered, which is why a use case that exercises the FILTER or the L0 allowlist (neither\n * of which is a row) can carry `null` there.\n */\nexport class L1UseCase {\n // eslint-disable-next-line @typescript-eslint/max-params -- four verbatim doc cells plus the classification behind them\n constructor(\n readonly num: number,\n readonly symptom: string,\n readonly state: string,\n readonly verdict: string,\n readonly fix: string,\n readonly classification: L1Classification | null = null,\n ) {}\n}\n\n/** One row of L1's decision table. Data-only → a class, per CLAUDE.md. */\nexport class L1Row {\n // eslint-disable-next-line @typescript-eslint/max-params -- five dimension cells plus act/why/cure/blockId/useCases\n constructor(\n readonly num: number,\n readonly k: L1KindMatch,\n readonly a: L1Agent,\n readonly r: L1Flag,\n readonly g: L1Flag,\n readonly p: 'root' | 'sub' | '-',\n readonly action: L1Action,\n /** The `why` cell, verbatim. */\n readonly why: string,\n readonly cure: L1Cure | null,\n readonly blockId: L1BlockId | null,\n readonly useCases: readonly L1UseCase[],\n ) {}\n\n matches(c: L1Classification): boolean {\n if (!this.kindMatches(c.kind)) return false;\n if (this.a !== '-' && (this.a === 'c') !== c.coordinator) return false;\n if (!flagMatches(this.r, c.readOnly)) return false;\n if (!flagMatches(this.g, c.git)) return false;\n return this.p === '-' || this.p === (c.atRoot ? 'root' : 'sub');\n }\n\n private kindMatches(kind: L1Kind): boolean {\n if (this.k === '-') return true;\n if (this.k === 'pw') return kind === 'p' || kind === 'w';\n return this.k === kind;\n }\n}\n\n// R and G are one boolean each behind a `y`/`n`/`-` cell, so one helper answers for both. (A is the\n// same shape but spelled `c`/`s`, and is matched inline above so the row literals read like the doc.)\n// webpieces-disable no-function-outside-class -- pure predicate for L1Row.matches above, in this data module\nfunction flagMatches(cell: L1Flag, value: boolean): boolean {\n if (cell === '-') return true;\n return (cell === 'y') === value;\n}\n\n/**\n * THE six L1 rows, in first-match-wins order.\n *\n * Rows 3 and 5 are the two structural blocks and they run as ONE step (runner.l1LocationBlock) so they\n * can never be reordered by accident. Every other row is a hand-down or an exemption, i.e. \"L1 has no\n * objection\" — which is why only rows 3 and 5 carry a blockId.\n */\nexport const L1_ROWS: readonly L1Row[] = [\n new L1Row(1, 'f', '-', '-', '-', '-', ACT_EXEMPT, 'different git repo — hands off', null, null, [\n new L1UseCase(1,\n '`cd repositories/vendored && git commit` goes through untouched',\n '`f` / `y` / - — row 1',\n 'ALLOW_EXEMPT',\n 'none needed — jurisdiction is judged on the RESOLVED target, after the `cd`; a different git repo is hands-off',\n new L1Classification('f', false, false, true, false)),\n ]),\n new L1Row(2, 'o', '-', '-', '-', '-', ACT_DOWN, 'see \"Not done\" below', null, null, []),\n new L1Row(3, 'w', 'c', 'n', '-', '-', ACT_BLOCK,\n 'the coordinator\\'s guards do not follow its `cd` — delegate to a subagent bound to the worktree',\n new L1Cure('delegate to a subagent bound to the worktree', 'Spawn a subagent bound to that worktree', false),\n 'coordinator-in-worktree', [\n new L1UseCase(12,\n 'you are the **coordinator**, you ran `git worktree add ../wt`, and `cd ../wt && pnpm build` is blocked',\n '`w` / `c` / `n` — row 3',\n 'BLOCK_AI_CURE',\n 'Option 1 (preferred): spawn a subagent bound to `<worktree>` — the Agent tool with worktree isolation, or have the subagent call `EnterWorktree` with `path: <worktree>` (it accepts a worktree you already created)<br>Do NOT: re-type the command, or conclude the harness ate your `cd` — it did not; your GUARDS did not follow it',\n new L1Classification('w', true, false, false, false)),\n ]),\n new L1Row(4, 'pw', '-', '-', 'n', '-', ACT_DOWN, 'force-to-root has no jurisdiction', null, null, [\n new L1UseCase(5,\n '`ls` from `packages/http/` runs normally',\n '`pw` / `n` / - — row 4',\n 'ALLOW (handed to L2)',\n 'none — force-to-root has no jurisdiction over non-git commands',\n new L1Classification('p', false, true, false, false)),\n new L1UseCase(6,\n '`pnpm test` from `packages/http/` runs normally',\n '`pw` / `n` / - — row 4',\n 'ALLOW (handed to L2)',\n 'none — deliberately untouched, so package-local test runs stay natural',\n new L1Classification('p', false, false, false, false)),\n new L1UseCase(10,\n '`echo \"cd sub && git push\"` passes',\n '`pw` / `n` / `root` — row 4',\n 'ALLOW (handed to L2)',\n 'none — the `cd` is inside quotes, so `ShellSegmentScan` never treats it as a scope escape',\n new L1Classification('p', false, false, false, true)),\n new L1UseCase(13,\n 'the same command from a **subagent** runs normally',\n '`w` / `s` — row 3 does not match',\n 'ALLOW (handed to L2)',\n 'none — a subagent pinned to a worktree is the correct pattern',\n new L1Classification('w', false, false, false, false)),\n new L1UseCase(14,\n 'the coordinator\\'s `cd <worktree> && ls`/`cat`/`grep` still runs',\n '`w` / `c` / `y` — row 3 does not match',\n 'ALLOW (handed to L2)',\n 'none — inspection is always open; so are the `Read` tool, `git -C <worktree> …` and `git show <branch>:<file>`, none of which move you',\n new L1Classification('w', true, true, false, false)),\n ]),\n new L1Row(5, 'pw', '-', '-', 'y', 'sub', ACT_BLOCK, '`cd <root> && <original>`',\n new L1Cure('`cd <root> && <original>`', 'Run git/gh commands from the repo root', true),\n 'force-to-root', [\n new L1UseCase(7,\n '`git status` from `packages/http/` is blocked',\n '`pw` / `y` / `sub` — row 5',\n 'BLOCK_AI_CURE',\n 'Option 1 (preferred): `cd <root> && git status`',\n new L1Classification('p', false, false, true, false)),\n new L1UseCase(8,\n '`cd packages/http && git status` **typed from the root** is blocked',\n '`pw` / `y` / `sub` — row 5',\n 'BLOCK_AI_CURE',\n 'Option 1 (preferred): `cd <root> && git status`<br>Do NOT: assume it is allowed because you started at the root — the predicate is `effectiveCwd === root`, i.e. the DESTINATION',\n new L1Classification('p', false, false, true, false)),\n new L1UseCase(11,\n '`cd <subdir> && git push` blocked with the force-to-root message, NOT the gated-flow one',\n '`pw` / `y` / `sub` — row 5; force-to-root runs first',\n 'BLOCK_AI_CURE',\n 'Option 1 (preferred): `cd <root> && git push`, which then gets the push guard\\'s real answer ← costs one extra turn by design; still blocked',\n new L1Classification('p', false, false, true, false)),\n ]),\n new L1Row(6, 'pw', '-', '-', 'y', 'root', ACT_DOWN, '', null, null, [\n new L1UseCase(9,\n '`cd <root> && git status` passes from anywhere',\n '`pw` / `y` / `root` — row 6',\n 'ALLOW (handed to L2)',\n 'none — this IS the prescribed cure',\n new L1Classification('p', false, false, true, true)),\n ]),\n];\n\n/**\n * The use cases that exercise something that is NOT a row: the excludePaths FILTER (2, 3, 4) and the L0\n * allowlist that runs ahead of L1 (15).\n *\n * They are use cases of L1 all the same — \"exempt\" is what emerges when the filter empties the rule\n * list, and case 15 is the invariant that a cure stays reachable from every tree — so they stay in the\n * doc's one numbered table. They carry no classification because no row classifies them.\n */\nexport const L1_UNROWED_USE_CASES: readonly L1UseCase[] = [\n new L1UseCase(2,\n 'Edit `repositories/vendored/foo.ts` allowed even on stale main',\n 'filter — the path is in `excludePaths`',\n 'ALLOW_EXEMPT',\n 'none needed'),\n new L1UseCase(3,\n 'Edit `packages/http/foo.ts` blocked on stale main',\n 'filter keeps the rules → L2 fires',\n 'BLOCK (at L2)',\n 'that is L2\\'s write-on-main verdict, not L1\\'s — follow the L2 message'),\n new L1UseCase(4,\n 'Edit `packages/http/foo.ts` judged even though the shell is in `/tmp`',\n 'filter, on the TARGET path',\n '→ L2',\n 'none — for file tools the cwd is irrelevant; do NOT `cd` anywhere to \"fix\" it'),\n new L1UseCase(15,\n 'the coordinator\\'s `cd <worktree> && pnpm install` still runs while row 3 is live',\n 'L0 allowlist, ahead of L1',\n 'ALLOW',\n 'none — a cure must stay reachable from every tree'),\n];\n\n/** Every use case, in the doc's numbering — the order the table is rendered and read in. */\n// webpieces-disable no-function-outside-class -- pure accessor over the two arrays above, beside them in this data module\nexport function allL1UseCases(): readonly L1UseCase[] {\n const all = [...L1_ROWS.flatMap((row: L1Row): readonly L1UseCase[] => row.useCases), ...L1_UNROWED_USE_CASES];\n return all.sort((a: L1UseCase, b: L1UseCase): number => a.num - b.num);\n}\n\n/**\n * FIRST MATCH WINS — the one lookup the guard and the tests share.\n *\n * Never null: rows 1, 2 and 4/5/6 between them cover every kind, and rows 4/5/6 partition G × P, so a\n * classification that matched nothing would be a hole in the matrix. The totality test asserts exactly\n * that, which is why this returns L1Row rather than L1Row | null.\n */\n// webpieces-disable no-function-outside-class -- the matcher over L1_ROWS, beside the array it reads\nexport function firstMatchingL1Row(c: L1Classification): L1Row {\n const row = L1_ROWS.find((r: L1Row): boolean => r.matches(c));\n if (row === undefined) throw new Error(`L1 matrix has a hole: no row matches ${JSON.stringify(c)}`);\n return row;\n}\n"]}
|
package/src/core/load-rules.js
CHANGED
|
@@ -33,6 +33,7 @@ const feature_branch_guard_1 = require("./rules/feature-branch-guard");
|
|
|
33
33
|
const read_stale_guard_1 = require("./rules/read-stale-guard");
|
|
34
34
|
const merged_branch_bash_guard_1 = require("./rules/merged-branch-bash-guard");
|
|
35
35
|
const stale_main_bash_guard_1 = require("./rules/stale-main-bash-guard");
|
|
36
|
+
const whole_repo_build_guard_1 = require("./rules/whole-repo-build-guard");
|
|
36
37
|
const match_rule_1 = require("./rules/match-rule");
|
|
37
38
|
const REQUIRED_FIELDS = ['name', 'description', 'scope', 'files', 'check'];
|
|
38
39
|
const VALID_SCOPES = new Set(['edit', 'file', 'bash']);
|
|
@@ -59,6 +60,7 @@ const BUILT_IN_RULE_MAP = {
|
|
|
59
60
|
'read-stale-guard': (c) => new read_stale_guard_1.ReadStaleGuardRule(c),
|
|
60
61
|
'merged-branch-bash-guard': (c) => new merged_branch_bash_guard_1.MergedBranchBashGuardRule(c),
|
|
61
62
|
'stale-main-bash-guard': (c) => new stale_main_bash_guard_1.StaleMainBashGuardRule(c),
|
|
63
|
+
'whole-repo-build-guard': (c) => new whole_repo_build_guard_1.WholeRepoBuildGuardRule(c),
|
|
62
64
|
};
|
|
63
65
|
// Index the typed config by rule name. Each value is the rule's *Config (a plain object from
|
|
64
66
|
// JSON), or undefined when the rule has no entry yet (the sync check reports those).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-rules.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport {\n BaseRuleConfig, RuleOptions, WebpiecesRulesConfig,\n NoAnyUnknownConfig, NoImplicitAnyConfig, MaxFileLinesConfig, ValidateTsInSrcConfig,\n NoDestructureConfig, RequireReturnTypeConfig, NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig, ThrowCauseRequiredConfig,\n NoSymbolDiTokensConfig, NoCustomCssConfig, NoProcessExitOutsideMainConfig, BranchCreationGuardConfig, PrCreationOrPushGuardConfig,\n MergeInProgressGuardConfig, PrMergeGuardConfig, RedirectHowToMergeMainConfig,\n NoJsFilesConfig, FeatureBranchGuardConfig, ReadStaleGuardConfig, MergedBranchBashGuardConfig, StaleMainBashGuardConfig, MatchRuleConfig,\n} from '@webpieces/rules-config';\n\nimport type { Rule, PlainRule } from './types';\nimport { InformAiError } from './types';\nimport { toError } from './to-error';\nimport { EmptyRuleConfig } from './rule-base';\nimport { CustomRuleAdapter } from './custom-rule-adapter';\nimport { builtInRuleNames } from './rules/index';\nimport { NoAnyUnknownRule } from './rules/no-any-unknown';\nimport { NoImplicitAnyRule } from './rules/no-implicit-any';\nimport { MaxFileLinesRule } from './rules/max-file-lines';\nimport { ValidateTsInSrcRule } from './rules/validate-ts-in-src';\nimport { NoDestructureRule } from './rules/no-destructure';\nimport { RequireReturnTypeRule } from './rules/require-return-type';\nimport { NoUnmanagedExceptionsRule } from './rules/no-unmanaged-exceptions';\nimport { CatchErrorPatternRule } from './rules/catch-error-pattern';\nimport { ThrowCauseRequiredRule } from './rules/throw-cause-required';\nimport { NoSymbolDiTokensRule } from './rules/no-symbol-di-tokens';\nimport { NoCustomCssRule } from './rules/no-custom-css';\nimport { NoProcessExitOutsideMainRule } from './rules/no-process-exit-outside-main';\nimport { BranchCreationGuardRule } from './rules/branch-creation-guard';\nimport { PrCreationOrPushGuardRule } from './rules/pr-creation-or-push-guard';\nimport { MergeInProgressGuardRule } from './rules/merge-in-progress-guard';\nimport { PrMergeGuardRule } from './rules/pr-merge-guard';\nimport { RedirectHowToMergeMainRule } from './rules/redirect-how-to-merge-main';\nimport { NoJsFilesRule } from './rules/no-js-files';\nimport { FeatureBranchGuardRule } from './rules/feature-branch-guard';\nimport { ReadStaleGuardRule } from './rules/read-stale-guard';\nimport { MergedBranchBashGuardRule } from './rules/merged-branch-bash-guard';\nimport { StaleMainBashGuardRule } from './rules/stale-main-bash-guard';\nimport { MatchRule } from './rules/match-rule';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\n// Each built-in rule is constructed from its typed *Config (the entry in webpieces.config.json).\n// The config arrives as a plain object structurally typed as the *Config class, so the `as`\n// narrows the shared BaseRuleConfig param back to the concrete config the rule consumes.\ntype RuleFactory = (config: BaseRuleConfig) => Rule;\n\nconst BUILT_IN_RULE_MAP: Record<string, RuleFactory> = {\n 'no-any-unknown': (c: BaseRuleConfig) => new NoAnyUnknownRule(c as NoAnyUnknownConfig),\n 'no-implicit-any': (c: BaseRuleConfig) => new NoImplicitAnyRule(c as NoImplicitAnyConfig),\n 'max-file-lines': (c: BaseRuleConfig) => new MaxFileLinesRule(c as MaxFileLinesConfig),\n 'validate-ts-in-src': (c: BaseRuleConfig) => new ValidateTsInSrcRule(c as ValidateTsInSrcConfig),\n 'no-destructure': (c: BaseRuleConfig) => new NoDestructureRule(c as NoDestructureConfig),\n 'require-return-type': (c: BaseRuleConfig) => new RequireReturnTypeRule(c as RequireReturnTypeConfig),\n 'no-unmanaged-exceptions': (c: BaseRuleConfig) => new NoUnmanagedExceptionsRule(c as NoUnmanagedExceptionsConfig),\n 'catch-error-pattern': (c: BaseRuleConfig) => new CatchErrorPatternRule(c as CatchErrorPatternConfig),\n 'throw-cause-required': (c: BaseRuleConfig) => new ThrowCauseRequiredRule(c as ThrowCauseRequiredConfig),\n 'no-symbol-di-tokens': (c: BaseRuleConfig) => new NoSymbolDiTokensRule(c as NoSymbolDiTokensConfig),\n 'no-custom-css': (c: BaseRuleConfig) => new NoCustomCssRule(c as NoCustomCssConfig),\n 'no-process-exit-outside-main': (c: BaseRuleConfig) => new NoProcessExitOutsideMainRule(c as NoProcessExitOutsideMainConfig),\n 'branch-creation-guard': (c: BaseRuleConfig) => new BranchCreationGuardRule(c as BranchCreationGuardConfig),\n 'pr-creation-or-push-guard': (c: BaseRuleConfig) => new PrCreationOrPushGuardRule(c as PrCreationOrPushGuardConfig),\n 'merge-in-progress-guard': (c: BaseRuleConfig) => new MergeInProgressGuardRule(c as MergeInProgressGuardConfig),\n 'pr-merge-guard': (c: BaseRuleConfig) => new PrMergeGuardRule(c as PrMergeGuardConfig),\n 'redirect-how-to-merge-main': (c: BaseRuleConfig) => new RedirectHowToMergeMainRule(c as RedirectHowToMergeMainConfig),\n 'no-js-files': (c: BaseRuleConfig) => new NoJsFilesRule(c as NoJsFilesConfig),\n 'feature-branch-guard': (c: BaseRuleConfig) => new FeatureBranchGuardRule(c as FeatureBranchGuardConfig),\n 'read-stale-guard': (c: BaseRuleConfig) => new ReadStaleGuardRule(c as ReadStaleGuardConfig),\n 'merged-branch-bash-guard': (c: BaseRuleConfig) => new MergedBranchBashGuardRule(c as MergedBranchBashGuardConfig),\n 'stale-main-bash-guard': (c: BaseRuleConfig) => new StaleMainBashGuardRule(c as StaleMainBashGuardConfig),\n};\n\n// Index the typed config by rule name. Each value is the rule's *Config (a plain object from\n// JSON), or undefined when the rule has no entry yet (the sync check reports those).\nfunction asConfigMap(config: WebpiecesRulesConfig): Record<string, BaseRuleConfig | undefined> {\n // webpieces-disable no-any-unknown -- index the typed config by dynamic rule name\n return config as unknown as Record<string, BaseRuleConfig | undefined>;\n}\n\nexport function loadRules(config: WebpiecesRulesConfig, workspaceRoot: string): readonly Rule[] {\n const builtIns = loadBuiltInRules(config);\n const custom = loadCustomRules(config, workspaceRoot);\n return [...builtIns, ...custom];\n}\n\n// One MatchRule per entry of the `match-rules` array. Kept separate from loadRules (built-ins/custom)\n// because match-rules live in their own validated section — they must NOT flow through the\n// config-sync check, which compares rule names against the `rules`/`hookGuards` map.\nexport function loadMatchRules(matchRules: readonly MatchRuleConfig[]): Rule[] {\n return matchRules.map((c: MatchRuleConfig) => new MatchRule(c));\n}\n\nfunction loadBuiltInRules(config: WebpiecesRulesConfig): Rule[] {\n const map = asConfigMap(config);\n const rules: Rule[] = [];\n for (const name of builtInRuleNames) {\n const factory = BUILT_IN_RULE_MAP[name];\n if (!factory) {\n process.stderr.write(`[ai-hooks] unknown built-in rule: ${name}\\n`);\n continue;\n }\n const ruleConfig = map[name] ?? new EmptyRuleConfig();\n rules.push(factory(ruleConfig));\n }\n return rules;\n}\n\nfunction loadCustomRules(config: WebpiecesRulesConfig, workspaceRoot: string): Rule[] {\n const dirs = config.rulesDir ?? [];\n // webpieces-disable no-any-unknown -- index the typed config by dynamic custom-rule name\n const map = config as unknown as Record<string, RuleOptions | undefined>;\n const rules: Rule[] = [];\n for (const plain of loadCustomPlainRules(dirs, workspaceRoot)) {\n const rawConfig = map[plain.name] ?? {};\n rules.push(new CustomRuleAdapter(plain, rawConfig));\n }\n return rules;\n}\n\nfunction loadCustomPlainRules(rulesDirs: readonly string[], workspaceRoot: string): PlainRule[] {\n const modules: PlainRule[] = [];\n for (const dir of rulesDirs) {\n const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);\n if (!fs.existsSync(absDir)) {\n process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\\n`);\n continue;\n }\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(absDir).filter((e: string) => e.endsWith('.js'));\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot read custom rules directory '${absDir}'`, { cause: error });\n }\n for (const entry of entries) {\n const full = path.join(absDir, entry);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(full);\n const candidate = mod.default || mod;\n if (validateRule(candidate)) modules.push(candidate);\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot load custom rule '${full}'`, { cause: error });\n }\n }\n }\n return modules;\n}\n\n// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary\nfunction validateRule(rule: unknown): rule is PlainRule {\n if (!rule || typeof rule !== 'object') {\n process.stderr.write('[ai-hooks] rule is not an object, skipping\\n');\n return false;\n }\n // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary\n const obj = rule as Record<string, unknown>;\n for (const field of REQUIRED_FIELDS) {\n if (obj[field] === undefined) {\n const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';\n process.stderr.write(`[ai-hooks] rule \"${name}\" missing required field: ${field}\\n`);\n return false;\n }\n }\n if (!VALID_SCOPES.has(obj['scope'] as string)) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" has invalid scope: ${String(obj['scope'])}\\n`);\n return false;\n }\n if (!Array.isArray(obj['files'])) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" files must be an array\\n`);\n return false;\n }\n if (typeof obj['check'] !== 'function') {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" check must be a function\\n`);\n return false;\n }\n return true;\n}\n\nexport function globMatches(pattern: string, filePath: string): boolean {\n const regex = globToRegex(pattern);\n return regex.test(filePath);\n}\n\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n"]}
|
|
1
|
+
{"version":3,"file":"load-rules.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/load-rules.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport {\n BaseRuleConfig, RuleOptions, WebpiecesRulesConfig,\n NoAnyUnknownConfig, NoImplicitAnyConfig, MaxFileLinesConfig, ValidateTsInSrcConfig,\n NoDestructureConfig, RequireReturnTypeConfig, NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig, ThrowCauseRequiredConfig,\n NoSymbolDiTokensConfig, NoCustomCssConfig, NoProcessExitOutsideMainConfig, BranchCreationGuardConfig, PrCreationOrPushGuardConfig,\n MergeInProgressGuardConfig, PrMergeGuardConfig, RedirectHowToMergeMainConfig,\n NoJsFilesConfig, FeatureBranchGuardConfig, ReadStaleGuardConfig, MergedBranchBashGuardConfig, StaleMainBashGuardConfig, WholeRepoBuildGuardConfig, MatchRuleConfig,\n} from '@webpieces/rules-config';\n\nimport type { Rule, PlainRule } from './types';\nimport { InformAiError } from './types';\nimport { toError } from './to-error';\nimport { EmptyRuleConfig } from './rule-base';\nimport { CustomRuleAdapter } from './custom-rule-adapter';\nimport { builtInRuleNames } from './rules/index';\nimport { NoAnyUnknownRule } from './rules/no-any-unknown';\nimport { NoImplicitAnyRule } from './rules/no-implicit-any';\nimport { MaxFileLinesRule } from './rules/max-file-lines';\nimport { ValidateTsInSrcRule } from './rules/validate-ts-in-src';\nimport { NoDestructureRule } from './rules/no-destructure';\nimport { RequireReturnTypeRule } from './rules/require-return-type';\nimport { NoUnmanagedExceptionsRule } from './rules/no-unmanaged-exceptions';\nimport { CatchErrorPatternRule } from './rules/catch-error-pattern';\nimport { ThrowCauseRequiredRule } from './rules/throw-cause-required';\nimport { NoSymbolDiTokensRule } from './rules/no-symbol-di-tokens';\nimport { NoCustomCssRule } from './rules/no-custom-css';\nimport { NoProcessExitOutsideMainRule } from './rules/no-process-exit-outside-main';\nimport { BranchCreationGuardRule } from './rules/branch-creation-guard';\nimport { PrCreationOrPushGuardRule } from './rules/pr-creation-or-push-guard';\nimport { MergeInProgressGuardRule } from './rules/merge-in-progress-guard';\nimport { PrMergeGuardRule } from './rules/pr-merge-guard';\nimport { RedirectHowToMergeMainRule } from './rules/redirect-how-to-merge-main';\nimport { NoJsFilesRule } from './rules/no-js-files';\nimport { FeatureBranchGuardRule } from './rules/feature-branch-guard';\nimport { ReadStaleGuardRule } from './rules/read-stale-guard';\nimport { MergedBranchBashGuardRule } from './rules/merged-branch-bash-guard';\nimport { StaleMainBashGuardRule } from './rules/stale-main-bash-guard';\nimport { WholeRepoBuildGuardRule } from './rules/whole-repo-build-guard';\nimport { MatchRule } from './rules/match-rule';\n\nconst REQUIRED_FIELDS: readonly string[] = ['name', 'description', 'scope', 'files', 'check'];\nconst VALID_SCOPES = new Set(['edit', 'file', 'bash']);\n\n// Each built-in rule is constructed from its typed *Config (the entry in webpieces.config.json).\n// The config arrives as a plain object structurally typed as the *Config class, so the `as`\n// narrows the shared BaseRuleConfig param back to the concrete config the rule consumes.\ntype RuleFactory = (config: BaseRuleConfig) => Rule;\n\nconst BUILT_IN_RULE_MAP: Record<string, RuleFactory> = {\n 'no-any-unknown': (c: BaseRuleConfig) => new NoAnyUnknownRule(c as NoAnyUnknownConfig),\n 'no-implicit-any': (c: BaseRuleConfig) => new NoImplicitAnyRule(c as NoImplicitAnyConfig),\n 'max-file-lines': (c: BaseRuleConfig) => new MaxFileLinesRule(c as MaxFileLinesConfig),\n 'validate-ts-in-src': (c: BaseRuleConfig) => new ValidateTsInSrcRule(c as ValidateTsInSrcConfig),\n 'no-destructure': (c: BaseRuleConfig) => new NoDestructureRule(c as NoDestructureConfig),\n 'require-return-type': (c: BaseRuleConfig) => new RequireReturnTypeRule(c as RequireReturnTypeConfig),\n 'no-unmanaged-exceptions': (c: BaseRuleConfig) => new NoUnmanagedExceptionsRule(c as NoUnmanagedExceptionsConfig),\n 'catch-error-pattern': (c: BaseRuleConfig) => new CatchErrorPatternRule(c as CatchErrorPatternConfig),\n 'throw-cause-required': (c: BaseRuleConfig) => new ThrowCauseRequiredRule(c as ThrowCauseRequiredConfig),\n 'no-symbol-di-tokens': (c: BaseRuleConfig) => new NoSymbolDiTokensRule(c as NoSymbolDiTokensConfig),\n 'no-custom-css': (c: BaseRuleConfig) => new NoCustomCssRule(c as NoCustomCssConfig),\n 'no-process-exit-outside-main': (c: BaseRuleConfig) => new NoProcessExitOutsideMainRule(c as NoProcessExitOutsideMainConfig),\n 'branch-creation-guard': (c: BaseRuleConfig) => new BranchCreationGuardRule(c as BranchCreationGuardConfig),\n 'pr-creation-or-push-guard': (c: BaseRuleConfig) => new PrCreationOrPushGuardRule(c as PrCreationOrPushGuardConfig),\n 'merge-in-progress-guard': (c: BaseRuleConfig) => new MergeInProgressGuardRule(c as MergeInProgressGuardConfig),\n 'pr-merge-guard': (c: BaseRuleConfig) => new PrMergeGuardRule(c as PrMergeGuardConfig),\n 'redirect-how-to-merge-main': (c: BaseRuleConfig) => new RedirectHowToMergeMainRule(c as RedirectHowToMergeMainConfig),\n 'no-js-files': (c: BaseRuleConfig) => new NoJsFilesRule(c as NoJsFilesConfig),\n 'feature-branch-guard': (c: BaseRuleConfig) => new FeatureBranchGuardRule(c as FeatureBranchGuardConfig),\n 'read-stale-guard': (c: BaseRuleConfig) => new ReadStaleGuardRule(c as ReadStaleGuardConfig),\n 'merged-branch-bash-guard': (c: BaseRuleConfig) => new MergedBranchBashGuardRule(c as MergedBranchBashGuardConfig),\n 'stale-main-bash-guard': (c: BaseRuleConfig) => new StaleMainBashGuardRule(c as StaleMainBashGuardConfig),\n 'whole-repo-build-guard': (c: BaseRuleConfig) => new WholeRepoBuildGuardRule(c as WholeRepoBuildGuardConfig),\n};\n\n// Index the typed config by rule name. Each value is the rule's *Config (a plain object from\n// JSON), or undefined when the rule has no entry yet (the sync check reports those).\nfunction asConfigMap(config: WebpiecesRulesConfig): Record<string, BaseRuleConfig | undefined> {\n // webpieces-disable no-any-unknown -- index the typed config by dynamic rule name\n return config as unknown as Record<string, BaseRuleConfig | undefined>;\n}\n\nexport function loadRules(config: WebpiecesRulesConfig, workspaceRoot: string): readonly Rule[] {\n const builtIns = loadBuiltInRules(config);\n const custom = loadCustomRules(config, workspaceRoot);\n return [...builtIns, ...custom];\n}\n\n// One MatchRule per entry of the `match-rules` array. Kept separate from loadRules (built-ins/custom)\n// because match-rules live in their own validated section — they must NOT flow through the\n// config-sync check, which compares rule names against the `rules`/`hookGuards` map.\nexport function loadMatchRules(matchRules: readonly MatchRuleConfig[]): Rule[] {\n return matchRules.map((c: MatchRuleConfig) => new MatchRule(c));\n}\n\nfunction loadBuiltInRules(config: WebpiecesRulesConfig): Rule[] {\n const map = asConfigMap(config);\n const rules: Rule[] = [];\n for (const name of builtInRuleNames) {\n const factory = BUILT_IN_RULE_MAP[name];\n if (!factory) {\n process.stderr.write(`[ai-hooks] unknown built-in rule: ${name}\\n`);\n continue;\n }\n const ruleConfig = map[name] ?? new EmptyRuleConfig();\n rules.push(factory(ruleConfig));\n }\n return rules;\n}\n\nfunction loadCustomRules(config: WebpiecesRulesConfig, workspaceRoot: string): Rule[] {\n const dirs = config.rulesDir ?? [];\n // webpieces-disable no-any-unknown -- index the typed config by dynamic custom-rule name\n const map = config as unknown as Record<string, RuleOptions | undefined>;\n const rules: Rule[] = [];\n for (const plain of loadCustomPlainRules(dirs, workspaceRoot)) {\n const rawConfig = map[plain.name] ?? {};\n rules.push(new CustomRuleAdapter(plain, rawConfig));\n }\n return rules;\n}\n\nfunction loadCustomPlainRules(rulesDirs: readonly string[], workspaceRoot: string): PlainRule[] {\n const modules: PlainRule[] = [];\n for (const dir of rulesDirs) {\n const absDir = path.isAbsolute(dir) ? dir : path.join(workspaceRoot, dir);\n if (!fs.existsSync(absDir)) {\n process.stderr.write(`[ai-hooks] rulesDir not found: ${absDir}\\n`);\n continue;\n }\n let entries: string[];\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n entries = fs.readdirSync(absDir).filter((e: string) => e.endsWith('.js'));\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot read custom rules directory '${absDir}'`, { cause: error });\n }\n for (const entry of entries) {\n const full = path.join(absDir, entry);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const mod = require(full);\n const candidate = mod.default || mod;\n if (validateRule(candidate)) modules.push(candidate);\n } catch (err: unknown) {\n const error = toError(err);\n throw new InformAiError(`Cannot load custom rule '${full}'`, { cause: error });\n }\n }\n }\n return modules;\n}\n\n// webpieces-disable no-any-unknown -- validates untrusted require() output at system boundary\nfunction validateRule(rule: unknown): rule is PlainRule {\n if (!rule || typeof rule !== 'object') {\n process.stderr.write('[ai-hooks] rule is not an object, skipping\\n');\n return false;\n }\n // webpieces-disable no-any-unknown -- narrowing from unknown at system boundary\n const obj = rule as Record<string, unknown>;\n for (const field of REQUIRED_FIELDS) {\n if (obj[field] === undefined) {\n const name = typeof obj['name'] === 'string' ? obj['name'] : '<unnamed>';\n process.stderr.write(`[ai-hooks] rule \"${name}\" missing required field: ${field}\\n`);\n return false;\n }\n }\n if (!VALID_SCOPES.has(obj['scope'] as string)) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" has invalid scope: ${String(obj['scope'])}\\n`);\n return false;\n }\n if (!Array.isArray(obj['files'])) {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" files must be an array\\n`);\n return false;\n }\n if (typeof obj['check'] !== 'function') {\n process.stderr.write(`[ai-hooks] rule \"${obj['name']}\" check must be a function\\n`);\n return false;\n }\n return true;\n}\n\nexport function globMatches(pattern: string, filePath: string): boolean {\n const regex = globToRegex(pattern);\n return regex.test(filePath);\n}\n\nfunction globToRegex(pattern: string): RegExp {\n let re = '';\n let i = 0;\n while (i < pattern.length) {\n const ch = pattern[i];\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n re += '.*';\n i += 2;\n if (pattern[i] === '/') i += 1;\n continue;\n }\n re += '[^/]*';\n i += 1;\n continue;\n }\n if (ch === '?') {\n re += '[^/]';\n i += 1;\n continue;\n }\n if ('.+^$(){}|[]\\\\'.includes(ch)) {\n re += '\\\\' + ch;\n i += 1;\n continue;\n }\n re += ch;\n i += 1;\n }\n return new RegExp('^' + re + '$');\n}\n"]}
|
package/src/core/log-stream.d.ts
CHANGED
|
@@ -30,23 +30,30 @@ export declare class StreamIdentity {
|
|
|
30
30
|
* is absent for every one of them — so agent identity alone cannot tell them apart.
|
|
31
31
|
*
|
|
32
32
|
* `O_APPEND` is indivisible only under `PIPE_BUF`, which is **512 bytes on macOS**. Measured
|
|
33
|
-
* 2026-08-06 across three repos:
|
|
34
|
-
*
|
|
33
|
+
* 2026-08-06 across three repos: the invocation stream 208/3306 lines (6.3%) exceed it, max 608 B;
|
|
34
|
+
* the decision stream 209/4097 (5.1%), max 625 B. So this tears TODAY, and the corrupted line
|
|
35
35
|
* is exactly the long one — the `recover=` line a human needs most.
|
|
36
36
|
*
|
|
37
|
-
* ─── The key:
|
|
38
|
-
* <local>/logs/<sessionId>-<agentId | "coordinator">-<hook
|
|
37
|
+
* ─── The key: the LAYER is the directory, the WRITER is the file ──────────────────────────────────
|
|
38
|
+
* <local>/logs/<stream>/<sessionId>-<agentId | "coordinator">-<hook>.log
|
|
39
39
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
* stream separates the LAYERS ('L-1-cd' | 'L0-shim' | 'L1-location' | …)
|
|
41
|
+
* sessionId separates concurrent Claude Code windows (`session_id`, on every hook payload)
|
|
42
|
+
* agentId separates subagents within one window (`agent_id`, subagent-only — absent = coordinator)
|
|
43
|
+
* hook separates the PARALLEL hooks ('guards' | 'rules' | 'guarantee-root')
|
|
43
44
|
*
|
|
44
|
-
* One writer per
|
|
45
|
+
* This class owns the FILE half. One writer per file, by construction, so appends cannot interleave
|
|
46
|
+
* and nothing needs a lock — and all three identity dimensions must stay in the filename for that to
|
|
47
|
+
* hold. `hook` especially: Claude Code runs the guards and rules hooks as separate processes IN
|
|
48
|
+
* PARALLEL on one tool call, so folding `hook` into the directory would put two concurrent appenders
|
|
49
|
+
* on one path, which is the tearing measured above.
|
|
45
50
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
51
|
+
* Nesting by STREAM is NOT the nesting this layout rejects. What it rejects is nesting by IDENTITY
|
|
52
|
+
* (`sessions/<id>/<agent>/…`), which turns every cross-session question into a directory walk. The
|
|
53
|
+
* layer is the one axis you almost always want to slice by first, and it was previously not
|
|
54
|
+
* expressible at all: L1 had no stream, so "show me every L1 decision" had no answer. Now
|
|
55
|
+
* `ls logs/L1-location/` is that answer, and a one-level wildcard recovers the flat view —
|
|
56
|
+
* `ls -t logs/[*]/<sid>-*` is still every layer at once, in time order.
|
|
50
57
|
*
|
|
51
58
|
* `transcript_path` is also unique per session, but it is a filesystem PATH — long, and full of
|
|
52
59
|
* separators that would have to be flattened anyway — and `session_id` is its stable identifier, so
|
|
@@ -57,7 +64,7 @@ export declare class StreamIdentity {
|
|
|
57
64
|
*
|
|
58
65
|
* ─── There is no un-split path ─────────────────────────────────────────────────────────────────────
|
|
59
66
|
* Every name is prefixed, always. A caller that never identifies renders as
|
|
60
|
-
* `unknown-coordinator-hook
|
|
67
|
+
* `unknown-coordinator-hook.log` — a distinct, greppable writer, NOT a shared file. Keeping a
|
|
61
68
|
* bare-name fallback would have meant two reachable spellings of one filename, with the tearing one
|
|
62
69
|
* reached by doing nothing; that is the widening-as-absence this whole class exists to remove, so it
|
|
63
70
|
* is not offered.
|
|
@@ -78,7 +85,7 @@ export declare class LogStream {
|
|
|
78
85
|
* The detached main-sync refresher (main-sync-refresh.ts → sync-main.ts) is a separate node
|
|
79
86
|
* process with a fresh, unidentified `logStream`. Before this existed the parent logged
|
|
80
87
|
* SPAWN_ATTEMPT to its own prefixed stream while the child logged START/FINISH/ERROR to the
|
|
81
|
-
* shared `unknown-coordinator-hook
|
|
88
|
+
* shared, unidentified `unknown-coordinator-hook` writer — so ONE refresh cycle was split across
|
|
82
89
|
* two files, every agent's child appended to that one shared file (the PIPE_BUF tearing this
|
|
83
90
|
* class exists to remove, still live on that stream), and the documented
|
|
84
91
|
* "SPAWN_ATTEMPT with no START means the child never launched" check read as a false failure on
|
|
@@ -86,12 +93,19 @@ export declare class LogStream {
|
|
|
86
93
|
*/
|
|
87
94
|
identity(): StreamIdentity;
|
|
88
95
|
/**
|
|
89
|
-
* This
|
|
96
|
+
* This WRITER's file name within a stream directory —
|
|
97
|
+
* `<sessionId>-<agentId|coordinator>-<hook><suffix>`, ALWAYS.
|
|
90
98
|
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
99
|
+
* The three identity dimensions stay in the FILE while the stream moves to the DIRECTORY
|
|
100
|
+
* (see log-streams.ts), because they are what makes one writer per file true and the stream is not:
|
|
101
|
+
* `wp-ai-guards-hook` and `wp-ai-rules-hook` are separate processes that Claude Code launches in
|
|
102
|
+
* PARALLEL on the same tool call, so dropping `hook` here would put two concurrent appenders on
|
|
103
|
+
* one path — the exact tearing this class exists to remove, reintroduced by a rename.
|
|
104
|
+
*
|
|
105
|
+
* `suffix` carries the extension so the rotation sibling gets the identical writer key: pass
|
|
106
|
+
* `.log`, and separately `.1.log`.
|
|
93
107
|
*/
|
|
94
|
-
|
|
108
|
+
writerFile(suffix: string): string;
|
|
95
109
|
}
|
|
96
110
|
/**
|
|
97
111
|
* Process-wide instance. The hook adapters identify it once at the top of the invocation and every
|
package/src/core/log-stream.js
CHANGED
|
@@ -38,23 +38,30 @@ exports.StreamIdentity = StreamIdentity;
|
|
|
38
38
|
* is absent for every one of them — so agent identity alone cannot tell them apart.
|
|
39
39
|
*
|
|
40
40
|
* `O_APPEND` is indivisible only under `PIPE_BUF`, which is **512 bytes on macOS**. Measured
|
|
41
|
-
* 2026-08-06 across three repos:
|
|
42
|
-
*
|
|
41
|
+
* 2026-08-06 across three repos: the invocation stream 208/3306 lines (6.3%) exceed it, max 608 B;
|
|
42
|
+
* the decision stream 209/4097 (5.1%), max 625 B. So this tears TODAY, and the corrupted line
|
|
43
43
|
* is exactly the long one — the `recover=` line a human needs most.
|
|
44
44
|
*
|
|
45
|
-
* ─── The key:
|
|
46
|
-
* <local>/logs/<sessionId>-<agentId | "coordinator">-<hook
|
|
45
|
+
* ─── The key: the LAYER is the directory, the WRITER is the file ──────────────────────────────────
|
|
46
|
+
* <local>/logs/<stream>/<sessionId>-<agentId | "coordinator">-<hook>.log
|
|
47
47
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* stream separates the LAYERS ('L-1-cd' | 'L0-shim' | 'L1-location' | …)
|
|
49
|
+
* sessionId separates concurrent Claude Code windows (`session_id`, on every hook payload)
|
|
50
|
+
* agentId separates subagents within one window (`agent_id`, subagent-only — absent = coordinator)
|
|
51
|
+
* hook separates the PARALLEL hooks ('guards' | 'rules' | 'guarantee-root')
|
|
51
52
|
*
|
|
52
|
-
* One writer per
|
|
53
|
+
* This class owns the FILE half. One writer per file, by construction, so appends cannot interleave
|
|
54
|
+
* and nothing needs a lock — and all three identity dimensions must stay in the filename for that to
|
|
55
|
+
* hold. `hook` especially: Claude Code runs the guards and rules hooks as separate processes IN
|
|
56
|
+
* PARALLEL on one tool call, so folding `hook` into the directory would put two concurrent appenders
|
|
57
|
+
* on one path, which is the tearing measured above.
|
|
53
58
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
59
|
+
* Nesting by STREAM is NOT the nesting this layout rejects. What it rejects is nesting by IDENTITY
|
|
60
|
+
* (`sessions/<id>/<agent>/…`), which turns every cross-session question into a directory walk. The
|
|
61
|
+
* layer is the one axis you almost always want to slice by first, and it was previously not
|
|
62
|
+
* expressible at all: L1 had no stream, so "show me every L1 decision" had no answer. Now
|
|
63
|
+
* `ls logs/L1-location/` is that answer, and a one-level wildcard recovers the flat view —
|
|
64
|
+
* `ls -t logs/[*]/<sid>-*` is still every layer at once, in time order.
|
|
58
65
|
*
|
|
59
66
|
* `transcript_path` is also unique per session, but it is a filesystem PATH — long, and full of
|
|
60
67
|
* separators that would have to be flattened anyway — and `session_id` is its stable identifier, so
|
|
@@ -65,7 +72,7 @@ exports.StreamIdentity = StreamIdentity;
|
|
|
65
72
|
*
|
|
66
73
|
* ─── There is no un-split path ─────────────────────────────────────────────────────────────────────
|
|
67
74
|
* Every name is prefixed, always. A caller that never identifies renders as
|
|
68
|
-
* `unknown-coordinator-hook
|
|
75
|
+
* `unknown-coordinator-hook.log` — a distinct, greppable writer, NOT a shared file. Keeping a
|
|
69
76
|
* bare-name fallback would have meant two reachable spellings of one filename, with the tearing one
|
|
70
77
|
* reached by doing nothing; that is the widening-as-absence this whole class exists to remove, so it
|
|
71
78
|
* is not offered.
|
|
@@ -95,7 +102,7 @@ class LogStream {
|
|
|
95
102
|
* The detached main-sync refresher (main-sync-refresh.ts → sync-main.ts) is a separate node
|
|
96
103
|
* process with a fresh, unidentified `logStream`. Before this existed the parent logged
|
|
97
104
|
* SPAWN_ATTEMPT to its own prefixed stream while the child logged START/FINISH/ERROR to the
|
|
98
|
-
* shared `unknown-coordinator-hook
|
|
105
|
+
* shared, unidentified `unknown-coordinator-hook` writer — so ONE refresh cycle was split across
|
|
99
106
|
* two files, every agent's child appended to that one shared file (the PIPE_BUF tearing this
|
|
100
107
|
* class exists to remove, still live on that stream), and the documented
|
|
101
108
|
* "SPAWN_ATTEMPT with no START means the child never launched" check read as a false failure on
|
|
@@ -105,14 +112,21 @@ class LogStream {
|
|
|
105
112
|
return new StreamIdentity(this.sessionId, this.agentId, this.hook);
|
|
106
113
|
}
|
|
107
114
|
/**
|
|
108
|
-
* This
|
|
115
|
+
* This WRITER's file name within a stream directory —
|
|
116
|
+
* `<sessionId>-<agentId|coordinator>-<hook><suffix>`, ALWAYS.
|
|
109
117
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
118
|
+
* The three identity dimensions stay in the FILE while the stream moves to the DIRECTORY
|
|
119
|
+
* (see log-streams.ts), because they are what makes one writer per file true and the stream is not:
|
|
120
|
+
* `wp-ai-guards-hook` and `wp-ai-rules-hook` are separate processes that Claude Code launches in
|
|
121
|
+
* PARALLEL on the same tool call, so dropping `hook` here would put two concurrent appenders on
|
|
122
|
+
* one path — the exact tearing this class exists to remove, reintroduced by a rename.
|
|
123
|
+
*
|
|
124
|
+
* `suffix` carries the extension so the rotation sibling gets the identical writer key: pass
|
|
125
|
+
* `.log`, and separately `.1.log`.
|
|
112
126
|
*/
|
|
113
|
-
|
|
127
|
+
writerFile(suffix) {
|
|
114
128
|
const agent = segment(this.agentId === '' ? 'coordinator' : this.agentId);
|
|
115
|
-
return `${segment(this.sessionId)}-${agent}-${segment(this.hook)}
|
|
129
|
+
return `${segment(this.sessionId)}-${agent}-${segment(this.hook)}${suffix}`;
|
|
116
130
|
}
|
|
117
131
|
}
|
|
118
132
|
exports.LogStream = LogStream;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log-stream.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/log-stream.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;GASG;AACH,MAAa,cAAc;IACd,SAAS,CAAS;IAClB,OAAO,CAAS;IAChB,IAAI,CAAS;IAEtB,YAAY,SAAiB,EAAE,OAAe,EAAE,IAAY;QACxD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACrB,CAAC;CACJ;AAVD,wCAUC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAa,SAAS;IAClB,iGAAiG;IACjG,+FAA+F;IAC/F,iGAAiG;IACjG,gGAAgG;IAChG,oCAAoC;IAC5B,SAAS,GAAG,SAAS,CAAC;IACtB,OAAO,GAAG,EAAE,CAAC;IACb,IAAI,GAAG,MAAM,CAAC;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,QAAwB;QAC7B,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;QACpC,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAC9B,CAAC;IAED;;;;;;;;;;;OAWG;IACH,QAAQ;QACJ,OAAO,IAAI,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACvE,CAAC;IAED;;;;;OAKG;IACH,QAAQ,CAAC,IAAY;QACjB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1E,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;IAC/E,CAAC;CACJ;AA/CD,8BA+CC;AAED;;;;;GAKG;AACH,gIAAgI;AAChI,SAAS,OAAO,CAAC,GAAW;IACxB,MAAM,OAAO,GAAG,GAAG;SACd,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAG,iDAAiD;SACpF,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAY,2DAA2D;SAC9F,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAe,4BAA4B;SAC/D,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAClB,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACU,QAAA,SAAS,GAAG,IAAI,SAAS,EAAE,CAAC","sourcesContent":["/**\n * WHO is writing — the three fields that make a log filename unique, carried as one value.\n *\n * Data-only (per CLAUDE.md: classes for data, explicit construction). It exists as a class rather\n * than three loose parameters because the identity has to CROSS A PROCESS BOUNDARY: the detached\n * main-sync refresher is a separate node process, and the spawner hands it these three fields on\n * argv so parent and child write ONE stream. A single named carrier is what makes \"read it back out\n * of the parent, put it on argv, set it in the child\" a three-line round trip instead of three\n * parallel string parameters that can be reordered at either end.\n */\nexport class StreamIdentity {\n readonly sessionId: string;\n readonly agentId: string;\n readonly hook: string;\n\n constructor(sessionId: string, agentId: string, hook: string) {\n this.sessionId = sessionId;\n this.agentId = agentId;\n this.hook = hook;\n }\n}\n\n/**\n * WHICH LOG FILE does this hook invocation append to?\n *\n * ─── The bug this exists to fix ────────────────────────────────────────────────────────────────────\n * Log paths used to be keyed by the git WORKTREE alone (`<local>/logs/…`). Three separate things share\n * a worktree, so three separate things shared one file:\n *\n * 1. **Parallel hooks.** The hooks reference says \"when multiple PreToolUse hooks match a tool call,\n * ALL matching hooks run in parallel\". `wp-ai-rules-hook` and `wp-ai-guards-hook` both match\n * Write/Edit/MultiEdit, so on every file edit TWO PROCESSES append to the same file at the same\n * time. L-1's `guarantee-root.sh` makes it three.\n * 2. **Subagents.** A subagent without worktree isolation shares the coordinator's tree.\n * 3. **Whole sessions.** Four Claude Code windows on one clone are four coordinators, and `agent_id`\n * is absent for every one of them — so agent identity alone cannot tell them apart.\n *\n * `O_APPEND` is indivisible only under `PIPE_BUF`, which is **512 bytes on macOS**. Measured\n * 2026-08-06 across three repos: `guard-invocations.log` 208/3306 lines (6.3%) exceed it, max 608 B;\n * `guard-sync-decisions.log` 209/4097 (5.1%), max 625 B. So this tears TODAY, and the corrupted line\n * is exactly the long one — the `recover=` line a human needs most.\n *\n * ─── The key: three dimensions, one FLAT filename ──────────────────────────────────────────────────\n * <local>/logs/<sessionId>-<agentId | \"coordinator\">-<hook>-<file>.log\n *\n * sessionId separates concurrent Claude Code windows (`session_id`, on every hook payload)\n * agentId separates subagents within one window (`agent_id`, subagent-only — absent = coordinator)\n * hook separates the PARALLEL hooks ('guards' | 'rules' | 'guarantee-root')\n *\n * One writer per FILE, by construction, so appends cannot interleave and nothing needs a lock.\n *\n * DELIBERATELY FLAT, not `sessions/<id>/<agent>/<hook>/<file>`. A nested tree makes the common\n * question — \"show me everything that happened, in time order\" — into a directory walk, when it should\n * be one glob: `ls logs/` shows every stream at once, `logs/<sid>-*` is one window, `*-<agent>-*` is one\n * subagent, `*-guards-*` is one hook. Rotation is unchanged because `.1.log` is still a suffix.\n *\n * `transcript_path` is also unique per session, but it is a filesystem PATH — long, and full of\n * separators that would have to be flattened anyway — and `session_id` is its stable identifier, so\n * session_id is the better key.\n *\n * The tree is still visible — every line already carries `root=` / `projectDir=` / `tree=` columns —\n * so nothing is lost by the filename not encoding it.\n *\n * ─── There is no un-split path ─────────────────────────────────────────────────────────────────────\n * Every name is prefixed, always. A caller that never identifies renders as\n * `unknown-coordinator-hook-<base>` — a distinct, greppable stream, NOT the shared file. Keeping a\n * bare-name fallback would have meant two reachable spellings of one filename, with the tearing one\n * reached by doing nothing; that is the widening-as-absence this whole class exists to remove, so it\n * is not offered.\n */\nexport class LogStream {\n // ALWAYS a real identity. There is no \"unset\" state and no bare-name branch, so there is exactly\n // ONE spelling of a log filename and a writer cannot reach the shared, tearing stream by doing\n // nothing. A caller with no Claude Code payload (the openclaw adapter, library consumers, specs)\n // gets UNIDENTIFIED below — which still prefixes, with `unknown`, so it is a distinct greppable\n // stream rather than a merge point.\n private sessionId = 'unknown';\n private agentId = '';\n private hook = 'hook';\n\n /**\n * Called once per invocation by the adapter that parsed the payload. `agentId` is empty for the\n * coordinator — that absence IS the signal, see AgentIdentity — and renders as `coordinator`.\n * An empty `sessionId` renders as `unknown`: visible, never merged into another stream.\n */\n identify(identity: StreamIdentity): void {\n this.sessionId = identity.sessionId;\n this.agentId = identity.agentId;\n this.hook = identity.hook;\n }\n\n /**\n * This process's identity, readable so it can be HANDED TO A CHILD PROCESS.\n *\n * The detached main-sync refresher (main-sync-refresh.ts → sync-main.ts) is a separate node\n * process with a fresh, unidentified `logStream`. Before this existed the parent logged\n * SPAWN_ATTEMPT to its own prefixed stream while the child logged START/FINISH/ERROR to the\n * shared `unknown-coordinator-hook-guard-async-work.log` — so ONE refresh cycle was split across\n * two files, every agent's child appended to that one shared file (the PIPE_BUF tearing this\n * class exists to remove, still live on that stream), and the documented\n * \"SPAWN_ATTEMPT with no START means the child never launched\" check read as a false failure on\n * every single cycle. The spawner now reads this and puts it on the child's argv.\n */\n identity(): StreamIdentity {\n return new StreamIdentity(this.sessionId, this.agentId, this.hook);\n }\n\n /**\n * This caller's name for `base` — `<sessionId>-<agentId|coordinator>-<hook>-<base>`, ALWAYS.\n *\n * Takes the WHOLE filename (`guard-invocations.log`, and separately `guard-invocations.1.log`) so\n * the rotation sibling gets the identical prefix and rotation keeps working untouched.\n */\n fileName(base: string): string {\n const agent = segment(this.agentId === '' ? 'coordinator' : this.agentId);\n return `${segment(this.sessionId)}-${agent}-${segment(this.hook)}-${base}`;\n }\n}\n\n/**\n * One path segment, sanitised. `session_id` and `agent_id` arrive from a JSON payload, so they are\n * UNTRUSTED INPUT being used to build a filesystem path: `../../../etc` must become a harmless name and\n * never escape the logs directory. Everything outside `[A-Za-z0-9._-]` collapses to `_`, a leading dot\n * is neutralised so nothing becomes a hidden file or `..`, and the result is capped and never empty.\n */\n// webpieces-disable no-function-outside-class -- pure string sanitiser, the module's own leaf helper beside the class it serves\nfunction segment(raw: string): string {\n const cleaned = raw\n .replace(/[^A-Za-z0-9._-]/g, '_') // kills every separator, so nothing can traverse\n .replace(/\\.{2,}/g, '_') // and no run of dots survives, so no segment reads as `..`\n .replace(/^\\.+/, '_') // nor becomes a hidden file\n .slice(0, 64);\n return cleaned === '' ? 'unknown' : cleaned;\n}\n\n/**\n * Process-wide instance. The hook adapters identify it once at the top of the invocation and every\n * writer downstream reads it, which is what keeps `logGuardDecision()` / `logRejection()` signatures\n * unchanged — the alternative was threading three more parameters through every call site.\n */\nexport const logStream = new LogStream();\n"]}
|
|
1
|
+
{"version":3,"file":"log-stream.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/log-stream.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;GASG;AACH,MAAa,cAAc;IACd,SAAS,CAAS;IAClB,OAAO,CAAS;IAChB,IAAI,CAAS;IAEtB,YAAY,SAAiB,EAAE,OAAe,EAAE,IAAY;QACxD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACrB,CAAC;CACJ;AAVD,wCAUC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAa,SAAS;IAClB,iGAAiG;IACjG,+FAA+F;IAC/F,iGAAiG;IACjG,gGAAgG;IAChG,oCAAoC;IAC5B,SAAS,GAAG,SAAS,CAAC;IACtB,OAAO,GAAG,EAAE,CAAC;IACb,IAAI,GAAG,MAAM,CAAC;IAEtB;;;;OAIG;IACH,QAAQ,CAAC,QAAwB;QAC7B,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;QACpC,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;IAC9B,CAAC;IAED;;;;;;;;;;;OAWG;IACH,QAAQ;QACJ,OAAO,IAAI,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACvE,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,MAAc;QACrB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1E,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC;IAChF,CAAC;CACJ;AAtDD,8BAsDC;AAED;;;;;GAKG;AACH,gIAAgI;AAChI,SAAS,OAAO,CAAC,GAAW;IACxB,MAAM,OAAO,GAAG,GAAG;SACd,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAG,iDAAiD;SACpF,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAY,2DAA2D;SAC9F,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAe,4BAA4B;SAC/D,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAClB,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACU,QAAA,SAAS,GAAG,IAAI,SAAS,EAAE,CAAC","sourcesContent":["/**\n * WHO is writing — the three fields that make a log filename unique, carried as one value.\n *\n * Data-only (per CLAUDE.md: classes for data, explicit construction). It exists as a class rather\n * than three loose parameters because the identity has to CROSS A PROCESS BOUNDARY: the detached\n * main-sync refresher is a separate node process, and the spawner hands it these three fields on\n * argv so parent and child write ONE stream. A single named carrier is what makes \"read it back out\n * of the parent, put it on argv, set it in the child\" a three-line round trip instead of three\n * parallel string parameters that can be reordered at either end.\n */\nexport class StreamIdentity {\n readonly sessionId: string;\n readonly agentId: string;\n readonly hook: string;\n\n constructor(sessionId: string, agentId: string, hook: string) {\n this.sessionId = sessionId;\n this.agentId = agentId;\n this.hook = hook;\n }\n}\n\n/**\n * WHICH LOG FILE does this hook invocation append to?\n *\n * ─── The bug this exists to fix ────────────────────────────────────────────────────────────────────\n * Log paths used to be keyed by the git WORKTREE alone (`<local>/logs/…`). Three separate things share\n * a worktree, so three separate things shared one file:\n *\n * 1. **Parallel hooks.** The hooks reference says \"when multiple PreToolUse hooks match a tool call,\n * ALL matching hooks run in parallel\". `wp-ai-rules-hook` and `wp-ai-guards-hook` both match\n * Write/Edit/MultiEdit, so on every file edit TWO PROCESSES append to the same file at the same\n * time. L-1's `guarantee-root.sh` makes it three.\n * 2. **Subagents.** A subagent without worktree isolation shares the coordinator's tree.\n * 3. **Whole sessions.** Four Claude Code windows on one clone are four coordinators, and `agent_id`\n * is absent for every one of them — so agent identity alone cannot tell them apart.\n *\n * `O_APPEND` is indivisible only under `PIPE_BUF`, which is **512 bytes on macOS**. Measured\n * 2026-08-06 across three repos: the invocation stream 208/3306 lines (6.3%) exceed it, max 608 B;\n * the decision stream 209/4097 (5.1%), max 625 B. So this tears TODAY, and the corrupted line\n * is exactly the long one — the `recover=` line a human needs most.\n *\n * ─── The key: the LAYER is the directory, the WRITER is the file ──────────────────────────────────\n * <local>/logs/<stream>/<sessionId>-<agentId | \"coordinator\">-<hook>.log\n *\n * stream separates the LAYERS ('L-1-cd' | 'L0-shim' | 'L1-location' | …)\n * sessionId separates concurrent Claude Code windows (`session_id`, on every hook payload)\n * agentId separates subagents within one window (`agent_id`, subagent-only — absent = coordinator)\n * hook separates the PARALLEL hooks ('guards' | 'rules' | 'guarantee-root')\n *\n * This class owns the FILE half. One writer per file, by construction, so appends cannot interleave\n * and nothing needs a lock — and all three identity dimensions must stay in the filename for that to\n * hold. `hook` especially: Claude Code runs the guards and rules hooks as separate processes IN\n * PARALLEL on one tool call, so folding `hook` into the directory would put two concurrent appenders\n * on one path, which is the tearing measured above.\n *\n * Nesting by STREAM is NOT the nesting this layout rejects. What it rejects is nesting by IDENTITY\n * (`sessions/<id>/<agent>/…`), which turns every cross-session question into a directory walk. The\n * layer is the one axis you almost always want to slice by first, and it was previously not\n * expressible at all: L1 had no stream, so \"show me every L1 decision\" had no answer. Now\n * `ls logs/L1-location/` is that answer, and a one-level wildcard recovers the flat view —\n * `ls -t logs/[*]/<sid>-*` is still every layer at once, in time order.\n *\n * `transcript_path` is also unique per session, but it is a filesystem PATH — long, and full of\n * separators that would have to be flattened anyway — and `session_id` is its stable identifier, so\n * session_id is the better key.\n *\n * The tree is still visible — every line already carries `root=` / `projectDir=` / `tree=` columns —\n * so nothing is lost by the filename not encoding it.\n *\n * ─── There is no un-split path ─────────────────────────────────────────────────────────────────────\n * Every name is prefixed, always. A caller that never identifies renders as\n * `unknown-coordinator-hook.log` — a distinct, greppable writer, NOT a shared file. Keeping a\n * bare-name fallback would have meant two reachable spellings of one filename, with the tearing one\n * reached by doing nothing; that is the widening-as-absence this whole class exists to remove, so it\n * is not offered.\n */\nexport class LogStream {\n // ALWAYS a real identity. There is no \"unset\" state and no bare-name branch, so there is exactly\n // ONE spelling of a log filename and a writer cannot reach the shared, tearing stream by doing\n // nothing. A caller with no Claude Code payload (the openclaw adapter, library consumers, specs)\n // gets UNIDENTIFIED below — which still prefixes, with `unknown`, so it is a distinct greppable\n // stream rather than a merge point.\n private sessionId = 'unknown';\n private agentId = '';\n private hook = 'hook';\n\n /**\n * Called once per invocation by the adapter that parsed the payload. `agentId` is empty for the\n * coordinator — that absence IS the signal, see AgentIdentity — and renders as `coordinator`.\n * An empty `sessionId` renders as `unknown`: visible, never merged into another stream.\n */\n identify(identity: StreamIdentity): void {\n this.sessionId = identity.sessionId;\n this.agentId = identity.agentId;\n this.hook = identity.hook;\n }\n\n /**\n * This process's identity, readable so it can be HANDED TO A CHILD PROCESS.\n *\n * The detached main-sync refresher (main-sync-refresh.ts → sync-main.ts) is a separate node\n * process with a fresh, unidentified `logStream`. Before this existed the parent logged\n * SPAWN_ATTEMPT to its own prefixed stream while the child logged START/FINISH/ERROR to the\n * shared, unidentified `unknown-coordinator-hook` writer — so ONE refresh cycle was split across\n * two files, every agent's child appended to that one shared file (the PIPE_BUF tearing this\n * class exists to remove, still live on that stream), and the documented\n * \"SPAWN_ATTEMPT with no START means the child never launched\" check read as a false failure on\n * every single cycle. The spawner now reads this and puts it on the child's argv.\n */\n identity(): StreamIdentity {\n return new StreamIdentity(this.sessionId, this.agentId, this.hook);\n }\n\n /**\n * This WRITER's file name within a stream directory —\n * `<sessionId>-<agentId|coordinator>-<hook><suffix>`, ALWAYS.\n *\n * The three identity dimensions stay in the FILE while the stream moves to the DIRECTORY\n * (see log-streams.ts), because they are what makes one writer per file true and the stream is not:\n * `wp-ai-guards-hook` and `wp-ai-rules-hook` are separate processes that Claude Code launches in\n * PARALLEL on the same tool call, so dropping `hook` here would put two concurrent appenders on\n * one path — the exact tearing this class exists to remove, reintroduced by a rename.\n *\n * `suffix` carries the extension so the rotation sibling gets the identical writer key: pass\n * `.log`, and separately `.1.log`.\n */\n writerFile(suffix: string): string {\n const agent = segment(this.agentId === '' ? 'coordinator' : this.agentId);\n return `${segment(this.sessionId)}-${agent}-${segment(this.hook)}${suffix}`;\n }\n}\n\n/**\n * One path segment, sanitised. `session_id` and `agent_id` arrive from a JSON payload, so they are\n * UNTRUSTED INPUT being used to build a filesystem path: `../../../etc` must become a harmless name and\n * never escape the logs directory. Everything outside `[A-Za-z0-9._-]` collapses to `_`, a leading dot\n * is neutralised so nothing becomes a hidden file or `..`, and the result is capped and never empty.\n */\n// webpieces-disable no-function-outside-class -- pure string sanitiser, the module's own leaf helper beside the class it serves\nfunction segment(raw: string): string {\n const cleaned = raw\n .replace(/[^A-Za-z0-9._-]/g, '_') // kills every separator, so nothing can traverse\n .replace(/\\.{2,}/g, '_') // and no run of dots survives, so no segment reads as `..`\n .replace(/^\\.+/, '_') // nor becomes a hidden file\n .slice(0, 64);\n return cleaned === '' ? 'unknown' : cleaned;\n}\n\n/**\n * Process-wide instance. The hook adapters identify it once at the top of the invocation and every\n * writer downstream reads it, which is what keeps `logGuardDecision()` / `logRejection()` signatures\n * unchanged — the alternative was threading three more parameters through every call site.\n */\nexport const logStream = new LogStream();\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE STREAM DIRECTORIES — `<state>/logs/<stream>/<sessionId>-<agentId|coordinator>-<hook>.log`.
|
|
3
|
+
*
|
|
4
|
+
* The stream is the DIRECTORY and the writer is the FILE. That split is the whole point: it puts the
|
|
5
|
+
* LAYER in the path, so "show me every L1 decision this session" is one glob
|
|
6
|
+
* (`logs/L1-location/<sid>-*`) instead of a question no glob could answer — every layer used to be
|
|
7
|
+
* either a suffix on a flat name or, for L1, nothing at all.
|
|
8
|
+
*
|
|
9
|
+
* These live in ai-hook-rules beside LogStream, because all FOUR writers are here of this layout and
|
|
10
|
+
* only two of them are TypeScript: the L-1 `guarantee-root.sh` and the L0 `ai-hook.sh` are POSIX sh
|
|
11
|
+
* rendered from `bin/guarantee-root.ts` and `bin/shim-audit-log.ts`, and they must spell these
|
|
12
|
+
* directories identically or the layers scatter. One constant, four consumers — the same reason
|
|
13
|
+
* LogStream itself lives here.
|
|
14
|
+
*
|
|
15
|
+
* NOT in rules-config, and that is load-bearing rather than tidiness: the `wp-*` bins are SPAWNED as
|
|
16
|
+
* processes with no tsconfig path mapping, so they resolve `@webpieces/rules-config` to the PUBLISHED
|
|
17
|
+
* copy in node_modules — a release behind local source. A stream name defined there would render as
|
|
18
|
+
* `undefined` inside a spawned `wp-upgrade-shim` while the in-process renderer used the real value,
|
|
19
|
+
* and the two shims would differ by exactly the constant. Same-package means same source, always.
|
|
20
|
+
*
|
|
21
|
+
* Nesting by STREAM is not the nesting that was rejected. What `logs/` refused was nesting by
|
|
22
|
+
* IDENTITY (`sessions/<id>/<agent>/…`), which breaks every cross-session question. A one-level
|
|
23
|
+
* wildcard recovers the flat view here — `ls -t logs/[star]/<sid>-*` is still every stream in time
|
|
24
|
+
* order, across every layer at once.
|
|
25
|
+
*/
|
|
26
|
+
export declare const LMINUS1_CD_STREAM = "L-1-cd";
|
|
27
|
+
export declare const L0_SHIM_STREAM = "L0-shim";
|
|
28
|
+
export declare const L1_LOCATION_STREAM = "L1-location";
|
|
29
|
+
export declare const L2_DECISIONS_STREAM = "L2-decisions";
|
|
30
|
+
export declare const CALLS_STREAM = "calls";
|
|
31
|
+
export declare const ASYNC_REFRESH_STREAM = "async-refresh";
|
|
32
|
+
export declare const REJECTIONS_STREAM = "rejections";
|
|
33
|
+
/**
|
|
34
|
+
* Every stream directory, for the sweeps and specs that must enumerate them rather than name one.
|
|
35
|
+
* A new stream that is not added here is invisible to retention — which is why this is a list and not
|
|
36
|
+
* a doc sentence.
|
|
37
|
+
*/
|
|
38
|
+
export declare const ALL_LOG_STREAMS: readonly string[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ALL_LOG_STREAMS = exports.REJECTIONS_STREAM = exports.ASYNC_REFRESH_STREAM = exports.CALLS_STREAM = exports.L2_DECISIONS_STREAM = exports.L1_LOCATION_STREAM = exports.L0_SHIM_STREAM = exports.LMINUS1_CD_STREAM = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* THE STREAM DIRECTORIES — `<state>/logs/<stream>/<sessionId>-<agentId|coordinator>-<hook>.log`.
|
|
6
|
+
*
|
|
7
|
+
* The stream is the DIRECTORY and the writer is the FILE. That split is the whole point: it puts the
|
|
8
|
+
* LAYER in the path, so "show me every L1 decision this session" is one glob
|
|
9
|
+
* (`logs/L1-location/<sid>-*`) instead of a question no glob could answer — every layer used to be
|
|
10
|
+
* either a suffix on a flat name or, for L1, nothing at all.
|
|
11
|
+
*
|
|
12
|
+
* These live in ai-hook-rules beside LogStream, because all FOUR writers are here of this layout and
|
|
13
|
+
* only two of them are TypeScript: the L-1 `guarantee-root.sh` and the L0 `ai-hook.sh` are POSIX sh
|
|
14
|
+
* rendered from `bin/guarantee-root.ts` and `bin/shim-audit-log.ts`, and they must spell these
|
|
15
|
+
* directories identically or the layers scatter. One constant, four consumers — the same reason
|
|
16
|
+
* LogStream itself lives here.
|
|
17
|
+
*
|
|
18
|
+
* NOT in rules-config, and that is load-bearing rather than tidiness: the `wp-*` bins are SPAWNED as
|
|
19
|
+
* processes with no tsconfig path mapping, so they resolve `@webpieces/rules-config` to the PUBLISHED
|
|
20
|
+
* copy in node_modules — a release behind local source. A stream name defined there would render as
|
|
21
|
+
* `undefined` inside a spawned `wp-upgrade-shim` while the in-process renderer used the real value,
|
|
22
|
+
* and the two shims would differ by exactly the constant. Same-package means same source, always.
|
|
23
|
+
*
|
|
24
|
+
* Nesting by STREAM is not the nesting that was rejected. What `logs/` refused was nesting by
|
|
25
|
+
* IDENTITY (`sessions/<id>/<agent>/…`), which breaks every cross-session question. A one-level
|
|
26
|
+
* wildcard recovers the flat view here — `ls -t logs/[star]/<sid>-*` is still every stream in time
|
|
27
|
+
* order, across every layer at once.
|
|
28
|
+
*/
|
|
29
|
+
exports.LMINUS1_CD_STREAM = 'L-1-cd';
|
|
30
|
+
exports.L0_SHIM_STREAM = 'L0-shim';
|
|
31
|
+
exports.L1_LOCATION_STREAM = 'L1-location';
|
|
32
|
+
exports.L2_DECISIONS_STREAM = 'L2-decisions';
|
|
33
|
+
exports.CALLS_STREAM = 'calls';
|
|
34
|
+
exports.ASYNC_REFRESH_STREAM = 'async-refresh';
|
|
35
|
+
exports.REJECTIONS_STREAM = 'rejections';
|
|
36
|
+
/**
|
|
37
|
+
* Every stream directory, for the sweeps and specs that must enumerate them rather than name one.
|
|
38
|
+
* A new stream that is not added here is invisible to retention — which is why this is a list and not
|
|
39
|
+
* a doc sentence.
|
|
40
|
+
*/
|
|
41
|
+
exports.ALL_LOG_STREAMS = [
|
|
42
|
+
exports.LMINUS1_CD_STREAM, exports.L0_SHIM_STREAM, exports.L1_LOCATION_STREAM, exports.L2_DECISIONS_STREAM,
|
|
43
|
+
exports.CALLS_STREAM, exports.ASYNC_REFRESH_STREAM, exports.REJECTIONS_STREAM,
|
|
44
|
+
];
|
|
45
|
+
//# sourceMappingURL=log-streams.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-streams.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/log-streams.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACU,QAAA,iBAAiB,GAAG,QAAQ,CAAC;AAC7B,QAAA,cAAc,GAAG,SAAS,CAAC;AAC3B,QAAA,kBAAkB,GAAG,aAAa,CAAC;AACnC,QAAA,mBAAmB,GAAG,cAAc,CAAC;AACrC,QAAA,YAAY,GAAG,OAAO,CAAC;AACvB,QAAA,oBAAoB,GAAG,eAAe,CAAC;AACvC,QAAA,iBAAiB,GAAG,YAAY,CAAC;AAE9C;;;;GAIG;AACU,QAAA,eAAe,GAAsB;IAC9C,yBAAiB,EAAE,sBAAc,EAAE,0BAAkB,EAAE,2BAAmB;IAC1E,oBAAY,EAAE,4BAAoB,EAAE,yBAAiB;CACxD,CAAC","sourcesContent":["/**\n * THE STREAM DIRECTORIES — `<state>/logs/<stream>/<sessionId>-<agentId|coordinator>-<hook>.log`.\n *\n * The stream is the DIRECTORY and the writer is the FILE. That split is the whole point: it puts the\n * LAYER in the path, so \"show me every L1 decision this session\" is one glob\n * (`logs/L1-location/<sid>-*`) instead of a question no glob could answer — every layer used to be\n * either a suffix on a flat name or, for L1, nothing at all.\n *\n * These live in ai-hook-rules beside LogStream, because all FOUR writers are here of this layout and\n * only two of them are TypeScript: the L-1 `guarantee-root.sh` and the L0 `ai-hook.sh` are POSIX sh\n * rendered from `bin/guarantee-root.ts` and `bin/shim-audit-log.ts`, and they must spell these\n * directories identically or the layers scatter. One constant, four consumers — the same reason\n * LogStream itself lives here.\n *\n * NOT in rules-config, and that is load-bearing rather than tidiness: the `wp-*` bins are SPAWNED as\n * processes with no tsconfig path mapping, so they resolve `@webpieces/rules-config` to the PUBLISHED\n * copy in node_modules — a release behind local source. A stream name defined there would render as\n * `undefined` inside a spawned `wp-upgrade-shim` while the in-process renderer used the real value,\n * and the two shims would differ by exactly the constant. Same-package means same source, always.\n *\n * Nesting by STREAM is not the nesting that was rejected. What `logs/` refused was nesting by\n * IDENTITY (`sessions/<id>/<agent>/…`), which breaks every cross-session question. A one-level\n * wildcard recovers the flat view here — `ls -t logs/[star]/<sid>-*` is still every stream in time\n * order, across every layer at once.\n */\nexport const LMINUS1_CD_STREAM = 'L-1-cd';\nexport const L0_SHIM_STREAM = 'L0-shim';\nexport const L1_LOCATION_STREAM = 'L1-location';\nexport const L2_DECISIONS_STREAM = 'L2-decisions';\nexport const CALLS_STREAM = 'calls';\nexport const ASYNC_REFRESH_STREAM = 'async-refresh';\nexport const REJECTIONS_STREAM = 'rejections';\n\n/**\n * Every stream directory, for the sweeps and specs that must enumerate them rather than name one.\n * A new stream that is not added here is invisible to retention — which is why this is a list and not\n * a doc sentence.\n */\nexport const ALL_LOG_STREAMS: readonly string[] = [\n LMINUS1_CD_STREAM, L0_SHIM_STREAM, L1_LOCATION_STREAM, L2_DECISIONS_STREAM,\n CALLS_STREAM, ASYNC_REFRESH_STREAM, REJECTIONS_STREAM,\n];\n"]}
|
|
@@ -8,9 +8,9 @@ export declare class SyncLogEvent {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Append one tab-separated line per refresher event to
|
|
11
|
-
* `.webpieces/logs
|
|
11
|
+
* `.webpieces/logs/async-refresh/<writer>.log` (see LogStream for the writer key). `root` is
|
|
12
12
|
* the workspace root holding `.webpieces`. Swallows all errors — logging must never block or fail
|
|
13
13
|
* the refresher (or the hook that spawns it).
|
|
14
14
|
*/
|
|
15
15
|
export declare function logSyncEvent(root: string, event: SyncLogEvent): void;
|
|
16
|
-
export declare function
|
|
16
|
+
export declare function refresherChildStdioPath(root: string): string;
|