odd-studio 3.7.4 → 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.
- package/README.md +0 -3
- package/bin/commands/init.js +3 -41
- package/bin/commands/status.js +0 -3
- package/bin/odd-studio.js +1 -1
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/codex-plugin/hooks.json +25 -7
- package/hooks/odd-studio.sh +13 -31
- package/package.json +1 -2
- package/plugins/plugin-gates.js +104 -79
- package/scripts/command-definitions.js +7 -47
- package/scripts/hook-definitions.js +137 -0
- package/scripts/install-codex-commands.js +11 -3
- package/scripts/install-commands.js +2 -2
- package/scripts/setup-codex-plugin.js +7 -0
- package/scripts/setup-hooks.js +6 -130
- package/scripts/state-schema.js +2 -4
- package/skill/SKILL.md +50 -675
- package/skill/docs/build/build-protocol.md +1 -5
- package/skill/docs/build/confirm-protocol.md +41 -0
- package/skill/docs/build/export-protocol.md +45 -0
- package/skill/docs/chapters/chapter-10.md +2 -2
- package/skill/docs/chapters/chapter-11.md +3 -5
- package/skill/docs/chapters/chapter-12.md +2 -2
- package/skill/docs/chapters/chapter-14.md +8 -12
- package/skill/docs/runtime/build-entry.md +64 -0
- package/skill/docs/runtime/shared-commands.md +65 -0
- package/skill/docs/runtime/status-protocol.md +11 -0
- package/skill/docs/startup/startup-protocol.md +90 -0
- package/skill/odd-build/SKILL.md +6 -4
- package/skill/odd-debug/SKILL.md +2 -4
- package/skill/odd-deploy/SKILL.md +8 -3
- package/skill/odd-plan/SKILL.md +10 -3
- package/skill/odd-status/SKILL.md +3 -3
- package/skill/odd-swarm/SKILL.md +5 -4
- package/templates/.odd/state.json +1 -3
- package/templates/AGENTS.md +52 -190
package/README.md
CHANGED
|
@@ -72,7 +72,6 @@ That single command:
|
|
|
72
72
|
- Scaffolds your project structure (`docs/`, `.odd/`, instruction files)
|
|
73
73
|
- Installs the ODD harness into the matching project-local agent surface (`.claude/`, `.opencode/`, or `plugins/odd-studio/`)
|
|
74
74
|
- Configures odd-flow MCP server for cross-session memory in the matching agent config
|
|
75
|
-
- Optionally installs Checkpoint security scanning (you'll be asked)
|
|
76
75
|
- Initialises git with an initial commit
|
|
77
76
|
|
|
78
77
|
**Then open your project in your AI coding agent and start ODD:**
|
|
@@ -150,9 +149,7 @@ ODD Studio installs safety gates that run automatically throughout your build. T
|
|
|
150
149
|
| Brief gate | Before agent spawning | Blocks build agents until the session brief is confirmed |
|
|
151
150
|
| Swarm write gate | Before file writes | Blocks writes during swarm builds unless from an assigned agent |
|
|
152
151
|
| Verify gate | Before state edits | Blocks premature outcome confirmation |
|
|
153
|
-
| Checkpoint gate | Before confirm/commit | Blocks verification and commits until a fresh security scan clears the latest build changes |
|
|
154
152
|
| odd-flow build gate | Before agent spawning | Blocks builds without odd-flow sync |
|
|
155
|
-
| odd-flow commit gate | Before git commit | Blocks commits during build without odd-flow sync |
|
|
156
153
|
| Outcome quality | After writing outcomes | Checks all 6 fields present; flags banned technical vocabulary |
|
|
157
154
|
| Persona quality | After writing personas | Checks all 7 dimensions are present |
|
|
158
155
|
| Code elegance | After writing source files | Checks file length against ODD limits |
|
package/bin/commands/init.js
CHANGED
|
@@ -29,7 +29,7 @@ export function registerInit(program, deps) {
|
|
|
29
29
|
const targetDir = projectName ? path.resolve(process.cwd(), projectName) : process.cwd();
|
|
30
30
|
const resolvedName = projectName || path.basename(process.cwd());
|
|
31
31
|
const agentLabel = getAgentLabel(agent);
|
|
32
|
-
const totalSteps =
|
|
32
|
+
const totalSteps = 4;
|
|
33
33
|
|
|
34
34
|
console.log(chalk.bold(` Setting up: ${chalk.cyan(resolvedName)}`) + chalk.dim(` (${agentLabel})\n`));
|
|
35
35
|
|
|
@@ -47,7 +47,6 @@ export function registerInit(program, deps) {
|
|
|
47
47
|
|
|
48
48
|
await installSkills({ PACKAGE_ROOT, isClaude, isOpenCode, options, print, targetDir, totalSteps });
|
|
49
49
|
await installHooks({ PACKAGE_ROOT, isClaude, isOpenCode, isCodex, options, print, targetDir, totalSteps });
|
|
50
|
-
await maybeInstallCheckpoint({ options, print, targetDir, totalSteps });
|
|
51
50
|
await configureMcp({ agent, options, print, targetDir, totalSteps });
|
|
52
51
|
|
|
53
52
|
print.blank();
|
|
@@ -142,51 +141,14 @@ async function installHooks(context) {
|
|
|
142
141
|
}
|
|
143
142
|
}
|
|
144
143
|
|
|
145
|
-
async function maybeInstallCheckpoint({ options, print, targetDir, totalSteps }) {
|
|
146
|
-
let installCheckpoint = false;
|
|
147
|
-
if (!options.yes) {
|
|
148
|
-
const { default: inquirer } = await import('inquirer');
|
|
149
|
-
const answer = await inquirer.prompt([{
|
|
150
|
-
type: 'confirm',
|
|
151
|
-
name: 'checkpoint',
|
|
152
|
-
message: 'Install Checkpoint security scanning into this project?',
|
|
153
|
-
default: false,
|
|
154
|
-
}]);
|
|
155
|
-
installCheckpoint = answer.checkpoint;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (!installCheckpoint) {
|
|
159
|
-
print.step(4, totalSteps, 'Skipping Checkpoint security scanning');
|
|
160
|
-
print.info('Enable later: npx @darrenjcoxon/vibeguard --install-tools');
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
print.step(4, totalSteps, 'Installing Checkpoint security scanning...');
|
|
165
|
-
const spinner = ora({ text: '', indent: 4 }).start();
|
|
166
|
-
try {
|
|
167
|
-
const { execFileSync } = await import('child_process');
|
|
168
|
-
execFileSync('npx', ['-y', '@darrenjcoxon/vibeguard', '--install-tools'], {
|
|
169
|
-
cwd: targetDir,
|
|
170
|
-
stdio: 'inherit',
|
|
171
|
-
timeout: 60000,
|
|
172
|
-
});
|
|
173
|
-
spinner.stop();
|
|
174
|
-
print.ok('Checkpoint security tools installed');
|
|
175
|
-
} catch {
|
|
176
|
-
spinner.stop();
|
|
177
|
-
print.warn('Checkpoint tools could not be installed');
|
|
178
|
-
print.info('Run: npx @darrenjcoxon/vibeguard --install-tools to enable later');
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
144
|
async function configureMcp({ agent, options, print, targetDir, totalSteps }) {
|
|
183
145
|
if (options.skipMcp) {
|
|
184
|
-
print.step(
|
|
146
|
+
print.step(4, totalSteps, 'Skipping odd-flow MCP server (--skip-mcp)');
|
|
185
147
|
print.info('odd-flow swarm coordination will not be available.');
|
|
186
148
|
return;
|
|
187
149
|
}
|
|
188
150
|
|
|
189
|
-
print.step(
|
|
151
|
+
print.step(4, totalSteps, 'Configuring odd-flow memory server...');
|
|
190
152
|
const spinner = ora({ text: '', indent: 4 }).start();
|
|
191
153
|
try {
|
|
192
154
|
const { default: setupMcp } = await import('../../scripts/setup-mcp.js');
|
package/bin/commands/status.js
CHANGED
|
@@ -28,9 +28,6 @@ export function registerStatus(program, deps) {
|
|
|
28
28
|
console.log(chalk.bold(' Debug: ') + (state.debugStrategy || 'strategy not set'));
|
|
29
29
|
console.log(chalk.bold(' Target: ') + (state.debugTarget || 'target not set'));
|
|
30
30
|
}
|
|
31
|
-
if (state.currentPhase === 'build') {
|
|
32
|
-
console.log(chalk.bold(' Checkpoint: ') + (state.checkpointStatus || 'unknown'));
|
|
33
|
-
}
|
|
34
31
|
print.blank();
|
|
35
32
|
|
|
36
33
|
if (state.personas?.length) {
|
package/bin/odd-studio.js
CHANGED
|
@@ -15,7 +15,7 @@ import { registerUninstall } from './commands/uninstall.js';
|
|
|
15
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
16
|
const __dirname = path.dirname(__filename);
|
|
17
17
|
const require = createRequire(import.meta.url);
|
|
18
|
-
const pkg = require('../package.json');
|
|
18
|
+
const pkg = require('../package.json');
|
|
19
19
|
|
|
20
20
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
21
21
|
const deps = { PACKAGE_ROOT, print };
|
package/codex-plugin/hooks.json
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
|
-
"matcher": "Write
|
|
18
|
+
"matcher": "Write",
|
|
19
19
|
"hooks": [
|
|
20
20
|
{
|
|
21
21
|
"type": "command",
|
|
@@ -32,15 +32,19 @@
|
|
|
32
32
|
]
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
-
"matcher": "
|
|
35
|
+
"matcher": "Edit",
|
|
36
36
|
"hooks": [
|
|
37
37
|
{
|
|
38
38
|
"type": "command",
|
|
39
|
-
"command": "./hooks/odd-studio.sh
|
|
39
|
+
"command": "./hooks/odd-studio.sh swarm-write"
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
"type": "command",
|
|
43
|
-
"command": "./hooks/odd-studio.sh
|
|
43
|
+
"command": "./hooks/odd-studio.sh verify-gate"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"type": "command",
|
|
47
|
+
"command": "./hooks/odd-studio.sh confirm-gate"
|
|
44
48
|
}
|
|
45
49
|
]
|
|
46
50
|
}
|
|
@@ -57,12 +61,26 @@
|
|
|
57
61
|
],
|
|
58
62
|
"PostToolUse": [
|
|
59
63
|
{
|
|
60
|
-
"matcher": "
|
|
64
|
+
"matcher": "Write",
|
|
61
65
|
"hooks": [
|
|
62
66
|
{
|
|
63
67
|
"type": "command",
|
|
64
|
-
"command": "./hooks/odd-studio.sh
|
|
65
|
-
}
|
|
68
|
+
"command": "./hooks/odd-studio.sh plan-complete-gate"
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"matcher": "Edit",
|
|
74
|
+
"hooks": [
|
|
75
|
+
{
|
|
76
|
+
"type": "command",
|
|
77
|
+
"command": "./hooks/odd-studio.sh plan-complete-gate"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"matcher": "Bash",
|
|
83
|
+
"hooks": [
|
|
66
84
|
{
|
|
67
85
|
"type": "command",
|
|
68
86
|
"command": "./hooks/odd-studio.sh session-save"
|
package/hooks/odd-studio.sh
CHANGED
|
@@ -136,6 +136,12 @@ build-gate)
|
|
|
136
136
|
[ "$TOOL_NAME" = "Agent" ] || exit 0
|
|
137
137
|
[ "$CURRENT_PHASE" = "build" ] || exit 0
|
|
138
138
|
|
|
139
|
+
# Quick-fix bypass: orchestrator can dispatch agents for small fixes without full swarm init.
|
|
140
|
+
# Marker has 1h TTL and must be explicitly created via `touch .odd/.odd-quick-fix`.
|
|
141
|
+
if marker_valid ".odd/.odd-quick-fix" 3600; then
|
|
142
|
+
exit 0
|
|
143
|
+
fi
|
|
144
|
+
|
|
139
145
|
# Allow brief-generation and sync agents through
|
|
140
146
|
AGENT_PROMPT=$(echo "$INPUT" | jq -r '.tool_input.prompt // empty' | head -c 300)
|
|
141
147
|
if echo "$AGENT_PROMPT" | grep -qiE '(session.brief|session-brief|generate.*brief|odd-sync|fix.*hook|update.*hook)'; then
|
|
@@ -179,6 +185,13 @@ swarm-write)
|
|
|
179
185
|
exit 0
|
|
180
186
|
fi
|
|
181
187
|
|
|
188
|
+
# Quick-fix bypass: orchestrator can edit source directly for small fixes.
|
|
189
|
+
# Marker has 1h TTL and must be explicitly created via `touch .odd/.odd-quick-fix`.
|
|
190
|
+
# Use for single-file typo/type fixes where full swarm ceremony is overkill.
|
|
191
|
+
if marker_valid ".odd/.odd-quick-fix" 3600; then
|
|
192
|
+
exit 0
|
|
193
|
+
fi
|
|
194
|
+
|
|
182
195
|
# Gate 2: Agent write token must be fresh (120s TTL)
|
|
183
196
|
# Only Task agents create this token — the orchestrator must NOT.
|
|
184
197
|
if ! marker_valid ".odd/.odd-flow-agent-token" 120; then
|
|
@@ -226,8 +239,6 @@ verify-gate)
|
|
|
226
239
|
exit 2
|
|
227
240
|
fi
|
|
228
241
|
|
|
229
|
-
# Checkpoint pre-check removed in v3.7.3 (see checkpoint-gate note below).
|
|
230
|
-
|
|
231
242
|
VERIFIED_CONFIRMED=$(get_state_field "verificationConfirmed")
|
|
232
243
|
if [ "$VERIFIED_CONFIRMED" != "true" ]; then
|
|
233
244
|
echo "ODD STUDIO [verify-gate]: Cannot mark NEW outcomes as verified." >&2
|
|
@@ -237,14 +248,6 @@ verify-gate)
|
|
|
237
248
|
exit 0
|
|
238
249
|
;;
|
|
239
250
|
|
|
240
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
241
|
-
# checkpoint-gate — REMOVED in v3.7.3
|
|
242
|
-
# Vibeguard/Checkpoint was treating dependency-lockfile CVEs as equivalent to
|
|
243
|
-
# code vulnerabilities, making every commit impossible while any transitive
|
|
244
|
-
# dep had an open advisory. Re-introduce only with a scoped scanner that
|
|
245
|
-
# ignores lockfile-only findings.
|
|
246
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
247
|
-
|
|
248
251
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
249
252
|
# PreToolUse: Edit|Write — blocks briefConfirmed without odd-flow store
|
|
250
253
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -263,14 +266,6 @@ confirm-gate)
|
|
|
263
266
|
exit 0
|
|
264
267
|
;;
|
|
265
268
|
|
|
266
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
267
|
-
# commit-gate — REMOVED in v3.7.4
|
|
268
|
-
# The dirty/ready marker treadmill was creating more friction than value.
|
|
269
|
-
# State persistence to odd-flow is now the orchestrator's responsibility at
|
|
270
|
-
# genuine persistence points (session end, outcome verified) rather than a
|
|
271
|
-
# hard-block on every commit.
|
|
272
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
273
|
-
|
|
274
269
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
275
270
|
# UserPromptSubmit — warns every turn if build phase without swarm
|
|
276
271
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -331,10 +326,6 @@ session-save)
|
|
|
331
326
|
exit 0
|
|
332
327
|
;;
|
|
333
328
|
|
|
334
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
335
|
-
# checkpoint-validate — REMOVED in v3.7.3 (see checkpoint-gate note above)
|
|
336
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
337
|
-
|
|
338
329
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
339
330
|
# PostToolUse: Write|Edit state.json — blocks phase transition without Steps 9, 9b, 9d
|
|
340
331
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -376,17 +367,9 @@ plan-complete-gate)
|
|
|
376
367
|
exit 0
|
|
377
368
|
;;
|
|
378
369
|
|
|
379
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
380
|
-
# state-dirty-mark — REMOVED in v3.7.4 (dirty/ready treadmill eliminated)
|
|
381
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
382
|
-
|
|
383
370
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
384
371
|
# PostToolUse: mcp__odd-flow__memory_store — brief-stored marker only
|
|
385
372
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
386
|
-
# v3.7.4: the state-ready / dirty treadmill has been removed. This hook now
|
|
387
|
-
# only creates the brief-stored marker after a session brief is persisted.
|
|
388
|
-
# odd-project-state stores are logged but do not touch any marker — state
|
|
389
|
-
# persistence is no longer gated by a marker state machine.
|
|
390
373
|
store-validate)
|
|
391
374
|
[ "$TOOL_NAME" = "mcp__odd-flow__memory_store" ] || exit 0
|
|
392
375
|
[ "$CURRENT_PHASE" = "build" ] || exit 0
|
|
@@ -489,7 +472,6 @@ code-quality)
|
|
|
489
472
|
|
|
490
473
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
491
474
|
# PostToolUse: Write|Edit — security baseline warnings (stderr, non-blocking)
|
|
492
|
-
# Checkpoint dirty-marking was removed in v3.7.3 along with checkpoint-gate.
|
|
493
475
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
494
476
|
security-quality)
|
|
495
477
|
[ "$TOOL_NAME" = "Write" ] || [ "$TOOL_NAME" = "Edit" ] || exit 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odd-studio",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.6",
|
|
4
4
|
"description": "Outcome-Driven Development for AI coding agents — a planning and build harness for domain experts building serious software with AI. Works with Claude Code, OpenCode, and Codex.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -47,7 +47,6 @@
|
|
|
47
47
|
"chalk": "^5.3.0",
|
|
48
48
|
"commander": "^12.0.0",
|
|
49
49
|
"fs-extra": "^11.2.0",
|
|
50
|
-
"inquirer": "^10.0.0",
|
|
51
50
|
"ora": "^8.0.1"
|
|
52
51
|
},
|
|
53
52
|
"devDependencies": {
|
package/plugins/plugin-gates.js
CHANGED
|
@@ -1,7 +1,56 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
1
2
|
import { resolve } from 'path';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
checkBriefQuality,
|
|
5
|
+
checkCodeElegance,
|
|
6
|
+
checkOutcomeQuality,
|
|
7
|
+
checkPersonaQuality,
|
|
8
|
+
checkSecurityBaseline,
|
|
9
|
+
} from './plugin-quality-checks.js';
|
|
10
|
+
import {
|
|
11
|
+
isBriefFile,
|
|
12
|
+
isOutcomeFile,
|
|
13
|
+
isPersonaFile,
|
|
14
|
+
isSourceCodePath,
|
|
15
|
+
isSrcFile,
|
|
16
|
+
} from './plugin-paths.js';
|
|
17
|
+
import { markerExists, markerValid, touchMarker } from './plugin-markers.js';
|
|
18
|
+
|
|
19
|
+
const HALF_HOUR = 1800000;
|
|
20
|
+
const TWO_HOURS = 7200000;
|
|
21
|
+
const ONE_DAY = 86400000;
|
|
22
|
+
const ONE_HOUR = 3600000;
|
|
23
|
+
const TWO_MINUTES = 120000;
|
|
24
|
+
|
|
25
|
+
function readMarker(name) {
|
|
26
|
+
return resolve(process.cwd(), '.odd', name);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isVerifiedUpgrade(event, filePath) {
|
|
30
|
+
if (!filePath.includes('state.json')) return false;
|
|
31
|
+
|
|
32
|
+
if (event.tool?.name === 'Write') {
|
|
33
|
+
const next = event.input?.content || '';
|
|
34
|
+
const nextCount = (next.match(/"buildStatus"\s*:\s*"verified"/g) || []).length;
|
|
35
|
+
const prevCount = existsSync(filePath)
|
|
36
|
+
? (readFileSync(filePath, 'utf8').match(/"buildStatus"\s*:\s*"verified"/g) || []).length
|
|
37
|
+
: 0;
|
|
38
|
+
return nextCount > prevCount;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const previous = event.input?.old_string || '';
|
|
42
|
+
const next = event.input?.new_string || '';
|
|
43
|
+
return (next.match(/"verified"/g) || []).length > (previous.match(/"verified"/g) || []).length;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function blocksBuildPhaseTransition(newContent) {
|
|
47
|
+
if (!/"currentPhase"\s*:\s*"build"/.test(newContent)) return false;
|
|
48
|
+
return (
|
|
49
|
+
!/"techStackDecided"\s*:\s*true/.test(newContent) ||
|
|
50
|
+
!/"designApproachDecided"\s*:\s*true/.test(newContent) ||
|
|
51
|
+
!/"architectureDocGenerated"\s*:\s*true/.test(newContent)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
5
54
|
|
|
6
55
|
export function createBeforeHook(readState) {
|
|
7
56
|
return (event) => {
|
|
@@ -12,63 +61,58 @@ export function createBeforeHook(readState) {
|
|
|
12
61
|
const filePath = event.input?.file_path || event.input?.path || '';
|
|
13
62
|
|
|
14
63
|
if (toolName === 'Agent' && state.currentPhase === 'build') {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
64
|
+
const prompt = (event.input?.prompt || '').slice(0, 300).toLowerCase();
|
|
65
|
+
|
|
66
|
+
if (!state.briefConfirmed && !prompt.match(/session.brief|session-brief|generate.*brief|write.*brief/)) {
|
|
67
|
+
return { blocked: true, message: 'ODD BRIEF GATE: Build agents blocked — session brief not confirmed. Generate and confirm the brief first.' };
|
|
20
68
|
}
|
|
21
69
|
|
|
22
|
-
if (state.briefConfirmed && !markerValid(
|
|
70
|
+
if (state.briefConfirmed && !markerValid(readMarker('.odd-flow-phase-synced'), HALF_HOUR)) {
|
|
23
71
|
return { blocked: true, message: 'ODD BUILD GATE: Brief confirmed but odd-flow not synced. Run /odd-sync first.' };
|
|
24
72
|
}
|
|
25
73
|
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
74
|
+
if (markerValid(readMarker('.odd-quick-fix'), ONE_HOUR)) return;
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
state.briefConfirmed &&
|
|
78
|
+
!markerValid(readMarker('.odd-flow-agents-ready'), TWO_HOURS) &&
|
|
79
|
+
!prompt.match(/session.brief|session-brief|generate.*brief|odd-sync|fix.*hook|update.*hook/)
|
|
80
|
+
) {
|
|
81
|
+
return { blocked: true, message: 'ODD BUILD GATE: Task agents blocked — odd-flow swarm not initialised. Run the full swarm init sequence.' };
|
|
31
82
|
}
|
|
32
83
|
}
|
|
33
84
|
|
|
34
85
|
if ((toolName === 'Write' || toolName === 'Edit') && state.currentPhase === 'build' && isSourceCodePath(filePath)) {
|
|
35
|
-
if (!markerValid(
|
|
86
|
+
if (!markerValid(readMarker('.odd-flow-swarm-active'), ONE_DAY)) {
|
|
36
87
|
return { blocked: true, message: `ODD SWARM WRITE GATE: Source writes blocked — no active build session. Run *build first. File: ${filePath}` };
|
|
37
88
|
}
|
|
38
|
-
|
|
39
|
-
|
|
89
|
+
|
|
90
|
+
if (state.debugSession || markerValid(readMarker('.odd-quick-fix'), ONE_HOUR)) return;
|
|
91
|
+
|
|
92
|
+
if (!markerValid(readMarker('.odd-flow-agent-token'), TWO_MINUTES)) {
|
|
93
|
+
return { blocked: true, message: `ODD SWARM WRITE GATE: Source writes blocked — no agent write token. Only Task agents can write source code, not the orchestrator. File: ${filePath}` };
|
|
40
94
|
}
|
|
41
95
|
}
|
|
42
96
|
|
|
43
97
|
if ((toolName === 'Edit' || toolName === 'Write') && filePath.includes('state.json')) {
|
|
44
98
|
const newContent = event.input?.new_string || event.input?.content || '';
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (newContent.includes('"verified"') && (markerExists('.checkpoint-dirty') || !markerExists('.checkpoint-clear'))) {
|
|
49
|
-
return { blocked: true, message: 'ODD CHECKPOINT GATE: Cannot mark outcomes as verified until a fresh Checkpoint scan clears the latest source changes.' };
|
|
50
|
-
}
|
|
51
|
-
if (newContent.includes('"verified"') && !state.verificationConfirmed) {
|
|
52
|
-
return { blocked: true, message: 'ODD VERIFY GATE: Cannot mark outcomes as verified. Walk through the verification checklist first.' };
|
|
53
|
-
}
|
|
54
|
-
if (/"briefConfirmed"\s*:\s*true/.test(newContent) && !markerExists('.odd-flow-brief-stored')) {
|
|
55
|
-
return { blocked: true, message: 'ODD CONFIRM GATE: Brief not stored in odd-flow memory. Store it first.' };
|
|
99
|
+
|
|
100
|
+
if (blocksBuildPhaseTransition(newContent)) {
|
|
101
|
+
return { blocked: true, message: 'ODD PLAN COMPLETE GATE: Finish technical architecture, design approach, and architecture docs before entering build phase.' };
|
|
56
102
|
}
|
|
57
|
-
}
|
|
58
103
|
|
|
59
|
-
|
|
60
|
-
const cmd = event.input?.command || '';
|
|
61
|
-
if (/git\s+commit/.test(cmd)) {
|
|
104
|
+
if (isVerifiedUpgrade(event, filePath)) {
|
|
62
105
|
if (state.buildMode === 'debug') {
|
|
63
|
-
return { blocked: true, message: 'ODD
|
|
64
|
-
}
|
|
65
|
-
if (markerExists('.checkpoint-dirty') || !markerExists('.checkpoint-clear')) {
|
|
66
|
-
return { blocked: true, message: 'ODD CHECKPOINT GATE: Commit blocked — run Checkpoint and clear the latest source changes first.' };
|
|
106
|
+
return { blocked: true, message: 'ODD VERIFY GATE: Cannot mark outcomes as verified while debug mode is active. Return to verification first.' };
|
|
67
107
|
}
|
|
68
|
-
|
|
69
|
-
|
|
108
|
+
|
|
109
|
+
if (!state.verificationConfirmed) {
|
|
110
|
+
return { blocked: true, message: 'ODD VERIFY GATE: Cannot mark outcomes as verified. Walk through the verification checklist first.' };
|
|
70
111
|
}
|
|
71
|
-
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (/"briefConfirmed"\s*:\s*true/.test(newContent) && !markerExists('.odd-flow-brief-stored')) {
|
|
115
|
+
return { blocked: true, message: 'ODD CONFIRM GATE: Brief not stored in odd-flow memory. Store it first.' };
|
|
72
116
|
}
|
|
73
117
|
}
|
|
74
118
|
};
|
|
@@ -89,44 +133,26 @@ export function createAfterHook(readState, readLastCommit, writeState) {
|
|
|
89
133
|
if (toolName === 'Write' && isBriefFile(filePath)) warnings.push(...checkBriefQuality(filePath));
|
|
90
134
|
|
|
91
135
|
const state = readState();
|
|
92
|
-
if ((toolName === 'Write' || toolName === 'Edit') && isSrcFile(filePath) && state?.currentPhase === 'build') {
|
|
93
|
-
touchMarker('.checkpoint-dirty');
|
|
94
|
-
removeMarker('.checkpoint-clear');
|
|
95
|
-
}
|
|
96
136
|
|
|
97
137
|
if (toolName === 'Bash') {
|
|
98
138
|
const cmd = event.input?.command || '';
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (state) {
|
|
106
|
-
state.lastCommit = readLastCommit();
|
|
107
|
-
state.lastSaved = new Date().toISOString();
|
|
108
|
-
state.lastCommitAt = state.lastSaved;
|
|
109
|
-
writeState(state);
|
|
110
|
-
touchMarker('.odd-flow-phase-synced');
|
|
111
|
-
touchMarker('.odd-flow-state-dirty');
|
|
112
|
-
}
|
|
139
|
+
if (/git\s+commit/.test(cmd) && event.exitCode === 0 && state) {
|
|
140
|
+
state.lastCommit = readLastCommit();
|
|
141
|
+
state.lastSaved = new Date().toISOString();
|
|
142
|
+
state.lastCommitAt = state.lastSaved;
|
|
143
|
+
writeState(state);
|
|
144
|
+
touchMarker('.odd-flow-phase-synced');
|
|
113
145
|
}
|
|
114
146
|
}
|
|
115
147
|
|
|
116
|
-
if (toolName === 'mcp__odd-flow__memory_store') {
|
|
117
|
-
|
|
118
|
-
if (state?.currentPhase === 'build' && event.input?.key === 'odd-project-state') {
|
|
119
|
-
touchMarker('.odd-flow-state-ready');
|
|
120
|
-
removeMarker('.odd-flow-state-dirty');
|
|
121
|
-
}
|
|
148
|
+
if (toolName === 'mcp__odd-flow__memory_store' && state?.currentPhase === 'build') {
|
|
149
|
+
if (event.input?.key?.startsWith('odd-session-brief-')) touchMarker('.odd-flow-brief-stored');
|
|
122
150
|
}
|
|
123
151
|
|
|
124
|
-
if (toolName === 'mcp__odd-flow__coordination_sync') {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
touchMarker('.odd-flow-phase-synced');
|
|
129
|
-
}
|
|
152
|
+
if (toolName === 'mcp__odd-flow__coordination_sync' && state?.currentPhase === 'build') {
|
|
153
|
+
touchMarker('.odd-flow-swarm-active');
|
|
154
|
+
touchMarker('.odd-flow-agents-ready');
|
|
155
|
+
touchMarker('.odd-flow-phase-synced');
|
|
130
156
|
}
|
|
131
157
|
|
|
132
158
|
if (warnings.length > 0) return { warnings };
|
|
@@ -137,20 +163,19 @@ export function createChatHook(readState) {
|
|
|
137
163
|
return () => {
|
|
138
164
|
const state = readState();
|
|
139
165
|
if (!state || state.currentPhase !== 'build') return;
|
|
166
|
+
|
|
140
167
|
if (state.buildMode === 'debug') {
|
|
141
|
-
return {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (markerExists('.checkpoint-dirty')) {
|
|
147
|
-
return { warnings: ['ODD CHECKPOINT DIRTY: Source changes have not passed a fresh security scan yet. Run Checkpoint before verification or commit.'] };
|
|
168
|
+
return {
|
|
169
|
+
warnings: [
|
|
170
|
+
`ODD DEBUG MODE: ${state.debugStrategy || 'Choose a strategy'} — keep the fix inside the active outcome and return to verification when ready.`,
|
|
171
|
+
],
|
|
172
|
+
};
|
|
148
173
|
}
|
|
149
174
|
|
|
150
|
-
const swarmPath =
|
|
151
|
-
if (!markerValid(swarmPath,
|
|
175
|
+
const swarmPath = readMarker('.odd-flow-swarm-active');
|
|
176
|
+
if (!markerValid(swarmPath, ONE_DAY)) {
|
|
152
177
|
const stale = markerExists('.odd-flow-swarm-active') ? ' (marker expired — re-init required)' : '';
|
|
153
|
-
return { warnings: [`ODD SWARM GUARD: You are in build phase. The odd-flow swarm must be initialised before
|
|
178
|
+
return { warnings: [`ODD SWARM GUARD: You are in build phase. The odd-flow swarm must be initialised before build work.${stale}`] };
|
|
154
179
|
}
|
|
155
180
|
};
|
|
156
181
|
}
|
|
@@ -10,76 +10,36 @@ export const COMMANDS = [
|
|
|
10
10
|
{
|
|
11
11
|
name: 'odd-plan',
|
|
12
12
|
description: 'Start or continue the planning phase — personas, outcomes, contracts, and Master Implementation Plan',
|
|
13
|
-
body: 'You are executing the ODD Studio `*plan` command.\n\nRead this file now:\n- `.opencode/odd/SKILL.md` — the
|
|
13
|
+
body: 'You are executing the ODD Studio `*plan` command.\n\nRead this file now:\n- `.opencode/odd/odd-plan/SKILL.md` — the planning command guide\n\nThen execute the `*plan` protocol exactly as documented, starting from the state check.',
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
name: 'odd-build',
|
|
17
17
|
description: 'Start or continue a build session — reads project state and executes the build protocol',
|
|
18
|
-
body: 'You are executing the ODD Studio `*build` command.\n\nRead
|
|
18
|
+
body: 'You are executing the ODD Studio `*build` command.\n\nRead this file now:\n- `.opencode/odd/odd-build/SKILL.md` — the build command guide\n\nThen execute the `*build` protocol exactly as documented, starting from the state check.',
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
name: 'odd-debug',
|
|
22
22
|
description: 'Start or continue an in-flow ODD debugging session — selects the correct debug approach and returns to verification',
|
|
23
|
-
body: 'You are executing the ODD Studio `*debug` command.\n\nRead
|
|
23
|
+
body: 'You are executing the ODD Studio `*debug` command.\n\nRead this file now:\n- `.opencode/odd/odd-debug/SKILL.md` — the debug command guide\n\nThen execute the `*debug` protocol exactly as documented, starting from the state check.',
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
name: 'odd-swarm',
|
|
27
27
|
description: 'Build all independent outcomes in the current phase simultaneously using odd-flow parallel agents',
|
|
28
|
-
body: 'You are executing the ODD Studio `*swarm` command.\n\nRead
|
|
28
|
+
body: 'You are executing the ODD Studio `*swarm` command.\n\nRead this file now:\n- `.opencode/odd/odd-swarm/SKILL.md` — the swarm command guide\n\nThen execute the `*swarm` protocol exactly as documented, starting from the state check.',
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
name: 'odd-status',
|
|
32
32
|
description: 'Show full project state, phase progress, and what comes next',
|
|
33
|
-
body: 'You are executing the ODD Studio `*status` command.\n\nRead this file now:\n- `.opencode/odd/SKILL.md` — the
|
|
33
|
+
body: 'You are executing the ODD Studio `*status` command.\n\nRead this file now:\n- `.opencode/odd/odd-status/SKILL.md` — the status command guide\n\nThen execute the `*status` protocol exactly as documented, starting from the state check.',
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
name: 'odd-deploy',
|
|
37
37
|
description: 'Verify all outcomes are confirmed, then deploy the current phase to production',
|
|
38
|
-
body: 'You are executing the ODD Studio `*deploy` command.\n\nRead this file now:\n- `.opencode/odd/SKILL.md` — the
|
|
38
|
+
body: 'You are executing the ODD Studio `*deploy` command.\n\nRead this file now:\n- `.opencode/odd/odd-deploy/SKILL.md` — the deploy command guide\n\nThen execute the `*deploy` protocol exactly as documented, starting from the state check.',
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
name: 'odd-sync',
|
|
42
42
|
description: 'Sync ODD project state to odd-flow memory. MUST be run before building.',
|
|
43
|
-
body:
|
|
44
|
-
|
|
45
|
-
## Step 1 — Read project state
|
|
46
|
-
|
|
47
|
-
Read \`.odd/state.json\` and store the full contents in a variable.
|
|
48
|
-
|
|
49
|
-
## Step 2 — Store state in odd-flow
|
|
50
|
-
|
|
51
|
-
Call \`mcp__odd-flow__memory_store\` with:
|
|
52
|
-
- **key**: \`odd-project-state\`
|
|
53
|
-
- **namespace**: \`odd-project\`
|
|
54
|
-
- **value**: the full contents of \`.odd/state.json\`
|
|
55
|
-
|
|
56
|
-
## Step 3 — Read and store the current session brief
|
|
57
|
-
|
|
58
|
-
Read \`sessionBriefCount\` from state.json. If it is greater than 0, read the file \`docs/session-brief-[N].md\` where N equals the sessionBriefCount value.
|
|
59
|
-
|
|
60
|
-
If the brief file exists, call \`mcp__odd-flow__memory_store\` with:
|
|
61
|
-
- **key**: \`odd-session-brief-[N]\` (replace [N] with the actual number)
|
|
62
|
-
- **namespace**: \`odd-project\`
|
|
63
|
-
- **value**: the full contents of the session brief file
|
|
64
|
-
|
|
65
|
-
## Step 4 — Create marker files
|
|
66
|
-
|
|
67
|
-
Run via shell:
|
|
68
|
-
\`\`\`bash
|
|
69
|
-
touch .odd/.odd-flow-phase-synced
|
|
70
|
-
\`\`\`
|
|
71
|
-
|
|
72
|
-
If a session brief was stored in Step 3, also run:
|
|
73
|
-
\`\`\`bash
|
|
74
|
-
touch .odd/.odd-flow-brief-stored
|
|
75
|
-
\`\`\`
|
|
76
|
-
|
|
77
|
-
## Step 5 — Confirm
|
|
78
|
-
|
|
79
|
-
Report to the user:
|
|
80
|
-
|
|
81
|
-
> odd-flow synced. Phase [X] state and brief [N] stored. Build agents unlocked.
|
|
82
|
-
|
|
83
|
-
Replace [X] with \`currentBuildPhase\` from state.json and [N] with the session brief number.`,
|
|
43
|
+
body: 'You are executing the ODD Studio `*sync` command.\n\nRead this file now:\n- `.opencode/odd/odd-sync/SKILL.md` — the sync command guide\n\nThen execute the sync steps exactly as documented.',
|
|
84
44
|
},
|
|
85
45
|
];
|