code-workspace-zhuiyi 0.1.0-beta.4 → 0.1.0-beta.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 +2 -2
- package/README.zh-CN.md +2 -2
- package/artifacts/manifest.json +1 -1
- package/bin/code-workspace-task-hook.js +42 -5
- package/package.json +3 -2
- package/src/cli/commands/init.js +3 -2
- package/src/cli/commands/update.js +25 -11
- package/src/core/doctor.js +22 -3
- package/src/core/init.js +3 -0
- package/src/core/initializer.js +30 -5
- package/src/core/managed-files.js +51 -10
- package/src/core/task-coordination-managed.js +86 -19
- package/src/core/task-coordination-protocol.js +84 -85
- package/src/hooks/adapters/claude.js +57 -7
- package/src/hooks/adapters/codex.js +59 -7
- package/src/hooks/adapters/common.js +169 -1
- package/src/hooks/adapters/index.js +42 -4
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ code-workspace init . \
|
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Use `--tools claude`, `--tools codex`, or `--tools none` to override the default tool selection. Codex monitoring is enabled by default when Codex is selected; use `--no-monitor` to disable it.
|
|
37
|
-
Pass `--coordination` to install the
|
|
37
|
+
Pass `--coordination` to install the write-coordination Hooks into the selected providers' native configuration.
|
|
38
38
|
|
|
39
39
|
Initialization writes only Workspace-owned state and integrations:
|
|
40
40
|
|
|
@@ -42,7 +42,7 @@ Initialization writes only Workspace-owned state and integrations:
|
|
|
42
42
|
- `USER_GUIDE.md`
|
|
43
43
|
- `CLAUDE.md` and/or `AGENTS.md`
|
|
44
44
|
- Workspace-specific commands and skills whose names start with `code-workspace-` or use the `/code-workspace` namespace
|
|
45
|
-
- `.codex/hooks.json` when monitoring is enabled
|
|
45
|
+
- `.codex/hooks.json` when monitoring or Codex coordination is enabled, and `.claude/settings.json` when Claude coordination is enabled
|
|
46
46
|
|
|
47
47
|
It does not create `openspec/`, install native `/opsx` commands, or install native `openspec-*` skills.
|
|
48
48
|
|
package/README.zh-CN.md
CHANGED
|
@@ -34,7 +34,7 @@ code-workspace init . \
|
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
可用 `--tools claude`、`--tools codex` 或 `--tools none` 覆盖默认工具选择。选择 Codex 时默认启用监控;可传 `--no-monitor` 关闭。
|
|
37
|
-
传入 `--coordination`
|
|
37
|
+
传入 `--coordination` 可把与 Monitor 独立的写入协调 Hook 安装到选中工具的原生配置中。
|
|
38
38
|
|
|
39
39
|
初始化只写入 Workspace 自有状态和集成:
|
|
40
40
|
|
|
@@ -42,7 +42,7 @@ code-workspace init . \
|
|
|
42
42
|
- `USER_GUIDE.md`
|
|
43
43
|
- `CLAUDE.md` 和/或 `AGENTS.md`
|
|
44
44
|
- 名称以 `code-workspace-` 开头或使用 `/code-workspace` 命名空间的 Workspace 专用命令与 Skill
|
|
45
|
-
-
|
|
45
|
+
- 启用监控或 Codex 协调时的 `.codex/hooks.json`,以及启用 Claude 协调时的 `.claude/settings.json`
|
|
46
46
|
|
|
47
47
|
它不会创建 `openspec/`,不会安装原生 `/opsx` 命令,也不会安装原生 `openspec-*` Skill。
|
|
48
48
|
|
package/artifacts/manifest.json
CHANGED
|
@@ -2,16 +2,53 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
const { runHookStdin } = require("../src/core/task-coordination-protocol");
|
|
5
|
+
const { getAdapter } = require("../src/hooks/adapters");
|
|
5
6
|
|
|
6
7
|
const provider = String(process.argv[2] || "").toLowerCase();
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
let adapter = null;
|
|
9
|
+
try {
|
|
10
|
+
adapter = getAdapter(provider);
|
|
11
|
+
} catch {
|
|
12
|
+
// Keep the provider error below provider-neutral because no native renderer
|
|
13
|
+
// is available for an unknown provider.
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function writeFailure(reason) {
|
|
17
|
+
const output = adapter?.renderFailure
|
|
18
|
+
? adapter.renderFailure({ remediation: reason })
|
|
19
|
+
: { decision: "block", reason };
|
|
20
|
+
process.stdout.write(`${JSON.stringify(output)}\n`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let workspaceRoot = process.cwd();
|
|
24
|
+
for (let index = 3; index < process.argv.length; index += 1) {
|
|
25
|
+
const argument = process.argv[index];
|
|
26
|
+
if (argument === "--workspace-root-b64") {
|
|
27
|
+
const encoded = process.argv[++index];
|
|
28
|
+
try {
|
|
29
|
+
workspaceRoot = Buffer.from(String(encoded || ""), "base64url").toString("utf8");
|
|
30
|
+
if (!workspaceRoot) throw new Error("empty path");
|
|
31
|
+
} catch {
|
|
32
|
+
writeFailure("Invalid task coordination Hook workspace root.");
|
|
33
|
+
process.exitCode = 2;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
} else if (argument === "--workspace-root") {
|
|
37
|
+
workspaceRoot = String(process.argv[++index] || "");
|
|
38
|
+
} else {
|
|
39
|
+
writeFailure(`Unsupported task coordination Hook option: ${argument}`);
|
|
40
|
+
process.exitCode = 2;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!adapter) {
|
|
45
|
+
writeFailure("Unsupported task coordination Hook provider.");
|
|
9
46
|
process.exitCode = 2;
|
|
10
|
-
} else {
|
|
11
|
-
runHookStdin(provider, { workspaceRoot
|
|
47
|
+
} else if (process.exitCode !== 2) {
|
|
48
|
+
runHookStdin(provider, { workspaceRoot })
|
|
12
49
|
.then((output) => process.stdout.write(`${JSON.stringify(output)}\n`))
|
|
13
50
|
.catch((error) => {
|
|
14
|
-
|
|
51
|
+
writeFailure(`Task coordination Hook failed closed: ${error.message}`);
|
|
15
52
|
process.exitCode = 1;
|
|
16
53
|
});
|
|
17
54
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-workspace-zhuiyi",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.6",
|
|
4
4
|
"description": "Local multi-project registry and safety layer for AI coding workspaces",
|
|
5
5
|
"author": "icebearx-ai",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
},
|
|
14
14
|
"bin": {
|
|
15
15
|
"code-workspace": "bin/code-workspace.js",
|
|
16
|
-
"code-w": "bin/code-workspace.js"
|
|
16
|
+
"code-w": "bin/code-workspace.js",
|
|
17
|
+
"code-workspace-task-hook": "bin/code-workspace-task-hook.js"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"assets",
|
package/src/cli/commands/init.js
CHANGED
|
@@ -111,6 +111,7 @@ async function executeInitUnlocked(invocation, root) {
|
|
|
111
111
|
const extensionPreparation = prepareExtensionPlans(extensionCatalogResult, requestedExtensions, { tools, state: extensionState, stateError: extensionStateInspection.error });
|
|
112
112
|
const extensionPlans = extensionPreparation.plans;
|
|
113
113
|
const toolSelection = { tools, source: plan ? (options.tools !== undefined ? "cli" : "interactive") : resolvedTools.source };
|
|
114
|
+
const coordination = options.coordination === true || existingState?.coordination === true;
|
|
114
115
|
const result = await initializeWorkspace(root, {
|
|
115
116
|
run,
|
|
116
117
|
tools,
|
|
@@ -120,7 +121,7 @@ async function executeInitUnlocked(invocation, root) {
|
|
|
120
121
|
workspaceUuid: plan?.workspace.uuid,
|
|
121
122
|
monitor: plan?.monitor.enable ?? (options.monitor === true ? true : options["no-monitor"] === true ? false : undefined),
|
|
122
123
|
monitorUrl: plan?.monitor.url || options["monitor-url"],
|
|
123
|
-
coordination
|
|
124
|
+
coordination,
|
|
124
125
|
language: plan?.language || options.language,
|
|
125
126
|
interactive: false,
|
|
126
127
|
initPlan: plan,
|
|
@@ -173,7 +174,7 @@ async function executeInitUnlocked(invocation, root) {
|
|
|
173
174
|
monitor: result.workspaceConfig.monitor,
|
|
174
175
|
language: result.language,
|
|
175
176
|
tools: toolSelection,
|
|
176
|
-
coordination
|
|
177
|
+
coordination,
|
|
177
178
|
migration: migrationData(result.migration),
|
|
178
179
|
permissions: result.permissions,
|
|
179
180
|
verification: result.verification,
|
|
@@ -13,7 +13,7 @@ const { WorkspaceError } = require("../../core/errors");
|
|
|
13
13
|
const { commitUpdateState, loadInitManifest } = require("../../core/init");
|
|
14
14
|
const { workspaceGuide } = require("../../core/language");
|
|
15
15
|
const { inspectManagedFiles, installManagedFiles, planManagedFiles } = require("../../core/managed-files");
|
|
16
|
-
const {
|
|
16
|
+
const { coordinationHookTargets, installCoordinationHooks, removeCoordinationHooks } = require("../../core/task-coordination-managed");
|
|
17
17
|
const { planWorkspaceMaintenance } = require("../../core/migration");
|
|
18
18
|
const { inspectProjectPermissions } = require("../../core/permissions");
|
|
19
19
|
const { resolveWorkspaceTools } = require("../../core/tools");
|
|
@@ -21,8 +21,8 @@ const { createFileTransaction } = require("../../core/transaction");
|
|
|
21
21
|
const { success } = require("../result");
|
|
22
22
|
const { migrationData } = require("./init");
|
|
23
23
|
|
|
24
|
-
function verifyUpdatedManagedFiles(root, manifest, tools, capabilities, variables) {
|
|
25
|
-
const inspection = inspectManagedFiles(root, manifest, tools, capabilities, variables);
|
|
24
|
+
function verifyUpdatedManagedFiles(root, manifest, tools, capabilities, variables, options = {}) {
|
|
25
|
+
const inspection = inspectManagedFiles(root, manifest, tools, capabilities, variables, options);
|
|
26
26
|
const incomplete = [
|
|
27
27
|
...inspection.managedOld.map((target) => ({ target, state: "managed-old" })),
|
|
28
28
|
...inspection.replaceable.map((target) => ({ target, state: "replaceable" })),
|
|
@@ -55,6 +55,10 @@ function updateWorkspace(root, options = {}) {
|
|
|
55
55
|
manifestTools: manifest.tools,
|
|
56
56
|
});
|
|
57
57
|
const tools = toolSelection.tools;
|
|
58
|
+
const previousCoordination = initialState.coordination === true;
|
|
59
|
+
const previousTools = Array.isArray(initialState.tools) ? initialState.tools : [];
|
|
60
|
+
const removedCoordinationTools = previousCoordination ? previousTools.filter((tool) => !tools.includes(tool)) : [];
|
|
61
|
+
const coordination = options.coordination === true || initialState.coordination === true;
|
|
58
62
|
const migration = planWorkspaceMaintenance(root, {
|
|
59
63
|
language: options.language,
|
|
60
64
|
allowLegacy: true,
|
|
@@ -64,7 +68,7 @@ function updateWorkspace(root, options = {}) {
|
|
|
64
68
|
const config = loadConfig(root, { defaultLanguage: language });
|
|
65
69
|
const nextConfig = { ...config, workspace: { ...config.workspace, language } };
|
|
66
70
|
const capabilities = nextConfig.monitor.enable ? ["monitor"] : [];
|
|
67
|
-
if (
|
|
71
|
+
if (coordination) capabilities.push("coordination");
|
|
68
72
|
const variables = { WORKSPACE_LANGUAGE: language, WORKSPACE_USER_GUIDE: workspaceGuide(language) };
|
|
69
73
|
let managedPlan;
|
|
70
74
|
let obsoletePlan;
|
|
@@ -72,6 +76,7 @@ function updateWorkspace(root, options = {}) {
|
|
|
72
76
|
managedPlan = planManagedFiles(root, manifest, tools, {
|
|
73
77
|
force: options.force === true,
|
|
74
78
|
capabilities,
|
|
79
|
+
coordination,
|
|
75
80
|
variables,
|
|
76
81
|
});
|
|
77
82
|
obsoletePlan = planObsoleteAssets(root, tools, { force: options.force === true });
|
|
@@ -88,7 +93,9 @@ function updateWorkspace(root, options = {}) {
|
|
|
88
93
|
resolveProjectConfigPath(root),
|
|
89
94
|
statePath(root),
|
|
90
95
|
...managedPlan.plans.map((plan) => plan.target),
|
|
91
|
-
...(
|
|
96
|
+
...coordinationHookTargets(root, coordination
|
|
97
|
+
? [...new Set([...tools, ...removedCoordinationTools])]
|
|
98
|
+
: removedCoordinationTools),
|
|
92
99
|
...obsoletePlan.map((plan) => path.join(root, plan.target)),
|
|
93
100
|
]);
|
|
94
101
|
try {
|
|
@@ -97,25 +104,31 @@ function updateWorkspace(root, options = {}) {
|
|
|
97
104
|
const managedFiles = installManagedFiles(root, manifest, tools, {
|
|
98
105
|
force: options.force === true,
|
|
99
106
|
capabilities,
|
|
107
|
+
coordination,
|
|
100
108
|
variables,
|
|
101
109
|
});
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
managedFiles.push(...removeCoordinationHooks(root, removedCoordinationTools).map((entry) => ({ ...entry, id: `task-coordination-remove-${entry.tool}` })));
|
|
111
|
+
if (coordination) {
|
|
112
|
+
managedFiles.push(...installCoordinationHooks(root, tools, { force: options.force === true }).map((entry) => ({ ...entry, id: `task-coordination-${entry.tool}` })));
|
|
104
113
|
}
|
|
105
114
|
options.injectFailure?.("after-managed-install", managedFiles);
|
|
106
115
|
saveConfig(root, nextConfig);
|
|
107
116
|
options.injectFailure?.("after-config-save", nextConfig);
|
|
108
|
-
verifyUpdatedManagedFiles(root, manifest, tools, capabilities, variables);
|
|
109
|
-
if (
|
|
110
|
-
const coordinationIncomplete =
|
|
117
|
+
verifyUpdatedManagedFiles(root, manifest, tools, capabilities, variables, { coordination });
|
|
118
|
+
if (coordination) {
|
|
119
|
+
const coordinationIncomplete = installCoordinationHooks(root, tools, { dryRun: true }).filter((entry) => entry.action !== "skip");
|
|
111
120
|
if (coordinationIncomplete.length > 0) {
|
|
112
121
|
throw new WorkspaceError("UPDATE_POSTCONDITION_FAILED", "Coordination Hook artifacts failed verification.", { files: coordinationIncomplete, remediation: "Run code-w update --coordination again after reviewing the reported files." });
|
|
113
122
|
}
|
|
114
123
|
}
|
|
124
|
+
const removedCoordinationIncomplete = removeCoordinationHooks(root, removedCoordinationTools, { dryRun: true }).filter((entry) => entry.action !== "skip");
|
|
125
|
+
if (removedCoordinationIncomplete.length > 0) {
|
|
126
|
+
throw new WorkspaceError("UPDATE_POSTCONDITION_FAILED", "Obsolete coordination Hook entries failed verification.", { files: removedCoordinationIncomplete, remediation: "Run code-w update again after reviewing the reported files." });
|
|
127
|
+
}
|
|
115
128
|
const result = {
|
|
116
129
|
language,
|
|
117
130
|
tools: toolSelection,
|
|
118
|
-
coordination
|
|
131
|
+
coordination,
|
|
119
132
|
migration: migrationData(migration),
|
|
120
133
|
obsoleteFiles,
|
|
121
134
|
managedFiles,
|
|
@@ -141,6 +154,7 @@ function updateWorkspace(root, options = {}) {
|
|
|
141
154
|
options.injectFailure?.("after-verify", result);
|
|
142
155
|
const state = commitUpdateState(root, manifest, {
|
|
143
156
|
tools,
|
|
157
|
+
coordination,
|
|
144
158
|
removeLegacyWorkspaceLanguage: true,
|
|
145
159
|
});
|
|
146
160
|
options.injectFailure?.("after-state-save", state);
|
package/src/core/doctor.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { CURRENT_CONFIG_VERSION, inspectConfigDomains, loadState } = require("./config");
|
|
2
2
|
const { add, result } = require("./diagnostics");
|
|
3
3
|
const { inspectManagedFiles } = require("./managed-files");
|
|
4
|
+
const { installCoordinationHooks } = require("./task-coordination-managed");
|
|
4
5
|
const { DEFAULT_WORKSPACE_LANGUAGE, workspaceGuide } = require("./language");
|
|
5
6
|
const { inspectProjectPermissions } = require("./permissions");
|
|
6
7
|
const { validateProjects } = require("./validation");
|
|
@@ -36,13 +37,17 @@ function doctorWorkspace(root, manifest, options = {}) {
|
|
|
36
37
|
add(output, "error", "WORKSPACE_IDENTITY_MISSING", "Local workspace name and UUID are missing. Re-run `code-w init .` to generate them.");
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
const state = loadState(root);
|
|
39
41
|
const toolSelection = options.toolSelection || resolveWorkspaceTools({
|
|
40
42
|
explicit: options.tools,
|
|
41
43
|
state: loadState(root),
|
|
42
44
|
manifestTools: manifest.tools,
|
|
43
45
|
});
|
|
44
46
|
const tools = toolSelection.tools;
|
|
45
|
-
const capabilities = options.capabilities ||
|
|
47
|
+
const capabilities = options.capabilities || [
|
|
48
|
+
...(inspection.monitor.valid && config.monitor?.enable ? ["monitor"] : []),
|
|
49
|
+
...(state?.coordination === true ? ["coordination"] : []),
|
|
50
|
+
];
|
|
46
51
|
if (inspection.monitor.valid && config.monitor?.enable && !tools.includes("codex")) {
|
|
47
52
|
add(output, "error", "MONITOR_CODEX_REQUIRED", "Agent monitoring is enabled, but Codex is not one of the selected tools.");
|
|
48
53
|
}
|
|
@@ -56,7 +61,7 @@ function doctorWorkspace(root, manifest, options = {}) {
|
|
|
56
61
|
const managed = inspectManagedFiles(root, manifest, tools, capabilities, {
|
|
57
62
|
WORKSPACE_LANGUAGE: language || DEFAULT_WORKSPACE_LANGUAGE,
|
|
58
63
|
WORKSPACE_USER_GUIDE: workspaceGuide(language || DEFAULT_WORKSPACE_LANGUAGE),
|
|
59
|
-
});
|
|
64
|
+
}, { coordination: capabilities.includes("coordination") });
|
|
60
65
|
for (const file of [...managed.managedOld, ...managed.replaceable]) {
|
|
61
66
|
add(output, "error", "MANAGED_FILE_OUTDATED", `Managed file is not at the desired version: ${file}`);
|
|
62
67
|
}
|
|
@@ -71,6 +76,21 @@ function doctorWorkspace(root, manifest, options = {}) {
|
|
|
71
76
|
} catch (error) {
|
|
72
77
|
add(output, "error", "MANAGED_FILE_MANIFEST_INVALID", error.message);
|
|
73
78
|
}
|
|
79
|
+
if (capabilities.includes("coordination")) {
|
|
80
|
+
try {
|
|
81
|
+
const coordination = installCoordinationHooks(root, tools, { dryRun: true });
|
|
82
|
+
output.coordinationHooks = coordination;
|
|
83
|
+
for (const entry of coordination.filter((item) => item.action !== "skip")) {
|
|
84
|
+
add(output, "error", "TASK_HOOK_NOT_INSTALLED", `Coordination Hook is not installed for ${entry.tool}: ${entry.target}`, {
|
|
85
|
+
tool: entry.tool,
|
|
86
|
+
target: entry.target,
|
|
87
|
+
remediation: "Run code-w update --coordination and retry.",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
add(output, "error", error.code || "TASK_HOOK_INSPECTION_FAILED", error.message, error.details || {});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
74
94
|
if (inspection.projects.valid && config.projects.length > 0) {
|
|
75
95
|
try {
|
|
76
96
|
output.permissionInspection = inspectProjectPermissions({ root, tools, projects: config.projects });
|
|
@@ -89,7 +109,6 @@ function doctorWorkspace(root, manifest, options = {}) {
|
|
|
89
109
|
}
|
|
90
110
|
}
|
|
91
111
|
|
|
92
|
-
const state = loadState(root);
|
|
93
112
|
if (!options.allowIncompleteState) {
|
|
94
113
|
if (!state || state.status !== "healthy") add(output, "error", "INIT_STATE_UNHEALTHY", "Local initialization state is missing or unhealthy.");
|
|
95
114
|
else {
|
package/src/core/init.js
CHANGED
|
@@ -113,6 +113,7 @@ function installWorkspaceDependencies(root, options = {}) {
|
|
|
113
113
|
|
|
114
114
|
function commitInitializationState(root, manifest, options = {}) {
|
|
115
115
|
const current = loadState(root) || {};
|
|
116
|
+
const coordination = options.coordination === undefined ? current.coordination === true : options.coordination === true;
|
|
116
117
|
const state = {
|
|
117
118
|
...current,
|
|
118
119
|
schemaVersion: 2,
|
|
@@ -120,6 +121,7 @@ function commitInitializationState(root, manifest, options = {}) {
|
|
|
120
121
|
appliedReleaseVersion: manifest.releaseVersion,
|
|
121
122
|
appliedManifestSha256: sha256(fs.readFileSync(options.manifestFile || MANIFEST_FILE)),
|
|
122
123
|
tools: options.tools || manifest.tools,
|
|
124
|
+
coordination,
|
|
123
125
|
};
|
|
124
126
|
delete state.workspaceLanguage;
|
|
125
127
|
saveState(root, state);
|
|
@@ -140,6 +142,7 @@ function commitUpdateState(root, manifest, options = {}) {
|
|
|
140
142
|
appliedReleaseVersion: manifest.releaseVersion,
|
|
141
143
|
appliedManifestSha256: sha256(fs.readFileSync(options.manifestFile || MANIFEST_FILE)),
|
|
142
144
|
...(options.tools !== undefined ? { tools: options.tools } : {}),
|
|
145
|
+
...(options.coordination !== undefined ? { coordination: options.coordination === true } : {}),
|
|
143
146
|
};
|
|
144
147
|
if (options.removeLegacyWorkspaceLanguage === true) delete state.workspaceLanguage;
|
|
145
148
|
saveState(root, state);
|
package/src/core/initializer.js
CHANGED
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
projectConfigPath,
|
|
12
12
|
ensureLocalIgnore,
|
|
13
13
|
loadConfig,
|
|
14
|
+
loadState,
|
|
14
15
|
normalizeConfig,
|
|
15
16
|
resolveProjectConfigPath,
|
|
16
17
|
saveConfig,
|
|
@@ -28,7 +29,7 @@ const {
|
|
|
28
29
|
} = require("./init");
|
|
29
30
|
const { applyPermissionPlan, permissionTargets, planPermissionChanges } = require("./permissions");
|
|
30
31
|
const { installManagedFiles } = require("./managed-files");
|
|
31
|
-
const {
|
|
32
|
+
const { coordinationHookTargets, installCoordinationHooks, removeCoordinationHooks } = require("./task-coordination-managed");
|
|
32
33
|
const { DEFAULT_WORKSPACE_LANGUAGE, workspaceGuide } = require("./language");
|
|
33
34
|
const { planWorkspaceMaintenance } = require("./migration");
|
|
34
35
|
const { createFileTransaction } = require("./transaction");
|
|
@@ -144,12 +145,20 @@ async function initializeWorkspaceStages(rootInput, options = {}) {
|
|
|
144
145
|
const core = installManagedFiles(root, manifest, options.tools, {
|
|
145
146
|
force: options.force === true,
|
|
146
147
|
capabilities,
|
|
148
|
+
coordination: options.coordination === true,
|
|
147
149
|
variables: { WORKSPACE_LANGUAGE: language, WORKSPACE_USER_GUIDE: workspaceGuide(language) },
|
|
148
150
|
});
|
|
151
|
+
const removedCoordination = options.previousCoordination === true
|
|
152
|
+
? removeCoordinationHooks(root, (options.previousTools || []).filter((tool) => !options.tools.includes(tool)))
|
|
153
|
+
: [];
|
|
149
154
|
const coordination = options.coordination === true
|
|
150
|
-
?
|
|
155
|
+
? installCoordinationHooks(root, options.tools, { force: options.force === true })
|
|
151
156
|
: [];
|
|
152
|
-
return [
|
|
157
|
+
return [
|
|
158
|
+
...core,
|
|
159
|
+
...removedCoordination.map((entry) => ({ ...entry, id: `task-coordination-remove-${entry.tool}` })),
|
|
160
|
+
...coordination.map((entry) => ({ ...entry, id: `task-coordination-${entry.tool}` })),
|
|
161
|
+
];
|
|
153
162
|
});
|
|
154
163
|
|
|
155
164
|
const localConfig = await stage("Prepare local workspace configuration", () => {
|
|
@@ -189,6 +198,7 @@ async function initializeWorkspaceStages(rootInput, options = {}) {
|
|
|
189
198
|
manifestFile: options.manifestFile || MANIFEST_FILE,
|
|
190
199
|
tools: options.tools,
|
|
191
200
|
language,
|
|
201
|
+
coordination: options.coordination === true,
|
|
192
202
|
})
|
|
193
203
|
);
|
|
194
204
|
|
|
@@ -219,6 +229,15 @@ async function initializeWorkspaceStages(rootInput, options = {}) {
|
|
|
219
229
|
|
|
220
230
|
async function initializeWorkspace(rootInput, options = {}) {
|
|
221
231
|
const root = path.resolve(rootInput || ".");
|
|
232
|
+
const existingState = loadState(root);
|
|
233
|
+
const coordination = options.coordination === true || existingState?.coordination === true;
|
|
234
|
+
const previousTools = Array.isArray(existingState?.tools) ? existingState.tools : [];
|
|
235
|
+
const removedCoordinationTools = existingState?.coordination === true
|
|
236
|
+
? previousTools.filter((tool) => !options.tools?.includes(tool))
|
|
237
|
+
: [];
|
|
238
|
+
const coordinationHookTools = coordination
|
|
239
|
+
? [...new Set([...(options.tools || []), ...removedCoordinationTools])]
|
|
240
|
+
: removedCoordinationTools;
|
|
222
241
|
const manifest = loadInitManifest(options.manifestFile || MANIFEST_FILE);
|
|
223
242
|
const projectFile = fs.existsSync(configPath(root))
|
|
224
243
|
? resolveProjectConfigPath(root)
|
|
@@ -229,14 +248,20 @@ async function initializeWorkspace(rootInput, options = {}) {
|
|
|
229
248
|
projectFile,
|
|
230
249
|
path.join(root, ".gitignore"),
|
|
231
250
|
path.join(root, ".codex", "config.toml"),
|
|
232
|
-
...(
|
|
251
|
+
...coordinationHookTargets(root, coordinationHookTools),
|
|
233
252
|
...permissionTargets(root, options.tools),
|
|
234
253
|
...manifest.managedFiles.map((entry) => path.join(root, entry.target)),
|
|
235
254
|
...OBSOLETE_ASSETS.map((target) => path.join(root, target)),
|
|
236
255
|
];
|
|
237
256
|
const transaction = createFileTransaction(files);
|
|
238
257
|
try {
|
|
239
|
-
const result = await initializeWorkspaceStages(root, {
|
|
258
|
+
const result = await initializeWorkspaceStages(root, {
|
|
259
|
+
...options,
|
|
260
|
+
coordination,
|
|
261
|
+
previousCoordination: existingState?.coordination === true,
|
|
262
|
+
previousTools,
|
|
263
|
+
transaction,
|
|
264
|
+
});
|
|
240
265
|
transaction.commit();
|
|
241
266
|
return result;
|
|
242
267
|
} catch (error) {
|
|
@@ -8,6 +8,7 @@ const { atomicWrite, sha256 } = require("./fs");
|
|
|
8
8
|
const { DEFAULT_WORKSPACE_LANGUAGE, workspaceGuide } = require("./language");
|
|
9
9
|
const { CODEX_HOOKS_TARGET, composeHookContent } = require("./extension-artifacts");
|
|
10
10
|
const { composeHookDocument } = require("./hooks");
|
|
11
|
+
const { coordinationFragment, mergeHooks } = require("./task-coordination-managed");
|
|
11
12
|
|
|
12
13
|
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
13
14
|
const ARTIFACTS_ROOT = path.join(PACKAGE_ROOT, "artifacts");
|
|
@@ -66,14 +67,34 @@ function hasExtensionHooks(state) {
|
|
|
66
67
|
);
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
function desiredManagedContent(root, entry, variables, extensionState, includeCore = true) {
|
|
70
|
+
function desiredManagedContent(root, entry, variables, extensionState, includeCore = true, options = {}) {
|
|
70
71
|
if (entry.target !== CODEX_HOOKS_TARGET) return renderManagedContent(entry, variables);
|
|
71
72
|
const target = path.join(root, entry.target);
|
|
72
|
-
if (extensionState === null && fs.existsSync(target)) return fs.readFileSync(target);
|
|
73
|
-
|
|
74
|
-
const
|
|
73
|
+
if (extensionState === null && fs.existsSync(target) && options.coordination !== true) return fs.readFileSync(target);
|
|
74
|
+
let base;
|
|
75
|
+
const preserveExisting = options.coordination === true && fs.existsSync(target);
|
|
76
|
+
if (preserveExisting) {
|
|
77
|
+
let existing;
|
|
78
|
+
try { existing = JSON.parse(fs.readFileSync(target, "utf8")); }
|
|
79
|
+
catch (error) { throw new WorkspaceError("MANAGED_FILE_UNKNOWN", `Cannot parse managed Hook target: ${target}: ${error.message}`, { target }); }
|
|
80
|
+
base = includeCore
|
|
81
|
+
? mergeHooks(existing, JSON.parse(renderManagedContent(entry, variables).toString("utf8")), "codex")
|
|
82
|
+
: existing;
|
|
83
|
+
} else {
|
|
84
|
+
base = includeCore ? renderManagedContent(entry, variables) : Buffer.from('{\n "hooks": {}\n}\n');
|
|
85
|
+
}
|
|
86
|
+
if (preserveExisting) {
|
|
87
|
+
const withCoordination = mergeHooks(base, coordinationFragment("codex", root), "codex");
|
|
88
|
+
return Buffer.from(`${JSON.stringify(withCoordination, null, 2)}\n`);
|
|
89
|
+
}
|
|
90
|
+
const legacy = composeHookContent(
|
|
91
|
+
typeof base === "string" || Buffer.isBuffer(base) ? base : JSON.stringify(base),
|
|
92
|
+
extensionState || { extensions: {} }
|
|
93
|
+
);
|
|
75
94
|
const composed = composeHookDocument(legacy, "codex", { extensions: {} }, extensionState || { extensions: {} });
|
|
76
|
-
return Buffer.from(`${JSON.stringify(composed, null, 2)}\n`);
|
|
95
|
+
if (options.coordination !== true) return Buffer.from(`${JSON.stringify(composed, null, 2)}\n`);
|
|
96
|
+
const withCoordination = mergeHooks(composed, coordinationFragment("codex", root), "codex");
|
|
97
|
+
return Buffer.from(`${JSON.stringify(withCoordination, null, 2)}\n`);
|
|
77
98
|
}
|
|
78
99
|
|
|
79
100
|
function resolveArtifact(relativePath) {
|
|
@@ -196,12 +217,19 @@ function previousInstalledSha(state, entry) {
|
|
|
196
217
|
return previous?.installedSha256 || previous?.sha256 || null;
|
|
197
218
|
}
|
|
198
219
|
|
|
199
|
-
function classifyManagedFile(root, entry, state, variables = {}, extensionState, includeCore = true) {
|
|
220
|
+
function classifyManagedFile(root, entry, state, variables = {}, extensionState, includeCore = true, options = {}) {
|
|
200
221
|
const target = path.join(root, entry.target);
|
|
201
|
-
const desiredSha256 = sha256(desiredManagedContent(root, entry, variables, extensionState, includeCore));
|
|
222
|
+
const desiredSha256 = sha256(desiredManagedContent(root, entry, variables, extensionState, includeCore, options));
|
|
202
223
|
if (!fs.existsSync(target)) return { state: "missing", target, sha256: null, desiredSha256 };
|
|
203
224
|
const actualSha256 = sha256(fs.readFileSync(target));
|
|
204
225
|
if (actualSha256 === desiredSha256) return { state: "current", target, sha256: actualSha256, desiredSha256 };
|
|
226
|
+
// Provider Hook documents are shared with the user. When coordination is
|
|
227
|
+
// enabled, the desired document is built by merging managed contributions
|
|
228
|
+
// into the current document, so arbitrary user entries must not be treated
|
|
229
|
+
// as an unknown managed-file overwrite.
|
|
230
|
+
if (entry.target === CODEX_HOOKS_TARGET && options.coordination === true) {
|
|
231
|
+
return { state: "replaceable", target, sha256: actualSha256, desiredSha256 };
|
|
232
|
+
}
|
|
205
233
|
if (actualSha256 === previousInstalledSha(state, entry)) return { state: "managed-old", target, sha256: actualSha256, desiredSha256 };
|
|
206
234
|
if ((entry.replaceable || []).some((accepted) => accepted.sha256 === actualSha256)) {
|
|
207
235
|
return { state: "replaceable", target, sha256: actualSha256, desiredSha256 };
|
|
@@ -218,7 +246,7 @@ function inspectManagedFiles(root, manifest, tools, capabilities = [], variables
|
|
|
218
246
|
if (hookEntry && (hasExtensionHooks(hooksState) || (hooksState === null && fs.existsSync(path.join(root, CODEX_HOOKS_TARGET))))) selected.add(hookEntry.id);
|
|
219
247
|
const output = { current: [], managedOld: [], replaceable: [], missing: [], unknown: [], files: [] };
|
|
220
248
|
for (const entry of manifest.managedFiles.filter((entry) => selected.has(entry.id))) {
|
|
221
|
-
const classified = classifyManagedFile(root, entry, state, variables, hooksState, coreSelected.has(entry.id));
|
|
249
|
+
const classified = classifyManagedFile(root, entry, state, variables, hooksState, coreSelected.has(entry.id), options);
|
|
222
250
|
const key = classified.state === "managed-old" ? "managedOld" : classified.state;
|
|
223
251
|
output[key].push(entry.target);
|
|
224
252
|
output.files.push({
|
|
@@ -244,6 +272,19 @@ function planManagedFiles(root, manifest, tools, options = {}) {
|
|
|
244
272
|
for (const entry of manifest.managedFiles) {
|
|
245
273
|
if (!selected.has(entry.id)) {
|
|
246
274
|
const previousSha = previousInstalledSha(state, entry);
|
|
275
|
+
if (entry.target === CODEX_HOOKS_TARGET && options.coordination === true) {
|
|
276
|
+
if (previousSha) {
|
|
277
|
+
plans.push({
|
|
278
|
+
entry,
|
|
279
|
+
target: path.join(root, entry.target),
|
|
280
|
+
action: "forget",
|
|
281
|
+
previous: null,
|
|
282
|
+
reason: "coordination-managed",
|
|
283
|
+
desiredSha256: null,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
247
288
|
if (!previousSha) continue;
|
|
248
289
|
const target = path.join(root, entry.target);
|
|
249
290
|
const exists = fs.existsSync(target);
|
|
@@ -261,7 +302,7 @@ function planManagedFiles(root, manifest, tools, options = {}) {
|
|
|
261
302
|
});
|
|
262
303
|
continue;
|
|
263
304
|
}
|
|
264
|
-
const classified = classifyManagedFile(root, entry, state, options.variables, hooksState, coreSelected.has(entry.id));
|
|
305
|
+
const classified = classifyManagedFile(root, entry, state, options.variables, hooksState, coreSelected.has(entry.id), options);
|
|
265
306
|
if (classified.state === "current") {
|
|
266
307
|
plans.push({ entry, target: classified.target, action: "skip", previous: null, reason: "current", desiredSha256: classified.desiredSha256 });
|
|
267
308
|
continue;
|
|
@@ -278,7 +319,7 @@ function planManagedFiles(root, manifest, tools, options = {}) {
|
|
|
278
319
|
action: "write",
|
|
279
320
|
reason: classified.state,
|
|
280
321
|
previous: fs.existsSync(classified.target) ? fs.readFileSync(classified.target) : null,
|
|
281
|
-
content: desiredManagedContent(root, entry, options.variables, hooksState, coreSelected.has(entry.id)),
|
|
322
|
+
content: desiredManagedContent(root, entry, options.variables, hooksState, coreSelected.has(entry.id), options),
|
|
282
323
|
desiredSha256: classified.desiredSha256,
|
|
283
324
|
});
|
|
284
325
|
}
|