godpowers 2.6.0 → 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 (72) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +26 -22
  3. package/RELEASE.md +38 -35
  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 +6 -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 +39 -1
  18. package/lib/context-writer.js +4 -4
  19. package/lib/gate.js +107 -9
  20. package/lib/installer-args.js +24 -0
  21. package/lib/pillars.js +2 -4
  22. package/lib/recipes.js +16 -0
  23. package/lib/router.js +1 -5
  24. package/lib/source-sync.js +1 -1
  25. package/lib/state-advance.js +244 -0
  26. package/lib/state-lock.js +8 -4
  27. package/lib/state-views.js +460 -0
  28. package/lib/state.js +52 -3
  29. package/package.json +1 -1
  30. package/routing/god-audit.yaml +1 -1
  31. package/routing/god-build.yaml +1 -1
  32. package/routing/god-context.yaml +1 -1
  33. package/routing/god-deploy.yaml +3 -1
  34. package/routing/god-design.yaml +2 -2
  35. package/routing/god-launch.yaml +4 -1
  36. package/routing/god-migrate.yaml +0 -1
  37. package/routing/god-mode.yaml +1 -1
  38. package/routing/god-observe.yaml +4 -1
  39. package/routing/god-prd.yaml +1 -1
  40. package/routing/god-reconcile.yaml +2 -5
  41. package/routing/god-sync.yaml +1 -1
  42. package/routing/recipes/returning-after-break.yaml +1 -1
  43. package/schema/state.v1.json +68 -1
  44. package/skills/god-arch.md +1 -1
  45. package/skills/god-build.md +6 -4
  46. package/skills/god-deploy.md +16 -14
  47. package/skills/god-design.md +3 -3
  48. package/skills/god-fast.md +2 -2
  49. package/skills/god-feature.md +1 -1
  50. package/skills/god-harden.md +3 -3
  51. package/skills/god-hotfix.md +1 -1
  52. package/skills/god-init.md +14 -10
  53. package/skills/god-launch.md +14 -12
  54. package/skills/god-lifecycle.md +2 -1
  55. package/skills/god-mode.md +5 -4
  56. package/skills/god-observe.md +15 -13
  57. package/skills/god-pause-work.md +2 -2
  58. package/skills/god-prd.md +5 -4
  59. package/skills/god-quick.md +1 -1
  60. package/skills/god-repo.md +1 -1
  61. package/skills/god-resume-work.md +5 -4
  62. package/skills/god-roadmap-update.md +1 -1
  63. package/skills/god-roadmap.md +1 -1
  64. package/skills/god-rollback.md +1 -1
  65. package/skills/god-skip.md +2 -2
  66. package/skills/god-stack.md +1 -1
  67. package/skills/god-standards.md +1 -1
  68. package/skills/god-status.md +2 -2
  69. package/skills/god-story.md +1 -1
  70. package/skills/god-sync.md +2 -2
  71. package/workflows/bluefield-arc.yaml +2 -4
  72. package/workflows/brownfield-arc.yaml +2 -4
@@ -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