@polymorphism-tech/morph-spec 4.8.18 → 4.9.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.
- package/CLAUDE.md +98 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +15 -56
- package/bin/task-manager.js +115 -14
- package/bin/validate.js +67 -33
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +201 -203
- package/docs/QUICKSTART.md +2 -2
- package/framework/CLAUDE.md +21 -0
- package/framework/agents.json +758 -164
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +2 -2
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +155 -0
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +1 -1
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +71 -2
- package/framework/hooks/claude-code/statusline.py +76 -30
- package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
- package/framework/hooks/shared/activity-logger.js +0 -24
- package/framework/hooks/shared/phase-utils.js +3 -0
- package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
- package/framework/hooks/shared/stale-task-reset.js +57 -0
- package/framework/hooks/shared/state-reader.js +2 -2
- package/framework/hooks/shared/worktree-helpers.js +53 -0
- package/framework/phases.json +40 -8
- package/framework/skills/level-0-meta/brainstorming/SKILL.md +1 -1
- package/framework/skills/level-0-meta/code-review/SKILL.md +1 -1
- package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +163 -163
- package/framework/skills/level-0-meta/frontend-review/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/morph-init/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +4 -4
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
- package/framework/skills/level-0-meta/post-implementation/SKILL.md +59 -12
- package/framework/skills/level-0-meta/simulation-checklist/SKILL.md +1 -1
- package/framework/skills/level-0-meta/terminal-title/SKILL.md +1 -1
- package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +1 -1
- package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +6 -5
- package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +215 -189
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +251 -251
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +382 -365
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +492 -450
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +194 -190
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +270 -270
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +285 -285
- package/framework/standards/STANDARDS.json +640 -88
- package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
- package/framework/templates/REGISTRY.json +1825 -1909
- package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
- package/framework/templates/docs/onboarding.md +1 -5
- package/framework/workflows/configs/nodejs-cli.json +40 -0
- package/package.json +2 -6
- package/src/commands/agents/dispatch-agents.js +55 -4
- package/src/commands/project/doctor.js +16 -47
- package/src/commands/project/init.js +1 -1
- package/src/commands/project/status.js +2 -2
- package/src/commands/project/update.js +381 -365
- package/src/commands/project/worktree.js +154 -0
- package/src/commands/state/advance-phase.js +120 -30
- package/src/commands/state/approve.js +2 -2
- package/src/commands/state/index.js +7 -8
- package/src/commands/state/phase-runner.js +1 -1
- package/src/commands/state/state.js +61 -6
- package/src/commands/tasks/task.js +78 -99
- package/src/commands/templates/template-render.js +93 -173
- package/src/commands/trust/trust.js +26 -21
- package/src/core/paths/output-schema.js +15 -0
- package/src/core/state/state-manager.js +28 -54
- package/src/core/workflows/workflow-detector.js +9 -87
- package/src/lib/phase-chain/phase-validator.js +330 -0
- package/src/lib/stack/stack-profile.js +88 -0
- package/src/lib/tasks/task-classifier.js +16 -0
- package/src/lib/tasks/test-runner.js +77 -0
- package/src/lib/trust/trust-manager.js +32 -144
- package/src/lib/validators/spec-validator.js +58 -4
- package/src/lib/validators/validation-runner.js +23 -11
- package/src/scripts/setup-infra.js +240 -224
- package/src/utils/agents-installer.js +2 -2
- package/src/utils/banner.js +1 -1
- package/src/utils/claude-settings-manager.js +1 -1
- package/src/utils/file-copier.js +1 -0
- package/src/utils/hooks-installer.js +258 -8
- package/framework/hooks/dev/check-sync-health.js +0 -117
- package/framework/hooks/dev/guard-version-numbers.js +0 -57
- package/framework/hooks/dev/sync-standards-registry.js +0 -60
- package/framework/hooks/dev/sync-template-registry.js +0 -60
- package/framework/hooks/dev/validate-skill-format.js +0 -70
- package/framework/hooks/dev/validate-standard-format.js +0 -73
- package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
- package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
- package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
- package/framework/workflows/configs/design-impl.json +0 -49
- package/framework/workflows/configs/express.json +0 -45
- package/framework/workflows/configs/fast-track.json +0 -42
- package/framework/workflows/configs/full-morph.json +0 -79
- package/framework/workflows/configs/fusion.json +0 -39
- package/framework/workflows/configs/long-running.json +0 -33
- package/framework/workflows/configs/spec-only.json +0 -43
- package/framework/workflows/configs/ui-refresh.json +0 -49
- package/framework/workflows/configs/zero-touch.json +0 -82
- package/src/commands/project/monitor.js +0 -295
- package/src/commands/project/tutorial.js +0 -115
- package/src/commands/state/validate-phase.js +0 -238
- package/src/commands/templates/generate-contracts.js +0 -445
- package/src/core/orchestrator.js +0 -171
- package/src/core/registry/command-registry.js +0 -28
- package/src/core/registry/index.js +0 -8
- package/src/core/registry/validator-registry.js +0 -204
- package/src/core/templates/template-validator.js +0 -296
- package/src/generator/config-generator.js +0 -206
- package/src/generator/templates/config.json.template +0 -40
- package/src/generator/templates/project.md.template +0 -67
- package/src/lib/agents/micro-agent-factory.js +0 -161
- package/src/lib/analysis/complexity-analyzer.js +0 -441
- package/src/lib/analysis/index.js +0 -7
- package/src/lib/analytics/analytics-engine.js +0 -345
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
- package/src/lib/checkpoints/index.js +0 -7
- package/src/lib/context/context-bundler.js +0 -241
- package/src/lib/context/context-optimizer.js +0 -212
- package/src/lib/context/context-tracker.js +0 -273
- package/src/lib/context/core-four-tracker.js +0 -201
- package/src/lib/context/mcp-optimizer.js +0 -200
- package/src/lib/execution/fusion-executor.js +0 -304
- package/src/lib/execution/parallel-executor.js +0 -270
- package/src/lib/hooks/stop-hook-executor.js +0 -286
- package/src/lib/hops/hop-composer.js +0 -221
- package/src/lib/phase-chain/eligibility-checker.js +0 -243
- package/src/lib/threads/thread-coordinator.js +0 -238
- package/src/lib/threads/thread-manager.js +0 -317
- package/src/lib/tracking/artifact-trail.js +0 -202
- package/src/scanner/project-scanner.js +0 -242
- package/src/ui/diff-display.js +0 -91
- package/src/ui/interactive-wizard.js +0 -96
- package/src/ui/user-review.js +0 -211
- package/src/ui/wizard-questions.js +0 -188
- package/src/utils/color-utils.js +0 -70
- package/src/utils/process-handler.js +0 -97
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task classifier — identifies test/validation tasks by title.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const TEST_TASK_PATTERN = /\b(tests?|testing|specs?|coverage|e2e|unit|integration|integra[cç][aã]o|valida[cç][aã]o|cobertura|testes?)\b/i;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns true if the task title indicates a testing or validation task.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} taskTitle
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
*/
|
|
13
|
+
export function isTestTask(taskTitle) {
|
|
14
|
+
if (!taskTitle) return false;
|
|
15
|
+
return TEST_TASK_PATTERN.test(taskTitle);
|
|
16
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Runner — Resolves and executes the project's test suite.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order for test command:
|
|
5
|
+
* 1. .morph/config/config.json → project.testCommand
|
|
6
|
+
* 2. package.json present → "npm test"
|
|
7
|
+
* 3. null (no test suite detected)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
import { existsSync, readFileSync } from 'fs';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the test command for a project.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} [projectPath] - Project root directory (defaults to '.')
|
|
18
|
+
* @returns {string | null} The test command to run, or null if none found
|
|
19
|
+
*/
|
|
20
|
+
export function resolveTestCommand(projectPath = '.') {
|
|
21
|
+
// 1. Read from .morph/config/config.json
|
|
22
|
+
const configPath = join(projectPath, '.morph', 'config', 'config.json');
|
|
23
|
+
if (existsSync(configPath)) {
|
|
24
|
+
try {
|
|
25
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
26
|
+
const cmd = config?.project?.testCommand;
|
|
27
|
+
if (cmd && typeof cmd === 'string') {
|
|
28
|
+
return cmd;
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// Malformed config — fall through to detection
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Detect package.json → npm test
|
|
36
|
+
const packageJsonPath = join(projectPath, 'package.json');
|
|
37
|
+
if (existsSync(packageJsonPath)) {
|
|
38
|
+
return 'npm test';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 3. Nothing found
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run the project's test suite.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} [projectPath] - Project root directory (defaults to '.')
|
|
49
|
+
* @param {object} [opts] - Optional overrides for testing
|
|
50
|
+
* @param {Function} [opts._execSync] - Override execSync for unit testing
|
|
51
|
+
* @returns {{ passed?: boolean, output?: string, exitCode?: number, skipped?: boolean, reason?: string }}
|
|
52
|
+
*/
|
|
53
|
+
export function runTestSuite(projectPath = '.', opts = {}) {
|
|
54
|
+
const cmd = resolveTestCommand(projectPath);
|
|
55
|
+
|
|
56
|
+
if (!cmd) {
|
|
57
|
+
return { skipped: true, reason: 'No testCommand configured and no known project file detected' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const exec = opts._execSync ?? execSync;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const output = exec(cmd, {
|
|
64
|
+
cwd: projectPath,
|
|
65
|
+
timeout: 120_000,
|
|
66
|
+
encoding: 'utf8',
|
|
67
|
+
stdio: 'pipe'
|
|
68
|
+
});
|
|
69
|
+
return { passed: true, output: output ?? '', exitCode: 0 };
|
|
70
|
+
} catch (err) {
|
|
71
|
+
return {
|
|
72
|
+
passed: false,
|
|
73
|
+
output: (err.stdout || '') + (err.stderr || ''),
|
|
74
|
+
exitCode: err.status ?? 1
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -1,32 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Trust Manager —
|
|
3
|
-
*
|
|
4
|
-
* Computes trust level from checkpoint pass rate history.
|
|
5
|
-
* Enables auto-approval gates for features with proven track records.
|
|
2
|
+
* Trust Manager — Simplified 3-level trust model
|
|
6
3
|
*
|
|
7
4
|
* Trust levels:
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* manual — nothing auto-approved (default)
|
|
6
|
+
* high — auto-approve low-risk gates (design, tasks)
|
|
7
|
+
* auto — auto-approve all gates
|
|
8
|
+
*
|
|
9
|
+
* Set with: morph-spec trust set <feature> <level>
|
|
12
10
|
*/
|
|
13
11
|
|
|
14
|
-
import { readFileSync,
|
|
12
|
+
import { readFileSync, existsSync } from 'fs';
|
|
15
13
|
import { join } from 'path';
|
|
14
|
+
import { saveState } from '../../core/state/state-manager.js';
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
const TRUST_THRESHOLDS = {
|
|
19
|
-
low: 0,
|
|
20
|
-
medium: 0.80,
|
|
21
|
-
high: 0.90,
|
|
22
|
-
maximum: 0.95
|
|
23
|
-
};
|
|
16
|
+
const VALID_LEVELS = ['manual', 'high', 'auto'];
|
|
24
17
|
|
|
25
|
-
// Gates that can be auto-approved
|
|
26
|
-
const AUTO_APPROVE_GATES = {
|
|
27
|
-
|
|
18
|
+
// Gates that can be auto-approved at each trust level
|
|
19
|
+
export const AUTO_APPROVE_GATES = {
|
|
20
|
+
manual: [],
|
|
28
21
|
high: ['design', 'tasks'],
|
|
29
|
-
|
|
22
|
+
auto: ['design', 'tasks', 'proposal', 'uiux']
|
|
30
23
|
};
|
|
31
24
|
|
|
32
25
|
/**
|
|
@@ -43,86 +36,41 @@ function loadState() {
|
|
|
43
36
|
}
|
|
44
37
|
}
|
|
45
38
|
|
|
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
39
|
/**
|
|
78
40
|
* Get current trust config for a feature
|
|
79
41
|
* @param {string} featureName
|
|
80
|
-
* @returns {{ level: string,
|
|
42
|
+
* @returns {{ level: string, autoApprove: string[], source: string, overrideReason?: string }}
|
|
81
43
|
*/
|
|
82
44
|
export function getTrust(featureName) {
|
|
83
45
|
const state = loadState();
|
|
84
46
|
const feature = state.features?.[featureName];
|
|
85
47
|
|
|
86
48
|
if (!feature) {
|
|
87
|
-
return { level: '
|
|
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
|
-
};
|
|
49
|
+
return { level: 'manual', autoApprove: [], source: 'default' };
|
|
100
50
|
}
|
|
101
51
|
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
const
|
|
52
|
+
const override = feature.trustConfig?.override;
|
|
53
|
+
const level = VALID_LEVELS.includes(override?.level) ? override.level : 'manual';
|
|
54
|
+
const source = override ? 'manual' : 'default';
|
|
105
55
|
|
|
106
56
|
return {
|
|
107
57
|
level,
|
|
108
|
-
passRate,
|
|
109
|
-
total,
|
|
110
|
-
passed,
|
|
111
58
|
autoApprove: AUTO_APPROVE_GATES[level] || [],
|
|
112
|
-
source
|
|
59
|
+
source,
|
|
60
|
+
overrideReason: override?.reason,
|
|
61
|
+
overrideAt: override?.setAt
|
|
113
62
|
};
|
|
114
63
|
}
|
|
115
64
|
|
|
116
65
|
/**
|
|
117
66
|
* Manually set trust level with reason
|
|
118
67
|
* @param {string} featureName
|
|
119
|
-
* @param {string} level - '
|
|
120
|
-
* @param {string} reason
|
|
68
|
+
* @param {string} level - 'manual' | 'high' | 'auto'
|
|
69
|
+
* @param {string} [reason]
|
|
121
70
|
*/
|
|
122
71
|
export function setTrust(featureName, level, reason) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
throw new Error(`Invalid trust level: ${level}. Valid: ${validLevels.join(', ')}`);
|
|
72
|
+
if (!VALID_LEVELS.includes(level)) {
|
|
73
|
+
throw new Error(`Invalid trust level: ${level}. Valid: ${VALID_LEVELS.join(', ')}`);
|
|
126
74
|
}
|
|
127
75
|
|
|
128
76
|
const state = loadState();
|
|
@@ -136,7 +84,7 @@ export function setTrust(featureName, level, reason) {
|
|
|
136
84
|
|
|
137
85
|
state.features[featureName].trustConfig.override = {
|
|
138
86
|
level,
|
|
139
|
-
reason
|
|
87
|
+
reason: reason || `Manual override to ${level}`,
|
|
140
88
|
setAt: new Date().toISOString(),
|
|
141
89
|
setBy: 'manual'
|
|
142
90
|
};
|
|
@@ -147,12 +95,12 @@ export function setTrust(featureName, level, reason) {
|
|
|
147
95
|
feature: featureName,
|
|
148
96
|
level,
|
|
149
97
|
autoApprove: AUTO_APPROVE_GATES[level] || [],
|
|
150
|
-
reason
|
|
98
|
+
reason: state.features[featureName].trustConfig.override.reason
|
|
151
99
|
};
|
|
152
100
|
}
|
|
153
101
|
|
|
154
102
|
/**
|
|
155
|
-
* Clear manual trust override (
|
|
103
|
+
* Clear manual trust override (reverts to default: manual)
|
|
156
104
|
* @param {string} featureName
|
|
157
105
|
*/
|
|
158
106
|
export function clearTrustOverride(featureName) {
|
|
@@ -166,7 +114,7 @@ export function clearTrustOverride(featureName) {
|
|
|
166
114
|
/**
|
|
167
115
|
* Check if a gate should be auto-approved based on trust level
|
|
168
116
|
* @param {string} featureName
|
|
169
|
-
* @param {string} gate - 'design' | 'tasks' | 'proposal'
|
|
117
|
+
* @param {string} gate - 'design' | 'tasks' | 'proposal' | 'uiux'
|
|
170
118
|
* @returns {{ autoApprove: boolean, level: string, reason: string }}
|
|
171
119
|
*/
|
|
172
120
|
export function shouldAutoApprove(featureName, gate) {
|
|
@@ -176,33 +124,19 @@ export function shouldAutoApprove(featureName, gate) {
|
|
|
176
124
|
return {
|
|
177
125
|
autoApprove: false,
|
|
178
126
|
level: trust.level,
|
|
179
|
-
reason: `Trust level "${trust.level}" does not auto-approve "${gate}" gate
|
|
127
|
+
reason: `Trust level "${trust.level}" does not auto-approve "${gate}" gate`
|
|
180
128
|
};
|
|
181
129
|
}
|
|
182
130
|
|
|
183
131
|
return {
|
|
184
132
|
autoApprove: true,
|
|
185
133
|
level: trust.level,
|
|
186
|
-
reason: `Auto-approved:
|
|
187
|
-
passRate: trust.passRate,
|
|
188
|
-
checkpointsTotal: trust.total
|
|
134
|
+
reason: `Auto-approved: trust level "${trust.level}" includes gate "${gate}"`
|
|
189
135
|
};
|
|
190
136
|
}
|
|
191
137
|
|
|
192
138
|
/**
|
|
193
|
-
* Get
|
|
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
|
|
139
|
+
* Get trust info for all features
|
|
206
140
|
* @returns {Array} Feature trust summaries
|
|
207
141
|
*/
|
|
208
142
|
export function getTrustHistory() {
|
|
@@ -214,9 +148,6 @@ export function getTrustHistory() {
|
|
|
214
148
|
return {
|
|
215
149
|
feature: name,
|
|
216
150
|
level: trust.level,
|
|
217
|
-
passRate: trust.passRate,
|
|
218
|
-
checkpointsTotal: trust.total || 0,
|
|
219
|
-
checkpointsPassed: trust.passed || 0,
|
|
220
151
|
autoApprove: trust.autoApprove,
|
|
221
152
|
source: trust.source,
|
|
222
153
|
phase: feature.phase,
|
|
@@ -224,46 +155,3 @@ export function getTrustHistory() {
|
|
|
224
155
|
};
|
|
225
156
|
});
|
|
226
157
|
}
|
|
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,7 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { readFileSync, existsSync } from 'fs';
|
|
11
11
|
import { join } from 'path';
|
|
12
|
-
import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
12
|
+
import { getAbsoluteOutputPath, resolveContractsOutputType } from '../../core/paths/output-schema.js';
|
|
13
|
+
import { getStackProfile } from '../stack/stack-profile.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Validate spec.md and contracts.cs at design time
|
|
@@ -20,8 +21,11 @@ import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
|
20
21
|
* @returns {Object} { status, errors, warnings, issues }
|
|
21
22
|
*/
|
|
22
23
|
export async function validateSpec(projectPath, featureName, options = {}) {
|
|
24
|
+
const { stack, isNextjs } = getStackProfile(projectPath);
|
|
25
|
+
const contractsType = resolveContractsOutputType(stack);
|
|
26
|
+
|
|
23
27
|
const specPath = getAbsoluteOutputPath(projectPath, featureName, 'spec');
|
|
24
|
-
const contractsPath = getAbsoluteOutputPath(projectPath, featureName,
|
|
28
|
+
const contractsPath = getAbsoluteOutputPath(projectPath, featureName, contractsType);
|
|
25
29
|
|
|
26
30
|
const issues = [];
|
|
27
31
|
|
|
@@ -32,10 +36,12 @@ export async function validateSpec(projectPath, featureName, options = {}) {
|
|
|
32
36
|
issues.push(...specIssues);
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
// Validate contracts
|
|
39
|
+
// Validate contracts file (stack-aware)
|
|
36
40
|
if (existsSync(contractsPath)) {
|
|
37
41
|
const contractsContent = readFileSync(contractsPath, 'utf8');
|
|
38
|
-
const contractsIssues =
|
|
42
|
+
const contractsIssues = isNextjs
|
|
43
|
+
? validateContractsTs(contractsContent)
|
|
44
|
+
: validateContractsCs(contractsContent);
|
|
39
45
|
issues.push(...contractsIssues);
|
|
40
46
|
}
|
|
41
47
|
|
|
@@ -240,6 +246,54 @@ function validateContractsCs(content) {
|
|
|
240
246
|
return issues;
|
|
241
247
|
}
|
|
242
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Validate contracts.ts structure for Next.js projects.
|
|
251
|
+
* Checks: has exports, has Zod schemas, no C# syntax.
|
|
252
|
+
*/
|
|
253
|
+
function validateContractsTs(content) {
|
|
254
|
+
const issues = [];
|
|
255
|
+
|
|
256
|
+
// Must have at least one export
|
|
257
|
+
if (!/^export\s+(interface|type|const|function)/m.test(content)) {
|
|
258
|
+
issues.push({
|
|
259
|
+
level: 'error',
|
|
260
|
+
type: 'contracts',
|
|
261
|
+
message: 'contracts.ts has no exported types, interfaces, or schemas',
|
|
262
|
+
solution: 'Add at least one export interface or export type'
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Should have at least one Zod schema (warning only)
|
|
267
|
+
if (!content.includes('z.object(')) {
|
|
268
|
+
issues.push({
|
|
269
|
+
level: 'warning',
|
|
270
|
+
type: 'contracts',
|
|
271
|
+
message: 'contracts.ts has no Zod schemas (z.object)',
|
|
272
|
+
solution: "Add Zod schemas for runtime validation. Import from 'zod'."
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Must NOT contain C# syntax
|
|
277
|
+
const csharpPatterns = [
|
|
278
|
+
{ re: /^namespace\s+/m, name: 'namespace declaration' },
|
|
279
|
+
{ re: /^using\s+System/m, name: 'C# using directive' },
|
|
280
|
+
{ re: /\bpublic\s+record\b/m, name: 'C# record type' },
|
|
281
|
+
{ re: /\bpublic\s+class\b/m, name: 'C# class declaration' },
|
|
282
|
+
];
|
|
283
|
+
for (const { re, name } of csharpPatterns) {
|
|
284
|
+
if (re.test(content)) {
|
|
285
|
+
issues.push({
|
|
286
|
+
level: 'error',
|
|
287
|
+
type: 'contracts',
|
|
288
|
+
message: `contracts.ts contains C# syntax (${name})`,
|
|
289
|
+
solution: 'This file was generated for .NET — regenerate contracts using the phase-design skill for a Next.js project'
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return issues;
|
|
295
|
+
}
|
|
296
|
+
|
|
243
297
|
/**
|
|
244
298
|
* Check if a string is PascalCase
|
|
245
299
|
*/
|
|
@@ -11,7 +11,14 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
11
11
|
import { join } from 'path';
|
|
12
12
|
import chalk from 'chalk';
|
|
13
13
|
import { loadState } from '../../core/state/state-manager.js';
|
|
14
|
-
import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
14
|
+
import { getAbsoluteOutputPath, resolveContractsOutputType } from '../../core/paths/output-schema.js';
|
|
15
|
+
import { getStackProfile } from '../stack/stack-profile.js';
|
|
16
|
+
|
|
17
|
+
/** Validators that only make sense for .NET/Blazor projects. */
|
|
18
|
+
const DOTNET_ONLY_VALIDATORS = new Set([
|
|
19
|
+
'architecture', 'packages', 'blazor', 'blazor-concurrency',
|
|
20
|
+
'blazor-state', 'contract-compliance'
|
|
21
|
+
]);
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
24
|
* Load agent → validators map from agents.json (data-driven)
|
|
@@ -92,9 +99,11 @@ export async function runValidation(projectPath, featureName, options = {}) {
|
|
|
92
99
|
console.log(chalk.gray(` (+ ${constraints.enabledValidators.length} from decisions)`));
|
|
93
100
|
}
|
|
94
101
|
|
|
95
|
-
// Always run contract compliance if contracts
|
|
96
|
-
const
|
|
97
|
-
|
|
102
|
+
// Always run contract compliance if the contracts file exists (stack-aware)
|
|
103
|
+
const _profileForContracts = getStackProfile(projectPath);
|
|
104
|
+
const contractsType = resolveContractsOutputType(_profileForContracts.stack);
|
|
105
|
+
const contractsPath = getAbsoluteOutputPath(projectPath, featureName, contractsType);
|
|
106
|
+
if (existsSync(contractsPath) && !_profileForContracts.isNextjs) {
|
|
98
107
|
validatorIds.push('contract-compliance');
|
|
99
108
|
}
|
|
100
109
|
|
|
@@ -152,26 +161,29 @@ export async function runValidation(projectPath, featureName, options = {}) {
|
|
|
152
161
|
* @returns {string[]} Array of validator IDs to run
|
|
153
162
|
*/
|
|
154
163
|
function detectValidators(featureName, projectPath = '.') {
|
|
164
|
+
const profile = getStackProfile(projectPath);
|
|
155
165
|
const state = loadState(false);
|
|
166
|
+
|
|
156
167
|
if (!state || !state.features[featureName]) {
|
|
157
|
-
return [
|
|
168
|
+
return [...profile.defaultValidators]; // stack-aware defaults
|
|
158
169
|
}
|
|
159
170
|
|
|
160
171
|
const feature = state.features[featureName];
|
|
161
172
|
const agents = feature.activeAgents || [];
|
|
162
|
-
const validatorSet = new Set();
|
|
173
|
+
const validatorSet = new Set(profile.defaultValidators); // stack defaults first
|
|
163
174
|
|
|
164
|
-
// Load agent → validators map from agents.json (data-driven)
|
|
165
175
|
const agentValidatorMap = loadAgentValidatorMap(projectPath);
|
|
166
|
-
|
|
167
176
|
for (const agentId of agents) {
|
|
168
177
|
const validators = agentValidatorMap[agentId] || [];
|
|
169
178
|
validators.forEach(v => validatorSet.add(v));
|
|
170
179
|
}
|
|
171
180
|
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
181
|
+
// Filter out .NET-only validators for non-.NET projects
|
|
182
|
+
if (!profile.isDotnet) {
|
|
183
|
+
for (const v of DOTNET_ONLY_VALIDATORS) {
|
|
184
|
+
validatorSet.delete(v);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
175
187
|
|
|
176
188
|
return Array.from(validatorSet);
|
|
177
189
|
}
|