agentwheel 0.11.0 → 0.14.3

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/openpack.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "name": "NestDevLab/agentwheel",
4
- "version": "0.11.0",
4
+ "version": "0.14.2",
5
5
  "provides": [
6
- { "type": "skills", "path": "skills", "required": true }
6
+ { "type": "skills", "path": "skills" }
7
7
  ]
8
8
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.11.0",
3
+ "version": "0.14.3",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,6 +40,7 @@
40
40
  "scripts": {
41
41
  "build": "tsup && chmod +x dist/index.js",
42
42
  "prepack": "pnpm build",
43
+ "sync-site-version": "node scripts/sync-site-version.mjs",
43
44
  "test": "vitest run",
44
45
  "typecheck": "tsc --noEmit"
45
46
  },
@@ -20,7 +20,7 @@ Mental model:
20
20
  ## Safety Rules
21
21
 
22
22
  - Prefer `agentwheel plan ...` or `agentwheel install --dry-run` before applying changes.
23
- - Do not hand-edit generated runtime files such as `.openclaw/skills`, `.claude/skills`, `.codex/skills`, `.hermes/skills`, or generated instructions.
23
+ - Do not hand-edit generated runtime files such as `skills/`, `.agents/skills`, `.claude/skills`, `.github/skills`, `~/.hermes/skills`, or generated instructions.
24
24
  - If a plan reports `drift` or `conflict`, stop and explain it. Do not use `--force` unless the user explicitly approves that scope.
25
25
  - Gmail, Drive, registry publishing, git commits, pushes, and runtime restarts are separate external side effects. Get explicit approval for them.
26
26
  - Programmatic adapters execute local code. Use `--adapter-module` only with `--allow-adapter-code` after the user approves that local code execution.
@@ -30,7 +30,7 @@ Mental model:
30
30
 
31
31
  ```bash
32
32
  agentwheel registry search tmux
33
- agentwheel add github:NestDevLab/agent-mesh --skill codex-tmux --adapter codex --mode tracking
33
+ agentwheel add github:NestDevLab/agent-mesh --skill codex-tmux --adapter codex --installation-type local --mode tracking
34
34
  agentwheel plan
35
35
  agentwheel install
36
36
  ```
@@ -45,9 +45,13 @@ agentwheel install
45
45
  To add and install in one step:
46
46
 
47
47
  ```bash
48
- agentwheel install github:owner/repo --adapter codex
48
+ agentwheel install github:owner/repo --adapter codex,claude
49
49
  ```
50
50
 
51
+ Explicit source installs with explicit adapters default to user-level artifacts. Use `--local` for
52
+ the current directory or `-t/--target-root <project>` for project/workspace installs. Use `--user`, `--local`, or
53
+ `-i/--installation-type <type>` when scope should be explicit.
54
+
51
55
  ## Workspace Setup
52
56
 
53
57
  Initialize an agentwheel workspace:
@@ -126,11 +130,11 @@ agentwheel add <source> --driver vercel-skills
126
130
  `add` saves a package entry in `.agentwheel/config.json`; it does not install runtime files by itself.
127
131
 
128
132
  ```bash
129
- agentwheel add github:owner/repo --adapter openclaw
130
- agentwheel add github:owner/repo --adapter claude
131
- agentwheel add github:owner/repo --adapter codex
132
- agentwheel add github:owner/repo --adapter hermes
133
- agentwheel add github:owner/repo --adapter copilot
133
+ agentwheel add github:owner/repo --adapter openclaw --installation-type local
134
+ agentwheel add github:owner/repo --adapter claude --installation-type local
135
+ agentwheel add github:owner/repo --adapter codex --installation-type local
136
+ agentwheel add github:owner/repo --adapter hermes --installation-type user
137
+ agentwheel add github:owner/repo --adapter copilot --installation-type local
134
138
  ```
135
139
 
136
140
  Select only part of a package:
@@ -146,9 +150,9 @@ source, such as a forked skill overriding a meta-pack dependency. The declaratio
146
150
  planning fails if it does not match exactly one losing artifact and one selected replacement:
147
151
 
148
152
  ```bash
149
- agentwheel add github:Yehonal/agent-toolkit#main \
153
+ agentwheel add github:example-org/agent-toolkit#main \
150
154
  --skill self-improve \
151
- --override 'github:FrancescoBorzi/agent-toolkit::skills/self-improve'
155
+ --override 'github:example-upstream/agent-toolkit::skills/self-improve'
152
156
  ```
153
157
 
154
158
  Equivalent config:
@@ -156,12 +160,13 @@ Equivalent config:
156
160
  ```json
157
161
  {
158
162
  "name": "agent-toolkit-nestdev",
159
- "source": "github:Yehonal/agent-toolkit#main",
163
+ "source": "github:example-org/agent-toolkit#main",
160
164
  "driver": "git",
161
165
  "adapter": "codex",
166
+ "installationType": "local",
162
167
  "mode": "tracking",
163
168
  "select": ["skills/self-improve"],
164
- "overrides": ["github:FrancescoBorzi/agent-toolkit::skills/self-improve"]
169
+ "overrides": ["github:example-upstream/agent-toolkit::skills/self-improve"]
165
170
  }
166
171
  ```
167
172
 
@@ -206,9 +211,14 @@ agentwheel install team-agent-pack
206
211
  Add and install a source in one step:
207
212
 
208
213
  ```bash
209
- agentwheel install github:owner/repo --adapter codex
214
+ agentwheel install github:owner/repo --adapter codex,claude
210
215
  ```
211
216
 
217
+ Explicit source installs with explicit adapters default to documented user-level installs. Pass
218
+ `--local` for the current directory or `-t/--target-root <project>` for project/workspace installs, and use `--user`, `--local`,
219
+ or `-i/--installation-type <type>` when scope should be explicit. If a package can be installed in
220
+ more than one type and no CLI/context default applies, agentwheel fails instead of guessing.
221
+
212
222
  Target selection order is `--target-root`, then `--agent`, then runtime auto-detection from the current directory, then the current directory.
213
223
 
214
224
  ## Status
@@ -219,8 +229,15 @@ Use status to see configured packages, lock/manifest state, and pending install
219
229
  agentwheel status
220
230
  agentwheel status --agent lab-codex
221
231
  agentwheel status --all
232
+ agentwheel status --profile daily
222
233
  ```
223
234
 
235
+ For profile-managed fleets, prefer `agentwheel status --profile <name>` over
236
+ direct agent status; profile status uses the same runtime resolution, adapter
237
+ config, installation type, and graph lock fingerprinting as
238
+ `install --profile <name>`. If a workspace defines a profile named `all`,
239
+ `agentwheel status --all` checks that profile.
240
+
224
241
  ## Named Agents And Profiles
225
242
 
226
243
  Global config is `~/.agentwheel/config.json`. Project config is `.agentwheel/config.json`; project values win.
@@ -233,8 +250,8 @@ Current config shape:
233
250
  "packages": [],
234
251
  "registry": {},
235
252
  "agents": {
236
- "lab-codex": { "adapter": "codex", "root": "/Users/me/project" },
237
- "lab-claude": { "adapter": "claude", "root": "/Users/me/project" }
253
+ "lab-codex": { "adapter": "codex", "installationType": "local", "root": "$HOME/project" },
254
+ "lab-claude": { "adapter": "claude", "installationType": "local", "root": "$HOME/project" }
238
255
  },
239
256
  "profiles": {
240
257
  "daily": {
@@ -300,8 +317,14 @@ Target configured agents:
300
317
  ```bash
301
318
  agentwheel update --agent lab-codex --dry-run
302
319
  agentwheel update --all --dry-run
320
+ agentwheel update --profile daily --dry-run
303
321
  ```
304
322
 
323
+ For profile-managed fleets, use `agentwheel update --profile <name>` before
324
+ `install --profile <name>` when tracking packages should move forward. If a
325
+ workspace defines a profile named `all`, `agentwheel update --all` checks that
326
+ profile.
327
+
305
328
  Limit an update to one configured package:
306
329
 
307
330
  ```bash
@@ -418,7 +441,7 @@ agentwheel install
418
441
  If the current directory has multiple runtime markers:
419
442
 
420
443
  ```bash
421
- agentwheel install --adapter codex --dry-run
444
+ agentwheel install --adapter codex --installation-type local --dry-run
422
445
  ```
423
446
 
424
447
  If a selected artifact is missing:
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: agentwheel-smoke
3
+ description: Run a read-only smoke diagnostic for the current AI harness and its Agentwheel-managed installation state. Use when asked to smoke test an agent, verify fleet deployment, inspect whether a harness is installed in the expected Agentwheel path, or summarize local Agentwheel status without mutating files or external systems.
4
+ ---
5
+
6
+ # Agentwheel Smoke
7
+
8
+ Use this skill to answer: "What harness am I running in, where was this skill installed, and what does Agentwheel think about this target?"
9
+
10
+ The default workflow is read-only. Do not run `agentwheel install`, `agentwheel update`, `agentwheel uninstall`, `agentwheel remember`, `agentwheel eject`, or any file-writing fix while using this skill unless the user separately approves that mutation scope.
11
+
12
+ ## Quick Start
13
+
14
+ From the user's current workspace, run the bundled smoke script:
15
+
16
+ ```bash
17
+ node path/to/agentwheel-smoke/scripts/smoke.mjs
18
+ ```
19
+
20
+ Use JSON output only when another tool or script needs to consume the result:
21
+
22
+ ```bash
23
+ node path/to/agentwheel-smoke/scripts/smoke.mjs --json
24
+ ```
25
+
26
+ When the user names an Agentwheel target, pass only read-only status target flags:
27
+
28
+ ```bash
29
+ node path/to/agentwheel-smoke/scripts/smoke.mjs --agent lab-codex
30
+ node path/to/agentwheel-smoke/scripts/smoke.mjs --profile daily
31
+ node path/to/agentwheel-smoke/scripts/smoke.mjs --all
32
+ ```
33
+
34
+ ## Report The Result
35
+
36
+ Summarize the script output in four parts:
37
+
38
+ - Overall: `PASS`, `WARN`, or `FAIL`.
39
+ - Harness: detected adapter, installation type, runtime root, and skill path.
40
+ - Agentwheel: CLI availability, version, config locations, packages, agents, profiles, and status result.
41
+ - Recommended next command: prefer `agentwheel status`, `agentwheel plan`, or `agentwheel install --dry-run`.
42
+
43
+ Treat these as important signals:
44
+
45
+ - `PASS`: skill path maps to a documented Agentwheel harness target and `agentwheel status` reports no pending work.
46
+ - `WARN`: the skill is running from package source, status is missing, config is absent, manifest is missing, or pending work is reported.
47
+ - `FAIL`: Agentwheel CLI is unavailable, the skill path maps to a known wrong target, `agentwheel status` exits unsuccessfully, or drift/conflict appears.
48
+
49
+ ## Compatibility Checks
50
+
51
+ Use these documented skill targets when explaining path compatibility:
52
+
53
+ - Codex local: `.agents/skills/<name>/SKILL.md`; Codex user: `~/.agents/skills/<name>/SKILL.md`.
54
+ - Claude local: `.claude/skills/<name>/SKILL.md`; Claude user: `~/.claude/skills/<name>/SKILL.md`.
55
+ - Copilot local: `.github/skills/<name>/SKILL.md`; Copilot user: `~/.copilot/skills/<name>/SKILL.md`.
56
+ - OpenClaw local: `skills/<name>/SKILL.md`; OpenClaw user: `~/.openclaw/skills/<name>/SKILL.md`.
57
+ - Hermes user: `~/.hermes/skills/<name>/SKILL.md`; local Hermes skills are unsupported by default.
58
+
59
+ Flag `.codex/skills/<name>/SKILL.md` as incompatible for Agentwheel-managed Codex skills.
60
+
61
+ ## Safety
62
+
63
+ This skill is diagnostic. It may inspect files and run read-only Agentwheel commands, but it must not mutate runtime outputs, package source, Google Workspace data, Git state, registry state, or deployment state.
@@ -0,0 +1,397 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync } from "node:child_process";
3
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
4
+ import { dirname, join, relative, resolve, sep } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import os from "node:os";
7
+
8
+ const skillName = "agentwheel-smoke";
9
+ const scriptPath = fileURLToPath(import.meta.url);
10
+ const skillRoot = dirname(dirname(scriptPath));
11
+ const cwd = process.cwd();
12
+ const home = os.homedir();
13
+
14
+ const parsed = parseArgs(process.argv.slice(2));
15
+ if (parsed.error) {
16
+ console.error(parsed.error);
17
+ process.exit(2);
18
+ }
19
+
20
+ const config = findConfigs(cwd, home);
21
+ const harness = detectHarness(skillRoot, cwd, home);
22
+ const envHint = detectHarnessFromEnv();
23
+ const agentwheel = inspectAgentwheel(parsed.statusArgs, envHint);
24
+ const manifests = findStateFiles(harness.runtimeRoot ?? projectRootFromConfig(config.projectConfig) ?? cwd);
25
+ const assessment = assess({ harness, agentwheel, manifests, config });
26
+ const report = {
27
+ overall: assessment.overall,
28
+ checks: assessment.checks,
29
+ harness,
30
+ agentwheel,
31
+ config,
32
+ manifests,
33
+ recommendedNextCommand: recommendedNextCommand(assessment, agentwheel.status.args),
34
+ };
35
+
36
+ if (parsed.json) {
37
+ console.log(JSON.stringify(report, null, 2));
38
+ } else {
39
+ console.log(formatMarkdown(report));
40
+ }
41
+
42
+ process.exit(report.overall === "FAIL" ? 1 : 0);
43
+
44
+ function parseArgs(args) {
45
+ const statusArgs = [];
46
+ let json = false;
47
+ for (let index = 0; index < args.length; index += 1) {
48
+ const arg = args[index];
49
+ if (arg === "--json") {
50
+ json = true;
51
+ continue;
52
+ }
53
+ if (["--all", "--all-detected"].includes(arg)) {
54
+ statusArgs.push(arg);
55
+ continue;
56
+ }
57
+ if (["--agent", "--profile", "--target-root", "--adapter", "--installation-type"].includes(arg)) {
58
+ const value = args[index + 1];
59
+ if (!value || value.startsWith("--")) return { error: `${arg} requires a value.` };
60
+ statusArgs.push(arg, value);
61
+ index += 1;
62
+ continue;
63
+ }
64
+ return { error: `Unsupported smoke option: ${arg}` };
65
+ }
66
+ return { json, statusArgs };
67
+ }
68
+
69
+ function inspectAgentwheel(statusArgs, hint) {
70
+ const which = run("command", ["-v", "agentwheel"], { shell: true });
71
+ const version = which.ok ? run("agentwheel", ["--version"]) : { ok: false, code: null, stdout: "", stderr: "agentwheel not found" };
72
+ let effectiveStatusArgs = statusArgs;
73
+ let status = which.ok ? run("agentwheel", ["status", ...effectiveStatusArgs]) : { ok: false, code: null, stdout: "", stderr: "agentwheel not found" };
74
+ if (!status.ok && statusArgs.length === 0 && /Multiple runtime directories detected/i.test(status.stderr) && hint?.adapter) {
75
+ effectiveStatusArgs = ["--adapter", hint.adapter, "--installation-type", hint.installationType ?? "local"];
76
+ status = run("agentwheel", ["status", ...effectiveStatusArgs]);
77
+ }
78
+ return {
79
+ cliAvailable: which.ok,
80
+ cliPath: firstLine(which.stdout),
81
+ version: firstLine(version.stdout),
82
+ status: {
83
+ args: effectiveStatusArgs,
84
+ ok: status.ok,
85
+ code: status.code,
86
+ stdout: status.stdout.trim(),
87
+ stderr: status.stderr.trim(),
88
+ summary: summarizeStatus(status.stdout, status.stderr),
89
+ },
90
+ };
91
+ }
92
+
93
+ function run(command, args, options = {}) {
94
+ try {
95
+ const stdout = execFileSync(command, args, {
96
+ cwd,
97
+ env: { ...process.env, AGENTWHEEL_NO_UPDATE_CHECK: "1" },
98
+ encoding: "utf8",
99
+ stdio: ["ignore", "pipe", "pipe"],
100
+ shell: options.shell ?? false,
101
+ });
102
+ return { ok: true, code: 0, stdout, stderr: "" };
103
+ } catch (error) {
104
+ return {
105
+ ok: false,
106
+ code: typeof error.status === "number" ? error.status : null,
107
+ stdout: String(error.stdout ?? ""),
108
+ stderr: String(error.stderr ?? error.message ?? ""),
109
+ };
110
+ }
111
+ }
112
+
113
+ function findConfigs(start, homeDir) {
114
+ const globalConfig = join(homeDir, ".agentwheel", "config.json");
115
+ const foundConfig = findUp(start, ".agentwheel/config.json");
116
+ const projectConfig = foundConfig && resolve(foundConfig) !== resolve(globalConfig) ? foundConfig : null;
117
+ return {
118
+ projectConfig,
119
+ globalConfig: existsSync(globalConfig) ? globalConfig : null,
120
+ project: projectConfig ? summarizeConfig(projectConfig) : null,
121
+ global: existsSync(globalConfig) ? summarizeConfig(globalConfig) : null,
122
+ };
123
+ }
124
+
125
+ function detectHarnessFromEnv() {
126
+ if (process.env.CODEX_THREAD_ID || process.env.CODEX_CI) return { adapter: "codex", installationType: "local" };
127
+ if (process.env.CLAUDECODE || process.env.CLAUDE_CODE) return { adapter: "claude", installationType: "local" };
128
+ if (process.env.GITHUB_COPILOT_CLI || process.env.COPILOT_AGENT) return { adapter: "copilot", installationType: "local" };
129
+ return null;
130
+ }
131
+
132
+ function summarizeConfig(path) {
133
+ try {
134
+ const json = JSON.parse(readFileSync(path, "utf8"));
135
+ return {
136
+ packages: Array.isArray(json.packages) ? json.packages.map((entry) => entry.name ?? entry.source ?? "unnamed") : [],
137
+ agents: json.agents && typeof json.agents === "object" ? Object.keys(json.agents) : [],
138
+ profiles: json.profiles && typeof json.profiles === "object" ? Object.keys(json.profiles) : [],
139
+ };
140
+ } catch (error) {
141
+ return { error: error.message };
142
+ }
143
+ }
144
+
145
+ function detectHarness(path, currentDir, homeDir) {
146
+ const normalized = resolve(path);
147
+ const homeRelative = relative(homeDir, normalized);
148
+ const localPatterns = [
149
+ { marker: [".agents", "skills", skillName], adapter: "codex", installationType: "local", expected: ".agents/skills/<name>" },
150
+ { marker: [".claude", "skills", skillName], adapter: "claude", installationType: "local", expected: ".claude/skills/<name>" },
151
+ { marker: [".github", "skills", skillName], adapter: "copilot", installationType: "local", expected: ".github/skills/<name>" },
152
+ { marker: ["skills", skillName], adapter: "openclaw", installationType: "local", expected: "skills/<name>" },
153
+ ];
154
+ const userPatterns = [
155
+ { root: join(homeDir, ".agents", "skills", skillName), adapter: "codex", installationType: "user", expected: "~/.agents/skills/<name>" },
156
+ { root: join(homeDir, ".claude", "skills", skillName), adapter: "claude", installationType: "user", expected: "~/.claude/skills/<name>" },
157
+ { root: join(homeDir, ".copilot", "skills", skillName), adapter: "copilot", installationType: "user", expected: "~/.copilot/skills/<name>" },
158
+ { root: join(homeDir, ".openclaw", "skills", skillName), adapter: "openclaw", installationType: "user", expected: "~/.openclaw/skills/<name>" },
159
+ { root: join(homeDir, ".hermes", "skills", skillName), adapter: "hermes", installationType: "user", expected: "~/.hermes/skills/<name>" },
160
+ ];
161
+ const wrongCodex = containsSegments(normalized, [".codex", "skills", skillName]);
162
+ if (wrongCodex) {
163
+ return {
164
+ adapter: "codex",
165
+ installationType: "unknown",
166
+ confidence: "high",
167
+ runtimeRoot: null,
168
+ skillPath: normalized,
169
+ expectedSkillPath: ".agents/skills/<name> or ~/.agents/skills/<name>",
170
+ compatibility: "FAIL",
171
+ note: ".codex/skills is not an Agentwheel-managed Codex skill target.",
172
+ };
173
+ }
174
+ for (const pattern of userPatterns) {
175
+ if (normalized === pattern.root || normalized.startsWith(`${pattern.root}${sep}`)) {
176
+ return {
177
+ adapter: pattern.adapter,
178
+ installationType: pattern.installationType,
179
+ confidence: "high",
180
+ runtimeRoot: homeDir,
181
+ skillPath: normalized,
182
+ expectedSkillPath: pattern.expected,
183
+ compatibility: "PASS",
184
+ note: `Skill path is under ${pattern.expected}.`,
185
+ };
186
+ }
187
+ }
188
+ for (const pattern of localPatterns) {
189
+ const root = rootBeforeMarker(normalized, pattern.marker);
190
+ if (!root) continue;
191
+ const packageSource = pattern.adapter === "openclaw" && isAgentwheelSourcePackage(root);
192
+ return {
193
+ adapter: packageSource ? "unknown" : pattern.adapter,
194
+ installationType: packageSource ? "source" : pattern.installationType,
195
+ confidence: packageSource ? "medium" : "high",
196
+ runtimeRoot: root,
197
+ skillPath: normalized,
198
+ expectedSkillPath: pattern.expected,
199
+ compatibility: packageSource ? "WARN" : "PASS",
200
+ note: packageSource
201
+ ? "Skill is running from the Agentwheel source package; this is useful for authoring but does not prove fleet deployment."
202
+ : `Skill path is under ${pattern.expected}.`,
203
+ };
204
+ }
205
+ return {
206
+ adapter: "unknown",
207
+ installationType: "unknown",
208
+ confidence: "low",
209
+ runtimeRoot: findUp(currentDir, ".agentwheel") ? dirname(findUp(currentDir, ".agentwheel")) : null,
210
+ skillPath: normalized,
211
+ expectedSkillPath: null,
212
+ compatibility: "WARN",
213
+ note: `Could not map ${homeRelative.startsWith("..") ? normalized : `~/${homeRelative}`} to a documented Agentwheel skill target.`,
214
+ };
215
+ }
216
+
217
+ function isAgentwheelSourcePackage(root) {
218
+ const openpackPath = join(root, "openpack.json");
219
+ if (!existsSync(openpackPath)) return false;
220
+ try {
221
+ const json = JSON.parse(readFileSync(openpackPath, "utf8"));
222
+ return json.name === "NestDevLab/agentwheel";
223
+ } catch {
224
+ return false;
225
+ }
226
+ }
227
+
228
+ function findStateFiles(root) {
229
+ const stateDir = join(root, ".agentwheel");
230
+ if (!existsSync(stateDir)) return { root, installManifests: [], sourceLocks: [] };
231
+ const files = readdirSync(stateDir).sort();
232
+ return {
233
+ root,
234
+ installManifests: files.filter((file) => file.endsWith(".install-manifest.json")),
235
+ sourceLocks: files.filter((file) => file.endsWith(".source-lock.json")),
236
+ };
237
+ }
238
+
239
+ function assess({ harness, agentwheel, manifests, config }) {
240
+ const checks = [];
241
+ pushCheck(checks, harness.compatibility, "harness-path", harness.note);
242
+ pushCheck(checks, agentwheel.cliAvailable ? "PASS" : "FAIL", "agentwheel-cli", agentwheel.cliAvailable ? `Found ${agentwheel.cliPath}` : "agentwheel is not available on PATH.");
243
+ pushCheck(checks, agentwheel.status.ok ? "PASS" : "FAIL", "agentwheel-status", agentwheel.status.ok ? "agentwheel status completed." : statusFailure(agentwheel.status));
244
+ pushCheck(checks, config.projectConfig || config.globalConfig ? "PASS" : "WARN", "agentwheel-config", config.projectConfig || config.globalConfig ? "Agentwheel config found." : "No project or global Agentwheel config found.");
245
+ const statusHasManifest = /Install manifest:\s*(?!missing\b).+/i.test(agentwheel.status.stdout);
246
+ pushCheck(
247
+ checks,
248
+ manifests.installManifests.length > 0 || statusHasManifest ? "PASS" : "WARN",
249
+ "install-manifest",
250
+ manifests.installManifests.length > 0
251
+ ? `${manifests.installManifests.length} install manifest file(s) found.`
252
+ : statusHasManifest
253
+ ? "agentwheel status reports an install manifest."
254
+ : "No install manifest files found under the target .agentwheel directory.",
255
+ );
256
+ if (statusHasBlockingDriftOrConflict(agentwheel.status.stdout + agentwheel.status.stderr)) {
257
+ pushCheck(checks, "FAIL", "drift-conflict", "agentwheel status reports drift or conflict.");
258
+ }
259
+ if (statusHasPendingInstallWork(agentwheel.status.stdout)) {
260
+ pushCheck(checks, "WARN", "pending-work", "agentwheel status reports pending install work.");
261
+ }
262
+ if (/Install manifest:\s*missing/i.test(agentwheel.status.stdout)) {
263
+ pushCheck(checks, "WARN", "manifest-missing", "agentwheel status reports a missing install manifest.");
264
+ }
265
+ const overall = checks.some((check) => check.status === "FAIL") ? "FAIL" : checks.some((check) => check.status === "WARN") ? "WARN" : "PASS";
266
+ return { overall, checks };
267
+ }
268
+
269
+ function projectRootFromConfig(configPath) {
270
+ return configPath ? dirname(dirname(configPath)) : null;
271
+ }
272
+
273
+ function statusHasBlockingDriftOrConflict(text) {
274
+ return /^Pending install work:.*(?:\bdrift=[1-9]\d*|\bconflict=[1-9]\d*|\bblocking=[1-9]\d*)/im.test(text)
275
+ || /^\s*(?:DRIFT|CONFLICT)\s+/im.test(text);
276
+ }
277
+
278
+ function statusHasPendingInstallWork(text) {
279
+ const line = String(text ?? "").split(/\r?\n/).find((entry) => /^Pending install work:/i.test(entry));
280
+ if (!line) return false;
281
+ const value = line.replace(/^Pending install work:/i, "").trim();
282
+ return value.length > 0 && !/^none$/i.test(value);
283
+ }
284
+
285
+ function recommendedNextCommand(assessment, statusArgs) {
286
+ const target = statusArgs.length > 0 ? ` ${statusArgs.join(" ")}` : "";
287
+ if (assessment.checks.some((check) => check.id === "agentwheel-cli" && check.status === "FAIL")) return "Install or expose the agentwheel CLI, then rerun the smoke test.";
288
+ if (assessment.checks.some((check) => check.id === "drift-conflict")) return `agentwheel status${target}`;
289
+ if (assessment.checks.some((check) => check.id === "pending-work" || check.id === "manifest-missing")) return `agentwheel plan${target}`;
290
+ return `agentwheel status${target}`;
291
+ }
292
+
293
+ function formatMarkdown(report) {
294
+ const lines = [
295
+ "# Agentwheel Smoke Report",
296
+ "",
297
+ `Overall: ${report.overall}`,
298
+ "",
299
+ "## Harness",
300
+ `- Detected adapter: ${report.harness.adapter}`,
301
+ `- Installation type: ${report.harness.installationType}`,
302
+ `- Confidence: ${report.harness.confidence}`,
303
+ `- Runtime root: ${report.harness.runtimeRoot ?? "unknown"}`,
304
+ `- Skill path: ${report.harness.skillPath}`,
305
+ `- Expected skill path: ${report.harness.expectedSkillPath ?? "unknown"}`,
306
+ `- Compatibility: ${report.harness.compatibility}`,
307
+ `- Note: ${report.harness.note}`,
308
+ "",
309
+ "## Agentwheel",
310
+ `- CLI available: ${yesNo(report.agentwheel.cliAvailable)}`,
311
+ `- CLI path: ${report.agentwheel.cliPath || "not found"}`,
312
+ `- Version: ${report.agentwheel.version || "unknown"}`,
313
+ `- Status command: ${report.agentwheel.status.ok ? "PASS" : "FAIL"}`,
314
+ `- Status args: ${report.agentwheel.status.args.length > 0 ? report.agentwheel.status.args.join(" ") : "none"}`,
315
+ `- Status summary: ${report.agentwheel.status.summary}`,
316
+ "",
317
+ "## Config",
318
+ `- Project config: ${report.config.projectConfig ?? "not found"}`,
319
+ `- Global config: ${report.config.globalConfig ?? "not found"}`,
320
+ `- Project packages: ${listOrNone(report.config.project?.packages)}`,
321
+ `- Project agents: ${listOrNone(report.config.project?.agents)}`,
322
+ `- Project profiles: ${listOrNone(report.config.project?.profiles)}`,
323
+ "",
324
+ "## Managed State",
325
+ `- State root checked: ${report.manifests.root}`,
326
+ `- Install manifests: ${listOrNone(report.manifests.installManifests)}`,
327
+ `- Source locks: ${listOrNone(report.manifests.sourceLocks)}`,
328
+ "",
329
+ "## Checks",
330
+ ...report.checks.map((check) => `- ${check.status} ${check.id}: ${check.message}`),
331
+ "",
332
+ "## Recommended Next Command",
333
+ `- ${report.recommendedNextCommand}`,
334
+ ];
335
+ if (report.agentwheel.status.stdout) {
336
+ lines.push("", "## agentwheel status stdout", "```text", report.agentwheel.status.stdout, "```");
337
+ }
338
+ if (report.agentwheel.status.stderr) {
339
+ lines.push("", "## agentwheel status stderr", "```text", report.agentwheel.status.stderr, "```");
340
+ }
341
+ return lines.join("\n");
342
+ }
343
+
344
+ function summarizeStatus(stdout, stderr) {
345
+ const text = `${stdout}\n${stderr}`.trim();
346
+ if (!text) return "no status output";
347
+ const interesting = text.split(/\r?\n/).filter((line) =>
348
+ /Status for|Install manifest|Graph lock|Pending install work|No agents configured|No packages configured|drift|conflict/i.test(line),
349
+ );
350
+ return interesting.length > 0 ? interesting.join(" | ") : firstLine(text);
351
+ }
352
+
353
+ function statusFailure(status) {
354
+ return status.stderr || status.stdout || "agentwheel status failed.";
355
+ }
356
+
357
+ function pushCheck(checks, status, id, message) {
358
+ checks.push({ status, id, message });
359
+ }
360
+
361
+ function firstLine(value) {
362
+ return String(value ?? "").trim().split(/\r?\n/)[0] ?? "";
363
+ }
364
+
365
+ function yesNo(value) {
366
+ return value ? "yes" : "no";
367
+ }
368
+
369
+ function listOrNone(values) {
370
+ return values && values.length > 0 ? values.join(", ") : "none";
371
+ }
372
+
373
+ function findUp(start, relativePath) {
374
+ let cursor = resolve(start);
375
+ while (true) {
376
+ const candidate = join(cursor, relativePath);
377
+ if (existsSync(candidate)) return candidate;
378
+ const next = dirname(cursor);
379
+ if (next === cursor) return null;
380
+ cursor = next;
381
+ }
382
+ }
383
+
384
+ function rootBeforeMarker(path, marker) {
385
+ const parts = resolve(path).split(sep).filter(Boolean);
386
+ for (let index = 0; index <= parts.length - marker.length; index += 1) {
387
+ if (marker.every((part, offset) => parts[index + offset] === part)) {
388
+ const rootParts = parts.slice(0, index);
389
+ return `${sep}${rootParts.join(sep)}`;
390
+ }
391
+ }
392
+ return null;
393
+ }
394
+
395
+ function containsSegments(path, marker) {
396
+ return Boolean(rootBeforeMarker(path, marker));
397
+ }