deweyou-cli 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## 0.2.1 - 2026-05-17
4
+
5
+ ### Fixed
6
+
7
+ - publish cli as deweyou-cli package
8
+ ## 0.2.0 - 2026-05-17
9
+
10
+ ### Added
11
+
12
+ - automate cli release changelog
13
+ - colocate deweyou cli package
14
+
15
+ ### Changed
16
+
17
+ - generate cli registry from assets
18
+ All notable `deweyou-cli` package changes will be documented in this file.
package/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # deweyou-cli
2
+
3
+ `deweyou-cli` bootstraps Dewey's personal agent workflows into any local
4
+ repository. It manages the repo-level wiring for selected skills and rules while
5
+ keeping the source assets in the central `deweyou/agents` hub.
6
+
7
+ The v0 scope is intentionally small:
8
+
9
+ - cache skills and rules from a local `deweyou/agents` checkout
10
+ - initialize a repository with selected skills and rules
11
+ - render the active agent context for the current repository
12
+ - diagnose whether the current repository is wired correctly
13
+
14
+ ## Install
15
+
16
+ From npm:
17
+
18
+ ```bash
19
+ npm install -g deweyou-cli
20
+ ```
21
+
22
+ During v0, point the CLI at your local `deweyou/agents` checkout before updating
23
+ the cache:
24
+
25
+ ```bash
26
+ export DEWEYOU_AGENTS_SOURCE=/path/to/deweyou/agents
27
+ deweyou-cli agent update
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```bash
33
+ cd /path/to/your/repo
34
+ deweyou-cli agent init
35
+ deweyou-cli agent doctor
36
+ deweyou-cli agent context --format markdown
37
+ ```
38
+
39
+ For a non-interactive setup that selects everything:
40
+
41
+ ```bash
42
+ deweyou-cli agent init --all --mode link --yes
43
+ ```
44
+
45
+ ## Mental Model
46
+
47
+ `deweyou/agents` is the asset hub. It owns `skills/` and `rules/`.
48
+
49
+ `deweyou-cli` is the workflow manager. It scans the hub assets, generates a
50
+ cache registry under your home directory, then writes a small
51
+ `.agents/manifest.json` into each repository so the repository knows which
52
+ assets are active.
53
+
54
+ Each repository chooses its own asset set. A coding repo can select coding
55
+ skills and rules; a writing or design repo can select different ones.
56
+
57
+ ## Commands
58
+
59
+ ### `deweyou-cli agent update`
60
+
61
+ Refreshes the local Dewey asset cache from `DEWEYOU_AGENTS_SOURCE`.
62
+
63
+ ```bash
64
+ DEWEYOU_AGENTS_SOURCE=/path/to/deweyou/agents deweyou-cli agent update
65
+ ```
66
+
67
+ This command writes the global cache at:
68
+
69
+ ```text
70
+ ~/.deweyou/agents/
71
+ ```
72
+
73
+ Run this after changing or pulling updates in the asset hub.
74
+
75
+ ### `deweyou-cli agent init`
76
+
77
+ Initializes the current repository with selected skills and rules.
78
+
79
+ ```bash
80
+ deweyou-cli agent init
81
+ ```
82
+
83
+ Without selection flags, this opens an interactive setup where you choose:
84
+
85
+ - install mode
86
+ - skills
87
+ - rules
88
+
89
+ Scripted examples:
90
+
91
+ ```bash
92
+ deweyou-cli agent init --all --mode link --yes
93
+ deweyou-cli agent init --skills code-knowledge,deweyou-design --rules code-style
94
+ deweyou-cli agent init --dry-run
95
+ ```
96
+
97
+ Flags:
98
+
99
+ | Flag | Meaning |
100
+ |------|---------|
101
+ | `--all` | Select every skill and rule from the cached registry. |
102
+ | `--skills a,b` | Select only the listed skill ids. Values are comma-separated. |
103
+ | `--rules a,b` | Select only the listed rule ids. Values are comma-separated. |
104
+ | `--mode link\|copy\|pointer` | Choose how the repository references selected assets. |
105
+ | `--yes` | Run without prompts. Requires `--all`, `--skills`, or `--rules`. |
106
+ | `--dry-run` | Print the planned files without writing them. |
107
+ | `--force` | Replace existing Dewey-managed asset destinations when needed. |
108
+
109
+ `--yes` does not guess a default asset set. It only confirms a scripted
110
+ selection you already provided.
111
+
112
+ ### `deweyou-cli agent context`
113
+
114
+ Prints the active Dewey agent context for the current repository.
115
+
116
+ ```bash
117
+ deweyou-cli agent context --format markdown
118
+ deweyou-cli agent context --format json
119
+ ```
120
+
121
+ Formats:
122
+
123
+ | Format | Meaning |
124
+ |--------|---------|
125
+ | `markdown` | Human-readable instructions and asset paths. This is the default. |
126
+ | `json` | Structured context for tooling or future integrations. |
127
+
128
+ The context output tells an agent which skills and rules are active, where their
129
+ files live, whether the hub commit changed, and whether any selected asset hash
130
+ changed in the local cache.
131
+
132
+ ### `deweyou-cli agent doctor`
133
+
134
+ Checks whether the current repository and local cache are healthy.
135
+
136
+ ```bash
137
+ deweyou-cli agent doctor
138
+ ```
139
+
140
+ It verifies:
141
+
142
+ - local cache registry exists and is valid
143
+ - repository `.agents/manifest.json` exists and is valid
144
+ - `AGENTS.md` exists
145
+ - selected skills and rules still exist in the registry
146
+ - selected asset hashes match the repository's initialized snapshot
147
+ - selected asset files are present
148
+ - symlinks are valid when using `link` mode
149
+
150
+ The command exits with a non-zero status when a check fails.
151
+
152
+ ## Install Modes
153
+
154
+ | Mode | Repository Writes | Best For |
155
+ |------|-------------------|----------|
156
+ | `link` | Symlinks selected assets into `.agents/skills/` and `.agents/rules/`. | Daily local work where updates should be immediately visible after cache refresh. |
157
+ | `copy` | Copies selected assets into `.agents/skills/` and `.agents/rules/`. | Repositories that should keep a snapshot of the selected assets. |
158
+ | `pointer` | Writes only `.agents/manifest.json` and `AGENTS.md`; assets stay in the global cache. | Minimal repo footprint and tooling that can follow absolute cache paths. |
159
+
160
+ ## Files Created
161
+
162
+ Depending on the selected mode, `deweyou-cli agent init` may create or update:
163
+
164
+ ```text
165
+ AGENTS.md
166
+ .agents/manifest.json
167
+ .agents/skills/<skill>/SKILL.md
168
+ .agents/rules/<rule>.md
169
+ ```
170
+
171
+ `AGENTS.md` receives a managed Dewey section that points agents at the selected
172
+ workflow context. Existing content outside that managed section is preserved.
173
+
174
+ ## Safety Notes
175
+
176
+ - Run `deweyou-cli agent update` before `deweyou-cli agent init`.
177
+ - Asset ids must be kebab-case and must exist in the cached registry.
178
+ - `--force` only replaces destinations that are already Dewey-managed. It
179
+ refuses to overwrite unrelated user-created files or directories.
180
+ - `--dry-run` is the safest way to preview what `init` would write.
181
+ - In v0, the asset source is local. Set `DEWEYOU_AGENTS_SOURCE` to the
182
+ `deweyou/agents` checkout you want to use.
183
+
184
+ ## Development
185
+
186
+ The CLI source and tests are written in TypeScript. Vite+ builds the published
187
+ JavaScript files into `dist/`.
188
+
189
+ ```bash
190
+ npm run typecheck
191
+ npm test
192
+ npm run test:coverage
193
+ npm run build
194
+ npm pack --dry-run
195
+ ```
196
+
197
+ ## Release
198
+
199
+ Merging CLI package changes into `main` runs the release workflow. It typechecks,
200
+ runs tests, verifies the package with `npm pack --dry-run`, infers the next
201
+ version from conventional commit messages, prepends [CHANGELOG.md](./CHANGELOG.md),
202
+ tags `cli-vX.Y.Z`, and publishes `deweyou-cli` to npm.
203
+
204
+ Release commit rules:
205
+
206
+ - `feat:` creates a minor release.
207
+ - `fix:`, `perf:`, and `refactor:` create a patch release.
208
+ - `!` or `BREAKING CHANGE` creates a major release.
209
+ - `docs:` entries are included in the changelog only when another releasable CLI
210
+ commit is present.
211
+ - `test:` and `chore:` do not publish by themselves.
212
+
213
+ ## Relationship To `deweyou/agents`
214
+
215
+ `deweyou/agents` continues to provide the actual skills, rules, and asset
216
+ validation workflow. The CLI generates the cache registry during
217
+ `deweyou-cli agent update`.
218
+
219
+ `deweyou-cli` does not replace those assets. It gives every repository a
220
+ repeatable way to choose and wire the assets it wants, without manually copying
221
+ or linking the same files again and again.
@@ -0,0 +1,231 @@
1
+ import { cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
2
+ import { execFile } from "node:child_process";
3
+ import { homedir } from "node:os";
4
+ import { basename, dirname, join, relative } from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { createHash } from "node:crypto";
7
+ import yaml from "js-yaml";
8
+ //#region src/cli/registry.ts
9
+ const { load: loadYaml } = yaml;
10
+ async function loadRegistry(root) {
11
+ return { assets: {
12
+ skills: await scanSkills(root),
13
+ rules: await scanRules(root)
14
+ } };
15
+ }
16
+ async function scanSkills(root) {
17
+ const skillsDir = join(root, "skills");
18
+ const assets = {};
19
+ for (const entry of await safeReaddir(skillsDir)) {
20
+ if (!entry.isDirectory()) continue;
21
+ const skillPath = join(skillsDir, entry.name);
22
+ const frontmatter = await parseFrontmatter(join(skillPath, "SKILL.md"));
23
+ const name = frontmatter.name;
24
+ if (name !== entry.name) throw new Error(`skill ${entry.name} name must match frontmatter`);
25
+ assets[name] = {
26
+ path: toPosix(relative(root, skillPath)),
27
+ description: frontmatter.description,
28
+ hash: await hashDirectory(skillPath),
29
+ tags: frontmatter.tags
30
+ };
31
+ }
32
+ return sortObject(assets);
33
+ }
34
+ async function scanRules(root) {
35
+ const rulesDir = join(root, "rules");
36
+ const assets = {};
37
+ for (const entry of await safeReaddir(rulesDir)) {
38
+ if (!entry.isFile()) continue;
39
+ if (entry.name === "README.md") continue;
40
+ if (!entry.name.endsWith(".md")) continue;
41
+ const rulePath = join(rulesDir, entry.name);
42
+ const frontmatter = await parseFrontmatter(rulePath);
43
+ const expectedName = basename(entry.name, ".md");
44
+ const name = frontmatter.name;
45
+ if (name !== expectedName) throw new Error(`rule ${expectedName} name must match frontmatter`);
46
+ assets[name] = {
47
+ path: toPosix(relative(root, rulePath)),
48
+ description: frontmatter.description,
49
+ hash: await hashFile(rulePath),
50
+ tags: frontmatter.tags
51
+ };
52
+ }
53
+ return sortObject(assets);
54
+ }
55
+ async function parseFrontmatter(path) {
56
+ const match = (await readFile(path, "utf8")).match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
57
+ if (!match) throw new Error(`${path} must include YAML frontmatter`);
58
+ const frontmatter = loadYaml(match[1]);
59
+ if (!isPlainObject(frontmatter)) throw new Error(`${path} frontmatter must be an object`);
60
+ return {
61
+ name: requiredString(frontmatter.name, `${path} frontmatter name`),
62
+ description: requiredString(frontmatter.description, `${path} frontmatter description`),
63
+ tags: optionalTags(frontmatter.tags, `${path} frontmatter tags`)
64
+ };
65
+ }
66
+ function requiredString(value, label) {
67
+ if (typeof value !== "string" || value.length === 0) throw new Error(`${label} must be a non-empty string`);
68
+ return value;
69
+ }
70
+ function optionalTags(value, label) {
71
+ if (value === void 0) return [];
72
+ if (!Array.isArray(value)) throw new Error(`${label} must be an array`);
73
+ return value.map((tag, index) => requiredString(tag, `${label}[${index}]`));
74
+ }
75
+ async function safeReaddir(path) {
76
+ try {
77
+ return await readdir(path, { withFileTypes: true });
78
+ } catch (error) {
79
+ if (!(error instanceof Error) || !("code" in error)) throw error;
80
+ if (error.code === "ENOENT") return [];
81
+ throw error;
82
+ }
83
+ }
84
+ async function hashDirectory(dir) {
85
+ const files = await collectFiles(dir);
86
+ const hash = createHash("sha256");
87
+ for (const file of files) {
88
+ const relativePath = toPosix(relative(dir, file));
89
+ hash.update(relativePath);
90
+ hash.update("\0");
91
+ hash.update(await readFile(file));
92
+ hash.update("\0");
93
+ }
94
+ return `sha256:${hash.digest("hex")}`;
95
+ }
96
+ async function collectFiles(dir) {
97
+ const files = [];
98
+ for (const entry of await safeReaddir(dir)) {
99
+ const path = join(dir, entry.name);
100
+ if (entry.isDirectory()) files.push(...await collectFiles(path));
101
+ else if (entry.isFile()) files.push(path);
102
+ }
103
+ return files.sort((a, b) => a.localeCompare(b));
104
+ }
105
+ async function hashFile(path) {
106
+ return `sha256:${createHash("sha256").update(await readFile(path)).digest("hex")}`;
107
+ }
108
+ function sortObject(object) {
109
+ return Object.fromEntries(Object.entries(object).sort(([a], [b]) => a.localeCompare(b)));
110
+ }
111
+ function toPosix(path) {
112
+ return path.split("\\").join("/");
113
+ }
114
+ function isPlainObject(value) {
115
+ return typeof value === "object" && value !== null && !Array.isArray(value);
116
+ }
117
+ //#endregion
118
+ //#region src/cli/manifest.ts
119
+ async function readJson(path, fallback) {
120
+ try {
121
+ return JSON.parse(await readFile(path, "utf8"));
122
+ } catch (error) {
123
+ if (!(error instanceof Error) || !("code" in error)) throw error;
124
+ if (error.code === "ENOENT" && arguments.length > 1) return fallback;
125
+ throw error;
126
+ }
127
+ }
128
+ async function writeJson(path, value) {
129
+ await mkdir(dirname(path), { recursive: true });
130
+ await writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
131
+ }
132
+ //#endregion
133
+ //#region src/cli/source.ts
134
+ const DEFAULT_SOURCE = "deweyou/agents";
135
+ function resolveSourceRoot({ env = process.env } = {}) {
136
+ if (env.DEWEYOU_AGENTS_SOURCE) return env.DEWEYOU_AGENTS_SOURCE;
137
+ throw new Error(`No local asset source configured. Set DEWEYOU_AGENTS_SOURCE to a checkout of ${DEFAULT_SOURCE}.`);
138
+ }
139
+ //#endregion
140
+ //#region src/cli/cache.ts
141
+ const CLI_VERSION = "0.1.0";
142
+ const execFileAsync = promisify(execFile);
143
+ function cachePaths({ homeDir = homedir() } = {}) {
144
+ const root = join(homeDir, ".deweyou", "agents");
145
+ return {
146
+ root,
147
+ assetsRoot: join(root, "assets"),
148
+ manifestPath: join(root, "manifest.json")
149
+ };
150
+ }
151
+ async function updateCache({ homeDir = homedir(), sourceRoot, cliVersion = CLI_VERSION } = {}) {
152
+ if (!sourceRoot) throw new Error("sourceRoot is required to update the Dewey assets cache");
153
+ const registry = await loadRegistry(sourceRoot);
154
+ const source = await resolveSourceSnapshot(sourceRoot);
155
+ const paths = cachePaths({ homeDir });
156
+ const tempAssetsRoot = join(paths.root, `assets.tmp-${process.pid}-${Date.now()}`);
157
+ try {
158
+ await mkdir(paths.root, { recursive: true });
159
+ await rm(tempAssetsRoot, {
160
+ recursive: true,
161
+ force: true
162
+ });
163
+ await mkdir(tempAssetsRoot, { recursive: true });
164
+ await writeJson(join(tempAssetsRoot, "registry.json"), registry);
165
+ await copyAssetDirectory(sourceRoot, tempAssetsRoot, "skills");
166
+ await copyAssetDirectory(sourceRoot, tempAssetsRoot, "rules");
167
+ await rm(paths.assetsRoot, {
168
+ recursive: true,
169
+ force: true
170
+ });
171
+ await rename(tempAssetsRoot, paths.assetsRoot);
172
+ const manifest = {
173
+ source,
174
+ cliVersion,
175
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
176
+ };
177
+ await writeJson(paths.manifestPath, manifest);
178
+ return manifest;
179
+ } catch (error) {
180
+ await rm(tempAssetsRoot, {
181
+ recursive: true,
182
+ force: true
183
+ });
184
+ throw error;
185
+ }
186
+ }
187
+ async function runUpdate(flags = {}) {
188
+ const sourceRoot = flags.sourceRoot ?? resolveSourceRoot();
189
+ const manifest = await updateCache({
190
+ homeDir: flags.homeDir,
191
+ sourceRoot,
192
+ cliVersion: CLI_VERSION
193
+ });
194
+ const sourceLabel = manifest.source.commit ?? "local files";
195
+ console.log(`Updated Dewey agent assets from ${sourceLabel}`);
196
+ return manifest;
197
+ }
198
+ async function resolveSourceSnapshot(sourceRoot) {
199
+ return {
200
+ root: sourceRoot,
201
+ commit: await resolveGitCommit(sourceRoot)
202
+ };
203
+ }
204
+ async function resolveGitCommit(sourceRoot) {
205
+ try {
206
+ const { stdout } = await execFileAsync("git", [
207
+ "-C",
208
+ sourceRoot,
209
+ "rev-parse",
210
+ "HEAD"
211
+ ]);
212
+ return stdout.trim() || null;
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
217
+ async function copyAssetDirectory(sourceRoot, assetsRoot, name) {
218
+ const source = join(sourceRoot, name);
219
+ try {
220
+ await stat(source);
221
+ } catch (error) {
222
+ /* v8 ignore next -- defensive guard for non-Node filesystem errors */
223
+ if (!(error instanceof Error) || !("code" in error)) throw error;
224
+ if (error.code === "ENOENT") return;
225
+ /* v8 ignore next -- stat() error variants are surfaced unchanged */
226
+ throw error;
227
+ }
228
+ await cp(source, join(assetsRoot, name), { recursive: true });
229
+ }
230
+ //#endregion
231
+ export { cachePaths, writeJson as n, runUpdate, readJson as t };
@@ -0,0 +1,207 @@
1
+ import { t as usageError } from "./deweyou.mjs";
2
+ import { cachePaths, t as readJson } from "./cache-CBEKVQeD.mjs";
3
+ import { readFile, stat } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+ //#region src/cli/context.ts
7
+ const MISSING_REPO_MANIFEST = "This repository has not been initialized. Run `deweyou-cli agent init`.";
8
+ const MISSING_CACHE_REGISTRY = "Dewey asset cache is missing. Run `deweyou-cli agent update`.";
9
+ const VALID_FORMATS = new Set(["markdown", "json"]);
10
+ async function resolveContext({ repoRoot = process.cwd(), homeDir = homedir() } = {}) {
11
+ const manifest = await readJson(join(repoRoot, ".agents", "manifest.json"), null);
12
+ if (!manifest) return {
13
+ ok: false,
14
+ error: MISSING_REPO_MANIFEST
15
+ };
16
+ const paths = cachePaths({ homeDir });
17
+ const registry = await readRegistry(join(paths.assetsRoot, "registry.json"));
18
+ const cacheManifest = await readJson(paths.manifestPath, null);
19
+ if (!registry) return {
20
+ ok: false,
21
+ error: MISSING_CACHE_REGISTRY
22
+ };
23
+ const missing = findMissingAssets(manifest, registry);
24
+ if (missing.length > 0) return {
25
+ ok: false,
26
+ error: `Repository manifest references assets missing from the Dewey registry: ${missing.join(", ")}`
27
+ };
28
+ const context = {
29
+ ok: true,
30
+ repo: {
31
+ root: repoRoot,
32
+ mode: manifest.mode
33
+ },
34
+ runtime: {
35
+ sourceCommit: cacheManifest?.source?.commit ?? null,
36
+ repoSourceCommit: manifest.source?.commit ?? null
37
+ },
38
+ assets: {
39
+ skills: selectedAssets({
40
+ names: manifest.assets?.skills ?? [],
41
+ registryAssets: registry.assets?.skills ?? {},
42
+ manifestAssets: manifest.assetSnapshot?.skills ?? {},
43
+ useManifestMetadata: manifest.mode === "copy",
44
+ pathForAsset: (name, asset) => skillPath({
45
+ repoRoot,
46
+ manifest,
47
+ name,
48
+ asset
49
+ })
50
+ }),
51
+ rules: selectedAssets({
52
+ names: manifest.assets?.rules ?? [],
53
+ registryAssets: registry.assets?.rules ?? {},
54
+ manifestAssets: manifest.assetSnapshot?.rules ?? {},
55
+ useManifestMetadata: manifest.mode === "copy",
56
+ pathForAsset: (name, asset) => rulePath({
57
+ repoRoot,
58
+ manifest,
59
+ name,
60
+ asset
61
+ })
62
+ })
63
+ },
64
+ _notice: {
65
+ update: sourceNotice(cacheManifest, manifest),
66
+ assets: selectedAssetNotice(manifest, registry)
67
+ }
68
+ };
69
+ const missingPaths = await findMissingPaths(context);
70
+ if (missingPaths.length > 0) return {
71
+ ok: false,
72
+ error: `Selected Dewey asset paths are missing: ${missingPaths.join(", ")}`
73
+ };
74
+ return context;
75
+ }
76
+ function renderMarkdownContext(context) {
77
+ if (context.ok === false) return `# Dewey Agent Context
78
+
79
+ ${context.error}
80
+ `;
81
+ return `# Dewey Agent Context
82
+
83
+ ## Required Protocol
84
+
85
+ - Use the active skills and rules listed here for this repository.
86
+ - Read only the referenced asset files when their instructions are needed.
87
+ - Do not dump full skill or rule bodies into chat context.
88
+
89
+ ## Active Skills
90
+
91
+ ${renderAssets(context.assets.skills)}
92
+
93
+ ## Active Rules
94
+
95
+ ${renderAssets(context.assets.rules)}
96
+
97
+ ## Runtime Notices
98
+
99
+ ${renderNotices(context)}
100
+ `;
101
+ }
102
+ async function runContext(flags = {}) {
103
+ const format = flags.format ?? "markdown";
104
+ if (!VALID_FORMATS.has(format)) throw usageError("format must be one of markdown or json");
105
+ const context = await resolveContext({
106
+ repoRoot: flags.repoRoot ?? process.cwd(),
107
+ homeDir: flags.homeDir ?? homedir()
108
+ });
109
+ if (format === "json") console.log(JSON.stringify(context, null, 2));
110
+ else console.log(renderMarkdownContext(context));
111
+ return context;
112
+ }
113
+ async function readRegistry(path) {
114
+ try {
115
+ const registry = JSON.parse(await readFile(path, "utf8"));
116
+ return {
117
+ ...registry,
118
+ assets: {
119
+ skills: registry.assets?.skills ?? {},
120
+ rules: registry.assets?.rules ?? {}
121
+ }
122
+ };
123
+ } catch (error) {
124
+ /* v8 ignore next -- defensive guard for non-Node filesystem errors */
125
+ if (!(error instanceof Error) || !("code" in error)) throw error;
126
+ if (error.code === "ENOENT") return null;
127
+ /* v8 ignore next -- non-missing read errors should bubble to the caller */
128
+ throw error;
129
+ }
130
+ }
131
+ function findMissingAssets(manifest, registry) {
132
+ return [...(manifest.assets?.skills ?? []).filter((name) => !registry.assets.skills[name]).map((name) => `skill:${name}`), ...(manifest.assets?.rules ?? []).filter((name) => !registry.assets.rules[name]).map((name) => `rule:${name}`)];
133
+ }
134
+ function selectedAssets({ names, registryAssets, manifestAssets, useManifestMetadata, pathForAsset }) {
135
+ return names.map((name) => {
136
+ const asset = registryAssets[name];
137
+ const metadata = useManifestMetadata && manifestAssets[name] ? manifestAssets[name] : asset;
138
+ return {
139
+ name,
140
+ description: metadata.description,
141
+ hash: metadata.hash,
142
+ path: pathForAsset(name, asset)
143
+ };
144
+ });
145
+ }
146
+ function skillPath({ repoRoot, manifest, name, asset }) {
147
+ if (manifest.mode === "pointer") return join(manifest.cacheRoot, asset.path, "SKILL.md");
148
+ return join(repoRoot, ".agents", "skills", name, "SKILL.md");
149
+ }
150
+ function rulePath({ repoRoot, manifest, name, asset }) {
151
+ if (manifest.mode === "pointer") return join(manifest.cacheRoot, asset.path);
152
+ return join(repoRoot, ".agents", "rules", `${name}.md`);
153
+ }
154
+ function renderAssets(assets) {
155
+ if (assets.length === 0) return "- None selected.";
156
+ return assets.map((asset) => `- ${asset.name} - ${asset.description}\n Hash: ${asset.hash}\n Path: ${asset.path}`).join("\n");
157
+ }
158
+ function sourceNotice(cacheManifest, manifest) {
159
+ const current = cacheManifest?.source?.commit ?? null;
160
+ const initialized = manifest.source?.commit ?? null;
161
+ if (!current || !initialized || current === initialized) return null;
162
+ return `Dewey asset cache is at commit ${current}; this repo was initialized with ${initialized}. Run \`deweyou-cli agent init\` to refresh selected assets.`;
163
+ }
164
+ function selectedAssetNotice(manifest, registry) {
165
+ const changed = [...changedAssets({
166
+ prefix: "skill",
167
+ names: manifest.assets?.skills ?? [],
168
+ registryAssets: registry.assets?.skills ?? {},
169
+ manifestAssets: manifest.assetSnapshot?.skills ?? {}
170
+ }), ...changedAssets({
171
+ prefix: "rule",
172
+ names: manifest.assets?.rules ?? [],
173
+ registryAssets: registry.assets?.rules ?? {},
174
+ manifestAssets: manifest.assetSnapshot?.rules ?? {}
175
+ })];
176
+ if (changed.length === 0) return null;
177
+ return `Selected Dewey assets changed in cache: ${changed.join(", ")}. Run \`deweyou-cli agent init\` to refresh.`;
178
+ }
179
+ function changedAssets({ prefix, names, registryAssets, manifestAssets }) {
180
+ return names.filter((name) => {
181
+ const current = registryAssets[name]?.hash;
182
+ const initialized = manifestAssets[name]?.hash;
183
+ return Boolean(current && initialized && current !== initialized);
184
+ }).map((name) => `${prefix}:${name}`);
185
+ }
186
+ function renderNotices(context) {
187
+ const notices = [context._notice.update, context._notice.assets].filter(Boolean);
188
+ if (notices.length === 0) return "- None.";
189
+ return notices.map((notice) => `- ${notice}`).join("\n");
190
+ }
191
+ async function findMissingPaths(context) {
192
+ const paths = [...context.assets.skills.map((asset) => asset.path), ...context.assets.rules.map((asset) => asset.path)];
193
+ return (await Promise.all(paths.map(async (path) => {
194
+ try {
195
+ await stat(path);
196
+ return null;
197
+ } catch (error) {
198
+ /* v8 ignore next -- defensive guard for non-Node filesystem errors */
199
+ if (!(error instanceof Error) || !("code" in error)) throw error;
200
+ if (error.code === "ENOENT") return path;
201
+ /* v8 ignore next -- non-missing stat errors should bubble to the caller */
202
+ throw error;
203
+ }
204
+ }))).filter((path) => Boolean(path));
205
+ }
206
+ //#endregion
207
+ export { runContext };