@michaelxwb/code-flow 0.6.4 → 0.8.1
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 +47 -3
- package/docs/USAGE.md +1529 -0
- package/docs/acceptance-contract.md +217 -0
- package/docs/ci/acceptance-evidence.md +77 -0
- package/docs/migrations/runtime-0.7.0.md +41 -0
- package/docs/migrations/runtime-0.8.0.md +154 -0
- package/docs/migrations/runtime-0.9.0.md +40 -0
- package/docs/spec-injection-hardening.md +54 -0
- package/docs/spec-injection-performance.json +73 -0
- package/docs/spec-optimization.md +12 -0
- package/package.json +18 -3
- package/src/adapters/claude/CLAUDE.md +1 -1
- package/src/adapters/claude/commands/cf-init.md +1 -3
- package/src/adapters/claude/commands/cf-spec.md +23 -3
- package/src/adapters/claude/commands/cf-stats.md +11 -1
- package/src/adapters/claude/commands/cf-sync.md +20 -6
- package/src/adapters/claude/commands/cf-task/align.md +25 -3
- package/src/adapters/claude/commands/cf-task/archive.md +36 -40
- package/src/adapters/claude/commands/cf-task/block.md +15 -2
- package/src/adapters/claude/commands/cf-task/graph.md +13 -1
- package/src/adapters/claude/commands/cf-task/note.md +13 -1
- package/src/adapters/claude/commands/cf-task/plan.md +137 -23
- package/src/adapters/claude/commands/cf-task/prd.md +13 -2
- package/src/adapters/claude/commands/cf-task/start.md +67 -35
- package/src/adapters/claude/commands/cf-task/status.md +12 -0
- package/src/adapters/claude/commands/cf-task/verify-e2e.md +72 -34
- package/src/adapters/claude/commands/cf-validate.md +30 -5
- package/src/adapters/codex/AGENTS.md +1 -1
- package/src/adapters/codex/hooks.json +14 -10
- package/src/adapters/codex/skills/cf-init/SKILL.md +4 -4
- package/src/adapters/codex/skills/cf-spec/SKILL.md +34 -6
- package/src/adapters/codex/skills/cf-stats/SKILL.md +11 -1
- package/src/adapters/codex/skills/cf-sync/SKILL.md +21 -6
- package/src/adapters/codex/skills/cf-task-align/SKILL.md +30 -8
- package/src/adapters/codex/skills/cf-task-archive/SKILL.md +36 -40
- package/src/adapters/codex/skills/cf-task-block/SKILL.md +15 -2
- package/src/adapters/codex/skills/cf-task-graph/SKILL.md +13 -1
- package/src/adapters/codex/skills/cf-task-note/SKILL.md +13 -1
- package/src/adapters/codex/skills/cf-task-plan/SKILL.md +175 -21
- package/src/adapters/codex/skills/cf-task-prd/SKILL.md +13 -2
- package/src/adapters/codex/skills/cf-task-start/SKILL.md +67 -35
- package/src/adapters/codex/skills/cf-task-status/SKILL.md +12 -0
- package/src/adapters/codex/skills/cf-task-verify-e2e/SKILL.md +73 -34
- package/src/adapters/codex/skills/cf-validate/SKILL.md +31 -5
- package/src/adapters/commands.json +115 -0
- package/src/adapters/costrict/CLAUDE.md +1 -1
- package/src/adapters/costrict/commands/cf-init.md +1 -3
- package/src/adapters/costrict/commands/cf-spec.md +23 -3
- package/src/adapters/costrict/commands/cf-stats.md +11 -1
- package/src/adapters/costrict/commands/cf-sync.md +20 -6
- package/src/adapters/costrict/commands/cf-task/align.md +25 -3
- package/src/adapters/costrict/commands/cf-task/archive.md +36 -40
- package/src/adapters/costrict/commands/cf-task/block.md +15 -2
- package/src/adapters/costrict/commands/cf-task/graph.md +13 -1
- package/src/adapters/costrict/commands/cf-task/note.md +13 -1
- package/src/adapters/costrict/commands/cf-task/plan.md +137 -23
- package/src/adapters/costrict/commands/cf-task/prd.md +13 -2
- package/src/adapters/costrict/commands/cf-task/start.md +67 -35
- package/src/adapters/costrict/commands/cf-task/status.md +12 -0
- package/src/adapters/costrict/commands/cf-task/verify-e2e.md +72 -34
- package/src/adapters/costrict/commands/cf-validate.md +30 -5
- package/src/adapters/install-plan.json +100 -0
- package/src/adapters/opencode/AGENTS.md +1 -1
- package/src/adapters/opencode/commands/cf-init.md +7 -7
- package/src/adapters/opencode/commands/cf-learn.md +6 -6
- package/src/adapters/opencode/commands/cf-spec.md +34 -2
- package/src/adapters/opencode/commands/cf-stats.md +17 -7
- package/src/adapters/opencode/commands/cf-sync.md +21 -6
- package/src/adapters/opencode/commands/cf-task/align.md +25 -8
- package/src/adapters/opencode/commands/cf-task/archive.md +36 -40
- package/src/adapters/opencode/commands/cf-task/block.md +16 -2
- package/src/adapters/opencode/commands/cf-task/graph.md +14 -1
- package/src/adapters/opencode/commands/cf-task/note.md +14 -1
- package/src/adapters/opencode/commands/cf-task/plan.md +137 -23
- package/src/adapters/opencode/commands/cf-task/prd.md +13 -2
- package/src/adapters/opencode/commands/cf-task/start.md +68 -41
- package/src/adapters/opencode/commands/cf-task/status.md +13 -0
- package/src/adapters/opencode/commands/cf-task/verify-e2e.md +72 -34
- package/src/adapters/opencode/commands/cf-validate.md +35 -9
- package/src/adapters/opencode/plugins/code-flow/index.js +128 -179
- package/src/adapters/platform-bindings.json +370 -0
- package/src/cli.js +369 -316
- package/src/core/code-flow/config.yml +4 -1
- package/src/core/code-flow/runtime-commands.json +259 -0
- package/src/core/code-flow/scripts/cf_acceptance_manifest.py +5 -289
- package/src/core/code-flow/scripts/cf_acceptance_runner.py +5 -187
- package/src/core/code-flow/scripts/cf_audit.py +10 -0
- package/src/core/code-flow/scripts/cf_codex_hook.py +10 -0
- package/src/core/code-flow/scripts/cf_design_lint.py +10 -0
- package/src/core/code-flow/scripts/cf_evidence_import.py +10 -0
- package/src/core/code-flow/scripts/cf_feedback.py +5 -51
- package/src/core/code-flow/scripts/cf_opencode_event.py +10 -0
- package/src/core/code-flow/scripts/cf_post_hook.py +6 -191
- package/src/core/code-flow/scripts/cf_pre_tool_hook.py +6 -99
- package/src/core/code-flow/scripts/cf_scenarios.py +10 -0
- package/src/core/code-flow/scripts/cf_spec_context.py +5 -1714
- package/src/core/code-flow/scripts/cf_spec_gate.py +5 -257
- package/src/core/code-flow/scripts/cf_spec_session.py +5 -114
- package/src/core/code-flow/scripts/cf_stats.py +5 -501
- package/src/core/code-flow/scripts/cf_stop_hook.py +6 -376
- package/src/core/code-flow/scripts/cf_sync.py +5 -135
- package/src/core/code-flow/scripts/cf_task_index.py +5 -143
- package/src/core/code-flow/scripts/cf_task_parallel.py +5 -380
- package/src/core/code-flow/scripts/cf_task_workflow.py +6 -103
- package/src/core/code-flow/scripts/cf_user_prompt_hook.py +6 -173
- package/src/core/code-flow/scripts/cf_validation.py +6 -77
- package/src/core/code-flow/scripts/codeflow/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_evidence.py +347 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_manifest_core.py +660 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_plan.py +202 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_registration.py +60 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_risk.py +73 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_schema.py +282 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_selection.py +266 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_inputs.py +172 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_registration.py +326 -0
- package/src/core/code-flow/scripts/{cf_checks.py → codeflow/acceptance/cf_checks.py} +8 -11
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_plan_quality.py +197 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_quality.py +174 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_source.py +300 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_traceability.py +502 -0
- package/src/core/code-flow/scripts/codeflow/cli/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_manifest.py +347 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_runner.py +1490 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_audit.py +533 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_design_lint.py +172 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_evidence_import.py +607 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_feedback.py +53 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_scenarios.py +58 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_spec_context.py +1175 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_spec_gate.py +60 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_spec_session.py +46 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_stats.py +540 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_sync.py +144 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_task_index.py +177 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_task_parallel.py +1142 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_task_workflow.py +742 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_validation.py +31 -0
- package/src/core/code-flow/scripts/codeflow/core/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_context_cache.py +67 -0
- package/src/core/code-flow/scripts/{cf_core.py → codeflow/core/cf_core.py} +119 -297
- package/src/core/code-flow/scripts/codeflow/core/cf_exec_base.py +325 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_fs.py +57 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_git.py +92 -0
- package/src/core/code-flow/scripts/{cf_log.py → codeflow/core/cf_log.py} +13 -7
- package/src/core/code-flow/scripts/codeflow/core/cf_markdown.py +77 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_native_paths.py +26 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_patch.py +33 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_runtime_install.py +32 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_stop_guard.py +53 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_task_state.py +9 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_validators.py +408 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_worktree_env.py +253 -0
- package/src/core/code-flow/scripts/codeflow/evidence/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/evidence/cf_evidence_store.py +527 -0
- package/src/core/code-flow/scripts/codeflow/hooks/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_codex_hook.py +95 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_opencode_event.py +98 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_post_hook.py +30 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_pre_tool_hook.py +41 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_stop_hook.py +292 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_user_prompt_hook.py +161 -0
- package/src/core/code-flow/scripts/codeflow/spec/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_active_task.py +412 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_context_refresh.py +301 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_edit_service.py +149 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_injection.py +111 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_rule_readiness.py +114 -0
- package/src/core/code-flow/scripts/{cf_scan.py → codeflow/spec/cf_scan.py} +93 -21
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_budget.py +55 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_context_model.py +477 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_impact.py +119 -0
- package/src/core/code-flow/scripts/{cf_spec_metadata.py → codeflow/spec/cf_spec_metadata.py} +37 -3
- package/src/core/code-flow/scripts/{cf_spec_migrate.py → codeflow/spec/cf_spec_migrate.py} +27 -10
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize.py +193 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize_apply.py +187 -0
- package/src/core/code-flow/scripts/{cf_spec_resolver.py → codeflow/spec/cf_spec_resolver.py} +39 -17
- package/src/core/code-flow/scripts/{cf_spec_router.py → codeflow/spec/cf_spec_router.py} +96 -36
- package/src/core/code-flow/scripts/{cf_spec_verify.py → codeflow/spec/cf_spec_verify.py} +252 -26
- package/src/core/code-flow/scripts/codeflow/spec/cf_stage_gate.py +330 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_task_projection.py +83 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_workflow_service.py +755 -0
- package/src/core/code-flow/scripts/{cf_workflow_transaction.py → codeflow/spec/cf_workflow_transaction.py} +29 -15
- package/src/core/code-flow/scripts/codeflow/task/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_requirement_verification.py +867 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_task_doc.py +178 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_task_merge.py +635 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_task_plan.py +251 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_task_runtime.py +570 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_validation_service.py +73 -0
- package/src/core/code-flow/scripts/runtime-resources.json +29 -0
- package/src/core/code-flow/specs/shared/design/design-frontend.md +18 -15
- package/src/core/code-flow/specs/shared/design/design-full.md +33 -23
- package/src/core/code-flow/specs/shared/design/design-lite.md +23 -13
- package/src/core/code-flow/specs/shared/scenarios-template.md +55 -0
- package/src/core/code-flow/validation.yml +9 -3
- package/src/migrate/runtime.js +213 -0
- package/src/migrate/spec-workflow.js +16 -1
- package/src/runtime.js +219 -0
- package/src/core/code-flow/scripts/cf_acceptance_evidence.py +0 -100
- package/src/core/code-flow/scripts/cf_acceptance_schema.py +0 -83
- package/src/core/code-flow/scripts/cf_exec_base.py +0 -112
- package/src/core/code-flow/scripts/cf_task_runtime.py +0 -299
- package/src/core/code-flow/scripts/cf_task_state.py +0 -3
- package/src/core/code-flow/scripts/cf_workflow_service.py +0 -366
- /package/src/core/code-flow/scripts/{cf_session_state.py → codeflow/core/cf_session_state.py} +0 -0
|
@@ -1,213 +1,162 @@
|
|
|
1
|
-
import { execFile } from "child_process";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import {
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { join, isAbsolute, resolve, dirname } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
4
|
|
|
5
|
-
// Note: intentionally no `import { Plugin } from "@opencode/plugin"`.
|
|
6
|
-
// The server does not auto-provide that package for local plugins, and
|
|
7
|
-
// `Plugin.define` is only an identity wrapper — a plain default export
|
|
8
|
-
// with `id` + `setup` satisfies the v2 plugin schema dependency-free,
|
|
9
|
-
// so `code-flow init` works offline without npm install.
|
|
10
|
-
|
|
11
|
-
const SCRIPT_DIR = ".code-flow/scripts";
|
|
12
|
-
const STOP_CHECK_TIMEOUT = 35000;
|
|
13
5
|
const HOOK_TIMEOUT = 5000;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return existing + "\n\n" + incoming;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function debugLog(projectRoot, msg) {
|
|
28
|
-
if (process.env.CF_DEBUG !== "1") return;
|
|
29
|
-
try {
|
|
30
|
-
const dir = join(projectRoot, ".code-flow");
|
|
31
|
-
mkdirSync(dir, { recursive: true });
|
|
32
|
-
const ts = new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
33
|
-
appendFileSync(join(dir, ".debug.log"), `${ts} [opencode] ${msg}\n`);
|
|
34
|
-
} catch {}
|
|
6
|
+
const IDLE_TIMEOUT = 35000;
|
|
7
|
+
|
|
8
|
+
function runtimeScript(directory) {
|
|
9
|
+
let root = resolve(directory);
|
|
10
|
+
while (!existsSync(join(root, ".code-flow/config.yml"))) {
|
|
11
|
+
const parent = dirname(root);
|
|
12
|
+
if (parent === root) throw new Error(`No code-flow runtime for OpenCode session at ${directory}`);
|
|
13
|
+
root = parent;
|
|
14
|
+
}
|
|
15
|
+
return join(root, ".code-flow/scripts/cf_opencode_event.py");
|
|
35
16
|
}
|
|
36
17
|
|
|
37
|
-
|
|
38
|
-
|
|
18
|
+
/** Combine model-visible feedback without dropping earlier gate failures. */
|
|
19
|
+
export function mergePending(existing, incoming) {
|
|
20
|
+
return [existing, incoming].filter(Boolean).join("\n\n");
|
|
39
21
|
}
|
|
40
22
|
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
[pythonPath(projectRoot, script)],
|
|
50
|
-
{ cwd: projectRoot, timeout, maxBuffer: 1024 * 1024 },
|
|
51
|
-
(error, stdout, stderr) => {
|
|
52
|
-
if (error) {
|
|
53
|
-
debugLog(projectRoot, `callHook ${script} failed: ${error.message || stderr}`);
|
|
54
|
-
resolve(null);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const text = (stdout || "").trim();
|
|
58
|
-
if (!text) {
|
|
59
|
-
resolve(null);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
23
|
+
function callRuntime(directory, kind, event, signal) {
|
|
24
|
+
return new Promise((accept, reject) => {
|
|
25
|
+
const child = execFile(process.platform === "win32" ? "py" : "python3",
|
|
26
|
+
[...(process.platform === "win32" ? ["-3"] : []), runtimeScript(directory)],
|
|
27
|
+
{ cwd: directory, timeout: kind === "session.idle" ? IDLE_TIMEOUT : HOOK_TIMEOUT,
|
|
28
|
+
maxBuffer: 1024 * 1024, signal }, (error, stdout, stderr) => {
|
|
29
|
+
if (error) return reject(new Error(`code-flow native runtime: ${error.message}; ${stderr.trim()}`));
|
|
30
|
+
if (stderr.trim()) console.error(stderr.trim());
|
|
62
31
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
32
|
+
const result = JSON.parse(stdout);
|
|
33
|
+
if (result.error) throw new Error(result.error);
|
|
34
|
+
if (typeof result.context !== "string" || typeof result.blocked !== "boolean") {
|
|
35
|
+
throw new Error("Invalid code-flow native RPC response");
|
|
36
|
+
}
|
|
37
|
+
accept(result);
|
|
38
|
+
} catch (failure) { reject(failure); }
|
|
39
|
+
});
|
|
40
|
+
child.stdin.on("error", reject);
|
|
41
|
+
child.stdin.end(JSON.stringify({ directory, kind, event }));
|
|
74
42
|
});
|
|
75
43
|
}
|
|
76
44
|
|
|
77
|
-
function
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
45
|
+
function createState() {
|
|
46
|
+
const pending = new Map();
|
|
47
|
+
const jobs = new Map();
|
|
48
|
+
let disposed = false;
|
|
49
|
+
return {
|
|
50
|
+
fault: "",
|
|
51
|
+
queue(sid, text) { if (!disposed && text) pending.set(sid, mergePending(pending.get(sid), text)); },
|
|
52
|
+
consume(sid) { const text = pending.get(sid); pending.delete(sid); return text; },
|
|
53
|
+
async wait(sid) { await jobs.get(sid); },
|
|
54
|
+
run(sid, operation) {
|
|
55
|
+
const job = (jobs.get(sid) || Promise.resolve()).then(operation, operation);
|
|
56
|
+
jobs.set(sid, job);
|
|
57
|
+
return job.finally(() => { if (jobs.get(sid) === job) jobs.delete(sid); });
|
|
58
|
+
},
|
|
59
|
+
clear(sid) { pending.delete(sid); },
|
|
60
|
+
dispose() { disposed = true; pending.clear(); jobs.clear(); },
|
|
61
|
+
};
|
|
92
62
|
}
|
|
93
63
|
|
|
94
|
-
async function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
debugLog(projectRoot, `stop-check feedback queued`);
|
|
64
|
+
async function sessionInfo(ctx, sid) {
|
|
65
|
+
if (typeof sid !== "string" || !sid) throw new Error("OpenCode native event requires sessionID");
|
|
66
|
+
const info = await ctx.session.get({ sessionID: sid });
|
|
67
|
+
if (typeof info?.location?.directory !== "string" || !isAbsolute(info.location.directory)) {
|
|
68
|
+
throw new Error(`OpenCode session ${sid} has no absolute location.directory`);
|
|
100
69
|
}
|
|
70
|
+
return info;
|
|
101
71
|
}
|
|
102
72
|
|
|
103
|
-
async function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// Append, never overwrite: queued stop-check feedback survives.
|
|
115
|
-
queueFeedback(event.sessionID, additional);
|
|
116
|
-
debugLog(projectRoot, `hook matched — context ${additional.length} chars cached`);
|
|
117
|
-
} else {
|
|
118
|
-
debugLog(projectRoot, `hook returned no context`);
|
|
73
|
+
async function invoke(ctx, state, kind, event, signal) {
|
|
74
|
+
let result;
|
|
75
|
+
try {
|
|
76
|
+
const info = await sessionInfo(ctx, event.sessionID);
|
|
77
|
+
result = await callRuntime(info.location.directory, kind, event, signal);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (!signal.aborted) {
|
|
80
|
+
const message = `code-flow OpenCode: ${error.message}`;
|
|
81
|
+
// The event subscriber owns idle error feedback, including lookup errors.
|
|
82
|
+
if (kind !== "session.idle") state.queue(event.sessionID, message);
|
|
83
|
+
console.error(message);
|
|
119
84
|
}
|
|
120
|
-
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
state.queue(event.sessionID, result.context);
|
|
88
|
+
if (result.blocked && ["tool.before", "session.prompt"].includes(kind)) throw new Error(result.context);
|
|
121
89
|
}
|
|
122
90
|
|
|
123
|
-
async function
|
|
124
|
-
await ctx.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
tool_input: { file_path: filePath },
|
|
132
|
-
session_id: event.sessionID || "",
|
|
133
|
-
});
|
|
134
|
-
const additional = result?.hookSpecificOutput?.additionalContext;
|
|
135
|
-
if (additional) {
|
|
136
|
-
// 反馈排队,下一轮 context hook 注入(opencode 无法当轮插话)
|
|
137
|
-
queueFeedback(event.sessionID, additional);
|
|
138
|
-
debugLog(projectRoot, `post-check feedback queued ${additional.length} chars`);
|
|
91
|
+
async function registerHooks(ctx, state, signal) {
|
|
92
|
+
await ctx.session.hook("prompt", event => {
|
|
93
|
+
if (!event.prompt?.text?.trim()) return;
|
|
94
|
+
return state.run(event.sessionID, () => invoke(ctx, state, "session.prompt", event, signal));
|
|
95
|
+
});
|
|
96
|
+
await ctx.session.hook("context", async event => {
|
|
97
|
+
try { await state.wait(event.sessionID); } catch (error) {
|
|
98
|
+
console.error(`code-flow pending event failed: ${error.message}`);
|
|
139
99
|
}
|
|
100
|
+
const text = mergePending(state.consume(event.sessionID), state.fault);
|
|
101
|
+
state.fault = "";
|
|
102
|
+
if (text) event.system.push({ type: "text", text });
|
|
140
103
|
});
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
104
|
+
await ctx.tool.hook("execute.before", event => {
|
|
105
|
+
if (!["edit", "write", "patch"].includes(event.tool)) return;
|
|
106
|
+
return state.run(event.sessionID, () => invoke(ctx, state, "tool.before", event, signal));
|
|
107
|
+
});
|
|
108
|
+
await ctx.tool.hook("execute.after", async event => {
|
|
109
|
+
if (!["edit", "write", "patch"].includes(event.tool)) return;
|
|
110
|
+
try {
|
|
111
|
+
await state.run(event.sessionID, () => invoke(ctx, state, "tool.after", event, signal));
|
|
112
|
+
} catch (error) {
|
|
113
|
+
// The mutation has already happened. Preserve its result and expose the
|
|
114
|
+
// runtime error through the next native context hook.
|
|
115
|
+
if (!signal.aborted) console.error(`code-flow post-edit check failed: ${error.message}`);
|
|
149
116
|
}
|
|
150
117
|
});
|
|
151
118
|
}
|
|
152
119
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (event.type === "session.created") {
|
|
164
|
-
if (event.data?.parentID) children.add(sid);
|
|
165
|
-
return { type: "created", sid };
|
|
166
|
-
}
|
|
167
|
-
if (event.type === "session.idle") {
|
|
168
|
-
return { type: "idle", sid, child: children.has(sid) };
|
|
169
|
-
}
|
|
170
|
-
return null;
|
|
171
|
-
},
|
|
172
|
-
};
|
|
120
|
+
async function observeIdle(ctx, state, event, signal) {
|
|
121
|
+
const sid = event.data?.sessionID;
|
|
122
|
+
if (!sid) throw new Error("OpenCode session.idle requires data.sessionID");
|
|
123
|
+
const info = await sessionInfo(ctx, sid);
|
|
124
|
+
if (resolve(info.location.directory) !== resolve(ctx.location.directory)) return;
|
|
125
|
+
if (info.parentID) {
|
|
126
|
+
const parent = await sessionInfo(ctx, info.parentID);
|
|
127
|
+
if (resolve(parent.location.directory) === resolve(info.location.directory)) return;
|
|
128
|
+
}
|
|
129
|
+
await state.run(sid, () => invoke(ctx, state, "session.idle", { sessionID: sid }, signal));
|
|
173
130
|
}
|
|
174
131
|
|
|
175
|
-
function
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
sessionContext.delete(observed.sid);
|
|
186
|
-
} else if (observed.child) {
|
|
187
|
-
debugLog(projectRoot, `session.idle sid=${observed.sid} (child) — stop-check skipped`);
|
|
188
|
-
} else {
|
|
189
|
-
await runStopCheck(projectRoot, observed.sid);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
} catch (err) {
|
|
193
|
-
if (err?.name !== "AbortError") {
|
|
194
|
-
debugLog(projectRoot, `event subscription ended: ${err?.message || err}`);
|
|
132
|
+
async function subscribe(ctx, state, signal) {
|
|
133
|
+
try {
|
|
134
|
+
for await (const event of ctx.event.subscribe({ signal })) {
|
|
135
|
+
try {
|
|
136
|
+
if (event.type === "session.deleted") state.clear(event.data.sessionID);
|
|
137
|
+
if (event.type === "session.idle") await observeIdle(ctx, state, event, signal);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (signal.aborted) return;
|
|
140
|
+
state.queue(event.data?.sessionID, `code-flow idle check failed: ${error.message}`);
|
|
141
|
+
console.error(`code-flow idle check failed: ${error.message}`);
|
|
195
142
|
}
|
|
196
143
|
}
|
|
197
|
-
}
|
|
198
|
-
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (!signal.aborted) {
|
|
146
|
+
state.fault = `code-flow OpenCode event subscription failed: ${error.message}`;
|
|
147
|
+
console.error(state.fault);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
199
150
|
}
|
|
200
151
|
|
|
201
|
-
|
|
152
|
+
// V2's native definition shape. No v1 server() entry or protocol adapter.
|
|
153
|
+
export default {
|
|
202
154
|
id: "code-flow",
|
|
203
155
|
async setup(ctx) {
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
await
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
return () => stopSubscription();
|
|
156
|
+
const state = createState();
|
|
157
|
+
const controller = new AbortController();
|
|
158
|
+
await registerHooks(ctx, state, controller.signal);
|
|
159
|
+
void subscribe(ctx, state, controller.signal);
|
|
160
|
+
return () => { controller.abort(); state.dispose(); };
|
|
210
161
|
},
|
|
211
162
|
};
|
|
212
|
-
|
|
213
|
-
export default CodeFlowPlugin;
|