odd-studio 3.7.5 → 3.7.6

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.
Files changed (36) hide show
  1. package/README.md +0 -3
  2. package/bin/commands/init.js +3 -41
  3. package/bin/commands/status.js +0 -3
  4. package/bin/odd-studio.js +1 -1
  5. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  6. package/codex-plugin/hooks.json +25 -7
  7. package/hooks/odd-studio.sh +0 -31
  8. package/package.json +1 -2
  9. package/plugins/plugin-gates.js +104 -79
  10. package/scripts/command-definitions.js +7 -47
  11. package/scripts/hook-definitions.js +137 -0
  12. package/scripts/install-codex-commands.js +11 -3
  13. package/scripts/install-commands.js +2 -2
  14. package/scripts/setup-codex-plugin.js +7 -0
  15. package/scripts/setup-hooks.js +6 -130
  16. package/scripts/state-schema.js +2 -4
  17. package/skill/SKILL.md +50 -675
  18. package/skill/docs/build/build-protocol.md +1 -5
  19. package/skill/docs/build/confirm-protocol.md +41 -0
  20. package/skill/docs/build/export-protocol.md +45 -0
  21. package/skill/docs/chapters/chapter-10.md +2 -2
  22. package/skill/docs/chapters/chapter-11.md +3 -5
  23. package/skill/docs/chapters/chapter-12.md +2 -2
  24. package/skill/docs/chapters/chapter-14.md +8 -12
  25. package/skill/docs/runtime/build-entry.md +64 -0
  26. package/skill/docs/runtime/shared-commands.md +65 -0
  27. package/skill/docs/runtime/status-protocol.md +11 -0
  28. package/skill/docs/startup/startup-protocol.md +90 -0
  29. package/skill/odd-build/SKILL.md +6 -4
  30. package/skill/odd-debug/SKILL.md +2 -4
  31. package/skill/odd-deploy/SKILL.md +8 -3
  32. package/skill/odd-plan/SKILL.md +10 -3
  33. package/skill/odd-status/SKILL.md +3 -3
  34. package/skill/odd-swarm/SKILL.md +5 -4
  35. package/templates/.odd/state.json +1 -3
  36. package/templates/AGENTS.md +52 -190
@@ -0,0 +1,137 @@
1
+ 'use strict';
2
+
3
+ export const HOOK_GROUPS = [
4
+ {
5
+ event: 'PreToolUse',
6
+ matcher: 'Agent',
7
+ gates: [
8
+ { name: 'brief-gate', timeout: 5, status: 'ODD brief gate...' },
9
+ { name: 'build-gate', timeout: 5, status: 'ODD build gate...' },
10
+ ],
11
+ },
12
+ {
13
+ event: 'PreToolUse',
14
+ matcher: 'Write',
15
+ gates: [
16
+ { name: 'swarm-write', timeout: 5, status: 'ODD swarm write gate...' },
17
+ { name: 'verify-gate', timeout: 5, status: 'ODD verification gate...' },
18
+ { name: 'confirm-gate', timeout: 5, status: 'ODD confirm gate...' },
19
+ ],
20
+ },
21
+ {
22
+ event: 'PreToolUse',
23
+ matcher: 'Edit',
24
+ gates: [
25
+ { name: 'swarm-write', timeout: 5, status: 'ODD swarm write gate...' },
26
+ { name: 'verify-gate', timeout: 5, status: 'ODD verification gate...' },
27
+ { name: 'confirm-gate', timeout: 5, status: 'ODD confirm gate...' },
28
+ ],
29
+ },
30
+ {
31
+ event: 'UserPromptSubmit',
32
+ matcher: null,
33
+ gates: [
34
+ { name: 'swarm-guard', timeout: 5, status: 'ODD swarm guard...' },
35
+ ],
36
+ },
37
+ {
38
+ event: 'PostToolUse',
39
+ matcher: 'Write',
40
+ gates: [
41
+ { name: 'plan-complete-gate', timeout: 5, status: 'ODD plan complete gate...' },
42
+ ],
43
+ },
44
+ {
45
+ event: 'PostToolUse',
46
+ matcher: 'Edit',
47
+ gates: [
48
+ { name: 'plan-complete-gate', timeout: 5, status: 'ODD plan complete gate...' },
49
+ ],
50
+ },
51
+ {
52
+ event: 'PostToolUse',
53
+ matcher: 'Bash',
54
+ gates: [
55
+ { name: 'session-save', timeout: 10, status: 'ODD session save...' },
56
+ ],
57
+ },
58
+ {
59
+ event: 'PostToolUse',
60
+ matcher: 'mcp__odd-flow__memory_store',
61
+ gates: [
62
+ { name: 'store-validate', timeout: 5, status: 'ODD store validate...' },
63
+ ],
64
+ },
65
+ {
66
+ event: 'PostToolUse',
67
+ matcher: 'mcp__odd-flow__coordination_sync',
68
+ gates: [
69
+ { name: 'sync-validate', timeout: 5, status: 'ODD sync validate...' },
70
+ ],
71
+ },
72
+ {
73
+ event: 'PostToolUse',
74
+ matcher: 'Write',
75
+ gates: [
76
+ { name: 'code-quality', timeout: 5, status: 'ODD code quality...' },
77
+ { name: 'security-quality', timeout: 5, status: 'ODD security quality...' },
78
+ { name: 'brief-quality', timeout: 5, status: 'ODD brief quality...' },
79
+ { name: 'outcome-quality', timeout: 5, status: 'ODD outcome quality...' },
80
+ ],
81
+ },
82
+ {
83
+ event: 'PostToolUse',
84
+ matcher: 'Edit',
85
+ gates: [
86
+ { name: 'code-quality', timeout: 5, status: 'ODD code quality...' },
87
+ { name: 'security-quality', timeout: 5, status: 'ODD security quality...' },
88
+ ],
89
+ },
90
+ ];
91
+
92
+ export function buildClaudeHooksConfig(hookFile) {
93
+ const hooks = {};
94
+
95
+ for (const { event, matcher, gates } of HOOK_GROUPS) {
96
+ if (!hooks[event]) hooks[event] = [];
97
+
98
+ const entry = {
99
+ _oddStudio: true,
100
+ hooks: gates.map((gate) => ({
101
+ type: 'command',
102
+ command: `.claude/hooks/${hookFile} ${gate.name}`,
103
+ timeout: gate.timeout,
104
+ statusMessage: gate.status,
105
+ })),
106
+ };
107
+
108
+ if (matcher) entry.matcher = matcher;
109
+ hooks[event].push(entry);
110
+ }
111
+
112
+ return hooks;
113
+ }
114
+
115
+ export function buildCodexHooksConfig(hookFile) {
116
+ const hooks = {};
117
+
118
+ for (const { event, matcher, gates } of HOOK_GROUPS) {
119
+ if (!hooks[event]) hooks[event] = [];
120
+
121
+ const entry = {
122
+ hooks: gates.map((gate) => ({
123
+ type: 'command',
124
+ command: `./hooks/${hookFile} ${gate.name}`,
125
+ })),
126
+ };
127
+
128
+ if (matcher) entry.matcher = matcher;
129
+ hooks[event].push(entry);
130
+ }
131
+
132
+ return { hooks };
133
+ }
134
+
135
+ export function countHookCommands() {
136
+ return HOOK_GROUPS.reduce((sum, group) => sum + group.gates.length, 0);
137
+ }
@@ -29,10 +29,10 @@ function buildMainCommand(cmd) {
29
29
  '',
30
30
  `# ${CODEX_COMMAND_PREFIX}${cmd.name}`,
31
31
  '',
32
- 'You are now operating as the ODD Studio coach.',
32
+ 'You are now operating as the ODD Studio startup coach.',
33
33
  '',
34
34
  'Read this file now:',
35
- `- \`${CODEX_ODD_ROOT}SKILL.md\` — the full ODD Studio coach`,
35
+ `- \`${CODEX_ODD_ROOT}SKILL.md\` — the ODD Studio startup coach`,
36
36
  '',
37
37
  'Then execute the startup state check exactly as documented.',
38
38
  '',
@@ -53,5 +53,13 @@ function buildSubcommand(cmd) {
53
53
  }
54
54
 
55
55
  function rewriteBody(body) {
56
- return body.replace(/`\.opencode\/odd\//g, `\`${CODEX_ODD_ROOT}`);
56
+ return body
57
+ .replace(/`\.opencode\/odd\/odd-build\/SKILL\.md`/g, '`plugins/odd-studio/skills/odd-build/SKILL.md`')
58
+ .replace(/`\.opencode\/odd\/odd-debug\/SKILL\.md`/g, '`plugins/odd-studio/skills/odd-debug/SKILL.md`')
59
+ .replace(/`\.opencode\/odd\/odd-plan\/SKILL\.md`/g, '`plugins/odd-studio/skills/odd-plan/SKILL.md`')
60
+ .replace(/`\.opencode\/odd\/odd-swarm\/SKILL\.md`/g, '`plugins/odd-studio/skills/odd-swarm/SKILL.md`')
61
+ .replace(/`\.opencode\/odd\/odd-status\/SKILL\.md`/g, '`plugins/odd-studio/skills/odd-status/SKILL.md`')
62
+ .replace(/`\.opencode\/odd\/odd-deploy\/SKILL\.md`/g, '`plugins/odd-studio/skills/odd-deploy/SKILL.md`')
63
+ .replace(/`\.opencode\/odd\/odd-sync\/SKILL\.md`/g, '`plugins/odd-studio/skills/odd-sync/SKILL.md`')
64
+ .replace(/`\.opencode\/odd\//g, `\`${CODEX_ODD_ROOT}`);
57
65
  }
@@ -63,10 +63,10 @@ export default async function installCommands(packageRoot, targetDir, options =
63
63
  '',
64
64
  '# /odd',
65
65
  '',
66
- 'You are now operating as the ODD Studio coach.',
66
+ 'You are now operating as the ODD Studio startup coach.',
67
67
  '',
68
68
  'Read this file now:',
69
- '- `.opencode/odd/SKILL.md` — the full ODD Studio coach',
69
+ '- `.opencode/odd/SKILL.md` — the ODD Studio startup coach',
70
70
  '',
71
71
  'Then execute the startup state check exactly as documented.',
72
72
  '',
@@ -7,6 +7,7 @@ import {
7
7
  ODD_HOOK_FILE,
8
8
  } from './assets.js';
9
9
  import installCodexCommands from './install-codex-commands.js';
10
+ import { buildCodexHooksConfig } from './hook-definitions.js';
10
11
 
11
12
  const PLUGIN_RELATIVE_PATH = `./plugins/${CODEX_PLUGIN_NAME}`;
12
13
  const MARKETPLACE_ENTRY = {
@@ -36,6 +37,7 @@ export default async function setupCodexPlugin(packageRoot, targetDir) {
36
37
  await installCodexSkills(skillSource, pluginDest);
37
38
  await installCodexCommands(pluginDest);
38
39
  await installCodexHook(hookSource, pluginDest);
40
+ await installCodexHooksConfig(pluginDest);
39
41
  const marketplaceUpdated = await updateMarketplace(targetDir);
40
42
 
41
43
  return { destination: pluginDest, marketplaceUpdated };
@@ -80,6 +82,11 @@ async function installCodexHook(hookSource, pluginDest) {
80
82
  await fs.chmod(hookDest, 0o755);
81
83
  }
82
84
 
85
+ async function installCodexHooksConfig(pluginDest) {
86
+ const hooksPath = path.join(pluginDest, 'hooks.json');
87
+ await fs.writeJson(hooksPath, buildCodexHooksConfig(ODD_HOOK_FILE), { spaces: 2 });
88
+ }
89
+
83
90
  async function updateMarketplace(targetDir) {
84
91
  const marketplacePath = path.join(targetDir, '.agents', 'plugins', 'marketplace.json');
85
92
  await fs.ensureDir(path.dirname(marketplacePath));
@@ -2,117 +2,7 @@
2
2
  import fs from 'fs-extra';
3
3
  import path from 'path';
4
4
  import { ODD_HOOK_FILE as HOOK_FILE } from './assets.js';
5
-
6
- // All ODD Studio gates, grouped by event + matcher.
7
- // Each entry becomes one hooks[] item in settings.json pointing to:
8
- // odd-studio.sh <gate-name>
9
- //
10
- // Two-marker system (swarm-write gate):
11
- // 1. .odd/.odd-flow-swarm-active — created by *build, 24h TTL (session marker)
12
- // 2. .odd/.odd-flow-agent-token — created by Task agents, 120s TTL (write token)
13
- //
14
- // The orchestrator (main conversation) can read files and coordinate,
15
- // but CANNOT write source code. Only Task agents can write source code,
16
- // and only after creating the agent token. This prevents the LLM from
17
- // bypassing swarm coordination by editing files directly from the main
18
- // conversation.
19
- const GATES = [
20
- // ── PreToolUse ──────────────────────────────────────────────────────────
21
- {
22
- event: 'PreToolUse',
23
- matcher: 'Agent',
24
- gates: [
25
- { name: 'brief-gate', timeout: 5, status: 'ODD brief gate...' },
26
- { name: 'build-gate', timeout: 5, status: 'ODD build gate...' },
27
- ],
28
- },
29
- {
30
- event: 'PreToolUse',
31
- matcher: 'Write',
32
- gates: [
33
- { name: 'swarm-write', timeout: 5, status: 'ODD swarm write gate...' },
34
- { name: 'verify-gate', timeout: 5, status: 'ODD verification gate...' },
35
- { name: 'confirm-gate', timeout: 5, status: 'ODD confirm gate...' },
36
- ],
37
- },
38
- {
39
- event: 'PreToolUse',
40
- matcher: 'Edit',
41
- gates: [
42
- { name: 'swarm-write', timeout: 5, status: 'ODD swarm write gate...' },
43
- { name: 'verify-gate', timeout: 5, status: 'ODD verification gate...' },
44
- { name: 'confirm-gate', timeout: 5, status: 'ODD confirm gate...' },
45
- ],
46
- },
47
- {
48
- event: 'PreToolUse',
49
- matcher: 'Bash',
50
- gates: [
51
- ],
52
- },
53
- // ── UserPromptSubmit ────────────────────────────────────────────────────
54
- {
55
- event: 'UserPromptSubmit',
56
- matcher: null,
57
- gates: [
58
- { name: 'swarm-guard', timeout: 5, status: 'ODD swarm guard...' },
59
- ],
60
- },
61
- // ── PostToolUse ─────────────────────────────────────────────────────────
62
- {
63
- event: 'PostToolUse',
64
- matcher: 'Write',
65
- gates: [
66
- { name: 'plan-complete-gate', timeout: 5, status: 'ODD plan complete gate...' },
67
- ],
68
- },
69
- {
70
- event: 'PostToolUse',
71
- matcher: 'Edit',
72
- gates: [
73
- { name: 'plan-complete-gate', timeout: 5, status: 'ODD plan complete gate...' },
74
- ],
75
- },
76
- {
77
- event: 'PostToolUse',
78
- matcher: 'Bash',
79
- gates: [
80
- { name: 'session-save', timeout: 10, status: 'ODD session save...' },
81
- ],
82
- },
83
- {
84
- event: 'PostToolUse',
85
- matcher: 'mcp__odd-flow__memory_store',
86
- gates: [
87
- { name: 'store-validate', timeout: 5, status: 'ODD store validate...' },
88
- ],
89
- },
90
- {
91
- event: 'PostToolUse',
92
- matcher: 'mcp__odd-flow__coordination_sync',
93
- gates: [
94
- { name: 'sync-validate', timeout: 5, status: 'ODD sync validate...' },
95
- ],
96
- },
97
- {
98
- event: 'PostToolUse',
99
- matcher: 'Write',
100
- gates: [
101
- { name: 'code-quality', timeout: 5, status: 'ODD code quality...' },
102
- { name: 'security-quality', timeout: 5, status: 'ODD security quality...' },
103
- { name: 'brief-quality', timeout: 5, status: 'ODD brief quality...' },
104
- { name: 'outcome-quality', timeout: 5, status: 'ODD outcome quality...' },
105
- ],
106
- },
107
- {
108
- event: 'PostToolUse',
109
- matcher: 'Edit',
110
- gates: [
111
- { name: 'code-quality', timeout: 5, status: 'ODD code quality...' },
112
- { name: 'security-quality', timeout: 5, status: 'ODD security quality...' },
113
- ],
114
- },
115
- ];
5
+ import { HOOK_GROUPS, buildClaudeHooksConfig, countHookCommands } from './hook-definitions.js';
116
6
 
117
7
  export default async function setupHooks(packageRoot, targetDir, options = {}) {
118
8
  const hookSource = path.join(packageRoot, 'hooks', HOOK_FILE);
@@ -149,33 +39,19 @@ export default async function setupHooks(packageRoot, targetDir, options = {}) {
149
39
  }
150
40
 
151
41
  // Step 4: Register all gates
152
- for (const group of GATES) {
153
- const { event, matcher, gates } = group;
42
+ const oddHooks = buildClaudeHooksConfig(HOOK_FILE);
43
+ for (const event of Object.keys(oddHooks)) {
154
44
  if (!settings.hooks[event]) settings.hooks[event] = [];
155
-
156
- const entry = {
157
- _oddStudio: true,
158
- hooks: gates.map((gate) => ({
159
- type: 'command',
160
- command: `.claude/hooks/${HOOK_FILE} ${gate.name}`,
161
- timeout: gate.timeout,
162
- statusMessage: gate.status,
163
- })),
164
- };
165
- if (matcher) entry.matcher = matcher;
166
-
167
- settings.hooks[event].push(entry);
45
+ settings.hooks[event].push(...oddHooks[event]);
168
46
  }
169
47
 
170
48
  // Step 5: Write project-local settings
171
49
  await fs.writeJson(settingsPath, settings, { spaces: 2 });
172
50
 
173
51
  // Count total gate registrations
174
- const totalGates = GATES.reduce((sum, g) => sum + g.gates.length, 0);
175
-
176
52
  return {
177
53
  hookFile: HOOK_FILE,
178
- hookCount: totalGates,
179
- registrations: GATES.length,
54
+ hookCount: countHookCommands(),
55
+ registrations: HOOK_GROUPS.length,
180
56
  };
181
57
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- export const STATE_VERSION = '2.1.0';
3
+ export const STATE_VERSION = '2.2.0';
4
4
 
5
5
  export const STATE_DEFAULTS = {
6
6
  version: STATE_VERSION,
@@ -24,6 +24,7 @@ export const STATE_DEFAULTS = {
24
24
  sessionBriefCount: 0,
25
25
  briefConfirmed: false,
26
26
  verificationConfirmed: false,
27
+ debugSession: false,
27
28
  swarmActive: false,
28
29
  buildPhase: null,
29
30
  currentBuildPhase: null,
@@ -32,9 +33,6 @@ export const STATE_DEFAULTS = {
32
33
  debugTarget: null,
33
34
  debugSummary: null,
34
35
  debugStartedAt: null,
35
- checkpointStatus: 'unknown',
36
- lastCheckpointAt: null,
37
- checkpointFindings: 0,
38
36
  securityBaselineVersion: '2026-04-12',
39
37
  notes: '',
40
38
  };