godpowers 2.2.1 → 2.3.0
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 +33 -7
- package/INSPIRATION.md +4 -2
- package/README.md +25 -12
- package/RELEASE.md +23 -19
- package/SKILL.md +2 -2
- package/agents/god-auditor.md +1 -1
- package/agents/god-deps-auditor.md +11 -0
- package/agents/god-executor.md +22 -5
- package/agents/god-greenfieldifier.md +1 -1
- package/agents/god-orchestrator.md +2 -2
- package/agents/god-planner.md +14 -0
- package/agents/god-pm.md +1 -1
- package/agents/god-reconciler.md +1 -1
- package/agents/god-roadmapper.md +1 -1
- package/agents/god-spec-reviewer.md +10 -0
- package/agents/god-stack-selector.md +4 -0
- package/agents/god-updater.md +1 -1
- package/bin/install.js +9 -3
- package/fixtures/dogfood/{half-migrated-gsd → half-migrated-planning}/manifest.json +2 -2
- package/lib/README.md +7 -1
- package/lib/atomic-write.js +85 -0
- package/lib/checkpoint.js +2 -1
- package/lib/context-writer.js +1 -1
- package/lib/executor-repair.js +65 -0
- package/lib/feature-awareness.js +1 -1
- package/lib/fs-async.js +2 -1
- package/lib/install-profiles.js +154 -0
- package/lib/installer-args.js +12 -0
- package/lib/installer-core.js +43 -8
- package/lib/linkage.js +2 -1
- package/lib/package-identity.js +38 -0
- package/lib/package-legitimacy.js +158 -0
- package/lib/planning-systems.js +9 -9
- package/lib/repo-surface-sync.js +1 -1
- package/lib/requirements.js +2 -1
- package/lib/reverse-sync.js +2 -1
- package/lib/source-grounding.js +177 -0
- package/lib/source-sync.js +6 -5
- package/lib/state.js +2 -1
- package/package.json +1 -1
- package/references/HAVE-NOTS.md +1 -1
- package/references/orchestration/MODE-DETECTION.md +2 -2
- package/references/shared/ORCHESTRATORS.md +3 -3
- package/routing/god-design.yaml +1 -1
- package/routing/god-migrate.yaml +3 -3
- package/schema/state.v1.json +1 -1
- package/skills/god-build.md +17 -3
- package/skills/god-doctor.md +2 -2
- package/skills/god-dogfood.md +2 -2
- package/skills/god-init.md +6 -6
- package/skills/god-migrate.md +10 -10
- package/skills/god-sync.md +2 -2
- package/skills/god-update-deps.md +4 -2
- package/skills/god-version.md +1 -1
- package/templates/IMPORTED-CONTEXT.md +1 -1
- package/templates/INITIAL-FINDINGS.md +2 -2
- /package/fixtures/dogfood/{half-migrated-gsd → half-migrated-planning}/.planning/PROJECT.md +0 -0
- /package/fixtures/dogfood/{half-migrated-gsd → half-migrated-planning}/.planning/REQUIREMENTS.md +0 -0
- /package/fixtures/dogfood/{half-migrated-gsd → half-migrated-planning}/.planning/ROADMAP.md +0 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const { execFileSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
const STALE_MONTHS = 18;
|
|
4
|
+
|
|
5
|
+
function normalizeName(name) {
|
|
6
|
+
return String(name || '')
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/^@/, '')
|
|
9
|
+
.replace(/[^a-z0-9]+/g, '');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function levenshtein(a, b) {
|
|
13
|
+
a = normalizeName(a);
|
|
14
|
+
b = normalizeName(b);
|
|
15
|
+
const dp = Array.from({ length: a.length + 1 }, () => Array(b.length + 1).fill(0));
|
|
16
|
+
for (let i = 0; i <= a.length; i++) dp[i][0] = i;
|
|
17
|
+
for (let j = 0; j <= b.length; j++) dp[0][j] = j;
|
|
18
|
+
for (let i = 1; i <= a.length; i++) {
|
|
19
|
+
for (let j = 1; j <= b.length; j++) {
|
|
20
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
21
|
+
dp[i][j] = Math.min(
|
|
22
|
+
dp[i - 1][j] + 1,
|
|
23
|
+
dp[i][j - 1] + 1,
|
|
24
|
+
dp[i - 1][j - 1] + cost
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return dp[a.length][b.length];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function monthsBetween(a, b) {
|
|
32
|
+
return Math.abs((b.getFullYear() - a.getFullYear()) * 12 + (b.getMonth() - a.getMonth()));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function latestPublishedAt(metadata) {
|
|
36
|
+
if (!metadata || !metadata.time) return null;
|
|
37
|
+
const latest = metadata['dist-tags'] && metadata['dist-tags'].latest;
|
|
38
|
+
const raw = latest && metadata.time[latest] ? metadata.time[latest] : metadata.time.modified;
|
|
39
|
+
return raw ? new Date(raw) : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function repoUrl(metadata) {
|
|
43
|
+
if (!metadata || !metadata.repository) return null;
|
|
44
|
+
if (typeof metadata.repository === 'string') return metadata.repository;
|
|
45
|
+
return metadata.repository.url || null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function typoRisk(name, knownNames = []) {
|
|
49
|
+
const risks = [];
|
|
50
|
+
for (const known of knownNames) {
|
|
51
|
+
if (normalizeName(name) === normalizeName(known)) continue;
|
|
52
|
+
const distance = levenshtein(name, known);
|
|
53
|
+
if (distance > 0 && distance <= 2) {
|
|
54
|
+
risks.push({ known, distance });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return risks.sort((a, b) => a.distance - b.distance);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function assessNpmMetadata(name, metadata, opts = {}) {
|
|
61
|
+
const now = opts.now || new Date();
|
|
62
|
+
const findings = [];
|
|
63
|
+
if (!metadata || metadata.missing) {
|
|
64
|
+
findings.push({
|
|
65
|
+
severity: 'fail',
|
|
66
|
+
code: 'package-missing',
|
|
67
|
+
message: `${name} was not found in the npm registry data.`
|
|
68
|
+
});
|
|
69
|
+
return { status: 'fail', findings, signals: { exists: false } };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const risks = typoRisk(name, opts.knownNames || []);
|
|
73
|
+
for (const risk of risks) {
|
|
74
|
+
findings.push({
|
|
75
|
+
severity: 'fail',
|
|
76
|
+
code: 'typo-risk',
|
|
77
|
+
message: `${name} is within edit distance ${risk.distance} of ${risk.known}.`
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const published = latestPublishedAt(metadata);
|
|
82
|
+
if (!published) {
|
|
83
|
+
findings.push({
|
|
84
|
+
severity: 'warn',
|
|
85
|
+
code: 'unknown-recency',
|
|
86
|
+
message: `${name} has no publish timestamp in registry metadata.`
|
|
87
|
+
});
|
|
88
|
+
} else if (monthsBetween(published, now) > STALE_MONTHS) {
|
|
89
|
+
findings.push({
|
|
90
|
+
severity: 'warn',
|
|
91
|
+
code: 'stale-package',
|
|
92
|
+
message: `${name} latest release is older than ${STALE_MONTHS} months.`
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!repoUrl(metadata)) {
|
|
97
|
+
findings.push({
|
|
98
|
+
severity: 'warn',
|
|
99
|
+
code: 'missing-repository',
|
|
100
|
+
message: `${name} does not expose a repository URL.`
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const status = findings.some(f => f.severity === 'fail')
|
|
105
|
+
? 'fail'
|
|
106
|
+
: findings.some(f => f.severity === 'warn')
|
|
107
|
+
? 'warn'
|
|
108
|
+
: 'pass';
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
status,
|
|
112
|
+
findings,
|
|
113
|
+
signals: {
|
|
114
|
+
exists: true,
|
|
115
|
+
latest: metadata['dist-tags'] && metadata['dist-tags'].latest,
|
|
116
|
+
publishedAt: published ? published.toISOString() : null,
|
|
117
|
+
repository: repoUrl(metadata)
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function npmView(name) {
|
|
123
|
+
try {
|
|
124
|
+
const raw = execFileSync('npm', ['view', name, '--json'], {
|
|
125
|
+
encoding: 'utf8',
|
|
126
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
127
|
+
});
|
|
128
|
+
return JSON.parse(raw);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
return { missing: true, error: error.message };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function checkNpmPackage(name, opts = {}) {
|
|
135
|
+
const metadata = opts.metadata || npmView(name);
|
|
136
|
+
if (metadata && metadata.error && opts.failOnUnknown === false) {
|
|
137
|
+
return {
|
|
138
|
+
status: 'unknown',
|
|
139
|
+
findings: [{
|
|
140
|
+
severity: 'warn',
|
|
141
|
+
code: 'registry-unavailable',
|
|
142
|
+
message: `${name} could not be verified from npm metadata.`
|
|
143
|
+
}],
|
|
144
|
+
signals: { exists: null }
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return assessNpmMetadata(name, metadata, opts);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
STALE_MONTHS,
|
|
152
|
+
normalizeName,
|
|
153
|
+
levenshtein,
|
|
154
|
+
typoRisk,
|
|
155
|
+
assessNpmMetadata,
|
|
156
|
+
checkNpmPackage,
|
|
157
|
+
latestPublishedAt
|
|
158
|
+
};
|
package/lib/planning-systems.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Planning System Migration
|
|
3
3
|
*
|
|
4
|
-
* Detects
|
|
4
|
+
* Detects legacy planning, BMAD, and Superpowers project artifacts, converts their useful
|
|
5
5
|
* signals into Godpowers preparation artifacts, and records source-system state
|
|
6
6
|
* so /god-sync can later write progress back through lib/source-sync.js.
|
|
7
7
|
*/
|
|
@@ -71,11 +71,11 @@ const GODPOWERS_ARTIFACTS = [
|
|
|
71
71
|
];
|
|
72
72
|
|
|
73
73
|
const SYSTEMS = {
|
|
74
|
-
|
|
75
|
-
displayName: '
|
|
76
|
-
markerPaths: ['.planning', '.
|
|
77
|
-
fileRoots: ['.planning', '.
|
|
78
|
-
standalonePatterns: [/^
|
|
74
|
+
'legacy-planning': {
|
|
75
|
+
displayName: 'legacy planning',
|
|
76
|
+
markerPaths: ['.planning', '.legacy-planning', 'LEGACY-PLANNING.md'],
|
|
77
|
+
fileRoots: ['.planning', '.legacy-planning'],
|
|
78
|
+
standalonePatterns: [/^legacy-planning.*\.md$/i]
|
|
79
79
|
},
|
|
80
80
|
bmad: {
|
|
81
81
|
displayName: 'BMAD',
|
|
@@ -246,7 +246,7 @@ function detectSystem(projectRoot, id, system) {
|
|
|
246
246
|
};
|
|
247
247
|
});
|
|
248
248
|
|
|
249
|
-
const hasPrimaryRoot = markerHits.some((marker) => ['.planning', '.
|
|
249
|
+
const hasPrimaryRoot = markerHits.some((marker) => ['.planning', '.legacy-planning', '_bmad-output', 'docs/superpowers'].includes(marker));
|
|
250
250
|
const score = markerHits.length * 3 + fileRecords.length;
|
|
251
251
|
const confidence = score >= 10 || (hasPrimaryRoot && fileRecords.length >= 3)
|
|
252
252
|
? 'high'
|
|
@@ -294,7 +294,7 @@ function buildImportedContext(detection) {
|
|
|
294
294
|
lines.push('');
|
|
295
295
|
|
|
296
296
|
if (detection.systems.length === 0) {
|
|
297
|
-
lines.push('- [DECISION] No
|
|
297
|
+
lines.push('- [DECISION] No legacy planning, BMAD, or Superpowers planning context was detected.');
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
for (const system of detection.systems) {
|
|
@@ -331,7 +331,7 @@ function buildImportedContext(detection) {
|
|
|
331
331
|
lines.push('## Sync-Back Policy');
|
|
332
332
|
lines.push('');
|
|
333
333
|
lines.push('- [DECISION] Godpowers may write managed sync-back summaries only inside Godpowers-owned fences or companion files.');
|
|
334
|
-
lines.push('- [DECISION] Godpowers must not rewrite
|
|
334
|
+
lines.push('- [DECISION] Godpowers must not rewrite legacy planning, BMAD, or Superpowers source documents outside managed sync-back sections.');
|
|
335
335
|
lines.push('- [DECISION] Sync-back exists so a project can return to the prior planning system with current Godpowers progress visible.');
|
|
336
336
|
|
|
337
337
|
lines.push('');
|
package/lib/repo-surface-sync.js
CHANGED
|
@@ -442,7 +442,7 @@ function dogfoodChecks(projectRoot) {
|
|
|
442
442
|
const pkg = readJson(projectRoot, 'package.json') || {};
|
|
443
443
|
const scriptsText = releaseGateText(projectRoot, pkg);
|
|
444
444
|
const scenarios = [
|
|
445
|
-
'fixtures/dogfood/half-migrated-
|
|
445
|
+
'fixtures/dogfood/half-migrated-planning/manifest.json',
|
|
446
446
|
'fixtures/dogfood/host-degraded/manifest.json',
|
|
447
447
|
'fixtures/dogfood/host-full/manifest.json',
|
|
448
448
|
'fixtures/dogfood/extension-authoring/manifest.json',
|
package/lib/requirements.js
CHANGED
|
@@ -31,6 +31,7 @@ const path = require('path');
|
|
|
31
31
|
|
|
32
32
|
const linkage = require('./linkage');
|
|
33
33
|
const state = require('./state');
|
|
34
|
+
const atomic = require('./atomic-write');
|
|
34
35
|
|
|
35
36
|
const PRD_PATH = '.godpowers/prd/PRD.md';
|
|
36
37
|
const ROADMAP_PATH = '.godpowers/roadmap/ROADMAP.md';
|
|
@@ -504,7 +505,7 @@ function writeLedger(projectRoot, derived) {
|
|
|
504
505
|
return LEDGER_PATH;
|
|
505
506
|
}
|
|
506
507
|
}
|
|
507
|
-
|
|
508
|
+
atomic.writeFileAtomic(file, rendered);
|
|
508
509
|
return LEDGER_PATH;
|
|
509
510
|
}
|
|
510
511
|
|
package/lib/reverse-sync.js
CHANGED
|
@@ -28,6 +28,7 @@ const impeccable = require('./impeccable-bridge');
|
|
|
28
28
|
const sourceSync = require('./source-sync');
|
|
29
29
|
const requirements = require('./requirements');
|
|
30
30
|
const state = require('./state');
|
|
31
|
+
const atomic = require('./atomic-write');
|
|
31
32
|
|
|
32
33
|
const FENCE_BEGIN = '<!-- godpowers:linkage:begin -->';
|
|
33
34
|
const FENCE_END = '<!-- godpowers:linkage:end -->';
|
|
@@ -68,7 +69,7 @@ function writeFenced(filePath, sectionContent) {
|
|
|
68
69
|
} else {
|
|
69
70
|
next = `${parsed.before}${fencedBlock}${parsed.after}`;
|
|
70
71
|
}
|
|
71
|
-
|
|
72
|
+
atomic.writeFileAtomic(filePath, next);
|
|
72
73
|
return { written: true, hadFenceBefore: parsed.fenced !== '' };
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const DEFAULT_IGNORE_DIRS = new Set([
|
|
5
|
+
'.git',
|
|
6
|
+
'.godpowers',
|
|
7
|
+
'node_modules',
|
|
8
|
+
'dist',
|
|
9
|
+
'build',
|
|
10
|
+
'coverage',
|
|
11
|
+
'.next',
|
|
12
|
+
'.turbo'
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
function normalizeRel(filePath) {
|
|
16
|
+
return String(filePath || '').split(path.sep).join('/');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function listFiles(root, opts = {}) {
|
|
20
|
+
const ignoreDirs = opts.ignoreDirs || DEFAULT_IGNORE_DIRS;
|
|
21
|
+
const out = [];
|
|
22
|
+
function walk(dir) {
|
|
23
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
if (ignoreDirs.has(entry.name)) continue;
|
|
26
|
+
walk(path.join(dir, entry.name));
|
|
27
|
+
} else if (entry.isFile()) {
|
|
28
|
+
const rel = normalizeRel(path.relative(root, path.join(dir, entry.name)));
|
|
29
|
+
out.push(rel);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (fs.existsSync(root)) walk(root);
|
|
34
|
+
return out.sort();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseSectionLists(planText) {
|
|
38
|
+
const result = {
|
|
39
|
+
existingFiles: [],
|
|
40
|
+
existingSymbols: [],
|
|
41
|
+
newArtifacts: [],
|
|
42
|
+
unchecked: []
|
|
43
|
+
};
|
|
44
|
+
let section = null;
|
|
45
|
+
const lines = String(planText || '').split(/\r?\n/);
|
|
46
|
+
for (const raw of lines) {
|
|
47
|
+
const heading = raw.match(/^#{2,4}\s+(.+)$/);
|
|
48
|
+
if (heading) {
|
|
49
|
+
const title = heading[1].toLowerCase();
|
|
50
|
+
if (/existing (files|references)/.test(title)) section = 'existingFiles';
|
|
51
|
+
else if (/existing symbols/.test(title)) section = 'existingSymbols';
|
|
52
|
+
else if (/new artifacts|files to create/.test(title)) section = 'newArtifacts';
|
|
53
|
+
else if (/unchecked references|unknown references/.test(title)) section = 'unchecked';
|
|
54
|
+
else section = null;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!section) continue;
|
|
58
|
+
const item = raw.match(/^\s*[-*]\s+(.*)$/);
|
|
59
|
+
if (!item) continue;
|
|
60
|
+
const value = item[1]
|
|
61
|
+
.replace(/^(file|symbol|new|unchecked)\s*:\s*/i, '')
|
|
62
|
+
.replace(/^`|`$/g, '')
|
|
63
|
+
.trim();
|
|
64
|
+
if (value) result[section].push(value);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function unique(values) {
|
|
70
|
+
return [...new Set(values.filter(Boolean))];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function findSymbolInFiles(root, symbol, files) {
|
|
74
|
+
const needle = String(symbol || '').trim();
|
|
75
|
+
if (!needle) return [];
|
|
76
|
+
const escaped = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
77
|
+
const re = new RegExp(`\\b${escaped}\\b`);
|
|
78
|
+
const hits = [];
|
|
79
|
+
for (const rel of files) {
|
|
80
|
+
const full = path.join(root, rel);
|
|
81
|
+
let text;
|
|
82
|
+
try {
|
|
83
|
+
text = fs.readFileSync(full, 'utf8');
|
|
84
|
+
} catch (_) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (re.test(text)) hits.push(rel);
|
|
88
|
+
}
|
|
89
|
+
return hits;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function checkPlan(projectRoot, planText, opts = {}) {
|
|
93
|
+
const parsed = parseSectionLists(planText);
|
|
94
|
+
const existingFiles = unique([...(opts.existingFiles || []), ...parsed.existingFiles]);
|
|
95
|
+
const existingSymbols = unique([...(opts.existingSymbols || []), ...parsed.existingSymbols]);
|
|
96
|
+
const newArtifacts = unique([...(opts.newArtifacts || []), ...parsed.newArtifacts]);
|
|
97
|
+
const unchecked = unique([...(opts.unchecked || []), ...parsed.unchecked]);
|
|
98
|
+
const files = listFiles(projectRoot, opts);
|
|
99
|
+
const fileSet = new Set(files);
|
|
100
|
+
|
|
101
|
+
const fileChecks = existingFiles.map(file => ({
|
|
102
|
+
type: 'file',
|
|
103
|
+
value: normalizeRel(file),
|
|
104
|
+
status: fileSet.has(normalizeRel(file)) ? 'pass' : 'fail'
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
const symbolChecks = existingSymbols.map(symbol => {
|
|
108
|
+
const hits = findSymbolInFiles(projectRoot, symbol, files);
|
|
109
|
+
return {
|
|
110
|
+
type: 'symbol',
|
|
111
|
+
value: symbol,
|
|
112
|
+
status: hits.length > 0 ? 'pass' : 'fail',
|
|
113
|
+
files: hits
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const checks = [...fileChecks, ...symbolChecks];
|
|
118
|
+
const failures = checks.filter(check => check.status === 'fail');
|
|
119
|
+
const warnings = unchecked.map(value => ({
|
|
120
|
+
type: 'unchecked',
|
|
121
|
+
value,
|
|
122
|
+
status: 'warn'
|
|
123
|
+
}));
|
|
124
|
+
const newArtifactSet = new Set(newArtifacts.map(normalizeRel));
|
|
125
|
+
const declaredNew = newArtifacts.map(file => ({
|
|
126
|
+
type: 'new-artifact',
|
|
127
|
+
value: normalizeRel(file),
|
|
128
|
+
status: fileSet.has(normalizeRel(file)) ? 'exists' : 'declared-new'
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
ok: failures.length === 0,
|
|
133
|
+
checks,
|
|
134
|
+
failures,
|
|
135
|
+
warnings,
|
|
136
|
+
declaredNew,
|
|
137
|
+
newArtifacts: [...newArtifactSet],
|
|
138
|
+
summary: {
|
|
139
|
+
pass: checks.filter(check => check.status === 'pass').length,
|
|
140
|
+
fail: failures.length,
|
|
141
|
+
warn: warnings.length,
|
|
142
|
+
declaredNew: declaredNew.length
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function renderReport(result) {
|
|
148
|
+
const lines = [];
|
|
149
|
+
lines.push(`Source grounding: ${result.ok ? 'PASS' : 'FAIL'}`);
|
|
150
|
+
lines.push(` passed: ${result.summary.pass}`);
|
|
151
|
+
lines.push(` failed: ${result.summary.fail}`);
|
|
152
|
+
lines.push(` unchecked: ${result.summary.warn}`);
|
|
153
|
+
if (result.failures.length > 0) {
|
|
154
|
+
lines.push('');
|
|
155
|
+
lines.push('Failures:');
|
|
156
|
+
for (const failure of result.failures) {
|
|
157
|
+
lines.push(` - ${failure.type}: ${failure.value}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (result.warnings.length > 0) {
|
|
161
|
+
lines.push('');
|
|
162
|
+
lines.push('Unchecked references:');
|
|
163
|
+
for (const warning of result.warnings) {
|
|
164
|
+
lines.push(` - ${warning.value}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return lines.join('\n');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = {
|
|
171
|
+
DEFAULT_IGNORE_DIRS,
|
|
172
|
+
listFiles,
|
|
173
|
+
parseSectionLists,
|
|
174
|
+
checkPlan,
|
|
175
|
+
renderReport,
|
|
176
|
+
findSymbolInFiles
|
|
177
|
+
};
|
package/lib/source-sync.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Source System Sync-Back
|
|
3
3
|
*
|
|
4
|
-
* Writes Godpowers progress back to detected
|
|
4
|
+
* Writes Godpowers progress back to detected legacy planning, BMAD, and Superpowers
|
|
5
5
|
* projects through managed companion files and optional managed fences.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -10,13 +10,14 @@ const path = require('path');
|
|
|
10
10
|
const crypto = require('crypto');
|
|
11
11
|
|
|
12
12
|
const state = require('./state');
|
|
13
|
+
const atomic = require('./atomic-write');
|
|
13
14
|
|
|
14
15
|
const FENCE_BEGIN = '<!-- godpowers:source-sync:begin -->';
|
|
15
16
|
const FENCE_END = '<!-- godpowers:source-sync:end -->';
|
|
16
17
|
|
|
17
18
|
const SYSTEM_TARGETS = {
|
|
18
|
-
|
|
19
|
-
companionCandidates: ['.planning/GODPOWERS-SYNC.md', '.
|
|
19
|
+
'legacy-planning': {
|
|
20
|
+
companionCandidates: ['.planning/GODPOWERS-SYNC.md', '.legacy-planning/GODPOWERS-SYNC.md'],
|
|
20
21
|
pointerCandidates: ['.planning/STATE.md']
|
|
21
22
|
},
|
|
22
23
|
bmad: {
|
|
@@ -68,7 +69,7 @@ function writeFenced(filePath, sectionContent) {
|
|
|
68
69
|
next = `${parsed.before}${block}${parsed.after}`;
|
|
69
70
|
}
|
|
70
71
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
71
|
-
|
|
72
|
+
atomic.writeFileAtomic(filePath, next);
|
|
72
73
|
return {
|
|
73
74
|
written: true,
|
|
74
75
|
hadFenceBefore: parsed.exists && parsed.fenced !== ''
|
|
@@ -134,7 +135,7 @@ function progressLines(projectRoot) {
|
|
|
134
135
|
lines.push('');
|
|
135
136
|
lines.push('## Return Path');
|
|
136
137
|
lines.push('');
|
|
137
|
-
lines.push('- [DECISION] If the project returns to
|
|
138
|
+
lines.push('- [DECISION] If the project returns to legacy planning, BMAD, or Superpowers, use this file as a migration note rather than treating it as a native source-system artifact.');
|
|
138
139
|
lines.push('- [OPEN QUESTION] Confirm which Godpowers decisions should be copied into native source-system documents before switching systems. Owner: user. Due: before switching systems.');
|
|
139
140
|
lines.push('');
|
|
140
141
|
return lines.join('\n');
|
package/lib/state.js
CHANGED
|
@@ -9,6 +9,7 @@ const fs = require('fs');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const crypto = require('crypto');
|
|
11
11
|
const asyncFs = require('./fs-async');
|
|
12
|
+
const atomic = require('./atomic-write');
|
|
12
13
|
|
|
13
14
|
const STATE_VERSION = '1.0.0';
|
|
14
15
|
const COMPLETE_STATUSES = new Set(['done', 'imported', 'skipped', 'not-required']);
|
|
@@ -138,7 +139,7 @@ function write(projectRoot, state) {
|
|
|
138
139
|
|
|
139
140
|
const file = statePath(projectRoot);
|
|
140
141
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
141
|
-
|
|
142
|
+
atomic.writeJsonAtomic(file, state);
|
|
142
143
|
return state;
|
|
143
144
|
}
|
|
144
145
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "godpowers",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "AI-powered development system: 111 slash commands and 40 specialist agents that take a project from raw idea to hardened production. Runs inside Claude Code, Codex, Cursor, Windsurf, Gemini, and 10+ other AI coding tools.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"godpowers": "./bin/install.js"
|
package/references/HAVE-NOTS.md
CHANGED
|
@@ -87,7 +87,7 @@ A command completed without a disk-derived dashboard, action brief, or
|
|
|
87
87
|
recommended next command. Fail.
|
|
88
88
|
|
|
89
89
|
### O-09 Sync-back ambiguity
|
|
90
|
-
Imported
|
|
90
|
+
Imported legacy planning, BMAD, or Superpowers context exists but the project does not say
|
|
91
91
|
whether managed sync-back is enabled, disabled, or not applicable. Fail.
|
|
92
92
|
|
|
93
93
|
### O-10 Host guarantees hidden
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
- Some `.godpowers/<tier>/<artifact>` files already exist
|
|
19
19
|
- OR existing codebase signals: package.json, Dockerfile, .github/workflows
|
|
20
20
|
- User describes an existing project they want to add Godpowers to
|
|
21
|
-
-
|
|
21
|
+
- legacy planning, BMAD, or Superpowers planning context is detected and should be imported
|
|
22
22
|
into Godpowers preparation artifacts
|
|
23
23
|
|
|
24
24
|
**Behavior**:
|
|
@@ -33,7 +33,7 @@ Codebase signals (for inferring partial completion):
|
|
|
33
33
|
- `.github/workflows/` or `.gitlab-ci.yml` exists -> CI present
|
|
34
34
|
- `tests/` or `*.test.*` files exist -> Build tier in progress
|
|
35
35
|
- `Dockerfile` + deploy config -> Deploy tier may be done
|
|
36
|
-
- `.planning/`, `.
|
|
36
|
+
- `.planning/`, `.legacy-planning/`, `_bmad-output/`, `.bmad/`, or Superpowers specs ->
|
|
37
37
|
source-system import and managed sync-back may be needed
|
|
38
38
|
|
|
39
39
|
## Mode C: Audit
|
|
@@ -45,7 +45,7 @@ If you arrive at Godpowers carrying artifacts from another system,
|
|
|
45
45
|
`/god-init` Mode B (gap-fill) and `/god-migrate` read what exists and map it
|
|
46
46
|
forward:
|
|
47
47
|
|
|
48
|
-
-
|
|
48
|
+
- legacy planning `.planning/` or `.legacy-planning/` context -> `.godpowers/prep/IMPORTED-CONTEXT.md`
|
|
49
49
|
and optional native seed artifacts
|
|
50
50
|
- BMAD `_bmad-output/` or `.bmad/` context -> imported preparation context and
|
|
51
51
|
open questions for PRD, architecture, roadmap, and stack
|
|
@@ -74,7 +74,7 @@ authoritative. `/god-sync` writes managed companion files such as:
|
|
|
74
74
|
The sync-back file is a bridge, not a second source of truth. It should contain
|
|
75
75
|
the current Godpowers status, imported-context mapping, open conflicts, and the
|
|
76
76
|
next safe route back into either system. It must be fenced or companion-owned
|
|
77
|
-
so Godpowers does not overwrite arbitrary
|
|
77
|
+
so Godpowers does not overwrite arbitrary legacy planning, BMAD, or Superpowers artifacts.
|
|
78
78
|
|
|
79
79
|
## Existing Godpowers projects after upgrades
|
|
80
80
|
|
|
@@ -94,7 +94,7 @@ frontmatter. There's no proprietary binary state. To leave:
|
|
|
94
94
|
system doesn't understand them (they're recoverable from code
|
|
95
95
|
annotations).
|
|
96
96
|
3. Use the most recent managed sync-back file as the return-path summary if the
|
|
97
|
-
target system is
|
|
97
|
+
target system is legacy planning, BMAD, or Superpowers.
|
|
98
98
|
4. Delete `.godpowers/`.
|
|
99
99
|
|
|
100
100
|
## What Godpowers does not try to be
|
package/routing/god-design.yaml
CHANGED
|
@@ -17,7 +17,7 @@ prerequisites:
|
|
|
17
17
|
- check: file:.godpowers/prep/INITIAL-FINDINGS.md
|
|
18
18
|
reason: "Initial findings can show UI and product-experience signals"
|
|
19
19
|
- check: file:.godpowers/prep/IMPORTED-CONTEXT.md
|
|
20
|
-
reason: "Imported
|
|
20
|
+
reason: "Imported legacy planning, Superpowers, or BMAD context can inform design hypotheses"
|
|
21
21
|
- check: file:.godpowers/arch/ARCH.md
|
|
22
22
|
reason: "ARCH names the UI surface that DESIGN applies to"
|
|
23
23
|
- check: file:.godpowers/stack/DECISION.md
|
package/routing/god-migrate.yaml
CHANGED
|
@@ -2,7 +2,7 @@ apiVersion: godpowers/v1
|
|
|
2
2
|
kind: CommandRouting
|
|
3
3
|
metadata:
|
|
4
4
|
command: /god-migrate
|
|
5
|
-
description: Detect and migrate
|
|
5
|
+
description: Detect and migrate legacy planning, BMAD, and Superpowers planning context
|
|
6
6
|
tier: 0
|
|
7
7
|
|
|
8
8
|
prerequisites:
|
|
@@ -16,7 +16,7 @@ execution:
|
|
|
16
16
|
context: current
|
|
17
17
|
reads:
|
|
18
18
|
- .planning/**
|
|
19
|
-
- .
|
|
19
|
+
- .legacy-planning/**
|
|
20
20
|
- _bmad-output/**
|
|
21
21
|
- .bmad/**
|
|
22
22
|
- docs/superpowers/**
|
|
@@ -29,7 +29,7 @@ execution:
|
|
|
29
29
|
- .godpowers/build/STATE.md
|
|
30
30
|
- .godpowers/state.json
|
|
31
31
|
- .planning/GODPOWERS-SYNC.md
|
|
32
|
-
- .
|
|
32
|
+
- .legacy-planning/GODPOWERS-SYNC.md
|
|
33
33
|
- _bmad-output/GODPOWERS-SYNC.md
|
|
34
34
|
- .bmad/GODPOWERS-SYNC.md
|
|
35
35
|
- docs/superpowers/GODPOWERS-SYNC.md
|
package/schema/state.v1.json
CHANGED
package/skills/god-build.md
CHANGED
|
@@ -30,6 +30,19 @@ Orchestrate the build via specialist agents.
|
|
|
30
30
|
Spawn **god-planner** in fresh context with ROADMAP, ARCH, DECISION.
|
|
31
31
|
Output: `.godpowers/build/PLAN.md` with vertical slices grouped into waves.
|
|
32
32
|
|
|
33
|
+
After the planner returns, run the source-grounding preflight from
|
|
34
|
+
`lib/source-grounding.js` against `.godpowers/build/PLAN.md`. The plan must
|
|
35
|
+
distinguish existing files, existing symbols, new artifacts, and unchecked
|
|
36
|
+
references before any executor starts.
|
|
37
|
+
|
|
38
|
+
Block execution when:
|
|
39
|
+
- A plan cites an existing file that does not exist.
|
|
40
|
+
- A plan cites an existing symbol that cannot be found in the repo.
|
|
41
|
+
- A cited reference is neither grounded nor declared as a new artifact.
|
|
42
|
+
|
|
43
|
+
Allow execution only when missing references are corrected, marked as new
|
|
44
|
+
artifacts, or explicitly accepted by the user as unchecked risk.
|
|
45
|
+
|
|
33
46
|
### Phase 2: Execute Waves
|
|
34
47
|
|
|
35
48
|
For each wave in PLAN.md:
|
|
@@ -59,9 +72,10 @@ After all waves:
|
|
|
59
72
|
2. Run linter. All clean.
|
|
60
73
|
3. Run the package's typecheck/check command when present. All pass.
|
|
61
74
|
4. If any verification command fails, do not mark Build complete. Re-enter
|
|
62
|
-
repair mode with the owning agent
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
repair mode with the owning agent. Classify the failure as retry,
|
|
76
|
+
decompose, prune, or escalate. Pass the exact failing diagnostics, rerun the
|
|
77
|
+
command, and repeat until green or until the same root failure exhausts the
|
|
78
|
+
repair budget.
|
|
65
79
|
5. Update PROGRESS.md: Build status = done
|
|
66
80
|
6. If the build plan or implementation establishes durable conventions, plan
|
|
67
81
|
pillar updates through `lib/pillars.planArtifactSync`. Under
|
package/skills/god-doctor.md
CHANGED
|
@@ -50,7 +50,7 @@ GODPOWERS DOCTOR
|
|
|
50
50
|
Install: claude (~/.claude/)
|
|
51
51
|
[OK] 111 skills installed
|
|
52
52
|
[OK] 40 agents installed
|
|
53
|
-
[OK] VERSION matches (2.
|
|
53
|
+
[OK] VERSION matches (2.3.0)
|
|
54
54
|
[WARN] routing/god-doctor.yaml exists but skill file did not until now
|
|
55
55
|
|
|
56
56
|
Project: /Users/.../my-project/.godpowers/
|
|
@@ -106,7 +106,7 @@ as a read-only diagnostic. It reports:
|
|
|
106
106
|
- runtime version recorded in `state.json`
|
|
107
107
|
- missing current Godpowers feature IDs
|
|
108
108
|
- missing managed AI-tool context fences
|
|
109
|
-
- unimported
|
|
109
|
+
- unimported legacy planning, BMAD, or Superpowers evidence that should route to
|
|
110
110
|
`/god-migrate`
|
|
111
111
|
- `god-greenfieldifier` recommendation when migration evidence is low
|
|
112
112
|
confidence or conflicting
|