godpowers 1.6.20 → 1.6.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +28 -9
- package/RELEASE.md +38 -21
- package/bin/install.js +81 -1
- package/fixtures/dogfood/extension-authoring/manifest.json +13 -0
- package/fixtures/dogfood/half-migrated-gsd/.planning/PROJECT.md +5 -0
- package/fixtures/dogfood/half-migrated-gsd/.planning/REQUIREMENTS.md +5 -0
- package/fixtures/dogfood/half-migrated-gsd/.planning/ROADMAP.md +5 -0
- package/fixtures/dogfood/half-migrated-gsd/manifest.json +16 -0
- package/fixtures/dogfood/host-degraded/manifest.json +5 -0
- package/fixtures/dogfood/host-full/home/.codex/agents/god-orchestrator.toml +2 -0
- package/fixtures/dogfood/host-full/manifest.json +5 -0
- package/fixtures/dogfood/suite-release-dry-run/.godpowers/suite-config.yaml +9 -0
- package/fixtures/dogfood/suite-release-dry-run/manifest.json +7 -0
- package/fixtures/dogfood/suite-release-dry-run/repo-a/package.json +4 -0
- package/fixtures/dogfood/suite-release-dry-run/repo-b/package.json +7 -0
- package/lib/README.md +3 -0
- package/lib/dashboard.js +73 -3
- package/lib/dogfood-runner.js +193 -0
- package/lib/extension-authoring.js +154 -0
- package/lib/feature-awareness.js +18 -0
- package/lib/host-capabilities.js +125 -0
- package/lib/release-surface-sync.js +30 -0
- package/lib/repo-surface-sync.js +128 -0
- package/lib/route-quality-sync.js +31 -4
- package/lib/suite-state.js +90 -1
- package/package.json +4 -3
- package/routing/god-dogfood.yaml +35 -0
- package/routing/god-init.yaml +1 -1
- package/routing/god-roadmap-update.yaml +1 -1
- package/routing/god-sync.yaml +1 -1
- package/skills/god-doctor.md +1 -1
- package/skills/god-dogfood.md +63 -0
- package/skills/god-version.md +1 -1
package/lib/dashboard.js
CHANGED
|
@@ -14,6 +14,7 @@ const router = require('./router');
|
|
|
14
14
|
const automationProviders = require('./automation-providers');
|
|
15
15
|
const repoDocSync = require('./repo-doc-sync');
|
|
16
16
|
const repoSurfaceSync = require('./repo-surface-sync');
|
|
17
|
+
const hostCapabilities = require('./host-capabilities');
|
|
17
18
|
|
|
18
19
|
const GOD_DIR = '.godpowers';
|
|
19
20
|
const PRD_PATH = '.godpowers/prd/PRD.md';
|
|
@@ -168,6 +169,7 @@ function proactiveChecks(projectRoot, changedFiles = []) {
|
|
|
168
169
|
const repoSurfaceStatus = repoSurface.status === 'fresh'
|
|
169
170
|
? 'fresh'
|
|
170
171
|
: `${repoSurface.stale.length} stale, suggest /god-doctor`;
|
|
172
|
+
const host = hostCapabilities.detect(projectRoot);
|
|
171
173
|
|
|
172
174
|
return {
|
|
173
175
|
checkpoint,
|
|
@@ -175,6 +177,7 @@ function proactiveChecks(projectRoot, changedFiles = []) {
|
|
|
175
177
|
sync,
|
|
176
178
|
docs: repoDocsStatus,
|
|
177
179
|
repoSurface: repoSurfaceStatus,
|
|
180
|
+
host: hostCapabilities.summary(host),
|
|
178
181
|
runtime: 'not-applicable',
|
|
179
182
|
automation: automationSummary(projectRoot),
|
|
180
183
|
security: sensitiveChanged ? 'sensitive files changed, suggest /god-harden' : 'clear',
|
|
@@ -217,7 +220,7 @@ function compute(projectRoot, opts = {}) {
|
|
|
217
220
|
|
|
218
221
|
if (!s) {
|
|
219
222
|
const next = { command: '/god-init', reason: 'No Godpowers project initialized' };
|
|
220
|
-
|
|
223
|
+
const result = {
|
|
221
224
|
source: 'runtime dashboard (lib/dashboard.js)',
|
|
222
225
|
state: 'not initialized',
|
|
223
226
|
mode: null,
|
|
@@ -234,9 +237,12 @@ function compute(projectRoot, opts = {}) {
|
|
|
234
237
|
completionBasis: 'missing .godpowers/state.json'
|
|
235
238
|
},
|
|
236
239
|
proactive: proactiveChecks(projectRoot, git.entries.map(statusPath)),
|
|
240
|
+
host: hostCapabilities.detect(projectRoot, opts.host || {}),
|
|
237
241
|
next,
|
|
238
242
|
openItems: ['No .godpowers/state.json found']
|
|
239
243
|
};
|
|
244
|
+
result.actionBrief = actionBrief(result);
|
|
245
|
+
return result;
|
|
240
246
|
}
|
|
241
247
|
|
|
242
248
|
const progress = state.progressSummary(s);
|
|
@@ -250,7 +256,7 @@ function compute(projectRoot, opts = {}) {
|
|
|
250
256
|
if (reviewCount(projectRoot) > 0) openItems.push('pending review items');
|
|
251
257
|
if (openItems.length === 0) openItems.push('none');
|
|
252
258
|
|
|
253
|
-
|
|
259
|
+
const result = {
|
|
254
260
|
source: 'runtime dashboard (lib/dashboard.js)',
|
|
255
261
|
state: progress.remaining === 0 ? 'complete' : 'in progress',
|
|
256
262
|
mode: s.mode || s['mode-announced-as'] || null,
|
|
@@ -261,12 +267,46 @@ function compute(projectRoot, opts = {}) {
|
|
|
261
267
|
index: git.index,
|
|
262
268
|
planning: planningVisibility(projectRoot, progress),
|
|
263
269
|
proactive: proactiveChecks(projectRoot, git.entries.map(statusPath)),
|
|
270
|
+
host: hostCapabilities.detect(projectRoot, opts.host || {}),
|
|
264
271
|
next,
|
|
265
272
|
openItems
|
|
266
273
|
};
|
|
274
|
+
result.actionBrief = actionBrief(result);
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function actionBrief(dashboard) {
|
|
279
|
+
const proactive = dashboard.proactive || {};
|
|
280
|
+
const next = dashboard.next || {};
|
|
281
|
+
const blockers = [];
|
|
282
|
+
for (const [label, value] of [
|
|
283
|
+
['Repo surface', proactive.repoSurface],
|
|
284
|
+
['Docs', proactive.docs],
|
|
285
|
+
['Host', proactive.host],
|
|
286
|
+
['Reviews', proactive.reviews],
|
|
287
|
+
['Sync', proactive.sync],
|
|
288
|
+
['Security', proactive.security],
|
|
289
|
+
['Dependencies', proactive.dependencies],
|
|
290
|
+
['Hygiene', proactive.hygiene]
|
|
291
|
+
]) {
|
|
292
|
+
if (!value) continue;
|
|
293
|
+
if (value === 'fresh' || value === 'none' || value === 'clear' || value === 'not-applicable') continue;
|
|
294
|
+
if (/^full on /.test(value)) continue;
|
|
295
|
+
if (/^available via /.test(value)) continue;
|
|
296
|
+
blockers.push(`${label}: ${value}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const recommended = next.command || 'describe the next intent';
|
|
300
|
+
return {
|
|
301
|
+
recommended,
|
|
302
|
+
reason: next.reason || 'No route was computed.',
|
|
303
|
+
confidence: blockers.length === 0 ? 'ready' : 'needs attention',
|
|
304
|
+
blockers: blockers.slice(0, 3),
|
|
305
|
+
overflow: Math.max(0, blockers.length - 3)
|
|
306
|
+
};
|
|
267
307
|
}
|
|
268
308
|
|
|
269
|
-
function render(dashboard) {
|
|
309
|
+
function render(dashboard, opts = {}) {
|
|
270
310
|
const current = dashboard.current || {};
|
|
271
311
|
const planning = dashboard.planning || {};
|
|
272
312
|
const proactive = dashboard.proactive || {};
|
|
@@ -277,6 +317,27 @@ function render(dashboard) {
|
|
|
277
317
|
const openItems = dashboard.openItems && dashboard.openItems.length > 0
|
|
278
318
|
? dashboard.openItems
|
|
279
319
|
: ['none'];
|
|
320
|
+
const brief = dashboard.actionBrief || actionBrief(dashboard);
|
|
321
|
+
if (opts.mode === 'brief' || opts.brief === true) {
|
|
322
|
+
return [
|
|
323
|
+
'Godpowers Dashboard',
|
|
324
|
+
'',
|
|
325
|
+
'Action brief:',
|
|
326
|
+
` Next: ${brief.recommended}`,
|
|
327
|
+
` Why: ${brief.reason}`,
|
|
328
|
+
` Readiness: ${brief.confidence}`,
|
|
329
|
+
` Attention: ${brief.blockers && brief.blockers.length > 0 ? brief.blockers.join('; ') : 'none'}${brief.overflow ? `; ${brief.overflow} more` : ''}`,
|
|
330
|
+
` Host guarantees: ${dashboard.host ? hostCapabilities.summary(dashboard.host) : proactive.host || 'unknown'}`,
|
|
331
|
+
'',
|
|
332
|
+
'Current status:',
|
|
333
|
+
` State: ${dashboard.state}`,
|
|
334
|
+
` Progress: ${progress.percent || 0}% workflow progress (${progress.completed || 0} of ${progress.total || 0} tracked steps complete)`,
|
|
335
|
+
'',
|
|
336
|
+
'Next:',
|
|
337
|
+
` Recommended: ${next.command || 'describe the next intent'}`,
|
|
338
|
+
` Why: ${next.reason || 'No route was computed.'}`
|
|
339
|
+
].join('\n');
|
|
340
|
+
}
|
|
280
341
|
|
|
281
342
|
return [
|
|
282
343
|
'Godpowers Dashboard',
|
|
@@ -291,6 +352,13 @@ function render(dashboard) {
|
|
|
291
352
|
` Worktree: ${dashboard.worktree}`,
|
|
292
353
|
` Index: ${dashboard.index}`,
|
|
293
354
|
'',
|
|
355
|
+
'Action brief:',
|
|
356
|
+
` Next: ${brief.recommended}`,
|
|
357
|
+
` Why: ${brief.reason}`,
|
|
358
|
+
` Readiness: ${brief.confidence}`,
|
|
359
|
+
` Attention: ${brief.blockers && brief.blockers.length > 0 ? brief.blockers.join('; ') : 'none'}${brief.overflow ? `; ${brief.overflow} more` : ''}`,
|
|
360
|
+
` Host guarantees: ${dashboard.host ? hostCapabilities.summary(dashboard.host) : proactive.host || 'unknown'}`,
|
|
361
|
+
'',
|
|
294
362
|
'Planning visibility:',
|
|
295
363
|
` PRD: ${prd.status || 'missing'}${prd.path ? ` ${prd.path}` : ''}`,
|
|
296
364
|
` Roadmap: ${roadmap.status || 'missing'}${roadmap.path ? ` ${roadmap.path}` : ''}`,
|
|
@@ -303,6 +371,7 @@ function render(dashboard) {
|
|
|
303
371
|
` Sync: ${proactive.sync || 'unknown'}`,
|
|
304
372
|
` Docs: ${proactive.docs || 'unknown'}`,
|
|
305
373
|
` Repo surface: ${proactive.repoSurface || 'unknown'}`,
|
|
374
|
+
` Host: ${proactive.host || 'unknown'}`,
|
|
306
375
|
` Runtime: ${proactive.runtime || 'unknown'}`,
|
|
307
376
|
` Automation: ${proactive.automation || 'unknown'}`,
|
|
308
377
|
` Security: ${proactive.security || 'unknown'}`,
|
|
@@ -325,5 +394,6 @@ module.exports = {
|
|
|
325
394
|
parseGitStatus,
|
|
326
395
|
proactiveChecks,
|
|
327
396
|
automationSummary,
|
|
397
|
+
actionBrief,
|
|
328
398
|
planningVisibility
|
|
329
399
|
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dogfood runner.
|
|
3
|
+
*
|
|
4
|
+
* Executes deterministic messy-repo scenarios against the Godpowers runtime.
|
|
5
|
+
* The runner uses fixtures so release gates can verify migrations, sync-back,
|
|
6
|
+
* host guarantees, package surfaces, extension authoring, and suite planning
|
|
7
|
+
* without touching real user projects.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
const planningSystems = require('./planning-systems');
|
|
15
|
+
const sourceSync = require('./source-sync');
|
|
16
|
+
const hostCapabilities = require('./host-capabilities');
|
|
17
|
+
const extensionAuthoring = require('./extension-authoring');
|
|
18
|
+
const suiteState = require('./suite-state');
|
|
19
|
+
|
|
20
|
+
const FIXTURE_ROOT = path.join(__dirname, '..', 'fixtures', 'dogfood');
|
|
21
|
+
|
|
22
|
+
function readJson(file) {
|
|
23
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function copyDir(src, dest) {
|
|
27
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
28
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
29
|
+
if (entry.name === 'manifest.json') continue;
|
|
30
|
+
const source = path.join(src, entry.name);
|
|
31
|
+
const target = path.join(dest, entry.name);
|
|
32
|
+
if (entry.isDirectory()) copyDir(source, target);
|
|
33
|
+
else fs.copyFileSync(source, target);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function listScenarios(root = FIXTURE_ROOT) {
|
|
38
|
+
if (!fs.existsSync(root)) return [];
|
|
39
|
+
return fs.readdirSync(root, { withFileTypes: true })
|
|
40
|
+
.filter((entry) => entry.isDirectory())
|
|
41
|
+
.map((entry) => {
|
|
42
|
+
const dir = path.join(root, entry.name);
|
|
43
|
+
const manifest = readJson(path.join(dir, 'manifest.json'));
|
|
44
|
+
return { id: entry.name, dir, manifest };
|
|
45
|
+
})
|
|
46
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function prepareScenario(scenario) {
|
|
50
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), `godpowers-dogfood-${scenario.id}-`));
|
|
51
|
+
copyDir(scenario.dir, tmp);
|
|
52
|
+
return tmp;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function check(condition, id, message, details = {}) {
|
|
56
|
+
return {
|
|
57
|
+
id,
|
|
58
|
+
status: condition ? 'pass' : 'fail',
|
|
59
|
+
message,
|
|
60
|
+
details
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function runMigrationScenario(projectRoot, manifest) {
|
|
65
|
+
const detection = planningSystems.detect(projectRoot);
|
|
66
|
+
const report = { detection, importResult: null, syncResult: null };
|
|
67
|
+
if (manifest.actions && manifest.actions.includes('import-planning-context')) {
|
|
68
|
+
report.importResult = planningSystems.importPlanningContext(projectRoot, { detection });
|
|
69
|
+
}
|
|
70
|
+
if (manifest.actions && manifest.actions.includes('sync-back')) {
|
|
71
|
+
report.syncResult = sourceSync.run(projectRoot);
|
|
72
|
+
}
|
|
73
|
+
return report;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function runExtensionScenario(projectRoot, manifest) {
|
|
77
|
+
const packName = manifest.extensionName || '@godpowers/dogfood-pack';
|
|
78
|
+
const target = path.join(projectRoot, 'extension-out');
|
|
79
|
+
return extensionAuthoring.scaffold(target, {
|
|
80
|
+
name: packName,
|
|
81
|
+
version: '0.1.0',
|
|
82
|
+
skill: 'god-dogfood-extension',
|
|
83
|
+
agent: 'god-dogfood-agent',
|
|
84
|
+
workflow: 'dogfood-workflow',
|
|
85
|
+
godpowersRange: '>=1.6.0'
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function runSuiteScenario(projectRoot, manifest) {
|
|
90
|
+
const plan = suiteState.planRelease(projectRoot, manifest.repo || 'repo-a', manifest.version || '1.2.4');
|
|
91
|
+
return plan;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function runScenario(scenario, opts = {}) {
|
|
95
|
+
const projectRoot = opts.projectRoot || prepareScenario(scenario);
|
|
96
|
+
const manifest = scenario.manifest;
|
|
97
|
+
const checks = [];
|
|
98
|
+
const context = {};
|
|
99
|
+
|
|
100
|
+
if (manifest.kind === 'planning-migration') {
|
|
101
|
+
context.migration = runMigrationScenario(projectRoot, manifest);
|
|
102
|
+
const detected = context.migration.detection.systems.map((system) => system.id);
|
|
103
|
+
for (const id of manifest.expectedSystems || []) {
|
|
104
|
+
checks.push(check(detected.includes(id), `detect-${id}`, `Detected ${id}.`, { detected }));
|
|
105
|
+
}
|
|
106
|
+
for (const relPath of manifest.expectedFiles || []) {
|
|
107
|
+
checks.push(check(fs.existsSync(path.join(projectRoot, relPath)), `file-${relPath}`, `${relPath} exists.`));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (manifest.kind === 'host-capabilities') {
|
|
112
|
+
context.host = hostCapabilities.detect(projectRoot, {
|
|
113
|
+
homeDir: path.join(projectRoot, 'home'),
|
|
114
|
+
env: { SHELL: '/bin/zsh', CODEX_HOME: path.join(projectRoot, 'home', '.codex') }
|
|
115
|
+
});
|
|
116
|
+
checks.push(check(context.host.level === manifest.expectedLevel,
|
|
117
|
+
'host-level',
|
|
118
|
+
`Host capability level is ${manifest.expectedLevel}.`,
|
|
119
|
+
{ level: context.host.level }));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (manifest.kind === 'extension-authoring') {
|
|
123
|
+
context.extension = runExtensionScenario(projectRoot, manifest);
|
|
124
|
+
for (const relPath of manifest.expectedFiles || []) {
|
|
125
|
+
checks.push(check(fs.existsSync(path.join(context.extension.path, relPath)),
|
|
126
|
+
`extension-file-${relPath}`,
|
|
127
|
+
`Extension file ${relPath} exists.`));
|
|
128
|
+
}
|
|
129
|
+
checks.push(check(context.extension.validation.length === 0,
|
|
130
|
+
'extension-validation',
|
|
131
|
+
'Scaffolded extension validates.',
|
|
132
|
+
{ validation: context.extension.validation }));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (manifest.kind === 'suite-release') {
|
|
136
|
+
context.suite = runSuiteScenario(projectRoot, manifest);
|
|
137
|
+
checks.push(check(context.suite.mode === 'dry-run',
|
|
138
|
+
'suite-dry-run',
|
|
139
|
+
'Suite release plan is dry-run mode.'));
|
|
140
|
+
checks.push(check(context.suite.impacted.length === (manifest.expectedImpacted || 0),
|
|
141
|
+
'suite-impact-count',
|
|
142
|
+
`Suite release impact count is ${manifest.expectedImpacted || 0}.`,
|
|
143
|
+
{ impacted: context.suite.impacted }));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const failed = checks.filter((item) => item.status !== 'pass');
|
|
147
|
+
return {
|
|
148
|
+
id: scenario.id,
|
|
149
|
+
name: manifest.name || scenario.id,
|
|
150
|
+
projectRoot,
|
|
151
|
+
status: failed.length === 0 ? 'pass' : 'fail',
|
|
152
|
+
checks,
|
|
153
|
+
context
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function runAll(opts = {}) {
|
|
158
|
+
const scenarios = listScenarios(opts.fixtureRoot);
|
|
159
|
+
const results = scenarios.map((scenario) => runScenario(scenario, opts));
|
|
160
|
+
const failed = results.filter((result) => result.status !== 'pass');
|
|
161
|
+
return {
|
|
162
|
+
status: failed.length === 0 ? 'pass' : 'fail',
|
|
163
|
+
total: results.length,
|
|
164
|
+
passed: results.length - failed.length,
|
|
165
|
+
failed: failed.length,
|
|
166
|
+
results
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function render(report) {
|
|
171
|
+
const lines = [];
|
|
172
|
+
lines.push('Godpowers Dogfood Report');
|
|
173
|
+
lines.push('');
|
|
174
|
+
lines.push(`Status: ${report.status}`);
|
|
175
|
+
lines.push(`Scenarios: ${report.passed} passed, ${report.failed} failed, ${report.total} total`);
|
|
176
|
+
for (const result of report.results) {
|
|
177
|
+
lines.push('');
|
|
178
|
+
lines.push(`## ${result.name}`);
|
|
179
|
+
lines.push(`Status: ${result.status}`);
|
|
180
|
+
for (const item of result.checks) {
|
|
181
|
+
lines.push(`- [${item.status.toUpperCase()}] ${item.message}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return lines.join('\n');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = {
|
|
188
|
+
FIXTURE_ROOT,
|
|
189
|
+
listScenarios,
|
|
190
|
+
runScenario,
|
|
191
|
+
runAll,
|
|
192
|
+
render
|
|
193
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension authoring helpers.
|
|
3
|
+
*
|
|
4
|
+
* Creates a publishable Godpowers extension skeleton that follows the same
|
|
5
|
+
* manifest, package, README, skill, agent, and workflow contracts as the
|
|
6
|
+
* first-party packs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const extensions = require('./extensions');
|
|
13
|
+
|
|
14
|
+
function ensureDir(filePath) {
|
|
15
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function write(filePath, content, overwrite) {
|
|
19
|
+
if (fs.existsSync(filePath) && overwrite !== true) return false;
|
|
20
|
+
ensureDir(filePath);
|
|
21
|
+
fs.writeFileSync(filePath, content);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function packageFolderName(name) {
|
|
26
|
+
return name.replace(/^@/, '').replace(/\//g, '-');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function scaffold(outputRoot, opts = {}) {
|
|
30
|
+
const name = opts.name || '@godpowers/custom-pack';
|
|
31
|
+
const version = opts.version || '0.1.0';
|
|
32
|
+
const skill = opts.skill || 'god-custom-pack';
|
|
33
|
+
const agent = opts.agent || null;
|
|
34
|
+
const workflow = opts.workflow || null;
|
|
35
|
+
const range = opts.godpowersRange || '>=1.6.0';
|
|
36
|
+
const folder = opts.folder || packageFolderName(name);
|
|
37
|
+
const root = path.join(outputRoot, folder);
|
|
38
|
+
const written = [];
|
|
39
|
+
|
|
40
|
+
const manifest = [
|
|
41
|
+
'apiVersion: godpowers/v1',
|
|
42
|
+
'kind: Extension',
|
|
43
|
+
'metadata:',
|
|
44
|
+
` name: ${name}`,
|
|
45
|
+
` version: ${version}`,
|
|
46
|
+
' description: Custom Godpowers extension pack.',
|
|
47
|
+
'engines:',
|
|
48
|
+
` godpowers: ${range}`,
|
|
49
|
+
'provides:',
|
|
50
|
+
' skills:',
|
|
51
|
+
` - ${skill}`,
|
|
52
|
+
...(agent ? [' agents:', ` - ${agent}`] : []),
|
|
53
|
+
...(workflow ? [' workflows:', ` - ${workflow}`] : []),
|
|
54
|
+
''
|
|
55
|
+
].join('\n');
|
|
56
|
+
|
|
57
|
+
const pkg = {
|
|
58
|
+
name,
|
|
59
|
+
version,
|
|
60
|
+
description: 'Custom Godpowers extension pack.',
|
|
61
|
+
license: 'MIT',
|
|
62
|
+
files: [
|
|
63
|
+
'manifest.yaml',
|
|
64
|
+
'README.md',
|
|
65
|
+
'skills/',
|
|
66
|
+
'agents/',
|
|
67
|
+
'workflows/',
|
|
68
|
+
'references/'
|
|
69
|
+
],
|
|
70
|
+
peerDependencies: {
|
|
71
|
+
godpowers: range
|
|
72
|
+
},
|
|
73
|
+
publishConfig: {
|
|
74
|
+
access: 'public'
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const readme = [
|
|
79
|
+
`# ${name}`,
|
|
80
|
+
'',
|
|
81
|
+
'Custom Godpowers extension pack.',
|
|
82
|
+
'',
|
|
83
|
+
'## Contents',
|
|
84
|
+
'',
|
|
85
|
+
`- Skill: ${skill}`,
|
|
86
|
+
...(agent ? [`- Agent: ${agent}`] : []),
|
|
87
|
+
...(workflow ? [`- Workflow: ${workflow}`] : []),
|
|
88
|
+
''
|
|
89
|
+
].join('\n');
|
|
90
|
+
|
|
91
|
+
const skillMd = [
|
|
92
|
+
'---',
|
|
93
|
+
`name: ${skill}`,
|
|
94
|
+
'description: |',
|
|
95
|
+
' Custom Godpowers extension command.',
|
|
96
|
+
'',
|
|
97
|
+
` Triggers on: "${skill.replace(/^god-/, 'god ')}", "/${skill}"`,
|
|
98
|
+
'---',
|
|
99
|
+
'',
|
|
100
|
+
`# /${skill}`,
|
|
101
|
+
'',
|
|
102
|
+
'- [DECISION] This extension command is scaffolded for project-specific behavior.',
|
|
103
|
+
'- [OPEN QUESTION] Replace this placeholder with the command workflow. Owner: extension author. Due: before publish.',
|
|
104
|
+
''
|
|
105
|
+
].join('\n');
|
|
106
|
+
|
|
107
|
+
if (write(path.join(root, 'manifest.yaml'), manifest, opts.overwrite)) written.push('manifest.yaml');
|
|
108
|
+
if (write(path.join(root, 'package.json'), `${JSON.stringify(pkg, null, 2)}\n`, opts.overwrite)) written.push('package.json');
|
|
109
|
+
if (write(path.join(root, 'README.md'), readme, opts.overwrite)) written.push('README.md');
|
|
110
|
+
if (write(path.join(root, 'skills', `${skill}.md`), skillMd, opts.overwrite)) written.push(`skills/${skill}.md`);
|
|
111
|
+
|
|
112
|
+
if (agent) {
|
|
113
|
+
const agentMd = [
|
|
114
|
+
'---',
|
|
115
|
+
`name: ${agent}`,
|
|
116
|
+
'description: Custom Godpowers extension specialist.',
|
|
117
|
+
'---',
|
|
118
|
+
'',
|
|
119
|
+
`# ${agent}`,
|
|
120
|
+
'',
|
|
121
|
+
'- [DECISION] Follow the extension command handoff and return only user-facing progress.',
|
|
122
|
+
''
|
|
123
|
+
].join('\n');
|
|
124
|
+
if (write(path.join(root, 'agents', `${agent}.md`), agentMd, opts.overwrite)) written.push(`agents/${agent}.md`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (workflow) {
|
|
128
|
+
const workflowYaml = [
|
|
129
|
+
'apiVersion: godpowers/v1',
|
|
130
|
+
'kind: Workflow',
|
|
131
|
+
`name: ${workflow}`,
|
|
132
|
+
'steps:',
|
|
133
|
+
` - command: /${skill}`,
|
|
134
|
+
''
|
|
135
|
+
].join('\n');
|
|
136
|
+
if (write(path.join(root, 'workflows', `${workflow}.yaml`), workflowYaml, opts.overwrite)) written.push(`workflows/${workflow}.yaml`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const parsed = extensions.parseManifest(fs.readFileSync(path.join(root, 'manifest.yaml'), 'utf8')).manifest;
|
|
140
|
+
const validation = extensions.validateManifest(parsed, opts.runtimeVersion || '1.6.0');
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
path: root,
|
|
144
|
+
name,
|
|
145
|
+
version,
|
|
146
|
+
written,
|
|
147
|
+
validation
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = {
|
|
152
|
+
scaffold,
|
|
153
|
+
packageFolderName
|
|
154
|
+
};
|
package/lib/feature-awareness.js
CHANGED
|
@@ -64,6 +64,24 @@ const FEATURES = [
|
|
|
64
64
|
since: '1.6.19',
|
|
65
65
|
commands: ['/god-sync', '/god-docs', '/god-doctor', '/god-status', '/god-mode'],
|
|
66
66
|
description: 'Detect release-facing drift across badges, release notes, changelog, package checks, and release checklist policy.'
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'dogfood-runner',
|
|
70
|
+
since: '1.6.22',
|
|
71
|
+
commands: ['/god-dogfood'],
|
|
72
|
+
description: 'Run messy-repo fixtures for migration, host guarantee, extension authoring, and suite release readiness.'
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'host-capabilities',
|
|
76
|
+
since: '1.6.22',
|
|
77
|
+
commands: ['/god-status', '/god-next'],
|
|
78
|
+
description: 'Report full, degraded, or unknown host guarantees in dashboard output.'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'extension-authoring',
|
|
82
|
+
since: '1.6.22',
|
|
83
|
+
commands: ['/god-extension-add', '/god-test-extension'],
|
|
84
|
+
description: 'Scaffold and validate publishable Godpowers extension packs.'
|
|
67
85
|
}
|
|
68
86
|
];
|
|
69
87
|
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host capability detection.
|
|
3
|
+
*
|
|
4
|
+
* Reports what the current AI coding host can actually guarantee at runtime.
|
|
5
|
+
* This keeps Godpowers honest when true fresh-context spawning or release
|
|
6
|
+
* tools depend on the host environment.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const cp = require('child_process');
|
|
13
|
+
|
|
14
|
+
function exists(filePath) {
|
|
15
|
+
return fs.existsSync(filePath);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function commandVersion(command, args, opts = {}) {
|
|
19
|
+
try {
|
|
20
|
+
const out = cp.execFileSync(command, args, {
|
|
21
|
+
cwd: opts.cwd || process.cwd(),
|
|
22
|
+
encoding: 'utf8',
|
|
23
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
24
|
+
timeout: opts.timeout || 1500
|
|
25
|
+
}).trim();
|
|
26
|
+
return out.split(/\r?\n/)[0] || 'installed';
|
|
27
|
+
} catch (err) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function hostName(env) {
|
|
33
|
+
if (env.CODEX_HOME || env.CODEX_SANDBOX || env.CODEX_ENV_PWD) return 'codex';
|
|
34
|
+
if (env.CLAUDECODE || env.CLAUDE_CODE || env.CLAUDE_CONFIG_DIR) return 'claude';
|
|
35
|
+
if (env.CURSOR_TRACE_ID || env.CURSOR_AGENT) return 'cursor';
|
|
36
|
+
if (env.WINDSURF) return 'windsurf';
|
|
37
|
+
return 'unknown';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function installedAgentSurfaces(homeDir) {
|
|
41
|
+
const codexAgents = path.join(homeDir, '.codex', 'agents');
|
|
42
|
+
const claudeAgents = path.join(homeDir, '.claude', 'agents');
|
|
43
|
+
return {
|
|
44
|
+
codex: exists(path.join(codexAgents, 'god-orchestrator.toml'))
|
|
45
|
+
|| exists(path.join(codexAgents, 'god-orchestrator.md')),
|
|
46
|
+
claude: exists(path.join(claudeAgents, 'god-orchestrator.md'))
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function detect(projectRoot, opts = {}) {
|
|
51
|
+
const env = opts.env || process.env;
|
|
52
|
+
const homeDir = opts.homeDir || os.homedir();
|
|
53
|
+
const root = projectRoot || process.cwd();
|
|
54
|
+
const installedAgents = opts.installedAgents || installedAgentSurfaces(homeDir);
|
|
55
|
+
const git = commandVersion('git', ['--version'], { cwd: root });
|
|
56
|
+
const npm = commandVersion('npm', ['--version'], { cwd: root });
|
|
57
|
+
const gh = commandVersion('gh', ['--version'], { cwd: root });
|
|
58
|
+
const shell = Boolean(env.SHELL || env.ComSpec);
|
|
59
|
+
const agentSpawn = Boolean(installedAgents.codex || installedAgents.claude || opts.agentSpawn);
|
|
60
|
+
const extensionAuthoring = exists(path.join(root, 'lib', 'extension-authoring.js'))
|
|
61
|
+
&& exists(path.join(root, 'schema', 'extension-manifest.v1.json'));
|
|
62
|
+
const suiteReleaseDryRun = exists(path.join(root, 'lib', 'suite-state.js'));
|
|
63
|
+
|
|
64
|
+
const gaps = [];
|
|
65
|
+
if (!shell) gaps.push('shell unavailable');
|
|
66
|
+
if (!git) gaps.push('git unavailable');
|
|
67
|
+
if (!npm) gaps.push('npm unavailable');
|
|
68
|
+
if (!agentSpawn) gaps.push('fresh-context agent spawn not detected');
|
|
69
|
+
if (!extensionAuthoring) gaps.push('extension authoring scaffold unavailable');
|
|
70
|
+
if (!suiteReleaseDryRun) gaps.push('suite release dry-run unavailable');
|
|
71
|
+
|
|
72
|
+
let level = 'unknown';
|
|
73
|
+
if (shell && git && npm && agentSpawn) level = 'full';
|
|
74
|
+
else if (shell && git && npm) level = 'degraded';
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
host: opts.host || hostName(env),
|
|
78
|
+
level,
|
|
79
|
+
guarantees: {
|
|
80
|
+
shell,
|
|
81
|
+
fileEdit: true,
|
|
82
|
+
node: process.version,
|
|
83
|
+
git,
|
|
84
|
+
npm,
|
|
85
|
+
gh,
|
|
86
|
+
agentSpawn,
|
|
87
|
+
extensionAuthoring,
|
|
88
|
+
suiteReleaseDryRun
|
|
89
|
+
},
|
|
90
|
+
installedAgents,
|
|
91
|
+
gaps
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function summary(report) {
|
|
96
|
+
if (!report) return 'unknown';
|
|
97
|
+
if (report.level === 'full') return `full on ${report.host}`;
|
|
98
|
+
const gap = report.gaps && report.gaps.length > 0 ? `, ${report.gaps[0]}` : '';
|
|
99
|
+
return `${report.level} on ${report.host}${gap}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function render(report) {
|
|
103
|
+
const lines = [];
|
|
104
|
+
lines.push('Host capabilities:');
|
|
105
|
+
lines.push(` Host: ${report.host}`);
|
|
106
|
+
lines.push(` Guarantee level: ${report.level}`);
|
|
107
|
+
lines.push(` Agent spawn: ${report.guarantees.agentSpawn ? 'detected' : 'not detected'}`);
|
|
108
|
+
lines.push(` Shell: ${report.guarantees.shell ? 'detected' : 'not detected'}`);
|
|
109
|
+
lines.push(` Git: ${report.guarantees.git || 'not detected'}`);
|
|
110
|
+
lines.push(` npm: ${report.guarantees.npm || 'not detected'}`);
|
|
111
|
+
lines.push(` GitHub CLI: ${report.guarantees.gh || 'not detected'}`);
|
|
112
|
+
lines.push(` Gaps: ${report.gaps.length > 0 ? report.gaps.join('; ') : 'none'}`);
|
|
113
|
+
return lines.join('\n');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
detect,
|
|
118
|
+
summary,
|
|
119
|
+
render,
|
|
120
|
+
_private: {
|
|
121
|
+
commandVersion,
|
|
122
|
+
hostName,
|
|
123
|
+
installedAgentSurfaces
|
|
124
|
+
}
|
|
125
|
+
};
|