omniconductor 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/LICENSE +219 -0
- package/NOTICE +11 -0
- package/README.md +526 -0
- package/THIRD_PARTY_NOTICES.md +31 -0
- package/TRADEMARKS.md +26 -0
- package/VISION.md +106 -0
- package/adapters/README.md +121 -0
- package/adapters/claude/README.md +102 -0
- package/adapters/claude/SUPPORTED-FEATURES.md +66 -0
- package/adapters/claude/hookify-templates/.recipe-scoped +9 -0
- package/adapters/claude/hookify-templates/README.md +93 -0
- package/adapters/claude/hookify-templates/block-completion-claim-without-push.local.md.template +56 -0
- package/adapters/claude/hookify-templates/block-direct-push-protected-branch.local.md.template +18 -0
- package/adapters/claude/hookify-templates/block-force-push-protected-branch.local.md.template +42 -0
- package/adapters/claude/hookify-templates/block-server-secret-in-client.local.md.template +36 -0
- package/adapters/claude/hookify-templates/warn-any-type-added.local.md.template +44 -0
- package/adapters/claude/hookify-templates/warn-commit-without-pre-commit-review.local.md.template +52 -0
- package/adapters/claude/hookify-templates/warn-console-direct.local.md.template +55 -0
- package/adapters/claude/hookify-templates/warn-create-table-without-access-control.local.md.template +35 -0
- package/adapters/claude/hookify-templates/warn-current-work-without-remaining-tasks.local.md.template +36 -0
- package/adapters/claude/hookify-templates/warn-gh-pr-create-without-pre-merge-review.local.md.template +38 -0
- package/adapters/claude/hookify-templates/warn-hardcoded-text-without-i18n-key.local.md.template +21 -0
- package/adapters/claude/hookify-templates/warn-on-gh-pr-merge.local.md.template +33 -0
- package/adapters/claude/hookify-templates/warn-plan-spec-without-remaining-tasks.local.md.template +44 -0
- package/adapters/claude/hookify-templates/warn-raw-hex-instead-of-token.local.md.template +21 -0
- package/adapters/claude/hookify-templates/warn-security-definer-without-search-path.local.md.template +30 -0
- package/adapters/claude/hookify-templates/warn-stop-commit-without-current-work.local.md.template +47 -0
- package/adapters/claude/hookify-templates/warn-user-manual-completion.local.md.template +40 -0
- package/adapters/claude/transform-spec.md +140 -0
- package/adapters/claude/transform.sh +1169 -0
- package/adapters/codex/README.md +87 -0
- package/adapters/codex/SUPPORTED-FEATURES.md +66 -0
- package/adapters/codex/transform-spec.md +90 -0
- package/adapters/codex/transform.sh +748 -0
- package/adapters/copilot/README.md +88 -0
- package/adapters/copilot/SUPPORTED-FEATURES.md +73 -0
- package/adapters/copilot/transform-spec.md +111 -0
- package/adapters/copilot/transform.sh +757 -0
- package/adapters/cursor/README.md +87 -0
- package/adapters/cursor/SUPPORTED-FEATURES.md +90 -0
- package/adapters/cursor/transform-spec.md +124 -0
- package/adapters/cursor/transform.sh +731 -0
- package/adapters/gemini/README.md +93 -0
- package/adapters/gemini/SUPPORTED-FEATURES.md +80 -0
- package/adapters/gemini/transform-spec.md +102 -0
- package/adapters/gemini/transform.sh +844 -0
- package/adapters/windsurf/README.md +93 -0
- package/adapters/windsurf/SUPPORTED-FEATURES.md +67 -0
- package/adapters/windsurf/transform-spec.md +99 -0
- package/adapters/windsurf/transform.sh +727 -0
- package/bin/omniconductor.js +130 -0
- package/core/README.md +64 -0
- package/core/anti-patterns/README.md +142 -0
- package/core/anti-patterns/dynamic-system-prompt.md +113 -0
- package/core/anti-patterns/frequent-rule-file-edit.md +83 -0
- package/core/anti-patterns/large-file-read-no-range.md +90 -0
- package/core/anti-patterns/no-sub-agent-dispatch.md +89 -0
- package/core/anti-patterns/single-monolithic-rule-file.md +99 -0
- package/core/anti-patterns/skill-eager-load.md +83 -0
- package/core/anti-patterns/tool-call-spam.md +93 -0
- package/core/docs-templates/CURRENT_WORK.md +64 -0
- package/core/docs-templates/INDEX.md +57 -0
- package/core/docs-templates/PLANS.md +44 -0
- package/core/docs-templates/README.md +40 -0
- package/core/docs-templates/REMAINING_TASKS.md +43 -0
- package/core/docs-templates/TASKS.md +51 -0
- package/core/docs-templates/specs/_example.md +121 -0
- package/core/hooks/README.md +62 -0
- package/core/hooks/pretool-agent-routing.sh.template +97 -0
- package/core/hooks/pretool-commit-current-work-check.sh.template +103 -0
- package/core/hooks/pretool-commit-test-coverage-check.sh.template +116 -0
- package/core/hooks/pretool-large-file-read-guard.sh.template +117 -0
- package/core/hooks/stop-cache-hit-baseline-check.sh.template +133 -0
- package/core/hooks/stop-r6-review-check.sh.template +80 -0
- package/core/hooks/stop-session-log-check.sh.template +101 -0
- package/core/hooks/stop-trajectory-log.sh.template +96 -0
- package/core/memory-pattern/EXAMPLES.md +114 -0
- package/core/memory-pattern/README.md +133 -0
- package/core/recipes/README.md +46 -0
- package/core/recipes/auto-mock-data.md +82 -0
- package/core/recipes/branch-strategy.md +100 -0
- package/core/recipes/coding-conventions.md +123 -0
- package/core/recipes/database-discipline.md +65 -0
- package/core/recipes/debugging.md +143 -0
- package/core/recipes/design-system.md +30 -0
- package/core/recipes/i18n.md +118 -0
- package/core/recipes/monorepo.md +101 -0
- package/core/recipes/self-improvement.md +61 -0
- package/core/recipes/tdd.md +128 -0
- package/core/recipes/web-mobile-parity.md +87 -0
- package/core/reflector/SCHEDULING.md +83 -0
- package/core/reflector/prune-lessons.sh +105 -0
- package/core/reflector/reflect-brief.md +11 -0
- package/core/reflector/reflect.command.md +13 -0
- package/core/reflector/run-weekly.sh +54 -0
- package/core/reflector/trajectory-log.sh +55 -0
- package/core/roles/README.md +61 -0
- package/core/roles/builder.md +75 -0
- package/core/roles/designer.md +93 -0
- package/core/roles/helper.md +70 -0
- package/core/roles/planner.md +123 -0
- package/core/roles/reflector.md +72 -0
- package/core/roles/reviewer.md +104 -0
- package/core/roles/scribe.md +89 -0
- package/core/universal-rules/README.md +71 -0
- package/core/universal-rules/meta-discipline.md +292 -0
- package/core/universal-rules/operations.md +162 -0
- package/core/universal-rules/quality-gates.md +191 -0
- package/core/universal-rules/spec-as-you-go.md +123 -0
- package/core/universal-rules/workflow.md +153 -0
- package/core/workflow/PHASES.md +134 -0
- package/core/workflow/README.md +56 -0
- package/docs/MANUAL-INSTALL.md +456 -0
- package/package.json +52 -0
- package/tools/check-framework-purity.sh +91 -0
- package/tools/measure-tokens.sh +169 -0
- package/tools/validate-adapter-output.sh +611 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
area: <your-area> # e.g., auth, billing, email
|
|
3
|
+
owner: <you-or-team>
|
|
4
|
+
status: draft|active|archived
|
|
5
|
+
last_synced: YYYY-MM-DD
|
|
6
|
+
api_routes: # list of API route paths owned by this area
|
|
7
|
+
- /api/<your-area>/<route>
|
|
8
|
+
pages: # list of pages owned by this area
|
|
9
|
+
- /<your-area>
|
|
10
|
+
components: # list of components owned by this area
|
|
11
|
+
- <YourComponent>
|
|
12
|
+
services: # list of services owned by this area
|
|
13
|
+
- <yourAreaService>
|
|
14
|
+
hooks: # list of hooks owned by this area
|
|
15
|
+
- useYourArea
|
|
16
|
+
db_tables: # list of DB tables owned by this area
|
|
17
|
+
- <table_name>
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Spec — `<your-area>` (PLACEHOLDER — copy + rename per area)
|
|
21
|
+
|
|
22
|
+
> **What this is**: single source-of-truth for everything in the `<your-area>` domain. Updated EVERY time code in this area changes (spec-as-you-go ABSOLUTE rule).
|
|
23
|
+
|
|
24
|
+
> **Status (P0 placeholder)**: copy this file, rename to your area, replace contents. P1 will provide a richer example.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Overview
|
|
29
|
+
|
|
30
|
+
One paragraph: what is this area, what problem does it solve, what are the boundaries.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## User-facing flows
|
|
35
|
+
|
|
36
|
+
For each user flow:
|
|
37
|
+
|
|
38
|
+
### Flow: <flow name>
|
|
39
|
+
|
|
40
|
+
1. User does X.
|
|
41
|
+
2. System responds Y.
|
|
42
|
+
3. User sees Z.
|
|
43
|
+
|
|
44
|
+
**Edge cases**:
|
|
45
|
+
- What happens on error.
|
|
46
|
+
- What happens on empty state.
|
|
47
|
+
- What happens on permission denied.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## API
|
|
52
|
+
|
|
53
|
+
For each route:
|
|
54
|
+
|
|
55
|
+
### `<METHOD> /api/<your-area>/<route>`
|
|
56
|
+
|
|
57
|
+
- **Auth**: required / optional / none.
|
|
58
|
+
- **Body**: schema.
|
|
59
|
+
- **Response**: schema (Result pattern: `{ data: T; error: null } | { data: null; error: string }`).
|
|
60
|
+
- **Errors**: list of error codes + meanings.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## UI
|
|
65
|
+
|
|
66
|
+
For each page or major component:
|
|
67
|
+
|
|
68
|
+
### Page: `/<your-area>`
|
|
69
|
+
|
|
70
|
+
- **Layout**: brief description.
|
|
71
|
+
- **Components used**: list.
|
|
72
|
+
- **State**: what state the page manages.
|
|
73
|
+
- **Permissions**: who can access.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Services
|
|
78
|
+
|
|
79
|
+
For each service function:
|
|
80
|
+
|
|
81
|
+
### `<yourAreaService>.<method>()`
|
|
82
|
+
|
|
83
|
+
- **Input**: parameters.
|
|
84
|
+
- **Output**: Result-pattern return.
|
|
85
|
+
- **Side effects**: DB writes, external API calls, etc.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## DB Schema
|
|
90
|
+
|
|
91
|
+
For each table:
|
|
92
|
+
|
|
93
|
+
### `<table_name>`
|
|
94
|
+
|
|
95
|
+
| Column | Type | Notes |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| id | uuid | PK |
|
|
98
|
+
| ... | ... | ... |
|
|
99
|
+
|
|
100
|
+
**Indexes**:
|
|
101
|
+
- ...
|
|
102
|
+
|
|
103
|
+
**RLS policies**:
|
|
104
|
+
- ...
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Known constraints / gotchas
|
|
109
|
+
|
|
110
|
+
- Limits, quotas, rate limits.
|
|
111
|
+
- Browser-specific behavior.
|
|
112
|
+
- Things future contributors must know.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Recent changes
|
|
117
|
+
|
|
118
|
+
- YYYY-MM-DD: <what changed and why>.
|
|
119
|
+
- YYYY-MM-DD: ...
|
|
120
|
+
|
|
121
|
+
(Append-only; oldest at bottom; archive once it grows past 20 entries.)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# `core/hooks/` — Universal hook templates
|
|
2
|
+
|
|
3
|
+
CONDUCTOR's universal hook spec. Templates here are compiled into native shell scripts by adapters that natively support hooks (Claude Code as of v0.2). Adapters without native hook support transform these into rule-text reminders embedded in the tool's primary rule file.
|
|
4
|
+
|
|
5
|
+
## The templates
|
|
6
|
+
|
|
7
|
+
| Template | Trigger | Action | Compile target |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `pretool-agent-routing.sh.template` | Agent / sub-agent dispatch about to fire | Validate routing (no `general-purpose`, explicit model) | Claude `.claude/hooks/pretool-agent-routing.sh` |
|
|
10
|
+
| `pretool-commit-current-work-check.sh.template` | PreToolUse on Bash `git commit` | Soft `ask` warn — surfaces a reason at commit time when 3+ source files are staged but `CURRENT_WORK.md` is not part of the commit; non-blocking, orchestrator approves to proceed. Override env disables: `CONDUCTOR_SKIP_CURRENT_WORK_HOOK=1`. | Claude `.claude/hooks/pretool-commit-current-work-check.sh` |
|
|
11
|
+
| `pretool-commit-test-coverage-check.sh.template` | PreToolUse on Bash `git commit` | Soft `ask` warn (quality-gates Q3) — surfaces a reason when a newly-added feature-shaped source file has no new test (path with `test`/`spec`/`__tests__`/`e2e`) in the same commit; non-blocking, orchestrator approves to proceed. Override env disables: `CONDUCTOR_SKIP_TEST_COVERAGE_HOOK=1`. | Claude `.claude/hooks/pretool-commit-test-coverage-check.sh` |
|
|
12
|
+
| `pretool-large-file-read-guard.sh.template` | PreToolUse on the Read tool | **Block** a Read of files ≥ `${CONDUCTOR_LARGE_FILE_LINE_THRESHOLD}` (default 500) lines when no `offset`/`limit` is supplied; recommends range-read or Grep (token-economy / anti-pattern `large-file-read-no-range`). Fail-open. Override env disables: `CONDUCTOR_ALLOW_LARGE_READ=1`. | Claude `.claude/hooks/pretool-large-file-read-guard.sh` |
|
|
13
|
+
| `stop-session-log-check.sh.template` | Session stop event | Block stop if recent commits exist + CURRENT_WORK.md or specs are stale | Claude `.claude/hooks/stop-session-log-check.sh` |
|
|
14
|
+
| `stop-r6-review-check.sh.template` | Session stop event | Remind to run pre-merge review on open PR | Claude `.claude/hooks/stop-r6-review-check.sh` |
|
|
15
|
+
| `stop-cache-hit-baseline-check.sh.template` | Session stop event | Non-blocking diagnostic — reads the latest session JSONL, computes cache hit rate, reminds when below baseline (token-economy). Fail-open. Override env disables: `CONDUCTOR_SKIP_CACHE_CHECK=1`. | Claude `.claude/hooks/stop-cache-hit-baseline-check.sh` |
|
|
16
|
+
| `stop-trajectory-log.sh.template` | Session stop event | Non-blocking — reads `transcript_path` + `session_id` from the Stop hook's **stdin** (exact provenance; no `~/.claude/projects` dir-scan) and **upserts** one pointer record per session (session id, transcript path, git HEAD, cwd) into `.conductor/trajectories/index.jsonl` for the Reflector (recipes/self-improvement.md). Same stdin approach as the non-Claude portable logger `core/reflector/trajectory-log.sh`. **Opt-in gated: no-ops unless `.conductor/reflect/` exists** (created only by the self-improvement recipe). Anchors to the project root; fail-open. Override: `CONDUCTOR_SKIP_TRAJLOG=1`. | Claude `.claude/hooks/stop-trajectory-log.sh` |
|
|
17
|
+
|
|
18
|
+
The two `pretool-commit-*` templates are **soft `ask` warns**: they emit
|
|
19
|
+
`{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":...}}`,
|
|
20
|
+
which surfaces the reason and routes to the permission flow — the orchestrator approves and
|
|
21
|
+
proceeds (or justifies). This is non-blocking and does NOT auto-approve (unlike
|
|
22
|
+
`permissionDecision: allow`, which would silently bypass the permission prompt). It mirrors
|
|
23
|
+
`pretool-agent-routing.sh.template`'s `deny`+reason shape (here `ask`+reason). They also fail
|
|
24
|
+
soft — any internal error exits 0. Each honors a per-commit override env var (above) for the
|
|
25
|
+
rare intentional exception.
|
|
26
|
+
|
|
27
|
+
## Frontmatter convention
|
|
28
|
+
|
|
29
|
+
Templates use a comment-block frontmatter (because shell scripts don't read YAML at runtime — the adapter parses the comment block):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
#!/usr/bin/env bash
|
|
33
|
+
#
|
|
34
|
+
# CONDUCTOR universal hook template
|
|
35
|
+
# hook_id: <id>
|
|
36
|
+
# trigger: <pretool | stop>
|
|
37
|
+
# action: <description>
|
|
38
|
+
# compile_targets: claude
|
|
39
|
+
# fallback: rule-text-reminder
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Per-tool compilation
|
|
43
|
+
|
|
44
|
+
| Adapter | Compile behavior |
|
|
45
|
+
|---|---|
|
|
46
|
+
| Claude | `transform.sh` substitutes placeholders (paths, branch names, cool-down windows), writes to `.claude/hooks/<hook>.sh`, runs `chmod +x`. |
|
|
47
|
+
| Cursor / Copilot / Gemini / Codex / Windsurf | Hook compiles to a rule-text reminder added to the appropriate rule file. The reminder includes the trigger condition, the action, and the rationale. |
|
|
48
|
+
|
|
49
|
+
## Placeholders
|
|
50
|
+
|
|
51
|
+
Templates use `${PLACEHOLDER}` syntax for values that the adapter substitutes at compile time:
|
|
52
|
+
|
|
53
|
+
| Placeholder | Replaced with |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `${CONDUCTOR_PROJECT_DIR}` | Absolute path to the target project |
|
|
56
|
+
| `${CONDUCTOR_PROTECTED_BRANCHES}` | Pipe-separated list (e.g., `main\|release\|develop`) |
|
|
57
|
+
| `${CONDUCTOR_COOLDOWN_SECONDS}` | Cool-down between reminders (typical: 1800 = 30 min) |
|
|
58
|
+
| `${CONDUCTOR_STALE_MINUTES}` | Staleness threshold (typical: 30) |
|
|
59
|
+
| `${CONDUCTOR_SOURCE_GLOB}` | Alternation of source extensions (e.g., `ts\|tsx`) |
|
|
60
|
+
| `${CONDUCTOR_CURRENT_WORK_PATH}` | Session-log path (default `docs/CURRENT_WORK.md`) |
|
|
61
|
+
|
|
62
|
+
Placeholders that the adapter does not know how to fill are passed through unchanged (with a warning) so the user can edit them post-install.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# CONDUCTOR universal hook template
|
|
4
|
+
# hook_id: pretool-agent-routing
|
|
5
|
+
# trigger: pretool
|
|
6
|
+
# action: Validate sub-agent dispatch — block forbidden subagent_type, require explicit model.
|
|
7
|
+
# compile_targets: claude
|
|
8
|
+
# fallback: rule-text-reminder
|
|
9
|
+
#
|
|
10
|
+
# Generated by adapters/<tool>/transform.sh.
|
|
11
|
+
# Tool-native semantics:
|
|
12
|
+
# - Claude Code: PreToolUse hook reads the tool-call payload from stdin (JSON)
|
|
13
|
+
# and validates Agent tool calls.
|
|
14
|
+
# - Other tools: this template is converted to rule-text by their adapter.
|
|
15
|
+
#
|
|
16
|
+
# Validations:
|
|
17
|
+
# V1. Agent tool MUST NOT use a forbidden subagent_type (e.g., "general-purpose"
|
|
18
|
+
# which defaults to a generic model and ignores project rules).
|
|
19
|
+
# Route to one of the 6 universal roles + project-specific roles instead.
|
|
20
|
+
# V2. Agent tool calls MUST include an explicit `model` parameter. The
|
|
21
|
+
# orchestrator is responsible for sizing tasks per
|
|
22
|
+
# universal-rules/meta-discipline.md section 6.
|
|
23
|
+
#
|
|
24
|
+
# Exit code meaning in PreToolUse hooks (Claude Code):
|
|
25
|
+
# 0 with no JSON output → allow
|
|
26
|
+
# 0 with {"permissionDecision":"deny"} → block with reason (shown to LLM)
|
|
27
|
+
# non-zero → allow (fail-open)
|
|
28
|
+
|
|
29
|
+
set -u
|
|
30
|
+
|
|
31
|
+
# ${CONDUCTOR_PROJECT_DIR} — absolute path to project root (filled by transform.sh).
|
|
32
|
+
# ${CONDUCTOR_FORBIDDEN_SUBAGENT_TYPES} — pipe-separated list (default: general-purpose).
|
|
33
|
+
|
|
34
|
+
# Read payload from stdin into env var so python can parse it safely.
|
|
35
|
+
export PAYLOAD_RAW="$(/bin/cat 2>/dev/null || true)"
|
|
36
|
+
[ -n "${PAYLOAD_RAW:-}" ] || exit 0
|
|
37
|
+
|
|
38
|
+
if ! command -v /usr/bin/python3 >/dev/null 2>&1; then
|
|
39
|
+
exit 0
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
PARSED=$(/usr/bin/python3 -c "
|
|
43
|
+
import json, os, sys
|
|
44
|
+
try:
|
|
45
|
+
d = json.loads(os.environ.get('PAYLOAD_RAW', '{}'))
|
|
46
|
+
if d.get('tool_name', '') != 'Agent':
|
|
47
|
+
sys.exit(0)
|
|
48
|
+
inp = d.get('tool_input', {}) or {}
|
|
49
|
+
print('SUB=' + inp.get('subagent_type', ''))
|
|
50
|
+
print('MODEL=' + inp.get('model', ''))
|
|
51
|
+
except Exception:
|
|
52
|
+
sys.exit(0)
|
|
53
|
+
" 2>/dev/null)
|
|
54
|
+
|
|
55
|
+
[ -z "$PARSED" ] && exit 0
|
|
56
|
+
|
|
57
|
+
while IFS='=' read -r key val; do
|
|
58
|
+
case "$key" in
|
|
59
|
+
SUB) SUB="$val" ;;
|
|
60
|
+
MODEL) MODEL="$val" ;;
|
|
61
|
+
esac
|
|
62
|
+
done <<< "$PARSED"
|
|
63
|
+
|
|
64
|
+
REASONS=""
|
|
65
|
+
|
|
66
|
+
# V1 — block forbidden subagent_type.
|
|
67
|
+
# Default forbidden list: general-purpose. Override at compile time.
|
|
68
|
+
FORBIDDEN_RE="${CONDUCTOR_FORBIDDEN_SUBAGENT_TYPES:-general-purpose}"
|
|
69
|
+
if [ -n "${SUB:-}" ] && echo "${SUB}" | /usr/bin/grep -qE "^(${FORBIDDEN_RE})$"; then
|
|
70
|
+
REASONS="[ROUTING VIOLATION] subagent_type='${SUB}' is FORBIDDEN. Route to one of the registered roles in core/roles/ (planner, builder, reviewer, helper, designer, scribe) or a project-specific role. If no role fits, do the work in the orchestrator thread."
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# V2 — require explicit model parameter.
|
|
74
|
+
if [ -z "${MODEL:-}" ]; then
|
|
75
|
+
MSG="[ROUTING VIOLATION] Agent tool call is missing the 'model' parameter. Orchestrator must classify the task and pass model='opus' | 'sonnet' | 'haiku' explicitly per universal-rules/meta-discipline.md section 6."
|
|
76
|
+
if [ -n "$REASONS" ]; then
|
|
77
|
+
REASONS="$REASONS $MSG"
|
|
78
|
+
else
|
|
79
|
+
REASONS="$MSG"
|
|
80
|
+
fi
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
[ -z "$REASONS" ] && exit 0
|
|
84
|
+
|
|
85
|
+
REASONS_JSON=$(REASONS="$REASONS" /usr/bin/python3 -c "
|
|
86
|
+
import json, os
|
|
87
|
+
print(json.dumps({
|
|
88
|
+
'hookSpecificOutput': {
|
|
89
|
+
'hookEventName': 'PreToolUse',
|
|
90
|
+
'permissionDecision': 'deny',
|
|
91
|
+
'permissionDecisionReason': os.environ.get('REASONS', ''),
|
|
92
|
+
}
|
|
93
|
+
}))
|
|
94
|
+
")
|
|
95
|
+
|
|
96
|
+
echo "$REASONS_JSON"
|
|
97
|
+
exit 0
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# CONDUCTOR universal hook template
|
|
4
|
+
# hook_id: pretool-commit-current-work-check
|
|
5
|
+
# trigger: pretool
|
|
6
|
+
# action: Soft-warn at commit time when 3+ source files are staged but
|
|
7
|
+
# ${CONDUCTOR_CURRENT_WORK_PATH} is not part of the same commit.
|
|
8
|
+
# compile_targets: claude
|
|
9
|
+
# fallback: rule-text-reminder
|
|
10
|
+
#
|
|
11
|
+
# Generated by adapters/<tool>/transform.sh.
|
|
12
|
+
#
|
|
13
|
+
# Commit-time companion to stop-session-log-check.sh.template. Soft warn, never blocks.
|
|
14
|
+
#
|
|
15
|
+
# Enforces:
|
|
16
|
+
# - operations.md O2 (CURRENT_WORK.md must update when work happens) — caught at
|
|
17
|
+
# commit time instead of (only) at session stop.
|
|
18
|
+
#
|
|
19
|
+
# Behavior:
|
|
20
|
+
# 1. Parse PreToolUse stdin payload (same method as pretool-agent-routing).
|
|
21
|
+
# 2. If tool_name != Bash → allow (silent exit 0).
|
|
22
|
+
# 3. If the command is not a `git commit` → allow (silent exit 0).
|
|
23
|
+
# 4. Count staged source files matching ${CONDUCTOR_SOURCE_GLOB}.
|
|
24
|
+
# 5. If 3+ staged source files AND ${CONDUCTOR_CURRENT_WORK_PATH} is NOT staged
|
|
25
|
+
# → emit a soft confirmation (permissionDecision: ask + permissionDecisionReason).
|
|
26
|
+
# 6. Override env / any error → allow (silent exit 0).
|
|
27
|
+
#
|
|
28
|
+
# Override (skip this check for a commit):
|
|
29
|
+
# CONDUCTOR_SKIP_CURRENT_WORK_HOOK=1
|
|
30
|
+
#
|
|
31
|
+
# Exit code meaning in PreToolUse hooks (Claude Code):
|
|
32
|
+
# 0 with no JSON output → allow (silent)
|
|
33
|
+
# 0 with {"permissionDecision":"ask","permissionDecisionReason":...}
|
|
34
|
+
# → surface the reason and route to the permission flow; the orchestrator
|
|
35
|
+
# approves and proceeds (or justifies). Does NOT block and does NOT
|
|
36
|
+
# auto-approve. This is the documented non-blocking soft-warn and matches
|
|
37
|
+
# pretool-agent-routing.sh.template's deny+reason shape (here: ask+reason).
|
|
38
|
+
# 0 with {"permissionDecision":"deny"} → block with reason
|
|
39
|
+
# non-zero → allow (fail-open)
|
|
40
|
+
|
|
41
|
+
set -u
|
|
42
|
+
|
|
43
|
+
# Placeholders filled by adapters/<tool>/transform.sh:
|
|
44
|
+
# ${CONDUCTOR_SOURCE_GLOB} — alternation of source extensions (default ts|tsx)
|
|
45
|
+
# ${CONDUCTOR_CURRENT_WORK_PATH} — session-log path (default docs/CURRENT_WORK.md)
|
|
46
|
+
|
|
47
|
+
# Honor opt-out before any work.
|
|
48
|
+
[ "${CONDUCTOR_SKIP_CURRENT_WORK_HOOK:-}" = "1" ] && exit 0
|
|
49
|
+
|
|
50
|
+
SRC_GLOB="${CONDUCTOR_SOURCE_GLOB:-ts|tsx}"
|
|
51
|
+
CURRENT_WORK="${CONDUCTOR_CURRENT_WORK_PATH:-docs/CURRENT_WORK.md}"
|
|
52
|
+
|
|
53
|
+
command -v /usr/bin/python3 >/dev/null 2>&1 || exit 0
|
|
54
|
+
|
|
55
|
+
# Read payload from stdin into env var so python can parse it safely.
|
|
56
|
+
export PAYLOAD_RAW="$(/bin/cat 2>/dev/null || true)"
|
|
57
|
+
[ -n "${PAYLOAD_RAW:-}" ] || exit 0
|
|
58
|
+
|
|
59
|
+
# Extract the Bash command (same extraction shape the existing hooks use).
|
|
60
|
+
CMD=$(/usr/bin/python3 -c "
|
|
61
|
+
import json, os, sys
|
|
62
|
+
try:
|
|
63
|
+
d = json.loads(os.environ.get('PAYLOAD_RAW', '{}'))
|
|
64
|
+
if d.get('tool_name', '') != 'Bash':
|
|
65
|
+
sys.exit(0)
|
|
66
|
+
inp = d.get('tool_input', {}) or {}
|
|
67
|
+
sys.stdout.write(inp.get('command', '') or '')
|
|
68
|
+
except Exception:
|
|
69
|
+
sys.exit(0)
|
|
70
|
+
" 2>/dev/null)
|
|
71
|
+
|
|
72
|
+
# Only act on git commit commands.
|
|
73
|
+
case "$CMD" in
|
|
74
|
+
*"git commit"*) : ;;
|
|
75
|
+
*) exit 0 ;;
|
|
76
|
+
esac
|
|
77
|
+
|
|
78
|
+
# Count staged source files (additions, modifications, etc.).
|
|
79
|
+
STAGED_SRC=$(git diff --cached --name-only 2>/dev/null | /usr/bin/grep -cE "\.(${SRC_GLOB})$" || true)
|
|
80
|
+
case "$STAGED_SRC" in
|
|
81
|
+
''|*[!0-9]*) exit 0 ;;
|
|
82
|
+
esac
|
|
83
|
+
|
|
84
|
+
[ "$STAGED_SRC" -ge 3 ] || exit 0
|
|
85
|
+
|
|
86
|
+
# Is the session log itself part of this commit?
|
|
87
|
+
if git diff --cached --name-only 2>/dev/null | /usr/bin/grep -qF "$CURRENT_WORK"; then
|
|
88
|
+
exit 0
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
REASON="[current-work-check] ${STAGED_SRC} source file(s) are staged but ${CURRENT_WORK} is not in this commit. Per operations.md O2, append the planned change to ${CURRENT_WORK} before committing so the session log stays in sync. (Skip once: set CONDUCTOR_SKIP_CURRENT_WORK_HOOK=1.)"
|
|
92
|
+
|
|
93
|
+
REASON="$REASON" /usr/bin/python3 -c "
|
|
94
|
+
import json, os
|
|
95
|
+
print(json.dumps({
|
|
96
|
+
'hookSpecificOutput': {
|
|
97
|
+
'hookEventName': 'PreToolUse',
|
|
98
|
+
'permissionDecision': 'ask',
|
|
99
|
+
'permissionDecisionReason': os.environ.get('REASON', ''),
|
|
100
|
+
}
|
|
101
|
+
}))
|
|
102
|
+
"
|
|
103
|
+
exit 0
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# CONDUCTOR universal hook template
|
|
4
|
+
# hook_id: pretool-commit-test-coverage-check
|
|
5
|
+
# trigger: pretool
|
|
6
|
+
# action: Soft-warn at commit time when a new feature-shaped source file is added
|
|
7
|
+
# with no accompanying test file in the same commit.
|
|
8
|
+
# compile_targets: claude
|
|
9
|
+
# fallback: rule-text-reminder
|
|
10
|
+
#
|
|
11
|
+
# Generated by adapters/<tool>/transform.sh.
|
|
12
|
+
#
|
|
13
|
+
# Script form of quality-gates Q3. Soft warn, never blocks.
|
|
14
|
+
#
|
|
15
|
+
# Enforces:
|
|
16
|
+
# - quality-gates.md Q3 (test coverage sync) — a new feature/route/page/endpoint/
|
|
17
|
+
# service should land with its test in the same commit.
|
|
18
|
+
#
|
|
19
|
+
# Behavior:
|
|
20
|
+
# 1. Parse PreToolUse stdin payload (same method as pretool-agent-routing).
|
|
21
|
+
# 2. If tool_name != Bash → allow. If command is not `git commit` → allow.
|
|
22
|
+
# 3. From `git diff --cached --name-only --diff-filter=A`, collect newly-ADDED
|
|
23
|
+
# source files matching ${CONDUCTOR_SOURCE_GLOB}.
|
|
24
|
+
# 4. Heuristic (kept generic on purpose): treat an added source file as
|
|
25
|
+
# "feature-shaped" if its path contains one of: route, page, endpoint,
|
|
26
|
+
# service, api, handler, controller, feature (case-insensitive). Pure type
|
|
27
|
+
# files / barrels / configs rarely contain those tokens, so they don't trip it.
|
|
28
|
+
# 5. If a feature-shaped added file exists AND no NEW test file is staged
|
|
29
|
+
# (path containing test, spec, __tests__, or e2e) → emit a soft confirmation
|
|
30
|
+
# (permissionDecision: ask + permissionDecisionReason) citing Q3.
|
|
31
|
+
# 6. Override env / any error → allow (silent exit 0).
|
|
32
|
+
#
|
|
33
|
+
# Override (skip this check for a commit):
|
|
34
|
+
# CONDUCTOR_SKIP_TEST_COVERAGE_HOOK=1
|
|
35
|
+
#
|
|
36
|
+
# Exit code meaning in PreToolUse hooks (Claude Code):
|
|
37
|
+
# 0 with no JSON output → allow (silent)
|
|
38
|
+
# 0 with {"permissionDecision":"ask","permissionDecisionReason":...}
|
|
39
|
+
# → surface the reason and route to the permission flow; the orchestrator
|
|
40
|
+
# approves and proceeds (or justifies). Does NOT block and does NOT
|
|
41
|
+
# auto-approve. This is the documented non-blocking soft-warn and matches
|
|
42
|
+
# pretool-agent-routing.sh.template's deny+reason shape (here: ask+reason).
|
|
43
|
+
# non-zero → allow (fail-open)
|
|
44
|
+
|
|
45
|
+
set -u
|
|
46
|
+
|
|
47
|
+
# Placeholders filled by adapters/<tool>/transform.sh:
|
|
48
|
+
# ${CONDUCTOR_SOURCE_GLOB} — alternation of source extensions (default ts|tsx)
|
|
49
|
+
|
|
50
|
+
# Honor opt-out before any work.
|
|
51
|
+
[ "${CONDUCTOR_SKIP_TEST_COVERAGE_HOOK:-}" = "1" ] && exit 0
|
|
52
|
+
|
|
53
|
+
SRC_GLOB="${CONDUCTOR_SOURCE_GLOB:-ts|tsx}"
|
|
54
|
+
|
|
55
|
+
command -v /usr/bin/python3 >/dev/null 2>&1 || exit 0
|
|
56
|
+
|
|
57
|
+
export PAYLOAD_RAW="$(/bin/cat 2>/dev/null || true)"
|
|
58
|
+
[ -n "${PAYLOAD_RAW:-}" ] || exit 0
|
|
59
|
+
|
|
60
|
+
CMD=$(/usr/bin/python3 -c "
|
|
61
|
+
import json, os, sys
|
|
62
|
+
try:
|
|
63
|
+
d = json.loads(os.environ.get('PAYLOAD_RAW', '{}'))
|
|
64
|
+
if d.get('tool_name', '') != 'Bash':
|
|
65
|
+
sys.exit(0)
|
|
66
|
+
inp = d.get('tool_input', {}) or {}
|
|
67
|
+
sys.stdout.write(inp.get('command', '') or '')
|
|
68
|
+
except Exception:
|
|
69
|
+
sys.exit(0)
|
|
70
|
+
" 2>/dev/null)
|
|
71
|
+
|
|
72
|
+
case "$CMD" in
|
|
73
|
+
*"git commit"*) : ;;
|
|
74
|
+
*) exit 0 ;;
|
|
75
|
+
esac
|
|
76
|
+
|
|
77
|
+
# Newly-added source files in this commit (added files only).
|
|
78
|
+
ADDED_SRC=$(git diff --cached --name-only --diff-filter=A 2>/dev/null | /usr/bin/grep -E "\.(${SRC_GLOB})$" || true)
|
|
79
|
+
[ -n "$ADDED_SRC" ] || exit 0
|
|
80
|
+
|
|
81
|
+
# Feature-shaped heuristic: path segment hints at a feature/route/page/endpoint/service.
|
|
82
|
+
# Kept deliberately generic so it works across stacks; excludes test files themselves.
|
|
83
|
+
# The substring match is intentionally loose (e.g. `pageant.ts` can trip `page`) — acceptable
|
|
84
|
+
# because this is a soft `ask` warn the orchestrator can wave through, not a hard block.
|
|
85
|
+
FEATURE_RE='(route|page|endpoint|service|api|handler|controller|feature)'
|
|
86
|
+
TEST_RE='(test|spec|__tests__|e2e)'
|
|
87
|
+
|
|
88
|
+
FEATURE_NEW=$(printf '%s\n' "$ADDED_SRC" \
|
|
89
|
+
| /usr/bin/grep -ivE "$TEST_RE" \
|
|
90
|
+
| /usr/bin/grep -icE "$FEATURE_RE" || true)
|
|
91
|
+
case "$FEATURE_NEW" in
|
|
92
|
+
''|*[!0-9]*) exit 0 ;;
|
|
93
|
+
esac
|
|
94
|
+
[ "$FEATURE_NEW" -ge 1 ] || exit 0
|
|
95
|
+
|
|
96
|
+
# Any NEW test file staged in this commit (added; covers the common case of a new feature).
|
|
97
|
+
NEW_TESTS=$(git diff --cached --name-only --diff-filter=A 2>/dev/null | /usr/bin/grep -icE "$TEST_RE" || true)
|
|
98
|
+
case "$NEW_TESTS" in
|
|
99
|
+
''|*[!0-9]*) NEW_TESTS=0 ;;
|
|
100
|
+
esac
|
|
101
|
+
|
|
102
|
+
[ "$NEW_TESTS" -ge 1 ] && exit 0
|
|
103
|
+
|
|
104
|
+
REASON="[test-coverage-check] A new feature-shaped source file is staged but no new test file (path with test/spec/__tests__/e2e) is in this commit. Per quality-gates.md Q3, add or update the covering test in the same commit. (Skip once: set CONDUCTOR_SKIP_TEST_COVERAGE_HOOK=1.)"
|
|
105
|
+
|
|
106
|
+
REASON="$REASON" /usr/bin/python3 -c "
|
|
107
|
+
import json, os
|
|
108
|
+
print(json.dumps({
|
|
109
|
+
'hookSpecificOutput': {
|
|
110
|
+
'hookEventName': 'PreToolUse',
|
|
111
|
+
'permissionDecision': 'ask',
|
|
112
|
+
'permissionDecisionReason': os.environ.get('REASON', ''),
|
|
113
|
+
}
|
|
114
|
+
}))
|
|
115
|
+
"
|
|
116
|
+
exit 0
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# CONDUCTOR universal hook template
|
|
4
|
+
# hook_id: pretool-large-file-read-guard
|
|
5
|
+
# trigger: pretool
|
|
6
|
+
# action: Block Read of files >= ${CONDUCTOR_LARGE_FILE_LINE_THRESHOLD}
|
|
7
|
+
# lines unless offset/limit is supplied (or override env set).
|
|
8
|
+
# compile_targets: claude
|
|
9
|
+
# fallback: rule-text-reminder
|
|
10
|
+
#
|
|
11
|
+
# Generated by adapters/<tool>/transform.sh.
|
|
12
|
+
#
|
|
13
|
+
# Enforces:
|
|
14
|
+
# - meta-discipline.md token-economy section: range reads mandatory for
|
|
15
|
+
# files exceeding the line threshold.
|
|
16
|
+
#
|
|
17
|
+
# Behavior:
|
|
18
|
+
# 1. Parse PreToolUse stdin payload.
|
|
19
|
+
# 2. If tool_name != Read → allow (silent exit 0).
|
|
20
|
+
# 3. Resolve file_path; if missing or unreadable → allow (let Read tool error).
|
|
21
|
+
# 4. wc -l file. If lines < threshold → allow.
|
|
22
|
+
# 5. If offset OR limit present → allow.
|
|
23
|
+
# 6. If override env set → allow.
|
|
24
|
+
# 7. Else → deny with diagnostic message recommending offset/limit or Grep.
|
|
25
|
+
#
|
|
26
|
+
# Override (rare, e.g. small JSON config that happens to be wide):
|
|
27
|
+
# ${CONDUCTOR_LARGE_READ_OVERRIDE_ENV}=1
|
|
28
|
+
#
|
|
29
|
+
# Exit code meaning in PreToolUse hooks (Claude Code):
|
|
30
|
+
# 0 with no JSON output → allow
|
|
31
|
+
# 0 with {"permissionDecision":"deny"} → block with reason (shown to LLM)
|
|
32
|
+
# non-zero → allow (fail-open)
|
|
33
|
+
|
|
34
|
+
set -u
|
|
35
|
+
|
|
36
|
+
# Placeholders filled by adapters/<tool>/transform.sh:
|
|
37
|
+
# ${CONDUCTOR_LARGE_FILE_LINE_THRESHOLD} — line threshold (default 500)
|
|
38
|
+
# ${CONDUCTOR_LARGE_READ_OVERRIDE_ENV} — env var to bypass (default CONDUCTOR_ALLOW_LARGE_READ)
|
|
39
|
+
|
|
40
|
+
THRESHOLD="${CONDUCTOR_LARGE_FILE_LINE_THRESHOLD:-500}"
|
|
41
|
+
OVERRIDE_ENV_NAME="${CONDUCTOR_LARGE_READ_OVERRIDE_ENV:-CONDUCTOR_ALLOW_LARGE_READ}"
|
|
42
|
+
|
|
43
|
+
# Honor opt-out before any work.
|
|
44
|
+
OVERRIDE_VAL="$(/usr/bin/printenv "$OVERRIDE_ENV_NAME" 2>/dev/null || true)"
|
|
45
|
+
[ "$OVERRIDE_VAL" = "1" ] && exit 0
|
|
46
|
+
|
|
47
|
+
command -v python3 >/dev/null 2>&1 || exit 0
|
|
48
|
+
|
|
49
|
+
PAYLOAD_RAW="$(/bin/cat 2>/dev/null || true)"
|
|
50
|
+
[ -n "$PAYLOAD_RAW" ] || exit 0
|
|
51
|
+
|
|
52
|
+
# Single python pass extracts tool_name + file_path + offset/limit presence.
|
|
53
|
+
# Output format: TOOL=<name>\nPATH=<file>\nHAS_OFFSET=<0|1>\nHAS_LIMIT=<0|1>
|
|
54
|
+
PARSED=$(PAYLOAD_RAW="$PAYLOAD_RAW" python3 -c "
|
|
55
|
+
import json, os, sys
|
|
56
|
+
try:
|
|
57
|
+
d = json.loads(os.environ.get('PAYLOAD_RAW', '{}'))
|
|
58
|
+
except Exception:
|
|
59
|
+
sys.exit(0)
|
|
60
|
+
if d.get('tool_name', '') != 'Read':
|
|
61
|
+
sys.exit(0)
|
|
62
|
+
inp = d.get('tool_input', {}) or {}
|
|
63
|
+
fp = inp.get('file_path', '') or ''
|
|
64
|
+
has_off = '1' if (inp.get('offset') is not None) else '0'
|
|
65
|
+
has_lim = '1' if (inp.get('limit') is not None) else '0'
|
|
66
|
+
print('TOOL=Read')
|
|
67
|
+
print('PATH=' + fp)
|
|
68
|
+
print('HAS_OFFSET=' + has_off)
|
|
69
|
+
print('HAS_LIMIT=' + has_lim)
|
|
70
|
+
" 2>/dev/null)
|
|
71
|
+
|
|
72
|
+
[ -n "$PARSED" ] || exit 0
|
|
73
|
+
|
|
74
|
+
FILE_PATH=""
|
|
75
|
+
HAS_OFFSET="0"
|
|
76
|
+
HAS_LIMIT="0"
|
|
77
|
+
while IFS='=' read -r key val; do
|
|
78
|
+
case "$key" in
|
|
79
|
+
PATH) FILE_PATH="$val" ;;
|
|
80
|
+
HAS_OFFSET) HAS_OFFSET="$val" ;;
|
|
81
|
+
HAS_LIMIT) HAS_LIMIT="$val" ;;
|
|
82
|
+
esac
|
|
83
|
+
done <<EOF
|
|
84
|
+
$PARSED
|
|
85
|
+
EOF
|
|
86
|
+
|
|
87
|
+
# Allow if range read.
|
|
88
|
+
if [ "$HAS_OFFSET" = "1" ] || [ "$HAS_LIMIT" = "1" ]; then
|
|
89
|
+
exit 0
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
# File must exist + be readable for size check; else let Read tool handle errors.
|
|
93
|
+
[ -n "$FILE_PATH" ] || exit 0
|
|
94
|
+
[ -r "$FILE_PATH" ] || exit 0
|
|
95
|
+
[ -f "$FILE_PATH" ] || exit 0
|
|
96
|
+
|
|
97
|
+
LINES=$(/usr/bin/wc -l < "$FILE_PATH" 2>/dev/null | /usr/bin/tr -d ' ')
|
|
98
|
+
case "$LINES" in
|
|
99
|
+
''|*[!0-9]*) exit 0 ;;
|
|
100
|
+
esac
|
|
101
|
+
|
|
102
|
+
[ "$LINES" -lt "$THRESHOLD" ] && exit 0
|
|
103
|
+
|
|
104
|
+
# Build deny payload.
|
|
105
|
+
REASON="[large-file-read-guard] File '${FILE_PATH}' has ${LINES} lines (threshold ${THRESHOLD}). Pass offset/limit (e.g. limit:100, offset:0) to range-read, or use Grep to find the specific symbol/section first. Override (rare, e.g. wide JSON config): set ${OVERRIDE_ENV_NAME}=1 in environment."
|
|
106
|
+
|
|
107
|
+
REASON="$REASON" python3 -c "
|
|
108
|
+
import json, os
|
|
109
|
+
print(json.dumps({
|
|
110
|
+
'hookSpecificOutput': {
|
|
111
|
+
'hookEventName': 'PreToolUse',
|
|
112
|
+
'permissionDecision': 'deny',
|
|
113
|
+
'permissionDecisionReason': os.environ.get('REASON', ''),
|
|
114
|
+
}
|
|
115
|
+
}))
|
|
116
|
+
"
|
|
117
|
+
exit 0
|