godpowers 1.6.16 → 1.6.19

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.
@@ -0,0 +1,512 @@
1
+ /**
2
+ * Repository surface sync.
3
+ *
4
+ * Detects structural drift between commands, routing, package metadata,
5
+ * agent contracts, workflows, recipes, extensions, and release policy docs.
6
+ * The helper is read-only by default. The apply path writes only safe local
7
+ * logs and optional missing routing stubs when explicitly requested.
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ const { parseSimpleYaml } = require('./intent');
14
+ const extensions = require('./extensions');
15
+ const repoDocSync = require('./repo-doc-sync');
16
+
17
+ const LOG_PATH = '.godpowers/surface/REPO-SURFACE-SYNC.md';
18
+
19
+ const REQUIRED_PACKAGE_FILE_ENTRIES = [
20
+ 'bin/',
21
+ 'skills/',
22
+ 'agents/god-*.md',
23
+ 'templates/',
24
+ 'references/',
25
+ 'routing/',
26
+ 'workflows/',
27
+ 'schema/',
28
+ 'lib/',
29
+ 'extensions/',
30
+ 'RELEASE.md',
31
+ 'SKILL.md',
32
+ 'AGENTS.md',
33
+ 'CHANGELOG.md',
34
+ 'LICENSE'
35
+ ];
36
+
37
+ const REQUIRED_PACKAGE_CHECKS = [
38
+ 'lib/feature-awareness.js',
39
+ 'lib/repo-doc-sync.js',
40
+ 'lib/repo-surface-sync.js',
41
+ 'routing/god-export-otel.yaml'
42
+ ];
43
+
44
+ function rel(projectRoot, absPath) {
45
+ return path.relative(projectRoot, absPath).split(path.sep).join('/');
46
+ }
47
+
48
+ function exists(projectRoot, relPath) {
49
+ return fs.existsSync(path.join(projectRoot, relPath));
50
+ }
51
+
52
+ function read(projectRoot, relPath) {
53
+ const file = path.join(projectRoot, relPath);
54
+ if (!fs.existsSync(file)) return '';
55
+ return fs.readFileSync(file, 'utf8');
56
+ }
57
+
58
+ function write(projectRoot, relPath, content) {
59
+ const file = path.join(projectRoot, relPath);
60
+ fs.mkdirSync(path.dirname(file), { recursive: true });
61
+ fs.writeFileSync(file, content);
62
+ }
63
+
64
+ function listFiles(projectRoot, relDir, pattern) {
65
+ const dir = path.join(projectRoot, relDir);
66
+ if (!fs.existsSync(dir)) return [];
67
+ return fs.readdirSync(dir)
68
+ .filter((name) => pattern.test(name))
69
+ .sort()
70
+ .map((name) => `${relDir}/${name}`.replace(/\\/g, '/'));
71
+ }
72
+
73
+ function readJson(projectRoot, relPath) {
74
+ try {
75
+ return JSON.parse(read(projectRoot, relPath));
76
+ } catch (err) {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ function routeForSkill(skillPath) {
82
+ const base = path.basename(skillPath, '.md');
83
+ return `routing/${base}.yaml`;
84
+ }
85
+
86
+ function commandForSkill(skillPath) {
87
+ return `/${path.basename(skillPath, '.md')}`;
88
+ }
89
+
90
+ function addCheck(checks, area, id, status, relPath, message, opts = {}) {
91
+ checks.push({
92
+ area,
93
+ id,
94
+ status,
95
+ path: relPath,
96
+ message,
97
+ severity: opts.severity || (status === 'fresh' ? 'info' : 'warning'),
98
+ safeFix: opts.safeFix === true,
99
+ spawn: opts.spawn || null
100
+ });
101
+ }
102
+
103
+ function routingChecks(projectRoot) {
104
+ const checks = [];
105
+ const skills = listFiles(projectRoot, 'skills', /^god.*\.md$/);
106
+ const routes = listFiles(projectRoot, 'routing', /^god.*\.yaml$/);
107
+ const skillRoutes = new Set(skills.map(routeForSkill));
108
+ const routeSet = new Set(routes);
109
+
110
+ for (const skill of skills) {
111
+ const route = routeForSkill(skill);
112
+ addCheck(
113
+ checks,
114
+ 'routing',
115
+ `route-for-${path.basename(skill, '.md')}`,
116
+ routeSet.has(route) ? 'fresh' : 'stale',
117
+ route,
118
+ routeSet.has(route)
119
+ ? `${commandForSkill(skill)} has routing metadata.`
120
+ : `${commandForSkill(skill)} is missing routing metadata.`,
121
+ { safeFix: !routeSet.has(route) }
122
+ );
123
+ }
124
+
125
+ for (const route of routes) {
126
+ if (!skillRoutes.has(route)) {
127
+ addCheck(
128
+ checks,
129
+ 'routing',
130
+ `skill-for-${path.basename(route, '.yaml')}`,
131
+ 'stale',
132
+ route,
133
+ `${route} has no matching skill file.`,
134
+ { severity: 'warning' }
135
+ );
136
+ }
137
+ }
138
+ return checks;
139
+ }
140
+
141
+ function packageChecks(projectRoot) {
142
+ const checks = [];
143
+ const pkg = readJson(projectRoot, 'package.json') || {};
144
+ const lock = readJson(projectRoot, 'package-lock.json');
145
+ const fileEntries = Array.isArray(pkg.files) ? pkg.files : [];
146
+
147
+ for (const entry of REQUIRED_PACKAGE_FILE_ENTRIES) {
148
+ addCheck(
149
+ checks,
150
+ 'package',
151
+ `package-files-${entry.replace(/[^a-z0-9]+/gi, '-')}`,
152
+ fileEntries.includes(entry) ? 'fresh' : 'stale',
153
+ 'package.json',
154
+ fileEntries.includes(entry)
155
+ ? `package.json includes ${entry}.`
156
+ : `package.json files is missing ${entry}.`
157
+ );
158
+ }
159
+
160
+ const packageCheckText = read(projectRoot, 'scripts/check-package-contents.js');
161
+ for (const required of REQUIRED_PACKAGE_CHECKS) {
162
+ addCheck(
163
+ checks,
164
+ 'package',
165
+ `package-check-${required.replace(/[^a-z0-9]+/gi, '-')}`,
166
+ packageCheckText.includes(required) ? 'fresh' : 'stale',
167
+ 'scripts/check-package-contents.js',
168
+ packageCheckText.includes(required)
169
+ ? `package contents check requires ${required}.`
170
+ : `package contents check does not require ${required}.`
171
+ );
172
+ }
173
+
174
+ if (lock && lock.version) {
175
+ addCheck(
176
+ checks,
177
+ 'package',
178
+ 'package-lock-version',
179
+ lock.version === pkg.version ? 'fresh' : 'stale',
180
+ 'package-lock.json',
181
+ lock.version === pkg.version
182
+ ? 'package-lock.json version matches package.json.'
183
+ : `package-lock.json version ${lock.version} does not match package.json ${pkg.version}.`
184
+ );
185
+ }
186
+
187
+ return checks;
188
+ }
189
+
190
+ function parseRoute(projectRoot, routePath) {
191
+ try {
192
+ return parseSimpleYaml(read(projectRoot, routePath));
193
+ } catch (err) {
194
+ return null;
195
+ }
196
+ }
197
+
198
+ function agentChecks(projectRoot) {
199
+ const checks = [];
200
+ const agents = new Set(listFiles(projectRoot, 'agents', /^god.*\.md$/)
201
+ .map((file) => path.basename(file, '.md')));
202
+ const routes = listFiles(projectRoot, 'routing', /^god.*\.yaml$/);
203
+ const missing = new Set();
204
+
205
+ for (const route of routes) {
206
+ const parsed = parseRoute(projectRoot, route);
207
+ const spawns = parsed && parsed.execution && Array.isArray(parsed.execution.spawns)
208
+ ? parsed.execution.spawns
209
+ : [];
210
+ for (const spawn of spawns) {
211
+ if (!String(spawn).startsWith('god-')) continue;
212
+ if (!/^god-[a-z0-9-]+$/.test(String(spawn))) continue;
213
+ if (!agents.has(spawn)) {
214
+ missing.add(`${route}:${spawn}`);
215
+ addCheck(
216
+ checks,
217
+ 'agents',
218
+ `missing-agent-${spawn}`,
219
+ 'stale',
220
+ route,
221
+ `${route} references missing agent ${spawn}.`,
222
+ { spawn: 'god-auditor' }
223
+ );
224
+ }
225
+ }
226
+ }
227
+
228
+ if (missing.size === 0) {
229
+ addCheck(checks, 'agents', 'route-spawn-targets', 'fresh',
230
+ 'routing/', 'All routed specialist spawns resolve to agent files.');
231
+ }
232
+
233
+ return checks;
234
+ }
235
+
236
+ function workflowRecipeChecks(projectRoot) {
237
+ const checks = [];
238
+ const workflows = listFiles(projectRoot, 'workflows', /\.yaml$/);
239
+ const recipes = listFiles(projectRoot, path.join('routing', 'recipes'), /\.yaml$/);
240
+ const commandFlows = read(projectRoot, 'docs/command-flows.md');
241
+
242
+ for (const workflow of workflows) {
243
+ const text = read(projectRoot, workflow);
244
+ const parsed = parseSimpleYaml(text);
245
+ const hasMetadata = Boolean(parsed.apiVersion || parsed.name || parsed.metadata);
246
+ addCheck(
247
+ checks,
248
+ 'workflow',
249
+ `workflow-metadata-${path.basename(workflow, '.yaml')}`,
250
+ hasMetadata ? 'fresh' : 'stale',
251
+ workflow,
252
+ hasMetadata ? `${workflow} has metadata.` : `${workflow} is missing parseable metadata.`,
253
+ { spawn: hasMetadata ? null : 'god-roadmap-reconciler' }
254
+ );
255
+ }
256
+
257
+ for (const recipe of recipes) {
258
+ const text = read(projectRoot, recipe);
259
+ const hasCommand = /\/god-[a-z0-9-]+/.test(text);
260
+ const recipeName = path.basename(recipe, '.yaml');
261
+ addCheck(
262
+ checks,
263
+ 'workflow',
264
+ `recipe-command-${recipeName}`,
265
+ hasCommand ? 'fresh' : 'stale',
266
+ recipe,
267
+ hasCommand ? `${recipe} includes a slash-command route.` : `${recipe} has no slash-command route.`,
268
+ { spawn: hasCommand ? null : 'god-roadmap-reconciler' }
269
+ );
270
+ }
271
+
272
+ if (workflows.length > 0) {
273
+ addCheck(
274
+ checks,
275
+ 'workflow',
276
+ 'command-flows-workflows',
277
+ commandFlows.includes('/god-docs') && commandFlows.includes('/god-sync') ? 'fresh' : 'stale',
278
+ 'docs/command-flows.md',
279
+ 'docs/command-flows.md includes core docs and sync flows.',
280
+ { spawn: 'god-roadmap-reconciler' }
281
+ );
282
+ }
283
+ return checks;
284
+ }
285
+
286
+ function extensionChecks(projectRoot) {
287
+ const checks = [];
288
+ const extRoot = path.join(projectRoot, 'extensions');
289
+ const pkg = readJson(projectRoot, 'package.json') || {};
290
+ if (!fs.existsSync(extRoot)) return checks;
291
+ for (const entry of fs.readdirSync(extRoot, { withFileTypes: true })) {
292
+ if (!entry.isDirectory()) continue;
293
+ const packRel = `extensions/${entry.name}`;
294
+ const manifestRel = `${packRel}/manifest.yaml`;
295
+ const packageRel = `${packRel}/package.json`;
296
+ const manifestText = read(projectRoot, manifestRel);
297
+ const packPkg = readJson(projectRoot, packageRel);
298
+ const parsed = manifestText ? extensions.parseManifest(manifestText).manifest : null;
299
+ const validation = parsed ? extensions.validateManifest(parsed, pkg.version || '0.0.0') : ['missing manifest'];
300
+
301
+ addCheck(
302
+ checks,
303
+ 'extensions',
304
+ `extension-manifest-${entry.name}`,
305
+ validation.length === 0 ? 'fresh' : 'stale',
306
+ manifestRel,
307
+ validation.length === 0
308
+ ? `${entry.name} manifest validates against current Godpowers.`
309
+ : `${entry.name} manifest validation failed: ${validation.join('; ')}.`,
310
+ { spawn: validation.length === 0 ? null : 'god-coordinator' }
311
+ );
312
+
313
+ if (parsed && packPkg) {
314
+ const sameName = parsed.metadata && parsed.metadata.name === packPkg.name;
315
+ const sameVersion = parsed.metadata && parsed.metadata.version === packPkg.version;
316
+ const sameEngine = packPkg.peerDependencies
317
+ && packPkg.peerDependencies.godpowers === parsed.engines.godpowers;
318
+ addCheck(checks, 'extensions', `extension-name-${entry.name}`,
319
+ sameName ? 'fresh' : 'stale', packageRel,
320
+ sameName ? `${entry.name} package name matches manifest.` : `${entry.name} package name does not match manifest.`,
321
+ { spawn: sameName ? null : 'god-coordinator' });
322
+ addCheck(checks, 'extensions', `extension-version-${entry.name}`,
323
+ sameVersion ? 'fresh' : 'stale', packageRel,
324
+ sameVersion ? `${entry.name} package version matches manifest.` : `${entry.name} package version does not match manifest.`,
325
+ { spawn: sameVersion ? null : 'god-coordinator' });
326
+ addCheck(checks, 'extensions', `extension-engine-${entry.name}`,
327
+ sameEngine ? 'fresh' : 'stale', packageRel,
328
+ sameEngine ? `${entry.name} peer dependency matches manifest engine.` : `${entry.name} peer dependency does not match manifest engine.`,
329
+ { spawn: sameEngine ? null : 'god-coordinator' });
330
+ for (const kind of ['skills', 'agents', 'workflows']) {
331
+ const provided = parsed.provides && Array.isArray(parsed.provides[kind])
332
+ ? parsed.provides[kind]
333
+ : [];
334
+ for (const item of provided) {
335
+ const ext = kind === 'workflows' ? 'yaml' : 'md';
336
+ const providedRel = `${packRel}/${kind}/${item}.${ext}`;
337
+ addCheck(checks, 'extensions', `extension-${kind}-${entry.name}-${item}`,
338
+ exists(projectRoot, providedRel) ? 'fresh' : 'stale',
339
+ providedRel,
340
+ exists(projectRoot, providedRel)
341
+ ? `${providedRel} exists.`
342
+ : `${providedRel} is missing.`,
343
+ { spawn: exists(projectRoot, providedRel) ? null : 'god-coordinator' });
344
+ }
345
+ }
346
+ }
347
+ }
348
+ return checks;
349
+ }
350
+
351
+ function releasePolicyChecks(projectRoot) {
352
+ const checks = [];
353
+ const docs = repoDocSync.detect(projectRoot);
354
+ addCheck(
355
+ checks,
356
+ 'release',
357
+ 'repo-doc-sync-fresh',
358
+ docs.status === 'fresh' ? 'fresh' : 'stale',
359
+ 'docs/repo-doc-sync.md',
360
+ docs.status === 'fresh'
361
+ ? 'Repository documentation sync reports fresh.'
362
+ : `Repository documentation sync reports ${docs.stale.length} stale checks.`,
363
+ { spawn: docs.status === 'fresh' ? null : 'god-docs-writer' }
364
+ );
365
+ addCheck(
366
+ checks,
367
+ 'release',
368
+ 'release-checklist-surface-sync',
369
+ read(projectRoot, 'docs/RELEASE-CHECKLIST.md').includes('repo-surface-sync'),
370
+ 'docs/RELEASE-CHECKLIST.md',
371
+ 'Release checklist references repo-surface-sync readiness.',
372
+ { spawn: 'god-docs-writer' }
373
+ );
374
+ return checks.map((check) => ({
375
+ ...check,
376
+ status: check.status === true ? 'fresh' : (check.status === false ? 'stale' : check.status)
377
+ }));
378
+ }
379
+
380
+ function detect(projectRoot) {
381
+ const checks = [
382
+ ...routingChecks(projectRoot),
383
+ ...packageChecks(projectRoot),
384
+ ...agentChecks(projectRoot),
385
+ ...workflowRecipeChecks(projectRoot),
386
+ ...extensionChecks(projectRoot),
387
+ ...releasePolicyChecks(projectRoot)
388
+ ];
389
+ const stale = checks.filter((check) => check.status !== 'fresh');
390
+ const byArea = {};
391
+ for (const check of checks) {
392
+ if (!byArea[check.area]) byArea[check.area] = { total: 0, stale: 0 };
393
+ byArea[check.area].total++;
394
+ if (check.status !== 'fresh') byArea[check.area].stale++;
395
+ }
396
+ const spawnRecommendations = [...new Set(stale.map((check) => check.spawn).filter(Boolean))]
397
+ .map((agent) => ({
398
+ agent,
399
+ reason: `Repo surface drift requires ${agent} judgment.`,
400
+ paths: [...new Set(stale.filter((check) => check.spawn === agent).map((check) => check.path))].sort()
401
+ }));
402
+ return {
403
+ status: stale.length === 0 ? 'fresh' : 'stale',
404
+ checks,
405
+ stale,
406
+ byArea,
407
+ spawnRecommendations
408
+ };
409
+ }
410
+
411
+ function routeTemplate(command) {
412
+ const name = command.replace(/^\//, '');
413
+ return [
414
+ 'apiVersion: godpowers/v1',
415
+ 'kind: CommandRouting',
416
+ 'metadata:',
417
+ ` command: ${command}`,
418
+ ' description: ',
419
+ ' tier: 0',
420
+ '',
421
+ 'prerequisites:',
422
+ ' required: []',
423
+ '',
424
+ 'execution:',
425
+ ' spawns: [built-in]',
426
+ ' context: fresh',
427
+ ' writes: []',
428
+ '',
429
+ 'success-path:',
430
+ ' next-recommended: /god-status',
431
+ '',
432
+ 'failure-path:',
433
+ ' on-error: /god-doctor',
434
+ '',
435
+ 'endoff:',
436
+ ' state-update: none',
437
+ ' events: [agent.start, agent.end]',
438
+ ''
439
+ ].join('\n').replace('description: ', `description: Route metadata for /${name}`);
440
+ }
441
+
442
+ function applySafeFixes(projectRoot, report, opts = {}) {
443
+ const applied = [];
444
+ if (!opts.fixRouting) return applied;
445
+ for (const check of report.stale) {
446
+ if (check.area !== 'routing' || !check.safeFix || !check.path.startsWith('routing/')) continue;
447
+ if (exists(projectRoot, check.path)) continue;
448
+ const command = `/${path.basename(check.path, '.yaml')}`;
449
+ write(projectRoot, check.path, routeTemplate(command));
450
+ applied.push({ path: check.path, check: check.id });
451
+ }
452
+ return applied;
453
+ }
454
+
455
+ function appendLog(projectRoot, before, after, applied) {
456
+ const now = new Date().toISOString();
457
+ const lines = [];
458
+ if (exists(projectRoot, LOG_PATH)) {
459
+ lines.push(read(projectRoot, LOG_PATH).replace(/\s*$/, ''));
460
+ lines.push('');
461
+ } else {
462
+ lines.push('# Repo Surface Sync Log');
463
+ lines.push('');
464
+ lines.push('- [DECISION] This file records structural repository surface sync checks run by Godpowers.');
465
+ lines.push('- [DECISION] Detection is read-only by default and safe fixes require explicit fix options.');
466
+ lines.push('');
467
+ }
468
+ lines.push(`## ${now}`);
469
+ lines.push('');
470
+ lines.push(`- [DECISION] Repo surface sync status before apply was ${before.status}.`);
471
+ lines.push(`- [DECISION] Repo surface sync status after apply is ${after.status}.`);
472
+ if (applied.length === 0) {
473
+ lines.push('- [DECISION] No structural repository surface files were changed.');
474
+ } else {
475
+ for (const item of applied) {
476
+ lines.push(`- [DECISION] Created or refreshed ${item.path} for ${item.check}.`);
477
+ }
478
+ }
479
+ for (const rec of after.spawnRecommendations) {
480
+ lines.push(`- [HYPOTHESIS] ${rec.agent} should review ${rec.paths.join(', ')}.`);
481
+ }
482
+ lines.push('');
483
+ write(projectRoot, LOG_PATH, lines.join('\n'));
484
+ }
485
+
486
+ function run(projectRoot, opts = {}) {
487
+ const before = detect(projectRoot);
488
+ const applied = applySafeFixes(projectRoot, before, opts);
489
+ const after = detect(projectRoot);
490
+ if (opts.log !== false) appendLog(projectRoot, before, after, applied);
491
+ return {
492
+ before,
493
+ after,
494
+ applied,
495
+ logPath: opts.log === false ? null : LOG_PATH
496
+ };
497
+ }
498
+
499
+ function summary(report) {
500
+ if (report.status === 'fresh') return 'fresh';
501
+ return `${report.stale.length} stale across ${Object.keys(report.byArea).length} areas`;
502
+ }
503
+
504
+ module.exports = {
505
+ LOG_PATH,
506
+ REQUIRED_PACKAGE_CHECKS,
507
+ REQUIRED_PACKAGE_FILE_ENTRIES,
508
+ detect,
509
+ run,
510
+ summary,
511
+ routeTemplate
512
+ };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "godpowers",
3
- "version": "1.6.16",
3
+ "version": "1.6.19",
4
4
  "description": "AI-powered development system: 109 slash commands and 40 specialist agents that take a project from raw idea to hardened production. Runs inside Claude Code, Codex, Cursor, Windsurf, Gemini, and 10+ other AI coding tools.",
5
5
  "bin": {
6
6
  "godpowers": "./bin/install.js"
7
7
  },
8
8
  "scripts": {
9
- "test": "node scripts/validate-skills.js && node scripts/test-doc-surface-counts.js && bash scripts/smoke.sh && node scripts/test-runtime.js && node scripts/test-router.js && node scripts/test-recipes.js && node scripts/test-context-writer.js && node scripts/test-pillars.js && node scripts/test-artifact-linter.js && node scripts/test-artifact-diff.js && node scripts/test-design-foundation.js && node scripts/test-linkage.js && node scripts/test-impact.js && node scripts/test-reverse-sync.js && node scripts/test-planning-systems.js && node scripts/test-feature-awareness.js && node scripts/test-integration.js && node scripts/test-cross-artifact.js && node scripts/test-awesome-design.js && node scripts/test-skillui-bridge.js && node scripts/test-runtime-verification.js && node scripts/test-agent-browser.js && node scripts/test-mode-d.js && node scripts/test-runtime-heuristics.js && node scripts/test-agent-validator.js && node scripts/test-story-validator.js && node scripts/test-state.js && node scripts/test-dashboard.js && node scripts/test-automation-providers.js && node scripts/test-intent.js && node scripts/test-events.js && node scripts/test-golden-artifacts.js && node scripts/test-install-smoke.js && node scripts/test-checkpoint.js && node scripts/test-extensions.js && node scripts/test-event-reader.js && node scripts/test-state-lock.js && node scripts/test-cost-saver.js && node scripts/test-budget-onoff.js && node scripts/test-workflow-runner.js && npm run test:e2e && node scripts/test-otel-exporter.js && node scripts/test-extensions-publish.js",
9
+ "test": "node scripts/validate-skills.js && node scripts/test-doc-surface-counts.js && bash scripts/smoke.sh && node scripts/test-runtime.js && node scripts/test-router.js && node scripts/test-recipes.js && node scripts/test-context-writer.js && node scripts/test-pillars.js && node scripts/test-artifact-linter.js && node scripts/test-artifact-diff.js && node scripts/test-design-foundation.js && node scripts/test-linkage.js && node scripts/test-impact.js && node scripts/test-reverse-sync.js && node scripts/test-planning-systems.js && node scripts/test-feature-awareness.js && node scripts/test-repo-doc-sync.js && node scripts/test-repo-surface-sync.js && node scripts/test-integration.js && node scripts/test-cross-artifact.js && node scripts/test-awesome-design.js && node scripts/test-skillui-bridge.js && node scripts/test-runtime-verification.js && node scripts/test-agent-browser.js && node scripts/test-mode-d.js && node scripts/test-runtime-heuristics.js && node scripts/test-agent-validator.js && node scripts/test-story-validator.js && node scripts/test-state.js && node scripts/test-dashboard.js && node scripts/test-automation-providers.js && node scripts/test-intent.js && node scripts/test-events.js && node scripts/test-golden-artifacts.js && node scripts/test-install-smoke.js && node scripts/test-checkpoint.js && node scripts/test-extensions.js && node scripts/test-event-reader.js && node scripts/test-state-lock.js && node scripts/test-cost-saver.js && node scripts/test-budget-onoff.js && node scripts/test-workflow-runner.js && npm run test:e2e && node scripts/test-otel-exporter.js && node scripts/test-extensions-publish.js",
10
10
  "prepublishOnly": "npm test",
11
11
  "validate-skills": "node scripts/validate-skills.js",
12
12
  "test:surface": "node scripts/test-doc-surface-counts.js",
@@ -0,0 +1,24 @@
1
+ apiVersion: godpowers/v1
2
+ kind: CommandRouting
3
+ metadata:
4
+ command: /god-export-otel
5
+ description: Export Godpowers events to OpenTelemetry OTLP JSON traces
6
+ tier: 0
7
+
8
+ prerequisites:
9
+ required: []
10
+
11
+ execution:
12
+ spawns: [built-in]
13
+ context: fresh
14
+ writes: []
15
+
16
+ success-path:
17
+ next-recommended: /god-status
18
+
19
+ failure-path:
20
+ on-error: /god-doctor
21
+
22
+ endoff:
23
+ state-update: none
24
+ events: [agent.start, agent.end]
@@ -23,6 +23,15 @@ Documentation work. Docs that don't lie.
23
23
 
24
24
  ## Orchestration
25
25
 
26
+ First call `lib/repo-doc-sync.run(projectRoot)` for mechanical repository
27
+ documentation claims such as README badges, version references, public surface
28
+ counts, and `/god-doctor` sample counts. Report this as `Agent: none, local
29
+ runtime only`.
30
+
31
+ Then call `lib/repo-surface-sync.run(projectRoot)` so documentation work sees
32
+ whether route, package, agent, workflow, recipe, extension, or release policy
33
+ surfaces disagree before prose claims are rewritten.
34
+
26
35
  Spawn **god-docs-writer** in fresh context.
27
36
 
28
37
  The agent:
@@ -56,6 +65,8 @@ Godpowers may invoke docs work proactively in two ways:
56
65
  |---|---|---|
57
66
  | Docs changed after code changed | Spawn `god-docs-writer` in drift-check mode when current workflow owns docs | Do not invent new docs scope |
58
67
  | Code changed after docs that claim current behavior | Suggest `/god-docs` or spawn drift-check inside `/god-mode`, `/god-feature`, `/god-refactor`, or `/god-sync` closeout | Verify claims against code before editing |
68
+ | Repo docs surface drift | Run `lib/repo-doc-sync.run` for safe mechanical fixes, then spawn `god-docs-writer` for prose | Do not auto-invent changelog or release notes |
69
+ | Repo structural surface drift | Run `lib/repo-surface-sync.run` and include findings in docs scope | Do not invent routing or agent ownership prose without evidence |
59
70
  | `REVIEW-REQUIRED.md` contains docs drift items | Suggest `/god-review-changes` first | Do not auto-clear review items |
60
71
 
61
72
  When auto-invoked, show:
@@ -65,6 +76,8 @@ Auto-invoked:
65
76
  Trigger: docs and code changed in the same workflow
66
77
  Agent: god-docs-writer
67
78
  Local syncs:
79
+ + repo-doc-sync: <safe mechanical fixes, prose review needed, or no-op>
80
+ + repo-surface-sync: <structural surface fresh, scoped findings, or no-op>
68
81
  + docs-drift-check: <N claims checked, N drift items>
69
82
  Artifacts: .godpowers/docs/UPDATE-LOG.md or no-op
70
83
  Log: .godpowers/docs/UPDATE-LOG.md
@@ -114,6 +114,30 @@ as a read-only diagnostic. It reports:
114
114
  `/god-doctor --fix` may call `lib/feature-awareness.run(projectRoot)` because
115
115
  that helper writes only safe state metadata and managed context fences.
116
116
 
117
+ ## Repo Documentation Sync
118
+
119
+ For initialized projects, `/god-doctor` calls `lib/repo-doc-sync.detect` as a
120
+ read-only diagnostic. It reports stale README badges, public surface counts,
121
+ release notes, changelog entries, contribution guidance, security policy, and
122
+ Pillars sync planning for changed repo docs.
123
+
124
+ `/god-doctor --fix` may call `lib/repo-doc-sync.run(projectRoot)` because that
125
+ helper writes only safe mechanical version, badge, and count claims. It should
126
+ recommend `god-docs-writer` when narrative release, contribution, support, or
127
+ security prose needs judgment.
128
+
129
+ ## Repo Surface Sync
130
+
131
+ For initialized projects, `/god-doctor` calls `lib/repo-surface-sync.detect`
132
+ as a read-only diagnostic. It reports structural drift across command routing,
133
+ package payload rules, agent spawn targets, workflow metadata, recipe command
134
+ routes, extension packs, and release policy checks.
135
+
136
+ `/god-doctor --fix` may call
137
+ `lib/repo-surface-sync.run(projectRoot, { fixRouting: true })` to create
138
+ missing routing metadata for shipped slash-command skills. Other structural
139
+ findings should recommend the scoped specialist named by the helper.
140
+
117
141
  ## Implementation
118
142
 
119
143
  Built-in, no spawned agent. Reads:
@@ -122,6 +146,9 @@ Built-in, no spawned agent. Reads:
122
146
  - `.godpowers/state.json`, `intent.yaml`, `log`, `linkage.json`
123
147
  - `lib/feature-awareness.detect(projectRoot)` for existing-project upgrade
124
148
  awareness
149
+ - `lib/repo-doc-sync.detect(projectRoot)` for repo documentation freshness
150
+ - `lib/repo-surface-sync.detect(projectRoot)` for structural repo surface
151
+ freshness
125
152
  - `bin/install.js` VERSION constant
126
153
 
127
154
  ## Exit codes
@@ -306,6 +306,8 @@ Sync status:
306
306
  Local syncs:
307
307
  + feature-awareness: <recorded runtime features, refreshed context, or no-op>
308
308
  + reverse-sync: <counts and result>
309
+ + repo-doc-sync: <refreshed repo docs, recommended god-docs-writer, or no-op>
310
+ + repo-surface-sync: <checked structural surfaces, recommended scoped agents, or no-op>
309
311
  + pillars-sync: <counts and result>
310
312
  + checkpoint-sync: <created, updated, no-op, or skipped>
311
313
  + context-refresh: <spawned, no-op, or skipped>
@@ -324,6 +326,16 @@ When `/god-mode` resumes an existing `.godpowers` project, it auto-invokes
324
326
  keeps upgraded projects aware of new runtime features, current context fences,
325
327
  and migration routes without rewriting user artifacts.
326
328
 
329
+ The mandatory final sync also receives repo documentation sync through
330
+ `/god-sync`. This keeps README badges, release surfaces, contribution guidance,
331
+ security policy checks, and Pillars context planning arc-ready before the
332
+ project run is declared complete.
333
+
334
+ The mandatory final sync also receives repo surface sync through `/god-sync`.
335
+ This keeps routes, packages, agent handoffs, workflow metadata, recipe routes,
336
+ extension packs, and release policy checks aligned before the project run is
337
+ declared complete.
338
+
327
339
  If `/god-mode` resumes an existing `.godpowers` project that lacks Pillars,
328
340
  it Pillar-izes the project before continuing. Existing `.godpowers` artifacts
329
341
  become managed source references in the relevant `agents/*.md` files.
@@ -345,6 +357,8 @@ Sync status:
345
357
  Local syncs:
346
358
  + feature-awareness: <recorded runtime features, refreshed context, or no-op>
347
359
  + reverse-sync: <counts and result>
360
+ + repo-doc-sync: <refreshed repo docs, recommended god-docs-writer, or no-op>
361
+ + repo-surface-sync: <checked structural surfaces, recommended scoped agents, or no-op>
348
362
  + pillars-sync: <counts and result>
349
363
  + checkpoint-sync: <created, updated, no-op, or skipped>
350
364
  + context-refresh: <spawned, no-op, or skipped>