godpowers 2.2.0 → 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.
Files changed (59) hide show
  1. package/CHANGELOG.md +44 -7
  2. package/INSPIRATION.md +4 -2
  3. package/README.md +25 -12
  4. package/RELEASE.md +23 -27
  5. package/SKILL.md +2 -2
  6. package/agents/god-auditor.md +1 -1
  7. package/agents/god-deps-auditor.md +11 -0
  8. package/agents/god-executor.md +22 -5
  9. package/agents/god-greenfieldifier.md +1 -1
  10. package/agents/god-orchestrator.md +2 -2
  11. package/agents/god-planner.md +14 -0
  12. package/agents/god-pm.md +1 -1
  13. package/agents/god-reconciler.md +1 -1
  14. package/agents/god-roadmapper.md +1 -1
  15. package/agents/god-spec-reviewer.md +10 -0
  16. package/agents/god-stack-selector.md +4 -0
  17. package/agents/god-updater.md +1 -1
  18. package/bin/install.js +9 -3
  19. package/fixtures/dogfood/{half-migrated-gsd → half-migrated-planning}/manifest.json +2 -2
  20. package/lib/README.md +7 -1
  21. package/lib/atomic-write.js +85 -0
  22. package/lib/checkpoint.js +2 -1
  23. package/lib/context-writer.js +1 -1
  24. package/lib/executor-repair.js +65 -0
  25. package/lib/feature-awareness.js +1 -1
  26. package/lib/fs-async.js +2 -1
  27. package/lib/install-profiles.js +154 -0
  28. package/lib/installer-args.js +12 -0
  29. package/lib/installer-core.js +43 -8
  30. package/lib/linkage.js +2 -1
  31. package/lib/package-identity.js +38 -0
  32. package/lib/package-legitimacy.js +158 -0
  33. package/lib/planning-systems.js +9 -9
  34. package/lib/repo-surface-sync.js +1 -1
  35. package/lib/requirements.js +38 -5
  36. package/lib/reverse-sync.js +12 -2
  37. package/lib/source-grounding.js +177 -0
  38. package/lib/source-sync.js +6 -5
  39. package/lib/state.js +2 -1
  40. package/package.json +1 -1
  41. package/references/HAVE-NOTS.md +1 -1
  42. package/references/orchestration/MODE-DETECTION.md +2 -2
  43. package/references/shared/ORCHESTRATORS.md +3 -3
  44. package/routing/god-design.yaml +1 -1
  45. package/routing/god-migrate.yaml +3 -3
  46. package/schema/state.v1.json +1 -1
  47. package/skills/god-build.md +17 -3
  48. package/skills/god-doctor.md +2 -2
  49. package/skills/god-dogfood.md +2 -2
  50. package/skills/god-init.md +6 -6
  51. package/skills/god-migrate.md +10 -10
  52. package/skills/god-sync.md +2 -2
  53. package/skills/god-update-deps.md +4 -2
  54. package/skills/god-version.md +1 -1
  55. package/templates/IMPORTED-CONTEXT.md +1 -1
  56. package/templates/INITIAL-FINDINGS.md +2 -2
  57. /package/fixtures/dogfood/{half-migrated-gsd → half-migrated-planning}/.planning/PROJECT.md +0 -0
  58. /package/fixtures/dogfood/{half-migrated-gsd → half-migrated-planning}/.planning/REQUIREMENTS.md +0 -0
  59. /package/fixtures/dogfood/{half-migrated-gsd → half-migrated-planning}/.planning/ROADMAP.md +0 -0
package/lib/README.md CHANGED
@@ -22,6 +22,7 @@ package-level integrations.
22
22
  | `dogfood-runner.js` | Run deterministic messy-repo scenarios against migration, host, extension, and suite release behavior. |
23
23
  | `budget.js` | Read and enforce configured budget controls. |
24
24
  | `cost-tracker.js` | Track token and cost estimates from event streams. |
25
+ | `atomic-write.js` | Write load-bearing files through temp-file validation and atomic rename. |
25
26
  | `fs-async.js` | Promise-based file read/write helpers for non-blocking runtime paths. |
26
27
 
27
28
  ## Events and observability
@@ -45,6 +46,7 @@ package-level integrations.
45
46
  | `agent-cache.js` | Cache agent metadata for faster routing. |
46
47
  | `agent-validator.js` | Validate agent frontmatter and contracts. |
47
48
  | `agent-refs.js` | Validate workflow agent references and scan skill/agent prose for phantom references. |
49
+ | `executor-repair.js` | Classify executor repair decisions as retry, decompose, prune, or escalate. |
48
50
  | `skill-surface.js` | Derive slash-command metadata from the individual `skills/` files. |
49
51
 
50
52
  ## Artifact quality
@@ -63,7 +65,7 @@ package-level integrations.
63
65
  |--------|---------|
64
66
  | `context-writer.js` | Produce tool-specific context files. |
65
67
  | `context-budget.js` | Keep generated context within budget. |
66
- | `planning-systems.js` | Detect and import GSD, BMAD, and Superpowers planning context. |
68
+ | `planning-systems.js` | Detect and import legacy planning, BMAD, and Superpowers planning context. |
67
69
  | `source-sync.js` | Write managed Godpowers progress back to source-system companion files. |
68
70
  | `design-detector.js` | Detect design-system conventions. |
69
71
  | `design-spec.js` | Normalize design specifications. |
@@ -74,6 +76,7 @@ package-level integrations.
74
76
  | `impeccable-bridge.js` | Bridge runtime checks into impeccable quality workflows. |
75
77
  | `extensions.js` | Load and validate extension packs. |
76
78
  | `extension-authoring.js` | Scaffold publishable extension packs with manifest, package, README, skill, agent, and workflow files. |
79
+ | `package-legitimacy.js` | Assess third-party package metadata for existence, typo risk, recency, and repository signals. |
77
80
  | `pillars.js` | Manage the Pillars project-context layer (`AGENTS.md` plus routed `agents/*.md`). |
78
81
 
79
82
  ## Repository and graph helpers
@@ -81,6 +84,7 @@ package-level integrations.
81
84
  | Module | Purpose |
82
85
  |--------|---------|
83
86
  | `code-scanner.js` | Scan source trees for routing and quality evidence. |
87
+ | `source-grounding.js` | Check that build plans cite existing files and symbols before execution starts. |
84
88
  | `cross-artifact-impact.js` | Detect relationships between changed artifacts. |
85
89
  | `cross-repo-linkage.js` | Track suite-level repository relationships. |
86
90
  | `drift-detector.js` | Detect context drift between artifacts and implementation. |
@@ -98,7 +102,9 @@ package-level integrations.
98
102
  | `installer-core.js` | Install and uninstall the Godpowers surface for each runtime. |
99
103
  | `installer-files.js` | File-copy helpers shared by the installer and its tests. |
100
104
  | `installer-args.js` | Parse `bin/install.js` arguments and subcommands. |
105
+ | `install-profiles.js` | Select smaller role-specific slash-command install surfaces. |
101
106
  | `installer-runtimes.js` | Map supported runtimes to their config directories. |
107
+ | `package-identity.js` | Centralize package name, version, repository, docs, and command identity. |
102
108
  | `automation-providers.js` | Detect and configure host-native automation providers. |
103
109
  | `dashboard.js` | Compute the next-step action brief and host guarantee line. |
104
110
  | `quick-proof.js` | Render the shipped proof fixture for `godpowers quick-proof`. |
@@ -0,0 +1,85 @@
1
+ const fs = require('fs');
2
+ const fsp = require('fs/promises');
3
+ const path = require('path');
4
+
5
+ function tempPathFor(filePath) {
6
+ const dir = path.dirname(filePath);
7
+ const base = path.basename(filePath);
8
+ return path.join(dir, `.${base}.${process.pid}.${Date.now()}.tmp`);
9
+ }
10
+
11
+ function validateContent(content, validate) {
12
+ if (typeof validate === 'function') validate(content);
13
+ }
14
+
15
+ function writeFileAtomic(filePath, content, opts = {}) {
16
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
17
+ const tmp = tempPathFor(filePath);
18
+ try {
19
+ fs.writeFileSync(tmp, content);
20
+ validateContent(content, opts.validateContent);
21
+ if (typeof opts.validateFile === 'function') opts.validateFile(tmp);
22
+ fs.renameSync(tmp, filePath);
23
+ return filePath;
24
+ } catch (error) {
25
+ try {
26
+ fs.rmSync(tmp, { force: true });
27
+ } catch (_) {
28
+ // Best-effort cleanup. The original file is still untouched.
29
+ }
30
+ throw error;
31
+ }
32
+ }
33
+
34
+ async function writeFileAtomicAsync(filePath, content, opts = {}) {
35
+ await fsp.mkdir(path.dirname(filePath), { recursive: true });
36
+ const tmp = tempPathFor(filePath);
37
+ try {
38
+ await fsp.writeFile(tmp, content);
39
+ validateContent(content, opts.validateContent);
40
+ if (typeof opts.validateFile === 'function') await opts.validateFile(tmp);
41
+ await fsp.rename(tmp, filePath);
42
+ return filePath;
43
+ } catch (error) {
44
+ try {
45
+ await fsp.rm(tmp, { force: true });
46
+ } catch (_) {
47
+ // Best-effort cleanup. The original file is still untouched.
48
+ }
49
+ throw error;
50
+ }
51
+ }
52
+
53
+ function jsonContent(value) {
54
+ return JSON.stringify(value, null, 2) + '\n';
55
+ }
56
+
57
+ function writeJsonAtomic(filePath, value, opts = {}) {
58
+ const content = jsonContent(value);
59
+ return writeFileAtomic(filePath, content, {
60
+ ...opts,
61
+ validateContent: text => {
62
+ JSON.parse(text);
63
+ validateContent(text, opts.validateContent);
64
+ }
65
+ });
66
+ }
67
+
68
+ async function writeJsonAtomicAsync(filePath, value, opts = {}) {
69
+ const content = jsonContent(value);
70
+ return writeFileAtomicAsync(filePath, content, {
71
+ ...opts,
72
+ validateContent: text => {
73
+ JSON.parse(text);
74
+ validateContent(text, opts.validateContent);
75
+ }
76
+ });
77
+ }
78
+
79
+ module.exports = {
80
+ tempPathFor,
81
+ writeFileAtomic,
82
+ writeFileAtomicAsync,
83
+ writeJsonAtomic,
84
+ writeJsonAtomicAsync
85
+ };
package/lib/checkpoint.js CHANGED
@@ -49,6 +49,7 @@
49
49
  const fs = require('fs');
50
50
  const path = require('path');
51
51
  const crypto = require('crypto');
52
+ const atomic = require('./atomic-write');
52
53
 
53
54
  const MAX_ACTIONS = 20;
54
55
  const MAX_FACTS = 10;
@@ -224,7 +225,7 @@ function write(projectRoot, state) {
224
225
  ''
225
226
  ].filter(line => line !== null).join('\n');
226
227
 
227
- fs.writeFileSync(file, fm + body);
228
+ atomic.writeFileAtomic(file, fm + body);
228
229
  return file;
229
230
  }
230
231
 
@@ -99,7 +99,7 @@ function buildCanonicalContent(state, opts = {}) {
99
99
  lines.push('- `/god-next` - what to run next, with reason');
100
100
  lines.push('- `/god-mode` - run the full autonomous project run');
101
101
  lines.push('- `/god-sync` - refresh artifacts, context, and source-system sync-back');
102
- lines.push('- `/god-migrate` - import or sync GSD, BMAD, or Superpowers context');
102
+ lines.push('- `/god-migrate` - import or sync legacy planning, BMAD, or Superpowers context');
103
103
  lines.push('- `/god-context refresh` - refresh AI-tool awareness for this project');
104
104
  lines.push('');
105
105
 
@@ -0,0 +1,65 @@
1
+ const STRATEGIES = Object.freeze({
2
+ RETRY: 'retry',
3
+ DECOMPOSE: 'decompose',
4
+ PRUNE: 'prune',
5
+ ESCALATE: 'escalate'
6
+ });
7
+
8
+ function classifyFailure(input = {}) {
9
+ const attempts = Number(input.attempts || 0);
10
+ const budget = Number(Object.prototype.hasOwnProperty.call(input, 'budget') ? input.budget : 2);
11
+ const error = String(input.error || '').toLowerCase();
12
+ const criteria = String(input.doneCriteria || '').toLowerCase();
13
+
14
+ if (attempts >= budget) {
15
+ return {
16
+ strategy: STRATEGIES.ESCALATE,
17
+ reason: 'repair budget exhausted'
18
+ };
19
+ }
20
+
21
+ if (/architecture|product decision|ambiguous|human/.test(error)) {
22
+ return {
23
+ strategy: STRATEGIES.ESCALATE,
24
+ reason: 'failure requires a human or architecture decision'
25
+ };
26
+ }
27
+
28
+ if (/not found|enoent|missing dependency|cannot find module|wrong path|permission|timeout|network|econn/.test(error)) {
29
+ return {
30
+ strategy: STRATEGIES.RETRY,
31
+ reason: 'failure looks environmental or mechanical'
32
+ };
33
+ }
34
+
35
+ if (/and|multiple|all of|end-to-end|full flow/.test(criteria) || /too broad|partial|only.*part/.test(error)) {
36
+ return {
37
+ strategy: STRATEGIES.DECOMPOSE,
38
+ reason: 'done criteria appears too broad for one verified step'
39
+ };
40
+ }
41
+
42
+ if (/out of scope|blocked by missing prerequisite|unsupported|cannot be implemented/.test(error)) {
43
+ return {
44
+ strategy: STRATEGIES.PRUNE,
45
+ reason: 'task appears infeasible in the current slice'
46
+ };
47
+ }
48
+
49
+ return {
50
+ strategy: STRATEGIES.RETRY,
51
+ reason: 'default to one focused retry before broader action'
52
+ };
53
+ }
54
+
55
+ function renderRepairLog(task, decision) {
56
+ const name = task || 'task';
57
+ const strategy = decision.strategy.toUpperCase();
58
+ return `[Executor Repair - ${strategy}] ${name}: ${decision.reason}`;
59
+ }
60
+
61
+ module.exports = {
62
+ STRATEGIES,
63
+ classifyFailure,
64
+ renderRepairLog
65
+ };
@@ -21,7 +21,7 @@ const FEATURES = [
21
21
  id: 'planning-system-migration',
22
22
  since: '1.6.15',
23
23
  commands: ['/god-migrate', '/god-init'],
24
- description: 'Detect and import GSD, BMAD, and Superpowers planning artifacts.'
24
+ description: 'Detect and import legacy planning, BMAD, and Superpowers planning artifacts.'
25
25
  },
26
26
  {
27
27
  id: 'source-system-sync-back',
package/lib/fs-async.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs/promises');
2
2
  const path = require('path');
3
+ const atomic = require('./atomic-write');
3
4
 
4
5
  async function exists(filePath) {
5
6
  try {
@@ -16,7 +17,7 @@ async function readJson(filePath) {
16
17
 
17
18
  async function writeJson(filePath, value) {
18
19
  await fs.mkdir(path.dirname(filePath), { recursive: true });
19
- await fs.writeFile(filePath, JSON.stringify(value, null, 2) + '\n');
20
+ await atomic.writeJsonAtomicAsync(filePath, value);
20
21
  return value;
21
22
  }
22
23
 
@@ -0,0 +1,154 @@
1
+ const COMMON = [
2
+ 'god',
3
+ 'god-help',
4
+ 'god-version',
5
+ 'god-next',
6
+ 'god-status',
7
+ 'god-progress',
8
+ 'god-doctor',
9
+ 'god-settings'
10
+ ];
11
+
12
+ const PROFILE_SKILLS = {
13
+ core: [
14
+ ...COMMON,
15
+ 'god-init',
16
+ 'god-mode',
17
+ 'god-build',
18
+ 'god-review',
19
+ 'god-sync',
20
+ 'god-quick',
21
+ 'god-fast'
22
+ ],
23
+ builder: [
24
+ ...COMMON,
25
+ 'god-init',
26
+ 'god-mode',
27
+ 'god-discuss',
28
+ 'god-explore',
29
+ 'god-list-assumptions',
30
+ 'god-prd',
31
+ 'god-design',
32
+ 'god-design-impact',
33
+ 'god-arch',
34
+ 'god-roadmap',
35
+ 'god-stack',
36
+ 'god-repo',
37
+ 'god-build',
38
+ 'god-add-tests',
39
+ 'god-feature',
40
+ 'god-story',
41
+ 'god-stories',
42
+ 'god-story-build',
43
+ 'god-story-verify',
44
+ 'god-story-close',
45
+ 'god-review',
46
+ 'god-test-runtime',
47
+ 'god-sync',
48
+ 'god-quick',
49
+ 'god-fast'
50
+ ],
51
+ maintainer: [
52
+ ...COMMON,
53
+ 'god-hygiene',
54
+ 'god-update-deps',
55
+ 'god-docs',
56
+ 'god-repair',
57
+ 'god-lint',
58
+ 'god-standards',
59
+ 'god-preflight',
60
+ 'god-audit',
61
+ 'god-agent-audit',
62
+ 'god-context',
63
+ 'god-context-scan',
64
+ 'god-locate',
65
+ 'god-scan',
66
+ 'god-link',
67
+ 'god-review-changes',
68
+ 'god-reconcile',
69
+ 'god-reconstruct',
70
+ 'god-migrate',
71
+ 'god-automation-status',
72
+ 'god-automation-setup',
73
+ 'god-extension-add',
74
+ 'god-extension-list',
75
+ 'god-extension-info',
76
+ 'god-extension-remove',
77
+ 'god-test-extension',
78
+ 'god-budget',
79
+ 'god-cost',
80
+ 'god-cache-clear',
81
+ 'god-logs',
82
+ 'god-metrics',
83
+ 'god-trace',
84
+ 'god-export-otel',
85
+ 'god-dogfood',
86
+ 'god-quick',
87
+ 'god-fast'
88
+ ],
89
+ suite: [
90
+ ...COMMON,
91
+ 'god-suite-init',
92
+ 'god-suite-status',
93
+ 'god-suite-sync',
94
+ 'god-suite-patch',
95
+ 'god-suite-release',
96
+ 'god-workstream',
97
+ 'god-pr-branch',
98
+ 'god-sync',
99
+ 'god-reconcile',
100
+ 'god-review',
101
+ 'god-quick',
102
+ 'god-fast'
103
+ ]
104
+ };
105
+
106
+ const PROFILE_DESCRIPTIONS = {
107
+ core: 'front door, status, init, build, review, sync, quick edits',
108
+ builder: 'core plus planning, design, stories, and runtime verification',
109
+ maintainer: 'core plus hygiene, deps, docs, repair, automation, and extensions',
110
+ suite: 'core plus multi-repo suite and workstream coordination',
111
+ full: 'all shipped slash commands'
112
+ };
113
+
114
+ function normalizeProfiles(value) {
115
+ if (!value) return ['full'];
116
+ const raw = String(value)
117
+ .split(',')
118
+ .map(part => part.trim().toLowerCase())
119
+ .filter(Boolean);
120
+ const profiles = raw.length > 0 ? raw : ['full'];
121
+ for (const profile of profiles) {
122
+ if (profile !== 'full' && !PROFILE_SKILLS[profile]) {
123
+ throw new Error(`Unknown install profile: ${profile}`);
124
+ }
125
+ }
126
+ if (profiles.includes('full')) return ['full'];
127
+ return [...new Set(profiles)];
128
+ }
129
+
130
+ function selectedSkillNames(profileValue, availableNames) {
131
+ const profiles = normalizeProfiles(profileValue);
132
+ if (profiles.includes('full')) return new Set(availableNames);
133
+ const selected = new Set();
134
+ for (const profile of profiles) {
135
+ for (const name of PROFILE_SKILLS[profile]) {
136
+ if (availableNames.includes(name)) selected.add(name);
137
+ }
138
+ }
139
+ return selected;
140
+ }
141
+
142
+ function describeProfiles(profileValue) {
143
+ return normalizeProfiles(profileValue)
144
+ .map(profile => `${profile}: ${PROFILE_DESCRIPTIONS[profile]}`)
145
+ .join('; ');
146
+ }
147
+
148
+ module.exports = {
149
+ PROFILE_SKILLS,
150
+ PROFILE_DESCRIPTIONS,
151
+ normalizeProfiles,
152
+ selectedSkillNames,
153
+ describeProfiles
154
+ };
@@ -29,6 +29,7 @@ function parseArgs(argv, cwd = process.cwd()) {
29
29
  all: false,
30
30
  help: false,
31
31
  uninstall: false,
32
+ profile: 'full',
32
33
  };
33
34
 
34
35
  for (let i = 0; i < args.length; i++) {
@@ -62,6 +63,15 @@ function parseArgs(argv, cwd = process.cwd()) {
62
63
  case '--all':
63
64
  opts.all = true;
64
65
  break;
66
+ case '--minimal':
67
+ opts.profile = 'core';
68
+ break;
69
+ case '--profile':
70
+ if (args[i + 1]) {
71
+ opts.profile = args[i + 1];
72
+ i++;
73
+ }
74
+ break;
65
75
  case '-h':
66
76
  case '--help':
67
77
  opts.help = true;
@@ -83,6 +93,8 @@ function parseArgs(argv, cwd = process.cwd()) {
83
93
  opts.extensionAgent = arg.slice('--agent='.length);
84
94
  } else if (arg.startsWith('--workflow=')) {
85
95
  opts.extensionWorkflow = arg.slice('--workflow='.length);
96
+ } else if (arg.startsWith('--profile=')) {
97
+ opts.profile = arg.slice('--profile='.length);
86
98
  } else if (arg.startsWith('--') && RUNTIMES[arg.slice(2)]) {
87
99
  opts.runtimes.push(arg.slice(2));
88
100
  }
@@ -3,8 +3,10 @@ const path = require('path');
3
3
 
4
4
  const { ensureDir, copyRecursive, copyRuntimeBundle } = require('./installer-files');
5
5
  const { resolveRuntime } = require('./installer-runtimes');
6
+ const { selectedSkillNames, normalizeProfiles } = require('./install-profiles');
7
+ const identity = require('./package-identity');
6
8
 
7
- const VERSION = require('../package.json').version;
9
+ const VERSION = identity.PACKAGE_VERSION;
8
10
 
9
11
  /**
10
12
  * @typedef {Object} InstallOptions
@@ -141,6 +143,15 @@ function removeSkillEntry(skillsDir, entry) {
141
143
  return false;
142
144
  }
143
145
 
146
+ function pruneGodpowersSkills(skillsDir) {
147
+ let removed = 0;
148
+ if (!fs.existsSync(skillsDir)) return removed;
149
+ for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
150
+ if (removeSkillEntry(skillsDir, entry)) removed++;
151
+ }
152
+ return removed;
153
+ }
154
+
144
155
  function installForRuntime(runtimeKey, srcDir, opts = {}) {
145
156
  const runtime = resolveRuntime(runtimeKey, opts);
146
157
  if (!runtime) {
@@ -151,7 +162,7 @@ function installForRuntime(runtimeKey, srcDir, opts = {}) {
151
162
  log(`\n Installing for \x1b[36m${runtime.name}\x1b[0m to \x1b[36m${runtime.configDir}\x1b[0m\n`);
152
163
  ensureDir(runtime.configDir);
153
164
 
154
- installSkills(srcDir, runtimeKey, runtime);
165
+ installSkills(srcDir, runtimeKey, runtime, opts);
155
166
  installAgents(srcDir, runtime);
156
167
  installMasterSkill(srcDir, runtimeKey, runtime);
157
168
  installDataDirs(srcDir, runtime);
@@ -159,18 +170,33 @@ function installForRuntime(runtimeKey, srcDir, opts = {}) {
159
170
 
160
171
  fs.writeFileSync(path.join(runtime.configDir, 'GODPOWERS_VERSION'), VERSION);
161
172
  success(`Wrote GODPOWERS_VERSION (${VERSION})`);
173
+ fs.writeFileSync(path.join(runtime.configDir, 'GODPOWERS_PROFILE'), normalizeProfiles(opts.profile).join(','));
174
+ success(`Wrote GODPOWERS_PROFILE (${normalizeProfiles(opts.profile).join(',')})`);
162
175
  return true;
163
176
  }
164
177
 
165
- function installSkills(srcDir, runtimeKey, runtime) {
178
+ function availableSkillFiles(srcDir) {
179
+ const skillsSrc = path.join(srcDir, 'skills');
180
+ if (!fs.existsSync(skillsSrc)) return [];
181
+ return fs.readdirSync(skillsSrc)
182
+ .filter(file => file.endsWith('.md'))
183
+ .sort();
184
+ }
185
+
186
+ function installSkills(srcDir, runtimeKey, runtime, opts = {}) {
166
187
  const skillsSrc = path.join(srcDir, 'skills');
167
188
  const skillsDest = path.join(runtime.configDir, runtime.skillsDir);
168
189
  if (!fs.existsSync(skillsSrc)) return;
169
190
 
170
191
  ensureDir(skillsDest);
192
+ pruneGodpowersSkills(skillsDest);
193
+ const files = availableSkillFiles(srcDir);
194
+ const names = files.map(file => path.basename(file, '.md'));
195
+ const selected = selectedSkillNames(opts.profile, names);
171
196
  let count = 0;
172
- for (const file of fs.readdirSync(skillsSrc)) {
173
- if (file.endsWith('.md')) {
197
+ for (const file of files) {
198
+ const name = path.basename(file, '.md');
199
+ if (selected.has(name)) {
174
200
  installSkillFile(path.join(skillsSrc, file), skillsDest, runtimeKey);
175
201
  count++;
176
202
  }
@@ -327,8 +353,15 @@ function uninstallHooks(runtimeKey, runtime) {
327
353
  }
328
354
 
329
355
  function countInstalledSurface(srcDir) {
356
+ return countProfileSurface(srcDir, { profile: 'full' });
357
+ }
358
+
359
+ function countProfileSurface(srcDir, opts = {}) {
360
+ const files = availableSkillFiles(srcDir);
361
+ const names = files.map(file => path.basename(file, '.md'));
362
+ const selected = selectedSkillNames(opts.profile, names);
330
363
  return {
331
- skills: fs.readdirSync(path.join(srcDir, 'skills')).filter(f => f.endsWith('.md')).length,
364
+ skills: selected.size,
332
365
  agents: fs.readdirSync(path.join(srcDir, 'agents')).filter(f => /^god-.*\.md$/.test(f)).length
333
366
  };
334
367
  }
@@ -336,10 +369,12 @@ function countInstalledSurface(srcDir) {
336
369
  module.exports = {
337
370
  installForRuntime,
338
371
  uninstallForRuntime,
339
- countInstalledSurface,
372
+ countInstalledSurface: countProfileSurface,
340
373
  installSkillFile,
341
374
  parseAgentFrontmatter,
342
375
  stripFrontmatter,
343
376
  writeCodexAgentToml,
344
- removeSkillEntry
377
+ removeSkillEntry,
378
+ pruneGodpowersSkills,
379
+ installSkills
345
380
  };
package/lib/linkage.js CHANGED
@@ -23,6 +23,7 @@
23
23
 
24
24
  const fs = require('fs');
25
25
  const path = require('path');
26
+ const atomic = require('./atomic-write');
26
27
 
27
28
  const ID_PATTERNS = {
28
29
  prd: /^P-(MUST|SHOULD|COULD)-\d+$/,
@@ -77,7 +78,7 @@ function readMap(filePath) {
77
78
  }
78
79
 
79
80
  function writeMap(filePath, data) {
80
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
81
+ atomic.writeJsonAtomic(filePath, data);
81
82
  }
82
83
 
83
84
  function linkKey(artifactId, filePath) {
@@ -0,0 +1,38 @@
1
+ const pkg = require('../package.json');
2
+
3
+ const PACKAGE_NAME = pkg.name;
4
+ const PACKAGE_VERSION = pkg.version;
5
+ const REPOSITORY_URL = pkg.repository && pkg.repository.url
6
+ ? pkg.repository.url
7
+ : 'git+https://github.com/aihxp/godpowers.git';
8
+ const HOMEPAGE_URL = pkg.homepage || 'https://github.com/aihxp/godpowers#readme';
9
+ const BUGS_URL = pkg.bugs && pkg.bugs.url
10
+ ? pkg.bugs.url
11
+ : 'https://github.com/aihxp/godpowers/issues';
12
+ const BIN_NAME = pkg.bin && Object.keys(pkg.bin)[0]
13
+ ? Object.keys(pkg.bin)[0]
14
+ : 'godpowers';
15
+
16
+ function repoSlug() {
17
+ const url = REPOSITORY_URL
18
+ .replace(/^git\+/, '')
19
+ .replace(/^https:\/\/github\.com\//, '')
20
+ .replace(/^git@github\.com:/, '')
21
+ .replace(/\.git$/, '');
22
+ return url || 'aihxp/godpowers';
23
+ }
24
+
25
+ function npxCommand(version = 'latest') {
26
+ return `npx ${PACKAGE_NAME}@${version}`;
27
+ }
28
+
29
+ module.exports = {
30
+ PACKAGE_NAME,
31
+ PACKAGE_VERSION,
32
+ REPOSITORY_URL,
33
+ HOMEPAGE_URL,
34
+ BUGS_URL,
35
+ BIN_NAME,
36
+ repoSlug,
37
+ npxCommand
38
+ };