opensquid 0.5.441 → 0.5.447
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 -0
- package/dist/functions/arm_scope.d.ts +27 -0
- package/dist/functions/arm_scope.d.ts.map +1 -0
- package/dist/functions/arm_scope.js +52 -0
- package/dist/functions/arm_scope.js.map +1 -0
- package/dist/functions/index.d.ts +1 -0
- package/dist/functions/index.d.ts.map +1 -1
- package/dist/functions/index.js +1 -0
- package/dist/functions/index.js.map +1 -1
- package/dist/runtime/bootstrap.d.ts.map +1 -1
- package/dist/runtime/bootstrap.js +2 -0
- package/dist/runtime/bootstrap.js.map +1 -1
- package/dist/runtime/handoff/render.d.ts +5 -4
- package/dist/runtime/handoff/render.d.ts.map +1 -1
- package/dist/runtime/handoff/render.js +7 -7
- package/dist/runtime/handoff/render.js.map +1 -1
- package/dist/runtime/hooks/active_task_mirror.js +0 -0
- package/dist/runtime/hooks/apply_patch.js +0 -0
- package/dist/runtime/hooks/dispatch.js +0 -0
- package/dist/runtime/hooks/hook_output.js +0 -0
- package/dist/runtime/hooks/memory_reconcile.js +0 -0
- package/dist/runtime/hooks/new_project_detect.js +0 -0
- package/dist/runtime/hooks/profession_resolver.js +0 -0
- package/dist/runtime/hooks/scope_intent.js +0 -0
- package/dist/runtime/hooks/session_id.js +0 -0
- package/dist/runtime/hooks/session_liveness.js +0 -0
- package/dist/runtime/hooks/stop_drive.js +0 -0
- package/dist/runtime/hooks/stop_stream.js +0 -0
- package/dist/runtime/hooks/subagent_guard.js +0 -0
- package/dist/runtime/hooks/transcript.js +0 -0
- package/dist/runtime/hooks/transcript_tasks.js +0 -0
- package/dist/runtime/ralph/orchestrator.d.ts.map +1 -1
- package/dist/runtime/ralph/orchestrator.js +2 -1
- package/dist/runtime/ralph/orchestrator.js.map +1 -1
- package/dist/setup/cli/limits_state.d.ts.map +1 -1
- package/dist/setup/cli/limits_state.js +6 -40
- package/dist/setup/cli/limits_state.js.map +1 -1
- package/dist/setup/cli/pack_walk.d.ts +32 -0
- package/dist/setup/cli/pack_walk.d.ts.map +1 -0
- package/dist/setup/cli/pack_walk.js +76 -0
- package/dist/setup/cli/pack_walk.js.map +1 -0
- package/dist/setup/cli/permissions_state.d.ts.map +1 -1
- package/dist/setup/cli/permissions_state.js +6 -37
- package/dist/setup/cli/permissions_state.js.map +1 -1
- package/dist/setup/cli/triggers_state.d.ts.map +1 -1
- package/dist/setup/cli/triggers_state.js +3 -29
- package/dist/setup/cli/triggers_state.js.map +1 -1
- package/dist/workgraph/events.d.ts.map +1 -1
- package/dist/workgraph/events.js +10 -0
- package/dist/workgraph/events.js.map +1 -1
- package/dist/workgraph/store.d.ts.map +1 -1
- package/dist/workgraph/store.js +5 -0
- package/dist/workgraph/store.js.map +1 -1
- package/dist/workgraph/types.d.ts +2 -1
- package/dist/workgraph/types.d.ts.map +1 -1
- package/docs/ARCHITECTURE.md +268 -0
- package/package.json +5 -3
- package/packs/builtin/coding-flow/skills/entry-and-handoffs/skill.yaml +13 -17
- package/dist/anti-drift/evaluator.d.ts +0 -88
- package/dist/anti-drift/evaluator.d.ts.map +0 -1
- package/dist/anti-drift/evaluator.js +0 -417
- package/dist/anti-drift/evaluator.js.map +0 -1
- package/dist/anti-drift/evaluator.test.js +0 -78
- package/dist/anti-drift/rules.d.ts +0 -80
- package/dist/anti-drift/rules.d.ts.map +0 -1
- package/dist/anti-drift/rules.js +0 -368
- package/dist/anti-drift/rules.js.map +0 -1
- package/dist/anti-drift/rules.test.js +0 -213
- package/dist/anti-drift/state.d.ts +0 -107
- package/dist/anti-drift/state.d.ts.map +0 -1
- package/dist/anti-drift/state.js +0 -177
- package/dist/anti-drift/state.js.map +0 -1
- package/dist/anti-drift/state.test.js +0 -120
- package/dist/chat/adapters/discord.d.ts +0 -41
- package/dist/chat/adapters/discord.d.ts.map +0 -1
- package/dist/chat/adapters/discord.js +0 -176
- package/dist/chat/adapters/discord.js.map +0 -1
- package/dist/chat/adapters/discord.test.js +0 -25
- package/dist/chat/adapters/slack.d.ts +0 -43
- package/dist/chat/adapters/slack.d.ts.map +0 -1
- package/dist/chat/adapters/slack.js +0 -172
- package/dist/chat/adapters/slack.js.map +0 -1
- package/dist/chat/adapters/slack.test.js +0 -30
- package/dist/chat/adapters/telegram.d.ts +0 -148
- package/dist/chat/adapters/telegram.d.ts.map +0 -1
- package/dist/chat/adapters/telegram.js +0 -498
- package/dist/chat/adapters/telegram.js.map +0 -1
- package/dist/chat/adapters/telegram.test.js +0 -94
- package/dist/chat/config.d.ts +0 -98
- package/dist/chat/config.d.ts.map +0 -1
- package/dist/chat/config.js +0 -185
- package/dist/chat/config.js.map +0 -1
- package/dist/chat/daemon/active-project.d.ts +0 -17
- package/dist/chat/daemon/active-project.d.ts.map +0 -1
- package/dist/chat/daemon/active-project.js +0 -23
- package/dist/chat/daemon/active-project.js.map +0 -1
- package/dist/chat/daemon/autospawn.d.ts +0 -40
- package/dist/chat/daemon/autospawn.d.ts.map +0 -1
- package/dist/chat/daemon/autospawn.js +0 -129
- package/dist/chat/daemon/autospawn.js.map +0 -1
- package/dist/chat/daemon/autospawn.test.js +0 -112
- package/dist/chat/daemon/cli.d.ts +0 -18
- package/dist/chat/daemon/cli.d.ts.map +0 -1
- package/dist/chat/daemon/cli.js +0 -71
- package/dist/chat/daemon/cli.js.map +0 -1
- package/dist/chat/daemon/collisions.js +0 -384
- package/dist/chat/daemon/health-check.d.ts +0 -69
- package/dist/chat/daemon/health-check.d.ts.map +0 -1
- package/dist/chat/daemon/health-check.js +0 -112
- package/dist/chat/daemon/health-check.js.map +0 -1
- package/dist/chat/daemon/inbox-read.d.ts +0 -35
- package/dist/chat/daemon/inbox-read.d.ts.map +0 -1
- package/dist/chat/daemon/inbox-read.js +0 -75
- package/dist/chat/daemon/inbox-read.js.map +0 -1
- package/dist/chat/daemon/inbox-read.test.js +0 -97
- package/dist/chat/daemon/inbox.d.ts +0 -63
- package/dist/chat/daemon/inbox.d.ts.map +0 -1
- package/dist/chat/daemon/inbox.js +0 -56
- package/dist/chat/daemon/inbox.js.map +0 -1
- package/dist/chat/daemon/inbox.test.js +0 -110
- package/dist/chat/daemon/lifecycle.d.ts +0 -71
- package/dist/chat/daemon/lifecycle.d.ts.map +0 -1
- package/dist/chat/daemon/lifecycle.js +0 -221
- package/dist/chat/daemon/lifecycle.js.map +0 -1
- package/dist/chat/daemon/lifecycle.test.js +0 -163
- package/dist/chat/daemon/protocol.d.ts +0 -107
- package/dist/chat/daemon/protocol.d.ts.map +0 -1
- package/dist/chat/daemon/protocol.js +0 -54
- package/dist/chat/daemon/protocol.js.map +0 -1
- package/dist/chat/daemon/routing.d.ts +0 -140
- package/dist/chat/daemon/routing.d.ts.map +0 -1
- package/dist/chat/daemon/routing.js +0 -198
- package/dist/chat/daemon/routing.js.map +0 -1
- package/dist/chat/daemon/routing.test.js +0 -259
- package/dist/chat/daemon/rpc-client.d.ts +0 -45
- package/dist/chat/daemon/rpc-client.d.ts.map +0 -1
- package/dist/chat/daemon/rpc-client.js +0 -133
- package/dist/chat/daemon/rpc-client.js.map +0 -1
- package/dist/chat/daemon/rpc-server.d.ts +0 -39
- package/dist/chat/daemon/rpc-server.d.ts.map +0 -1
- package/dist/chat/daemon/rpc-server.js +0 -385
- package/dist/chat/daemon/rpc-server.js.map +0 -1
- package/dist/chat/daemon/rpc.test.js +0 -177
- package/dist/chat/daemon/subscribers.js +0 -257
- package/dist/chat/daemon/worker.d.ts +0 -27
- package/dist/chat/daemon/worker.d.ts.map +0 -1
- package/dist/chat/daemon/worker.js +0 -313
- package/dist/chat/daemon/worker.js.map +0 -1
- package/dist/chat/daemon/workspace-topic.js +0 -324
- package/dist/chat/env-token.d.ts +0 -60
- package/dist/chat/env-token.d.ts.map +0 -1
- package/dist/chat/env-token.js +0 -137
- package/dist/chat/env-token.js.map +0 -1
- package/dist/chat/env-token.test.js +0 -160
- package/dist/chat/factory.d.ts +0 -30
- package/dist/chat/factory.d.ts.map +0 -1
- package/dist/chat/factory.js +0 -50
- package/dist/chat/factory.js.map +0 -1
- package/dist/chat/factory.test.js +0 -55
- package/dist/chat/gateway.d.ts +0 -176
- package/dist/chat/gateway.d.ts.map +0 -1
- package/dist/chat/gateway.js +0 -146
- package/dist/chat/gateway.js.map +0 -1
- package/dist/chat/gateway.test.js +0 -192
- package/dist/claude-md.d.ts +0 -39
- package/dist/claude-md.d.ts.map +0 -1
- package/dist/claude-md.js +0 -113
- package/dist/claude-md.js.map +0 -1
- package/dist/claude-md.test.js +0 -91
- package/dist/codex/activate.d.ts +0 -66
- package/dist/codex/activate.d.ts.map +0 -1
- package/dist/codex/activate.js +0 -329
- package/dist/codex/activate.js.map +0 -1
- package/dist/codex/activate.test.js +0 -229
- package/dist/codex/bundled-default/bundled-default.test.js +0 -161
- package/dist/codex/cli-publish.test.js +0 -133
- package/dist/codex/cli.d.ts +0 -35
- package/dist/codex/cli.d.ts.map +0 -1
- package/dist/codex/cli.js +0 -554
- package/dist/codex/cli.js.map +0 -1
- package/dist/codex/cli.test.js +0 -277
- package/dist/codex/import-skill-md.d.ts +0 -53
- package/dist/codex/import-skill-md.d.ts.map +0 -1
- package/dist/codex/import-skill-md.js +0 -236
- package/dist/codex/import-skill-md.js.map +0 -1
- package/dist/codex/import-skill-md.test.js +0 -225
- package/dist/codex/loader.d.ts +0 -27
- package/dist/codex/loader.d.ts.map +0 -1
- package/dist/codex/loader.js +0 -86
- package/dist/codex/loader.js.map +0 -1
- package/dist/codex/loader.test.js +0 -75
- package/dist/codex/parse.d.ts +0 -28
- package/dist/codex/parse.d.ts.map +0 -1
- package/dist/codex/parse.js +0 -309
- package/dist/codex/parse.js.map +0 -1
- package/dist/codex/parse.test.js +0 -241
- package/dist/codex/store.d.ts +0 -87
- package/dist/codex/store.d.ts.map +0 -1
- package/dist/codex/store.js +0 -205
- package/dist/codex/store.js.map +0 -1
- package/dist/codex/store.test.js +0 -242
- package/dist/codex/types.d.ts +0 -398
- package/dist/codex/types.d.ts.map +0 -1
- package/dist/codex/types.js +0 -21
- package/dist/codex/types.js.map +0 -1
- package/dist/config.d.ts +0 -53
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -202
- package/dist/config.js.map +0 -1
- package/dist/config.test.js +0 -117
- package/dist/engine/cli.d.ts +0 -14
- package/dist/engine/cli.d.ts.map +0 -1
- package/dist/engine/cli.js +0 -171
- package/dist/engine/cli.js.map +0 -1
- package/dist/engine/client.d.ts +0 -219
- package/dist/engine/client.d.ts.map +0 -1
- package/dist/engine/client.js +0 -312
- package/dist/engine/client.js.map +0 -1
- package/dist/engine/config.d.ts +0 -62
- package/dist/engine/config.d.ts.map +0 -1
- package/dist/engine/config.js +0 -223
- package/dist/engine/config.js.map +0 -1
- package/dist/engine/index.d.ts +0 -17
- package/dist/engine/index.d.ts.map +0 -1
- package/dist/engine/index.js +0 -16
- package/dist/engine/index.js.map +0 -1
- package/dist/engine/resolver.d.ts +0 -62
- package/dist/engine/resolver.d.ts.map +0 -1
- package/dist/engine/resolver.js +0 -103
- package/dist/engine/resolver.js.map +0 -1
- package/dist/engine/singleton.d.ts +0 -95
- package/dist/engine/singleton.d.ts.map +0 -1
- package/dist/engine/singleton.js +0 -325
- package/dist/engine/singleton.js.map +0 -1
- package/dist/engine/types.d.ts +0 -402
- package/dist/engine/types.d.ts.map +0 -1
- package/dist/engine/types.js +0 -22
- package/dist/engine/types.js.map +0 -1
- package/dist/engine-binary-resolver.js +0 -110
- package/dist/engine-binary-resolver.test.js +0 -61
- package/dist/engine-cli.js +0 -60
- package/dist/engine-client.js +0 -301
- package/dist/engine-client.test.js +0 -118
- package/dist/functions/chain_state.d.ts +0 -51
- package/dist/functions/chain_state.d.ts.map +0 -1
- package/dist/functions/chain_state.js +0 -59
- package/dist/functions/chain_state.js.map +0 -1
- package/dist/hooks/drift-catalog.d.ts +0 -68
- package/dist/hooks/drift-catalog.d.ts.map +0 -1
- package/dist/hooks/drift-catalog.js +0 -184
- package/dist/hooks/drift-catalog.js.map +0 -1
- package/dist/hooks/drift-catalog.test.js +0 -154
- package/dist/hooks/drift-patterns.d.ts +0 -110
- package/dist/hooks/drift-patterns.d.ts.map +0 -1
- package/dist/hooks/drift-patterns.js +0 -289
- package/dist/hooks/drift-patterns.js.map +0 -1
- package/dist/hooks/drift-patterns.test.js +0 -325
- package/dist/hooks/engine-vocab-gate.d.ts +0 -108
- package/dist/hooks/engine-vocab-gate.d.ts.map +0 -1
- package/dist/hooks/engine-vocab-gate.js +0 -225
- package/dist/hooks/engine-vocab-gate.js.map +0 -1
- package/dist/hooks/engine-vocab-gate.test.js +0 -170
- package/dist/hooks/heartbeat.d.ts +0 -107
- package/dist/hooks/heartbeat.d.ts.map +0 -1
- package/dist/hooks/heartbeat.js +0 -316
- package/dist/hooks/heartbeat.js.map +0 -1
- package/dist/hooks/heartbeat.test.js +0 -393
- package/dist/hooks/honesty-ledger-session-scope.test.js +0 -100
- package/dist/hooks/honesty-ledger.d.ts +0 -123
- package/dist/hooks/honesty-ledger.d.ts.map +0 -1
- package/dist/hooks/honesty-ledger.js +0 -226
- package/dist/hooks/honesty-ledger.js.map +0 -1
- package/dist/hooks/honesty-ledger.test.js +0 -466
- package/dist/hooks/inline-report-check.d.ts +0 -63
- package/dist/hooks/inline-report-check.d.ts.map +0 -1
- package/dist/hooks/inline-report-check.js +0 -88
- package/dist/hooks/inline-report-check.js.map +0 -1
- package/dist/hooks/inline-report-check.test.js +0 -96
- package/dist/hooks/pre-tool-use.d.ts +0 -62
- package/dist/hooks/pre-tool-use.d.ts.map +0 -1
- package/dist/hooks/pre-tool-use.js +0 -342
- package/dist/hooks/pre-tool-use.js.map +0 -1
- package/dist/hooks/pre-tool-use.test.js +0 -134
- package/dist/hooks/session-end.d.ts +0 -15
- package/dist/hooks/session-end.d.ts.map +0 -1
- package/dist/hooks/session-end.js +0 -60
- package/dist/hooks/session-end.js.map +0 -1
- package/dist/hooks/session-end.test.js +0 -52
- package/dist/hooks/stop.d.ts +0 -35
- package/dist/hooks/stop.d.ts.map +0 -1
- package/dist/hooks/stop.js +0 -136
- package/dist/hooks/stop.js.map +0 -1
- package/dist/hooks/transcript-active-task.test.js +0 -342
- package/dist/hooks/transcript.d.ts +0 -26
- package/dist/hooks/transcript.d.ts.map +0 -1
- package/dist/hooks/transcript.js +0 -266
- package/dist/hooks/transcript.js.map +0 -1
- package/dist/hooks/transcript.test.js +0 -103
- package/dist/hooks/user-prompt-submit.d.ts +0 -74
- package/dist/hooks/user-prompt-submit.d.ts.map +0 -1
- package/dist/hooks/user-prompt-submit.js +0 -256
- package/dist/hooks/user-prompt-submit.js.map +0 -1
- package/dist/hooks/user-prompt-submit.test.js +0 -118
- package/dist/hooks/versioning-gate.d.ts +0 -101
- package/dist/hooks/versioning-gate.d.ts.map +0 -1
- package/dist/hooks/versioning-gate.js +0 -245
- package/dist/hooks/versioning-gate.js.map +0 -1
- package/dist/hooks/versioning-gate.test.js +0 -368
- package/dist/hooks/workflow-gate.d.ts +0 -64
- package/dist/hooks/workflow-gate.d.ts.map +0 -1
- package/dist/hooks/workflow-gate.js +0 -152
- package/dist/hooks/workflow-gate.js.map +0 -1
- package/dist/hooks/workflow-gate.test.js +0 -197
- package/dist/hooks-cli.d.ts +0 -25
- package/dist/hooks-cli.d.ts.map +0 -1
- package/dist/hooks-cli.js +0 -286
- package/dist/hooks-cli.js.map +0 -1
- package/dist/hooks-cli.test.js +0 -148
- package/dist/origin.d.ts +0 -16
- package/dist/origin.d.ts.map +0 -1
- package/dist/origin.js +0 -92
- package/dist/origin.js.map +0 -1
- package/dist/packs/seed_lessons_ingest.d.ts +0 -30
- package/dist/packs/seed_lessons_ingest.d.ts.map +0 -1
- package/dist/packs/seed_lessons_ingest.js +0 -107
- package/dist/packs/seed_lessons_ingest.js.map +0 -1
- package/dist/project-cli.d.ts +0 -7
- package/dist/project-cli.d.ts.map +0 -1
- package/dist/project-cli.js +0 -145
- package/dist/project-cli.js.map +0 -1
- package/dist/project.d.ts +0 -127
- package/dist/project.d.ts.map +0 -1
- package/dist/project.js +0 -281
- package/dist/project.js.map +0 -1
- package/dist/project.test.js +0 -287
- package/dist/rag/backends/loop_engine.d.ts +0 -61
- package/dist/rag/backends/loop_engine.d.ts.map +0 -1
- package/dist/rag/backends/loop_engine.js +0 -160
- package/dist/rag/backends/loop_engine.js.map +0 -1
- package/dist/recall.d.ts +0 -82
- package/dist/recall.d.ts.map +0 -1
- package/dist/recall.js +0 -81
- package/dist/recall.js.map +0 -1
- package/dist/runtime/agent_bridge/autospawn.d.ts +0 -131
- package/dist/runtime/agent_bridge/autospawn.d.ts.map +0 -1
- package/dist/runtime/agent_bridge/autospawn.js +0 -251
- package/dist/runtime/agent_bridge/autospawn.js.map +0 -1
- package/dist/runtime/chain_state.d.ts +0 -124
- package/dist/runtime/chain_state.d.ts.map +0 -1
- package/dist/runtime/chain_state.js +0 -189
- package/dist/runtime/chain_state.js.map +0 -1
- package/dist/runtime/hooks/permission_decision.d.ts +0 -34
- package/dist/runtime/hooks/permission_decision.d.ts.map +0 -1
- package/dist/runtime/hooks/permission_decision.js +0 -39
- package/dist/runtime/hooks/permission_decision.js.map +0 -1
- package/dist/runtime/workflow_fsm.d.ts +0 -21
- package/dist/runtime/workflow_fsm.d.ts.map +0 -1
- package/dist/runtime/workflow_fsm.js +0 -25
- package/dist/runtime/workflow_fsm.js.map +0 -1
- package/dist/runtime/workflow_map.d.ts +0 -26
- package/dist/runtime/workflow_map.d.ts.map +0 -1
- package/dist/runtime/workflow_map.js +0 -38
- package/dist/runtime/workflow_map.js.map +0 -1
- package/dist/scope.d.ts +0 -48
- package/dist/scope.d.ts.map +0 -1
- package/dist/scope.js +0 -111
- package/dist/scope.js.map +0 -1
- package/dist/setup/cli/topic_create_step.d.ts +0 -84
- package/dist/setup/cli/topic_create_step.d.ts.map +0 -1
- package/dist/setup/cli/topic_create_step.js +0 -213
- package/dist/setup/cli/topic_create_step.js.map +0 -1
- package/dist/system-export.d.ts +0 -65
- package/dist/system-export.d.ts.map +0 -1
- package/dist/system-export.js +0 -194
- package/dist/system-export.js.map +0 -1
- package/dist/utterance/classifier.d.ts +0 -53
- package/dist/utterance/classifier.d.ts.map +0 -1
- package/dist/utterance/classifier.js +0 -184
- package/dist/utterance/classifier.js.map +0 -1
- package/dist/utterance/classifier.test.js +0 -147
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Drift pattern catalog — known anti-patterns opensquid intercepts at
|
|
3
|
-
* the Claude Code PreToolUse hook before the agent commits the action.
|
|
4
|
-
*
|
|
5
|
-
* Each pattern has:
|
|
6
|
-
* - `id` — stable identifier for the rule
|
|
7
|
-
* - `trigger` — matcher against tool call input
|
|
8
|
-
* - `lesson` — short reference to the lesson that owns this rule
|
|
9
|
-
* - `message` — what the agent sees in stderr when intercepted
|
|
10
|
-
* - `severity` — "block" (exit 2 stops the call) or "warn" (stderr
|
|
11
|
-
* only, call proceeds)
|
|
12
|
-
*
|
|
13
|
-
* Patterns are CONSERVATIVE on purpose: we'd rather miss a drift than
|
|
14
|
-
* spam false positives. The catalog grows lesson-by-lesson as new
|
|
15
|
-
* drifts are observed and the user endorses the rule.
|
|
16
|
-
*/
|
|
17
|
-
// ---------------------------------------------------------------------
|
|
18
|
-
// Catalog — start with drifts observed in actual build sessions
|
|
19
|
-
// ---------------------------------------------------------------------
|
|
20
|
-
export const DRIFT_PATTERNS = [
|
|
21
|
-
// 1. git commit --amend — never amend (locked override, 2026-05-15)
|
|
22
|
-
{
|
|
23
|
-
id: "never-amend",
|
|
24
|
-
tool: "Bash",
|
|
25
|
-
trigger: { kind: "bash_regex", pattern: "git\\s+commit\\b[^\\n]*\\s--amend\\b" },
|
|
26
|
-
lesson: "auto-commit",
|
|
27
|
-
severity: "block",
|
|
28
|
-
message: "BLOCKED: `git commit --amend` violates the never-amend rule (CLAUDE.md " +
|
|
29
|
-
"claude-overrides:v1, feedback_auto_commit). Even on unpushed commits — " +
|
|
30
|
-
"make a follow-up commit instead. Override only if the user explicitly " +
|
|
31
|
-
"requested an amend in THIS turn.",
|
|
32
|
-
},
|
|
33
|
-
// 2. git push without explicit user request — block by default
|
|
34
|
-
{
|
|
35
|
-
id: "no-implicit-push",
|
|
36
|
-
tool: "Bash",
|
|
37
|
-
trigger: { kind: "bash_regex", pattern: "git\\s+push\\b" },
|
|
38
|
-
lesson: "auto-commit",
|
|
39
|
-
severity: "block",
|
|
40
|
-
message: "BLOCKED: `git push` requires explicit user authorization. " +
|
|
41
|
-
"Commits stay local until the user says push. If the user just said " +
|
|
42
|
-
"to push (or has pre-authorized pushes per CLAUDE.md), bypass with " +
|
|
43
|
-
"OPENSQUID_SKIP_DRIFT=1 for this command.",
|
|
44
|
-
},
|
|
45
|
-
// 3. Engine commit subject containing consumer-product strings
|
|
46
|
-
// (substrate-purity rule). Heuristic: matches `git commit -m` in a
|
|
47
|
-
// bash command whose body contains `codex`, `opensquid`, or
|
|
48
|
-
// `MindCraftor` AND is running inside engine/.
|
|
49
|
-
//
|
|
50
|
-
// This one INTENTIONALLY peeks inside the -m "..." quoted message,
|
|
51
|
-
// so it opts out of the default quote stripping.
|
|
52
|
-
{
|
|
53
|
-
id: "substrate-purity",
|
|
54
|
-
tool: "Bash",
|
|
55
|
-
trigger: {
|
|
56
|
-
kind: "bash_regex",
|
|
57
|
-
pattern: "loop/engine.*git\\s+commit[^\\n]*-m[^\\n]*(codex|opensquid|MindCraftor)",
|
|
58
|
-
strip_quotes: false,
|
|
59
|
-
},
|
|
60
|
-
lesson: "code-quality",
|
|
61
|
-
severity: "warn",
|
|
62
|
-
message: "WARN: engine commit message appears to reference a consumer-product " +
|
|
63
|
-
"concept (codex / opensquid / MindCraftor). Engine commit messages " +
|
|
64
|
-
"must stay substrate-pure — engine speaks in engine types only. " +
|
|
65
|
-
"Re-word using Pack provenance / lesson / authorship terminology.",
|
|
66
|
-
},
|
|
67
|
-
// 4. Force-push to main/master — extra protection on top of #2
|
|
68
|
-
{
|
|
69
|
-
id: "no-force-push-main",
|
|
70
|
-
tool: "Bash",
|
|
71
|
-
trigger: {
|
|
72
|
-
kind: "bash_regex",
|
|
73
|
-
pattern: "git\\s+push\\b[^\\n]*(--force|-f)\\b[^\\n]*\\b(main|master)\\b",
|
|
74
|
-
},
|
|
75
|
-
lesson: "auto-commit",
|
|
76
|
-
severity: "block",
|
|
77
|
-
message: "BLOCKED: force-push to main/master is destructive. Requires explicit " +
|
|
78
|
-
"user request — and even then, prefer a regular push or a new branch.",
|
|
79
|
-
},
|
|
80
|
-
// 5. Telegram routing — task-completion reports sent to plugin:telegram
|
|
81
|
-
// reply (DM) instead of opensquid chat_send (supergroup). 0.7.24 / D2.
|
|
82
|
-
// Heuristic: the text starts with the agent's report marker `🦑 #<N>`.
|
|
83
|
-
{
|
|
84
|
-
id: "telegram-redirect-report",
|
|
85
|
-
tool: "mcp__plugin_telegram_telegram__reply",
|
|
86
|
-
trigger: { kind: "text_regex", pattern: "^\\s*🦑\\s+#\\d", field: "text" },
|
|
87
|
-
lesson: "telegram-reports",
|
|
88
|
-
severity: "warn",
|
|
89
|
-
message: "WARN: this message looks like a task-completion report (starts with `🦑 #N`). " +
|
|
90
|
-
"Per [[feedback_telegram_reports]], reports go via `mcp__opensquid__chat_send` " +
|
|
91
|
-
"to the project's `report_channel` (supergroup + topic), not via " +
|
|
92
|
-
"plugin:telegram reply (which is the user's DM). Re-route via chat_send if " +
|
|
93
|
-
"this is meant to be a task report. Catches drift D2.",
|
|
94
|
-
},
|
|
95
|
-
// 6. Bundled multi-purpose commit — `git commit -m` message that
|
|
96
|
-
// references 2+ distinct task numbers. The D4 incident was commit
|
|
97
|
-
// bef7eff bundling "close #166 + defer #168 + section-header
|
|
98
|
-
// rewrite" into one commit. The 2-task-ref heuristic is narrow
|
|
99
|
-
// enough to avoid most legitimate single-purpose commits while
|
|
100
|
-
// catching the typical bundle shape ("close #X and defer #Y").
|
|
101
|
-
// 0.7.28 / D4. strip_quotes=false so the -m body is scanned.
|
|
102
|
-
{
|
|
103
|
-
id: "bundled-commit",
|
|
104
|
-
tool: "Bash",
|
|
105
|
-
trigger: {
|
|
106
|
-
kind: "bash_regex",
|
|
107
|
-
pattern: "git\\s+commit\\b[^\\n]*-m\\b[^\\n]*#\\d+[^\\n]*#\\d+",
|
|
108
|
-
strip_quotes: false,
|
|
109
|
-
},
|
|
110
|
-
lesson: "auto-commit",
|
|
111
|
-
severity: "warn",
|
|
112
|
-
message: "WARN: commit message references 2+ task numbers (`#N`). Per the auto-commit " +
|
|
113
|
-
"rule (CLAUDE.md), prefer multiple small logical commits over one large " +
|
|
114
|
-
"catchall. If these task numbers are genuinely one logical unit (e.g. one " +
|
|
115
|
-
"task closing two others as duplicates), proceed. Catches drift D4.",
|
|
116
|
-
},
|
|
117
|
-
];
|
|
118
|
-
/**
|
|
119
|
-
* Run the catalog against a tool call. Returns every matching pattern;
|
|
120
|
-
* caller decides block-vs-warn based on highest severity.
|
|
121
|
-
*/
|
|
122
|
-
export function findDrifts(call) {
|
|
123
|
-
const hits = [];
|
|
124
|
-
for (const pattern of DRIFT_PATTERNS) {
|
|
125
|
-
if (pattern.tool !== "*" && pattern.tool !== call.tool)
|
|
126
|
-
continue;
|
|
127
|
-
if (matches(pattern.trigger, call)) {
|
|
128
|
-
hits.push({ pattern });
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return hits;
|
|
132
|
-
}
|
|
133
|
-
function matches(trigger, call) {
|
|
134
|
-
switch (trigger.kind) {
|
|
135
|
-
case "bash_contains": {
|
|
136
|
-
const cmd = stringField(call.input, "command");
|
|
137
|
-
if (cmd === null)
|
|
138
|
-
return false;
|
|
139
|
-
const haystack = trigger.strip_quotes === false ? cmd : stripQuotedStrings(cmd);
|
|
140
|
-
return haystack.includes(trigger.needle);
|
|
141
|
-
}
|
|
142
|
-
case "bash_regex": {
|
|
143
|
-
const cmd = stringField(call.input, "command");
|
|
144
|
-
if (cmd === null)
|
|
145
|
-
return false;
|
|
146
|
-
const haystack = trigger.strip_quotes === false ? cmd : stripQuotedStrings(cmd);
|
|
147
|
-
try {
|
|
148
|
-
return new RegExp(trigger.pattern).test(haystack);
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
case "text_regex": {
|
|
155
|
-
const text = stringField(call.input, trigger.field);
|
|
156
|
-
if (text === null)
|
|
157
|
-
return false;
|
|
158
|
-
try {
|
|
159
|
-
return new RegExp(trigger.pattern).test(text);
|
|
160
|
-
}
|
|
161
|
-
catch {
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Remove single- and double-quoted string contents PLUS HEREDOC bodies
|
|
169
|
-
* from a shell command so drift patterns match REAL shell tokens, not
|
|
170
|
-
* text that happens to appear inside `echo "..."`, `grep '...'`, or a
|
|
171
|
-
* `git commit -m "$(cat <<'EOF' ... EOF)"` body.
|
|
172
|
-
*
|
|
173
|
-
* Approximate: doesn't handle backslash-escaped quotes or `$(...)`
|
|
174
|
-
* nesting beyond the HEREDOC. Sufficient for the false-positive cases
|
|
175
|
-
* observed in real Claude Code sessions; tighten if a real-world
|
|
176
|
-
* counter-example surfaces.
|
|
177
|
-
*
|
|
178
|
-
* v0.6.5 (#136) — added HEREDOC body stripping. Previously, a
|
|
179
|
-
* `git commit -m` with a HEREDOC message body containing the literal
|
|
180
|
-
* string "git push" would false-fire the no-implicit-push drift block
|
|
181
|
-
* because the entire HEREDOC body was part of the bash command string.
|
|
182
|
-
* Now the body is stripped before pattern matching. Caught dogfooding
|
|
183
|
-
* the v0.6.4 commit (commit message described regex patterns containing
|
|
184
|
-
* the word "git push" and the drift-block fired against itself).
|
|
185
|
-
*
|
|
186
|
-
* Replacement is empty rather than a placeholder so adjacent tokens
|
|
187
|
-
* still parse correctly (e.g. `cmd "literal" && more` → `cmd && more`).
|
|
188
|
-
*/
|
|
189
|
-
function stripQuotedStrings(s) {
|
|
190
|
-
return stripHeredocBodies(s)
|
|
191
|
-
.replace(/'[^']*'/g, "")
|
|
192
|
-
.replace(/"[^"]*"/g, "");
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Strip HEREDOC bodies (`<<DELIM ... DELIM` and variants) from a
|
|
196
|
-
* shell command.
|
|
197
|
-
*
|
|
198
|
-
* Recognizes:
|
|
199
|
-
* <<EOF ... \nEOF (unquoted delimiter, expansion-allowing)
|
|
200
|
-
* <<'EOF' ... \nEOF (single-quoted, literal body)
|
|
201
|
-
* <<"EOF" ... \nEOF (double-quoted, literal body)
|
|
202
|
-
* <<-EOF ... \nEOF (tab-stripping mode)
|
|
203
|
-
* <<-'EOF' ... \nEOF (combined)
|
|
204
|
-
*
|
|
205
|
-
* Delimiter is any word-char sequence (EOF, END, HERE, MARKER, etc.).
|
|
206
|
-
* Lazy `[\s\S]*?` matches across newlines; `\b` after the backref
|
|
207
|
-
* ensures `EOFX` doesn't close an `<<EOF` block.
|
|
208
|
-
*
|
|
209
|
-
* If a HEREDOC has no closing delimiter (truncated input), regex
|
|
210
|
-
* doesn't match and the body stays intact — fail-open behavior.
|
|
211
|
-
*
|
|
212
|
-
* Exported for direct unit testing.
|
|
213
|
-
*/
|
|
214
|
-
export function stripHeredocBodies(s) {
|
|
215
|
-
// `\n[ \t]*\1\b` — allow leading whitespace before the closing
|
|
216
|
-
// delimiter so the `<<-DELIM` (tab-stripping) variant matches its
|
|
217
|
-
// indented closing line (`\t\tEOF`). The permissive whitespace
|
|
218
|
-
// also covers the plain `<<DELIM` case where users sometimes
|
|
219
|
-
// accidentally indent the closing line — no real harm.
|
|
220
|
-
return s.replace(/<<-?\s*['"]?(\w+)['"]?[\s\S]*?\n[ \t]*\1\b/g, "");
|
|
221
|
-
}
|
|
222
|
-
function stringField(input, field) {
|
|
223
|
-
const v = input[field];
|
|
224
|
-
return typeof v === "string" ? v : null;
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Decide the final exit code + message from a list of hits.
|
|
228
|
-
*
|
|
229
|
-
* - Any "block" hit → exit 2 with all blocking messages
|
|
230
|
-
* - Only "warn" hits → exit 0, print warnings to stderr
|
|
231
|
-
* - No hits → exit 0 silently
|
|
232
|
-
*
|
|
233
|
-
* Emergency bypass: `OPENSQUID_SKIP_DRIFT=1` downgrades every block to
|
|
234
|
-
* an audit-trail warning (exit 0, stderr explains the bypass). Two ways
|
|
235
|
-
* to set it:
|
|
236
|
-
*
|
|
237
|
-
* 1. Parent process env — useful for whole-session bypass (e.g. set
|
|
238
|
-
* before launching Claude Code).
|
|
239
|
-
* 2. Inline command prefix — e.g. `OPENSQUID_SKIP_DRIFT=1 git push`.
|
|
240
|
-
* The hook reads the COMMAND STRING from the Bash tool input and
|
|
241
|
-
* sees the prefix even though the env var never reaches the hook's
|
|
242
|
-
* own process.env (the hook is a sibling subprocess spawned by
|
|
243
|
-
* Claude Code, not a child of the would-be Bash subprocess).
|
|
244
|
-
*
|
|
245
|
-
* Matches the shape of the version-gate (`OPENSQUID_SKIP_VERSION_GATE=1`)
|
|
246
|
-
* and workflow-gate (`OPENSQUID_SKIP_WORKFLOW_GATE=1`) bypasses so the
|
|
247
|
-
* operator only has one mental model — except those two only check
|
|
248
|
-
* process.env (their hooks happen before any command runs); drift-
|
|
249
|
-
* patterns additionally checks the command-string prefix so the bypass
|
|
250
|
-
* can be requested per-command from within an existing session.
|
|
251
|
-
*/
|
|
252
|
-
export function decide(hits, call) {
|
|
253
|
-
if (hits.length === 0)
|
|
254
|
-
return { exit: 0, stderr: "" };
|
|
255
|
-
if (isDriftBypassed(call)) {
|
|
256
|
-
const ids = hits.map((h) => h.pattern.id).join(", ");
|
|
257
|
-
return {
|
|
258
|
-
exit: 0,
|
|
259
|
-
stderr: `🦑 [opensquid drift-patterns] BYPASSED via OPENSQUID_SKIP_DRIFT=1 (hits: ${ids})\n`,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
const blocks = hits.filter((h) => h.pattern.severity === "block");
|
|
263
|
-
const warns = hits.filter((h) => h.pattern.severity === "warn");
|
|
264
|
-
const lines = [];
|
|
265
|
-
for (const h of blocks) {
|
|
266
|
-
lines.push(`🦑 [opensquid drift-block] ${h.pattern.id}: ${h.pattern.message}`);
|
|
267
|
-
}
|
|
268
|
-
for (const h of warns) {
|
|
269
|
-
lines.push(`🦑 [opensquid drift-warn] ${h.pattern.id}: ${h.pattern.message}`);
|
|
270
|
-
}
|
|
271
|
-
return {
|
|
272
|
-
exit: blocks.length > 0 ? 2 : 0,
|
|
273
|
-
stderr: lines.join("\n") + "\n",
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
function isDriftBypassed(call) {
|
|
277
|
-
if (process.env.OPENSQUID_SKIP_DRIFT === "1")
|
|
278
|
-
return true;
|
|
279
|
-
if (!call)
|
|
280
|
-
return false;
|
|
281
|
-
const cmd = stringField(call.input, "command");
|
|
282
|
-
if (cmd === null)
|
|
283
|
-
return false;
|
|
284
|
-
// Inline prefix: zero or more env-var assignments may precede the
|
|
285
|
-
// bypass var. Permissive on whitespace; strict on the value (must be
|
|
286
|
-
// literally "1" to match the env-var semantics).
|
|
287
|
-
return /(^|\s|;|&&)\s*OPENSQUID_SKIP_DRIFT=1(\s|$)/.test(cmd);
|
|
288
|
-
}
|
|
289
|
-
//# sourceMappingURL=drift-patterns.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"drift-patterns.js","sourceRoot":"","sources":["../../src.legacy/hooks/drift-patterns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAyBH,wEAAwE;AACxE,gEAAgE;AAChE,wEAAwE;AAExE,MAAM,CAAC,MAAM,cAAc,GAAmB;IAC5C,oEAAoE;IACpE;QACE,EAAE,EAAE,aAAa;QACjB,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,sCAAsC,EAAE;QAChF,MAAM,EAAE,aAAa;QACrB,QAAQ,EAAE,OAAO;QACjB,OAAO,EACL,yEAAyE;YACzE,yEAAyE;YACzE,wEAAwE;YACxE,kCAAkC;KACrC;IAED,+DAA+D;IAC/D;QACE,EAAE,EAAE,kBAAkB;QACtB,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,gBAAgB,EAAE;QAC1D,MAAM,EAAE,aAAa;QACrB,QAAQ,EAAE,OAAO;QACjB,OAAO,EACL,4DAA4D;YAC5D,qEAAqE;YACrE,oEAAoE;YACpE,0CAA0C;KAC7C;IAED,+DAA+D;IAC/D,sEAAsE;IACtE,+DAA+D;IAC/D,kDAAkD;IAClD,EAAE;IACF,sEAAsE;IACtE,oDAAoD;IACpD;QACE,EAAE,EAAE,kBAAkB;QACtB,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE;YACP,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,yEAAyE;YAClF,YAAY,EAAE,KAAK;SACpB;QACD,MAAM,EAAE,cAAc;QACtB,QAAQ,EAAE,MAAM;QAChB,OAAO,EACL,sEAAsE;YACtE,oEAAoE;YACpE,iEAAiE;YACjE,kEAAkE;KACrE;IAED,+DAA+D;IAC/D;QACE,EAAE,EAAE,oBAAoB;QACxB,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE;YACP,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,gEAAgE;SAC1E;QACD,MAAM,EAAE,aAAa;QACrB,QAAQ,EAAE,OAAO;QACjB,OAAO,EACL,uEAAuE;YACvE,sEAAsE;KACzE;IAED,wEAAwE;IACxE,0EAA0E;IAC1E,0EAA0E;IAC1E;QACE,EAAE,EAAE,0BAA0B;QAC9B,IAAI,EAAE,sCAAsC;QAC5C,OAAO,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE;QAC1E,MAAM,EAAE,kBAAkB;QAC1B,QAAQ,EAAE,MAAM;QAChB,OAAO,EACL,gFAAgF;YAChF,gFAAgF;YAChF,kEAAkE;YAClE,4EAA4E;YAC5E,sDAAsD;KACzD;IAED,iEAAiE;IACjE,qEAAqE;IACrE,gEAAgE;IAChE,kEAAkE;IAClE,kEAAkE;IAClE,kEAAkE;IAClE,gEAAgE;IAChE;QACE,EAAE,EAAE,gBAAgB;QACpB,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE;YACP,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,sDAAsD;YAC/D,YAAY,EAAE,KAAK;SACpB;QACD,MAAM,EAAE,aAAa;QACrB,QAAQ,EAAE,MAAM;QAChB,OAAO,EACL,8EAA8E;YAC9E,yEAAyE;YACzE,2EAA2E;YAC3E,oEAAoE;KACvE;CACF,CAAC;AAeF;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,IAAmB;IAC5C,MAAM,IAAI,GAAe,EAAE,CAAC;IAC5B,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,IAAI,KAAK,GAAG,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;YAAE,SAAS;QACjE,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;YACnC,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,OAAO,CAAC,OAAqB,EAAE,IAAmB;IACzD,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,eAAe,CAAC,CAAC,CAAC;YACrB,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YAC/C,IAAI,GAAG,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YAC/B,MAAM,QAAQ,GAAG,OAAO,CAAC,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;YAChF,OAAO,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC3C,CAAC;QACD,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YAC/C,IAAI,GAAG,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YAC/B,MAAM,QAAQ,GAAG,OAAO,CAAC,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;YAChF,IAAI,CAAC;gBACH,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACpD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QACD,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YACpD,IAAI,IAAI,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YAChC,IAAI,CAAC;gBACH,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,SAAS,kBAAkB,CAAC,CAAS;IACnC,OAAO,kBAAkB,CAAC,CAAC,CAAC;SACzB,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,kBAAkB,CAAC,CAAS;IAC1C,+DAA+D;IAC/D,kEAAkE;IAClE,+DAA+D;IAC/D,6DAA6D;IAC7D,uDAAuD;IACvD,OAAO,CAAC,CAAC,OAAO,CAAC,6CAA6C,EAAE,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,WAAW,CAAC,KAA8B,EAAE,KAAa;IAChE,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IACvB,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,MAAM,CACpB,IAAgB,EAChB,IAAoB;IAKpB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACtD,IAAI,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,OAAO;YACL,IAAI,EAAE,CAAC;YACP,MAAM,EAAE,4EAA4E,GAAG,KAAK;SAC7F,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC;IAChE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACjF,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACjF,CAAC;IACD,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI;KAChC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,IAAoB;IAC3C,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAC1D,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAC/C,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC/B,kEAAkE;IAClE,qEAAqE;IACrE,iDAAiD;IACjD,OAAO,4CAA4C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChE,CAAC"}
|
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { decide, findDrifts, stripHeredocBodies } from "./drift-patterns.js";
|
|
3
|
-
function bash(command) {
|
|
4
|
-
return { tool: "Bash", input: { command } };
|
|
5
|
-
}
|
|
6
|
-
function telegramReply(text) {
|
|
7
|
-
return {
|
|
8
|
-
tool: "mcp__plugin_telegram_telegram__reply",
|
|
9
|
-
input: { chat_id: "8075471258", text },
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
describe("drift catalog — never-amend", () => {
|
|
13
|
-
it("blocks `git commit --amend`", () => {
|
|
14
|
-
const hits = findDrifts(bash('git commit --amend -m "fix typo"'));
|
|
15
|
-
expect(hits.map((h) => h.pattern.id)).toContain("never-amend");
|
|
16
|
-
expect(decide(hits).exit).toBe(2);
|
|
17
|
-
});
|
|
18
|
-
it("blocks `git commit -a --amend`", () => {
|
|
19
|
-
const hits = findDrifts(bash("git commit -a --amend"));
|
|
20
|
-
expect(hits.map((h) => h.pattern.id)).toContain("never-amend");
|
|
21
|
-
});
|
|
22
|
-
it("doesn't fire on a normal `git commit -m`", () => {
|
|
23
|
-
const hits = findDrifts(bash('git commit -m "regular commit"'));
|
|
24
|
-
expect(hits.map((h) => h.pattern.id)).not.toContain("never-amend");
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
describe("drift catalog — no-implicit-push", () => {
|
|
28
|
-
it("blocks `git push`", () => {
|
|
29
|
-
const hits = findDrifts(bash("git push origin main"));
|
|
30
|
-
expect(hits.map((h) => h.pattern.id)).toContain("no-implicit-push");
|
|
31
|
-
expect(decide(hits).exit).toBe(2);
|
|
32
|
-
});
|
|
33
|
-
it("blocks `git push -u origin feature`", () => {
|
|
34
|
-
const hits = findDrifts(bash("git push -u origin feature/x"));
|
|
35
|
-
expect(hits.map((h) => h.pattern.id)).toContain("no-implicit-push");
|
|
36
|
-
});
|
|
37
|
-
it("force-push to main is ALSO caught by both rules", () => {
|
|
38
|
-
const hits = findDrifts(bash("git push --force origin main"));
|
|
39
|
-
const ids = hits.map((h) => h.pattern.id);
|
|
40
|
-
expect(ids).toContain("no-implicit-push");
|
|
41
|
-
expect(ids).toContain("no-force-push-main");
|
|
42
|
-
expect(decide(hits).exit).toBe(2);
|
|
43
|
-
});
|
|
44
|
-
it("doesn't fire on `git pull` or `git status`", () => {
|
|
45
|
-
expect(findDrifts(bash("git pull")).length).toBe(0);
|
|
46
|
-
expect(findDrifts(bash("git status")).length).toBe(0);
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
describe("drift catalog — substrate-purity (engine commits)", () => {
|
|
50
|
-
it("warns on engine commit message referencing 'codex'", () => {
|
|
51
|
-
const hits = findDrifts(bash('cd /Users/slee/projects/loop/engine && git commit -m "v1.1: codex support"'));
|
|
52
|
-
expect(hits.map((h) => h.pattern.id)).toContain("substrate-purity");
|
|
53
|
-
// Severity = warn, so exit stays 0 (call proceeds).
|
|
54
|
-
expect(decide(hits).exit).toBe(0);
|
|
55
|
-
});
|
|
56
|
-
it("warns on engine commit referencing 'opensquid'", () => {
|
|
57
|
-
const hits = findDrifts(bash('cd ~/projects/loop/engine && git commit -m "support for opensquid pack"'));
|
|
58
|
-
expect(hits.map((h) => h.pattern.id)).toContain("substrate-purity");
|
|
59
|
-
});
|
|
60
|
-
it("doesn't fire on substrate-pure engine commits", () => {
|
|
61
|
-
const hits = findDrifts(bash('cd /Users/slee/projects/loop/engine && git commit -m "v1.1: Pack authorship"'));
|
|
62
|
-
expect(hits.map((h) => h.pattern.id)).not.toContain("substrate-purity");
|
|
63
|
-
});
|
|
64
|
-
it("doesn't fire on opensquid commits mentioning codex (correct context)", () => {
|
|
65
|
-
const hits = findDrifts(bash('cd /Users/slee/projects/opensquid && git commit -m "v0.4: codex CLI"'));
|
|
66
|
-
expect(hits.map((h) => h.pattern.id)).not.toContain("substrate-purity");
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
describe("decide — combining hits", () => {
|
|
70
|
-
it("returns exit 0 + empty stderr on no hits", () => {
|
|
71
|
-
const { exit, stderr } = decide([]);
|
|
72
|
-
expect(exit).toBe(0);
|
|
73
|
-
expect(stderr).toBe("");
|
|
74
|
-
});
|
|
75
|
-
it("returns exit 2 if any hit is severity=block", () => {
|
|
76
|
-
const hits = findDrifts(bash("git push --force origin main"));
|
|
77
|
-
const { exit, stderr } = decide(hits);
|
|
78
|
-
expect(exit).toBe(2);
|
|
79
|
-
expect(stderr).toContain("BLOCKED");
|
|
80
|
-
});
|
|
81
|
-
it("returns exit 0 + stderr on warn-only hits", () => {
|
|
82
|
-
const hits = findDrifts(bash('cd /Users/slee/projects/loop/engine && git commit -m "codex stuff"'));
|
|
83
|
-
const { exit, stderr } = decide(hits);
|
|
84
|
-
expect(exit).toBe(0);
|
|
85
|
-
expect(stderr).toContain("WARN");
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
describe("non-bash tools are not matched by bash rules", () => {
|
|
89
|
-
it("Edit calls bypass git rules", () => {
|
|
90
|
-
const call = {
|
|
91
|
-
tool: "Edit",
|
|
92
|
-
input: { file_path: "/x/y", old_string: "git commit --amend", new_string: "" },
|
|
93
|
-
};
|
|
94
|
-
expect(findDrifts(call).length).toBe(0);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
describe("false-positive resistance — patterns inside quoted strings", () => {
|
|
98
|
-
it("never-amend ignores --amend inside double-quoted string", () => {
|
|
99
|
-
const hits = findDrifts(bash('echo "git commit --amend in a string literal"'));
|
|
100
|
-
expect(hits.map((h) => h.pattern.id)).not.toContain("never-amend");
|
|
101
|
-
});
|
|
102
|
-
it("never-amend ignores --amend inside single-quoted string", () => {
|
|
103
|
-
const hits = findDrifts(bash("grep 'git commit --amend' file.txt"));
|
|
104
|
-
expect(hits.map((h) => h.pattern.id)).not.toContain("never-amend");
|
|
105
|
-
});
|
|
106
|
-
it("no-implicit-push ignores 'git push' in an echo literal", () => {
|
|
107
|
-
const hits = findDrifts(bash('echo "to deploy run git push origin main"'));
|
|
108
|
-
expect(hits.map((h) => h.pattern.id)).not.toContain("no-implicit-push");
|
|
109
|
-
});
|
|
110
|
-
it("never-amend STILL fires when amend is a real shell token", () => {
|
|
111
|
-
const hits = findDrifts(bash('git commit --amend -m "fix typo"'));
|
|
112
|
-
expect(hits.map((h) => h.pattern.id)).toContain("never-amend");
|
|
113
|
-
});
|
|
114
|
-
it("never-amend fires after shell continuation (&&, ;, |)", () => {
|
|
115
|
-
expect(findDrifts(bash("cd /repo && git commit --amend")).map((h) => h.pattern.id)).toContain("never-amend");
|
|
116
|
-
expect(findDrifts(bash("foo; git commit --amend")).map((h) => h.pattern.id)).toContain("never-amend");
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
// =====================================================================
|
|
120
|
-
// v0.6.5 (#136) — HEREDOC body stripping. Caught while dogfooding the
|
|
121
|
-
// v0.6.4 commit: the no-implicit-push drift fired against a `git commit`
|
|
122
|
-
// whose HEREDOC commit message body contained the literal string
|
|
123
|
-
// describing a regex pattern (the words `git push` appeared in prose
|
|
124
|
-
// describing a pattern). The hook scanned the entire bash command
|
|
125
|
-
// string including HEREDOC bodies → false-positive block.
|
|
126
|
-
//
|
|
127
|
-
// Fix: stripHeredocBodies runs before stripQuotedStrings so the body
|
|
128
|
-
// is removed before any drift regex sees it.
|
|
129
|
-
// =====================================================================
|
|
130
|
-
describe("stripHeredocBodies (v0.6.5)", () => {
|
|
131
|
-
it("strips single-quoted-delimiter HEREDOC body", () => {
|
|
132
|
-
const cmd = `git commit -m "$(cat <<'EOF'
|
|
133
|
-
This body contains git push origin main
|
|
134
|
-
EOF
|
|
135
|
-
)"`;
|
|
136
|
-
expect(stripHeredocBodies(cmd)).not.toContain("git push");
|
|
137
|
-
});
|
|
138
|
-
it("strips unquoted-delimiter HEREDOC body", () => {
|
|
139
|
-
const cmd = `cat <<MARKER
|
|
140
|
-
inner content with git push verbatim
|
|
141
|
-
MARKER`;
|
|
142
|
-
expect(stripHeredocBodies(cmd)).not.toContain("git push");
|
|
143
|
-
});
|
|
144
|
-
it("strips double-quoted-delimiter HEREDOC body", () => {
|
|
145
|
-
const cmd = `cat <<"END"
|
|
146
|
-
git push --force here
|
|
147
|
-
END`;
|
|
148
|
-
expect(stripHeredocBodies(cmd)).not.toContain("git push");
|
|
149
|
-
});
|
|
150
|
-
it("strips tab-stripping (<<-) variant", () => {
|
|
151
|
-
const cmd = `cat <<-EOF
|
|
152
|
-
\t\tgit push danger
|
|
153
|
-
\tEOF`;
|
|
154
|
-
expect(stripHeredocBodies(cmd)).not.toContain("git push");
|
|
155
|
-
});
|
|
156
|
-
it("strips multiple HEREDOCs in one command", () => {
|
|
157
|
-
const cmd = `cat <<'A'
|
|
158
|
-
contains git push
|
|
159
|
-
A
|
|
160
|
-
echo "between"
|
|
161
|
-
cat <<'B'
|
|
162
|
-
contains git commit --amend
|
|
163
|
-
B`;
|
|
164
|
-
const stripped = stripHeredocBodies(cmd);
|
|
165
|
-
expect(stripped).not.toContain("git push");
|
|
166
|
-
expect(stripped).not.toContain("git commit --amend");
|
|
167
|
-
});
|
|
168
|
-
it("leaves a truncated HEREDOC (no closing delimiter) intact (fail-open)", () => {
|
|
169
|
-
const cmd = `cat <<EOF
|
|
170
|
-
truncated body with git push but no EOF closing`;
|
|
171
|
-
// No \nEOF\b on its own → regex doesn't match → fail-open
|
|
172
|
-
expect(stripHeredocBodies(cmd)).toContain("git push");
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
describe("drift catalog — HEREDOC false-positive resistance (v0.6.5 #136)", () => {
|
|
176
|
-
it("no-implicit-push does NOT fire when 'git push' appears only in a HEREDOC commit message", () => {
|
|
177
|
-
// This is the exact pattern that bit me during the v0.6.4 commit.
|
|
178
|
-
const cmd = `git -c commit.gpgsign=false commit -m "$(cat <<'EOF'
|
|
179
|
-
feat: blah
|
|
180
|
-
|
|
181
|
-
- pushed (bash_regex git push) — this LITERAL string in the message
|
|
182
|
-
body would have tripped the no-implicit-push drift block before
|
|
183
|
-
the v0.6.5 fix.
|
|
184
|
-
EOF
|
|
185
|
-
)"`;
|
|
186
|
-
const hits = findDrifts(bash(cmd));
|
|
187
|
-
expect(hits.map((h) => h.pattern.id)).not.toContain("no-implicit-push");
|
|
188
|
-
});
|
|
189
|
-
it("never-amend does NOT fire on 'git commit --amend' in HEREDOC commit body", () => {
|
|
190
|
-
const cmd = `git commit -m "$(cat <<'EOF'
|
|
191
|
-
Mentioning git commit --amend in the message body for context.
|
|
192
|
-
EOF
|
|
193
|
-
)"`;
|
|
194
|
-
const hits = findDrifts(bash(cmd));
|
|
195
|
-
expect(hits.map((h) => h.pattern.id)).not.toContain("never-amend");
|
|
196
|
-
});
|
|
197
|
-
it("STILL fires when 'git push' is the actual command after a HEREDOC", () => {
|
|
198
|
-
// The HEREDOC ends, then a real git push follows. Must still block.
|
|
199
|
-
const cmd = `cat <<'EOF'
|
|
200
|
-
some prose
|
|
201
|
-
EOF
|
|
202
|
-
git push origin main`;
|
|
203
|
-
const hits = findDrifts(bash(cmd));
|
|
204
|
-
expect(hits.map((h) => h.pattern.id)).toContain("no-implicit-push");
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
// =====================================================================
|
|
208
|
-
// v0.6.6 (#137) — OPENSQUID_SKIP_DRIFT emergency bypass. Mirrors
|
|
209
|
-
// OPENSQUID_SKIP_VERSION_GATE / OPENSQUID_SKIP_WORKFLOW_GATE shape so
|
|
210
|
-
// the operator only has one mental model for "this hook is wrong, get
|
|
211
|
-
// out of my way". The documented uninstall-hooks workaround doesn't
|
|
212
|
-
// actually work mid-session because Claude Code caches the settings.json
|
|
213
|
-
// hook command at session start.
|
|
214
|
-
// =====================================================================
|
|
215
|
-
describe("OPENSQUID_SKIP_DRIFT bypass (v0.6.6)", () => {
|
|
216
|
-
// Vitest runs tests serially within a file by default; we restore the
|
|
217
|
-
// env var after each test so other tests aren't tainted.
|
|
218
|
-
afterEach(() => {
|
|
219
|
-
delete process.env.OPENSQUID_SKIP_DRIFT;
|
|
220
|
-
});
|
|
221
|
-
it("ALLOWS (exit 0) with bypass warning when OPENSQUID_SKIP_DRIFT=1 is in parent env and a block would fire", () => {
|
|
222
|
-
process.env.OPENSQUID_SKIP_DRIFT = "1";
|
|
223
|
-
const call = bash("git push origin main");
|
|
224
|
-
const hits = findDrifts(call);
|
|
225
|
-
expect(hits.length).toBeGreaterThan(0);
|
|
226
|
-
const { exit, stderr } = decide(hits, call);
|
|
227
|
-
expect(exit).toBe(0);
|
|
228
|
-
expect(stderr).toContain("BYPASSED via OPENSQUID_SKIP_DRIFT=1");
|
|
229
|
-
expect(stderr).toContain("no-implicit-push");
|
|
230
|
-
});
|
|
231
|
-
it("ALLOWS via INLINE prefix `OPENSQUID_SKIP_DRIFT=1 git push ...` even when parent env unset", () => {
|
|
232
|
-
delete process.env.OPENSQUID_SKIP_DRIFT;
|
|
233
|
-
const call = bash("OPENSQUID_SKIP_DRIFT=1 git push origin main");
|
|
234
|
-
const hits = findDrifts(call);
|
|
235
|
-
expect(hits.length).toBeGreaterThan(0);
|
|
236
|
-
const { exit, stderr } = decide(hits, call);
|
|
237
|
-
expect(exit).toBe(0);
|
|
238
|
-
expect(stderr).toContain("BYPASSED via OPENSQUID_SKIP_DRIFT=1");
|
|
239
|
-
});
|
|
240
|
-
it("ALLOWS via inline prefix after `cd ... &&` chain", () => {
|
|
241
|
-
const call = bash("cd /tmp && OPENSQUID_SKIP_DRIFT=1 git push origin main");
|
|
242
|
-
const hits = findDrifts(call);
|
|
243
|
-
const { exit } = decide(hits, call);
|
|
244
|
-
expect(exit).toBe(0);
|
|
245
|
-
});
|
|
246
|
-
it("includes ALL hit ids in the bypass message (operator audit trail)", () => {
|
|
247
|
-
process.env.OPENSQUID_SKIP_DRIFT = "1";
|
|
248
|
-
const call = bash("git push --force origin main");
|
|
249
|
-
const hits = findDrifts(call);
|
|
250
|
-
const { stderr } = decide(hits, call);
|
|
251
|
-
expect(stderr).toContain("no-implicit-push");
|
|
252
|
-
expect(stderr).toContain("no-force-push-main");
|
|
253
|
-
});
|
|
254
|
-
it("does NOT bypass when env var is unset or != '1'", () => {
|
|
255
|
-
process.env.OPENSQUID_SKIP_DRIFT = "true";
|
|
256
|
-
const call = bash("git push origin main");
|
|
257
|
-
const hits = findDrifts(call);
|
|
258
|
-
const { exit } = decide(hits, call);
|
|
259
|
-
expect(exit).toBe(2);
|
|
260
|
-
});
|
|
261
|
-
it("does NOT bypass when inline prefix value != '1'", () => {
|
|
262
|
-
const call = bash("OPENSQUID_SKIP_DRIFT=true git push origin main");
|
|
263
|
-
const hits = findDrifts(call);
|
|
264
|
-
const { exit } = decide(hits, call);
|
|
265
|
-
expect(exit).toBe(2);
|
|
266
|
-
});
|
|
267
|
-
it("does NOT bypass when var name appears only as substring", () => {
|
|
268
|
-
// Defensive: `MY_OPENSQUID_SKIP_DRIFT=1` shouldn't match (leading word-boundary).
|
|
269
|
-
const call = bash("MY_OPENSQUID_SKIP_DRIFT=1 git push origin main");
|
|
270
|
-
const hits = findDrifts(call);
|
|
271
|
-
const { exit } = decide(hits, call);
|
|
272
|
-
expect(exit).toBe(2);
|
|
273
|
-
});
|
|
274
|
-
it("emits nothing on empty hits regardless of env var", () => {
|
|
275
|
-
process.env.OPENSQUID_SKIP_DRIFT = "1";
|
|
276
|
-
const { exit, stderr } = decide([]);
|
|
277
|
-
expect(exit).toBe(0);
|
|
278
|
-
expect(stderr).toBe("");
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
describe("drift catalog — telegram-redirect-report (D2)", () => {
|
|
282
|
-
it("warns when plugin:telegram reply starts with `🦑 #N` task-report marker", () => {
|
|
283
|
-
const hits = findDrifts(telegramReply("🦑 #170 — engine-client startupAck fix\n\nshipped"));
|
|
284
|
-
expect(hits.map((h) => h.pattern.id)).toContain("telegram-redirect-report");
|
|
285
|
-
expect(decide(hits).exit).toBe(0); // warn-severity = non-blocking
|
|
286
|
-
});
|
|
287
|
-
it("warns with leading whitespace before the marker", () => {
|
|
288
|
-
const hits = findDrifts(telegramReply(" 🦑 #4 — cleanup commit"));
|
|
289
|
-
expect(hits.map((h) => h.pattern.id)).toContain("telegram-redirect-report");
|
|
290
|
-
});
|
|
291
|
-
it("does NOT fire on regular plugin:telegram replies", () => {
|
|
292
|
-
const hits = findDrifts(telegramReply("ok, working on it now"));
|
|
293
|
-
expect(hits.map((h) => h.pattern.id)).not.toContain("telegram-redirect-report");
|
|
294
|
-
});
|
|
295
|
-
it("does NOT fire on chat_send (the correct tool for reports)", () => {
|
|
296
|
-
const hits = findDrifts({
|
|
297
|
-
tool: "mcp__opensquid__chat_send",
|
|
298
|
-
input: { text: "🦑 #170 — full 7-phase report goes here" },
|
|
299
|
-
});
|
|
300
|
-
expect(hits.map((h) => h.pattern.id)).not.toContain("telegram-redirect-report");
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
describe("drift catalog — bundled-commit (D4)", () => {
|
|
304
|
-
it("warns on a commit message that references 2+ task numbers", () => {
|
|
305
|
-
const hits = findDrifts(bash('git commit -m "close #166 and defer #168"'));
|
|
306
|
-
expect(hits.map((h) => h.pattern.id)).toContain("bundled-commit");
|
|
307
|
-
expect(decide(hits).exit).toBe(0); // warn-severity = non-blocking
|
|
308
|
-
});
|
|
309
|
-
it('warns on inline `-m "..."` with 2 #N refs on one line', () => {
|
|
310
|
-
const hits = findDrifts(bash('git commit -m "release: #166 + #168 + cleanup"'));
|
|
311
|
-
expect(hits.map((h) => h.pattern.id)).toContain("bundled-commit");
|
|
312
|
-
});
|
|
313
|
-
// Known limitation: HEREDOC commit message bodies are stripped by
|
|
314
|
-
// stripHeredocBodies BEFORE pattern matching, so refs inside the
|
|
315
|
-
// HEREDOC body don't fire this pattern. Adding staged-content-aware
|
|
316
|
-
// detection (via a dedicated bundled-commit-gate) is deferred.
|
|
317
|
-
it("does NOT fire on a single-task commit", () => {
|
|
318
|
-
const hits = findDrifts(bash('git commit -m "fix(workflow-gate): session_id mismatch (#166)"'));
|
|
319
|
-
expect(hits.map((h) => h.pattern.id)).not.toContain("bundled-commit");
|
|
320
|
-
});
|
|
321
|
-
it("does NOT fire on commits with no task ref at all", () => {
|
|
322
|
-
const hits = findDrifts(bash('git commit -m "refactor: drift-patterns.ts"'));
|
|
323
|
-
expect(hits.map((h) => h.pattern.id)).not.toContain("bundled-commit");
|
|
324
|
-
});
|
|
325
|
-
});
|