@polymorphism-tech/morph-spec 4.2.0 → 4.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.
- package/CLAUDE.md +108 -946
- package/bin/morph-spec.js +284 -9
- package/bin/task-manager.cjs +102 -14
- package/bin/validate.js +4 -4
- package/docs/{v3.0 → next-generation}/AGENTS.md +1 -1
- package/docs/next-generation/CONTEXT-OPTIMIZATION.md +267 -0
- package/docs/next-generation/EXECUTION-FLOW.md +274 -0
- package/docs/next-generation/META-PROMPTS.md +235 -0
- package/docs/next-generation/MIGRATION-GUIDE.md +253 -0
- package/docs/next-generation/THREAD-MANAGEMENT.md +240 -0
- package/package.json +5 -5
- package/src/commands/agents/agents-fuse.js +97 -0
- package/src/commands/agents/micro-agent.js +112 -0
- package/src/commands/agents/spawn-team.js +69 -4
- package/src/commands/agents/squad-template.js +146 -0
- package/src/commands/analytics/analytics.js +176 -0
- package/src/commands/context/context-prime.js +63 -0
- package/src/commands/context/core-four.js +54 -0
- package/src/commands/mcp/mcp.js +102 -0
- package/src/commands/project/detect-agents.js +32 -2
- package/src/commands/project/detect.js +11 -1
- package/src/commands/project/doctor.js +573 -356
- package/src/commands/project/init.js +9 -2
- package/src/commands/project/update.js +13 -3
- package/src/commands/state/advance-phase.js +448 -416
- package/src/commands/state/state.js +14 -12
- package/src/commands/tasks/task.js +1 -1
- package/src/commands/templates/template-render.js +80 -1
- package/src/commands/threads/thread-template.js +103 -0
- package/src/commands/threads/threads.js +261 -0
- package/src/commands/trust/trust.js +205 -0
- package/src/{orchestrator.js → core/orchestrator.js} +8 -8
- package/src/core/state/state-manager.js +37 -17
- package/src/core/workflows/workflow-detector.js +114 -3
- package/src/lib/agents/micro-agent-factory.js +161 -0
- package/src/lib/analytics/analytics-engine.js +345 -0
- package/src/lib/checkpoints/checkpoint-hooks.js +298 -258
- package/src/lib/context/context-bundler.js +240 -0
- package/src/lib/context/context-optimizer.js +212 -0
- package/src/lib/context/context-tracker.js +273 -0
- package/src/lib/context/core-four-tracker.js +201 -0
- package/src/lib/context/mcp-optimizer.js +200 -0
- package/src/lib/detectors/index.js +1 -1
- package/src/lib/detectors/standards-generator.js +77 -17
- package/src/lib/detectors/structure-detector.js +67 -39
- package/src/lib/execution/fusion-executor.js +304 -0
- package/src/lib/execution/parallel-executor.js +270 -0
- package/src/lib/generators/context-generator.js +3 -3
- package/src/lib/generators/recap-generator.js +32 -12
- package/src/lib/hooks/hook-executor.js +169 -0
- package/src/lib/hooks/stop-hook-executor.js +286 -0
- package/src/lib/hops/hop-composer.js +221 -0
- package/src/lib/threads/thread-coordinator.js +238 -0
- package/src/lib/threads/thread-manager.js +317 -0
- package/src/lib/tracking/artifact-trail.js +202 -0
- package/src/lib/trust/trust-manager.js +269 -0
- package/src/lib/validators/design-system/design-system-validator.js +2 -2
- package/src/lib/validators/validation-runner.js +14 -30
- package/src/utils/hooks-installer.js +69 -0
- package/stacks/blazor-azure/.morph/config/agents.json +72 -3
- package/stacks/nextjs-supabase/.morph/config/agents.json +3 -3
- package/docs/llm-interaction-config.md +0 -735
- package/docs/v3.0/EXECUTION-FLOW.md +0 -1304
- package/src/commands/utils/migrate-state.js +0 -158
- package/src/commands/utils/upgrade.js +0 -346
- package/src/lib/validators/architecture-validator.js +0 -60
- package/src/lib/validators/content-validator.js +0 -164
- package/src/lib/validators/package-validator.js +0 -61
- package/src/lib/validators/ui-contrast-validator.js +0 -44
- package/stacks/blazor-azure/.claude/commands/morph-apply.md +0 -221
- package/stacks/blazor-azure/.claude/commands/morph-archive.md +0 -79
- package/stacks/blazor-azure/.claude/commands/morph-deploy.md +0 -529
- package/stacks/blazor-azure/.claude/commands/morph-infra.md +0 -209
- package/stacks/blazor-azure/.claude/commands/morph-preflight.md +0 -227
- package/stacks/blazor-azure/.claude/commands/morph-proposal.md +0 -122
- package/stacks/blazor-azure/.claude/commands/morph-status.md +0 -86
- package/stacks/blazor-azure/.claude/commands/morph-troubleshoot.md +0 -122
- package/stacks/blazor-azure/.claude/skills/level-0-meta/README.md +0 -7
- package/stacks/blazor-azure/.claude/skills/level-0-meta/code-review.md +0 -226
- package/stacks/blazor-azure/.claude/skills/level-0-meta/morph-checklist.md +0 -117
- package/stacks/blazor-azure/.claude/skills/level-0-meta/simulation-checklist.md +0 -77
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/README.md +0 -7
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/morph-replicate.md +0 -213
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-clarify.md +0 -131
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-design.md +0 -213
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-setup.md +0 -106
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-tasks.md +0 -164
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-uiux.md +0 -169
- package/stacks/blazor-azure/.claude/skills/level-2-domains/README.md +0 -14
- package/stacks/blazor-azure/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +0 -192
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/po-pm-advisor.md +0 -197
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/prompt-engineer.md +0 -189
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/seo-growth-hacker.md +0 -320
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/standards-architect.md +0 -156
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/api-designer.md +0 -59
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/dotnet-senior.md +0 -77
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ef-modeler.md +0 -58
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +0 -126
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ms-agent-expert.md +0 -45
- package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/blazor-builder.md +0 -210
- package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/nextjs-expert.md +0 -154
- package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +0 -191
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-architect.md +0 -142
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-deploy-specialist.md +0 -699
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +0 -126
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/container-specialist.md +0 -131
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +0 -119
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/asaas-financial.md +0 -130
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/azure-identity.md +0 -142
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/clerk-auth.md +0 -108
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/hangfire-orchestrator.md +0 -64
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/resend-email.md +0 -119
- package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/code-analyzer.md +0 -235
- package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/testing-specialist.md +0 -126
- package/stacks/blazor-azure/.claude/skills/level-3-technologies/README.md +0 -7
- package/stacks/blazor-azure/.claude/skills/level-4-patterns/README.md +0 -7
- package/stacks/blazor-azure/.morph/archive/.gitkeep +0 -25
- package/stacks/blazor-azure/.morph/features/.gitkeep +0 -25
- package/stacks/blazor-azure/.morph/schemas/agent.schema.json +0 -296
- package/stacks/blazor-azure/.morph/schemas/tasks.schema.json +0 -220
- package/stacks/blazor-azure/.morph/specs/.gitkeep +0 -20
- package/stacks/blazor-azure/.morph/test-infra/example.bicep +0 -59
- package/stacks/nextjs-supabase/.claude/commands/morph-apply.md +0 -221
- package/stacks/nextjs-supabase/.claude/commands/morph-archive.md +0 -79
- package/stacks/nextjs-supabase/.claude/commands/morph-deploy.md +0 -529
- package/stacks/nextjs-supabase/.claude/commands/morph-infra.md +0 -209
- package/stacks/nextjs-supabase/.claude/commands/morph-preflight.md +0 -227
- package/stacks/nextjs-supabase/.claude/commands/morph-proposal.md +0 -122
- package/stacks/nextjs-supabase/.claude/commands/morph-status.md +0 -86
- package/stacks/nextjs-supabase/.claude/commands/morph-troubleshoot.md +0 -122
- package/stacks/nextjs-supabase/.claude/settings.local.json +0 -6
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/backend/dotnet-supabase.md +0 -244
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/frontend/nextjs-supabase.md +0 -335
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/infrastructure/easypanel-deployer.md +0 -189
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/integrations/supabase-expert.md +0 -50
- /package/docs/{v3.0 → next-generation}/ANALYSIS.md +0 -0
- /package/docs/{v3.0 → next-generation}/ARCHITECTURE.md +0 -0
- /package/docs/{v3.0 → next-generation}/FEATURES.md +0 -0
- /package/docs/{v3.0 → next-generation}/README.md +0 -0
- /package/docs/{v3.0 → next-generation}/ROADMAP.md +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Artifact Trail — Debugging trail system
|
|
3
|
+
*
|
|
4
|
+
* Creates checkpoint artifact directories and saves:
|
|
5
|
+
* - Intermediate files (spec drafts, contract drafts)
|
|
6
|
+
* - Agent reasoning logs
|
|
7
|
+
* - Validator output logs
|
|
8
|
+
* - Failure artifacts on checkpoint failure
|
|
9
|
+
*
|
|
10
|
+
* Structure:
|
|
11
|
+
* .morph/features/{feature}/artifacts/
|
|
12
|
+
* checkpoint-{n}/
|
|
13
|
+
* spec-draft.md
|
|
14
|
+
* contracts-draft.cs
|
|
15
|
+
* agent-reasoning.json
|
|
16
|
+
* validator-output.json
|
|
17
|
+
* checkpoint-{n}/failures/
|
|
18
|
+
* failure-detail.json
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
|
22
|
+
import { join } from 'path';
|
|
23
|
+
|
|
24
|
+
const BASE_DIR = join(process.cwd(), '.morph/features');
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Path Helpers
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
function artifactsDir(feature) {
|
|
31
|
+
return join(BASE_DIR, feature, 'artifacts');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function checkpointDir(feature, checkpointNum) {
|
|
35
|
+
return join(artifactsDir(feature), `checkpoint-${checkpointNum}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function failuresDir(feature, checkpointNum) {
|
|
39
|
+
return join(checkpointDir(feature, checkpointNum), 'failures');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function ensureDir(dir) {
|
|
43
|
+
if (!existsSync(dir)) {
|
|
44
|
+
mkdirSync(dir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
return dir;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Checkpoint Artifacts
|
|
51
|
+
// ============================================================================
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Initialize a checkpoint artifact directory
|
|
55
|
+
* @param {string} feature - Feature name
|
|
56
|
+
* @param {number} checkpointNum - Checkpoint number
|
|
57
|
+
* @returns {string} Path to checkpoint directory
|
|
58
|
+
*/
|
|
59
|
+
export function initCheckpointArtifacts(feature, checkpointNum) {
|
|
60
|
+
const dir = checkpointDir(feature, checkpointNum);
|
|
61
|
+
ensureDir(dir);
|
|
62
|
+
|
|
63
|
+
const manifest = {
|
|
64
|
+
feature,
|
|
65
|
+
checkpointNum,
|
|
66
|
+
createdAt: new Date().toISOString(),
|
|
67
|
+
files: []
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
writeFileSync(join(dir, 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
|
71
|
+
return dir;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Save an intermediate artifact file to a checkpoint directory
|
|
76
|
+
* @param {string} feature - Feature name
|
|
77
|
+
* @param {number} checkpointNum - Checkpoint number
|
|
78
|
+
* @param {string} filename - File name (e.g., 'spec-draft.md')
|
|
79
|
+
* @param {string} content - File content
|
|
80
|
+
* @returns {string} Full path to saved file
|
|
81
|
+
*/
|
|
82
|
+
export function saveArtifact(feature, checkpointNum, filename, content) {
|
|
83
|
+
const dir = ensureDir(checkpointDir(feature, checkpointNum));
|
|
84
|
+
const filePath = join(dir, filename);
|
|
85
|
+
|
|
86
|
+
writeFileSync(filePath, content, 'utf8');
|
|
87
|
+
|
|
88
|
+
// Update manifest
|
|
89
|
+
const manifestPath = join(dir, 'manifest.json');
|
|
90
|
+
if (existsSync(manifestPath)) {
|
|
91
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
92
|
+
if (!manifest.files.includes(filename)) {
|
|
93
|
+
manifest.files.push(filename);
|
|
94
|
+
manifest.updatedAt = new Date().toISOString();
|
|
95
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return filePath;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Save agent reasoning log
|
|
104
|
+
* @param {string} feature - Feature name
|
|
105
|
+
* @param {number} checkpointNum - Checkpoint number
|
|
106
|
+
* @param {Object} reasoning - Agent reasoning data
|
|
107
|
+
*/
|
|
108
|
+
export function saveAgentReasoning(feature, checkpointNum, reasoning) {
|
|
109
|
+
const entry = {
|
|
110
|
+
timestamp: new Date().toISOString(),
|
|
111
|
+
...reasoning
|
|
112
|
+
};
|
|
113
|
+
saveArtifact(feature, checkpointNum, 'agent-reasoning.json', JSON.stringify(entry, null, 2));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Save validator output log
|
|
118
|
+
* @param {string} feature - Feature name
|
|
119
|
+
* @param {number} checkpointNum - Checkpoint number
|
|
120
|
+
* @param {Object} validatorOutput - Validator results
|
|
121
|
+
*/
|
|
122
|
+
export function saveValidatorOutput(feature, checkpointNum, validatorOutput) {
|
|
123
|
+
const entry = {
|
|
124
|
+
timestamp: new Date().toISOString(),
|
|
125
|
+
...validatorOutput
|
|
126
|
+
};
|
|
127
|
+
saveArtifact(feature, checkpointNum, 'validator-output.json', JSON.stringify(entry, null, 2));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// Failure Artifacts
|
|
132
|
+
// ============================================================================
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Save failure artifacts when a checkpoint fails
|
|
136
|
+
* @param {string} feature - Feature name
|
|
137
|
+
* @param {number} checkpointNum - Checkpoint number
|
|
138
|
+
* @param {Object} failure - Failure details
|
|
139
|
+
* @param {string} failure.reason - Failure reason
|
|
140
|
+
* @param {Array} failure.errors - Array of error objects
|
|
141
|
+
* @param {number} failure.attemptNumber - Which retry attempt (1-3)
|
|
142
|
+
*/
|
|
143
|
+
export function saveFailureArtifacts(feature, checkpointNum, failure) {
|
|
144
|
+
const dir = ensureDir(failuresDir(feature, checkpointNum));
|
|
145
|
+
const filename = `failure-attempt-${failure.attemptNumber || 1}.json`;
|
|
146
|
+
|
|
147
|
+
const entry = {
|
|
148
|
+
feature,
|
|
149
|
+
checkpointNum,
|
|
150
|
+
timestamp: new Date().toISOString(),
|
|
151
|
+
...failure
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
writeFileSync(join(dir, filename), JSON.stringify(entry, null, 2), 'utf8');
|
|
155
|
+
return join(dir, filename);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get all failure artifacts for a checkpoint
|
|
160
|
+
* @param {string} feature - Feature name
|
|
161
|
+
* @param {number} checkpointNum - Checkpoint number
|
|
162
|
+
* @returns {Array} Array of failure records
|
|
163
|
+
*/
|
|
164
|
+
export function getFailureArtifacts(feature, checkpointNum) {
|
|
165
|
+
const dir = failuresDir(feature, checkpointNum);
|
|
166
|
+
if (!existsSync(dir)) return [];
|
|
167
|
+
|
|
168
|
+
const files = readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
169
|
+
|
|
170
|
+
return files.map(f => {
|
|
171
|
+
try {
|
|
172
|
+
return JSON.parse(readFileSync(join(dir, f), 'utf8'));
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}).filter(Boolean);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* List all checkpoints for a feature
|
|
181
|
+
* @param {string} feature - Feature name
|
|
182
|
+
* @returns {Array} Array of checkpoint info objects
|
|
183
|
+
*/
|
|
184
|
+
export function listCheckpointArtifacts(feature) {
|
|
185
|
+
const dir = artifactsDir(feature);
|
|
186
|
+
if (!existsSync(dir)) return [];
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
190
|
+
.filter(d => d.isDirectory() && d.name.startsWith('checkpoint-'))
|
|
191
|
+
.map(d => {
|
|
192
|
+
const checkpointPath = join(dir, d.name);
|
|
193
|
+
const manifestPath = join(checkpointPath, 'manifest.json');
|
|
194
|
+
if (existsSync(manifestPath)) {
|
|
195
|
+
return JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
196
|
+
}
|
|
197
|
+
return { name: d.name, path: checkpointPath };
|
|
198
|
+
});
|
|
199
|
+
} catch {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trust Manager — Track-record and auto-approval logic
|
|
3
|
+
*
|
|
4
|
+
* Computes trust level from checkpoint pass rate history.
|
|
5
|
+
* Enables auto-approval gates for features with proven track records.
|
|
6
|
+
*
|
|
7
|
+
* Trust levels:
|
|
8
|
+
* low < 80% pass rate
|
|
9
|
+
* medium 80–90%
|
|
10
|
+
* high 90–95%
|
|
11
|
+
* maximum > 95%
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
|
|
17
|
+
// Default trust config (matches llm-interaction.json thresholds)
|
|
18
|
+
const TRUST_THRESHOLDS = {
|
|
19
|
+
low: 0,
|
|
20
|
+
medium: 0.80,
|
|
21
|
+
high: 0.90,
|
|
22
|
+
maximum: 0.95
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Gates that can be auto-approved by trust level
|
|
26
|
+
const AUTO_APPROVE_GATES = {
|
|
27
|
+
medium: ['design'],
|
|
28
|
+
high: ['design', 'tasks'],
|
|
29
|
+
maximum: ['design', 'tasks', 'proposal']
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Load state.json
|
|
34
|
+
* @returns {Object} State
|
|
35
|
+
*/
|
|
36
|
+
function loadState() {
|
|
37
|
+
const statePath = join(process.cwd(), '.morph/state.json');
|
|
38
|
+
if (!existsSync(statePath)) return { features: {} };
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(readFileSync(statePath, 'utf8'));
|
|
41
|
+
} catch {
|
|
42
|
+
return { features: {} };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Save state.json
|
|
48
|
+
* @param {Object} state
|
|
49
|
+
*/
|
|
50
|
+
function saveState(state) {
|
|
51
|
+
const statePath = join(process.cwd(), '.morph/state.json');
|
|
52
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Calculate trust level from checkpoint history
|
|
57
|
+
* @param {Array} checkpoints - Array of { passed: boolean, ... }
|
|
58
|
+
* @returns {{ level: string, passRate: number, total: number, passed: number }}
|
|
59
|
+
*/
|
|
60
|
+
export function calculateTrust(checkpoints = []) {
|
|
61
|
+
if (checkpoints.length === 0) {
|
|
62
|
+
return { level: 'low', passRate: 0, total: 0, passed: 0 };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const total = checkpoints.length;
|
|
66
|
+
const passed = checkpoints.filter(c => c.passed).length;
|
|
67
|
+
const passRate = passed / total;
|
|
68
|
+
|
|
69
|
+
let level = 'low';
|
|
70
|
+
if (passRate >= TRUST_THRESHOLDS.maximum) level = 'maximum';
|
|
71
|
+
else if (passRate >= TRUST_THRESHOLDS.high) level = 'high';
|
|
72
|
+
else if (passRate >= TRUST_THRESHOLDS.medium) level = 'medium';
|
|
73
|
+
|
|
74
|
+
return { level, passRate, total, passed };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get current trust config for a feature
|
|
79
|
+
* @param {string} featureName
|
|
80
|
+
* @returns {{ level: string, passRate: number, autoApprove: string[], override?: string }}
|
|
81
|
+
*/
|
|
82
|
+
export function getTrust(featureName) {
|
|
83
|
+
const state = loadState();
|
|
84
|
+
const feature = state.features?.[featureName];
|
|
85
|
+
|
|
86
|
+
if (!feature) {
|
|
87
|
+
return { level: 'low', passRate: 0, autoApprove: [], source: 'default' };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check for manual override first
|
|
91
|
+
if (feature.trustConfig?.override) {
|
|
92
|
+
return {
|
|
93
|
+
level: feature.trustConfig.override.level,
|
|
94
|
+
passRate: feature.trustConfig.passRate || 0,
|
|
95
|
+
autoApprove: AUTO_APPROVE_GATES[feature.trustConfig.override.level] || [],
|
|
96
|
+
source: 'manual',
|
|
97
|
+
overrideReason: feature.trustConfig.override.reason,
|
|
98
|
+
overrideAt: feature.trustConfig.override.setAt
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Calculate from checkpoint history
|
|
103
|
+
const checkpoints = feature.checkpoints || [];
|
|
104
|
+
const { level, passRate, total, passed } = calculateTrust(checkpoints);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
level,
|
|
108
|
+
passRate,
|
|
109
|
+
total,
|
|
110
|
+
passed,
|
|
111
|
+
autoApprove: AUTO_APPROVE_GATES[level] || [],
|
|
112
|
+
source: 'calculated'
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Manually set trust level with reason
|
|
118
|
+
* @param {string} featureName
|
|
119
|
+
* @param {string} level - 'low' | 'medium' | 'high' | 'maximum'
|
|
120
|
+
* @param {string} reason
|
|
121
|
+
*/
|
|
122
|
+
export function setTrust(featureName, level, reason) {
|
|
123
|
+
const validLevels = ['low', 'medium', 'high', 'maximum'];
|
|
124
|
+
if (!validLevels.includes(level)) {
|
|
125
|
+
throw new Error(`Invalid trust level: ${level}. Valid: ${validLevels.join(', ')}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const state = loadState();
|
|
129
|
+
if (!state.features[featureName]) {
|
|
130
|
+
throw new Error(`Feature not found: ${featureName}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!state.features[featureName].trustConfig) {
|
|
134
|
+
state.features[featureName].trustConfig = {};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
state.features[featureName].trustConfig.override = {
|
|
138
|
+
level,
|
|
139
|
+
reason,
|
|
140
|
+
setAt: new Date().toISOString(),
|
|
141
|
+
setBy: 'manual'
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
saveState(state);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
feature: featureName,
|
|
148
|
+
level,
|
|
149
|
+
autoApprove: AUTO_APPROVE_GATES[level] || [],
|
|
150
|
+
reason
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Clear manual trust override (revert to calculated)
|
|
156
|
+
* @param {string} featureName
|
|
157
|
+
*/
|
|
158
|
+
export function clearTrustOverride(featureName) {
|
|
159
|
+
const state = loadState();
|
|
160
|
+
if (state.features[featureName]?.trustConfig?.override) {
|
|
161
|
+
delete state.features[featureName].trustConfig.override;
|
|
162
|
+
saveState(state);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check if a gate should be auto-approved based on trust level
|
|
168
|
+
* @param {string} featureName
|
|
169
|
+
* @param {string} gate - 'design' | 'tasks' | 'proposal'
|
|
170
|
+
* @returns {{ autoApprove: boolean, level: string, reason: string }}
|
|
171
|
+
*/
|
|
172
|
+
export function shouldAutoApprove(featureName, gate) {
|
|
173
|
+
const trust = getTrust(featureName);
|
|
174
|
+
|
|
175
|
+
if (!trust.autoApprove.includes(gate)) {
|
|
176
|
+
return {
|
|
177
|
+
autoApprove: false,
|
|
178
|
+
level: trust.level,
|
|
179
|
+
reason: `Trust level "${trust.level}" does not auto-approve "${gate}" gate (need: ${getMinLevelForGate(gate)})`
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
autoApprove: true,
|
|
185
|
+
level: trust.level,
|
|
186
|
+
reason: `Auto-approved: ${trust.level} trust (${Math.round(trust.passRate * 100)}% pass rate, ${trust.total} checkpoints)`,
|
|
187
|
+
passRate: trust.passRate,
|
|
188
|
+
checkpointsTotal: trust.total
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get minimum trust level required for a gate
|
|
194
|
+
* @param {string} gate
|
|
195
|
+
* @returns {string}
|
|
196
|
+
*/
|
|
197
|
+
function getMinLevelForGate(gate) {
|
|
198
|
+
for (const [level, gates] of Object.entries(AUTO_APPROVE_GATES)) {
|
|
199
|
+
if (gates.includes(gate)) return level;
|
|
200
|
+
}
|
|
201
|
+
return 'maximum';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get trust history for all features
|
|
206
|
+
* @returns {Array} Feature trust summaries
|
|
207
|
+
*/
|
|
208
|
+
export function getTrustHistory() {
|
|
209
|
+
const state = loadState();
|
|
210
|
+
const features = state.features || {};
|
|
211
|
+
|
|
212
|
+
return Object.entries(features).map(([name, feature]) => {
|
|
213
|
+
const trust = getTrust(name);
|
|
214
|
+
return {
|
|
215
|
+
feature: name,
|
|
216
|
+
level: trust.level,
|
|
217
|
+
passRate: trust.passRate,
|
|
218
|
+
checkpointsTotal: trust.total || 0,
|
|
219
|
+
checkpointsPassed: trust.passed || 0,
|
|
220
|
+
autoApprove: trust.autoApprove,
|
|
221
|
+
source: trust.source,
|
|
222
|
+
phase: feature.phase,
|
|
223
|
+
status: feature.status
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Auto-calculate and update trust in state
|
|
230
|
+
* @param {string} featureName
|
|
231
|
+
* @returns {Object} Updated trust config
|
|
232
|
+
*/
|
|
233
|
+
export function autoCalculateTrust(featureName) {
|
|
234
|
+
const state = loadState();
|
|
235
|
+
const feature = state.features?.[featureName];
|
|
236
|
+
|
|
237
|
+
if (!feature) {
|
|
238
|
+
throw new Error(`Feature not found: ${featureName}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const checkpoints = feature.checkpoints || [];
|
|
242
|
+
const { level, passRate, total, passed } = calculateTrust(checkpoints);
|
|
243
|
+
|
|
244
|
+
if (!state.features[featureName].trustConfig) {
|
|
245
|
+
state.features[featureName].trustConfig = {};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
state.features[featureName].trustConfig = {
|
|
249
|
+
...state.features[featureName].trustConfig,
|
|
250
|
+
level,
|
|
251
|
+
passRate,
|
|
252
|
+
checkpointsTotal: total,
|
|
253
|
+
checkpointsPassed: passed,
|
|
254
|
+
lastCalculated: new Date().toISOString()
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
saveState(state);
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
feature: featureName,
|
|
261
|
+
level,
|
|
262
|
+
passRate,
|
|
263
|
+
total,
|
|
264
|
+
passed,
|
|
265
|
+
autoApprove: AUTO_APPROVE_GATES[level] || []
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export { TRUST_THRESHOLDS, AUTO_APPROVE_GATES };
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
11
11
|
import { join, extname } from 'path';
|
|
12
|
-
import { parseColors, parseTypography, parseSpacing } from '
|
|
13
|
-
import { detectDesignSystem } from '
|
|
12
|
+
import { parseColors, parseTypography, parseSpacing } from '../../generators/design-system-generator.js';
|
|
13
|
+
import { detectDesignSystem } from '../../detectors/design-system-detector.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Validate design system compliance
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { existsSync, readFileSync } from 'fs';
|
|
11
11
|
import { join } from 'path';
|
|
12
12
|
import chalk from 'chalk';
|
|
13
|
-
import { loadState } from '
|
|
13
|
+
import { loadState } from '../../core/state/state-manager.js';
|
|
14
14
|
import { resolveAgentsConfigPath } from '../stacks/stack-resolver.js';
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -172,63 +172,47 @@ function detectValidators(featureName, projectPath = '.') {
|
|
|
172
172
|
async function runSingleValidator(validatorId, projectPath, featureName, options) {
|
|
173
173
|
switch (validatorId) {
|
|
174
174
|
case 'architecture': {
|
|
175
|
-
const { validateArchitecture } = await import('./
|
|
175
|
+
const { validateArchitecture } = await import('./architecture/architecture-validator.js');
|
|
176
176
|
return await validateArchitecture(projectPath, options);
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
case 'packages': {
|
|
180
|
-
const { validatePackages } = await import('./
|
|
180
|
+
const { validatePackages } = await import('./packages/package-validator.js');
|
|
181
181
|
return await validatePackages(projectPath, options);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
case 'contrast': {
|
|
185
|
-
const { validateContrast } = await import('./
|
|
185
|
+
const { validateContrast } = await import('./ui/ui-contrast-validator.js');
|
|
186
186
|
return await validateContrast(projectPath, options);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
case 'blazor': {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return await validateBlazorPatterns(projectPath, options);
|
|
193
|
-
} catch {
|
|
194
|
-
return null; // Validator may not exist or have different export
|
|
195
|
-
}
|
|
190
|
+
const { validateBlazorPatterns } = await import('./blazor/blazor-validator.js');
|
|
191
|
+
return await validateBlazorPatterns(projectPath, options);
|
|
196
192
|
}
|
|
197
193
|
|
|
198
194
|
case 'blazor-concurrency': {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return await analyzeConcurrency(projectPath, options);
|
|
202
|
-
} catch {
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
195
|
+
const { analyzeConcurrency } = await import('./blazor/blazor-concurrency-analyzer.js');
|
|
196
|
+
return await analyzeConcurrency(projectPath, options);
|
|
205
197
|
}
|
|
206
198
|
|
|
207
199
|
case 'blazor-state': {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return await validateState(projectPath, options);
|
|
211
|
-
} catch {
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
200
|
+
const { validateState } = await import('./blazor/blazor-state-validator.js');
|
|
201
|
+
return await validateState(projectPath, options);
|
|
214
202
|
}
|
|
215
203
|
|
|
216
204
|
case 'css': {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return await validateCss(projectPath, options);
|
|
220
|
-
} catch {
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
205
|
+
const { validateCss } = await import('./css/css-validator.js');
|
|
206
|
+
return await validateCss(projectPath, options);
|
|
223
207
|
}
|
|
224
208
|
|
|
225
209
|
case 'contract-compliance': {
|
|
226
|
-
const { validateContracts } = await import('./
|
|
210
|
+
const { validateContracts } = await import('./contracts/contract-compliance-validator.js');
|
|
227
211
|
return await validateContracts(projectPath, featureName, options);
|
|
228
212
|
}
|
|
229
213
|
|
|
230
214
|
case 'design-system': {
|
|
231
|
-
const { validateDesignSystem } = await import('./
|
|
215
|
+
const { validateDesignSystem } = await import('./design-system/design-system-validator.js');
|
|
232
216
|
return await validateDesignSystem(projectPath, featureName, options);
|
|
233
217
|
}
|
|
234
218
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Hooks Installer
|
|
3
|
+
*
|
|
4
|
+
* Merges the MORPH-SPEC agent-teams PostToolUse hook into
|
|
5
|
+
* .claude/settings.local.json without overwriting existing config.
|
|
6
|
+
*
|
|
7
|
+
* Called by `morph-spec init` and `morph-spec update`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
|
|
14
|
+
const DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Install or update the MORPH-SPEC PostToolUse hook in .claude/settings.local.json.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} targetPath - Project root directory
|
|
20
|
+
* @returns {boolean} true if the hook was newly added, false if it was already present
|
|
21
|
+
*/
|
|
22
|
+
export async function installClaudeHooks(targetPath) {
|
|
23
|
+
const claudeDir = join(targetPath, '.claude');
|
|
24
|
+
const settingsPath = join(claudeDir, 'settings.local.json');
|
|
25
|
+
|
|
26
|
+
// Read existing settings or start empty
|
|
27
|
+
let settings = {};
|
|
28
|
+
if (existsSync(settingsPath)) {
|
|
29
|
+
try {
|
|
30
|
+
settings = JSON.parse(await readFile(settingsPath, 'utf-8'));
|
|
31
|
+
} catch {
|
|
32
|
+
// Malformed JSON — start fresh to avoid corrupting the file
|
|
33
|
+
settings = {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Ensure hooks.PostToolUse array exists
|
|
38
|
+
settings.hooks = settings.hooks || {};
|
|
39
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
|
|
40
|
+
|
|
41
|
+
// Check if dispatch.js is already registered (avoid duplicate entries)
|
|
42
|
+
const alreadyRegistered = settings.hooks.PostToolUse.some(entry =>
|
|
43
|
+
Array.isArray(entry.hooks) &&
|
|
44
|
+
entry.hooks.some(h => h.command === DISPATCH_COMMAND)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (alreadyRegistered) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Add the agent-teams dispatch hook
|
|
52
|
+
settings.hooks.PostToolUse.push({
|
|
53
|
+
matcher: 'Bash',
|
|
54
|
+
hooks: [
|
|
55
|
+
{
|
|
56
|
+
type: 'command',
|
|
57
|
+
command: DISPATCH_COMMAND
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Ensure .claude directory exists
|
|
63
|
+
if (!existsSync(claudeDir)) {
|
|
64
|
+
await mkdir(claudeDir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
68
|
+
return true;
|
|
69
|
+
}
|