godpowers 1.6.19 → 1.6.21
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/AGENTS.md +29 -6
- package/CHANGELOG.md +60 -0
- package/README.md +11 -8
- package/RELEASE.md +39 -29
- package/SKILL.md +7 -1
- package/lib/README.md +3 -0
- package/lib/dashboard.js +43 -2
- package/lib/feature-awareness.js +18 -0
- package/lib/recipe-coverage-sync.js +149 -0
- package/lib/release-surface-sync.js +177 -0
- package/lib/repo-surface-sync.js +86 -4
- package/lib/route-quality-sync.js +313 -0
- package/lib/router.js +4 -1
- package/package.json +2 -2
- package/routing/god-init.yaml +1 -1
- package/routing/god-party.yaml +4 -2
- package/routing/god-roadmap-update.yaml +1 -1
- package/routing/god-story-build.yaml +11 -2
- package/routing/god-sync.yaml +1 -1
- package/routing/recipes/automation-setup.yaml +25 -0
- package/routing/recipes/context-refresh.yaml +26 -0
- package/routing/recipes/release-maintenance.yaml +27 -0
- package/routing/recipes/story-work.yaml +29 -0
- package/skills/god-doctor.md +8 -1
- package/skills/god-sync.md +8 -4
- package/skills/god-version.md +1 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release surface sync.
|
|
3
|
+
*
|
|
4
|
+
* Detects whether release-facing repo surfaces agree before a package or tag
|
|
5
|
+
* is treated as ready: package metadata, lockfile, release notes, changelog,
|
|
6
|
+
* README badge, release checklist, and package payload guardrails.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const LOG_PATH = '.godpowers/surface/RELEASE-SURFACE-SYNC.md';
|
|
13
|
+
|
|
14
|
+
const REQUIRED_PACKAGE_GUARDS = [
|
|
15
|
+
'lib/route-quality-sync.js',
|
|
16
|
+
'lib/recipe-coverage-sync.js',
|
|
17
|
+
'lib/release-surface-sync.js'
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const REQUIRED_RELEASE_TESTS = [
|
|
21
|
+
'scripts/test-automation-surface-sync.js',
|
|
22
|
+
'scripts/test-repo-surface-sync.js',
|
|
23
|
+
'scripts/test-extensions-publish.js',
|
|
24
|
+
'scripts/test-mode-d.js',
|
|
25
|
+
'scripts/test-install-smoke.js'
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
function read(projectRoot, relPath) {
|
|
29
|
+
const file = path.join(projectRoot, relPath);
|
|
30
|
+
if (!fs.existsSync(file)) return '';
|
|
31
|
+
return fs.readFileSync(file, 'utf8');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function write(projectRoot, relPath, content) {
|
|
35
|
+
const file = path.join(projectRoot, relPath);
|
|
36
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
37
|
+
fs.writeFileSync(file, content);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readJson(projectRoot, relPath) {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(read(projectRoot, relPath));
|
|
43
|
+
} catch (err) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function addCheck(checks, id, status, relPath, message, opts = {}) {
|
|
49
|
+
checks.push({
|
|
50
|
+
area: 'release-surface',
|
|
51
|
+
id,
|
|
52
|
+
status,
|
|
53
|
+
path: relPath,
|
|
54
|
+
message,
|
|
55
|
+
severity: opts.severity || (status === 'fresh' ? 'info' : 'warning'),
|
|
56
|
+
spawn: opts.spawn || null
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function detect(projectRoot) {
|
|
61
|
+
const checks = [];
|
|
62
|
+
const pkg = readJson(projectRoot, 'package.json') || {};
|
|
63
|
+
const lock = readJson(projectRoot, 'package-lock.json') || {};
|
|
64
|
+
const version = pkg.version || '0.0.0';
|
|
65
|
+
|
|
66
|
+
addCheck(
|
|
67
|
+
checks,
|
|
68
|
+
'package-lock-version',
|
|
69
|
+
lock.version === version ? 'fresh' : 'stale',
|
|
70
|
+
'package-lock.json',
|
|
71
|
+
lock.version === version
|
|
72
|
+
? 'package-lock.json version matches package.json.'
|
|
73
|
+
: `package-lock.json version ${lock.version || 'missing'} does not match package.json ${version}.`
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const surfaces = [
|
|
77
|
+
['readme-version-badge', 'README.md', `version-${version}-blue`],
|
|
78
|
+
['changelog-version', 'CHANGELOG.md', `## [${version}]`],
|
|
79
|
+
['release-version', 'RELEASE.md', `Godpowers ${version}`],
|
|
80
|
+
['release-checklist-route-quality', 'docs/RELEASE-CHECKLIST.md', 'route-quality-sync'],
|
|
81
|
+
['release-checklist-recipe-coverage', 'docs/RELEASE-CHECKLIST.md', 'recipe-coverage-sync'],
|
|
82
|
+
['release-checklist-release-surface', 'docs/RELEASE-CHECKLIST.md', 'release-surface-sync']
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
for (const [id, relPath, expected] of surfaces) {
|
|
86
|
+
const ok = read(projectRoot, relPath).includes(expected);
|
|
87
|
+
addCheck(
|
|
88
|
+
checks,
|
|
89
|
+
id,
|
|
90
|
+
ok ? 'fresh' : 'stale',
|
|
91
|
+
relPath,
|
|
92
|
+
ok ? `${relPath} includes ${expected}.` : `${relPath} is missing ${expected}.`,
|
|
93
|
+
{ spawn: ok ? null : 'god-docs-writer' }
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const packageCheckText = read(projectRoot, 'scripts/check-package-contents.js');
|
|
98
|
+
for (const required of REQUIRED_PACKAGE_GUARDS) {
|
|
99
|
+
const ok = packageCheckText.includes(required);
|
|
100
|
+
addCheck(
|
|
101
|
+
checks,
|
|
102
|
+
`package-guard-${required.replace(/[^a-z0-9]+/gi, '-')}`,
|
|
103
|
+
ok ? 'fresh' : 'stale',
|
|
104
|
+
'scripts/check-package-contents.js',
|
|
105
|
+
ok
|
|
106
|
+
? `Package contents check requires ${required}.`
|
|
107
|
+
: `Package contents check does not require ${required}.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const scriptsText = JSON.stringify(pkg.scripts || {});
|
|
112
|
+
for (const required of REQUIRED_RELEASE_TESTS) {
|
|
113
|
+
const ok = scriptsText.includes(required);
|
|
114
|
+
addCheck(
|
|
115
|
+
checks,
|
|
116
|
+
`release-test-${required.replace(/[^a-z0-9]+/gi, '-')}`,
|
|
117
|
+
ok ? 'fresh' : 'stale',
|
|
118
|
+
'package.json',
|
|
119
|
+
ok
|
|
120
|
+
? `Release gate includes ${required}.`
|
|
121
|
+
: `Release gate does not include ${required}.`,
|
|
122
|
+
{ spawn: ok ? null : 'god-auditor' }
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const stale = checks.filter((check) => check.status !== 'fresh');
|
|
127
|
+
return {
|
|
128
|
+
status: stale.length === 0 ? 'fresh' : 'stale',
|
|
129
|
+
checks,
|
|
130
|
+
stale
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function appendLog(projectRoot, before, after) {
|
|
135
|
+
const now = new Date().toISOString();
|
|
136
|
+
const lines = [];
|
|
137
|
+
if (fs.existsSync(path.join(projectRoot, LOG_PATH))) {
|
|
138
|
+
lines.push(read(projectRoot, LOG_PATH).replace(/\s*$/, ''));
|
|
139
|
+
lines.push('');
|
|
140
|
+
} else {
|
|
141
|
+
lines.push('# Release Surface Sync Log');
|
|
142
|
+
lines.push('');
|
|
143
|
+
lines.push('- [DECISION] This file records release-surface sync checks run by Godpowers.');
|
|
144
|
+
lines.push('');
|
|
145
|
+
}
|
|
146
|
+
lines.push(`## ${now}`);
|
|
147
|
+
lines.push('');
|
|
148
|
+
lines.push(`- [DECISION] Release surface status before apply was ${before.status}.`);
|
|
149
|
+
lines.push(`- [DECISION] Release surface status after apply is ${after.status}.`);
|
|
150
|
+
lines.push('');
|
|
151
|
+
write(projectRoot, LOG_PATH, lines.join('\n'));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function run(projectRoot, opts = {}) {
|
|
155
|
+
const before = detect(projectRoot);
|
|
156
|
+
const after = detect(projectRoot);
|
|
157
|
+
if (opts.log !== false) appendLog(projectRoot, before, after);
|
|
158
|
+
return {
|
|
159
|
+
before,
|
|
160
|
+
after,
|
|
161
|
+
applied: [],
|
|
162
|
+
logPath: opts.log === false ? null : LOG_PATH
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function summary(report) {
|
|
167
|
+
return report.status === 'fresh' ? 'fresh' : `${report.stale.length} stale`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = {
|
|
171
|
+
LOG_PATH,
|
|
172
|
+
REQUIRED_PACKAGE_GUARDS,
|
|
173
|
+
REQUIRED_RELEASE_TESTS,
|
|
174
|
+
detect,
|
|
175
|
+
run,
|
|
176
|
+
summary
|
|
177
|
+
};
|
package/lib/repo-surface-sync.js
CHANGED
|
@@ -13,6 +13,9 @@ const path = require('path');
|
|
|
13
13
|
const { parseSimpleYaml } = require('./intent');
|
|
14
14
|
const extensions = require('./extensions');
|
|
15
15
|
const repoDocSync = require('./repo-doc-sync');
|
|
16
|
+
const routeQualitySync = require('./route-quality-sync');
|
|
17
|
+
const recipeCoverageSync = require('./recipe-coverage-sync');
|
|
18
|
+
const releaseSurfaceSync = require('./release-surface-sync');
|
|
16
19
|
|
|
17
20
|
const LOG_PATH = '.godpowers/surface/REPO-SURFACE-SYNC.md';
|
|
18
21
|
|
|
@@ -38,6 +41,9 @@ const REQUIRED_PACKAGE_CHECKS = [
|
|
|
38
41
|
'lib/feature-awareness.js',
|
|
39
42
|
'lib/repo-doc-sync.js',
|
|
40
43
|
'lib/repo-surface-sync.js',
|
|
44
|
+
'lib/route-quality-sync.js',
|
|
45
|
+
'lib/recipe-coverage-sync.js',
|
|
46
|
+
'lib/release-surface-sync.js',
|
|
41
47
|
'routing/god-export-otel.yaml'
|
|
42
48
|
];
|
|
43
49
|
|
|
@@ -204,9 +210,12 @@ function agentChecks(projectRoot) {
|
|
|
204
210
|
|
|
205
211
|
for (const route of routes) {
|
|
206
212
|
const parsed = parseRoute(projectRoot, route);
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
: []
|
|
213
|
+
const execution = parsed && parsed.execution ? parsed.execution : {};
|
|
214
|
+
const spawns = [
|
|
215
|
+
...(Array.isArray(execution.spawns) ? execution.spawns : []),
|
|
216
|
+
...(Array.isArray(execution['secondary-spawns']) ? execution['secondary-spawns'] : []),
|
|
217
|
+
...(Array.isArray(execution['parallel-spawns']) ? execution['parallel-spawns'] : [])
|
|
218
|
+
].map((spawn) => (spawn && typeof spawn === 'object' && spawn.agent) ? spawn.agent : spawn);
|
|
210
219
|
for (const spawn of spawns) {
|
|
211
220
|
if (!String(spawn).startsWith('god-')) continue;
|
|
212
221
|
if (!/^god-[a-z0-9-]+$/.test(String(spawn))) continue;
|
|
@@ -348,6 +357,75 @@ function extensionChecks(projectRoot) {
|
|
|
348
357
|
return checks;
|
|
349
358
|
}
|
|
350
359
|
|
|
360
|
+
function suiteChecks(projectRoot) {
|
|
361
|
+
const checks = [];
|
|
362
|
+
const pkg = readJson(projectRoot, 'package.json') || {};
|
|
363
|
+
const scriptsText = JSON.stringify(pkg.scripts || {});
|
|
364
|
+
const roadmap = read(projectRoot, 'docs/ROADMAP.md');
|
|
365
|
+
const suiteCommands = [
|
|
366
|
+
'god-suite-init',
|
|
367
|
+
'god-suite-status',
|
|
368
|
+
'god-suite-sync',
|
|
369
|
+
'god-suite-patch',
|
|
370
|
+
'god-suite-release'
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
addCheck(
|
|
374
|
+
checks,
|
|
375
|
+
'suite',
|
|
376
|
+
'suite-runtime-helper',
|
|
377
|
+
exists(projectRoot, 'lib/suite-state.js') ? 'fresh' : 'stale',
|
|
378
|
+
'lib/suite-state.js',
|
|
379
|
+
exists(projectRoot, 'lib/suite-state.js')
|
|
380
|
+
? 'Mode D suite state helper exists.'
|
|
381
|
+
: 'Mode D suite state helper is missing.',
|
|
382
|
+
{ spawn: exists(projectRoot, 'lib/suite-state.js') ? null : 'god-coordinator' }
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
addCheck(
|
|
386
|
+
checks,
|
|
387
|
+
'suite',
|
|
388
|
+
'suite-test-gate',
|
|
389
|
+
scriptsText.includes('scripts/test-mode-d.js') ? 'fresh' : 'stale',
|
|
390
|
+
'package.json',
|
|
391
|
+
scriptsText.includes('scripts/test-mode-d.js')
|
|
392
|
+
? 'Release gate includes Mode D suite tests.'
|
|
393
|
+
: 'Release gate does not include Mode D suite tests.',
|
|
394
|
+
{ spawn: scriptsText.includes('scripts/test-mode-d.js') ? null : 'god-coordinator' }
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
addCheck(
|
|
398
|
+
checks,
|
|
399
|
+
'suite',
|
|
400
|
+
'suite-docs',
|
|
401
|
+
roadmap.includes('Mode D') ? 'fresh' : 'stale',
|
|
402
|
+
'docs/ROADMAP.md',
|
|
403
|
+
roadmap.includes('Mode D')
|
|
404
|
+
? 'Roadmap documents Mode D suite support.'
|
|
405
|
+
: 'Roadmap does not document Mode D suite support.',
|
|
406
|
+
{ spawn: roadmap.includes('Mode D') ? null : 'god-coordinator' }
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
for (const command of suiteCommands) {
|
|
410
|
+
const skill = `skills/${command}.md`;
|
|
411
|
+
const route = `routing/${command}.yaml`;
|
|
412
|
+
const ok = exists(projectRoot, skill) && exists(projectRoot, route);
|
|
413
|
+
addCheck(
|
|
414
|
+
checks,
|
|
415
|
+
'suite',
|
|
416
|
+
`suite-command-${command}`,
|
|
417
|
+
ok ? 'fresh' : 'stale',
|
|
418
|
+
skill,
|
|
419
|
+
ok
|
|
420
|
+
? `/${command} has skill and routing metadata.`
|
|
421
|
+
: `/${command} is missing a skill or route.`,
|
|
422
|
+
{ spawn: ok ? null : 'god-coordinator' }
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return checks;
|
|
427
|
+
}
|
|
428
|
+
|
|
351
429
|
function releasePolicyChecks(projectRoot) {
|
|
352
430
|
const checks = [];
|
|
353
431
|
const docs = repoDocSync.detect(projectRoot);
|
|
@@ -384,7 +462,11 @@ function detect(projectRoot) {
|
|
|
384
462
|
...agentChecks(projectRoot),
|
|
385
463
|
...workflowRecipeChecks(projectRoot),
|
|
386
464
|
...extensionChecks(projectRoot),
|
|
387
|
-
...
|
|
465
|
+
...suiteChecks(projectRoot),
|
|
466
|
+
...releasePolicyChecks(projectRoot),
|
|
467
|
+
...routeQualitySync.detect(projectRoot).checks,
|
|
468
|
+
...recipeCoverageSync.detect(projectRoot).checks,
|
|
469
|
+
...releaseSurfaceSync.detect(projectRoot).checks
|
|
388
470
|
];
|
|
389
471
|
const stale = checks.filter((check) => check.status !== 'fresh');
|
|
390
472
|
const byArea = {};
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route quality sync.
|
|
3
|
+
*
|
|
4
|
+
* Detects disconnected route automation surfaces: symbolic spawn tokens,
|
|
5
|
+
* unresolved agent targets, contextual exits without an approved reason,
|
|
6
|
+
* missing standards coverage, and agent-spawn routes without trace events.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const { parseSimpleYaml } = require('./intent');
|
|
13
|
+
|
|
14
|
+
const LOG_PATH = '.godpowers/surface/ROUTE-QUALITY-SYNC.md';
|
|
15
|
+
|
|
16
|
+
const CONTEXTUAL_NEXT_ALLOWED = new Set([
|
|
17
|
+
'/god',
|
|
18
|
+
'/god-agent-audit',
|
|
19
|
+
'/god-budget',
|
|
20
|
+
'/god-cache-clear',
|
|
21
|
+
'/god-check-todos',
|
|
22
|
+
'/god-context-scan',
|
|
23
|
+
'/god-cost',
|
|
24
|
+
'/god-discuss',
|
|
25
|
+
'/god-doctor',
|
|
26
|
+
'/god-extension-add',
|
|
27
|
+
'/god-extension-info',
|
|
28
|
+
'/god-extension-list',
|
|
29
|
+
'/god-extension-remove',
|
|
30
|
+
'/god-graph',
|
|
31
|
+
'/god-help',
|
|
32
|
+
'/god-lifecycle',
|
|
33
|
+
'/god-list-assumptions',
|
|
34
|
+
'/god-locate',
|
|
35
|
+
'/god-logs',
|
|
36
|
+
'/god-metrics',
|
|
37
|
+
'/god-next',
|
|
38
|
+
'/god-redo',
|
|
39
|
+
'/god-resume-work',
|
|
40
|
+
'/god-test-extension',
|
|
41
|
+
'/god-thread',
|
|
42
|
+
'/god-trace',
|
|
43
|
+
'/god-workstream'
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const STANDARDS_EXEMPT_COMMANDS = new Set([
|
|
47
|
+
'/god-archaeology',
|
|
48
|
+
'/god-audit',
|
|
49
|
+
'/god-automation-setup',
|
|
50
|
+
'/god-debug',
|
|
51
|
+
'/god-discuss',
|
|
52
|
+
'/god-explore',
|
|
53
|
+
'/god-feature',
|
|
54
|
+
'/god-hotfix',
|
|
55
|
+
'/god-hygiene',
|
|
56
|
+
'/god-init',
|
|
57
|
+
'/god-org-context',
|
|
58
|
+
'/god-party',
|
|
59
|
+
'/god-pause-work',
|
|
60
|
+
'/god-preflight',
|
|
61
|
+
'/god-reconstruct',
|
|
62
|
+
'/god-roadmap-check',
|
|
63
|
+
'/god-smite',
|
|
64
|
+
'/god-tech-debt'
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
function read(projectRoot, relPath) {
|
|
68
|
+
const file = path.join(projectRoot, relPath);
|
|
69
|
+
if (!fs.existsSync(file)) return '';
|
|
70
|
+
return fs.readFileSync(file, 'utf8');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function write(projectRoot, relPath, content) {
|
|
74
|
+
const file = path.join(projectRoot, relPath);
|
|
75
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
76
|
+
fs.writeFileSync(file, content);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function listFiles(projectRoot, relDir, pattern) {
|
|
80
|
+
const dir = path.join(projectRoot, relDir);
|
|
81
|
+
if (!fs.existsSync(dir)) return [];
|
|
82
|
+
return fs.readdirSync(dir)
|
|
83
|
+
.filter((name) => pattern.test(name))
|
|
84
|
+
.sort()
|
|
85
|
+
.map((name) => `${relDir}/${name}`.replace(/\\/g, '/'));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function arr(value) {
|
|
89
|
+
return Array.isArray(value) ? value : [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseRoute(projectRoot, routePath) {
|
|
93
|
+
try {
|
|
94
|
+
return parseSimpleYaml(read(projectRoot, routePath)) || {};
|
|
95
|
+
} catch (err) {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function addCheck(checks, id, status, relPath, message, opts = {}) {
|
|
101
|
+
checks.push({
|
|
102
|
+
area: 'route-quality',
|
|
103
|
+
id,
|
|
104
|
+
status,
|
|
105
|
+
path: relPath,
|
|
106
|
+
message,
|
|
107
|
+
severity: opts.severity || (status === 'fresh' ? 'info' : 'warning'),
|
|
108
|
+
spawn: opts.spawn || null
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function spawnTokens(route) {
|
|
113
|
+
const execution = route.execution || {};
|
|
114
|
+
return normalizeSpawnList([
|
|
115
|
+
...arr(execution.spawns),
|
|
116
|
+
...arr(execution['secondary-spawns']),
|
|
117
|
+
...arr(execution['parallel-spawns'])
|
|
118
|
+
]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeSpawnList(tokens) {
|
|
122
|
+
return tokens
|
|
123
|
+
.map((token) => {
|
|
124
|
+
if (token && typeof token === 'object' && token.agent) return token.agent;
|
|
125
|
+
return token;
|
|
126
|
+
})
|
|
127
|
+
.filter((token) => token !== null && token !== undefined);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isAtomicSpawn(token) {
|
|
131
|
+
return token === 'built-in' || /^god-[a-z0-9-]+$/.test(token);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function detect(projectRoot) {
|
|
135
|
+
const checks = [];
|
|
136
|
+
const routes = listFiles(projectRoot, 'routing', /^god.*\.yaml$/);
|
|
137
|
+
const agents = new Set(listFiles(projectRoot, 'agents', /^god.*\.md$/)
|
|
138
|
+
.map((file) => path.basename(file, '.md')));
|
|
139
|
+
let symbolicCount = 0;
|
|
140
|
+
let unresolvedCount = 0;
|
|
141
|
+
let contextualExitCount = 0;
|
|
142
|
+
let standardsExemptCount = 0;
|
|
143
|
+
let traceEventMissingCount = 0;
|
|
144
|
+
|
|
145
|
+
for (const routePath of routes) {
|
|
146
|
+
const route = parseRoute(projectRoot, routePath);
|
|
147
|
+
const command = route.metadata && route.metadata.command
|
|
148
|
+
? route.metadata.command
|
|
149
|
+
: `/${path.basename(routePath, '.yaml')}`;
|
|
150
|
+
const tokens = spawnTokens(route);
|
|
151
|
+
|
|
152
|
+
for (const token of tokens) {
|
|
153
|
+
if (!isAtomicSpawn(String(token))) {
|
|
154
|
+
symbolicCount++;
|
|
155
|
+
addCheck(
|
|
156
|
+
checks,
|
|
157
|
+
`symbolic-spawn-${command.replace(/[^a-z0-9]+/gi, '-')}`,
|
|
158
|
+
'stale',
|
|
159
|
+
routePath,
|
|
160
|
+
`${command} uses symbolic spawn token ${token}.`,
|
|
161
|
+
{ spawn: 'god-auditor' }
|
|
162
|
+
);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (String(token).startsWith('god-') && !agents.has(String(token))) {
|
|
166
|
+
unresolvedCount++;
|
|
167
|
+
addCheck(
|
|
168
|
+
checks,
|
|
169
|
+
`unresolved-spawn-${command.replace(/[^a-z0-9]+/gi, '-')}-${token}`,
|
|
170
|
+
'stale',
|
|
171
|
+
routePath,
|
|
172
|
+
`${command} references missing agent ${token}.`,
|
|
173
|
+
{ spawn: 'god-auditor' }
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const agentTokens = tokens
|
|
179
|
+
.map((token) => String(token))
|
|
180
|
+
.filter((token) => /^god-[a-z0-9-]+$/.test(token));
|
|
181
|
+
const events = arr(route.endoff && route.endoff.events).map((event) => String(event));
|
|
182
|
+
if (agentTokens.length > 0 && (!events.includes('agent.start') || !events.includes('agent.end'))) {
|
|
183
|
+
traceEventMissingCount++;
|
|
184
|
+
addCheck(
|
|
185
|
+
checks,
|
|
186
|
+
`missing-trace-events-${command.replace(/[^a-z0-9]+/gi, '-')}`,
|
|
187
|
+
'stale',
|
|
188
|
+
routePath,
|
|
189
|
+
`${command} spawns agents but does not declare both agent.start and agent.end trace events.`,
|
|
190
|
+
{ spawn: 'god-auditor' }
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const next = route['success-path'] && route['success-path']['next-recommended'];
|
|
195
|
+
const conditionalNext = route['success-path'] && arr(route['success-path']['conditional-next']);
|
|
196
|
+
if (next === 'varies' && conditionalNext.length === 0) {
|
|
197
|
+
if (CONTEXTUAL_NEXT_ALLOWED.has(command)) {
|
|
198
|
+
contextualExitCount++;
|
|
199
|
+
} else {
|
|
200
|
+
addCheck(
|
|
201
|
+
checks,
|
|
202
|
+
`unapproved-varies-${command.replace(/[^a-z0-9]+/gi, '-')}`,
|
|
203
|
+
'stale',
|
|
204
|
+
routePath,
|
|
205
|
+
`${command} uses next-recommended: varies without an approved contextual-exit classification.`,
|
|
206
|
+
{ spawn: 'god-roadmap-reconciler' }
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const writes = arr(route.execution && route.execution.writes);
|
|
212
|
+
const writesDurableSurface = writes.length > 0;
|
|
213
|
+
if (writesDurableSurface && !route.standards) {
|
|
214
|
+
if (STANDARDS_EXEMPT_COMMANDS.has(command)) {
|
|
215
|
+
standardsExemptCount++;
|
|
216
|
+
} else {
|
|
217
|
+
addCheck(
|
|
218
|
+
checks,
|
|
219
|
+
`missing-standards-${command.replace(/[^a-z0-9]+/gi, '-')}`,
|
|
220
|
+
'stale',
|
|
221
|
+
routePath,
|
|
222
|
+
`${command} writes durable surfaces but has no standards block or approved exemption.`,
|
|
223
|
+
{ spawn: 'god-auditor' }
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (symbolicCount === 0) {
|
|
230
|
+
addCheck(checks, 'atomic-spawn-tokens', 'fresh', 'routing/', 'All route spawn tokens are atomic.');
|
|
231
|
+
}
|
|
232
|
+
if (unresolvedCount === 0) {
|
|
233
|
+
addCheck(checks, 'resolved-spawn-targets', 'fresh', 'routing/', 'All route spawn targets resolve to shipped agents or built-in runtime work.');
|
|
234
|
+
}
|
|
235
|
+
addCheck(
|
|
236
|
+
checks,
|
|
237
|
+
'contextual-exit-policy',
|
|
238
|
+
checks.some((check) => check.id.startsWith('unapproved-varies-')) ? 'stale' : 'fresh',
|
|
239
|
+
'routing/',
|
|
240
|
+
`${contextualExitCount} contextual route exits are approved and all other next routes are explicit.`,
|
|
241
|
+
{ spawn: checks.some((check) => check.id.startsWith('unapproved-varies-')) ? 'god-roadmap-reconciler' : null }
|
|
242
|
+
);
|
|
243
|
+
addCheck(
|
|
244
|
+
checks,
|
|
245
|
+
'standards-policy',
|
|
246
|
+
checks.some((check) => check.id.startsWith('missing-standards-')) ? 'stale' : 'fresh',
|
|
247
|
+
'routing/',
|
|
248
|
+
`${standardsExemptCount} durable-writing routes have approved standards exemptions and all other writing routes declare standards.`,
|
|
249
|
+
{ spawn: checks.some((check) => check.id.startsWith('missing-standards-')) ? 'god-auditor' : null }
|
|
250
|
+
);
|
|
251
|
+
addCheck(
|
|
252
|
+
checks,
|
|
253
|
+
'agent-trace-policy',
|
|
254
|
+
traceEventMissingCount === 0 ? 'fresh' : 'stale',
|
|
255
|
+
'routing/',
|
|
256
|
+
traceEventMissingCount === 0
|
|
257
|
+
? 'All agent-spawning routes declare agent.start and agent.end trace events.'
|
|
258
|
+
: `${traceEventMissingCount} agent-spawning routes are missing required trace events.`,
|
|
259
|
+
{ spawn: traceEventMissingCount === 0 ? null : 'god-auditor' }
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const stale = checks.filter((check) => check.status !== 'fresh');
|
|
263
|
+
return {
|
|
264
|
+
status: stale.length === 0 ? 'fresh' : 'stale',
|
|
265
|
+
checks,
|
|
266
|
+
stale
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function appendLog(projectRoot, before, after) {
|
|
271
|
+
const now = new Date().toISOString();
|
|
272
|
+
const lines = [];
|
|
273
|
+
if (fs.existsSync(path.join(projectRoot, LOG_PATH))) {
|
|
274
|
+
lines.push(read(projectRoot, LOG_PATH).replace(/\s*$/, ''));
|
|
275
|
+
lines.push('');
|
|
276
|
+
} else {
|
|
277
|
+
lines.push('# Route Quality Sync Log');
|
|
278
|
+
lines.push('');
|
|
279
|
+
lines.push('- [DECISION] This file records route-quality sync checks run by Godpowers.');
|
|
280
|
+
lines.push('');
|
|
281
|
+
}
|
|
282
|
+
lines.push(`## ${now}`);
|
|
283
|
+
lines.push('');
|
|
284
|
+
lines.push(`- [DECISION] Route quality status before apply was ${before.status}.`);
|
|
285
|
+
lines.push(`- [DECISION] Route quality status after apply is ${after.status}.`);
|
|
286
|
+
lines.push('');
|
|
287
|
+
write(projectRoot, LOG_PATH, lines.join('\n'));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function run(projectRoot, opts = {}) {
|
|
291
|
+
const before = detect(projectRoot);
|
|
292
|
+
const after = detect(projectRoot);
|
|
293
|
+
if (opts.log !== false) appendLog(projectRoot, before, after);
|
|
294
|
+
return {
|
|
295
|
+
before,
|
|
296
|
+
after,
|
|
297
|
+
applied: [],
|
|
298
|
+
logPath: opts.log === false ? null : LOG_PATH
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function summary(report) {
|
|
303
|
+
return report.status === 'fresh' ? 'fresh' : `${report.stale.length} stale`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
module.exports = {
|
|
307
|
+
LOG_PATH,
|
|
308
|
+
CONTEXTUAL_NEXT_ALLOWED,
|
|
309
|
+
STANDARDS_EXEMPT_COMMANDS,
|
|
310
|
+
detect,
|
|
311
|
+
run,
|
|
312
|
+
summary
|
|
313
|
+
};
|
package/lib/router.js
CHANGED
|
@@ -221,7 +221,10 @@ function getSpawnedAgents(command) {
|
|
|
221
221
|
if (!routing || !routing.execution) return [];
|
|
222
222
|
const primary = routing.execution.spawns || [];
|
|
223
223
|
const secondary = routing.execution['secondary-spawns'] || [];
|
|
224
|
-
|
|
224
|
+
const parallel = routing.execution['parallel-spawns'] || [];
|
|
225
|
+
return [...primary, ...secondary, ...parallel]
|
|
226
|
+
.map(spawn => (spawn && typeof spawn === 'object' && spawn.agent) ? spawn.agent : spawn)
|
|
227
|
+
.filter(spawn => spawn !== null && spawn !== undefined);
|
|
225
228
|
}
|
|
226
229
|
|
|
227
230
|
/**
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "godpowers",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.21",
|
|
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-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",
|
|
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-automation-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",
|
package/routing/god-init.yaml
CHANGED
package/routing/god-party.yaml
CHANGED
|
@@ -9,9 +9,11 @@ prerequisites:
|
|
|
9
9
|
required: []
|
|
10
10
|
|
|
11
11
|
execution:
|
|
12
|
-
spawns: [
|
|
12
|
+
spawns: [built-in]
|
|
13
13
|
context: fresh
|
|
14
|
-
|
|
14
|
+
parallel-spawns: [god-pm, god-architect, god-executor, god-quality-reviewer, god-harden-auditor, god-launch-strategist]
|
|
15
|
+
writes:
|
|
16
|
+
- .godpowers/party/*.md
|
|
15
17
|
|
|
16
18
|
success-path:
|
|
17
19
|
next-recommended: /god-next
|