godpowers 2.5.2 → 2.7.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 (75) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +49 -19
  3. package/RELEASE.md +41 -29
  4. package/SKILL.md +46 -48
  5. package/agents/god-deploy-engineer.md +2 -2
  6. package/agents/god-designer.md +3 -2
  7. package/agents/god-greenfieldifier.md +2 -4
  8. package/agents/god-launch-strategist.md +4 -5
  9. package/agents/god-observability-engineer.md +5 -5
  10. package/agents/god-reconciler.md +10 -4
  11. package/agents/god-retrospective.md +1 -1
  12. package/agents/god-updater.md +5 -5
  13. package/bin/install.js +9 -1
  14. package/fixtures/gate/build-pass/.godpowers/state.json +33 -0
  15. package/lib/README.md +2 -0
  16. package/lib/artifact-map.js +15 -3
  17. package/lib/cli-dispatch.js +51 -1
  18. package/lib/context-writer.js +4 -4
  19. package/lib/gate.js +107 -9
  20. package/lib/host-capabilities.js +53 -3
  21. package/lib/installer-args.js +25 -0
  22. package/lib/mcp-info.js +93 -0
  23. package/lib/pillars.js +2 -4
  24. package/lib/recipes.js +16 -0
  25. package/lib/router.js +1 -5
  26. package/lib/source-sync.js +1 -1
  27. package/lib/state-advance.js +244 -0
  28. package/lib/state-lock.js +8 -4
  29. package/lib/state-views.js +460 -0
  30. package/lib/state.js +52 -3
  31. package/package.json +7 -2
  32. package/routing/god-audit.yaml +1 -1
  33. package/routing/god-build.yaml +1 -1
  34. package/routing/god-context.yaml +1 -1
  35. package/routing/god-deploy.yaml +3 -1
  36. package/routing/god-design.yaml +2 -2
  37. package/routing/god-launch.yaml +4 -1
  38. package/routing/god-migrate.yaml +0 -1
  39. package/routing/god-mode.yaml +1 -1
  40. package/routing/god-observe.yaml +4 -1
  41. package/routing/god-prd.yaml +1 -1
  42. package/routing/god-reconcile.yaml +2 -5
  43. package/routing/god-sync.yaml +1 -1
  44. package/routing/recipes/returning-after-break.yaml +1 -1
  45. package/schema/state.v1.json +68 -1
  46. package/skills/god-arch.md +1 -1
  47. package/skills/god-build.md +6 -4
  48. package/skills/god-deploy.md +16 -14
  49. package/skills/god-design.md +3 -3
  50. package/skills/god-fast.md +2 -2
  51. package/skills/god-feature.md +1 -1
  52. package/skills/god-harden.md +3 -3
  53. package/skills/god-hotfix.md +1 -1
  54. package/skills/god-init.md +14 -10
  55. package/skills/god-launch.md +14 -12
  56. package/skills/god-lifecycle.md +2 -1
  57. package/skills/god-mode.md +5 -4
  58. package/skills/god-next.md +2 -1
  59. package/skills/god-observe.md +15 -13
  60. package/skills/god-pause-work.md +2 -2
  61. package/skills/god-prd.md +5 -4
  62. package/skills/god-quick.md +1 -1
  63. package/skills/god-repo.md +1 -1
  64. package/skills/god-resume-work.md +5 -4
  65. package/skills/god-roadmap-update.md +1 -1
  66. package/skills/god-roadmap.md +1 -1
  67. package/skills/god-rollback.md +1 -1
  68. package/skills/god-skip.md +2 -2
  69. package/skills/god-stack.md +1 -1
  70. package/skills/god-standards.md +1 -1
  71. package/skills/god-status.md +6 -5
  72. package/skills/god-story.md +1 -1
  73. package/skills/god-sync.md +2 -2
  74. package/workflows/bluefield-arc.yaml +2 -4
  75. package/workflows/brownfield-arc.yaml +2 -4
package/lib/pillars.js CHANGED
@@ -96,8 +96,7 @@ const ARTIFACT_PILLAR_MAP = [
96
96
  { pattern: /(^|\/)stack\/DECISION\.md$/i, pillars: ['stack'] },
97
97
  { pattern: /(^|\/)roadmap\/ROADMAP\.md$/i, pillars: ['context', 'quality'] },
98
98
  { pattern: /(^|\/)build\/PLAN\.md$/i, pillars: ['quality', 'repo'] },
99
- { pattern: /(^|\/)deploy\/STATE\.md$/i, pillars: ['deploy'] },
100
- { pattern: /(^|\/)observe\/STATE\.md$/i, pillars: ['observe'] },
99
+ { pattern: /^\.godpowers\/state\.json$/i, pillars: ['context', 'deploy', 'observe'] },
101
100
  { pattern: /(^|\/)harden\/FINDINGS\.md$/i, pillars: ['security', 'auth'] },
102
101
  { pattern: /(^|\/)design\/DESIGN\.md$/i, pillars: ['ui'] },
103
102
  { pattern: /(^|\/)design\/PRODUCT\.md$/i, pillars: ['context', 'ui'] }
@@ -109,8 +108,7 @@ const GODPOWERS_ARTIFACTS = [
109
108
  '.godpowers/stack/DECISION.md',
110
109
  '.godpowers/roadmap/ROADMAP.md',
111
110
  '.godpowers/build/PLAN.md',
112
- '.godpowers/deploy/STATE.md',
113
- '.godpowers/observe/STATE.md',
111
+ '.godpowers/state.json',
114
112
  '.godpowers/harden/FINDINGS.md',
115
113
  '.godpowers/design/DESIGN.md',
116
114
  '.godpowers/design/PRODUCT.md'
package/lib/recipes.js CHANGED
@@ -112,6 +112,14 @@ function evaluateStateCondition(condition, projectRoot) {
112
112
  if (cond.startsWith('file:')) {
113
113
  return fs.existsSync(path.join(projectRoot, cond.slice(5).trim()));
114
114
  }
115
+ if (cond.startsWith('state:')) {
116
+ const m = cond.slice(6).trim().match(/^([\w.-]+)\s*==\s*(.+)$/);
117
+ if (!m) return true;
118
+ const [, dottedPath, expected] = m;
119
+ const s = state.read(projectRoot);
120
+ const actual = state.valueAtPath(s, dottedPath);
121
+ return actual === expected.trim() || actual === parseValue(expected.trim());
122
+ }
115
123
  if (cond.startsWith('lifecycle-phase ==')) {
116
124
  const expected = cond.split('==')[1].trim();
117
125
  const s = state.read(projectRoot);
@@ -131,6 +139,14 @@ function evaluateStateCondition(condition, projectRoot) {
131
139
  return true;
132
140
  }
133
141
 
142
+ function parseValue(value) {
143
+ if (value === 'true') return true;
144
+ if (value === 'false') return false;
145
+ if (value === 'null') return null;
146
+ if (/^\d+$/.test(value)) return parseInt(value, 10);
147
+ return value;
148
+ }
149
+
134
150
  /**
135
151
  * Suggest top recipes based on current project state alone (no intent text).
136
152
  */
package/lib/router.js CHANGED
@@ -119,11 +119,7 @@ function evaluateCheck(check, projectRoot) {
119
119
  if (!match) return false;
120
120
  const [, dottedPath, expected] = match;
121
121
  const s = state.read(projectRoot);
122
- if (!s) return false;
123
- const actual = dottedPath.split('.').reduce((acc, k) => {
124
- if (!acc || k === '__proto__' || k === 'constructor' || k === 'prototype') return undefined;
125
- return acc[k];
126
- }, s.tiers || s);
122
+ const actual = state.valueAtPath(s, dottedPath);
127
123
  return actual === expected || actual === parseValue(expected);
128
124
  }
129
125
 
@@ -131,7 +131,7 @@ function progressLines(projectRoot) {
131
131
  lines.push(summarizeArtifact(projectRoot, '.godpowers/arch/ARCH.md', 'Architecture'));
132
132
  lines.push(summarizeArtifact(projectRoot, '.godpowers/roadmap/ROADMAP.md', 'Roadmap'));
133
133
  lines.push(summarizeArtifact(projectRoot, '.godpowers/stack/DECISION.md', 'Stack'));
134
- lines.push(summarizeArtifact(projectRoot, '.godpowers/build/STATE.md', 'Build state'));
134
+ lines.push(summarizeArtifact(projectRoot, '.godpowers/state.json', 'Godpowers state'));
135
135
  lines.push('');
136
136
  lines.push('## Return Path');
137
137
  lines.push('');
@@ -0,0 +1,244 @@
1
+ /**
2
+ * State advance CLI mutation.
3
+ *
4
+ * Moves one tracked Godpowers step to a new status through state.json,
5
+ * cooperative locking, and generated markdown view refresh.
6
+ */
7
+
8
+ const stateStore = require('./state');
9
+ const stateLock = require('./state-lock');
10
+ const stateViews = require('./state-views');
11
+
12
+ const VALID_STATUSES = new Set([
13
+ 'pending',
14
+ 'in-flight',
15
+ 'done',
16
+ 'skipped',
17
+ 'imported',
18
+ 'failed',
19
+ 're-invoked',
20
+ 'not-required'
21
+ ]);
22
+
23
+ function statusList() {
24
+ return Array.from(VALID_STATUSES).join(', ');
25
+ }
26
+
27
+ function check(status, reason, detail = {}) {
28
+ return {
29
+ id: detail.id || reason,
30
+ status,
31
+ reason: detail.message || reason,
32
+ artifact: detail.artifact || '.godpowers/state.json'
33
+ };
34
+ }
35
+
36
+ function resultFailure(projectRoot, id, reason, opts = {}) {
37
+ return {
38
+ command: 'state advance',
39
+ verdict: 'fail',
40
+ project: projectRoot,
41
+ step: opts.step || null,
42
+ status: opts.status || null,
43
+ previousStatus: null,
44
+ updated: null,
45
+ warnings: opts.warnings || [],
46
+ checks: [check('fail', reason, { id })],
47
+ findings: [{ id, severity: 'error', artifact: '.godpowers/state.json', reason }],
48
+ summary: { updated: false, state: '.godpowers/state.json', views: [stateViews.PROGRESS_VIEW_PATH] }
49
+ };
50
+ }
51
+
52
+ function resultPass(projectRoot, target, previousStatus, status, updated, warnings, views) {
53
+ return {
54
+ command: 'state advance',
55
+ verdict: 'pass',
56
+ project: projectRoot,
57
+ step: {
58
+ tierKey: target.tierKey,
59
+ subStepKey: target.subStepKey,
60
+ tierLabel: target.tierLabel,
61
+ subStepLabel: target.subStepLabel,
62
+ ordinal: target.ordinal
63
+ },
64
+ status,
65
+ previousStatus,
66
+ updated,
67
+ warnings,
68
+ checks: [check('pass', `advanced ${target.tierKey}.${target.subStepKey} to ${status}`, { id: 'state-advanced' })],
69
+ findings: [],
70
+ summary: { updated: true, state: '.godpowers/state.json', views: views || [stateViews.PROGRESS_VIEW_PATH] }
71
+ };
72
+ }
73
+
74
+ function normalizeStep(rawStep) {
75
+ return String(rawStep || '').trim();
76
+ }
77
+
78
+ function splitStep(rawStep) {
79
+ const step = normalizeStep(rawStep);
80
+ const compound = step.match(/^(tier-\d+)[.:/](.+)$/);
81
+ if (compound) {
82
+ return { tierKey: compound[1], subStepKey: compound[2] };
83
+ }
84
+ if (/^\d+$/.test(step)) {
85
+ return { ordinal: Number(step) };
86
+ }
87
+ return { subStepKey: step };
88
+ }
89
+
90
+ function resolveStep(currentState, rawStep) {
91
+ const token = splitStep(rawStep);
92
+ const steps = stateStore.orderedSubSteps(currentState);
93
+
94
+ if (token.ordinal != null) {
95
+ return steps.find(step => step.ordinal === token.ordinal) || null;
96
+ }
97
+
98
+ if (token.tierKey) {
99
+ return steps.find(step =>
100
+ step.tierKey === token.tierKey && step.subStepKey === token.subStepKey
101
+ ) || null;
102
+ }
103
+
104
+ const matches = steps.filter(step => step.subStepKey === token.subStepKey);
105
+ if (matches.length === 1) return matches[0];
106
+ if (matches.length > 1) {
107
+ const err = new Error(`ambiguous step: ${rawStep}`);
108
+ err.code = 'AMBIGUOUS_STEP';
109
+ err.matches = matches.map(step => `${step.tierKey}.${step.subStepKey}`);
110
+ throw err;
111
+ }
112
+ return null;
113
+ }
114
+
115
+ function validateRequest(projectRoot, opts) {
116
+ if (!projectRoot) {
117
+ return resultFailure(projectRoot, 'project-required', 'state advance requires --project=<path>', opts);
118
+ }
119
+ if (!opts.step) {
120
+ return resultFailure(projectRoot, 'step-required', 'state advance requires --step=<step>', opts);
121
+ }
122
+ if (!opts.status) {
123
+ return resultFailure(projectRoot, 'status-required', 'state advance requires --status=<status>', opts);
124
+ }
125
+ if (!VALID_STATUSES.has(opts.status)) {
126
+ return resultFailure(projectRoot, 'status-invalid', `invalid status "${opts.status}"; expected one of ${statusList()}`, opts);
127
+ }
128
+ return null;
129
+ }
130
+
131
+ function advance(projectRoot, opts = {}) {
132
+ const request = {
133
+ step: normalizeStep(opts.step),
134
+ status: String(opts.status || '').trim(),
135
+ holder: opts.holder || `godpowers-state-advance:${process.pid}`,
136
+ ttlMs: opts.ttlMs
137
+ };
138
+ const invalid = validateRequest(projectRoot, request);
139
+ if (invalid) return invalid;
140
+
141
+ const initialState = stateStore.read(projectRoot);
142
+ if (!initialState) {
143
+ return resultFailure(projectRoot, 'state-missing', 'state.json not found', request);
144
+ }
145
+
146
+ let target;
147
+ try {
148
+ target = resolveStep(initialState, request.step);
149
+ } catch (e) {
150
+ if (e.code === 'AMBIGUOUS_STEP') {
151
+ return resultFailure(
152
+ projectRoot,
153
+ 'step-ambiguous',
154
+ `ambiguous step "${request.step}"; use one of ${e.matches.join(', ')}`,
155
+ request
156
+ );
157
+ }
158
+ throw e;
159
+ }
160
+ if (!target) {
161
+ return resultFailure(projectRoot, 'step-not-found', `tracked step not found: ${request.step}`, request);
162
+ }
163
+
164
+ const lockResult = stateLock.acquire(projectRoot, {
165
+ holder: request.holder,
166
+ scope: `${target.tierKey}.${target.subStepKey}`,
167
+ ttlMs: request.ttlMs
168
+ });
169
+ if (!lockResult.acquired) {
170
+ return resultFailure(
171
+ projectRoot,
172
+ 'lock-unavailable',
173
+ `state lock unavailable: held by ${lockResult.holder} on ${lockResult.scope}`,
174
+ request
175
+ );
176
+ }
177
+
178
+ const warnings = [];
179
+ try {
180
+ const currentState = stateStore.read(projectRoot);
181
+ const freshTarget = resolveStep(currentState, request.step);
182
+ if (!freshTarget) {
183
+ return resultFailure(projectRoot, 'step-not-found', `tracked step not found after lock: ${request.step}`, request);
184
+ }
185
+
186
+ const tier = currentState.tiers[freshTarget.tierKey] || {};
187
+ const current = tier[freshTarget.subStepKey] || {};
188
+ const previousStatus = current.status || 'pending';
189
+ const updated = opts.now || new Date().toISOString();
190
+ currentState.tiers[freshTarget.tierKey][freshTarget.subStepKey] = {
191
+ ...current,
192
+ status: request.status,
193
+ updated
194
+ };
195
+ stateStore.write(projectRoot, currentState, {
196
+ onStateViewWarning: warning => warnings.push(warning)
197
+ });
198
+ return resultPass(
199
+ projectRoot,
200
+ freshTarget,
201
+ previousStatus,
202
+ request.status,
203
+ updated,
204
+ warnings,
205
+ stateViews.viewPathsForState(currentState)
206
+ );
207
+ } finally {
208
+ stateLock.release(projectRoot, request.holder);
209
+ }
210
+ }
211
+
212
+ function render(result) {
213
+ const lines = [];
214
+ lines.push('Godpowers State Advance');
215
+ lines.push('');
216
+ lines.push(`Verdict: ${result.verdict}`);
217
+ if (result.verdict === 'pass') {
218
+ const step = result.step;
219
+ lines.push(`Step: ${step.tierKey}.${step.subStepKey}`);
220
+ lines.push(`Status: ${result.previousStatus} to ${result.status}`);
221
+ lines.push(`Updated: ${result.updated}`);
222
+ for (const warning of result.warnings || []) {
223
+ lines.push(`Warning: ${warning}`);
224
+ }
225
+ } else {
226
+ for (const finding of result.findings || []) {
227
+ lines.push(`Error: ${finding.reason}`);
228
+ }
229
+ }
230
+ return lines.join('\n');
231
+ }
232
+
233
+ function exitCode(result) {
234
+ return result && result.verdict === 'pass' ? 0 : 1;
235
+ }
236
+
237
+ module.exports = {
238
+ VALID_STATUSES,
239
+ advance,
240
+ render,
241
+ exitCode,
242
+ resolveStep,
243
+ statusList
244
+ };
package/lib/state-lock.js CHANGED
@@ -46,6 +46,10 @@ const state = require('./state');
46
46
 
47
47
  const DEFAULT_TTL_MS = 5 * 60 * 1000; // 5 minutes
48
48
 
49
+ function writeLockState(projectRoot, nextState) {
50
+ return state.write(projectRoot, nextState, { refreshViews: false });
51
+ }
52
+
49
53
  function nowIso(offsetMs) {
50
54
  const d = offsetMs ? new Date(Date.now() + offsetMs) : new Date();
51
55
  return d.toISOString();
@@ -102,7 +106,7 @@ function acquire(projectRoot, opts = {}) {
102
106
  (existing.scope === scope || existing.scope === 'all' || scope === 'all')) {
103
107
  existing.expires = nowIso(ttlMs);
104
108
  s.lock = existing;
105
- state.write(projectRoot, s);
109
+ writeLockState(projectRoot, s);
106
110
  return { acquired: true, lock: existing, reentrant: true };
107
111
  }
108
112
  if (scopesConflict(existing.scope || 'all', scope)) {
@@ -134,7 +138,7 @@ function acquire(projectRoot, opts = {}) {
134
138
  };
135
139
  const reclaimedFrom = existing && isStale(existing) ? existing.holder : null;
136
140
  s.lock = lock;
137
- state.write(projectRoot, s);
141
+ writeLockState(projectRoot, s);
138
142
  return {
139
143
  acquired: true,
140
144
  lock,
@@ -158,7 +162,7 @@ function release(projectRoot, holder) {
158
162
  return { released: false, reason: 'wrong-holder', heldBy: lock.holder };
159
163
  }
160
164
  s.lock = null;
161
- state.write(projectRoot, s);
165
+ writeLockState(projectRoot, s);
162
166
  return { released: true, releasedAt: nowIso() };
163
167
  }
164
168
 
@@ -176,7 +180,7 @@ function reclaim(projectRoot, holder) {
176
180
  }
177
181
  const prev = lock.holder;
178
182
  s.lock = null;
179
- state.write(projectRoot, s);
183
+ writeLockState(projectRoot, s);
180
184
  return { reclaimed: true, previousHolder: prev };
181
185
  }
182
186