@polymorphism-tech/morph-spec 7.2.0 → 7.4.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 (74) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +4 -2
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +2 -2
  8. package/framework/MORPH.md +29 -12
  9. package/framework/agents.json +132 -132
  10. package/framework/commands/morph-apply.md +1 -1
  11. package/framework/commands/morph-proposal.md +1 -1
  12. package/framework/evals/rubrics/README.md +9 -8
  13. package/framework/hooks/README.md +3 -1
  14. package/framework/hooks/claude-code/core/gate-guard.js +160 -0
  15. package/framework/hooks/claude-code/core/state-sync.js +15 -3
  16. package/framework/hooks/claude-code/core/validate-completion.js +241 -136
  17. package/framework/hooks/shared/state-reader.js +236 -221
  18. package/framework/phases.json +1 -1
  19. package/framework/skills/README.md +2 -2
  20. package/framework/skills/core/morph-apply/SKILL.md +63 -21
  21. package/framework/skills/core/morph-brainstorming/SKILL.md +5 -1
  22. package/framework/skills/core/morph-eval/SKILL.md +4 -5
  23. package/framework/skills/core/morph-implement/SKILL.md +47 -19
  24. package/framework/skills/core/morph-plan/SKILL.md +8 -2
  25. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  26. package/framework/skills/core/morph-review/SKILL.md +13 -6
  27. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  28. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  29. package/framework/standards/ai-agents/setup.md +258 -244
  30. package/framework/templates/README.md +708 -709
  31. package/framework/templates/REGISTRY.json +1 -20
  32. package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
  33. package/framework/templates/docs/proposal.md +171 -171
  34. package/framework/templates/feature/recap.md +145 -145
  35. package/package.json +1 -1
  36. package/src/commands/project/doctor.js +941 -946
  37. package/src/commands/project/init.js +517 -515
  38. package/src/commands/project/status.js +373 -376
  39. package/src/commands/state/approve.js +251 -213
  40. package/src/commands/validation/validate-feature.js +356 -287
  41. package/src/core/state/state-manager.js +910 -934
  42. package/src/dashboard/app/App.jsx +29 -31
  43. package/src/dashboard/app/components/Sidebar.jsx +44 -45
  44. package/src/dashboard/scanner/index.js +20 -21
  45. package/src/lib/context/context-builder.js +3 -3
  46. package/src/lib/generators/context-generator.js +2 -3
  47. package/src/lib/generators/mandate-generator.js +48 -92
  48. package/src/lib/generators/metadata-extractor.js +385 -386
  49. package/src/lib/generators/recap-generator.js +285 -285
  50. package/src/lib/risk-detector.js +2 -2
  51. package/src/lib/validators/content/content-validator.js +89 -286
  52. package/src/lib/validators/content/index.js +0 -1
  53. package/src/utils/hooks-installer.js +527 -516
  54. package/framework/templates/feature/tasks.md +0 -199
  55. package/framework/workflows/README.md +0 -1041
  56. package/framework/workflows/configs/fast-track.json +0 -46
  57. package/framework/workflows/configs/full.json +0 -64
  58. package/framework/workflows/configs/quick.json +0 -32
  59. package/framework/workflows/configs/standard.json +0 -57
  60. package/framework/workflows/docs/STORY-DRIVEN-DEVELOPMENT.md +0 -392
  61. package/framework/workflows/docs/design-impl.md +0 -37
  62. package/framework/workflows/docs/enforcement-pipeline.md +0 -668
  63. package/framework/workflows/docs/fast-track.md +0 -29
  64. package/framework/workflows/docs/full-morph.md +0 -69
  65. package/framework/workflows/docs/standard.md +0 -49
  66. package/framework/workflows/docs/ui-refresh.md +0 -39
  67. package/src/commands/state/index.js +0 -7
  68. package/src/core/workflows/workflow-detector.js +0 -586
  69. package/src/core/workflows/workflow-router.js +0 -204
  70. package/src/dashboard/app/pages/Workflows.jsx +0 -346
  71. package/src/dashboard/scanner/workflows-scanner.js +0 -18
  72. package/src/lib/phase-chain/phase-validator.js +0 -359
  73. package/src/lib/validators/content/cross-phase-validator.js +0 -281
  74. package/src/lib/validators/spec-validator.js +0 -312
@@ -1,213 +1,251 @@
1
- import { Command } from 'commander';
2
- import chalk from 'chalk';
3
- import { getFeature, setApprovalGate, getApprovalGate, getApprovalHistory } from '../../core/state/state-manager.js';
4
-
5
- /**
6
- * Unapprove action function - Revoke a previously approved gate
7
- */
8
- export async function unapproveCommand(featureName, gateName, options = {}) {
9
- try {
10
- if (!VALID_GATES.includes(gateName)) {
11
- console.error(chalk.red(`\n❌ Invalid gate: ${gateName}`));
12
- console.log(chalk.gray(`Valid gates: ${VALID_GATES.join(', ')}\n`));
13
- process.exit(1);
14
- }
15
-
16
- const feature = getFeature(featureName);
17
- if (!feature) {
18
- console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
19
- process.exit(1);
20
- }
21
-
22
- const currentStatus = getApprovalGate(featureName, gateName);
23
- if (!currentStatus?.approved) {
24
- console.log(chalk.yellow(`\n⚠️ Gate "${gateName}" is not currently approved — nothing to revoke\n`));
25
- return;
26
- }
27
-
28
- await setApprovalGate(featureName, gateName, false, {
29
- revokedBy: options.revoker || process.env.USER || process.env.USERNAME || 'user',
30
- revokedAt: new Date().toISOString(),
31
- reason: options.reason
32
- });
33
-
34
- console.log(chalk.yellow(`\n↩️ Gate "${gateName}" approval revoked for feature: ${featureName}`));
35
- console.log(chalk.gray(`Workflow is now blocked at the ${gateName} gate\n`));
36
-
37
- } catch (error) {
38
- console.error(chalk.red(`\n❌ Error revoking gate: ${error.message}\n`));
39
- process.exit(1);
40
- }
41
- }
42
-
43
- /**
44
- * Approval Commands - Manage approval gates for MORPH workflow
45
- *
46
- * Commands:
47
- * - approve: Mark a gate as approved
48
- * - unapprove: Revoke a gate approval
49
- * - approval-status: Show approval status for all gates
50
- */
51
-
52
- const program = new Command();
53
-
54
- /**
55
- * Valid approval gate names
56
- */
57
- const VALID_GATES = ['proposal', 'uiux', 'plan', 'review'];
58
-
59
- /**
60
- * Approve action function - Mark a gate as approved
61
- */
62
- export async function approveCommand(featureName, gateName, options = {}) {
63
- try {
64
- // Validate gate name
65
- if (!VALID_GATES.includes(gateName)) {
66
- console.error(chalk.red(`\n❌ Invalid gate: ${gateName}`));
67
- console.log(chalk.gray(`Valid gates: ${VALID_GATES.join(', ')}\n`));
68
- process.exit(1);
69
- }
70
-
71
- // Get feature state
72
- const feature = getFeature(featureName);
73
- if (!feature) {
74
- console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
75
- process.exit(1);
76
- }
77
-
78
- // Check if already approved
79
- const currentStatus = getApprovalGate(featureName, gateName);
80
- if (currentStatus?.approved) {
81
- console.log(chalk.yellow(`\n⚠️ Gate "${gateName}" is already approved`));
82
- console.log(chalk.gray(`Approved at: ${currentStatus.timestamp}`));
83
- console.log(chalk.gray(`Approved by: ${currentStatus.approvedBy || 'Unknown'}\n`));
84
- return;
85
- }
86
-
87
- // Set approval
88
- await setApprovalGate(featureName, gateName, true, {
89
- approvedBy: options.approver || process.env.USER || process.env.USERNAME || 'user',
90
- approvedAt: new Date().toISOString(),
91
- notes: options.notes
92
- });
93
-
94
- console.log(chalk.green(`\n✅ Gate "${gateName}" approved for feature: ${featureName}`));
95
- console.log(chalk.gray(`Workflow can now proceed past ${gateName} phase\n`));
96
-
97
- } catch (error) {
98
- console.error(chalk.red(`\n❌ Error approving gate: ${error.message}\n`));
99
- process.exit(1);
100
- }
101
- }
102
-
103
- /**
104
- * Approve command - Mark a gate as approved
105
- */
106
- program
107
- .command('approve <feature> <gate>')
108
- .description('Approve a phase gate to allow workflow progression')
109
- .action(approveCommand);
110
-
111
- /**
112
- * Approval status action function - Show all gates and their status
113
- */
114
- export async function approvalStatusCommand(featureName, options = {}) {
115
- try {
116
- // Get feature state
117
- const feature = getFeature(featureName);
118
- if (!feature) {
119
- console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
120
- process.exit(1);
121
- }
122
-
123
- // JSON output
124
- if (options.json) {
125
- const status = {
126
- feature: featureName,
127
- phase: feature.phase,
128
- status: feature.status,
129
- gates: {}
130
- };
131
-
132
- for (const gateName of VALID_GATES) {
133
- status.gates[gateName] = getApprovalGate(featureName, gateName) || { approved: false };
134
- }
135
-
136
- console.log(JSON.stringify(status, null, 2));
137
- return;
138
- }
139
-
140
- console.log(chalk.bold(`\n📋 Approval Status for: ${featureName}`));
141
- console.log('━'.repeat(60));
142
-
143
- // Show current phase
144
- console.log(chalk.cyan(`\nCurrent Phase: ${feature.phase || 'unknown'}`));
145
- console.log(chalk.gray(`Feature Status: ${feature.status || 'unknown'}\n`));
146
-
147
- // Show each gate
148
- console.log(chalk.bold('Approval Gates:'));
149
-
150
- for (const gateName of VALID_GATES) {
151
- const gateStatus = getApprovalGate(featureName, gateName);
152
-
153
- if (!gateStatus) {
154
- console.log(` ${chalk.gray('○')} ${gateName}: ${chalk.gray('Not yet evaluated')}`);
155
- continue;
156
- }
157
-
158
- if (gateStatus.approved) {
159
- console.log(` ${chalk.green('✓')} ${gateName}: ${chalk.green('Approved')}`);
160
- if (gateStatus.timestamp) {
161
- console.log(chalk.gray(` Approved at: ${gateStatus.timestamp}`));
162
- }
163
- if (gateStatus.approvedBy) {
164
- console.log(chalk.gray(` Approved by: ${gateStatus.approvedBy}`));
165
- }
166
- } else {
167
- console.log(` ${chalk.red('✗')} ${gateName}: ${chalk.red('Rejected / Not Approved')}`);
168
- if (gateStatus.reason) {
169
- console.log(chalk.gray(` Reason: ${gateStatus.reason}`));
170
- }
171
- if (gateStatus.timestamp) {
172
- console.log(chalk.gray(` Rejected at: ${gateStatus.timestamp}`));
173
- }
174
- }
175
- }
176
-
177
- // Show approval history if available
178
- const history = getApprovalHistory(featureName);
179
- if (history && history.length > 0) {
180
- console.log(chalk.bold('\nApproval History:'));
181
- history.slice(-5).forEach(entry => {
182
- const status = entry.approved ? chalk.green('✓ Approved') : chalk.red('✗ Rejected');
183
- console.log(` ${status} ${entry.gate} - ${entry.timestamp}`);
184
- });
185
- }
186
-
187
- console.log('\n' + '━'.repeat(60) + '\n');
188
-
189
- } catch (error) {
190
- console.error(chalk.red(`\n❌ Error showing approval status: ${error.message}\n`));
191
- process.exit(1);
192
- }
193
- }
194
-
195
- /**
196
- * Approval status command - Show all gates and their status
197
- */
198
- program
199
- .command('approval-status <feature>')
200
- .alias('status')
201
- .description('Show approval status for all gates')
202
- .action(approvalStatusCommand);
203
-
204
- // Only parse if run directly (not imported as module)
205
- if (import.meta.url === `file://${process.argv[1]}`) {
206
- if (process.argv.length > 2) {
207
- program.parse(process.argv);
208
- } else {
209
- program.help();
210
- }
211
- }
212
-
213
- export default program;
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { getFeature, setApprovalGate, getApprovalGate, getApprovalHistory } from '../../core/state/state-manager.js';
4
+
5
+ /**
6
+ * Unapprove action function - Revoke a previously approved gate
7
+ */
8
+ export async function unapproveCommand(featureName, gateName, options = {}) {
9
+ try {
10
+ if (!VALID_GATES.includes(gateName)) {
11
+ console.error(chalk.red(`\n❌ Invalid gate: ${gateName}`));
12
+ console.log(chalk.gray(`Valid gates: ${VALID_GATES.join(', ')}\n`));
13
+ process.exit(1);
14
+ }
15
+
16
+ const feature = getFeature(featureName);
17
+ if (!feature) {
18
+ console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
19
+ process.exit(1);
20
+ }
21
+
22
+ const currentStatus = getApprovalGate(featureName, gateName);
23
+ if (!currentStatus?.approved) {
24
+ console.log(chalk.yellow(`\n⚠️ Gate "${gateName}" is not currently approved — nothing to revoke\n`));
25
+ return;
26
+ }
27
+
28
+ // Same honesty rule as approveCommand: don't pretend a human revoked
29
+ // the gate when there's no interactive shell behind the invocation.
30
+ let revokedBy;
31
+ if (options.revoker) {
32
+ revokedBy = options.revoker;
33
+ } else if (options.mode === 'auto') {
34
+ revokedBy = 'auto';
35
+ } else if (process.stdin.isTTY) {
36
+ revokedBy = process.env.USER || process.env.USERNAME || 'user';
37
+ } else {
38
+ revokedBy = 'auto';
39
+ }
40
+
41
+ await setApprovalGate(featureName, gateName, false, {
42
+ revokedBy,
43
+ revokedAt: new Date().toISOString(),
44
+ reason: options.reason
45
+ });
46
+
47
+ console.log(chalk.yellow(`\n↩️ Gate "${gateName}" approval revoked for feature: ${featureName}`));
48
+ console.log(chalk.gray(`Workflow is now blocked at the ${gateName} gate\n`));
49
+
50
+ } catch (error) {
51
+ console.error(chalk.red(`\n❌ Error revoking gate: ${error.message}\n`));
52
+ process.exit(1);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Approval Commands - Manage approval gates for MORPH workflow
58
+ *
59
+ * Commands:
60
+ * - approve: Mark a gate as approved
61
+ * - unapprove: Revoke a gate approval
62
+ * - approval-status: Show approval status for all gates
63
+ */
64
+
65
+ const program = new Command();
66
+
67
+ /**
68
+ * Valid approval gate names
69
+ */
70
+ const VALID_GATES = ['proposal', 'uiux', 'plan', 'review'];
71
+
72
+ /**
73
+ * Approve action function - Mark a gate as approved
74
+ */
75
+ export async function approveCommand(featureName, gateName, options = {}) {
76
+ try {
77
+ // Validate gate name
78
+ if (!VALID_GATES.includes(gateName)) {
79
+ console.error(chalk.red(`\n❌ Invalid gate: ${gateName}`));
80
+ console.log(chalk.gray(`Valid gates: ${VALID_GATES.join(', ')}\n`));
81
+ process.exit(1);
82
+ }
83
+
84
+ // Get feature state
85
+ const feature = getFeature(featureName);
86
+ if (!feature) {
87
+ console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
88
+ process.exit(1);
89
+ }
90
+
91
+ // Check if already approved
92
+ const currentStatus = getApprovalGate(featureName, gateName);
93
+ if (currentStatus?.approved) {
94
+ console.log(chalk.yellow(`\n⚠️ Gate "${gateName}" is already approved`));
95
+ console.log(chalk.gray(`Approved at: ${currentStatus.timestamp}`));
96
+ console.log(chalk.gray(`Approved by: ${currentStatus.approvedBy || 'Unknown'}\n`));
97
+ return;
98
+ }
99
+
100
+ // Set approval.
101
+ //
102
+ // approvedBy resolution (most explicit wins):
103
+ // 1. --approver flag (CLI caller signalled who approved)
104
+ // 2. --mode auto → "auto" (intentionally non-human signature)
105
+ // 3. interactive shell (TTY on stdin) → $USER / $USERNAME (a real
106
+ // human is at the terminal; their name is the honest answer)
107
+ // 4. non-interactive (no TTY) → "auto"
108
+ //
109
+ // The TTY check exists because morph-spec is increasingly invoked
110
+ // from headless contexts (claude -p subprocesses, CI, the
111
+ // morph-e2e-tester). Stamping the host's $USERNAME on a gate that
112
+ // no human actually approved is misleading — it lies to the audit
113
+ // trail. "auto" reads as what it is: a deterministic policy gate,
114
+ // not a human signing off.
115
+ let approvedBy;
116
+ if (options.approver) {
117
+ approvedBy = options.approver;
118
+ } else if (options.mode === 'auto') {
119
+ approvedBy = 'auto';
120
+ } else if (process.stdin.isTTY) {
121
+ approvedBy = process.env.USER || process.env.USERNAME || 'user';
122
+ } else {
123
+ approvedBy = 'auto';
124
+ }
125
+
126
+ await setApprovalGate(featureName, gateName, true, {
127
+ approvedBy,
128
+ approvedAt: new Date().toISOString(),
129
+ notes: options.notes
130
+ });
131
+
132
+ console.log(chalk.green(`\n✅ Gate "${gateName}" approved for feature: ${featureName}`));
133
+ console.log(chalk.gray(`Workflow can now proceed past ${gateName} phase\n`));
134
+
135
+ } catch (error) {
136
+ console.error(chalk.red(`\n❌ Error approving gate: ${error.message}\n`));
137
+ process.exit(1);
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Approve command - Mark a gate as approved
143
+ */
144
+ program
145
+ .command('approve <feature> <gate>')
146
+ .description('Approve a phase gate to allow workflow progression')
147
+ .action(approveCommand);
148
+
149
+ /**
150
+ * Approval status action function - Show all gates and their status
151
+ */
152
+ export async function approvalStatusCommand(featureName, options = {}) {
153
+ try {
154
+ // Get feature state
155
+ const feature = getFeature(featureName);
156
+ if (!feature) {
157
+ console.error(chalk.red(`\n❌ Feature not found: ${featureName}\n`));
158
+ process.exit(1);
159
+ }
160
+
161
+ // JSON output
162
+ if (options.json) {
163
+ const status = {
164
+ feature: featureName,
165
+ phase: feature.phase,
166
+ status: feature.status,
167
+ gates: {}
168
+ };
169
+
170
+ for (const gateName of VALID_GATES) {
171
+ status.gates[gateName] = getApprovalGate(featureName, gateName) || { approved: false };
172
+ }
173
+
174
+ console.log(JSON.stringify(status, null, 2));
175
+ return;
176
+ }
177
+
178
+ console.log(chalk.bold(`\n📋 Approval Status for: ${featureName}`));
179
+ console.log('━'.repeat(60));
180
+
181
+ // Show current phase
182
+ console.log(chalk.cyan(`\nCurrent Phase: ${feature.phase || 'unknown'}`));
183
+ console.log(chalk.gray(`Feature Status: ${feature.status || 'unknown'}\n`));
184
+
185
+ // Show each gate
186
+ console.log(chalk.bold('Approval Gates:'));
187
+
188
+ for (const gateName of VALID_GATES) {
189
+ const gateStatus = getApprovalGate(featureName, gateName);
190
+
191
+ if (!gateStatus) {
192
+ console.log(` ${chalk.gray('○')} ${gateName}: ${chalk.gray('Not yet evaluated')}`);
193
+ continue;
194
+ }
195
+
196
+ if (gateStatus.approved) {
197
+ console.log(` ${chalk.green('✓')} ${gateName}: ${chalk.green('Approved')}`);
198
+ if (gateStatus.timestamp) {
199
+ console.log(chalk.gray(` Approved at: ${gateStatus.timestamp}`));
200
+ }
201
+ if (gateStatus.approvedBy) {
202
+ console.log(chalk.gray(` Approved by: ${gateStatus.approvedBy}`));
203
+ }
204
+ } else {
205
+ console.log(` ${chalk.red('✗')} ${gateName}: ${chalk.red('Rejected / Not Approved')}`);
206
+ if (gateStatus.reason) {
207
+ console.log(chalk.gray(` Reason: ${gateStatus.reason}`));
208
+ }
209
+ if (gateStatus.timestamp) {
210
+ console.log(chalk.gray(` Rejected at: ${gateStatus.timestamp}`));
211
+ }
212
+ }
213
+ }
214
+
215
+ // Show approval history if available
216
+ const history = getApprovalHistory(featureName);
217
+ if (history && history.length > 0) {
218
+ console.log(chalk.bold('\nApproval History:'));
219
+ history.slice(-5).forEach(entry => {
220
+ const status = entry.approved ? chalk.green('✓ Approved') : chalk.red('✗ Rejected');
221
+ console.log(` ${status} ${entry.gate} - ${entry.timestamp}`);
222
+ });
223
+ }
224
+
225
+ console.log('\n' + '━'.repeat(60) + '\n');
226
+
227
+ } catch (error) {
228
+ console.error(chalk.red(`\n❌ Error showing approval status: ${error.message}\n`));
229
+ process.exit(1);
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Approval status command - Show all gates and their status
235
+ */
236
+ program
237
+ .command('approval-status <feature>')
238
+ .alias('status')
239
+ .description('Show approval status for all gates')
240
+ .action(approvalStatusCommand);
241
+
242
+ // Only parse if run directly (not imported as module)
243
+ if (import.meta.url === `file://${process.argv[1]}`) {
244
+ if (process.argv.length > 2) {
245
+ program.parse(process.argv);
246
+ } else {
247
+ program.help();
248
+ }
249
+ }
250
+
251
+ export default program;