godpowers 2.2.1 → 2.3.1

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.
Files changed (81) hide show
  1. package/CHANGELOG.md +65 -7
  2. package/INSPIRATION.md +4 -2
  3. package/README.md +35 -22
  4. package/RELEASE.md +44 -23
  5. package/SKILL.md +2 -2
  6. package/agents/god-auditor.md +1 -1
  7. package/agents/god-debugger.md +44 -8
  8. package/agents/god-deps-auditor.md +11 -0
  9. package/agents/god-executor.md +50 -5
  10. package/agents/god-greenfieldifier.md +1 -1
  11. package/agents/god-orchestrator.md +2 -2
  12. package/agents/god-planner.md +14 -0
  13. package/agents/god-pm.md +1 -1
  14. package/agents/god-quality-reviewer.md +23 -1
  15. package/agents/god-reconciler.md +1 -1
  16. package/agents/god-roadmapper.md +1 -1
  17. package/agents/god-spec-reviewer.md +16 -0
  18. package/agents/god-stack-selector.md +4 -0
  19. package/agents/god-updater.md +1 -1
  20. package/bin/install.js +9 -3
  21. package/fixtures/dogfood/{half-migrated-gsd → half-migrated-planning}/manifest.json +2 -2
  22. package/lib/README.md +9 -2
  23. package/lib/agent-cache.js +25 -3
  24. package/lib/atomic-write.js +85 -0
  25. package/lib/checkpoint.js +2 -1
  26. package/lib/code-intelligence.js +161 -0
  27. package/lib/context-writer.js +1 -1
  28. package/lib/dashboard.js +54 -20
  29. package/lib/executor-repair.js +65 -0
  30. package/lib/extensions.js +6 -13
  31. package/lib/feature-awareness.js +2 -2
  32. package/lib/fs-async.js +2 -1
  33. package/lib/host-capabilities.js +4 -0
  34. package/lib/install-profiles.js +155 -0
  35. package/lib/installer-args.js +12 -0
  36. package/lib/installer-core.js +43 -8
  37. package/lib/linkage.js +2 -1
  38. package/lib/package-identity.js +38 -0
  39. package/lib/package-legitimacy.js +158 -0
  40. package/lib/planning-systems.js +16 -13
  41. package/lib/quick-proof.js +5 -2
  42. package/lib/repo-surface-sync.js +1 -1
  43. package/lib/requirements.js +2 -1
  44. package/lib/reverse-sync.js +2 -1
  45. package/lib/route-quality-sync.js +2 -0
  46. package/lib/source-grounding.js +177 -0
  47. package/lib/source-sync.js +6 -5
  48. package/lib/state.js +2 -1
  49. package/package.json +2 -2
  50. package/references/HAVE-NOTS.md +1 -1
  51. package/references/orchestration/MODE-DETECTION.md +2 -2
  52. package/references/shared/ORCHESTRATORS.md +3 -3
  53. package/routing/god-design.yaml +1 -1
  54. package/routing/god-extension-scaffold.yaml +25 -0
  55. package/routing/god-migrate.yaml +3 -3
  56. package/routing/god-stack.yaml +1 -1
  57. package/routing/recipes/add-feature-mid-arc-pause.yaml +6 -0
  58. package/routing/recipes/brownfield-onboarding.yaml +5 -2
  59. package/routing/recipes/extension-authoring.yaml +32 -0
  60. package/routing/recipes/greenfield-fast.yaml +3 -0
  61. package/routing/recipes/production-broken.yaml +4 -0
  62. package/routing/recipes/release-maintenance.yaml +3 -0
  63. package/routing/recipes/returning-after-break.yaml +3 -0
  64. package/routing/recipes/weekly-health-check.yaml +2 -0
  65. package/schema/state.v1.json +1 -1
  66. package/skills/god-build.md +17 -3
  67. package/skills/god-discuss.md +10 -5
  68. package/skills/god-doctor.md +9 -3
  69. package/skills/god-dogfood.md +2 -2
  70. package/skills/god-extension-scaffold.md +66 -0
  71. package/skills/god-init.md +6 -6
  72. package/skills/god-migrate.md +10 -10
  73. package/skills/god-sync.md +2 -2
  74. package/skills/god-update-deps.md +4 -2
  75. package/skills/god-version.md +2 -2
  76. package/skills/god.md +8 -11
  77. package/templates/IMPORTED-CONTEXT.md +1 -1
  78. package/templates/INITIAL-FINDINGS.md +2 -2
  79. /package/fixtures/dogfood/{half-migrated-gsd → half-migrated-planning}/.planning/PROJECT.md +0 -0
  80. /package/fixtures/dogfood/{half-migrated-gsd → half-migrated-planning}/.planning/REQUIREMENTS.md +0 -0
  81. /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
+ };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Planning System Migration
3
3
  *
4
- * Detects GSD, BMAD, and Superpowers project artifacts, converts their useful
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
- gsd: {
75
- displayName: 'GSD',
76
- markerPaths: ['.planning', '.gsd', 'GSD.md'],
77
- fileRoots: ['.planning', '.gsd'],
78
- standalonePatterns: [/^gsd.*\.md$/i]
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',
@@ -112,7 +112,7 @@ function hashFiles(projectRoot, files) {
112
112
  for (const file of files.map((f) => f.path).sort()) {
113
113
  const full = path.join(projectRoot, file);
114
114
  h.update(file);
115
- if (fs.existsSync(full) && fs.statSync(full).isFile()) {
115
+ if (fs.existsSync(full) && fs.lstatSync(full).isFile()) {
116
116
  h.update(fs.readFileSync(full));
117
117
  }
118
118
  }
@@ -125,8 +125,10 @@ function isTextFile(filePath) {
125
125
 
126
126
  function readText(projectRoot, relPath) {
127
127
  const full = path.join(projectRoot, relPath);
128
- if (!fs.existsSync(full) || !fs.statSync(full).isFile()) return '';
129
- const size = fs.statSync(full).size;
128
+ if (!fs.existsSync(full)) return '';
129
+ const stat = fs.lstatSync(full);
130
+ if (!stat.isFile()) return '';
131
+ const size = stat.size;
130
132
  const buffer = fs.readFileSync(full);
131
133
  const raw = buffer.slice(0, Math.min(size, MAX_FILE_BYTES)).toString('utf8');
132
134
  return raw.replace(/\r\n/g, '\n');
@@ -134,7 +136,8 @@ function readText(projectRoot, relPath) {
134
136
 
135
137
  function walkFiles(rootDir, projectRoot, out = []) {
136
138
  if (!fs.existsSync(rootDir) || out.length >= MAX_SYSTEM_FILES) return out;
137
- const stat = fs.statSync(rootDir);
139
+ const stat = fs.lstatSync(rootDir);
140
+ if (stat.isSymbolicLink()) return out;
138
141
  if (stat.isFile()) {
139
142
  if (isTextFile(rootDir)) out.push(rel(projectRoot, rootDir));
140
143
  return out;
@@ -246,7 +249,7 @@ function detectSystem(projectRoot, id, system) {
246
249
  };
247
250
  });
248
251
 
249
- const hasPrimaryRoot = markerHits.some((marker) => ['.planning', '.gsd', '_bmad-output', 'docs/superpowers'].includes(marker));
252
+ const hasPrimaryRoot = markerHits.some((marker) => ['.planning', '.legacy-planning', '_bmad-output', 'docs/superpowers'].includes(marker));
250
253
  const score = markerHits.length * 3 + fileRecords.length;
251
254
  const confidence = score >= 10 || (hasPrimaryRoot && fileRecords.length >= 3)
252
255
  ? 'high'
@@ -294,7 +297,7 @@ function buildImportedContext(detection) {
294
297
  lines.push('');
295
298
 
296
299
  if (detection.systems.length === 0) {
297
- lines.push('- [DECISION] No GSD, BMAD, or Superpowers planning context was detected.');
300
+ lines.push('- [DECISION] No legacy planning, BMAD, or Superpowers planning context was detected.');
298
301
  }
299
302
 
300
303
  for (const system of detection.systems) {
@@ -331,7 +334,7 @@ function buildImportedContext(detection) {
331
334
  lines.push('## Sync-Back Policy');
332
335
  lines.push('');
333
336
  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 GSD, BMAD, or Superpowers source documents outside managed sync-back sections.');
337
+ lines.push('- [DECISION] Godpowers must not rewrite legacy planning, BMAD, or Superpowers source documents outside managed sync-back sections.');
335
338
  lines.push('- [DECISION] Sync-back exists so a project can return to the prior planning system with current Godpowers progress visible.');
336
339
 
337
340
  lines.push('');
@@ -93,6 +93,9 @@ function render(proof, opts = {}) {
93
93
  const next = proof.dashboard.next || {};
94
94
  const progress = proof.dashboard.progress || {};
95
95
  const planning = proof.dashboard.planning || {};
96
+ const projectCommandRoot = path.resolve(proof.projectRoot) === path.resolve(proof.fixtureRoot)
97
+ ? '.'
98
+ : proof.projectRoot;
96
99
 
97
100
  if (opts.brief) {
98
101
  return [
@@ -140,8 +143,8 @@ function render(proof, opts = {}) {
140
143
  ` npx godpowers next --project=${proof.fixtureRoot} --brief`,
141
144
  '',
142
145
  'Try it on your project:',
143
- ` npx godpowers status --project=${proof.projectRoot} --brief`,
144
- ` npx godpowers next --project=${proof.projectRoot} --brief`
146
+ ` npx godpowers status --project=${projectCommandRoot} --brief`,
147
+ ` npx godpowers next --project=${projectCommandRoot} --brief`
145
148
  ].join('\n');
146
149
  }
147
150
 
@@ -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-gsd/manifest.json',
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',
@@ -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
- fs.writeFileSync(file, rendered);
508
+ atomic.writeFileAtomic(file, rendered);
508
509
  return LEDGER_PATH;
509
510
  }
510
511
 
@@ -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
- fs.writeFileSync(filePath, next);
72
+ atomic.writeFileAtomic(filePath, next);
72
73
  return { written: true, hadFenceBefore: parsed.fenced !== '' };
73
74
  }
74
75
 
@@ -27,6 +27,7 @@ const CONTEXTUAL_NEXT_ALLOWED = new Set([
27
27
  '/god-extension-info',
28
28
  '/god-extension-list',
29
29
  '/god-extension-remove',
30
+ '/god-extension-scaffold',
30
31
  '/god-graph',
31
32
  '/god-help',
32
33
  '/god-lifecycle',
@@ -61,6 +62,7 @@ const STANDARDS_EXEMPT_COMMANDS = new Set([
61
62
  '/god-progress',
62
63
  '/god-reconstruct',
63
64
  '/god-roadmap-check',
65
+ '/god-extension-scaffold',
64
66
  '/god-smite',
65
67
  '/god-tech-debt'
66
68
  ]);
@@ -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
+ };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Source System Sync-Back
3
3
  *
4
- * Writes Godpowers progress back to detected GSD, BMAD, and Superpowers
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
- gsd: {
19
- companionCandidates: ['.planning/GODPOWERS-SYNC.md', '.gsd/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
- fs.writeFileSync(filePath, next);
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 GSD, BMAD, or Superpowers, use this file as a migration note rather than treating it as a native source-system artifact.');
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
- fs.writeFileSync(file, JSON.stringify(state, null, 2) + '\n');
142
+ atomic.writeJsonAtomic(file, state);
142
143
  return state;
143
144
  }
144
145
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "godpowers",
3
- "version": "2.2.1",
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.",
3
+ "version": "2.3.1",
4
+ "description": "AI-powered development system: 112 slash commands and 40 specialist agents that take a project from raw idea to hardened production. Runs inside Claude Code, Codex, Cursor, Windsurf, Gemini, and 10+ other AI coding tools.",
5
5
  "bin": {
6
6
  "godpowers": "./bin/install.js"
7
7
  },
@@ -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 GSD, BMAD, or Superpowers context exists but the project does not say
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
- - GSD, BMAD, or Superpowers planning context is detected and should be imported
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/`, `.gsd/`, `_bmad-output/`, `.bmad/`, or Superpowers specs ->
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
- - GSD `.planning/` or `.gsd/` context -> `.godpowers/prep/IMPORTED-CONTEXT.md`
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 GSD, BMAD, or Superpowers artifacts.
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 GSD, BMAD, or Superpowers.
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
@@ -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 GSD, Superpowers, or BMAD context can inform design hypotheses"
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
@@ -0,0 +1,25 @@
1
+ apiVersion: godpowers/v1
2
+ kind: CommandRouting
3
+ metadata:
4
+ command: /god-extension-scaffold
5
+ description: Create a publishable extension pack skeleton
6
+ tier: 0
7
+
8
+ prerequisites:
9
+ required: []
10
+
11
+ execution:
12
+ spawns: [built-in]
13
+ context: fresh
14
+ writes:
15
+ - extension pack skeleton
16
+
17
+ success-path:
18
+ next-recommended: /god-test-extension
19
+
20
+ failure-path:
21
+ on-error: /god-doctor
22
+
23
+ endoff:
24
+ state-update: tier-0 updated for /god-extension-scaffold
25
+ events: [agent.start, artifact.created, agent.end]