godpowers 1.6.21 → 1.6.23
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 +6 -0
- package/CHANGELOG.md +75 -0
- package/INSPIRATION.md +6 -0
- package/README.md +25 -9
- package/RELEASE.md +50 -58
- package/SKILL.md +24 -4
- package/agents/god-orchestrator.md +18 -3
- package/agents/god-reconciler.md +52 -5
- package/agents/god-updater.md +84 -2
- 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/hooks/pre-tool-use.sh +13 -1
- package/hooks/session-start.sh +12 -0
- package/lib/README.md +3 -0
- package/lib/dashboard.js +30 -1
- package/lib/dogfood-runner.js +193 -0
- package/lib/events.js +6 -0
- package/lib/extension-authoring.js +154 -0
- package/lib/feature-awareness.js +30 -0
- package/lib/have-nots-validator.js +2 -2
- package/lib/host-capabilities.js +125 -0
- package/lib/release-surface-sync.js +6 -0
- package/lib/repo-surface-sync.js +58 -0
- package/lib/suite-state.js +90 -1
- package/lib/workflow-runner.js +4 -0
- package/package.json +5 -4
- package/references/HAVE-NOTS.md +16 -0
- package/references/orchestration/MODE-DETECTION.md +36 -3
- package/references/orchestration/README.md +5 -2
- package/references/planning/ROADMAP-ANTIPATTERNS.md +1 -1
- package/references/shared/ORCHESTRATORS.md +42 -11
- package/references/shared/README.md +4 -4
- package/routing/god-dogfood.yaml +35 -0
- package/schema/events.v1.json +9 -0
- package/schema/intent.v1.yaml.json +5 -1
- package/schema/recipe.v1.json +2 -1
- package/schema/routing.v1.json +20 -0
- package/schema/state.v1.json +51 -0
- package/schema/workflow.v1.json +31 -2
- package/skills/god-doctor.md +1 -1
- package/skills/god-dogfood.md +63 -0
- package/skills/god-mode.md +4 -1
- package/skills/god-reconcile.md +13 -4
- package/skills/god-version.md +2 -2
- package/templates/DOCS-UPDATE-LOG.md +14 -0
- package/templates/IMPORTED-CONTEXT.md +2 -0
- package/templates/INITIAL-FINDINGS.md +5 -0
- package/templates/PROGRESS.md +8 -0
- package/workflows/audit-only.yaml +12 -0
- package/workflows/bluefield-arc.yaml +16 -1
- package/workflows/brownfield-arc.yaml +17 -1
- package/workflows/deps-audit.yaml +13 -0
- package/workflows/docs-arc.yaml +23 -0
- package/workflows/feature-arc.yaml +14 -0
- package/workflows/full-arc.yaml +19 -0
- package/workflows/hotfix-arc.yaml +14 -0
- package/workflows/hygiene.yaml +6 -0
- package/workflows/migration-arc.yaml +15 -0
- package/workflows/postmortem.yaml +13 -0
- package/workflows/refactor-arc.yaml +14 -0
- package/workflows/spike.yaml +11 -0
|
@@ -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
|
+
};
|
package/lib/events.js
CHANGED
|
@@ -20,6 +20,12 @@ const VALID_EVENT_NAMES = new Set([
|
|
|
20
20
|
'gate.fail', 'gate.pass',
|
|
21
21
|
'tier.skip',
|
|
22
22
|
'state.repair', 'state.rollback',
|
|
23
|
+
'local-helper.run', 'local-helper.complete',
|
|
24
|
+
'dashboard.render',
|
|
25
|
+
'host-capabilities.detect',
|
|
26
|
+
'dogfood.run',
|
|
27
|
+
'source-system.import', 'source-system.sync-back',
|
|
28
|
+
'repo-doc-sync.detect', 'repo-surface-sync.detect',
|
|
23
29
|
'extension.install', 'extension.activate',
|
|
24
30
|
// Cost / cache / budget (v0.14 token cost saver)
|
|
25
31
|
'cost.recorded', 'cache.hit', 'cache.miss', 'budget.exceeded',
|
|
@@ -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,36 @@ 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: 'dashboard-action-brief',
|
|
76
|
+
since: '1.6.22',
|
|
77
|
+
commands: ['/god-status', '/god-next', '/god-mode'],
|
|
78
|
+
description: 'Render compressed action brief, readiness, attention, and host guarantee lines from disk state.'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'host-capabilities',
|
|
82
|
+
since: '1.6.22',
|
|
83
|
+
commands: ['/god-status', '/god-next'],
|
|
84
|
+
description: 'Report full, degraded, or unknown host guarantees in dashboard output.'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'extension-authoring',
|
|
88
|
+
since: '1.6.22',
|
|
89
|
+
commands: ['/god-extension-add', '/god-test-extension'],
|
|
90
|
+
description: 'Scaffold and validate publishable Godpowers extension packs.'
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'suite-release-dry-run',
|
|
94
|
+
since: '1.6.22',
|
|
95
|
+
commands: ['/god-suite-release'],
|
|
96
|
+
description: 'Plan Mode D suite releases with impacted dependents and planned writes before mutation.'
|
|
67
97
|
}
|
|
68
98
|
];
|
|
69
99
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Have-Nots Validator
|
|
3
3
|
*
|
|
4
|
-
* Registry of mechanical checks against the
|
|
4
|
+
* Registry of mechanical checks against the 156 named have-nots from
|
|
5
5
|
* references/HAVE-NOTS.md. Each check returns structured findings:
|
|
6
6
|
*
|
|
7
7
|
* { code, severity: 'error'|'warning'|'info', line, column, message, suggestion }
|
|
@@ -70,7 +70,7 @@ function findPositions(content, regex) {
|
|
|
70
70
|
/** U-08: em or en dash present */
|
|
71
71
|
function checkEmEnDash(content) {
|
|
72
72
|
const findings = [];
|
|
73
|
-
const positions = findPositions(content, /[
|
|
73
|
+
const positions = findPositions(content, /[\u2013\u2014]/g);
|
|
74
74
|
for (const p of positions) {
|
|
75
75
|
findings.push({
|
|
76
76
|
code: 'U-08',
|
|
@@ -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
|
+
};
|
|
@@ -12,12 +12,18 @@ const path = require('path');
|
|
|
12
12
|
const LOG_PATH = '.godpowers/surface/RELEASE-SURFACE-SYNC.md';
|
|
13
13
|
|
|
14
14
|
const REQUIRED_PACKAGE_GUARDS = [
|
|
15
|
+
'lib/dogfood-runner.js',
|
|
16
|
+
'lib/extension-authoring.js',
|
|
17
|
+
'lib/host-capabilities.js',
|
|
15
18
|
'lib/route-quality-sync.js',
|
|
16
19
|
'lib/recipe-coverage-sync.js',
|
|
17
20
|
'lib/release-surface-sync.js'
|
|
18
21
|
];
|
|
19
22
|
|
|
20
23
|
const REQUIRED_RELEASE_TESTS = [
|
|
24
|
+
'scripts/test-dogfood-runner.js',
|
|
25
|
+
'scripts/test-extension-authoring.js',
|
|
26
|
+
'scripts/test-host-capabilities.js',
|
|
21
27
|
'scripts/test-automation-surface-sync.js',
|
|
22
28
|
'scripts/test-repo-surface-sync.js',
|
|
23
29
|
'scripts/test-extensions-publish.js',
|
package/lib/repo-surface-sync.js
CHANGED
|
@@ -29,6 +29,7 @@ const REQUIRED_PACKAGE_FILE_ENTRIES = [
|
|
|
29
29
|
'workflows/',
|
|
30
30
|
'schema/',
|
|
31
31
|
'lib/',
|
|
32
|
+
'fixtures/',
|
|
32
33
|
'extensions/',
|
|
33
34
|
'RELEASE.md',
|
|
34
35
|
'SKILL.md',
|
|
@@ -41,6 +42,9 @@ const REQUIRED_PACKAGE_CHECKS = [
|
|
|
41
42
|
'lib/feature-awareness.js',
|
|
42
43
|
'lib/repo-doc-sync.js',
|
|
43
44
|
'lib/repo-surface-sync.js',
|
|
45
|
+
'lib/dogfood-runner.js',
|
|
46
|
+
'lib/extension-authoring.js',
|
|
47
|
+
'lib/host-capabilities.js',
|
|
44
48
|
'lib/route-quality-sync.js',
|
|
45
49
|
'lib/recipe-coverage-sync.js',
|
|
46
50
|
'lib/release-surface-sync.js',
|
|
@@ -426,6 +430,59 @@ function suiteChecks(projectRoot) {
|
|
|
426
430
|
return checks;
|
|
427
431
|
}
|
|
428
432
|
|
|
433
|
+
function dogfoodChecks(projectRoot) {
|
|
434
|
+
const checks = [];
|
|
435
|
+
const pkg = readJson(projectRoot, 'package.json') || {};
|
|
436
|
+
const scriptsText = JSON.stringify(pkg.scripts || {});
|
|
437
|
+
const scenarios = [
|
|
438
|
+
'fixtures/dogfood/half-migrated-gsd/manifest.json',
|
|
439
|
+
'fixtures/dogfood/host-degraded/manifest.json',
|
|
440
|
+
'fixtures/dogfood/host-full/manifest.json',
|
|
441
|
+
'fixtures/dogfood/extension-authoring/manifest.json',
|
|
442
|
+
'fixtures/dogfood/suite-release-dry-run/manifest.json'
|
|
443
|
+
];
|
|
444
|
+
|
|
445
|
+
addCheck(
|
|
446
|
+
checks,
|
|
447
|
+
'dogfood',
|
|
448
|
+
'dogfood-runtime-helper',
|
|
449
|
+
exists(projectRoot, 'lib/dogfood-runner.js') ? 'fresh' : 'stale',
|
|
450
|
+
'lib/dogfood-runner.js',
|
|
451
|
+
exists(projectRoot, 'lib/dogfood-runner.js')
|
|
452
|
+
? 'Dogfood runner helper exists.'
|
|
453
|
+
: 'Dogfood runner helper is missing.',
|
|
454
|
+
{ spawn: exists(projectRoot, 'lib/dogfood-runner.js') ? null : 'god-auditor' }
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
addCheck(
|
|
458
|
+
checks,
|
|
459
|
+
'dogfood',
|
|
460
|
+
'dogfood-test-gate',
|
|
461
|
+
scriptsText.includes('scripts/test-dogfood-runner.js') ? 'fresh' : 'stale',
|
|
462
|
+
'package.json',
|
|
463
|
+
scriptsText.includes('scripts/test-dogfood-runner.js')
|
|
464
|
+
? 'Release gate includes dogfood runner tests.'
|
|
465
|
+
: 'Release gate does not include dogfood runner tests.',
|
|
466
|
+
{ spawn: scriptsText.includes('scripts/test-dogfood-runner.js') ? null : 'god-auditor' }
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
for (const scenario of scenarios) {
|
|
470
|
+
addCheck(
|
|
471
|
+
checks,
|
|
472
|
+
'dogfood',
|
|
473
|
+
`dogfood-scenario-${path.basename(path.dirname(scenario))}`,
|
|
474
|
+
exists(projectRoot, scenario) ? 'fresh' : 'stale',
|
|
475
|
+
scenario,
|
|
476
|
+
exists(projectRoot, scenario)
|
|
477
|
+
? `${scenario} exists.`
|
|
478
|
+
: `${scenario} is missing.`,
|
|
479
|
+
{ spawn: exists(projectRoot, scenario) ? null : 'god-auditor' }
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return checks;
|
|
484
|
+
}
|
|
485
|
+
|
|
429
486
|
function releasePolicyChecks(projectRoot) {
|
|
430
487
|
const checks = [];
|
|
431
488
|
const docs = repoDocSync.detect(projectRoot);
|
|
@@ -463,6 +520,7 @@ function detect(projectRoot) {
|
|
|
463
520
|
...workflowRecipeChecks(projectRoot),
|
|
464
521
|
...extensionChecks(projectRoot),
|
|
465
522
|
...suiteChecks(projectRoot),
|
|
523
|
+
...dogfoodChecks(projectRoot),
|
|
466
524
|
...releasePolicyChecks(projectRoot),
|
|
467
525
|
...routeQualitySync.detect(projectRoot).checks,
|
|
468
526
|
...recipeCoverageSync.detect(projectRoot).checks,
|