genesis-compiler 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -213,8 +213,23 @@ skill.
213
213
 
214
214
  `.agents/skills/.genesis-managed.json` records only copies Genesis owns.
215
215
  Genesis never overwrites an unmanaged skill and preserves locally modified
216
- managed skills. Run `genesis init` after manually editing `genesis/stack.md`,
217
- or use `genesis stack add`, to synchronize selected skills.
216
+ managed skills. `genesis check` and `genesis inspect skills` distinguish outdated
217
+ managed copies from local customizations. After updating Genesis or a selected
218
+ skill package, run `genesis skills sync` to refresh only those skill directories
219
+ and their ownership manifest. Repeating it makes no changes. It does not install
220
+ packages, initialize project files, migrate contracts, rebuild indexes, or run
221
+ verification.
222
+
223
+ Projects that declare `genesis-compiler` use the skills from that installed
224
+ project dependency, including when a host embeds a different compiler. An
225
+ unprepared declared dependency is reported instead of substituting the host's
226
+ version. Other projects use the running compiler's packaged skills. Package
227
+ upgrades remain explicit dependency changes.
228
+
229
+ Inspection, context generation, and session hooks remain read-only and report
230
+ skill maintenance diagnostics. An assistant that already loaded old skills
231
+ needs its supported context refresh before using their replacement; changing
232
+ files alone does not refresh its existing context.
218
233
 
219
234
  ## Commands at a glance
220
235
 
@@ -228,9 +243,11 @@ or use `genesis stack add`, to synchronize selected skills.
228
243
  | `genesis engineering set <profile>` | Records the selected engineering profile while preserving project-specific requirements. |
229
244
  | `genesis stack list` | Lists technology components from the installed Stack catalog. |
230
245
  | `genesis stack add <piece...>` | Resolves component dependencies/conflicts, records the selection, materializes missing project contracts, synchronizes declared skills, and prints the one-time preparation prompt. It does not install application packages or run an agent. |
246
+ | `genesis skills sync` | Refreshes selected, unmodified managed skills while preserving customized and unmanaged copies. |
231
247
  | `genesis context <path...>` | Returns path-focused Program explanations, indexed functions, selected technology guidance, exact project contracts, and relevant skills. |
232
248
  | `genesis index [function-or-path...]` | Regenerates the derived code indexes and optionally searches the existing function/method inventory. |
233
249
  | `genesis inspect environment` | Reports declared resources, public defaults, missing input names, and environment-file paths without returning secret values. |
250
+ | `genesis inspect skills` | Reports selected skill freshness and ownership without changing files. |
234
251
  | `genesis inspect section <name>` | Returns one exact consumer-owned Stack section without interpreting or executing it. |
235
252
  | `genesis prompt [request...]` | Generates the ordinary implementation prompt. Use `--task` for opening, adoption, Deslop, explanation, review, and other explicit prompt tasks. |
236
253
  | `genesis verify` | Executes only the ordered finite commands in `## Verification` and records exact evidence. |
@@ -875,6 +892,7 @@ import {
875
892
  inspectCollaboration,
876
893
  inspectEngineering,
877
894
  inspectEnvironment,
895
+ inspectSkills,
878
896
  inspectStackSection,
879
897
  installCodex,
880
898
  listEngineeringProfiles,
@@ -884,6 +902,7 @@ import {
884
902
  projectTurnContext,
885
903
  setCollaboration,
886
904
  setEngineeringProfile,
905
+ syncSkills,
887
906
  verify,
888
907
  withTrustedGitRepository,
889
908
  } from 'genesis-compiler';
@@ -900,6 +919,12 @@ codebase. `installCodex()` installs the optional global discovery plugin.
900
919
  project selection used by the CLI and hosts.
901
920
  `inspectCollaboration()` and `setCollaboration()` expose the portable
902
921
  communication declaration and Genesis-owned choice expansions.
922
+ `inspectSkills()` and `syncSkills()` expose the same inspection and narrow
923
+ synchronization used by the CLI. Both accept `projectRoot` and `stackPackages`.
924
+ Inspection returns aggregate status, per-skill status for valid installed copies,
925
+ and diagnostics for missing, invalid, outdated, customized, or unmanaged skills.
926
+ Synchronization returns changed paths, preservation diagnostics, and the resulting
927
+ `inspection`. Hosts own source-write exclusion and assistant context lifecycle.
903
928
  `projectSessionContext()` composes stable project guidance with one explicitly
904
929
  supplied host-driver result. `projectTurnContext()` invokes that same optional
905
930
  driver for the separate untrusted turn lane without accepting user-message
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-compiler",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "description": "An agent-independent prompt, multi-language code-index, cleanup, and verification companion with project agent guidance.",
6
6
  "repository": {
package/src/cli.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  inspectEnvironment,
16
16
  inspectStackSection,
17
17
  inspectProject,
18
+ inspectSkills,
18
19
  installCodex,
19
20
  listEngineeringProfiles,
20
21
  listStackPieces,
@@ -22,6 +23,7 @@ import {
22
23
  migrate,
23
24
  setCollaboration,
24
25
  setEngineeringProfile,
26
+ syncSkills,
25
27
  verify,
26
28
  } from './index.js';
27
29
  import {
@@ -54,6 +56,7 @@ const USAGE = `Usage:
54
56
  genesis engineering set <profile>
55
57
  genesis stack list
56
58
  genesis stack add <piece...>
59
+ genesis skills sync
57
60
  genesis templates list
58
61
  genesis templates apply <catalogue:technology/variant>
59
62
  genesis context <path...>
@@ -61,6 +64,7 @@ const USAGE = `Usage:
61
64
  genesis migrate
62
65
  genesis inspect environment
63
66
  genesis inspect project
67
+ genesis inspect skills
64
68
  genesis inspect section <name>
65
69
  genesis prompt [request...]
66
70
  genesis prompt --task <start|adopt|work|deslop|program|blueprint|describe|review> [request...]
@@ -80,7 +84,7 @@ prompt to the agent you already use. Review all edits through the ordinary Git
80
84
  diff, then run genesis verify for the Stack's concrete checks.
81
85
  `;
82
86
 
83
- const COMMANDS = new Set(['adopt', 'check', 'codex', 'collaboration', 'context', 'engineering', 'hook', 'index', 'init', 'inspect', 'migrate', 'prompt', 'stack', 'templates', 'verify']);
87
+ const COMMANDS = new Set(['adopt', 'check', 'codex', 'collaboration', 'context', 'engineering', 'hook', 'index', 'init', 'inspect', 'migrate', 'prompt', 'skills', 'stack', 'templates', 'verify']);
84
88
 
85
89
  function parseCommand(argv) {
86
90
  if (argv.length === 0 || argv.includes('--help') || argv.includes('-h') || argv[0] === 'help') {
@@ -167,6 +171,10 @@ function parseCommand(argv) {
167
171
  if (action === 'set' && operands.length !== 2) {
168
172
  fail('CLI_ENGINEERING_PROFILE_REQUIRED', 'Command engineering set requires exactly one profile.');
169
173
  }
174
+ } else if (command === 'skills') {
175
+ if (operands.length !== 1 || operands[0] !== 'sync') {
176
+ fail('CLI_SKILLS_ACTION_REQUIRED', 'Command skills requires exactly: sync.');
177
+ }
170
178
  } else if (command === 'codex') {
171
179
  if (operands.length !== 1 || operands[0] !== 'install') {
172
180
  fail('CLI_CODEX_ACTION_REQUIRED', 'Command codex requires exactly: install.');
@@ -174,12 +182,12 @@ function parseCommand(argv) {
174
182
  } else if (command === 'context' && operands.length === 0) {
175
183
  fail('CONTEXT_PATH_REQUIRED', 'Command context requires at least one project path.');
176
184
  } else if (command === 'inspect') {
177
- const ordinaryInspection = operands.length === 1 && ['environment', 'project'].includes(operands[0]);
185
+ const ordinaryInspection = operands.length === 1 && ['environment', 'project', 'skills'].includes(operands[0]);
178
186
  const sectionInspection = operands.length >= 2 && operands[0] === 'section';
179
187
  if (!ordinaryInspection && !sectionInspection) {
180
188
  fail(
181
189
  'CLI_INSPECT_TARGET_REQUIRED',
182
- 'Command inspect requires project, environment, or section <name>.',
190
+ 'Command inspect requires project, environment, skills, or section <name>.',
183
191
  );
184
192
  }
185
193
  } else if (command === 'hook' && (operands.length !== 1 || !['discover', 'session', 'turn'].includes(operands[0]))) {
@@ -278,6 +286,12 @@ function writeInspection(result) {
278
286
  }
279
287
 
280
288
  function writeResult(command, result) {
289
+ if (command === 'inspect' && result.inspection === 'skills') {
290
+ line(process.stdout, `Agent Skills: ${result.status}`);
291
+ for (const skill of result.skills) line(process.stdout, ` - ${skill.name}: ${skill.status}`);
292
+ for (const diagnostic of result.diagnostics) line(process.stdout, `${diagnostic.code}: ${diagnostic.message}`);
293
+ return;
294
+ }
281
295
  if (command === 'templates') {
282
296
  if (result.templates) for (const entry of result.templates) line(process.stdout, `${entry.id}: ${entry.name} — ${entry.description}`);
283
297
  else {
@@ -419,6 +433,7 @@ async function execute({ command, operands, options }, { signal } = {}) {
419
433
  return operands[0] === 'list' ? listTemplates(templateOptions) : applyTemplate({ ...templateOptions, templateId: operands[1] });
420
434
  }
421
435
  if (command === 'migrate') return migrate({ projectRoot, stackPackages });
436
+ if (command === 'skills') return syncSkills({ projectRoot, stackPackages });
422
437
  if (command === 'adopt') {
423
438
  return adoptProject({ projectRoot, request: operands.join(' '), stackPackages });
424
439
  }
@@ -496,7 +511,7 @@ async function execute({ command, operands, options }, { signal } = {}) {
496
511
  }),
497
512
  };
498
513
  }
499
- const inspections = { environment: inspectEnvironment, project: inspectProject };
514
+ const inspections = { environment: inspectEnvironment, project: inspectProject, skills: inspectSkills };
500
515
  return {
501
516
  inspection: operands[0],
502
517
  ...await inspections[operands[0]]({ projectRoot, stackPackages }),
@@ -611,7 +626,12 @@ export async function runCli(argv = process.argv.slice(2)) {
611
626
  formatDiagnostic,
612
627
  );
613
628
  if (options.json) line(process.stdout, JSON.stringify(result));
614
- else writeResult(parsed.command, result);
629
+ else {
630
+ writeResult(parsed.command, result);
631
+ for (const warning of result.warnings || []) {
632
+ if (warning.code !== formatDiagnostic?.code) line(process.stderr, `WARNING: ${warning.code}: ${warning.message}`);
633
+ }
634
+ }
615
635
  return ['blocked', 'failed', 'invalid'].includes(result?.status) ? 2 : 0;
616
636
  } catch (error) {
617
637
  const diagnostic = asDiagnostic(error);
@@ -242,12 +242,12 @@ async function describeSkill(directory, source) {
242
242
  };
243
243
  }
244
244
 
245
- async function packageDirectory(packageName, resolveFrom = null) {
245
+ async function packageDirectory(packageName, resolveFrom = null, { projectOnly = false } = {}) {
246
246
  const roots = [
247
247
  ...(resolveFrom
248
248
  ? createRequire(path.join(resolveFrom, 'package.json')).resolve.paths(packageName) || []
249
249
  : []),
250
- ...(require.resolve.paths(packageName) || []),
250
+ ...(projectOnly ? [] : require.resolve.paths(packageName) || []),
251
251
  ];
252
252
  for (const modulesRoot of [...new Set(roots)]) {
253
253
  const manifest = path.join(modulesRoot, packageName, 'package.json');
@@ -274,7 +274,7 @@ async function packageDirectory(packageName, resolveFrom = null) {
274
274
  async function resolveLocator(locator) {
275
275
  const root = locator.package
276
276
  ? await packageDirectory(locator.package, locator.resolveFrom)
277
- : packageRoot;
277
+ : locator.root || packageRoot;
278
278
  const directory = path.join(root, locator.path);
279
279
  const source = locator.source || (
280
280
  locator.package
@@ -287,9 +287,23 @@ async function resolveLocator(locator) {
287
287
  }
288
288
 
289
289
  /** Resolves the three Genesis workflow skills plus authoritative selected Stack skills. */
290
- export async function projectSkillPlan(stack) {
290
+ export async function projectSkillPlan(stack, { projectRoot } = {}) {
291
+ let coreRoot = packageRoot;
292
+ if (projectRoot) {
293
+ let manifest;
294
+ try { manifest = JSON.parse(await readFile(path.join(projectRoot, 'package.json'), 'utf8')); } catch (error) {
295
+ if (!['ENOENT', 'ENOTDIR'].includes(error?.code)) throw error;
296
+ }
297
+ if (manifest?.name === 'genesis-compiler') {
298
+ coreRoot = projectRoot;
299
+ } else if (['dependencies', 'devDependencies', 'optionalDependencies'].some((field) => (
300
+ Object.hasOwn(manifest?.[field] || {}, 'genesis-compiler')
301
+ ))) {
302
+ coreRoot = await packageDirectory('genesis-compiler', projectRoot, { projectOnly: true });
303
+ }
304
+ }
291
305
  const locators = [
292
- ...CORE_SKILLS,
306
+ ...CORE_SKILLS.map((skill) => ({ ...skill, root: coreRoot })),
293
307
  ...(stack?.components || []).flatMap((component) => (
294
308
  component.skill ? [{ ...component.skill, component: component.id }] : []
295
309
  )),
@@ -386,9 +400,19 @@ function targetFiles(skill) {
386
400
  return skill.files.map(({ path: file }) => `${SKILLS_ROOT}/${skill.name}/${file}`);
387
401
  }
388
402
 
403
+ function skillStatus(installed, managed, expected) {
404
+ if (!installed) return 'missing';
405
+ if (!managed) return 'unmanaged';
406
+ if (installed.hash !== managed.hash && installed.hash !== expected.hash) return 'customized';
407
+ if (installed.hash !== expected.hash || managed.hash !== expected.hash || managed.source !== expected.source) {
408
+ return 'outdated';
409
+ }
410
+ return 'current';
411
+ }
412
+
389
413
  /** Installs only Genesis-owned or authoritative Stack-declared skills. */
390
414
  export async function syncProjectSkills({ projectRoot, stack } = {}) {
391
- const desired = await projectSkillPlan(stack);
415
+ const desired = await projectSkillPlan(stack, { projectRoot });
392
416
  const desiredNames = new Set(desired.map(({ name }) => name));
393
417
  const manifest = await readManifest(projectRoot);
394
418
  const changedFiles = [];
@@ -400,13 +424,14 @@ export async function syncProjectSkills({ projectRoot, stack } = {}) {
400
424
  const managed = Object.hasOwn(manifest.value.skills, skill.name)
401
425
  ? manifest.value.skills[skill.name]
402
426
  : null;
403
- if (!installed) {
427
+ const status = skillStatus(installed, managed, skill);
428
+ if (status === 'missing') {
404
429
  await replaceSkill(skill.directory, target);
405
430
  manifest.value.skills[skill.name] = { hash: skill.hash, source: skill.source };
406
431
  changedFiles.push(...targetFiles(skill));
407
432
  continue;
408
433
  }
409
- if (!managed) {
434
+ if (status === 'unmanaged') {
410
435
  diagnostics.push({
411
436
  code: 'AGENT_SKILL_EXTERNAL',
412
437
  message: `Preserved existing project Agent Skill ${skill.name}; Genesis does not own it.`,
@@ -414,11 +439,7 @@ export async function syncProjectSkills({ projectRoot, stack } = {}) {
414
439
  });
415
440
  continue;
416
441
  }
417
- if (installed.hash !== managed.hash) {
418
- if (installed.hash === skill.hash) {
419
- manifest.value.skills[skill.name] = { hash: skill.hash, source: skill.source };
420
- continue;
421
- }
442
+ if (status === 'customized') {
422
443
  diagnostics.push({
423
444
  code: 'AGENT_SKILL_CUSTOMIZED',
424
445
  message: `Preserved locally modified Agent Skill ${skill.name}.`,
@@ -426,10 +447,12 @@ export async function syncProjectSkills({ projectRoot, stack } = {}) {
426
447
  });
427
448
  continue;
428
449
  }
429
- if (installed.hash !== skill.hash || managed.source !== skill.source) {
430
- await replaceSkill(skill.directory, target);
450
+ if (status === 'outdated') {
451
+ if (installed.hash !== skill.hash) {
452
+ await replaceSkill(skill.directory, target);
453
+ changedFiles.push(...targetFiles(installed), ...targetFiles(skill));
454
+ }
431
455
  manifest.value.skills[skill.name] = { hash: skill.hash, source: skill.source };
432
- changedFiles.push(...new Set([...targetFiles(installed), ...targetFiles(skill)]));
433
456
  }
434
457
  }
435
458
 
@@ -469,13 +492,14 @@ export async function syncProjectSkills({ projectRoot, stack } = {}) {
469
492
 
470
493
  /** Inspects the exact project copies without changing them. */
471
494
  export async function inspectProjectSkills({ projectRoot, stack } = {}) {
472
- const desired = await projectSkillPlan(stack);
495
+ const desired = await projectSkillPlan(stack, { projectRoot });
473
496
  const manifest = await readManifest(projectRoot);
474
497
  const diagnostics = [];
475
498
  const skills = [];
476
499
  let customized = false;
477
500
  let invalid = false;
478
501
  let missing = false;
502
+ let outdated = false;
479
503
  for (const expected of desired) {
480
504
  const target = path.join(projectRoot, SKILLS_ROOT, expected.name);
481
505
  let installed;
@@ -488,7 +512,7 @@ export async function inspectProjectSkills({ projectRoot, stack } = {}) {
488
512
  missing = true;
489
513
  diagnostics.push({
490
514
  code: 'AGENT_SKILL_MISSING',
491
- message: `Project Agent Skill is missing: ${SKILLS_ROOT}/${expected.name}/SKILL.md.`,
515
+ message: `Project Agent Skill is missing: ${SKILLS_ROOT}/${expected.name}/SKILL.md. Run \`genesis skills sync\` to install selected skills.`,
492
516
  details: { name: expected.name },
493
517
  });
494
518
  continue;
@@ -496,30 +520,39 @@ export async function inspectProjectSkills({ projectRoot, stack } = {}) {
496
520
  const managed = Object.hasOwn(manifest.value.skills, expected.name)
497
521
  ? manifest.value.skills[expected.name]
498
522
  : null;
499
- if (!managed) {
523
+ const status = skillStatus(installed, managed, expected);
524
+ if (status === 'unmanaged') {
500
525
  customized = true;
501
526
  diagnostics.push({
502
527
  code: 'AGENT_SKILL_EXTERNAL',
503
528
  message: `Project Agent Skill ${expected.name} is externally managed; Genesis preserved it.`,
504
529
  details: { name: expected.name, path: `${SKILLS_ROOT}/${expected.name}/SKILL.md` },
505
530
  });
506
- } else if (installed.hash !== managed.hash && installed.hash !== expected.hash) {
531
+ } else if (status === 'customized') {
507
532
  customized = true;
508
533
  diagnostics.push({
509
534
  code: 'AGENT_SKILL_CUSTOMIZED',
510
535
  message: `Project Agent Skill ${expected.name} differs from its Genesis-managed source.`,
511
536
  details: { name: expected.name, path: `${SKILLS_ROOT}/${expected.name}/SKILL.md` },
512
537
  });
538
+ } else if (status === 'outdated') {
539
+ outdated = true;
540
+ diagnostics.push({
541
+ code: 'AGENT_SKILL_OUTDATED',
542
+ message: `Project Agent Skill ${expected.name} is outdated relative to the installed source. Run \`genesis skills sync\` to update unmodified managed skills.`,
543
+ details: { name: expected.name, path: `${SKILLS_ROOT}/${expected.name}/SKILL.md` },
544
+ });
513
545
  }
514
546
  skills.push({
515
547
  name: installed.name,
516
548
  description: installed.description,
517
549
  path: `${SKILLS_ROOT}/${installed.name}/SKILL.md`,
518
550
  component: expected.component,
551
+ status,
519
552
  });
520
553
  }
521
554
  return {
522
- status: invalid ? 'invalid' : missing ? 'missing' : customized ? 'customized' : 'current',
555
+ status: invalid ? 'invalid' : missing ? 'missing' : outdated ? 'outdated' : customized ? 'customized' : 'current',
523
556
  diagnostics,
524
557
  skills,
525
558
  };
@@ -140,7 +140,7 @@ export async function checkProject({
140
140
  if (program.status === 'missing') guidance.push('Generate an explanatory Program prompt with genesis prompt --task program.');
141
141
  if (engineering.status === 'defaulted') guidance.push('Run genesis init or genesis engineering set focused.v1 to record the default engineering approach.');
142
142
  if (program.status === 'invalid') guidance.push('Repair the Program with genesis prompt --task program.');
143
- if (skills.status === 'missing') guidance.push('Run genesis init to install selected project Agent Skills.');
143
+ if (['missing', 'outdated'].includes(skills.status)) guidance.push('Run genesis skills sync to refresh selected project Agent Skills.');
144
144
  if (skills.status === 'invalid') guidance.push('Repair the reported Agent Skill or managed-skill manifest.');
145
145
  if (missingResources.length > 0) {
146
146
  guidance.push(`${missingResources.map(({ message }) => message).join(' ')} Prompt generation remains available.`);
@@ -152,7 +152,7 @@ export async function checkProject({
152
152
 
153
153
  const needsAttention = program.status === 'missing'
154
154
  || engineering.status === 'defaulted'
155
- || skills.status === 'missing'
155
+ || ['missing', 'outdated'].includes(skills.status)
156
156
  || missingResources.length > 0
157
157
  || extensionDiagnostics.length > 0
158
158
  || ['missing', 'stale', 'unconfigured'].includes(verification.status);
@@ -1,7 +1,8 @@
1
1
  import { readCollaboration } from './collaboration.js';
2
+ import { inspectProjectSkills } from './agent-skills.js';
2
3
  import { GENESIS_CONTRACTS } from './contracts.js';
3
4
  import { readEngineering, readEngineeringBaseline } from './engineering.js';
4
- import { GenesisError } from './errors.js';
5
+ import { asDiagnostic, GenesisError } from './errors.js';
5
6
  import { gitContext } from './git.js';
6
7
  import { inspectProject } from './project-inspection.js';
7
8
  import { readStack } from './stack.js';
@@ -118,6 +119,11 @@ export async function projectSessionContext({
118
119
  inspectProject({ projectRoot: root, stackPackages }),
119
120
  ]);
120
121
  const availableComponents = await optionalStackComponentIds(root, stack, stackPackages);
122
+ const skills = stack && inspection.projectFormat.status === 'current'
123
+ ? await inspectProjectSkills({ projectRoot: root, stack }).catch((error) => ({
124
+ diagnostics: [asDiagnostic(error)],
125
+ }))
126
+ : { diagnostics: [] };
121
127
  const selected = stack?.components.map(({ id }) => id) || [];
122
128
  const stackStatus = stack
123
129
  ? (selected.length > 0 ? selected.join(', ') : 'none')
@@ -140,6 +146,8 @@ export async function projectSessionContext({
140
146
  '- When the user names an unselected technology that exactly matches this catalog, run `genesis stack list`, ask whether to add and prepare it, and wait for confirmation. If confirmed, run `genesis stack add <piece...>` and follow its returned preparation prompt in the same task; then use `genesis context` and any installed technology skill. Never infer dependency commands from a component id. If declined or unmatched, continue through authoritative technology documentation without inventing a component.',
141
147
  '- Project-owned operation sections in `genesis/stack.md` are the durable application contract. Make the implementation satisfy them or update a complete section to match evidenced reality; Genesis executes only Verification.',
142
148
  '- Use the relevant project Agent Skills below `.agents/skills/`.',
149
+ ...skills.diagnostics.map(({ message }) => `- Agent Skill maintenance: ${message}`),
150
+ ...(skills.diagnostics.length > 0 ? ['- Skill inspection is read-only. Synchronization is an explicit source change; follow the current task authorization and host write boundary before running it.'] : []),
143
151
  '- After locating source, run `genesis context <path...>`; before adding a helper or public operation, run `genesis index <name-or-path...>` and reuse an existing owner.',
144
152
  '- Program is fallible explanation; code, tests, and runtime behavior remain evidence.',
145
153
  '- Keep Blueprint and affected Program explanations aligned with intentional observable product behavior in the same implementation turn. Private restructuring may need only source citations or no explanatory change.',
@@ -158,7 +166,7 @@ export async function projectSessionContext({
158
166
  collaboration.guidance,
159
167
  ...(hostContext ? ['', 'HOST CONTEXT', '', hostContext] : []),
160
168
  ].join('\n');
161
- return composedContext(GENESIS_CONTRACTS.sessionContext, output);
169
+ return { ...composedContext(GENESIS_CONTRACTS.sessionContext, output), warnings: skills.diagnostics };
162
170
  }
163
171
 
164
172
  export async function projectTurnContext({ hostDriver, hostDriverInput } = {}) {
package/src/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import path from 'node:path';
2
2
 
3
3
  import { checkProject } from './index/check.js';
4
+ import { inspectProjectSkills, syncProjectSkills } from './index/agent-skills.js';
5
+ import { gitContext } from './index/git.js';
6
+ import { inspectProjectFormatAtRoot, requireCurrentProjectFormat } from './index/project-format.js';
4
7
  import { buildProjectIndex, GENESIS_DERIVED_ARTIFACTS } from './index/code-index.js';
5
8
  import {
6
9
  inspectProjectCollaboration,
@@ -200,3 +203,23 @@ export function verify(options) {
200
203
  export function check(options) {
201
204
  return checkProject(options);
202
205
  }
206
+
207
+ async function projectSkillContext({ projectRoot = process.cwd(), stackPackages = [] } = {}) {
208
+ const root = (await gitContext(projectRoot)).repositoryRoot;
209
+ requireCurrentProjectFormat(await inspectProjectFormatAtRoot(root), { allowUninitialized: true });
210
+ return { projectRoot: root, stack: await readStack(root, { stackPackages }) };
211
+ }
212
+
213
+ export async function inspectSkills(options) {
214
+ return inspectProjectSkills(await projectSkillContext(options));
215
+ }
216
+
217
+ export async function syncSkills(options) {
218
+ const context = await projectSkillContext(options);
219
+ const result = await syncProjectSkills(context);
220
+ return {
221
+ ...result,
222
+ inspection: await inspectProjectSkills(context),
223
+ guidance: 'Review changed skills in the ordinary Git diff. An assistant that already loaded them needs its supported context refresh before using the new guidance.',
224
+ };
225
+ }