Skip to content

Commit 93c33c2

Browse files
more changes
1 parent bcb4742 commit 93c33c2

9 files changed

Lines changed: 49 additions & 187 deletions

File tree

.ai-workspace/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ This directory contains the infrastructure for the AI Workspace Template.
2121
| Script | Purpose |
2222
|--------|---------|
2323
| `align-workspace.py` | Pre-commit: regenerates AGENTS.md, manages feature dirs |
24-
| `session-sync.py` | Session start: syncs submodules, discovers tools |
24+
| `session-start.py` | Session start: reports repo status, discovers tools |
2525
| `transpile-skills.py` | Validates and distributes skills |
2626
| `transpile-commands.py` | Validates and distributes commands |
2727
| `mktmpdir.py` | Creates unique task directories under `.tmp/` |
@@ -46,7 +46,6 @@ The workspace is configured via `ai-workspace.toml` at the repository root. Key
4646
|---------|---------|
4747
| `[workspace]` | General settings (name) |
4848
| `[repositories]` | Repository status and sync settings |
49-
| `[hooks.session_start]` | Session start script config |
5049
| `[features]` | Enable/disable skills, commands, agent-docs |
5150
| `[tools]` | Tool discovery settings |
5251
| `[distribution]` | Where to distribute skills/commands |

.ai-workspace/docs/configuration.md

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@ include_workspace_root = false
1818
template_upstream_url = "https://github.com/MichaelYochpaz/ai-workspace-template.git"
1919
template_upstream_git_remote = "template-upstream"
2020

21-
[hooks.session_start]
22-
script = ".ai-workspace/scripts/session-start.py"
23-
timeout = 120
24-
2521
[features]
2622
skills = true
2723
commands = true
@@ -93,27 +89,22 @@ Controls repository status reporting at session start. Section: `[repositories]`
9389

9490
---
9591

96-
## Session start hook
97-
98-
Configures the script that runs when an AI tool session starts. Section: `[hooks.session_start]`
99-
100-
`script`
101-
: **Type:** string -- **Default:** *(required)*
102-
103-
Path to the session-start script
92+
## Session hooks
10493

105-
`timeout`
106-
: **Type:** int -- **Default:** `120`
94+
Session hooks are pre-configured statically in each tool's config directory -- they're committed to the repo and work out of the box.
10795

108-
Timeout in seconds
109-
110-
The default script (`.ai-workspace/scripts/session-start.py`) runs two tasks:
96+
The session-start script (`.ai-workspace/scripts/session-start.py`) runs two tasks:
11197

11298
1. **Repository status** -- Reports each submodule's branch, uncommitted changes, and how far behind remote
11399
2. **Tool discovery** -- Detects installed CLI tools and injects usage instructions
114100

115101
The output is wrapped in `<session-context>` XML tags that agents consume automatically.
116102

103+
Hook configurations are stored in:
104+
105+
- **Claude Code** -- `.claude/settings.json`
106+
- **OpenCode** -- `.opencode/plugins/session-sync.ts`
107+
117108
---
118109

119110
## Features
@@ -254,7 +245,6 @@ Configuration is applied at two points:
254245

255246
1. **Pre-commit** (`align-workspace.py`) -- Reads `ai-workspace.toml` and:
256247
- Manages feature directories (create/remove based on `[features]`)
257-
- Registers session hooks with Claude Code (`.claude/settings.json`)
258248
- Regenerates `schema.json`
259249
- Renders `AGENTS.md` from the Jinja2 template
260250
- Runs skill and command validators

.ai-workspace/docs/index.md

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,55 +12,65 @@ A meta-repository template that aggregates related repositories using git submod
1212

1313
Aggregate repos as submodules with automatic git status reporting to agents
1414

15-
[:octicons-arrow-right-24: Learn more](repositories.md)
15+
[Learn more :material-arrow-right:](repositories.md)
1616

17-
- :material-magnify:{ .lg .middle } __Tool Discovery__
17+
- :material-file-document-outline:{ .lg .middle } __Agent Docs__
1818

1919
---
2020

21-
Detect installed CLI tools and inject usage instructions into agent context
21+
Modular docs that agents selectively load based on task relevance
2222

23-
[:octicons-arrow-right-24: Learn more](features/tool-discovery.md)
23+
[Learn more :material-arrow-right:](features/agent-docs.md)
2424

25-
- :material-file-document-outline:{ .lg .middle } __Agent Docs__
25+
- :material-file-cog:{ .lg .middle } __AGENTS.md Generation__
2626

2727
---
2828

29-
Modular docs that agents selectively load based on task relevance
29+
Auto-generate agent instructions from templates and config on every commit
3030

31-
[:octicons-arrow-right-24: Learn more](features/agent-docs.md)
31+
[Learn more :material-arrow-right:](agents-md.md)
3232

33-
- :material-file-cog:{ .lg .middle } __AGENTS.md Generation__
33+
- :material-magnify:{ .lg .middle } __Tool Discovery__
3434

3535
---
3636

37-
Auto-generate agent instructions from templates and config on every commit
37+
Detect installed CLI tools and inject usage instructions into agent context
3838

39-
[:octicons-arrow-right-24: Learn more](agents-md.md)
39+
[Learn more :material-arrow-right:](features/tool-discovery.md)
4040

4141
- :material-lightning-bolt:{ .lg .middle } __Skills__
4242

4343
---
4444

4545
Distribute skill directories to tool-specific paths via symlinks
4646

47-
[:octicons-arrow-right-24: Learn more](features/skills.md)
47+
[Learn more :material-arrow-right:](features/skills.md)
4848

4949
- :material-console:{ .lg .middle } __Commands__
5050

5151
---
5252

5353
Distribute command files across tools, converting formats where needed
5454

55-
[:octicons-arrow-right-24: Learn more](features/commands.md)
55+
[Learn more :material-arrow-right:](features/commands.md)
56+
57+
- :material-folder-clock:{ .lg .middle } __Temporary Files__
58+
59+
---
60+
61+
Git-ignored workspace for agent artifacts, organized by task
62+
63+
[Learn more :material-arrow-right:](features/temporary-files.md)
5664

5765
</div>
5866

5967
## How it works
6068

61-
### Session start
69+
### Session hooks
70+
71+
Some AI tools support **hooks** -- scripts that run automatically at specific lifecycle points (e.g., session start, pre-prompt). This workspace uses a session-start hook to inject context into the agent before it begins work.
6272

63-
For tools with hook support ([Claude Code](https://code.claude.com/docs/en/hooks), [OpenCode](https://opencode.ai/docs/plugins/)), a session-start script runs automatically when an AI session begins:
73+
For tools with hook support ([Claude Code](https://code.claude.com/docs/en/hooks), [OpenCode](https://opencode.ai/docs/plugins/)), the session-start script runs automatically when an AI session begins:
6474

6575
``` mermaid
6676
graph LR
@@ -78,8 +88,8 @@ The agent immediately knows which repos exist, what branch each is on, whether t
7888
Separately, on every commit, pre-commit hooks keep the workspace aligned:
7989

8090
- Regenerate `AGENTS.md` from templates and config
81-
- Validate skill and command frontmatter
82-
- Manage feature directories
91+
- Validate skill and command definitions
92+
- Sync workspace structure with config
8393

8494
These are two independent flows -- session hooks handle runtime context, pre-commit handles workspace integrity.
8595

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/* Center the last card when there's an odd number in a 2-column grid */
2+
.grid.cards > ul > li:last-child:nth-child(odd) {
3+
grid-column: 1 / -1;
4+
max-width: 50%;
5+
justify-self: center;
6+
}

.ai-workspace/schema.json

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -68,46 +68,6 @@
6868
"title": "FeaturesConfig",
6969
"type": "object"
7070
},
71-
"HookConfig": {
72-
"description": "Configuration for a single hook.",
73-
"properties": {
74-
"script": {
75-
"description": "Path to the script to run",
76-
"title": "Script",
77-
"type": "string"
78-
},
79-
"timeout": {
80-
"default": 120,
81-
"description": "Timeout in seconds",
82-
"title": "Timeout",
83-
"type": "integer"
84-
}
85-
},
86-
"required": [
87-
"script"
88-
],
89-
"title": "HookConfig",
90-
"type": "object"
91-
},
92-
"HooksConfig": {
93-
"description": "Hook definitions.",
94-
"properties": {
95-
"session_start": {
96-
"anyOf": [
97-
{
98-
"$ref": "#/$defs/HookConfig"
99-
},
100-
{
101-
"type": "null"
102-
}
103-
],
104-
"default": null,
105-
"description": "Hook that runs when AI tool sessions start"
106-
}
107-
},
108-
"title": "HooksConfig",
109-
"type": "object"
110-
},
11171
"RepositoriesConfig": {
11272
"description": "Repository status and sync configuration.",
11373
"properties": {
@@ -173,9 +133,6 @@
173133
"features": {
174134
"$ref": "#/$defs/FeaturesConfig"
175135
},
176-
"hooks": {
177-
"$ref": "#/$defs/HooksConfig"
178-
},
179136
"repositories": {
180137
"$ref": "#/$defs/RepositoriesConfig"
181138
},

.ai-workspace/scripts/align-workspace.py

Lines changed: 4 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#!/usr/bin/env python3
22
"""Align workspace configuration with ai-workspace.toml."""
33

4-
import json
54
import shutil
65
import subprocess
76
import sys
@@ -16,7 +15,6 @@
1615
sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
1716
from config import AIWorkspaceConfig, export_schema, load_config
1817

19-
MANAGED_SIGNATURE = ".ai-workspace/scripts/"
2018
PLACEHOLDER_TAG = "<placeholder>"
2119

2220

@@ -30,52 +28,6 @@ class AgentDoc:
3028
doc_path: str
3129

3230

33-
def load_json(path: Path) -> dict[str, Any]:
34-
"""Load JSON file, returning empty dict if missing or invalid."""
35-
if not path.exists():
36-
return {}
37-
try:
38-
with open(path, "r", encoding="utf-8") as f:
39-
return json.load(f)
40-
except json.JSONDecodeError:
41-
print(f"Warning: {path} contains invalid JSON. Starting fresh.")
42-
return {}
43-
44-
45-
def save_deterministic_json(data: dict[str, Any], path: Path) -> None:
46-
"""Save JSON with deterministic key ordering for minimal diffs."""
47-
path.parent.mkdir(parents=True, exist_ok=True)
48-
with open(path, "w", encoding="utf-8") as f:
49-
json.dump(data, f, indent=2, sort_keys=True)
50-
f.write("\n") # POSIX trailing newline
51-
52-
53-
def is_managed_hook(hook: dict[str, Any]) -> bool:
54-
"""Check if a hook is managed by ai-workspace."""
55-
if "hooks" in hook:
56-
for subhook in hook["hooks"]:
57-
if subhook.get("type") == "command":
58-
command = subhook.get("command", "")
59-
if MANAGED_SIGNATURE in command:
60-
return True
61-
return False
62-
63-
64-
def merge_claude_settings(
65-
existing: dict[str, Any],
66-
managed_hooks: list[dict[str, Any]],
67-
) -> dict[str, Any]:
68-
"""Merge managed hooks into existing settings, preserving user hooks."""
69-
if "hooks" not in existing:
70-
existing["hooks"] = {}
71-
72-
current_hooks = existing["hooks"].get("SessionStart", [])
73-
user_hooks = [h for h in current_hooks if not is_managed_hook(h)]
74-
existing["hooks"]["SessionStart"] = user_hooks + managed_hooks
75-
76-
return existing
77-
78-
7931
def is_safe_to_remove(dir_path: Path, readme_name: str = "README.md") -> bool:
8032
"""Check if directory can be safely removed (only has template files)."""
8133
if not dir_path.exists():
@@ -120,31 +72,6 @@ def manage_features(config: AIWorkspaceConfig, base_dir: Path) -> None:
12072
)
12173

12274

123-
def register_claude_hooks(config: AIWorkspaceConfig, base_dir: Path) -> None:
124-
"""Register hooks with Claude Code."""
125-
if not config.hooks.session_start:
126-
return
127-
128-
settings_path = base_dir / ".claude/settings.json"
129-
existing = load_json(settings_path)
130-
131-
managed_hooks = [
132-
{
133-
"matcher": "startup|resume",
134-
"hooks": [
135-
{
136-
"type": "command",
137-
"command": f'uv run "$CLAUDE_PROJECT_DIR/{config.hooks.session_start.script}"',
138-
"timeout": config.hooks.session_start.timeout,
139-
"statusMessage": "Initializing session...",
140-
}
141-
],
142-
}
143-
]
144-
145-
merged = merge_claude_settings(existing, managed_hooks)
146-
save_deterministic_json(merged, settings_path)
147-
print(f"Updated {settings_path}")
14875

14976

15077
def sync_schema(base_dir: Path) -> bool:
@@ -460,19 +387,16 @@ def main():
460387
print(f"Error: {e}")
461388
sys.exit(1)
462389

463-
# 3. Register hooks
464-
register_claude_hooks(config, base_dir)
465-
466-
# 4. Sync schema
390+
# 3. Sync schema
467391
sync_schema(base_dir)
468392

469-
# 5. Render AGENTS.md
393+
# 4. Render AGENTS.md
470394
render_agents_md(config, base_dir)
471395

472-
# 6. Run validators
396+
# 5. Run validators
473397
run_validators(config, base_dir)
474398

475-
# 7. Check for template updates (non-blocking notice)
399+
# 6. Check for template updates (non-blocking notice)
476400
check_template_updates(config, base_dir)
477401

478402
print()

.ai-workspace/zensical.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ nav = [
2020

2121
]
2222

23+
extra_css = ["stylesheets/custom.css"]
24+
2325
[project.repo]
2426
url = "https://github.com/MichaelYochpaz/ai-workspace-template"
2527
name = "MichaelYochpaz/ai-workspace-template"

.opencode/plugins/session-sync.ts

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,12 @@
11
// Injects session-start script output into the system prompt.
2-
// Reads configuration from ai-workspace.toml at runtime.
32

43
import type { Plugin } from "@opencode-ai/plugin";
54

6-
const CONFIG = "ai-workspace.toml";
5+
const SCRIPT = ".ai-workspace/scripts/session-start.py";
76
const SERVICE = "SessionStartPlugin";
87

9-
interface Config {
10-
hooks?: {
11-
session_start?: {
12-
script?: string;
13-
};
14-
};
15-
}
16-
178
export const SessionStartPlugin: Plugin = async (ctx) => {
18-
// Read script path from config once at plugin initialization
19-
const config = await Bun.file(CONFIG).text().then(
20-
(text) => Bun.TOML.parse(text) as Config,
21-
() => null
22-
);
23-
const script = config?.hooks?.session_start?.script;
24-
25-
if (!script) {
26-
await ctx.client.app.log({
27-
body: { level: "info", message: "No session_start hook configured in ai-workspace.toml", service: SERVICE }
28-
});
29-
return {};
30-
}
9+
const script = SCRIPT;
3110

3211
// Cache Promises (not resolved values) to prevent race conditions between
3312
// session.created and experimental.chat.system.transform firing concurrently

0 commit comments

Comments
 (0)