@noodleseed/one 0.72.0 → 0.73.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.
Files changed (30) hide show
  1. package/dist/agent-skills-state.d.ts +40 -0
  2. package/dist/agent-skills-state.d.ts.map +1 -0
  3. package/dist/agent-skills-state.js +184 -0
  4. package/dist/agent-skills-state.js.map +1 -0
  5. package/dist/agents.d.ts +15 -4
  6. package/dist/agents.d.ts.map +1 -1
  7. package/dist/agents.js +196 -76
  8. package/dist/agents.js.map +1 -1
  9. package/dist/skills-update.d.ts +1 -0
  10. package/dist/skills-update.d.ts.map +1 -1
  11. package/dist/skills-update.js +81 -5
  12. package/dist/skills-update.js.map +1 -1
  13. package/node_modules/@noodle-borg/agent-kit/dist/behavior-skills.d.ts +20 -0
  14. package/node_modules/@noodle-borg/agent-kit/dist/behavior-skills.d.ts.map +1 -0
  15. package/node_modules/@noodle-borg/agent-kit/dist/behavior-skills.js +280 -0
  16. package/node_modules/@noodle-borg/agent-kit/dist/behavior-skills.js.map +1 -0
  17. package/node_modules/@noodle-borg/agent-kit/dist/index.d.ts +18 -4
  18. package/node_modules/@noodle-borg/agent-kit/dist/index.d.ts.map +1 -1
  19. package/node_modules/@noodle-borg/agent-kit/dist/index.js +57 -46
  20. package/node_modules/@noodle-borg/agent-kit/dist/index.js.map +1 -1
  21. package/node_modules/@noodle-borg/agent-kit/dist/skill-registry.d.ts +31 -0
  22. package/node_modules/@noodle-borg/agent-kit/dist/skill-registry.d.ts.map +1 -0
  23. package/node_modules/@noodle-borg/agent-kit/dist/skill-registry.js +116 -0
  24. package/node_modules/@noodle-borg/agent-kit/dist/skill-registry.js.map +1 -0
  25. package/node_modules/@noodle-borg/agent-kit/dist/skill-router.d.ts +3 -3
  26. package/node_modules/@noodle-borg/agent-kit/dist/skill-router.d.ts.map +1 -1
  27. package/node_modules/@noodle-borg/agent-kit/dist/skill-router.js +35 -25
  28. package/node_modules/@noodle-borg/agent-kit/dist/skill-router.js.map +1 -1
  29. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  30. package/package.json +1 -1
@@ -0,0 +1,40 @@
1
+ import { type AgentTarget } from '@noodle-borg/agent-kit';
2
+ export interface ManagedSkillFileInput {
3
+ readonly path: string;
4
+ readonly content: string;
5
+ readonly skill: string;
6
+ readonly skillVersion: string;
7
+ }
8
+ export interface ManagedSkillFileAction {
9
+ readonly path: string;
10
+ readonly target: AgentTarget;
11
+ readonly action: 'created' | 'unchanged' | 'overwritten' | 'removed' | 'skipped';
12
+ readonly reason?: 'user-edited-removed-generated-file';
13
+ }
14
+ export interface ManagedSkillStateFile {
15
+ readonly path: string;
16
+ readonly sha256: string;
17
+ readonly skill: string;
18
+ readonly skillVersion: string;
19
+ }
20
+ export interface ManagedSkillState {
21
+ readonly schemaVersion: 1;
22
+ readonly packageVersion: string;
23
+ readonly target: AgentTarget;
24
+ readonly files: readonly ManagedSkillStateFile[];
25
+ }
26
+ export declare function managedSkillsStatePath(target: AgentTarget): string;
27
+ export declare function assertManagedSkillFilesSafe(input: {
28
+ readonly project: string;
29
+ readonly target: AgentTarget;
30
+ readonly files: readonly Pick<ManagedSkillFileInput, 'path'>[];
31
+ }): void;
32
+ export declare function reconcileManagedSkillFiles(input: {
33
+ readonly project: string;
34
+ readonly target: AgentTarget;
35
+ readonly packageVersion: string;
36
+ readonly files: readonly ManagedSkillFileInput[];
37
+ readonly write: boolean;
38
+ }): readonly ManagedSkillFileAction[];
39
+ export declare function readManagedSkillState(project: string, target: AgentTarget): ManagedSkillState | undefined;
40
+ //# sourceMappingURL=agent-skills-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-skills-state.d.ts","sourceRoot":"","sources":["../src/agent-skills-state.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,wBAAwB,CAAC;AAEzE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,aAAa,GAAG,SAAS,GAAG,SAAS,CAAC;IACjF,QAAQ,CAAC,MAAM,CAAC,EAAE,oCAAoC,CAAC;CACxD;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,qBAAqB,EAAE,CAAC;CAClD;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAElE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,SAAS,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,EAAE,CAAC;CAChE,GAAG,IAAI,CASP;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB,GAAG,SAAS,sBAAsB,EAAE,CAmEpC;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,WAAW,GAClB,iBAAiB,GAAG,SAAS,CAE/B"}
@@ -0,0 +1,184 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { contentSha256 } from '@noodle-borg/agent-kit';
5
+ export function managedSkillsStatePath(target) {
6
+ return `${skillRoot(target)}/.noodle-managed.json`;
7
+ }
8
+ export function assertManagedSkillFilesSafe(input) {
9
+ assertManagedPathSafe(input.project, skillRoot(input.target));
10
+ assertManagedPathSafe(input.project, managedSkillsStatePath(input.target));
11
+ for (const file of input.files) {
12
+ if (!isOwnedPath(input.target, file.path)) {
13
+ throw new Error(`invalid managed skill path: ${file.path}`);
14
+ }
15
+ assertManagedPathSafe(input.project, file.path);
16
+ }
17
+ }
18
+ export function reconcileManagedSkillFiles(input) {
19
+ const files = validateInputs(input.target, input.files);
20
+ if (input.write) {
21
+ assertManagedSkillFilesSafe({
22
+ project: input.project,
23
+ target: input.target,
24
+ files,
25
+ });
26
+ }
27
+ const nextPaths = new Set(files.map((file) => file.path));
28
+ const previous = readState(input.project, input.target);
29
+ if (input.write && previous !== undefined) {
30
+ for (const file of previous.files)
31
+ assertManagedPathSafe(input.project, file.path);
32
+ }
33
+ const actions = files.map((file) => {
34
+ const full = join(input.project, file.path);
35
+ const existing = existsSync(full) ? readFileSync(full, 'utf8') : undefined;
36
+ return {
37
+ path: file.path,
38
+ target: input.target,
39
+ action: existing === undefined
40
+ ? 'created'
41
+ : existing === file.content
42
+ ? 'unchanged'
43
+ : 'overwritten',
44
+ };
45
+ });
46
+ const removable = [];
47
+ for (const file of previous?.files ?? []) {
48
+ if (nextPaths.has(file.path) || !isOwnedPath(input.target, file.path))
49
+ continue;
50
+ const full = join(input.project, file.path);
51
+ if (!existsSync(full))
52
+ continue;
53
+ if (contentSha256(readFileSync(full, 'utf8')) === file.sha256) {
54
+ removable.push(file.path);
55
+ actions.push({ path: file.path, target: input.target, action: 'removed' });
56
+ }
57
+ else {
58
+ actions.push({
59
+ path: file.path,
60
+ target: input.target,
61
+ action: 'skipped',
62
+ reason: 'user-edited-removed-generated-file',
63
+ });
64
+ }
65
+ }
66
+ if (!input.write)
67
+ return actions;
68
+ for (const file of files) {
69
+ const action = actions.find((candidate) => candidate.path === file.path)?.action;
70
+ if (action !== 'unchanged')
71
+ atomicWrite(join(input.project, file.path), file.content);
72
+ }
73
+ for (const path of removable)
74
+ rmSync(join(input.project, path), { force: true });
75
+ const state = {
76
+ schemaVersion: 1,
77
+ packageVersion: input.packageVersion,
78
+ target: input.target,
79
+ files: files.map((file) => ({
80
+ path: file.path,
81
+ sha256: contentSha256(file.content),
82
+ skill: file.skill,
83
+ skillVersion: file.skillVersion,
84
+ })),
85
+ };
86
+ atomicWrite(join(input.project, managedSkillsStatePath(input.target)), `${JSON.stringify(state, null, 2)}\n`);
87
+ return actions;
88
+ }
89
+ export function readManagedSkillState(project, target) {
90
+ return readState(project, target);
91
+ }
92
+ function validateInputs(target, files) {
93
+ const paths = new Set();
94
+ for (const file of files) {
95
+ if (!isOwnedPath(target, file.path) || paths.has(file.path)) {
96
+ throw new Error(`invalid or duplicate managed skill path: ${file.path}`);
97
+ }
98
+ if (!file.path.startsWith(`${skillRoot(target)}/${file.skill}/`)) {
99
+ throw new Error(`managed skill identity does not match path: ${file.path}`);
100
+ }
101
+ paths.add(file.path);
102
+ }
103
+ return files;
104
+ }
105
+ function readState(project, target) {
106
+ try {
107
+ const value = JSON.parse(readFileSync(join(project, managedSkillsStatePath(target)), 'utf8'));
108
+ if (!isRecord(value) || value.schemaVersion !== 1 || value.target !== target)
109
+ return undefined;
110
+ if (typeof value.packageVersion !== 'string' || !Array.isArray(value.files))
111
+ return undefined;
112
+ const files = [];
113
+ const paths = new Set();
114
+ for (const file of value.files) {
115
+ if (!isRecord(file) ||
116
+ typeof file.path !== 'string' ||
117
+ typeof file.sha256 !== 'string' ||
118
+ typeof file.skill !== 'string' ||
119
+ typeof file.skillVersion !== 'string' ||
120
+ !/^[a-f0-9]{64}$/.test(file.sha256) ||
121
+ !isOwnedPath(target, file.path) ||
122
+ paths.has(file.path)) {
123
+ return undefined;
124
+ }
125
+ paths.add(file.path);
126
+ files.push({
127
+ path: file.path,
128
+ sha256: file.sha256,
129
+ skill: file.skill,
130
+ skillVersion: file.skillVersion,
131
+ });
132
+ }
133
+ return { schemaVersion: 1, packageVersion: value.packageVersion, target, files };
134
+ }
135
+ catch {
136
+ return undefined;
137
+ }
138
+ }
139
+ function atomicWrite(path, content) {
140
+ mkdirSync(dirname(path), { recursive: true });
141
+ const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
142
+ writeFileSync(temporary, content);
143
+ renameSync(temporary, path);
144
+ }
145
+ function assertManagedPathSafe(project, relativePath) {
146
+ const absoluteProject = resolve(project);
147
+ const segments = relativePath.split('/');
148
+ let current = absoluteProject;
149
+ for (let index = 0; index <= segments.length; index += 1) {
150
+ const stats = lstatIfPresent(current);
151
+ if (stats?.isSymbolicLink()) {
152
+ throw new Error(`refusing managed skill path with symbolic link: ${current}`);
153
+ }
154
+ if (index < segments.length && stats !== undefined && !stats.isDirectory()) {
155
+ throw new Error(`refusing managed skill path with non-directory ancestor: ${current}`);
156
+ }
157
+ if (index < segments.length)
158
+ current = join(current, segments[index] ?? '');
159
+ }
160
+ }
161
+ function lstatIfPresent(path) {
162
+ try {
163
+ return lstatSync(path);
164
+ }
165
+ catch (error) {
166
+ if (isRecord(error) && error.code === 'ENOENT')
167
+ return undefined;
168
+ throw error;
169
+ }
170
+ }
171
+ function skillRoot(target) {
172
+ return target === 'codex' ? '.agents/skills' : '.claude/skills';
173
+ }
174
+ function isOwnedPath(target, path) {
175
+ const prefix = `${skillRoot(target)}/`;
176
+ return (path.startsWith(prefix) &&
177
+ !path.includes('\\') &&
178
+ path.split('/').every((segment) => segment !== '' && segment !== '.' && segment !== '..') &&
179
+ path !== managedSkillsStatePath(target));
180
+ }
181
+ function isRecord(value) {
182
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
183
+ }
184
+ //# sourceMappingURL=agent-skills-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-skills-state.js","sourceRoot":"","sources":["../src/agent-skills-state.ts"],"names":[],"mappings":"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"}
package/dist/agents.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type AgentTarget } from '@noodle-borg/agent-kit';
1
+ import { type AgentTarget, type SkillRegistry } from '@noodle-borg/agent-kit';
2
2
  import { type JsonError } from './commands/output.js';
3
3
  import { type ConfigLocation } from './config.js';
4
4
  interface AgentArgs {
@@ -14,13 +14,24 @@ interface AgentArgs {
14
14
  type ResolvedAgentArgs = AgentArgs & {
15
15
  readonly agents: readonly AgentTarget[];
16
16
  };
17
- type AgentSetupInput = Omit<ResolvedAgentArgs, 'action' | 'refresh'> & {
18
- readonly skillsOverride?: ReadonlyMap<string, string>;
17
+ export type AgentSetupInput = Omit<ResolvedAgentArgs, 'action' | 'refresh'> & {
18
+ readonly skillsOverride?: ResolvedSkillsSnapshot;
19
+ readonly registry?: SkillRegistry;
19
20
  };
21
+ export interface ResolvedSkillsSnapshot {
22
+ readonly packageVersion: string;
23
+ readonly files: readonly {
24
+ readonly path: string;
25
+ readonly content: string;
26
+ readonly target: AgentTarget;
27
+ readonly skill: string;
28
+ readonly skillVersion: string;
29
+ }[];
30
+ }
20
31
  export interface AgentFileAction {
21
32
  readonly path: string;
22
33
  readonly target: AgentTarget;
23
- readonly action: 'created' | 'unchanged' | 'updated' | 'skipped' | 'overwritten';
34
+ readonly action: 'created' | 'unchanged' | 'updated' | 'skipped' | 'overwritten' | 'removed';
24
35
  readonly reason?: string;
25
36
  }
26
37
  export interface AgentSetupReport {
@@ -1 +1 @@
1
- {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,WAAW,EAMjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,sBAAsB,CAAC;AACrF,OAAO,EAAE,KAAK,cAAc,EAAyB,MAAM,aAAa,CAAC;AAYzE,UAAU,SAAS;IACjB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,wGAAwG;IACxG,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,KAAK,iBAAiB,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,CAAA;CAAE,CAAC;AAEjF,KAAK,eAAe,GAAG,IAAI,CAAC,iBAAiB,EAAE,QAAQ,GAAG,SAAS,CAAC,GAAG;IACrE,QAAQ,CAAC,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvD,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAC;IACjF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED,gGAAgG;AAChG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAAG,SAAS,CAOrF;AAED,wBAAsB,SAAS,CAC7B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC,MAAM,CAAC,CAajB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,gBAAgB,CAkFnE;AAED,qFAAqF;AACrF,wBAAsB,6BAA6B,CACjD,IAAI,EAAE,IAAI,CAAC,eAAe,EAAE,gBAAgB,CAAC,GAAG;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,EAC9E,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,cAAc,CAAC,EAAE,cAAc,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAmB3B"}
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"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"}
package/dist/agents.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { delimiter, dirname, join, resolve } from 'node:path';
3
- import { AGENT_KIT_VERSION, contentSha256, parseAgentTargets, reconcileManagedBlock, renderAgentFiles, skillFileMappings, } from '@noodle-borg/agent-kit';
3
+ import { AGENT_KIT_VERSION, contentSha256, parseAgentTargets, reconcileManagedBlock, renderAgentFiles, } from '@noodle-borg/agent-kit';
4
+ import { assertManagedSkillFilesSafe, readManagedSkillState, reconcileManagedSkillFiles, } from './agent-skills-state.js';
4
5
  import { printJsonFailure, printJsonOk } from './commands/output.js';
5
6
  import { configDir, readConfig } from './config.js';
6
7
  import { readPluginCompatibility } from './plugin-mode/compatibility.js';
@@ -35,27 +36,28 @@ export async function runAgents(rest, env = process.env, configLocation) {
35
36
  return 2;
36
37
  }
37
38
  export function setupAgents(args) {
38
- const projectDefaults = readResolvedProjectConfig(args.project);
39
- const link = readProjectLink(args.project);
40
- const org = link?.org ?? projectDefaults.org;
41
- const app = link?.app ?? projectDefaults.app;
42
39
  const planned = renderAgentFiles({
43
40
  targets: args.agents,
44
- project: Object.assign({}, projectDefaults.name !== undefined ? { name: projectDefaults.name } : {}, projectDefaults.entrypoint !== undefined ? { entrypoint: projectDefaults.entrypoint } : {}, projectDefaults.env !== undefined ? { env: projectDefaults.env } : {}, projectDefaults.accessMode !== undefined ? { accessMode: projectDefaults.accessMode } : {}, projectDefaults.template !== undefined ? { template: projectDefaults.template } : {}, org !== undefined ? { org } : {}, app !== undefined ? { app } : {}),
41
+ ...(args.registry !== undefined ? { registry: args.registry } : {}),
42
+ project: resolveAgentProjectMetadata(args.project),
45
43
  });
46
- // Remote (registry-fresh) skills override the bundled skill files, keyed by installed path so each
47
- // SKILL.md and reference is replaced by its own registry-verified content. The managed block stays
48
- // bundled because it is project-specific and rendered with the user's metadata.
49
- const override = args.skillsOverride;
50
- for (const file of planned) {
51
- if (file.mode === 'generated-file' && override !== undefined) {
52
- const content = override.get(file.path);
53
- if (content !== undefined)
54
- file.content = content;
44
+ // A registry-fresh snapshot replaces the bundled generated tree as one complete unit. The managed
45
+ // host block stays bundled because it is project-specific.
46
+ const generatedFiles = args.skillsOverride === undefined
47
+ ? planned.filter((file) => file.mode === 'generated-file')
48
+ : args.skillsOverride.files.filter((file) => args.agents.includes(file.target));
49
+ validateResolvedSkillsSnapshot(args.agents, generatedFiles);
50
+ if (args.write) {
51
+ for (const target of args.agents) {
52
+ assertManagedSkillFilesSafe({
53
+ project: args.project,
54
+ target,
55
+ files: generatedFiles.filter((file) => file.target === target),
56
+ });
55
57
  }
56
58
  }
57
59
  const files = [];
58
- for (const file of planned) {
60
+ for (const file of planned.filter((candidate) => candidate.mode === 'managed-block')) {
59
61
  const full = join(args.project, file.path);
60
62
  const existing = existsSync(full) ? readFileSync(full, 'utf8') : undefined;
61
63
  if (!args.write) {
@@ -66,35 +68,38 @@ export function setupAgents(args) {
66
68
  });
67
69
  continue;
68
70
  }
69
- if (file.mode === 'managed-block') {
70
- const result = reconcileManagedBlock({
71
- ...(existing !== undefined ? { existing } : {}),
72
- block: file.content,
73
- force: args.force,
74
- });
75
- if (result.action !== 'unchanged' && result.action !== 'skipped') {
71
+ const result = reconcileManagedBlock({
72
+ ...(existing !== undefined ? { existing } : {}),
73
+ block: file.content,
74
+ force: args.force,
75
+ });
76
+ if (result.action !== 'unchanged' && result.action !== 'skipped') {
77
+ if (args.write) {
76
78
  mkdirSync(dirname(full), { recursive: true });
77
79
  writeFileSync(full, result.content);
78
80
  }
79
- files.push({
80
- path: file.path,
81
- target: file.target,
82
- action: result.action,
83
- ...(result.reason !== undefined ? { reason: result.reason } : {}),
84
- });
85
- }
86
- else {
87
- const action = existing === undefined
88
- ? 'created'
89
- : existing === file.content
90
- ? 'unchanged'
91
- : 'overwritten';
92
- if (action !== 'unchanged') {
93
- mkdirSync(dirname(full), { recursive: true });
94
- writeFileSync(full, file.content);
95
- }
96
- files.push({ path: file.path, target: file.target, action });
97
81
  }
82
+ files.push({
83
+ path: file.path,
84
+ target: file.target,
85
+ action: result.action,
86
+ ...(result.reason !== undefined ? { reason: result.reason } : {}),
87
+ });
88
+ }
89
+ for (const target of args.agents) {
90
+ const generated = generatedFiles.filter((file) => file.target === target);
91
+ files.push(...reconcileManagedSkillFiles({
92
+ project: args.project,
93
+ target,
94
+ packageVersion: args.skillsOverride?.packageVersion ?? AGENT_KIT_VERSION,
95
+ files: generated.map((file) => ({
96
+ path: file.path,
97
+ content: file.content,
98
+ skill: file.skill ?? failMissingSkillIdentity(file.path),
99
+ skillVersion: file.skillVersion ?? failMissingSkillIdentity(file.path),
100
+ })),
101
+ write: args.write,
102
+ }));
98
103
  }
99
104
  return {
100
105
  ok: true,
@@ -107,6 +112,44 @@ export function setupAgents(args) {
107
112
  : [],
108
113
  };
109
114
  }
115
+ function resolveAgentProjectMetadata(project) {
116
+ const projectDefaults = readResolvedProjectConfig(project);
117
+ const link = readProjectLink(project);
118
+ const org = link?.org ?? projectDefaults.org;
119
+ const app = link?.app ?? projectDefaults.app;
120
+ return Object.assign({}, projectDefaults.name !== undefined ? { name: projectDefaults.name } : {}, projectDefaults.entrypoint !== undefined ? { entrypoint: projectDefaults.entrypoint } : {}, projectDefaults.env !== undefined ? { env: projectDefaults.env } : {}, projectDefaults.accessMode !== undefined ? { accessMode: projectDefaults.accessMode } : {}, projectDefaults.template !== undefined ? { template: projectDefaults.template } : {}, org !== undefined ? { org } : {}, app !== undefined ? { app } : {});
121
+ }
122
+ function validateResolvedSkillsSnapshot(targets, files) {
123
+ const paths = new Set();
124
+ for (const file of files) {
125
+ const expectedRoot = `${file.target === 'codex' ? '.agents' : '.claude'}/skills/${file.skill ?? ''}/`;
126
+ if (file.skill === undefined ||
127
+ !file.path.startsWith(expectedRoot) ||
128
+ file.path.includes('\\') ||
129
+ file.path
130
+ .split('/')
131
+ .some((segment) => segment === '' || segment === '.' || segment === '..') ||
132
+ paths.has(file.path)) {
133
+ throw new Error(`resolved skill snapshot has invalid or duplicate path: ${file.path}`);
134
+ }
135
+ paths.add(file.path);
136
+ }
137
+ for (const target of targets) {
138
+ const targetFiles = files.filter((file) => file.target === target);
139
+ if (targetFiles.length === 0)
140
+ throw new Error(`resolved skill snapshot is missing ${target}`);
141
+ const skills = new Set(targetFiles.map((file) => file.skill));
142
+ for (const skill of skills) {
143
+ if (skill === undefined ||
144
+ !targetFiles.some((file) => file.skill === skill && file.path.endsWith(`/skills/${skill}/SKILL.md`))) {
145
+ throw new Error(`resolved skill snapshot is missing SKILL.md for ${target}/${skill ?? '?'}`);
146
+ }
147
+ }
148
+ }
149
+ }
150
+ function failMissingSkillIdentity(path) {
151
+ throw new Error(`generated skill file is missing registry identity: ${path}`);
152
+ }
110
153
  /** Resolve the active Agent Kit profile before rendering any project agent files. */
111
154
  export async function setupAgentsWithResolvedSkills(args, env = process.env, configLocation) {
112
155
  // Interactive writes may fetch registry-latest, while plugin mode resolves the exact compatibility pin.
@@ -175,17 +218,20 @@ async function resolveSkillsOverride(refresh, configLocation, pluginRequiredVers
175
218
  });
176
219
  if (result.status !== 'ready' || result.kit === undefined)
177
220
  return undefined;
178
- // Map each publishable file (e.g. `skills/codex/references/cli-commands.md`) to its installed
179
- // destination so the override replaces every file in the tree, not just the SKILL.md.
180
- const installedByPublish = new Map(skillFileMappings().map((m) => [m.publishPath, m.installedPath]));
181
- const override = new Map();
221
+ const files = [];
182
222
  for (const file of result.kit.manifest.files) {
183
223
  const content = result.kit.files.get(file.path);
184
- const installedPath = installedByPublish.get(file.path);
185
- if (content !== undefined && installedPath !== undefined)
186
- override.set(installedPath, content);
224
+ if (content === undefined)
225
+ return undefined;
226
+ files.push({
227
+ path: file.installedPath,
228
+ content,
229
+ target: file.agentTarget,
230
+ skill: file.skill,
231
+ skillVersion: file.skillVersion,
232
+ });
187
233
  }
188
- return override;
234
+ return { packageVersion: result.kit.version, files };
189
235
  }
190
236
  async function runAgentDoctor(args, env, configLocation) {
191
237
  const pluginRequiredVersion = pluginRequiredAgentKitVersion(env);
@@ -197,26 +243,64 @@ async function runAgentDoctor(args, env, configLocation) {
197
243
  version: pluginRequiredVersion,
198
244
  })
199
245
  : undefined;
200
- const installedByPublish = new Map(skillFileMappings().map((mapping) => [mapping.publishPath, mapping.installedPath]));
201
- const pinnedExpectedByPath = new Map();
202
- if (pluginPinnedKit !== undefined) {
203
- for (const file of pluginPinnedKit.manifest.files) {
204
- const installedPath = installedByPublish.get(file.path);
205
- const content = pluginPinnedKit.files.get(file.path);
206
- if (installedPath !== undefined && content !== undefined) {
207
- pinnedExpectedByPath.set(installedPath, content);
208
- }
209
- }
210
- }
246
+ const projectMetadata = resolveAgentProjectMetadata(args.project);
211
247
  const checks = args.agents.flatMap((target) => {
212
- const files = renderAgentFiles({ targets: [target] });
213
- const expectedByPath = pluginPinnedKit !== undefined
214
- ? pinnedExpectedByPath
215
- : new Map(files.map((file) => [file.path, file.content]));
216
- const installedSkillVersion = readInstalledSkillVersion(join(args.project, target === 'codex'
217
- ? '.agents/skills/noodle-seed/SKILL.md'
218
- : '.claude/skills/noodle-seed/SKILL.md'));
219
- const shouldHashCheckReferences = installedSkillVersion === (pluginRequiredVersion ?? AGENT_KIT_VERSION);
248
+ const bundled = renderAgentFiles({ targets: [target], project: projectMetadata });
249
+ const state = readManagedSkillState(args.project, target);
250
+ const useOwnedSnapshot = pluginPinnedKit === undefined &&
251
+ state !== undefined &&
252
+ state.packageVersion !== AGENT_KIT_VERSION;
253
+ const generatedBase = pluginPinnedKit !== undefined
254
+ ? pluginPinnedKit.manifest.files
255
+ .filter((file) => file.agentTarget === target)
256
+ .map((file) => ({
257
+ path: file.installedPath,
258
+ mode: 'generated-file',
259
+ skill: file.skill,
260
+ skillVersion: file.skillVersion,
261
+ expectedSha256: file.sha256,
262
+ }))
263
+ : useOwnedSnapshot
264
+ ? state.files.map((file) => ({
265
+ path: file.path,
266
+ mode: 'generated-file',
267
+ skill: file.skill,
268
+ skillVersion: file.skillVersion,
269
+ expectedSha256: file.sha256,
270
+ }))
271
+ : bundled
272
+ .filter((file) => file.mode === 'generated-file')
273
+ .map((file) => ({
274
+ path: file.path,
275
+ mode: file.mode,
276
+ skill: file.skill ?? failMissingSkillIdentity(file.path),
277
+ skillVersion: file.skillVersion ?? failMissingSkillIdentity(file.path),
278
+ expectedSha256: contentSha256(file.content),
279
+ }));
280
+ const generated = pluginPinnedKit === undefined && state !== undefined && !useOwnedSnapshot
281
+ ? [
282
+ ...generatedBase,
283
+ ...state.files
284
+ .filter((file) => !generatedBase.some((candidate) => candidate.path === file.path))
285
+ .map((file) => ({
286
+ path: file.path,
287
+ mode: 'generated-file',
288
+ skill: file.skill,
289
+ skillVersion: file.skillVersion,
290
+ expectedSha256: file.sha256,
291
+ })),
292
+ ]
293
+ : generatedBase;
294
+ const files = [
295
+ ...bundled
296
+ .filter((file) => file.mode === 'managed-block')
297
+ .map((file) => ({
298
+ path: file.path,
299
+ mode: file.mode,
300
+ expectedContent: file.content,
301
+ })),
302
+ ...generated,
303
+ ];
220
304
  const referenceBaselineAvailable = pluginRequiredVersion === undefined ||
221
305
  pluginRequiredVersion === AGENT_KIT_VERSION ||
222
306
  pluginPinnedKit !== undefined;
@@ -236,27 +320,64 @@ async function runAgentDoctor(args, env, configLocation) {
236
320
  message: 'run noodle agents setup --write',
237
321
  };
238
322
  }
239
- // Only the router SKILL.md carries version frontmatter; bundled-version references are hash-checked.
323
+ if (file.mode === 'managed-block' && file.expectedContent !== undefined) {
324
+ const reconciliation = reconcileManagedBlock({
325
+ existing: readFileSync(path, 'utf8'),
326
+ block: file.expectedContent.trimEnd(),
327
+ });
328
+ if (reconciliation.action !== 'unchanged') {
329
+ return {
330
+ name: file.path,
331
+ level: 'WARN',
332
+ message: 'modified or stale; run noodle agents setup --write',
333
+ };
334
+ }
335
+ }
336
+ const skillVersion = file.mode === 'generated-file' && file.skill !== undefined
337
+ ? readInstalledSkillVersion(join(args.project, `${target === 'codex' ? '.agents' : '.claude'}/skills/${file.skill}/SKILL.md`))
338
+ : undefined;
240
339
  if (file.mode === 'generated-file' && file.path.endsWith('/SKILL.md')) {
241
- const pluginVersionMismatch = pluginRequiredVersion !== undefined && installedSkillVersion !== pluginRequiredVersion;
340
+ const comparisonVersion = pluginRequiredVersion ?? expectedSkillsVersion;
341
+ const pluginVersionMismatch = pluginRequiredVersion !== undefined && skillVersion !== pluginRequiredVersion;
242
342
  const staleness = pluginVersionMismatch
243
343
  ? 'stale'
244
- : skillStaleness(installedSkillVersion, expectedSkillsVersion);
344
+ : skillStaleness(skillVersion, comparisonVersion);
245
345
  const expectedVersionLabel = pluginRequiredVersion !== undefined ? 'plugin-required' : 'registry';
346
+ if (staleness !== 'stale' && file.expectedSha256 !== undefined) {
347
+ const current = contentSha256(readFileSync(path, 'utf8')) === file.expectedSha256;
348
+ if (!current) {
349
+ return {
350
+ name: file.path,
351
+ level: 'WARN',
352
+ message: 'modified; run noodle agents setup --write',
353
+ };
354
+ }
355
+ }
246
356
  return {
247
357
  name: file.path,
248
358
  level: staleness === 'stale' ? 'WARN' : 'PASS',
249
359
  message: staleness === 'stale'
250
360
  ? pluginVersionMismatch
251
- ? `installed v${installedSkillVersion ?? '?'} does not match ${expectedVersionLabel} v${expectedSkillsVersion}`
252
- : `installed v${installedSkillVersion ?? '?'} is older than ${expectedVersionLabel} v${expectedSkillsVersion}`
361
+ ? `installed v${skillVersion ?? '?'} does not match ${expectedVersionLabel} v${comparisonVersion}`
362
+ : `installed v${skillVersion ?? '?'} is older than ${expectedVersionLabel} v${comparisonVersion}`
253
363
  : staleness === 'current'
254
364
  ? `current`
255
365
  : 'present',
256
366
  };
257
367
  }
258
368
  if (file.mode === 'generated-file' &&
259
- shouldHashCheckReferences &&
369
+ !file.path.endsWith('/SKILL.md') &&
370
+ pluginRequiredVersion !== undefined &&
371
+ pluginPinnedKit === undefined &&
372
+ skillVersion === pluginRequiredVersion) {
373
+ return {
374
+ name: file.path,
375
+ level: 'WARN',
376
+ message: `plugin-required v${pluginRequiredVersion} cache unavailable; run noodle agents setup --write`,
377
+ };
378
+ }
379
+ if (file.mode === 'generated-file' &&
380
+ skillVersion === file.skillVersion &&
260
381
  !file.path.endsWith('/SKILL.md')) {
261
382
  if (!referenceBaselineAvailable) {
262
383
  return {
@@ -265,9 +386,8 @@ async function runAgentDoctor(args, env, configLocation) {
265
386
  message: `plugin-required v${pluginRequiredVersion} cache unavailable; run noodle agents setup --write`,
266
387
  };
267
388
  }
268
- const expected = expectedByPath.get(file.path);
269
389
  const actual = readFileSync(path, 'utf8');
270
- const current = expected !== undefined && contentSha256(actual) === contentSha256(expected);
390
+ const current = file.expectedSha256 !== undefined && contentSha256(actual) === file.expectedSha256;
271
391
  return {
272
392
  name: file.path,
273
393
  level: current ? 'PASS' : 'WARN',