agentic-workflow-manager 3.1.0 → 3.2.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/dist/src/commands/add.js +84 -0
- package/dist/src/commands/agent.js +95 -0
- package/dist/src/commands/backup.js +46 -0
- package/dist/src/commands/doctor.js +80 -4
- package/dist/src/commands/hooks/claude.js +192 -0
- package/dist/src/commands/hooks/codex.js +191 -0
- package/dist/src/commands/hooks/index.js +23 -8
- package/dist/src/commands/hooks/install.js +11 -107
- package/dist/src/commands/hooks/resync.js +28 -18
- package/dist/src/commands/hooks/shared.js +94 -0
- package/dist/src/commands/hooks/status.js +13 -64
- package/dist/src/commands/hooks/uninstall.js +11 -47
- package/dist/src/commands/init.js +90 -28
- package/dist/src/commands/registry/index.js +1 -1
- package/dist/src/commands/sync.js +113 -0
- package/dist/src/commands/update.js +107 -0
- package/dist/src/core/agent-targets.js +50 -0
- package/dist/src/core/artifact-state.js +65 -0
- package/dist/src/core/atomic-file.js +72 -0
- package/dist/src/core/bundle-install.js +60 -38
- package/dist/src/core/bundles.js +21 -0
- package/dist/src/core/context/managed-block.js +190 -0
- package/dist/src/core/context/orchestrator.js +3 -1
- package/dist/src/core/context/project-constitution-inject.js +1 -1
- package/dist/src/core/context/regenerate.js +3 -3
- package/dist/src/core/context/strategies/codex-agents.js +100 -0
- package/dist/src/core/diagnostics/checks.js +16 -0
- package/dist/src/core/diagnostics/context.js +37 -8
- package/dist/src/core/diagnostics/provider-checks.js +219 -0
- package/dist/src/core/executor.js +30 -9
- package/dist/src/core/init/mutation-targets.js +139 -0
- package/dist/src/core/init/provider-facts.js +164 -0
- package/dist/src/core/init/steps.js +65 -9
- package/dist/src/core/install-planner.js +206 -0
- package/dist/src/core/install-transaction.js +405 -0
- package/dist/src/core/profile.js +1 -1
- package/dist/src/core/provider-artifacts.js +54 -0
- package/dist/src/core/provider-version.js +36 -0
- package/dist/src/core/reconciliation.js +61 -0
- package/dist/src/core/registries.js +10 -0
- package/dist/src/core/renderers/canonical-agent.js +27 -0
- package/dist/src/core/renderers/codex-agent.js +60 -0
- package/dist/src/core/skill-integrity.js +4 -3
- package/dist/src/index.js +97 -289
- package/dist/src/providers/index.js +143 -39
- package/dist/src/ui/provider-preflight.js +26 -0
- package/dist/src/utils/config.js +101 -9
- package/dist/tests/commands/agent.test.js +103 -0
- package/dist/tests/commands/backup.test.js +109 -0
- package/dist/tests/commands/doctor.test.js +65 -4
- package/dist/tests/commands/hooks/codex.test.js +230 -0
- package/dist/tests/commands/hooks/install.test.js +20 -1
- package/dist/tests/commands/hooks/resync.test.js +53 -6
- package/dist/tests/commands/hooks/status.test.js +10 -0
- package/dist/tests/commands/hooks/uninstall.test.js +12 -0
- package/dist/tests/commands/init.test.js +195 -8
- package/dist/tests/commands/multi-agent-targeting.test.js +231 -0
- package/dist/tests/core/agent-targets.test.js +45 -0
- package/dist/tests/core/artifact-state.test.js +144 -0
- package/dist/tests/core/atomic-file.test.js +96 -0
- package/dist/tests/core/bundle-install.test.js +208 -17
- package/dist/tests/core/bundles.test.js +12 -0
- package/dist/tests/core/context/managed-block.test.js +90 -0
- package/dist/tests/core/context/orchestrator.test.js +48 -3
- package/dist/tests/core/context/regenerate.test.js +1 -1
- package/dist/tests/core/context/strategies/codex-agents.test.js +185 -0
- package/dist/tests/core/context/strategies/config-instructions.test.js +1 -1
- package/dist/tests/core/diagnostics/checks.test.js +19 -0
- package/dist/tests/core/diagnostics/context.test.js +74 -0
- package/dist/tests/core/diagnostics/provider-checks.test.js +252 -0
- package/dist/tests/core/executor.test.js +20 -0
- package/dist/tests/core/export/engine.test.js +3 -2
- package/dist/tests/core/init/mutation-targets.test.js +235 -0
- package/dist/tests/core/init/orchestrator.test.js +1 -1
- package/dist/tests/core/init/provider-facts.test.js +129 -0
- package/dist/tests/core/init/steps.test.js +106 -2
- package/dist/tests/core/install-planner.test.js +224 -0
- package/dist/tests/core/install-transaction.test.js +257 -0
- package/dist/tests/core/provider-artifacts.test.js +64 -0
- package/dist/tests/core/provider-version.test.js +53 -0
- package/dist/tests/core/reconciliation.test.js +165 -0
- package/dist/tests/core/registries-sync.test.js +4 -4
- package/dist/tests/core/renderers/canonical-agent.test.js +58 -0
- package/dist/tests/core/renderers/codex-agent.test.js +128 -0
- package/dist/tests/core/versioning.test.js +1 -1
- package/dist/tests/integration/codex-provider-isolated.test.js +206 -0
- package/dist/tests/providers/hooks-config.test.js +43 -25
- package/dist/tests/providers/index.test.js +144 -39
- package/dist/tests/structural/codex-agent-escaping-completeness.test.js +77 -0
- package/dist/tests/ui/provider-preflight.test.js +25 -0
- package/dist/tests/utils/config.test.js +178 -18
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@ const profile_1 = require("../profile");
|
|
|
16
16
|
const bundles_1 = require("../bundles");
|
|
17
17
|
const skill_integrity_1 = require("../skill-integrity");
|
|
18
18
|
const paths_1 = require("../paths");
|
|
19
|
+
const provider_checks_1 = require("./provider-checks");
|
|
19
20
|
// Estado de un artefacto en <dir>/<skill>: link vivo / symlink colgante / ausente.
|
|
20
21
|
function linkState(dir, skill) {
|
|
21
22
|
const p = path_1.default.join(dir, skill);
|
|
@@ -44,12 +45,12 @@ function classifyLinks(skillNames, dir) {
|
|
|
44
45
|
}
|
|
45
46
|
function detectGitState(repoDir) {
|
|
46
47
|
try {
|
|
47
|
-
const porcelain = (0, child_process_1.
|
|
48
|
+
const porcelain = (0, child_process_1.execFileSync)('git', ['status', '--porcelain'], { cwd: repoDir, stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' })
|
|
48
49
|
.toString().trim();
|
|
49
50
|
if (porcelain.length > 0)
|
|
50
51
|
return 'dirty';
|
|
51
52
|
try {
|
|
52
|
-
const behind = (0, child_process_1.
|
|
53
|
+
const behind = (0, child_process_1.execFileSync)('git', ['rev-list', '--count', 'HEAD..@{u}'], { cwd: repoDir, stdio: ['ignore', 'pipe', 'ignore'], timeout: 3000, encoding: 'utf8' })
|
|
53
54
|
.toString().trim();
|
|
54
55
|
if (behind !== '' && behind !== '0')
|
|
55
56
|
return 'behind';
|
|
@@ -67,8 +68,8 @@ function detectGitState(repoDir) {
|
|
|
67
68
|
function gatherContextInjection() {
|
|
68
69
|
const out = [];
|
|
69
70
|
const orch = new orchestrator_1.InjectionOrchestrator();
|
|
70
|
-
for (const agent of
|
|
71
|
-
const inj = providers_1.
|
|
71
|
+
for (const agent of providers_1.AGENT_TARGETS) {
|
|
72
|
+
const inj = (0, providers_1.providerFor)(agent).injection;
|
|
72
73
|
if (!inj || inj.type !== 'config-instructions')
|
|
73
74
|
continue;
|
|
74
75
|
if (!fs_1.default.existsSync(inj.configPath))
|
|
@@ -96,17 +97,22 @@ function gatherMachine(bundles, agent = 'claude-code') {
|
|
|
96
97
|
const first = regs[0];
|
|
97
98
|
const cachePresent = !!first && fs_1.default.existsSync(path_1.default.join(first.contentRoot, '.git'));
|
|
98
99
|
const gitState = cachePresent ? detectGitState(first.contentRoot) : undefined;
|
|
99
|
-
// hook (reutiliza computeHookStatus)
|
|
100
|
+
// hook (reutiliza computeHookStatus) — Task 9 fix: this used to be hardcoded to
|
|
101
|
+
// 'claude-code' regardless of `agent`, so `awm init --agent codex`'s own hook
|
|
102
|
+
// precondition silently read CLAUDE's hook status instead of Codex's. Whenever
|
|
103
|
+
// Claude's hook was already installed, stepHook would then wrongly conclude
|
|
104
|
+
// Codex's hook was "already present" and skip installing it (R18 regression —
|
|
105
|
+
// caught while building this task's real end-to-end init test).
|
|
100
106
|
let hookPresent = false;
|
|
101
107
|
let hookDegraded = false;
|
|
102
108
|
try {
|
|
103
|
-
const hs = (0, status_1.computeHookStatus)(
|
|
109
|
+
const hs = (0, status_1.computeHookStatus)(agent);
|
|
104
110
|
hookPresent = hs.checks.settingsEntry.ok;
|
|
105
111
|
hookDegraded = hs.overall === 'DEGRADED';
|
|
106
112
|
}
|
|
107
113
|
catch { /* sin soporte de hooks → ausente */ }
|
|
108
114
|
// devCore (bundle baseline)
|
|
109
|
-
const skillsDir = providers_1.
|
|
115
|
+
const skillsDir = (0, providers_1.providerFor)(agent).skill.global;
|
|
110
116
|
const baseline = bundles.find((b) => b.scope === 'baseline');
|
|
111
117
|
let devCorePresent = false;
|
|
112
118
|
let brokenLinks = [];
|
|
@@ -116,6 +122,26 @@ function gatherMachine(bundles, agent = 'claude-code') {
|
|
|
116
122
|
const absent = skillNames.filter((s) => !linked.includes(s) && !broken.includes(s));
|
|
117
123
|
devCorePresent = skillNames.length > 0 && (linked.length + broken.length) > 0;
|
|
118
124
|
brokenLinks = [...broken, ...absent];
|
|
125
|
+
// Agent-type artifacts are never shared across agents (R12/R13 —
|
|
126
|
+
// install-planner.ts — unlike skills, where OpenCode and Codex both
|
|
127
|
+
// resolve to ~/.agents/skills). A shared skill directory already
|
|
128
|
+
// linked by a co-owner agent must not make THIS agent's own native
|
|
129
|
+
// agent artifacts look "present" when they were never installed for
|
|
130
|
+
// it specifically — otherwise stepDevCore (init/steps.ts) would skip
|
|
131
|
+
// re-running and Codex would never get its .toml rendered on a
|
|
132
|
+
// machine where OpenCode was initialized first. Found while building
|
|
133
|
+
// the real Codex+OpenCode coexistence E2E test
|
|
134
|
+
// (tests/integration/codex-provider-isolated.test.ts).
|
|
135
|
+
const agentConfig = (0, providers_1.providerFor)(agent).agent;
|
|
136
|
+
if (agentConfig) {
|
|
137
|
+
const agentNames = (0, bundles_1.resolveBundleAgents)(baseline.name, bundles);
|
|
138
|
+
if (agentNames.length > 0) {
|
|
139
|
+
const filenames = agentNames.map((n) => agentConfig.renderer === 'codex-agent-toml' ? `${n}.toml` : n);
|
|
140
|
+
const { linked: agentLinked, broken: agentBroken } = classifyLinks(filenames, agentConfig.global);
|
|
141
|
+
const agentAbsent = filenames.filter((f) => !agentLinked.includes(f) && !agentBroken.includes(f));
|
|
142
|
+
brokenLinks = [...brokenLinks, ...agentBroken, ...agentAbsent];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
119
145
|
}
|
|
120
146
|
// ambient (deseados desde ~/.awm/config.json)
|
|
121
147
|
let wanted = [];
|
|
@@ -145,7 +171,7 @@ function gatherMachine(bundles, agent = 'claude-code') {
|
|
|
145
171
|
function gatherProject(root, bundles, agent = 'claude-code') {
|
|
146
172
|
const profile = (0, profile_1.readProfile)(root);
|
|
147
173
|
const profilePresent = fs_1.default.existsSync(path_1.default.join(root, '.awm', 'profile.json'));
|
|
148
|
-
const localSkillsDir = path_1.default.join(root, providers_1.
|
|
174
|
+
const localSkillsDir = path_1.default.join(root, (0, providers_1.providerFor)(agent).skill.local);
|
|
149
175
|
const expected = [];
|
|
150
176
|
for (const ext of profile.extensions) {
|
|
151
177
|
for (const s of (0, bundles_1.resolveBundleSkills)(ext, bundles))
|
|
@@ -172,8 +198,11 @@ function gatherContext(opts = {}) {
|
|
|
172
198
|
const bundles = opts.bundles ?? (0, bundles_1.discoverAllBundles)();
|
|
173
199
|
const agent = opts.agent ?? 'claude-code';
|
|
174
200
|
const root = (0, profile_1.findProjectRoot)(cwd);
|
|
201
|
+
const agents = opts.agents ?? [agent];
|
|
202
|
+
const scanSkills = opts.scanSkills ?? ((dir) => (0, skill_integrity_1.classifyGlobalSkills)(dir, (0, registries_1.contentRoots)()));
|
|
175
203
|
return {
|
|
176
204
|
machine: gatherMachine(bundles, agent),
|
|
177
205
|
project: root ? gatherProject(root, bundles, agent) : null,
|
|
206
|
+
providers: (0, provider_checks_1.gatherProviderChecks)(agents, scanSkills),
|
|
178
207
|
};
|
|
179
208
|
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.gatherProviderChecks = gatherProviderChecks;
|
|
7
|
+
// src/core/diagnostics/provider-checks.ts
|
|
8
|
+
//
|
|
9
|
+
// Builds the per-provider diagnostic matrix consumed by `awm doctor`
|
|
10
|
+
// (Task 9). Kept in its own module (rather than growing context.ts or
|
|
11
|
+
// checks.ts) because it has a distinct concern: turning raw provider state
|
|
12
|
+
// (binary version, skill/agent dirs, hook trust, context injection) into a
|
|
13
|
+
// small set of stable, JSON-friendly `ProviderCheck` rows — one per
|
|
14
|
+
// provider, deduplicated for physical directories shared between providers
|
|
15
|
+
// (today: OpenCode and Codex both read/write ~/.agents/skills).
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const providers_1 = require("../../providers");
|
|
19
|
+
const provider_version_1 = require("../provider-version");
|
|
20
|
+
const status_1 = require("../../commands/hooks/status");
|
|
21
|
+
const orchestrator_1 = require("../context/orchestrator");
|
|
22
|
+
const registries_1 = require("../registries");
|
|
23
|
+
function binaryVersionCheck(agent) {
|
|
24
|
+
const provider = (0, providers_1.providerFor)(agent);
|
|
25
|
+
if (!provider.versionCommand || !provider.minimumVersion) {
|
|
26
|
+
// No version gate for this agent — nothing to enforce, treat as supported.
|
|
27
|
+
return { id: 'binary.version', state: 'supported' };
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const { version } = (0, provider_version_1.assertProviderSupported)(agent);
|
|
31
|
+
return { id: 'binary.version', state: 'supported', target: version ?? undefined };
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
const message = err.message;
|
|
35
|
+
const missing = /not installed|not available on PATH/i.test(message);
|
|
36
|
+
return {
|
|
37
|
+
id: 'binary.version',
|
|
38
|
+
state: missing ? 'missing' : 'unsupported',
|
|
39
|
+
detail: message,
|
|
40
|
+
remediationCode: missing ? 'install-provider-binary' : 'upgrade-provider-binary',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function skillsGlobalCheck(dir, owners, integrity) {
|
|
45
|
+
const shared = owners.length > 1;
|
|
46
|
+
const broken = integrity.repairable.length + integrity.dead.length;
|
|
47
|
+
// Broken links are checked BEFORE shared: 'shared' is a non-degrading/OK state
|
|
48
|
+
// (see checks.ts's DEGRADING_PROVIDER_STATES), so if it were set unconditionally
|
|
49
|
+
// for a shared dir it would silently mask real broken/dead symlinks — a green
|
|
50
|
+
// checkmark next to "N broken links → repair-global-skills" would contradict its
|
|
51
|
+
// own trailing text, and `overall` would never degrade despite real breakage.
|
|
52
|
+
let state;
|
|
53
|
+
if (broken > 0) {
|
|
54
|
+
state = 'broken';
|
|
55
|
+
}
|
|
56
|
+
else if (shared) {
|
|
57
|
+
state = 'shared';
|
|
58
|
+
}
|
|
59
|
+
else if (!fs_1.default.existsSync(dir)) {
|
|
60
|
+
state = 'absent';
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
state = 'healthy';
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
id: 'skills.global',
|
|
67
|
+
state,
|
|
68
|
+
target: dir,
|
|
69
|
+
owners: shared ? owners : undefined,
|
|
70
|
+
detail: broken > 0 ? `${broken} broken links` : undefined,
|
|
71
|
+
remediationCode: broken > 0 ? 'repair-global-skills' : undefined,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** R8: verify the Codex `.toml` agents this run's renderer would have produced still parse. */
|
|
75
|
+
function tomlAgentsHealthy(dir, entries) {
|
|
76
|
+
const tomls = entries.filter((e) => e.endsWith('.toml'));
|
|
77
|
+
const broken = tomls.filter((file) => {
|
|
78
|
+
try {
|
|
79
|
+
const content = fs_1.default.readFileSync(path_1.default.join(dir, file), 'utf8');
|
|
80
|
+
// renderCodexAgent (Task 4) always emits this key — its absence means the
|
|
81
|
+
// file was hand-edited or truncated, not a shape `awm` would have written.
|
|
82
|
+
return !content.includes('developer_instructions = ');
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}).length;
|
|
88
|
+
return { broken };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Returns `null` when a check doesn't structurally apply to `agent` (e.g. Antigravity
|
|
92
|
+
* has no `agent`/`hooks`/`injection` config; OpenCode has no hooks; Claude Code's global
|
|
93
|
+
* context rides its SessionStart hook, already covered by hook.trust). `null` rows are
|
|
94
|
+
* dropped by `gatherProviderChecks` — omitted entirely, not rendered as a failure. This
|
|
95
|
+
* is deliberately distinct from `state: 'unsupported'`, which `binaryVersionCheck` still
|
|
96
|
+
* uses for a genuine failure (an installed CLI version below the required minimum) — that
|
|
97
|
+
* state correctly degrades `overall` (see checks.ts's DEGRADING_PROVIDER_STATES) and must
|
|
98
|
+
* never be reused to mean "not applicable", or a fully-healthy single-provider `awm doctor`
|
|
99
|
+
* run (e.g. claude-code-only, opencode-only, antigravity-only) would always render
|
|
100
|
+
* inapplicable rows as red ✖ and `overall` could never be 'healthy'.
|
|
101
|
+
*/
|
|
102
|
+
function agentsNativeCheck(agent) {
|
|
103
|
+
const provider = (0, providers_1.providerFor)(agent);
|
|
104
|
+
if (!provider.agent)
|
|
105
|
+
return null;
|
|
106
|
+
const dir = provider.agent.global;
|
|
107
|
+
let entries;
|
|
108
|
+
try {
|
|
109
|
+
entries = fs_1.default.readdirSync(dir);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return { id: 'agents.native', state: 'absent', target: dir };
|
|
113
|
+
}
|
|
114
|
+
if (entries.length === 0)
|
|
115
|
+
return { id: 'agents.native', state: 'absent', target: dir };
|
|
116
|
+
if (provider.agent.renderer === 'codex-agent-toml') {
|
|
117
|
+
const { broken } = tomlAgentsHealthy(dir, entries);
|
|
118
|
+
return {
|
|
119
|
+
id: 'agents.native',
|
|
120
|
+
state: broken > 0 ? 'broken' : 'healthy',
|
|
121
|
+
target: dir,
|
|
122
|
+
detail: broken > 0 ? `${broken} malformed .toml` : undefined,
|
|
123
|
+
remediationCode: broken > 0 ? 'reinstall-native-agents' : undefined,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return { id: 'agents.native', state: 'healthy', target: dir };
|
|
127
|
+
}
|
|
128
|
+
function hookTrustCheck(agent) {
|
|
129
|
+
const provider = (0, providers_1.providerFor)(agent);
|
|
130
|
+
if (!provider.hooks)
|
|
131
|
+
return null;
|
|
132
|
+
let status;
|
|
133
|
+
try {
|
|
134
|
+
status = (0, status_1.computeHookStatus)(agent);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return { id: 'hook.trust', state: 'absent', remediationCode: 'awm-init' };
|
|
138
|
+
}
|
|
139
|
+
if (status.overall === 'NOT_INSTALLED') {
|
|
140
|
+
return { id: 'hook.trust', state: 'absent', remediationCode: 'awm-init' };
|
|
141
|
+
}
|
|
142
|
+
if (status.trust) {
|
|
143
|
+
// Codex: 'pending-trust' | 'healthy' | 'stale' map directly onto ProviderCheckState.
|
|
144
|
+
return {
|
|
145
|
+
id: 'hook.trust',
|
|
146
|
+
state: status.trust,
|
|
147
|
+
remediationCode: status.trust === 'pending-trust' ? 'open-hooks-trust' : undefined,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return { id: 'hook.trust', state: status.overall === 'HEALTHY' ? 'healthy' : 'broken' };
|
|
151
|
+
}
|
|
152
|
+
/** R7: reflects the provider's global context-delivery mechanism (config-instructions /
|
|
153
|
+
* managed-agents-md). claude-code's context rides the SessionStart hook — already
|
|
154
|
+
* covered by hook.trust, so this check is OMITTED (returns null) rather than reported,
|
|
155
|
+
* to avoid double-reporting the same fact as a separate, redundant row. */
|
|
156
|
+
function contextGlobalCheck(agent) {
|
|
157
|
+
const injection = (0, providers_1.providerFor)(agent).injection;
|
|
158
|
+
if (!injection || injection.type === 'cc-settings-merge') {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
let state = 'absent';
|
|
162
|
+
try {
|
|
163
|
+
const orchestrator = new orchestrator_1.InjectionOrchestrator();
|
|
164
|
+
const result = orchestrator.contextStatus({
|
|
165
|
+
agent,
|
|
166
|
+
scope: 'global',
|
|
167
|
+
registryRoot: (0, registries_1.capabilityRoot)('skills') ?? '',
|
|
168
|
+
installMethod: 'symlink',
|
|
169
|
+
profileExtensions: [],
|
|
170
|
+
});
|
|
171
|
+
state = result === 'injected' ? 'delivered' : result === 'stale' ? 'stale' : 'absent';
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
state = 'absent';
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
id: 'context.global',
|
|
178
|
+
state,
|
|
179
|
+
remediationCode: state === 'delivered' ? undefined : 'awm-init',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Builds one `ProviderFacts` row per requested agent. Physical skill directories shared
|
|
184
|
+
* between providers (OpenCode + Codex both use `~/.agents/skills`) are scanned exactly
|
|
185
|
+
* once via `scanSkills` and every owner's `skills.global` check is marked `state: 'shared'`
|
|
186
|
+
* — mirrors the dedup principle `install-planner.ts` (Task 5) already applies to writes.
|
|
187
|
+
*
|
|
188
|
+
* Named `gatherProviderChecks` (not `gatherProviderFacts`) to avoid colliding with the
|
|
189
|
+
* unrelated `gatherProviderFacts` in `core/init/provider-facts.ts` (Task 8, baseline-hash
|
|
190
|
+
* snapshot for rollback comparison — a different shape, a different purpose). No file
|
|
191
|
+
* currently imports both, but the names are close enough to trip up a future reader/editor.
|
|
192
|
+
*/
|
|
193
|
+
function gatherProviderChecks(agents, scanSkills) {
|
|
194
|
+
const ownersByDir = new Map();
|
|
195
|
+
for (const agent of agents) {
|
|
196
|
+
const dir = (0, providers_1.providerFor)(agent).skill.global;
|
|
197
|
+
const owners = ownersByDir.get(dir) ?? [];
|
|
198
|
+
owners.push(agent);
|
|
199
|
+
ownersByDir.set(dir, owners);
|
|
200
|
+
}
|
|
201
|
+
const scansByDir = new Map();
|
|
202
|
+
for (const dir of ownersByDir.keys()) {
|
|
203
|
+
scansByDir.set(dir, scanSkills(dir));
|
|
204
|
+
}
|
|
205
|
+
return agents.map((agent) => {
|
|
206
|
+
const provider = (0, providers_1.providerFor)(agent);
|
|
207
|
+
const dir = provider.skill.global;
|
|
208
|
+
const owners = ownersByDir.get(dir) ?? [agent];
|
|
209
|
+
const integrity = scansByDir.get(dir) ?? { valid: [], repairable: [], dead: [] };
|
|
210
|
+
const checks = [
|
|
211
|
+
binaryVersionCheck(agent),
|
|
212
|
+
skillsGlobalCheck(dir, owners, integrity),
|
|
213
|
+
agentsNativeCheck(agent),
|
|
214
|
+
hookTrustCheck(agent),
|
|
215
|
+
contextGlobalCheck(agent),
|
|
216
|
+
].filter((check) => check !== null);
|
|
217
|
+
return { id: agent, label: provider.label, checks };
|
|
218
|
+
});
|
|
219
|
+
}
|
|
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.removeArtifact = removeArtifact;
|
|
7
|
+
exports.stageArtifact = stageArtifact;
|
|
8
|
+
exports.replaceArtifact = replaceArtifact;
|
|
7
9
|
exports.installArtifact = installArtifact;
|
|
8
10
|
// src/core/executor.ts
|
|
9
11
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -22,20 +24,39 @@ function removeArtifact(targetPath) {
|
|
|
22
24
|
}
|
|
23
25
|
fs_1.default.rmSync(targetPath, { recursive: true, force: true });
|
|
24
26
|
}
|
|
25
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Stages `sourcePath` next to `targetPath` (same parent directory) WITHOUT
|
|
29
|
+
* touching `targetPath` itself (R17): the live target is never removed until
|
|
30
|
+
* a stage has already succeeded. Returns the staged path, ready to be swapped
|
|
31
|
+
* in via `replaceArtifact`.
|
|
32
|
+
*/
|
|
33
|
+
function stageArtifact(sourcePath, targetPath, method) {
|
|
26
34
|
if (!fs_1.default.existsSync(sourcePath)) {
|
|
27
35
|
throw new Error(`Source path does not exist: ${sourcePath}`);
|
|
28
36
|
}
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
// Clean up existing if it exists
|
|
34
|
-
fs_1.default.rmSync(targetPath, { recursive: true, force: true });
|
|
37
|
+
const parent = path_1.default.dirname(targetPath);
|
|
38
|
+
fs_1.default.mkdirSync(parent, { recursive: true });
|
|
39
|
+
const staged = path_1.default.join(parent, `.${path_1.default.basename(targetPath)}.${process.pid}.staged`);
|
|
40
|
+
fs_1.default.rmSync(staged, { recursive: true, force: true });
|
|
35
41
|
if (method === 'symlink') {
|
|
36
|
-
fs_1.default.symlinkSync(sourcePath,
|
|
42
|
+
fs_1.default.symlinkSync(sourcePath, staged, 'dir');
|
|
37
43
|
}
|
|
38
44
|
else {
|
|
39
|
-
fs_1.default.cpSync(sourcePath,
|
|
45
|
+
fs_1.default.cpSync(sourcePath, staged, { recursive: true });
|
|
40
46
|
}
|
|
47
|
+
return staged;
|
|
48
|
+
}
|
|
49
|
+
/** Atomically swaps a staged artifact into `targetPath`, replacing whatever is there. */
|
|
50
|
+
function replaceArtifact(staged, targetPath) {
|
|
51
|
+
fs_1.default.rmSync(targetPath, { recursive: true, force: true });
|
|
52
|
+
fs_1.default.renameSync(staged, targetPath);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Convenience wrapper over `stageArtifact` + `replaceArtifact` for callers
|
|
56
|
+
* that don't need transactional multi-target coordination (see
|
|
57
|
+
* install-transaction.ts's `applyInstallPlan` for the transactional path).
|
|
58
|
+
*/
|
|
59
|
+
function installArtifact(sourcePath, targetPath, method) {
|
|
60
|
+
const staged = stageArtifact(sourcePath, targetPath, method);
|
|
61
|
+
replaceArtifact(staged, targetPath);
|
|
41
62
|
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.planInitMutationTargets = planInitMutationTargets;
|
|
7
|
+
// src/core/init/mutation-targets.ts
|
|
8
|
+
//
|
|
9
|
+
// Enumerates, WITHOUT writing anything, every filesystem path a real `awm
|
|
10
|
+
// init` run for a given agent is about to touch. `runInit` feeds this list to
|
|
11
|
+
// `beginBackupSession` (install-transaction.ts) BEFORE any step runs, so a
|
|
12
|
+
// failed init can roll every one of them back to its pre-run state.
|
|
13
|
+
//
|
|
14
|
+
// Deliberately generous rather than exhaustively precise: a path that ends up
|
|
15
|
+
// unused this run (e.g. an ambient bundle whose signal isn't present) is
|
|
16
|
+
// harmless to include — beginBackupSession just snapshots it (a no-op backup
|
|
17
|
+
// entry when nothing exists there yet). The one failure mode that matters is
|
|
18
|
+
// under-enumeration: a real write to a path NOT in this list would land
|
|
19
|
+
// outside the backup session and survive a rollback.
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const providers_1 = require("../../providers");
|
|
22
|
+
const bundles_1 = require("../bundles");
|
|
23
|
+
const bundle_install_1 = require("../bundle-install");
|
|
24
|
+
const install_planner_1 = require("../install-planner");
|
|
25
|
+
const artifact_state_1 = require("../artifact-state");
|
|
26
|
+
const paths_1 = require("../paths");
|
|
27
|
+
const profile_1 = require("../profile");
|
|
28
|
+
const registries_1 = require("../registries");
|
|
29
|
+
// Thin wrapper over install-planner.ts's `physicalTarget` (the single source
|
|
30
|
+
// of truth for the dir+filename computation, including the `.toml` rename for
|
|
31
|
+
// codex-agent-toml) — adapts its throw-on-unsupported contract to this
|
|
32
|
+
// module's null-on-unsupported one, and drops the `renderer` field this
|
|
33
|
+
// module's callers never needed.
|
|
34
|
+
function physicalTarget(type, agent, scope, installName, projectRoot) {
|
|
35
|
+
try {
|
|
36
|
+
return (0, install_planner_1.physicalTarget)({ name: installName, installName, type, sourcePath: '' }, agent, scope, projectRoot ?? '').targetPath;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function addBundleTargets(targets, bundleName, bundles, agent, scope, projectRoot, contentDir) {
|
|
43
|
+
let intents;
|
|
44
|
+
try {
|
|
45
|
+
intents = (0, bundle_install_1.expandBundleArtifacts)({
|
|
46
|
+
bundleName,
|
|
47
|
+
bundles,
|
|
48
|
+
agents: [agent],
|
|
49
|
+
method: 'symlink',
|
|
50
|
+
projectRoot,
|
|
51
|
+
contentDir,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Bundle not resolvable (e.g. missing dependency in a partially-seeded
|
|
56
|
+
// test registry) — nothing to enumerate for it, not a hard failure.
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
for (const intent of intents) {
|
|
60
|
+
const p = physicalTarget(intent.type, agent, scope, intent.installName, projectRoot);
|
|
61
|
+
if (p)
|
|
62
|
+
targets.add(p);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Enumerates every path `runInitSteps` may write to for `agent`, given the
|
|
67
|
+
* discovered bundle catalog. Driven entirely by `agent` (the single target
|
|
68
|
+
* being initialized this run) and `bundles` — not by preferences, since
|
|
69
|
+
* enumeration never needs to know about any OTHER enabled agent's targets.
|
|
70
|
+
* Pure / read-only (does hit the filesystem to discover project root and
|
|
71
|
+
* read the project profile, but never writes).
|
|
72
|
+
*/
|
|
73
|
+
function planInitMutationTargets(params) {
|
|
74
|
+
const { cwd, agent, bundles } = params;
|
|
75
|
+
const targets = new Set();
|
|
76
|
+
// preferences.json + the artifact ownership ledger (state/artifacts.json)
|
|
77
|
+
targets.add(path_1.default.join((0, paths_1.awmHome)(), 'preferences.json'));
|
|
78
|
+
targets.add((0, artifact_state_1.artifactStateFile)());
|
|
79
|
+
const provider = (0, providers_1.providerFor)(agent);
|
|
80
|
+
// hook: settings file (or hooks.json for codex) + the scripts directory it points at
|
|
81
|
+
if (provider.hooks) {
|
|
82
|
+
targets.add(provider.hooks.settingsPath);
|
|
83
|
+
targets.add(provider.hooks.scriptsDir);
|
|
84
|
+
}
|
|
85
|
+
// Global skills directory itself, not just the bundle-derived skill
|
|
86
|
+
// subdirectories enumerated below. stepGlobalSkillsRepair (steps.ts) calls
|
|
87
|
+
// repairGlobalSkills, which readdirSync's the WHOLE skills dir and
|
|
88
|
+
// rm/symlinks individual entries classified as repairable/dead — i.e.
|
|
89
|
+
// entries that, by definition, don't belong to any currently-known
|
|
90
|
+
// bundle, so they can never be enumerated by addBundleTargets below.
|
|
91
|
+
// Adding the parent directory here makes beginBackupSession snapshot the
|
|
92
|
+
// whole tree (backupEntryFor does a recursive fs.cpSync for directory
|
|
93
|
+
// targets — verified in install-transaction.ts), covering any entry that
|
|
94
|
+
// repair might mutate. Broad-but-safe, per this module's own philosophy.
|
|
95
|
+
targets.add(provider.skill.global);
|
|
96
|
+
// global context / AGENTS.md injection (covered by the hook for claude-code)
|
|
97
|
+
const injection = provider.injection;
|
|
98
|
+
if (injection) {
|
|
99
|
+
if (injection.type === 'config-instructions')
|
|
100
|
+
targets.add(injection.configPath);
|
|
101
|
+
if (injection.type === 'managed-agents-md')
|
|
102
|
+
targets.add(injection.globalPath);
|
|
103
|
+
}
|
|
104
|
+
// machine-level bundle targets: baseline (dev-core) + ambient, global scope
|
|
105
|
+
const roots = (0, registries_1.contentRoots)();
|
|
106
|
+
const contentDir = roots[0] ?? '';
|
|
107
|
+
const machineBundles = bundles.filter((b) => b.scope === 'baseline' || b.scope === 'ambient');
|
|
108
|
+
for (const b of machineBundles) {
|
|
109
|
+
addBundleTargets(targets, b.name, bundles, agent, 'global', cwd, contentDir);
|
|
110
|
+
}
|
|
111
|
+
// project-level: profile, sensors manifest, project injection, and every
|
|
112
|
+
// extension currently recorded in .awm/profile.json
|
|
113
|
+
const projectRoot = (0, profile_1.findProjectRoot)(cwd);
|
|
114
|
+
if (projectRoot) {
|
|
115
|
+
targets.add(path_1.default.join(projectRoot, '.awm', 'profile.json'));
|
|
116
|
+
targets.add(path_1.default.join(projectRoot, '.awm', 'sensors.json'));
|
|
117
|
+
if (injection?.type === 'config-instructions') {
|
|
118
|
+
targets.add(path_1.default.join(projectRoot, path_1.default.basename(injection.configPath)));
|
|
119
|
+
}
|
|
120
|
+
if (injection?.type === 'managed-agents-md') {
|
|
121
|
+
targets.add(path_1.default.join(projectRoot, path_1.default.basename(injection.localFile)));
|
|
122
|
+
}
|
|
123
|
+
let profile;
|
|
124
|
+
try {
|
|
125
|
+
profile = (0, profile_1.readProfile)(projectRoot);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
profile = { extensions: [] };
|
|
129
|
+
}
|
|
130
|
+
for (const ext of profile.extensions) {
|
|
131
|
+
const extBundle = bundles.find((b) => b.name === ext);
|
|
132
|
+
if (!extBundle)
|
|
133
|
+
continue;
|
|
134
|
+
const scope = (0, bundles_1.defaultScopeForBundle)(extBundle.scope);
|
|
135
|
+
addBundleTargets(targets, ext, bundles, agent, scope, projectRoot, contentDir);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return Array.from(targets);
|
|
139
|
+
}
|