agentwheel 0.10.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.9.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.10.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:
@@ -141,6 +145,35 @@ agentwheel add github:owner/repo --select skills/code-review --select rules/core
141
145
  agentwheel add github:owner/repo --select skills/code-review,rules/core.md
142
146
  ```
143
147
 
148
+ Use a source override when a selected package should replace the same artifact coming from another
149
+ source, such as a forked skill overriding a meta-pack dependency. The declaration is explicit and
150
+ planning fails if it does not match exactly one losing artifact and one selected replacement:
151
+
152
+ ```bash
153
+ agentwheel add github:example-org/agent-toolkit#main \
154
+ --skill self-improve \
155
+ --override 'github:example-upstream/agent-toolkit::skills/self-improve'
156
+ ```
157
+
158
+ Equivalent config:
159
+
160
+ ```json
161
+ {
162
+ "name": "agent-toolkit-nestdev",
163
+ "source": "github:example-org/agent-toolkit#main",
164
+ "driver": "git",
165
+ "adapter": "codex",
166
+ "installationType": "local",
167
+ "mode": "tracking",
168
+ "select": ["skills/self-improve"],
169
+ "overrides": ["github:example-upstream/agent-toolkit::skills/self-improve"]
170
+ }
171
+ ```
172
+
173
+ `source::type/name` identifies the artifact being replaced. `github:owner/repo` matches any ref
174
+ for that repo; include `#main` or another ref to narrow it. Review `OVERRIDE` lines in
175
+ `agentwheel plan`, `agentwheel deps tree`, or `agentwheel deps why` before applying fleet-wide.
176
+
144
177
  Use `--name` for a stable local alias:
145
178
 
146
179
  ```bash
@@ -178,9 +211,14 @@ agentwheel install team-agent-pack
178
211
  Add and install a source in one step:
179
212
 
180
213
  ```bash
181
- agentwheel install github:owner/repo --adapter codex
214
+ agentwheel install github:owner/repo --adapter codex,claude
182
215
  ```
183
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
+
184
222
  Target selection order is `--target-root`, then `--agent`, then runtime auto-detection from the current directory, then the current directory.
185
223
 
186
224
  ## Status
@@ -191,8 +229,15 @@ Use status to see configured packages, lock/manifest state, and pending install
191
229
  agentwheel status
192
230
  agentwheel status --agent lab-codex
193
231
  agentwheel status --all
232
+ agentwheel status --profile daily
194
233
  ```
195
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
+
196
241
  ## Named Agents And Profiles
197
242
 
198
243
  Global config is `~/.agentwheel/config.json`. Project config is `.agentwheel/config.json`; project values win.
@@ -205,8 +250,8 @@ Current config shape:
205
250
  "packages": [],
206
251
  "registry": {},
207
252
  "agents": {
208
- "lab-codex": { "adapter": "codex", "root": "/Users/me/project" },
209
- "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" }
210
255
  },
211
256
  "profiles": {
212
257
  "daily": {
@@ -272,8 +317,14 @@ Target configured agents:
272
317
  ```bash
273
318
  agentwheel update --agent lab-codex --dry-run
274
319
  agentwheel update --all --dry-run
320
+ agentwheel update --profile daily --dry-run
275
321
  ```
276
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
+
277
328
  Limit an update to one configured package:
278
329
 
279
330
  ```bash
@@ -289,7 +340,9 @@ Drift means a managed runtime output changed outside agentwheel. Fix drift by ch
289
340
 
290
341
  - Layer local instructions with `agentwheel remember`.
291
342
  - Add separate local artifacts under `.agentwheel/additions`.
292
- - Override an upstream item under `.agentwheel/overrides`.
343
+ - Override upstream content under `.agentwheel/overrides`.
344
+ - Override source precedence with package `overrides` when a forked source should replace a
345
+ colliding upstream artifact.
293
346
  - Eject an item into `.agentwheel/ejected` when the user wants local ownership.
294
347
 
295
348
  Append durable local instruction text:
@@ -388,7 +441,7 @@ agentwheel install
388
441
  If the current directory has multiple runtime markers:
389
442
 
390
443
  ```bash
391
- agentwheel install --adapter codex --dry-run
444
+ agentwheel install --adapter codex --installation-type local --dry-run
392
445
  ```
393
446
 
394
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
+ }