godpowers 0.15.0 → 0.15.2
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 +52 -0
- package/README.md +2 -1
- package/bin/install.js +36 -8
- package/lib/checkpoint.js +1 -0
- package/lib/code-scanner.js +3 -6
- package/lib/context-writer.js +2 -2
- package/lib/events.js +10 -10
- package/lib/extensions.js +1 -0
- package/lib/intent.js +36 -4
- package/lib/linkage.js +108 -4
- package/lib/router.js +6 -5
- package/package.json +19 -4
- package/skills/god-help.md +3 -1
- package/skills/god-next.md +21 -13
- package/skills/god-standards.md +3 -2
- package/skills/god-version.md +1 -1
- package/skills/god.md +17 -8
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,58 @@ All notable changes to Godpowers will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.15.2] - 2026-05-11
|
|
9
|
+
|
|
10
|
+
Runtime hardening release. Fixes packaging and workflow edge cases found by
|
|
11
|
+
a deep audit, then locks them behind regression tests and pack publish gates.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Installed `/god`, `/god-next`, `/god-help`, `/god-standards`, and
|
|
15
|
+
`/god-version` now point agents at the installed `godpowers-runtime`
|
|
16
|
+
bundle when the user is not inside the repository checkout.
|
|
17
|
+
- Installer now copies `package.json` into `godpowers-runtime`, so installed
|
|
18
|
+
OTel exports report the real Godpowers version instead of `0.0.0`.
|
|
19
|
+
- Linkage scans now replace stale scanner-owned links instead of only adding
|
|
20
|
+
new links. Manual links are preserved, and legacy maps without source
|
|
21
|
+
metadata are migrated safely.
|
|
22
|
+
- `checkpoint.recordFact()` now preserves existing actions instead of wiping
|
|
23
|
+
the checkpoint action history.
|
|
24
|
+
- Context writer now reads root-level `mode` and `scale`, matching the
|
|
25
|
+
canonical `state.json` shape produced by `lib/state.js`.
|
|
26
|
+
- Event hash chains now stay valid when an event line is larger than 4KB.
|
|
27
|
+
- Extension reinstall now clears the old installed pack directory first, so
|
|
28
|
+
deleted files do not remain active after reinstall.
|
|
29
|
+
- Installer uninstall now removes all installed data/runtime directories,
|
|
30
|
+
including workflows, schema, routing, and `godpowers-runtime`.
|
|
31
|
+
|
|
32
|
+
### Tests
|
|
33
|
+
- Added regression coverage for stale linkage cleanup, checkpoint action
|
|
34
|
+
preservation, root-level mode/scale context rendering, large event hash
|
|
35
|
+
chains, installed OTel version metadata, runtime bundle guidance in installed
|
|
36
|
+
skills, extension reinstall cleanup, and uninstall cleanup.
|
|
37
|
+
- Extension pack publish gate now verifies package peer dependency ranges
|
|
38
|
+
match the manifest and include the current Godpowers version.
|
|
39
|
+
|
|
40
|
+
## [0.15.1] - 2026-05-11
|
|
41
|
+
|
|
42
|
+
Metadata + documentation polish. No code changes.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- `package.json` description rewritten from tagline to searchable
|
|
46
|
+
one-liner: explains what godpowers is and lists the AI tools it
|
|
47
|
+
runs in. Improves npm registry discoverability.
|
|
48
|
+
- `package.json` keywords expanded 10 -> 25. Adds variants like
|
|
49
|
+
`ai-agent`, `ai-orchestration`, specific tool names
|
|
50
|
+
(`claude-code`, `windsurf`, `gemini`, `copilot`), and artifact
|
|
51
|
+
taxonomy (`prd`, `architecture`, `roadmap`, `specialist-agents`).
|
|
52
|
+
- `docs/reference.md`: catalogue gap closed. The 23 skills shipped
|
|
53
|
+
in v0.13 - v0.15 are now indexed (recovery, observability, the
|
|
54
|
+
OTel exporter, extension management). Index now matches the 104
|
|
55
|
+
skill files on disk.
|
|
56
|
+
- `README.md`: clean Install section without scaffold disclaimers.
|
|
57
|
+
- `docs/ROADMAP.md`: v0.16 and v1.0 sections rewritten in a tighter,
|
|
58
|
+
goal-focused tone.
|
|
59
|
+
|
|
8
60
|
## [0.15.0] - 2026-05-11
|
|
9
61
|
|
|
10
62
|
Distribution release. Godpowers and its three first-party packs are
|
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/aihxp/godpowers/actions/workflows/ci.yml)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-
[](CHANGELOG.md)
|
|
6
|
+
[](https://www.npmjs.com/package/godpowers)
|
|
6
7
|
|
|
7
8
|
**Ship fast. Ship right. Ship everything. Ship accountably.**
|
|
8
9
|
|
package/bin/install.js
CHANGED
|
@@ -157,6 +157,20 @@ function copyRecursive(src, dest) {
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
function copyRuntimeBundle(srcDir, destDir) {
|
|
161
|
+
ensureDir(destDir);
|
|
162
|
+
for (const dir of ['lib', 'routing', 'workflows', 'schema', 'templates', 'references']) {
|
|
163
|
+
const src = path.join(srcDir, dir);
|
|
164
|
+
if (fs.existsSync(src)) {
|
|
165
|
+
copyRecursive(src, path.join(destDir, dir));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const packageJson = path.join(srcDir, 'package.json');
|
|
169
|
+
if (fs.existsSync(packageJson)) {
|
|
170
|
+
fs.copyFileSync(packageJson, path.join(destDir, 'package.json'));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
160
174
|
// ---------------------------------------------------------------------------
|
|
161
175
|
// Parse args
|
|
162
176
|
// ---------------------------------------------------------------------------
|
|
@@ -299,6 +313,11 @@ function installForRuntime(runtimeKey, srcDir) {
|
|
|
299
313
|
success('Installed routing/');
|
|
300
314
|
}
|
|
301
315
|
|
|
316
|
+
// 4f. Install the executable runtime bundle with lib/ next to its data dirs.
|
|
317
|
+
const runtimeBundleDest = path.join(runtime.configDir, 'godpowers-runtime');
|
|
318
|
+
copyRuntimeBundle(srcDir, runtimeBundleDest);
|
|
319
|
+
success('Installed runtime bundle/');
|
|
320
|
+
|
|
302
321
|
// 5. Install hooks (Claude Code only for now)
|
|
303
322
|
if (runtimeKey === 'claude') {
|
|
304
323
|
const hooksSrc = path.join(srcDir, 'hooks');
|
|
@@ -339,6 +358,10 @@ function uninstallForRuntime(runtimeKey) {
|
|
|
339
358
|
const agentsDir = path.join(runtime.configDir, 'agents');
|
|
340
359
|
const templatesDir = path.join(runtime.configDir, 'godpowers-templates');
|
|
341
360
|
const referencesDir = path.join(runtime.configDir, 'godpowers-references');
|
|
361
|
+
const workflowsDir = path.join(runtime.configDir, 'godpowers-workflows');
|
|
362
|
+
const schemaDir = path.join(runtime.configDir, 'godpowers-schema');
|
|
363
|
+
const routingDir = path.join(runtime.configDir, 'godpowers-routing');
|
|
364
|
+
const runtimeBundleDir = path.join(runtime.configDir, 'godpowers-runtime');
|
|
342
365
|
const versionFile = path.join(runtime.configDir, 'GODPOWERS_VERSION');
|
|
343
366
|
|
|
344
367
|
let removed = 0;
|
|
@@ -366,14 +389,19 @@ function uninstallForRuntime(runtimeKey) {
|
|
|
366
389
|
success(`Removed ${agentsRemoved} god-* agent(s)`);
|
|
367
390
|
}
|
|
368
391
|
|
|
369
|
-
// Remove
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
392
|
+
// Remove installed data and runtime directories
|
|
393
|
+
for (const dir of [
|
|
394
|
+
templatesDir,
|
|
395
|
+
referencesDir,
|
|
396
|
+
workflowsDir,
|
|
397
|
+
schemaDir,
|
|
398
|
+
routingDir,
|
|
399
|
+
runtimeBundleDir
|
|
400
|
+
]) {
|
|
401
|
+
if (fs.existsSync(dir)) {
|
|
402
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
403
|
+
success(`Removed ${path.basename(dir)}/`);
|
|
404
|
+
}
|
|
377
405
|
}
|
|
378
406
|
|
|
379
407
|
// Remove hooks (Claude Code only)
|
package/lib/checkpoint.js
CHANGED
|
@@ -228,6 +228,7 @@ function recordFact(projectRoot, fact) {
|
|
|
228
228
|
facts.unshift(f);
|
|
229
229
|
|
|
230
230
|
const state = existing ? frontmatterToState(existing.frontmatter, facts) : { facts };
|
|
231
|
+
state.actions = existing ? existing.actions.slice() : [];
|
|
231
232
|
state.facts = facts;
|
|
232
233
|
return write(projectRoot, state);
|
|
233
234
|
}
|
package/lib/code-scanner.js
CHANGED
|
@@ -230,12 +230,9 @@ function scan(projectRoot, opts = {}) {
|
|
|
230
230
|
*/
|
|
231
231
|
function applyScan(projectRoot, scanResult, opts = {}) {
|
|
232
232
|
const { links } = scanResult;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (r.added) added++;
|
|
237
|
-
}
|
|
238
|
-
return { totalLinks: links.length, added };
|
|
233
|
+
const source = opts.source || 'code-scanner';
|
|
234
|
+
const result = linkage.bulkReplaceFromSource(projectRoot, source, links);
|
|
235
|
+
return { totalLinks: links.length, added: result.added, removed: result.removed };
|
|
239
236
|
}
|
|
240
237
|
|
|
241
238
|
// ============================================================================
|
package/lib/context-writer.js
CHANGED
|
@@ -79,8 +79,8 @@ function buildCanonicalContent(state, opts = {}) {
|
|
|
79
79
|
lines.push('');
|
|
80
80
|
|
|
81
81
|
const projectName = (state && state.project && state.project.name) || opts.projectName || '(unnamed)';
|
|
82
|
-
const mode = (state && state.project && state.project.mode) || opts.mode || 'unknown';
|
|
83
|
-
const scale = (state && state.project && state.project.scale) || opts.scale || 'unknown';
|
|
82
|
+
const mode = (state && (state.mode || (state.project && state.project.mode))) || opts.mode || 'unknown';
|
|
83
|
+
const scale = (state && (state.scale || (state.project && state.project.scale))) || opts.scale || 'unknown';
|
|
84
84
|
lines.push(`- Project: ${projectName}`);
|
|
85
85
|
lines.push(`- Mode: ${mode} Scale: ${scale}`);
|
|
86
86
|
lines.push('- State: `.godpowers/state.json` (machine) and `.godpowers/PROGRESS.md` (human)');
|
package/lib/events.js
CHANGED
|
@@ -38,6 +38,13 @@ function eventsPath(projectRoot, runId) {
|
|
|
38
38
|
return path.join(projectRoot, '.godpowers', 'runs', runId, 'events.jsonl');
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
function readLastNonEmptyLine(file) {
|
|
42
|
+
const raw = fs.readFileSync(file, 'utf8').trimEnd();
|
|
43
|
+
if (!raw) return null;
|
|
44
|
+
const i = raw.lastIndexOf('\n');
|
|
45
|
+
return i >= 0 ? raw.slice(i + 1) : raw;
|
|
46
|
+
}
|
|
47
|
+
|
|
41
48
|
/**
|
|
42
49
|
* Start a new run. Returns a run handle with trace_id and write functions.
|
|
43
50
|
*/
|
|
@@ -105,17 +112,10 @@ function emit(file, event) {
|
|
|
105
112
|
if (fs.existsSync(file)) {
|
|
106
113
|
const stat = fs.statSync(file);
|
|
107
114
|
if (stat.size > 0) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const chunkSize = Math.min(4096, stat.size);
|
|
111
|
-
const buf = Buffer.alloc(chunkSize);
|
|
112
|
-
fs.readSync(fd, buf, 0, chunkSize, stat.size - chunkSize);
|
|
113
|
-
fs.closeSync(fd);
|
|
114
|
-
const tail = buf.toString('utf8');
|
|
115
|
-
const lines = tail.split('\n').filter(l => l.trim());
|
|
116
|
-
if (lines.length > 0) {
|
|
115
|
+
const line = readLastNonEmptyLine(file);
|
|
116
|
+
if (line) {
|
|
117
117
|
prev = 'sha256:' + crypto.createHash('sha256')
|
|
118
|
-
.update(
|
|
118
|
+
.update(line)
|
|
119
119
|
.digest('hex');
|
|
120
120
|
}
|
|
121
121
|
}
|
package/lib/extensions.js
CHANGED
|
@@ -212,6 +212,7 @@ function install(runtimeConfigDir, sourceDir, godpowersVersion) {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
const destDir = path.join(extensionsDir(runtimeConfigDir), manifest.metadata.name);
|
|
215
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
215
216
|
fs.mkdirSync(destDir, { recursive: true });
|
|
216
217
|
|
|
217
218
|
// Copy manifest + standard pack subdirs if they exist
|
package/lib/intent.js
CHANGED
|
@@ -38,7 +38,8 @@ function parseSimpleYaml(content) {
|
|
|
38
38
|
const result = {};
|
|
39
39
|
const stack = [{ obj: result, indent: -1, key: null, isArray: false, parent: null }];
|
|
40
40
|
|
|
41
|
-
for (let
|
|
41
|
+
for (let i = 0; i < lines.length; i++) {
|
|
42
|
+
let line = lines[i];
|
|
42
43
|
if (!line.trim() || line.trim().startsWith('#')) continue;
|
|
43
44
|
// Strip inline comments (but not # inside quotes)
|
|
44
45
|
const hashIdx = line.indexOf(' #');
|
|
@@ -60,8 +61,6 @@ function parseSimpleYaml(content) {
|
|
|
60
61
|
stack.pop();
|
|
61
62
|
}
|
|
62
63
|
const parent = stack[stack.length - 1].obj;
|
|
63
|
-
const parentMeta = stack[stack.length - 1];
|
|
64
|
-
|
|
65
64
|
// List item: "- key: value" or "- value"
|
|
66
65
|
if (trimmed.startsWith('- ')) {
|
|
67
66
|
const rest = trimmed.slice(2);
|
|
@@ -108,7 +107,9 @@ function parseSimpleYaml(content) {
|
|
|
108
107
|
parent[key] = child;
|
|
109
108
|
stack.push({ obj: child, indent, key, parent });
|
|
110
109
|
} else if (valueStr === '|' || valueStr === '>') {
|
|
111
|
-
|
|
110
|
+
const block = readBlockScalar(lines, i + 1, indent, valueStr === '>');
|
|
111
|
+
parent[key] = block.value;
|
|
112
|
+
i = block.nextIndex - 1;
|
|
112
113
|
} else {
|
|
113
114
|
parent[key] = parseValue(valueStr);
|
|
114
115
|
}
|
|
@@ -117,6 +118,37 @@ function parseSimpleYaml(content) {
|
|
|
117
118
|
return cleanArrays(result);
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
function readBlockScalar(lines, startIndex, parentIndent, folded) {
|
|
122
|
+
const blockLines = [];
|
|
123
|
+
let i = startIndex;
|
|
124
|
+
|
|
125
|
+
for (; i < lines.length; i++) {
|
|
126
|
+
const raw = lines[i];
|
|
127
|
+
if (!raw.trim()) {
|
|
128
|
+
blockLines.push('');
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const indent = raw.length - raw.trimStart().length;
|
|
132
|
+
if (indent <= parentIndent) break;
|
|
133
|
+
blockLines.push(raw);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const nonBlankIndents = blockLines
|
|
137
|
+
.filter(line => line.trim())
|
|
138
|
+
.map(line => line.length - line.trimStart().length);
|
|
139
|
+
const trimIndent = nonBlankIndents.length
|
|
140
|
+
? Math.min(...nonBlankIndents)
|
|
141
|
+
: parentIndent + 2;
|
|
142
|
+
const normalized = blockLines.map(line => (
|
|
143
|
+
line.trim() ? line.slice(Math.min(trimIndent, line.length)) : ''
|
|
144
|
+
));
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
value: folded ? normalized.join(' ').replace(/\s+/g, ' ').trim() : normalized.join('\n').trimEnd(),
|
|
148
|
+
nextIndex: i
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
120
152
|
function parseValue(str) {
|
|
121
153
|
if (str === 'true') return true;
|
|
122
154
|
if (str === 'false') return false;
|
package/lib/linkage.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Storage:
|
|
9
9
|
* .godpowers/links/artifact-to-code.json - forward map
|
|
10
10
|
* .godpowers/links/code-to-artifact.json - reverse map
|
|
11
|
+
* .godpowers/links/link-sources.json - source ownership map
|
|
11
12
|
* .godpowers/links/LINKAGE-LOG.md - append-only history
|
|
12
13
|
*
|
|
13
14
|
* Stable ID format:
|
|
@@ -53,6 +54,10 @@ function reversePath(projectRoot) {
|
|
|
53
54
|
return path.join(linksDir(projectRoot), 'code-to-artifact.json');
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
function sourcePath(projectRoot) {
|
|
58
|
+
return path.join(linksDir(projectRoot), 'link-sources.json');
|
|
59
|
+
}
|
|
60
|
+
|
|
56
61
|
function logPath(projectRoot) {
|
|
57
62
|
return path.join(linksDir(projectRoot), 'LINKAGE-LOG.md');
|
|
58
63
|
}
|
|
@@ -75,6 +80,19 @@ function writeMap(filePath, data) {
|
|
|
75
80
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
|
|
76
81
|
}
|
|
77
82
|
|
|
83
|
+
function linkKey(artifactId, filePath) {
|
|
84
|
+
return `${artifactId}\u0000${filePath}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseLinkKey(key) {
|
|
88
|
+
const i = key.indexOf('\u0000');
|
|
89
|
+
return { artifactId: key.slice(0, i), file: key.slice(i + 1) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readSources(projectRoot) {
|
|
93
|
+
return readMap(sourcePath(projectRoot));
|
|
94
|
+
}
|
|
95
|
+
|
|
78
96
|
/**
|
|
79
97
|
* Read forward map: artifact ID -> array of file paths.
|
|
80
98
|
*/
|
|
@@ -111,8 +129,16 @@ function addLink(projectRoot, artifactId, filePath, opts = {}) {
|
|
|
111
129
|
fwd[artifactId].sort();
|
|
112
130
|
rev[normFile].sort();
|
|
113
131
|
|
|
132
|
+
const sources = readSources(projectRoot);
|
|
133
|
+
const source = opts.source || 'manual';
|
|
134
|
+
const key = linkKey(artifactId, normFile);
|
|
135
|
+
if (!sources[key]) sources[key] = [];
|
|
136
|
+
if (!sources[key].includes(source)) sources[key].push(source);
|
|
137
|
+
sources[key].sort();
|
|
138
|
+
|
|
114
139
|
writeMap(forwardPath(projectRoot), fwd);
|
|
115
140
|
writeMap(reversePath(projectRoot), rev);
|
|
141
|
+
writeMap(sourcePath(projectRoot), sources);
|
|
116
142
|
|
|
117
143
|
if (!fwdHad || !revHad) {
|
|
118
144
|
appendLog(projectRoot, `+ link: ${artifactId} <-> ${normFile}` + (opts.source ? ` (via ${opts.source})` : ''));
|
|
@@ -143,8 +169,12 @@ function removeLink(projectRoot, artifactId, filePath) {
|
|
|
143
169
|
if (before !== (rev[normFile] ? rev[normFile].length : 0)) removed = true;
|
|
144
170
|
}
|
|
145
171
|
|
|
172
|
+
const sources = readSources(projectRoot);
|
|
173
|
+
delete sources[linkKey(artifactId, normFile)];
|
|
174
|
+
|
|
146
175
|
writeMap(forwardPath(projectRoot), fwd);
|
|
147
176
|
writeMap(reversePath(projectRoot), rev);
|
|
177
|
+
writeMap(sourcePath(projectRoot), sources);
|
|
148
178
|
|
|
149
179
|
if (removed) {
|
|
150
180
|
appendLog(projectRoot, `- link: ${artifactId} <-> ${normFile}`);
|
|
@@ -204,12 +234,84 @@ function appendLog(projectRoot, message) {
|
|
|
204
234
|
*/
|
|
205
235
|
function bulkReplaceFromSource(projectRoot, source, links) {
|
|
206
236
|
ensureDir(projectRoot);
|
|
207
|
-
const
|
|
237
|
+
const fwd = readForward(projectRoot);
|
|
238
|
+
const rev = readReverse(projectRoot);
|
|
239
|
+
const sources = readSources(projectRoot);
|
|
240
|
+
const desired = new Set();
|
|
241
|
+
let removed = 0;
|
|
242
|
+
let added = 0;
|
|
243
|
+
|
|
208
244
|
for (const { artifactId, file } of links) {
|
|
209
|
-
const
|
|
210
|
-
|
|
245
|
+
const normFile = path.relative(projectRoot, path.resolve(projectRoot, file));
|
|
246
|
+
desired.add(linkKey(artifactId, normFile));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const knownSourceKeys = Object.keys(sources)
|
|
250
|
+
.filter(key => Array.isArray(sources[key]) && sources[key].includes(source));
|
|
251
|
+
const hasSourceMetadata = Object.keys(sources).length > 0;
|
|
252
|
+
const replaceKeys = knownSourceKeys.length > 0
|
|
253
|
+
? knownSourceKeys
|
|
254
|
+
: hasSourceMetadata
|
|
255
|
+
? []
|
|
256
|
+
: Object.entries(fwd).flatMap(([artifactId, files]) =>
|
|
257
|
+
files.map(file => linkKey(artifactId, file)));
|
|
258
|
+
|
|
259
|
+
for (const key of replaceKeys) {
|
|
260
|
+
if (desired.has(key)) continue;
|
|
261
|
+
const { artifactId, file } = parseLinkKey(key);
|
|
262
|
+
sources[key] = (sources[key] || []).filter(s => s !== source);
|
|
263
|
+
if (sources[key] && sources[key].length > 0) continue;
|
|
264
|
+
|
|
265
|
+
delete sources[key];
|
|
266
|
+
if (fwd[artifactId]) {
|
|
267
|
+
fwd[artifactId] = fwd[artifactId].filter(f => f !== file);
|
|
268
|
+
if (fwd[artifactId].length === 0) delete fwd[artifactId];
|
|
269
|
+
}
|
|
270
|
+
if (rev[file]) {
|
|
271
|
+
rev[file] = rev[file].filter(id => id !== artifactId);
|
|
272
|
+
if (rev[file].length === 0) delete rev[file];
|
|
273
|
+
}
|
|
274
|
+
appendLog(projectRoot, `- link: ${artifactId} <-> ${file} (via ${source})`);
|
|
275
|
+
removed++;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
for (const { artifactId, file } of links) {
|
|
279
|
+
const normFile = path.relative(projectRoot, path.resolve(projectRoot, file));
|
|
280
|
+
const key = linkKey(artifactId, normFile);
|
|
281
|
+
if (!fwd[artifactId]) fwd[artifactId] = [];
|
|
282
|
+
if (!rev[normFile]) rev[normFile] = [];
|
|
283
|
+
const hadLink = fwd[artifactId].includes(normFile) && rev[normFile].includes(artifactId);
|
|
284
|
+
if (!fwd[artifactId].includes(normFile)) fwd[artifactId].push(normFile);
|
|
285
|
+
if (!rev[normFile].includes(artifactId)) rev[normFile].push(artifactId);
|
|
286
|
+
if (!sources[key]) sources[key] = [];
|
|
287
|
+
if (!sources[key].includes(source)) sources[key].push(source);
|
|
288
|
+
sources[key].sort();
|
|
289
|
+
if (!hadLink) {
|
|
290
|
+
appendLog(projectRoot, `+ link: ${artifactId} <-> ${normFile} (via ${source})`);
|
|
291
|
+
added++;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
for (const files of Object.values(fwd)) files.sort();
|
|
296
|
+
for (const ids of Object.values(rev)) ids.sort();
|
|
297
|
+
|
|
298
|
+
writeMap(forwardPath(projectRoot), fwd);
|
|
299
|
+
writeMap(reversePath(projectRoot), rev);
|
|
300
|
+
writeMap(sourcePath(projectRoot), sources);
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
count: links.length,
|
|
304
|
+
added,
|
|
305
|
+
removed
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function clearSourceMetadata(projectRoot) {
|
|
310
|
+
ensureDir(projectRoot);
|
|
311
|
+
const file = sourcePath(projectRoot);
|
|
312
|
+
if (fs.existsSync(file)) {
|
|
313
|
+
fs.rmSync(file, { force: true });
|
|
211
314
|
}
|
|
212
|
-
return { count: links.length, added: log.length };
|
|
213
315
|
}
|
|
214
316
|
|
|
215
317
|
module.exports = {
|
|
@@ -225,8 +327,10 @@ module.exports = {
|
|
|
225
327
|
coverage,
|
|
226
328
|
appendLog,
|
|
227
329
|
bulkReplaceFromSource,
|
|
330
|
+
clearSourceMetadata,
|
|
228
331
|
forwardPath,
|
|
229
332
|
reversePath,
|
|
333
|
+
sourcePath,
|
|
230
334
|
logPath,
|
|
231
335
|
linksDir
|
|
232
336
|
};
|
package/lib/router.js
CHANGED
|
@@ -83,6 +83,12 @@ function checkPrerequisites(command, projectRoot) {
|
|
|
83
83
|
* Predicates: file:path, state:dotted.path == value, env:VAR
|
|
84
84
|
*/
|
|
85
85
|
function evaluateCheck(check, projectRoot) {
|
|
86
|
+
// OR conditions must be handled before prefix-specific checks so both
|
|
87
|
+
// branches can be evaluated as independent predicates.
|
|
88
|
+
if (check.includes(' OR ')) {
|
|
89
|
+
return check.split(' OR ').some(part => evaluateCheck(part.trim(), projectRoot));
|
|
90
|
+
}
|
|
91
|
+
|
|
86
92
|
// file:path
|
|
87
93
|
if (check.startsWith('file:')) {
|
|
88
94
|
const filePath = check.slice(5).trim();
|
|
@@ -101,11 +107,6 @@ function evaluateCheck(check, projectRoot) {
|
|
|
101
107
|
return actual === expected || actual === parseValue(expected);
|
|
102
108
|
}
|
|
103
109
|
|
|
104
|
-
// OR conditions
|
|
105
|
-
if (check.includes(' OR ')) {
|
|
106
|
-
return check.split(' OR ').some(part => evaluateCheck(part.trim(), projectRoot));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
110
|
// mode-A-greenfield: pass-through hint, treat as satisfiable
|
|
110
111
|
if (check.includes('greenfield') || check.includes('mode-A')) {
|
|
111
112
|
return !fs.existsSync(path.join(projectRoot, '.godpowers'));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "godpowers",
|
|
3
|
-
"version": "0.15.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.15.2",
|
|
4
|
+
"description": "AI-powered development system: 104 slash commands and 38 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
|
},
|
|
@@ -19,15 +19,30 @@
|
|
|
19
19
|
},
|
|
20
20
|
"keywords": [
|
|
21
21
|
"ai",
|
|
22
|
-
"
|
|
22
|
+
"ai-agent",
|
|
23
|
+
"ai-agents",
|
|
24
|
+
"ai-development",
|
|
25
|
+
"ai-coding",
|
|
26
|
+
"ai-orchestration",
|
|
23
27
|
"agent",
|
|
28
|
+
"agents",
|
|
24
29
|
"claude",
|
|
30
|
+
"claude-code",
|
|
25
31
|
"codex",
|
|
26
32
|
"cursor",
|
|
33
|
+
"windsurf",
|
|
34
|
+
"gemini",
|
|
35
|
+
"copilot",
|
|
27
36
|
"tdd",
|
|
28
37
|
"orchestration",
|
|
29
38
|
"godmode",
|
|
30
|
-
"slash-commands"
|
|
39
|
+
"slash-commands",
|
|
40
|
+
"skills",
|
|
41
|
+
"prd",
|
|
42
|
+
"architecture",
|
|
43
|
+
"roadmap",
|
|
44
|
+
"specialist-agents",
|
|
45
|
+
"developer-tools"
|
|
31
46
|
],
|
|
32
47
|
"author": "Godpowers",
|
|
33
48
|
"license": "MIT",
|
package/skills/god-help.md
CHANGED
|
@@ -51,7 +51,9 @@ Show only one category (lifecycle, planning, building, shipping, etc.).
|
|
|
51
51
|
Built-in, no spawned agent. Reads:
|
|
52
52
|
- `<runtime>/skills/*.md` frontmatter
|
|
53
53
|
- `.godpowers/state.json` (for current state line)
|
|
54
|
-
-
|
|
54
|
+
- `<runtimeRoot>/lib/recipes.js` (for suggested next)
|
|
55
|
+
|
|
56
|
+
Resolve `<runtimeRoot>` as `<projectRoot>` when `<projectRoot>/lib/recipes.js` exists. Otherwise use the installed bundle at `<tool-config-dir>/godpowers-runtime`, where `<tool-config-dir>` is the directory that contains this installed skill.
|
|
55
57
|
|
|
56
58
|
## When to use
|
|
57
59
|
|
package/skills/god-next.md
CHANGED
|
@@ -3,7 +3,7 @@ name: god-next
|
|
|
3
3
|
description: |
|
|
4
4
|
Decision engine. For any command intent, checks prerequisites, proposes
|
|
5
5
|
auto-completion of missing prerequisites, runs standards checks at gates,
|
|
6
|
-
and suggests next commands after success. Backed by routing
|
|
6
|
+
and suggests next commands after success. Backed by runtime routing YAML
|
|
7
7
|
configurations.
|
|
8
8
|
|
|
9
9
|
Triggers on: "god next", "/god-next", "what's next", "what should I do next",
|
|
@@ -15,16 +15,24 @@ description: |
|
|
|
15
15
|
The unified decision engine. Routes between commands based on disk state,
|
|
16
16
|
routing definitions, and user intent.
|
|
17
17
|
|
|
18
|
+
## Runtime module resolution
|
|
19
|
+
|
|
20
|
+
Before reading routing data or calling runtime modules, resolve the Godpowers runtime root:
|
|
21
|
+
|
|
22
|
+
1. If `<projectRoot>/lib/router.js` exists, use the repository checkout runtime at `<projectRoot>`.
|
|
23
|
+
2. Otherwise use the installed bundle at `<tool-config-dir>/godpowers-runtime`, where `<tool-config-dir>` is the directory that contains this installed skill, such as `~/.claude`, `~/.codex`, `~/.cursor`, `~/.windsurf`, or `~/.gemini`.
|
|
24
|
+
3. Read routing definitions from `<runtimeRoot>/routing/*.yaml` and recipes from `<runtimeRoot>/routing/recipes/*.yaml`.
|
|
25
|
+
|
|
18
26
|
## Three modes of invocation
|
|
19
27
|
|
|
20
28
|
### Mode 1: After a command (post-completion routing)
|
|
21
29
|
The completing skill calls /god-next to determine what's next.
|
|
22
|
-
Reads
|
|
30
|
+
Reads `<runtimeRoot>/routing/<just-completed>.yaml`, gets `success-path.next-recommended`,
|
|
23
31
|
suggests it.
|
|
24
32
|
|
|
25
33
|
### Mode 2: Before a command (pre-flight)
|
|
26
34
|
User wants to run /god-X. /god-next checks prerequisites first.
|
|
27
|
-
Reads
|
|
35
|
+
Reads `<runtimeRoot>/routing/<X>.yaml`, evaluates prerequisites, proposes auto-completion
|
|
28
36
|
if any are missing.
|
|
29
37
|
|
|
30
38
|
### Mode 3: Standalone (state-driven)
|
|
@@ -40,7 +48,7 @@ A skill completes (e.g., /god-prd just finished)
|
|
|
40
48
|
Skill calls: /god-next --after=/god-prd
|
|
41
49
|
|
|
|
42
50
|
v
|
|
43
|
-
Read routing/god-prd.yaml
|
|
51
|
+
Read <runtimeRoot>/routing/god-prd.yaml
|
|
44
52
|
|
|
|
45
53
|
v
|
|
46
54
|
Get success-path.next-recommended (e.g., "/god-arch")
|
|
@@ -76,7 +84,7 @@ User types: /god-arch
|
|
|
76
84
|
The /god-arch skill calls: /god-next --before=/god-arch
|
|
77
85
|
|
|
|
78
86
|
v
|
|
79
|
-
Read routing/god-arch.yaml
|
|
87
|
+
Read <runtimeRoot>/routing/god-arch.yaml
|
|
80
88
|
|
|
|
81
89
|
v
|
|
82
90
|
For each prerequisite in prerequisites.required:
|
|
@@ -109,7 +117,7 @@ User types: /god-next
|
|
|
109
117
|
Read .godpowers/state.json (or PROGRESS.md as fallback)
|
|
110
118
|
|
|
|
111
119
|
v
|
|
112
|
-
Use lib/router.suggestNext(projectRoot) <- structural next
|
|
120
|
+
Use <runtimeRoot>/lib/router.js suggestNext(projectRoot) <- structural next
|
|
113
121
|
|
|
|
114
122
|
v
|
|
115
123
|
For each tier in order:
|
|
@@ -118,7 +126,7 @@ For each tier in order:
|
|
|
118
126
|
|
|
|
119
127
|
v
|
|
120
128
|
If all tiers done:
|
|
121
|
-
- Use lib/recipes.suggestForState(projectRoot) <- scenario-aware
|
|
129
|
+
- Use <runtimeRoot>/lib/recipes.js suggestForState(projectRoot) <- scenario-aware
|
|
122
130
|
- Returns recipes matching current lifecycle phase
|
|
123
131
|
|
|
|
124
132
|
v
|
|
@@ -132,7 +140,7 @@ Display: "Suggested next: /god-arch
|
|
|
132
140
|
User says: "I need to add a new feature mid-development"
|
|
133
141
|
|
|
|
134
142
|
v
|
|
135
|
-
/god-next consults lib/recipes.matchIntent(text, projectRoot)
|
|
143
|
+
/god-next consults <runtimeRoot>/lib/recipes.js matchIntent(text, projectRoot)
|
|
136
144
|
|
|
|
137
145
|
v
|
|
138
146
|
Returns ranked recipes matching:
|
|
@@ -150,20 +158,20 @@ Display top match with the recipe's sequence:
|
|
|
150
158
|
Run this sequence? Or see other matches?"
|
|
151
159
|
```
|
|
152
160
|
|
|
153
|
-
This is the recipe-driven decision support: agents consult
|
|
161
|
+
This is the recipe-driven decision support: agents consult `<runtimeRoot>/routing/recipes/`
|
|
154
162
|
when user intent is fuzzy or doesn't map to a single command.
|
|
155
163
|
|
|
156
164
|
## Routing data
|
|
157
165
|
|
|
158
|
-
Routing definitions live in
|
|
159
|
-
-
|
|
160
|
-
-
|
|
166
|
+
Routing definitions live in `<runtimeRoot>/routing/*.yaml`. Each command has a file:
|
|
167
|
+
- `<runtimeRoot>/routing/god-prd.yaml`
|
|
168
|
+
- `<runtimeRoot>/routing/god-arch.yaml`
|
|
161
169
|
- ...
|
|
162
170
|
|
|
163
171
|
These define prerequisites, execution, success-path, failure-path, and
|
|
164
172
|
endoff for each command.
|
|
165
173
|
|
|
166
|
-
The
|
|
174
|
+
The `<runtimeRoot>/lib/router.js` JS module provides programmatic queries:
|
|
167
175
|
- `getRouting(command)` - load a command's routing
|
|
168
176
|
- `checkPrerequisites(command, projectRoot)` - prereq check
|
|
169
177
|
- `getNextCommand(command)` - get success-path next
|
package/skills/god-standards.md
CHANGED
|
@@ -23,8 +23,9 @@ Run quality gate check on an artifact.
|
|
|
23
23
|
## Process
|
|
24
24
|
|
|
25
25
|
1. Identify the artifact to check (user provides, or auto-detect from PROGRESS.md)
|
|
26
|
-
2.
|
|
27
|
-
3.
|
|
26
|
+
2. Resolve the Godpowers runtime root: use `<projectRoot>` when `<projectRoot>/lib/router.js` exists, otherwise use the installed bundle at `<tool-config-dir>/godpowers-runtime`
|
|
27
|
+
3. Look up the routing for the relevant tier (`<runtimeRoot>/lib/router.js` getStandards)
|
|
28
|
+
4. Spawn god-standards-check in fresh context with:
|
|
28
29
|
- artifact-path
|
|
29
30
|
- tier
|
|
30
31
|
- have-nots-list (from routing)
|
package/skills/god-version.md
CHANGED
|
@@ -34,4 +34,4 @@ soft if offline).
|
|
|
34
34
|
|
|
35
35
|
Built-in, no spawned agent. Reads:
|
|
36
36
|
- `<runtime>/GODPOWERS_VERSION`
|
|
37
|
-
- File counts in `<runtime>/skills/`, `<runtime>/agents/`,
|
|
37
|
+
- File counts in `<runtime>/skills/`, `<runtime>/agents/`, `<runtime>/godpowers-runtime/workflows/`, and `<runtime>/godpowers-runtime/routing/recipes/`
|
package/skills/god.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: god
|
|
3
3
|
description: |
|
|
4
4
|
Front door. Take free-text intent from the user, match it to a recipe via
|
|
5
|
-
|
|
5
|
+
the Godpowers runtime recipes module, and propose the matching command sequence. If no
|
|
6
6
|
text given, fall back to state-driven suggestion (same as /god-next Mode 3).
|
|
7
7
|
|
|
8
8
|
Triggers on: "/god", "god", "/god help", "I want to ...", "how do I ..."
|
|
@@ -13,7 +13,16 @@ description: |
|
|
|
13
13
|
|
|
14
14
|
The natural-language entry point. Users describe what they want; this skill
|
|
15
15
|
matches the intent to a recipe and suggests the right command sequence. No
|
|
16
|
-
agent is spawned here. This is a thin router on top of
|
|
16
|
+
agent is spawned here. This is a thin router on top of the Godpowers runtime
|
|
17
|
+
recipes module.
|
|
18
|
+
|
|
19
|
+
## Runtime module resolution
|
|
20
|
+
|
|
21
|
+
Before calling runtime modules, resolve the Godpowers runtime root:
|
|
22
|
+
|
|
23
|
+
1. If `<projectRoot>/lib/recipes.js` exists, use the repository checkout runtime at `<projectRoot>`.
|
|
24
|
+
2. Otherwise use the installed bundle at `<tool-config-dir>/godpowers-runtime`, where `<tool-config-dir>` is the directory that contains this installed skill, such as `~/.claude`, `~/.codex`, `~/.cursor`, `~/.windsurf`, or `~/.gemini`.
|
|
25
|
+
3. Load recipes from `<runtimeRoot>/lib/recipes.js`, routing from `<runtimeRoot>/lib/router.js`, and recipe YAML from `<runtimeRoot>/routing/recipes/`.
|
|
17
26
|
|
|
18
27
|
## Why this exists
|
|
19
28
|
|
|
@@ -50,11 +59,11 @@ Treat everything after `/god` as free text. If empty, treat as state-driven.
|
|
|
50
59
|
|
|
51
60
|
```
|
|
52
61
|
text empty?
|
|
53
|
-
yes -> state-driven: call lib/recipes.suggestForState(projectRoot)
|
|
62
|
+
yes -> state-driven: call <runtimeRoot>/lib/recipes.js suggestForState(projectRoot)
|
|
54
63
|
display top 3 recipes ranked by current lifecycle phase
|
|
55
|
-
also call lib/router.suggestNext(projectRoot) for structural next
|
|
64
|
+
also call <runtimeRoot>/lib/router.js suggestNext(projectRoot) for structural next
|
|
56
65
|
|
|
57
|
-
no -> intent-driven: call lib/recipes.matchIntent(text, projectRoot)
|
|
66
|
+
no -> intent-driven: call <runtimeRoot>/lib/recipes.js matchIntent(text, projectRoot)
|
|
58
67
|
take top 1-3 matches by score
|
|
59
68
|
if highest score >= 10 (exact phrase match): propose directly
|
|
60
69
|
if highest score 5-9 (all-words match): propose with confirmation
|
|
@@ -116,8 +125,8 @@ If user picks the structural next:
|
|
|
116
125
|
## Interaction model
|
|
117
126
|
|
|
118
127
|
This skill is a router, not an orchestrator. It:
|
|
119
|
-
- Reads recipes (via
|
|
120
|
-
- Reads state (via
|
|
128
|
+
- Reads recipes (via `<runtimeRoot>/lib/recipes.js`)
|
|
129
|
+
- Reads state (via `<runtimeRoot>/lib/state.js`)
|
|
121
130
|
- Proposes commands
|
|
122
131
|
|
|
123
132
|
It does NOT:
|
|
@@ -194,7 +203,7 @@ Suggested: /god-next show all valid next-step options
|
|
|
194
203
|
## Why a skill, not an agent
|
|
195
204
|
|
|
196
205
|
The matching and dispatch logic is mechanical (lookups against
|
|
197
|
-
|
|
206
|
+
`<runtimeRoot>/routing/recipes/*.yaml`) and has no need for fresh-context isolation. Running
|
|
198
207
|
it as a skill keeps it fast, lets the user see the proposed commands, and
|
|
199
208
|
avoids stacking another orchestrator layer above `god-orchestrator`. See
|
|
200
209
|
`docs/concepts.md` (the Quarterback section) for why we don't add a second
|