dev-flow-codex 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +20 -0
- package/LICENSE +201 -0
- package/README.md +243 -0
- package/bin/dev-flow-codex.mjs +216 -0
- package/lib/lifecycle.mjs +1133 -0
- package/lib/paths.mjs +170 -0
- package/package.json +51 -0
- package/plugin/.codex-plugin/plugin.json +25 -0
- package/plugin/.mcp.json +12 -0
- package/plugin/skills/dev-flow/SKILL.md +243 -0
- package/plugin/skills/dev-flow/agents/openai.yaml +2 -0
- package/runtime/darwin-arm64/dev-flow +0 -0
package/lib/paths.mjs
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { chmod, lstat, mkdir, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
export const DATA_DIRECTORY_ENVIRONMENT = "DEV_FLOW_DATA_DIR";
|
|
7
|
+
export const SUPPORTED_RUNTIME_KEY = "darwin-arm64";
|
|
8
|
+
|
|
9
|
+
export function packageRootFromModule(moduleUrl = import.meta.url) {
|
|
10
|
+
return dirname(dirname(fileURLToPath(moduleUrl)));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function resolveProductPaths({
|
|
14
|
+
packageRoot = packageRootFromModule(),
|
|
15
|
+
homeDirectory = homedir(),
|
|
16
|
+
platform = process.platform,
|
|
17
|
+
arch = process.arch,
|
|
18
|
+
environment = process.env,
|
|
19
|
+
} = {}) {
|
|
20
|
+
const runtimeKey = `${platform}-${arch}`;
|
|
21
|
+
if (runtimeKey !== SUPPORTED_RUNTIME_KEY) {
|
|
22
|
+
throw new Error(`unsupported platform ${runtimeKey}; Feature 003 supports ${SUPPORTED_RUNTIME_KEY}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const canonicalPackageRoot = await canonicalExistingDirectory(packageRoot, "package root");
|
|
26
|
+
const canonicalHome = await canonicalExistingDirectory(homeDirectory, "home directory");
|
|
27
|
+
const productSupportRoot = join(
|
|
28
|
+
canonicalHome,
|
|
29
|
+
"Library",
|
|
30
|
+
"Application Support",
|
|
31
|
+
"dev-flow",
|
|
32
|
+
);
|
|
33
|
+
await assertNoSymlinkComponents(canonicalHome, productSupportRoot);
|
|
34
|
+
|
|
35
|
+
const runtimePath = containedPath(
|
|
36
|
+
canonicalPackageRoot,
|
|
37
|
+
join(canonicalPackageRoot, "runtime", runtimeKey, "dev-flow"),
|
|
38
|
+
"runtime",
|
|
39
|
+
);
|
|
40
|
+
const pluginRoot = containedPath(
|
|
41
|
+
canonicalPackageRoot,
|
|
42
|
+
join(canonicalPackageRoot, "plugin"),
|
|
43
|
+
"plugin",
|
|
44
|
+
);
|
|
45
|
+
const registrationsDirectory = containedPath(
|
|
46
|
+
productSupportRoot,
|
|
47
|
+
join(productSupportRoot, "registrations"),
|
|
48
|
+
"registration directory",
|
|
49
|
+
);
|
|
50
|
+
const receiptPath = containedPath(
|
|
51
|
+
productSupportRoot,
|
|
52
|
+
join(registrationsDirectory, "codex.json"),
|
|
53
|
+
"receipt",
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const explicitDataDirectory = environment?.[DATA_DIRECTORY_ENVIRONMENT] ?? "";
|
|
57
|
+
let dataDirectory;
|
|
58
|
+
let usesDefaultDataDirectory;
|
|
59
|
+
if (explicitDataDirectory !== "") {
|
|
60
|
+
if (!isAbsolute(explicitDataDirectory)) {
|
|
61
|
+
throw new Error(`${DATA_DIRECTORY_ENVIRONMENT} must be an absolute path`);
|
|
62
|
+
}
|
|
63
|
+
const normalized = resolve(explicitDataDirectory);
|
|
64
|
+
let canonical;
|
|
65
|
+
try {
|
|
66
|
+
canonical = await realpath(explicitDataDirectory);
|
|
67
|
+
const info = await stat(canonical);
|
|
68
|
+
if (!info.isDirectory()) {
|
|
69
|
+
throw new Error("not a directory");
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new Error(`${DATA_DIRECTORY_ENVIRONMENT} must name an existing directory`, {
|
|
73
|
+
cause: error,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (canonical !== normalized) {
|
|
77
|
+
throw new Error(`${DATA_DIRECTORY_ENVIRONMENT} must be canonical and may not use a symbolic link`);
|
|
78
|
+
}
|
|
79
|
+
dataDirectory = canonical;
|
|
80
|
+
usesDefaultDataDirectory = false;
|
|
81
|
+
} else {
|
|
82
|
+
dataDirectory = containedPath(
|
|
83
|
+
productSupportRoot,
|
|
84
|
+
join(productSupportRoot, "data"),
|
|
85
|
+
"default data directory",
|
|
86
|
+
);
|
|
87
|
+
usesDefaultDataDirectory = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return Object.freeze({
|
|
91
|
+
packageRoot: canonicalPackageRoot,
|
|
92
|
+
marketplaceRoot: canonicalPackageRoot,
|
|
93
|
+
pluginRoot,
|
|
94
|
+
runtimePath,
|
|
95
|
+
homeDirectory: canonicalHome,
|
|
96
|
+
productSupportRoot,
|
|
97
|
+
registrationsDirectory,
|
|
98
|
+
receiptPath,
|
|
99
|
+
dataDirectory,
|
|
100
|
+
usesDefaultDataDirectory,
|
|
101
|
+
runtimeKey,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function ensureDefaultDataDirectory(paths) {
|
|
106
|
+
if (!paths?.usesDefaultDataDirectory) {
|
|
107
|
+
throw new Error("refusing to create an explicit data directory");
|
|
108
|
+
}
|
|
109
|
+
const expected = containedPath(
|
|
110
|
+
paths.productSupportRoot,
|
|
111
|
+
join(paths.productSupportRoot, "data"),
|
|
112
|
+
"default data directory",
|
|
113
|
+
);
|
|
114
|
+
if (paths.dataDirectory !== expected) {
|
|
115
|
+
throw new Error("default data directory does not match the product-owned path");
|
|
116
|
+
}
|
|
117
|
+
await assertNoSymlinkComponents(paths.homeDirectory, paths.productSupportRoot);
|
|
118
|
+
await mkdir(paths.dataDirectory, { recursive: true, mode: 0o700 });
|
|
119
|
+
await assertNoSymlinkComponents(paths.homeDirectory, paths.dataDirectory);
|
|
120
|
+
await chmod(paths.dataDirectory, 0o700);
|
|
121
|
+
return paths.dataDirectory;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function containedPath(root, candidate, label = "path") {
|
|
125
|
+
const canonicalRoot = resolve(root);
|
|
126
|
+
const canonicalCandidate = resolve(candidate);
|
|
127
|
+
const offset = relative(canonicalRoot, canonicalCandidate);
|
|
128
|
+
if (offset === ".." || offset.startsWith(`..${sep}`) || isAbsolute(offset)) {
|
|
129
|
+
throw new Error(`${label} escapes its owned root`);
|
|
130
|
+
}
|
|
131
|
+
return canonicalCandidate;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function canonicalExistingDirectory(path, label) {
|
|
135
|
+
if (!isAbsolute(path)) {
|
|
136
|
+
throw new Error(`${label} must be absolute`);
|
|
137
|
+
}
|
|
138
|
+
let canonical;
|
|
139
|
+
try {
|
|
140
|
+
canonical = await realpath(path);
|
|
141
|
+
const info = await stat(canonical);
|
|
142
|
+
if (!info.isDirectory()) {
|
|
143
|
+
throw new Error("not a directory");
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
throw new Error(`${label} must name an existing directory`, { cause: error });
|
|
147
|
+
}
|
|
148
|
+
return canonical;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function assertNoSymlinkComponents(root, candidate) {
|
|
152
|
+
const canonicalRoot = resolve(root);
|
|
153
|
+
const canonicalCandidate = containedPath(canonicalRoot, candidate, "product path");
|
|
154
|
+
const components = relative(canonicalRoot, canonicalCandidate).split(sep).filter(Boolean);
|
|
155
|
+
let current = canonicalRoot;
|
|
156
|
+
for (const component of components) {
|
|
157
|
+
current = join(current, component);
|
|
158
|
+
try {
|
|
159
|
+
const info = await lstat(current);
|
|
160
|
+
if (info.isSymbolicLink()) {
|
|
161
|
+
throw new Error(`product path contains a symbolic link: ${current}`);
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (error?.code === "ENOENT") {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dev-flow-codex",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Explicit-only Dev Flow integration for Codex CLI.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Innocent-children/dev-flow.git",
|
|
10
|
+
"directory": "packages/codex"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"os": [
|
|
14
|
+
"darwin"
|
|
15
|
+
],
|
|
16
|
+
"cpu": [
|
|
17
|
+
"arm64"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public",
|
|
21
|
+
"registry": "https://registry.npmjs.org/"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=24"
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"dev-flow-codex": "bin/dev-flow-codex.mjs"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
".agents/plugins/marketplace.json",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"bin/dev-flow-codex.mjs",
|
|
33
|
+
"lib/lifecycle.mjs",
|
|
34
|
+
"lib/paths.mjs",
|
|
35
|
+
"plugin/.codex-plugin/plugin.json",
|
|
36
|
+
"plugin/.mcp.json",
|
|
37
|
+
"plugin/skills/dev-flow/SKILL.md",
|
|
38
|
+
"plugin/skills/dev-flow/agents/openai.yaml",
|
|
39
|
+
"runtime/darwin-arm64/dev-flow"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"test": "node --test tests/*.test.mjs",
|
|
43
|
+
"test:package": "node --test tests/package-contract.test.mjs",
|
|
44
|
+
"test:lifecycle": "node --test tests/lifecycle.test.mjs",
|
|
45
|
+
"test:parser": "node --test tests/journey-evidence.test.mjs",
|
|
46
|
+
"test:native-smoke": "node --test tests/journey-harness.test.mjs",
|
|
47
|
+
"pack:dry": "pnpm pack --dry-run --json",
|
|
48
|
+
"build:local": "../../scripts/build-codex-local.sh",
|
|
49
|
+
"smoke:fixture": "../../scripts/run-codex-real-journey.sh --fixture success"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dev-flow-codex",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Explicit-only Dev Flow integration for Codex CLI.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Dev Flow"
|
|
7
|
+
},
|
|
8
|
+
"license": "Apache-2.0",
|
|
9
|
+
"skills": "./skills/",
|
|
10
|
+
"mcpServers": "./.mcp.json",
|
|
11
|
+
"interface": {
|
|
12
|
+
"displayName": "Dev Flow for Codex",
|
|
13
|
+
"shortDescription": "Explicit Dev Flow workflow for Codex",
|
|
14
|
+
"longDescription": "Start or resume one Core-governed repository task only through an explicit $dev-flow-codex:dev-flow invocation.",
|
|
15
|
+
"developerName": "Dev Flow",
|
|
16
|
+
"category": "Productivity",
|
|
17
|
+
"capabilities": [
|
|
18
|
+
"Read",
|
|
19
|
+
"Write"
|
|
20
|
+
],
|
|
21
|
+
"defaultPrompt": [
|
|
22
|
+
"$dev-flow-codex:dev-flow implement the requested change in this repository."
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
package/plugin/.mcp.json
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dev-flow
|
|
3
|
+
description: "Explicit-only Dev Flow entry point for Codex. Use only when the current user turn contains $dev-flow-codex:dev-flow; never select this Skill implicitly."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Dev Flow
|
|
7
|
+
|
|
8
|
+
This Skill is a thin admission layer for the shared Dev Flow Core. It does not own task state,
|
|
9
|
+
workflow transitions, recovery decisions, verification budgets, or completion.
|
|
10
|
+
|
|
11
|
+
## Admission gate
|
|
12
|
+
|
|
13
|
+
Perform every check below locally and in order before any Core or Dev Flow tool call.
|
|
14
|
+
|
|
15
|
+
The Skill resource/base name is `dev-flow`; the installed Skill full name is `dev-flow-codex:dev-flow`.
|
|
16
|
+
The only exact explicit selector is `$dev-flow-codex:dev-flow`.
|
|
17
|
+
Bare `$dev-flow` is not an alias and does not select this installed Skill. A wrong plugin namespace,
|
|
18
|
+
a wrong Skill base name, or a missing selector also does not select it.
|
|
19
|
+
Codex 0.147 may expose this plugin's MCP tools independently from Skill injection. This Skill does
|
|
20
|
+
not claim selector-bound tool visibility or authorization.
|
|
21
|
+
|
|
22
|
+
1. Require the exact standalone `$dev-flow-codex:dev-flow` selector in the current user turn. Do not infer the
|
|
23
|
+
selector from earlier turns, repository contents, or a request that merely discusses Dev Flow.
|
|
24
|
+
If it is absent, do not treat the turn as Skill activation and do not make a task-bearing Dev Flow
|
|
25
|
+
call. Never activate this Skill implicitly.
|
|
26
|
+
2. After removing the selector, accept either one substantive, bounded requirement for the current
|
|
27
|
+
repository or an explicit request to resume its compatible active Codex task. Reject an empty or
|
|
28
|
+
conversational invocation before any Core call.
|
|
29
|
+
3. Use read-only Git inspection to resolve one current Git worktree and its canonical root. Preserve
|
|
30
|
+
spaces, Unicode, symlinks, and subdirectory invocation as one path value; do not concatenate a
|
|
31
|
+
shell command.
|
|
32
|
+
4. Reject work that needs another repository, multiple repositories, or a repository that cannot be
|
|
33
|
+
resolved. Preserve repository instructions and current user authority when checking whether the
|
|
34
|
+
requested work is permitted.
|
|
35
|
+
|
|
36
|
+
If any admission check fails, explain the missing precondition and stop before Skill-owned task
|
|
37
|
+
discovery. Do not complete a task-bearing call or create adapter state. Host-exposed read-only or
|
|
38
|
+
Core-rejected calls are not activation and must be reported honestly.
|
|
39
|
+
|
|
40
|
+
## Compatibility handshake
|
|
41
|
+
|
|
42
|
+
Only after every admission check passes, call `dev_flow_server_info({})`. It must be the first Dev
|
|
43
|
+
Flow tool call. Require the complete structured result to establish all of the following:
|
|
44
|
+
|
|
45
|
+
- product is exactly `dev-flow`;
|
|
46
|
+
- Core version equals the packaged product version;
|
|
47
|
+
- schema identifies Core Contract `0.1`;
|
|
48
|
+
- transport is exactly `stdio` and health is exactly `ready`;
|
|
49
|
+
- the supported host set contains `codex`;
|
|
50
|
+
- the reported tool catalog contains exactly these six raw names, in this order:
|
|
51
|
+
|
|
52
|
+
1. `dev_flow_server_info`
|
|
53
|
+
2. `dev_flow_open_task`
|
|
54
|
+
3. `dev_flow_get_task`
|
|
55
|
+
4. `dev_flow_get_next_action`
|
|
56
|
+
5. `dev_flow_apply_action`
|
|
57
|
+
6. `dev_flow_cancel_task`
|
|
58
|
+
|
|
59
|
+
An incomplete, truncated, malformed, incompatible, missing, additional, or reordered catalog is a
|
|
60
|
+
failed handshake. Stop without probing an undocumented tool or continuing to task discovery.
|
|
61
|
+
|
|
62
|
+
## Task discovery
|
|
63
|
+
|
|
64
|
+
After the handshake, call `dev_flow_open_task` with `host=codex` and the canonical current
|
|
65
|
+
worktree.
|
|
66
|
+
|
|
67
|
+
- For an explicit resume request, omit `new_task` and let Core select the unique compatible active
|
|
68
|
+
Codex task.
|
|
69
|
+
- For a new substantive request, provide a bounded contract derived only from the current user
|
|
70
|
+
request, repository instructions, stated exclusions, observable acceptance criteria, and the
|
|
71
|
+
granted verification authority.
|
|
72
|
+
- Forward `new_task` with exactly the members `goal`, `scope`, `out_of_scope`,
|
|
73
|
+
`acceptance_criteria`, and `verification_budget`, with no additional members. Forward
|
|
74
|
+
`verification_budget` with exactly `level`, `max_automatic_commands`, `allow_full_suite`, and
|
|
75
|
+
`allow_manual_handoff`; do not invent aliases for any of these Core-declared fields.
|
|
76
|
+
- Ask before opening a new task if a material goal, scope, acceptance, or verification choice
|
|
77
|
+
cannot be derived without changing user intent.
|
|
78
|
+
- Let Core decide whether an exactly compatible contract creates or resumes a task. Never choose,
|
|
79
|
+
merge, or take over task records locally.
|
|
80
|
+
|
|
81
|
+
The complete open result is authoritative. Report a Core ownership or contract conflict unchanged
|
|
82
|
+
in meaning and stop.
|
|
83
|
+
|
|
84
|
+
## Governed action loop
|
|
85
|
+
|
|
86
|
+
For an active task, perform each iteration in this order:
|
|
87
|
+
|
|
88
|
+
1. Obtain one complete, fresh action from the open result or `dev_flow_get_next_action`.
|
|
89
|
+
2. Treat the returned task ID, revision, action ID, action kind, repository-binding digest, allowed
|
|
90
|
+
effects, required evidence, payload schema, guidance, blocker, and outcome as one inseparable
|
|
91
|
+
Core result.
|
|
92
|
+
3. Stop when that result reports a blocker or terminal outcome.
|
|
93
|
+
4. Perform only the current action's allowed effects, under the repository instructions and
|
|
94
|
+
current user authority.
|
|
95
|
+
5. Count verification commands and label evidence by how it actually ran.
|
|
96
|
+
6. Build only the closed payload requested by the returned payload schema.
|
|
97
|
+
7. Before dispatch, generate and retain an opaque request ID with the exact identity and payload.
|
|
98
|
+
8. Submit exactly one mutation through `dev_flow_apply_action` using that retained material.
|
|
99
|
+
9. After a complete successful mutation, continue only from the returned authoritative next action
|
|
100
|
+
or outcome, or make one fresh Core read before doing more work.
|
|
101
|
+
|
|
102
|
+
Do not infer a transition, reinterpret an error, or decide completion from repository contents or
|
|
103
|
+
host judgment.
|
|
104
|
+
|
|
105
|
+
## Closed forwarding contract
|
|
106
|
+
|
|
107
|
+
For every mutation, forward `host=codex`; the task ID and revision from the same fresh result; the
|
|
108
|
+
exact action ID, action kind, and repository-binding digest; one caller-generated request ID; and a
|
|
109
|
+
closed payload containing only fields allowed by Core's returned schema. Do not add unknown fields,
|
|
110
|
+
aliases, command logs, environment dumps, inferred status, or locally invented recovery flags.
|
|
111
|
+
|
|
112
|
+
Use the fresh action's `payload_contract` as the discriminator for the corresponding closed schema
|
|
113
|
+
branch in the `dev_flow_apply_action` input. `required_evidence` names describe obligations; they are
|
|
114
|
+
not payload field names. If you cannot identify and read that exact schema branch, stop before
|
|
115
|
+
calling `dev_flow_apply_action` instead of guessing or deriving keys from evidence names.
|
|
116
|
+
|
|
117
|
+
Before calling, read the `dev_flow_apply_action` tool's supplied `inputSchema`: under `allOf`, choose
|
|
118
|
+
the `oneOf` branch whose `action_kind.const` equals the fresh action kind, resolve the payload `$ref`
|
|
119
|
+
through the same schema's `$defs`, and send exactly its `required` members. Do not search the
|
|
120
|
+
repository or installed package, inspect a binary or log, or start another MCP server to recover the
|
|
121
|
+
schema.
|
|
122
|
+
|
|
123
|
+
At the top-level, forward `request_id`, `host`, `task_id`, `revision`, `action_id`, `action_kind`,
|
|
124
|
+
and `repository_binding_digest`. The `payload` object contains only phase schema fields; never nest
|
|
125
|
+
the enclosing request inside `payload`.
|
|
126
|
+
|
|
127
|
+
Bind a source-neutral `fresh_action` from `result.task.current_action` when Core returns a task, or
|
|
128
|
+
from `result.action` when `dev_flow_get_next_action` returns the action directly. Map that same
|
|
129
|
+
fresh action into the tool input exactly:
|
|
130
|
+
|
|
131
|
+
- caller-generated opaque identity -> top-level `request_id`;
|
|
132
|
+
- exact value `codex` -> top-level `host`;
|
|
133
|
+
- `fresh_action.task_id` -> top-level `task_id`;
|
|
134
|
+
- `fresh_action.revision` -> top-level `revision`;
|
|
135
|
+
- `fresh_action.action_id` -> top-level `action_id`;
|
|
136
|
+
- `fresh_action.kind` -> top-level `action_kind`;
|
|
137
|
+
- `fresh_action.repository_binding_digest` -> top-level `repository_binding_digest`;
|
|
138
|
+
- the payload object built from the selected schema branch -> top-level `payload`.
|
|
139
|
+
|
|
140
|
+
Use this type-preserving structure for the `dev_flow_apply_action` arguments:
|
|
141
|
+
|
|
142
|
+
```text
|
|
143
|
+
apply_arguments = {
|
|
144
|
+
"request_id": caller_request_id,
|
|
145
|
+
"host": "codex",
|
|
146
|
+
"task_id": fresh_action.task_id,
|
|
147
|
+
"revision": fresh_action.revision,
|
|
148
|
+
"action_id": fresh_action.action_id,
|
|
149
|
+
"action_kind": fresh_action.kind,
|
|
150
|
+
"repository_binding_digest": fresh_action.repository_binding_digest,
|
|
151
|
+
"payload": payload_for_selected_schema_branch
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`revision` remains an integer, not a string. `payload` remains an object, not a string, containing
|
|
156
|
+
exactly the selected branch's required members.
|
|
157
|
+
|
|
158
|
+
Do not wrap that request inside an outer `payload` object.
|
|
159
|
+
|
|
160
|
+
Use `recovery_apply` only when a fresh Core recovery assessment explicitly requires the exact
|
|
161
|
+
Core-defined form. Resolve context ambiguity through an ordinary Core read, never by guessing.
|
|
162
|
+
|
|
163
|
+
## Recovery-before-retry contract
|
|
164
|
+
|
|
165
|
+
A mutation result is uncertain when it is missing, malformed, cancelled, truncated, or
|
|
166
|
+
transport-failed instead of returning one complete structured result. All five shapes use the same
|
|
167
|
+
read-before-retry procedure.
|
|
168
|
+
|
|
169
|
+
Before calling `dev_flow_apply_action`, retain the original `request_id`, `task_id`, `source_phase`,
|
|
170
|
+
`revision`, `action_id`, `action_kind`, `repository_binding_digest`, and exact closed `payload` from
|
|
171
|
+
the same fresh action and the same apply dispatch. Never derive or reconstruct any of them from an
|
|
172
|
+
incomplete response or partial output.
|
|
173
|
+
|
|
174
|
+
When all required non-payload original identity values are retained, construct the operation probe
|
|
175
|
+
as exactly this closed `operation_probe`:
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"operation_id": "<original apply request_id>",
|
|
180
|
+
"source_phase": "<original source phase>",
|
|
181
|
+
"expected_revision": 3,
|
|
182
|
+
"action_id": "<original action id>",
|
|
183
|
+
"action_kind": "<original action kind>",
|
|
184
|
+
"repository_binding_digest": "<original issuance binding digest>",
|
|
185
|
+
"payload": {}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`operation_id` is the original apply `request_id`, never the current read request ID.
|
|
190
|
+
`expected_revision` is the original action `revision`. `repository_binding_digest` is the original
|
|
191
|
+
issuance binding. `payload` is the exact original closed payload. If the payload was not completely
|
|
192
|
+
retained, `payload` must be JSON `null`; never reconstruct it from partial output, repository text,
|
|
193
|
+
or model memory. Do not add a caller-supplied payload digest or any other member.
|
|
194
|
+
|
|
195
|
+
1. The Skill does not immediately repeat `dev_flow_apply_action` or automatically retry.
|
|
196
|
+
2. Use the original `task_id` to call `dev_flow_get_task` with the exact `operation_probe`.
|
|
197
|
+
3. Call `dev_flow_get_next_action` only when a current action or outcome is needed. If both reads
|
|
198
|
+
carry a probe, both reads use the same original `operation_probe`.
|
|
199
|
+
4. A stale pre-dispatch Task snapshot is not an authoritative read-back. Obey only a complete fresh
|
|
200
|
+
Core result and obey the complete Core recovery assessment and advice.
|
|
201
|
+
5. Permit retry or recovery only when Core explicitly says it is safe to retry or recover. Do not
|
|
202
|
+
branch on, decide, or interpret any recovery classification in the Skill.
|
|
203
|
+
6. Otherwise stop and report the authoritative blocker or recovery condition.
|
|
204
|
+
|
|
205
|
+
If any required identity is missing or incomplete, do not construct or send an `operation_probe`;
|
|
206
|
+
send no fabricated probe and no half probe. Do not complete missing values from a partial response,
|
|
207
|
+
do not assume `not_started`, and do not automatically retry. Stop and report that the Skill cannot
|
|
208
|
+
prove the mutation state.
|
|
209
|
+
|
|
210
|
+
A complete structured `ok=false` result is a domain error, not transport uncertainty. Never convert
|
|
211
|
+
that domain error to missing or transport-failed. When it reports `retry_safe=false` and
|
|
212
|
+
`action=none`, stop. Do not call `dev_flow_get_next_action` or `dev_flow_apply_action` to repair or
|
|
213
|
+
retry that rejected mutation.
|
|
214
|
+
|
|
215
|
+
## Evidence and verification budget
|
|
216
|
+
|
|
217
|
+
- Count verification commands exactly against Core's budget.
|
|
218
|
+
- Do not run a prohibited full suite. When automatic capacity is exhausted, present the remaining
|
|
219
|
+
allowed work as a manual handoff.
|
|
220
|
+
- Preserve repository instructions and explicit user authority even when broader work is possible.
|
|
221
|
+
- Keep static inspection, simulated Core execution, user-performed evidence, and native automated
|
|
222
|
+
evidence distinctly labelled.
|
|
223
|
+
- Submit actual evidence sources and outcomes. Never relabel a failed, skipped, or unavailable
|
|
224
|
+
check as passed.
|
|
225
|
+
|
|
226
|
+
## Blocked and terminal behavior
|
|
227
|
+
|
|
228
|
+
Stop repository work when Core returns an authoritative blocker, ownership or contract conflict,
|
|
229
|
+
`DONE`, or `CANCELLED`. Report Core's blocker and unblock condition, exact terminal outcome and
|
|
230
|
+
evidence summary, cancellation, or conflict without replacing or merging a task. Use
|
|
231
|
+
`dev_flow_cancel_task` only after explicit user authority and a fresh current Core identity.
|
|
232
|
+
|
|
233
|
+
Codex's belief that source work is complete does not override Core, and a blocker is not success.
|
|
234
|
+
|
|
235
|
+
## Presentation contract
|
|
236
|
+
|
|
237
|
+
Use complete structured Core results for every decision. A concise user summary must still preserve
|
|
238
|
+
task identity, current revision, whether a mutation committed, any blocker or recovery condition,
|
|
239
|
+
verification evidence and limits, and the terminal outcome. Never request or display private
|
|
240
|
+
database locations.
|
|
241
|
+
|
|
242
|
+
If only a truncated preview is available, treat the operation as uncertain and follow the recovery
|
|
243
|
+
contract. Do not fill missing data from a local catalog or silently discard outcome-bearing fields.
|
|
Binary file
|