odd-studio 3.7.5 → 3.7.7
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 +0 -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 +13 -9
- 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 +3 -5
- 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
|
@@ -239,8 +239,6 @@ verify-gate)
|
|
|
239
239
|
exit 2
|
|
240
240
|
fi
|
|
241
241
|
|
|
242
|
-
# Checkpoint pre-check removed in v3.7.3 (see checkpoint-gate note below).
|
|
243
|
-
|
|
244
242
|
VERIFIED_CONFIRMED=$(get_state_field "verificationConfirmed")
|
|
245
243
|
if [ "$VERIFIED_CONFIRMED" != "true" ]; then
|
|
246
244
|
echo "ODD STUDIO [verify-gate]: Cannot mark NEW outcomes as verified." >&2
|
|
@@ -250,14 +248,6 @@ verify-gate)
|
|
|
250
248
|
exit 0
|
|
251
249
|
;;
|
|
252
250
|
|
|
253
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
254
|
-
# checkpoint-gate — REMOVED in v3.7.3
|
|
255
|
-
# Vibeguard/Checkpoint was treating dependency-lockfile CVEs as equivalent to
|
|
256
|
-
# code vulnerabilities, making every commit impossible while any transitive
|
|
257
|
-
# dep had an open advisory. Re-introduce only with a scoped scanner that
|
|
258
|
-
# ignores lockfile-only findings.
|
|
259
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
260
|
-
|
|
261
251
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
262
252
|
# PreToolUse: Edit|Write — blocks briefConfirmed without odd-flow store
|
|
263
253
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -276,14 +266,6 @@ confirm-gate)
|
|
|
276
266
|
exit 0
|
|
277
267
|
;;
|
|
278
268
|
|
|
279
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
280
|
-
# commit-gate — REMOVED in v3.7.4
|
|
281
|
-
# The dirty/ready marker treadmill was creating more friction than value.
|
|
282
|
-
# State persistence to odd-flow is now the orchestrator's responsibility at
|
|
283
|
-
# genuine persistence points (session end, outcome verified) rather than a
|
|
284
|
-
# hard-block on every commit.
|
|
285
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
286
|
-
|
|
287
269
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
288
270
|
# UserPromptSubmit — warns every turn if build phase without swarm
|
|
289
271
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -344,10 +326,6 @@ session-save)
|
|
|
344
326
|
exit 0
|
|
345
327
|
;;
|
|
346
328
|
|
|
347
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
348
|
-
# checkpoint-validate — REMOVED in v3.7.3 (see checkpoint-gate note above)
|
|
349
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
350
|
-
|
|
351
329
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
352
330
|
# PostToolUse: Write|Edit state.json — blocks phase transition without Steps 9, 9b, 9d
|
|
353
331
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -389,17 +367,9 @@ plan-complete-gate)
|
|
|
389
367
|
exit 0
|
|
390
368
|
;;
|
|
391
369
|
|
|
392
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
393
|
-
# state-dirty-mark — REMOVED in v3.7.4 (dirty/ready treadmill eliminated)
|
|
394
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
395
|
-
|
|
396
370
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
397
371
|
# PostToolUse: mcp__odd-flow__memory_store — brief-stored marker only
|
|
398
372
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
399
|
-
# v3.7.4: the state-ready / dirty treadmill has been removed. This hook now
|
|
400
|
-
# only creates the brief-stored marker after a session brief is persisted.
|
|
401
|
-
# odd-project-state stores are logged but do not touch any marker — state
|
|
402
|
-
# persistence is no longer gated by a marker state machine.
|
|
403
373
|
store-validate)
|
|
404
374
|
[ "$TOOL_NAME" = "mcp__odd-flow__memory_store" ] || exit 0
|
|
405
375
|
[ "$CURRENT_PHASE" = "build" ] || exit 0
|
|
@@ -502,7 +472,6 @@ code-quality)
|
|
|
502
472
|
|
|
503
473
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
504
474
|
# PostToolUse: Write|Edit — security baseline warnings (stderr, non-blocking)
|
|
505
|
-
# Checkpoint dirty-marking was removed in v3.7.3 along with checkpoint-gate.
|
|
506
475
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
507
476
|
security-quality)
|
|
508
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.7",
|
|
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
|
];
|