@polymorphism-tech/morph-spec 4.22.0 → 6.0.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 +57 -71
- package/README.md +2 -2
- package/bin/morph-spec.js +224 -491
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +55 -38
- package/framework/MORPH.md +244 -0
- package/framework/agents.json +127 -1198
- package/framework/commands/morph-apply.md +56 -240
- package/framework/commands/morph-archive.md +48 -85
- package/framework/commands/morph-preflight.md +7 -7
- package/framework/commands/morph-proposal.md +35 -191
- package/framework/commands/morph-status.md +19 -50
- package/framework/commands/morph-troubleshoot.md +2 -2
- package/framework/evals/rubrics/README.md +44 -0
- package/framework/evals/rubrics/architecture.md +46 -0
- package/framework/evals/rubrics/code-quality.md +50 -0
- package/framework/evals/rubrics/contracts.md +47 -0
- package/framework/evals/rubrics/test-coverage.md +50 -0
- package/framework/evals/scenarios/dotnet-crud.yaml +42 -0
- package/framework/evals/scenarios/nextjs-auth.yaml +38 -0
- package/framework/evals/suites/standard.yaml +5 -0
- package/framework/evals/trace-schema.md +67 -0
- package/framework/hooks/claude-code/core/README.md +3 -0
- package/framework/hooks/claude-code/core/denylist.json +14 -0
- package/framework/hooks/claude-code/{pre-tool-use → core}/enforce-phase-writes.js +5 -5
- package/framework/hooks/claude-code/core/post-edit-typecheck.js +69 -0
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +29 -0
- package/framework/hooks/claude-code/{pre-tool-use → core}/protect-spec-files.js +4 -6
- package/framework/hooks/claude-code/{stop → core}/validate-completion.js +13 -82
- package/framework/hooks/shared/phase-utils.js +20 -31
- package/framework/hooks/shared/state-reader.js +4 -5
- package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -243
- package/framework/phases.json +41 -115
- package/framework/rubrics/eval-rubric.json +66 -0
- package/framework/skills/README.md +39 -64
- package/framework/skills/core/README.md +3 -0
- package/framework/skills/core/morph-brainstorming/SKILL.md +102 -0
- package/framework/skills/core/morph-eval/SKILL.md +57 -0
- package/framework/skills/core/morph-implement/SKILL.md +83 -0
- package/framework/skills/core/morph-plan/SKILL.md +64 -0
- package/framework/skills/core/morph-review/SKILL.md +62 -0
- package/framework/skills/core/morph-stack-scan/SKILL.md +109 -0
- package/framework/skills/core/morph-standards/SKILL.md +49 -0
- package/framework/templates/CLAUDE.md.core.guide.md +29 -0
- package/framework/templates/CLAUDE.md.core.template +22 -0
- package/framework/templates/REGISTRY.json +4 -4
- package/framework/workflows/configs/fast-track.json +5 -10
- package/framework/workflows/configs/full.json +4 -8
- package/framework/workflows/configs/quick.json +32 -0
- package/framework/workflows/configs/standard.json +57 -64
- package/package.json +7 -6
- package/src/commands/mcp/mcp-setup.js +58 -31
- package/src/commands/project/doctor.js +69 -70
- package/src/commands/project/init.js +26 -5
- package/src/commands/project/status.js +18 -19
- package/src/commands/state/approve.js +1 -1
- package/src/commands/validation/validate-feature.js +18 -112
- package/src/core/paths/output-schema.js +133 -152
- package/src/core/state/phase-state-machine.js +10 -24
- package/src/core/state/state-manager.js +17 -17
- package/src/core/workflows/workflow-detector.js +214 -31
- package/src/core/workflows/workflow-router.js +204 -0
- package/src/dashboard/app/pages/AgentMap.jsx +32 -33
- package/src/dashboard/app/pages/LiveState.jsx +1 -1
- package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
- package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
- package/src/dashboard/app/pages/Workflows.jsx +2 -2
- package/src/dashboard/scanner/agents-scanner.js +30 -35
- package/src/lib/agents/metrics-aggregator.js +1 -1
- package/src/lib/agents/output-collector.js +7 -9
- package/src/lib/context/context-builder.js +289 -0
- package/src/lib/detectors/claude-config-detector.js +19 -3
- package/src/lib/generators/context-generator.js +20 -42
- package/src/lib/generators/index.js +1 -0
- package/src/lib/generators/mandate-generator.js +512 -0
- package/src/lib/generators/metadata-extractor.js +1 -1
- package/src/lib/generators/recap-generator.js +3 -4
- package/src/lib/installers/mcp-installer.js +300 -92
- package/src/lib/phase-chain/phase-validator.js +36 -40
- package/src/lib/stack/stack-profile.js +74 -91
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/tasks/wave-resolver.js +353 -0
- package/src/lib/validators/checkpoint-persistence.js +2 -2
- package/src/lib/validators/content/cross-phase-validator.js +281 -0
- package/src/lib/validators/content/index.js +1 -0
- package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
- package/src/scripts/global-install.js +2 -27
- package/src/utils/agents-installer.js +224 -447
- package/src/utils/claude-settings-manager.js +57 -15
- package/src/utils/file-copier.js +1 -0
- package/src/utils/hooks-installer.js +15 -117
- package/src/utils/skills-installer.js +12 -18
- package/bin/detect-agents.js +0 -226
- package/bin/task-manager.js +0 -1036
- package/framework/agents/README.md +0 -13
- package/framework/agents/ai-agents/ai-system-architect.md +0 -194
- package/framework/agents/ai-agents/vector-search-expert.md +0 -247
- package/framework/agents/architecture/architecture-expert.md +0 -98
- package/framework/agents/architecture/morph-spec-architect.md +0 -66
- package/framework/agents/architecture/packages-validator.md +0 -89
- package/framework/agents/architecture/security-expert.md +0 -79
- package/framework/agents/architecture/seo-growth-hacker.md +0 -168
- package/framework/agents/architecture/standards-architect.md +0 -153
- package/framework/agents/backend/dotnet-senior.md +0 -163
- package/framework/agents/backend/ef-modeler.md +0 -119
- package/framework/agents/backend/hangfire-orchestrator.md +0 -133
- package/framework/agents/backend/ms-agent-expert.md +0 -55
- package/framework/agents/backend/neon-expert.md +0 -379
- package/framework/agents/frontend/css-specialist.md +0 -89
- package/framework/agents/frontend/design-system-validator.md +0 -100
- package/framework/agents/frontend/gsap-animator.md +0 -131
- package/framework/agents/frontend/nextjs-component-validator.md +0 -97
- package/framework/agents/frontend/nextjs-expert.md +0 -201
- package/framework/agents/frontend/ui-designer.md +0 -259
- package/framework/agents/infrastructure/infra-architect.md +0 -215
- package/framework/agents/integrations/asaas-financial.md +0 -138
- package/framework/agents/integrations/neon-auth.md +0 -151
- package/framework/agents/integrations/resend-email.md +0 -127
- package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
- package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
- package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
- package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
- package/framework/hooks/shared/compact-restore.js +0 -123
- package/framework/hooks/shared/dispatch-helpers.js +0 -147
- package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
- package/framework/hooks/shared/stale-task-reset.js +0 -57
- package/framework/hooks/shared/worktree-helpers.js +0 -75
- package/framework/skills/level-0-meta/README.md +0 -7
- package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
- package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
- package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
- package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
- package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
- package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
- package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
- package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
- package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
- package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
- package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
- package/framework/skills/level-0-meta/morph-gemini-image/SKILL.md +0 -290
- package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
- package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -401
- package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
- package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
- package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
- package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
- package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
- package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
- package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
- package/framework/skills/level-1-workflows/README.md +0 -7
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
- package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
- package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -634
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -54
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
- package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
- package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
- package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
- package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -995
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
- package/framework/squad-templates/backend-only.json +0 -47
- package/framework/squad-templates/frontend-only.json +0 -56
- package/framework/squad-templates/full-stack.json +0 -74
- package/src/commands/agents/agent-metrics.js +0 -174
- package/src/commands/agents/audit-dispatch.js +0 -82
- package/src/commands/agents/detect-agents.js +0 -275
- package/src/commands/agents/dispatch-agents.js +0 -461
- package/src/commands/agents/index.js +0 -13
- package/src/commands/agents/manage-agents.js +0 -352
- package/src/commands/agents/project-metrics.js +0 -74
- package/src/commands/phase/phase-reset.js +0 -74
- package/src/commands/scope/escalate.js +0 -217
- package/src/commands/state/advance-phase.js +0 -600
- package/src/commands/state/phase-runner.js +0 -215
- package/src/commands/state/state.js +0 -444
- package/src/commands/task/expand.js +0 -100
- package/src/commands/tasks/index.js +0 -4
- package/src/commands/tasks/task.js +0 -105
- package/src/commands/templates/index.js +0 -5
- package/src/commands/templates/template-render.js +0 -93
- package/src/commands/trust/trust.js +0 -118
- package/src/lib/agents/agents-manager.js +0 -349
- package/src/lib/build/build-runner.js +0 -99
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
- package/src/lib/contracts/sprint-contract-generator.js +0 -137
- package/src/lib/scope/impact-analyzer.js +0 -106
- package/src/lib/trust/trust-manager.js +0 -157
- package/src/utils/tier-helpers.js +0 -24
- /package/framework/hooks/claude-code/{post-tool-use → core}/loop-detect.js +0 -0
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* PostToolUse Hook: Dispatch + Validator Injection
|
|
5
|
-
*
|
|
6
|
-
* Event: PostToolUse | Matcher: Bash
|
|
7
|
-
*
|
|
8
|
-
* Handles:
|
|
9
|
-
* - morph-spec task done: auto-checkpoint every 3 tasks + validator dispatch injection
|
|
10
|
-
* - morph-spec phase advance: phase chain evaluation + validator dispatch injection
|
|
11
|
-
* - morph-spec approve: pass-through
|
|
12
|
-
*
|
|
13
|
-
* Validator dispatch: extracts VALIDATION DISPATCH JSON from tool output
|
|
14
|
-
* and injects it as additionalContext so Claude reliably dispatches validators
|
|
15
|
-
* as subagents via the Agent tool.
|
|
16
|
-
*
|
|
17
|
-
* Fail-open: exits 0 on any error.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { execSync } from 'child_process';
|
|
21
|
-
import { readFileSync, existsSync } from 'fs';
|
|
22
|
-
import { resolve, join, dirname } from 'path';
|
|
23
|
-
import { fileURLToPath } from 'url';
|
|
24
|
-
|
|
25
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
-
import { readStdin } from '../../shared/stdin-reader.js';
|
|
27
|
-
import { stateExists, getFeature, getFeaturePhase } from '../../shared/state-reader.js';
|
|
28
|
-
import { pass, injectContext } from '../../shared/hook-response.js';
|
|
29
|
-
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
30
|
-
import { extractValidationDispatch, formatValidatorInstruction, getToolOutput, parseTaskDoneCommand, parsePhaseAdvanceCommand, parseDispatchAgentsCommand } from '../../shared/dispatch-helpers.js';
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
if (!stateExists()) pass();
|
|
34
|
-
|
|
35
|
-
const payload = await readStdin();
|
|
36
|
-
if (!payload) pass();
|
|
37
|
-
|
|
38
|
-
const command = payload?.tool_input?.command || '';
|
|
39
|
-
if (!command) pass();
|
|
40
|
-
|
|
41
|
-
await dispatch(command, payload);
|
|
42
|
-
} catch {
|
|
43
|
-
// Fail-open
|
|
44
|
-
process.exit(0);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function dispatch(command, payload) {
|
|
48
|
-
// morph-spec task done <feature> <taskId> [--skip-validation]
|
|
49
|
-
const taskDoneParsed = parseTaskDoneCommand(command);
|
|
50
|
-
if (taskDoneParsed) {
|
|
51
|
-
const { featureName } = taskDoneParsed;
|
|
52
|
-
const skipValidation = command.includes('--skip-validation');
|
|
53
|
-
|
|
54
|
-
const contextParts = [];
|
|
55
|
-
|
|
56
|
-
// Check if checkpoint should run (every 3 tasks)
|
|
57
|
-
let dispatchResult = 'task_done';
|
|
58
|
-
try {
|
|
59
|
-
const feature = getFeature(featureName);
|
|
60
|
-
if (feature?.tasks) {
|
|
61
|
-
const completed = (feature.tasks.completed || 0) + 1;
|
|
62
|
-
if (completed > 0 && completed % 3 === 0) {
|
|
63
|
-
const checkpointNum = Math.floor(completed / 3);
|
|
64
|
-
run(`morph-spec checkpoint-save ${featureName} --note "Auto-checkpoint #${checkpointNum} at task ${completed}"`);
|
|
65
|
-
dispatchResult = 'checkpoint_saved';
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
} catch {
|
|
69
|
-
// Non-blocking
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Evaluator dispatch: if sprint contract exists, instruct Claude to dispatch evaluator
|
|
73
|
-
try {
|
|
74
|
-
const taskId = taskDoneParsed.taskId;
|
|
75
|
-
if (taskId && !command.includes('--skip-eval')) {
|
|
76
|
-
const contractPath = join(process.cwd(), '.morph', 'features', featureName, '5-implement', 'contracts', `${taskId}-contract.yaml`);
|
|
77
|
-
if (existsSync(contractPath)) {
|
|
78
|
-
const contractContent = readFileSync(contractPath, 'utf-8');
|
|
79
|
-
let changedFiles = '';
|
|
80
|
-
try {
|
|
81
|
-
changedFiles = execSync('git diff --name-only HEAD~1 2>/dev/null | head -10', {
|
|
82
|
-
cwd: process.cwd(), encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe']
|
|
83
|
-
}).trim();
|
|
84
|
-
} catch { /* no git */ }
|
|
85
|
-
|
|
86
|
-
// Include score context from latest validation if available
|
|
87
|
-
let scoreContext = '';
|
|
88
|
-
try {
|
|
89
|
-
const evalFeature = getFeature(featureName);
|
|
90
|
-
const taskScores = evalFeature?.validationHistory?.[taskId]?.scores;
|
|
91
|
-
if (taskScores) {
|
|
92
|
-
scoreContext = `\n## Previous Validation Score: ${taskScores.score}/10\n` +
|
|
93
|
-
Object.entries(taskScores.breakdown || {})
|
|
94
|
-
.map(([cat, d]) => ` ${cat}: ${Math.round(d.score * 10) / 10}/10 (weight ${Math.round(d.weight * 100)}%)`)
|
|
95
|
-
.join('\n') + '\n\nFocus your evaluation on categories with the lowest scores.\n';
|
|
96
|
-
}
|
|
97
|
-
} catch { /* non-blocking */ }
|
|
98
|
-
|
|
99
|
-
contextParts.push(
|
|
100
|
-
`🔍 EVALUATOR DISPATCH\n` +
|
|
101
|
-
`════════════════════════════════════════\n` +
|
|
102
|
-
`Sprint contract exists for ${taskId}. Dispatch an evaluator sub-agent:\n\n` +
|
|
103
|
-
`Use the Agent tool with:\n` +
|
|
104
|
-
` subagent_type: "general-purpose"\n` +
|
|
105
|
-
` model: "sonnet"\n` +
|
|
106
|
-
` prompt: (see below)\n\n` +
|
|
107
|
-
`--- EVALUATOR PROMPT ---\n` +
|
|
108
|
-
`You are an ADVERSARIAL evaluator. Your default assumption is that defects exist.\n` +
|
|
109
|
-
`Do NOT confirm correctness — actively search for problems.\n\n` +
|
|
110
|
-
`## Contract\n${contractContent}\n\n` +
|
|
111
|
-
`## Changed Files\n${changedFiles || '(unable to determine)'}\n` +
|
|
112
|
-
`${scoreContext}\n` +
|
|
113
|
-
`## Instructions\n` +
|
|
114
|
-
`1. Read each changed file\n` +
|
|
115
|
-
`2. Run the verification commands from the contract\n` +
|
|
116
|
-
`3. Evaluate each definitionOfDone item: PASS or FAIL with evidence\n` +
|
|
117
|
-
`4. For each category, score 0-10 and explain deficiencies found\n` +
|
|
118
|
-
`5. A clean report is suspicious — dig deeper before declaring PASS\n` +
|
|
119
|
-
`6. Return a structured result with overall PASS/FAIL and score breakdown\n` +
|
|
120
|
-
`--- END EVALUATOR PROMPT ---`
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
} catch {
|
|
125
|
-
// Non-blocking — evaluator is optional
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Always inject validator dispatch — deferred if --skip-validation
|
|
129
|
-
const validatorText = await buildValidatorDispatchContext(featureName, payload);
|
|
130
|
-
if (validatorText) {
|
|
131
|
-
if (skipValidation) {
|
|
132
|
-
// Include budget info when available
|
|
133
|
-
let budgetInfo = '';
|
|
134
|
-
try {
|
|
135
|
-
const feature = getFeature(featureName);
|
|
136
|
-
if (feature) {
|
|
137
|
-
const total = feature.tasks?.total || 0;
|
|
138
|
-
const budget = total === 0 ? 1 : Math.max(1, Math.min(5, Math.ceil(total * 0.2)));
|
|
139
|
-
const used = (feature.skipValidationLog || []).length;
|
|
140
|
-
budgetInfo = ` (budget: ${used}/${budget})`;
|
|
141
|
-
}
|
|
142
|
-
} catch {
|
|
143
|
-
// Non-blocking
|
|
144
|
-
}
|
|
145
|
-
contextParts.push(
|
|
146
|
-
`⏩ DEFERRED TO CHECKPOINT — validators skipped on this task${budgetInfo} but MUST run at next checkpoint:\n\n` +
|
|
147
|
-
validatorText
|
|
148
|
-
);
|
|
149
|
-
dispatchResult = 'task_done_deferred_validators';
|
|
150
|
-
} else {
|
|
151
|
-
contextParts.push(validatorText);
|
|
152
|
-
dispatchResult = 'task_done_with_validators';
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
logHookActivity('phase-dispatch', 'PostToolUse', dispatchResult);
|
|
157
|
-
|
|
158
|
-
if (contextParts.length > 0) {
|
|
159
|
-
injectContext(contextParts.join('\n\n'));
|
|
160
|
-
}
|
|
161
|
-
pass();
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// morph-spec phase advance <feature>
|
|
165
|
-
const phaseAdvanceParsed = parsePhaseAdvanceCommand(command);
|
|
166
|
-
if (phaseAdvanceParsed) {
|
|
167
|
-
const { featureName } = phaseAdvanceParsed;
|
|
168
|
-
|
|
169
|
-
const contextParts = [];
|
|
170
|
-
|
|
171
|
-
// Phase chain evaluation
|
|
172
|
-
const chainText = buildPhaseChainContext(featureName);
|
|
173
|
-
if (chainText) contextParts.push(chainText);
|
|
174
|
-
|
|
175
|
-
// Validator dispatch injection
|
|
176
|
-
const validatorText = await buildValidatorDispatchContext(featureName, payload);
|
|
177
|
-
if (validatorText) contextParts.push(validatorText);
|
|
178
|
-
|
|
179
|
-
// Mandatory dispatch instruction when advancing to implement phase
|
|
180
|
-
try {
|
|
181
|
-
const advancedPhase = getFeaturePhase(featureName);
|
|
182
|
-
if (advancedPhase === 'implement') {
|
|
183
|
-
contextParts.push(
|
|
184
|
-
'🔧 MANDATORY AGENT DISPATCH\n' +
|
|
185
|
-
'════════════════════════════════════════\n' +
|
|
186
|
-
`Run this command BEFORE starting any task:\n\n` +
|
|
187
|
-
` morph-spec dispatch-agents ${featureName} implement\n\n` +
|
|
188
|
-
`Task start will be BLOCKED until dispatch is recorded.\n` +
|
|
189
|
-
`Use the dispatch output to determine execution mode (single/subagents/agent-teams).`
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
} catch {
|
|
193
|
-
// Non-blocking
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
logHookActivity('phase-dispatch', 'PostToolUse',
|
|
197
|
-
validatorText ? 'phase_advance_with_validators' : 'phase_chain');
|
|
198
|
-
|
|
199
|
-
if (contextParts.length > 0) {
|
|
200
|
-
injectContext(contextParts.join('\n\n'));
|
|
201
|
-
}
|
|
202
|
-
pass();
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// morph-spec dispatch-agents <feature> <phase>
|
|
206
|
-
const dispatchParsed = parseDispatchAgentsCommand(command);
|
|
207
|
-
if (dispatchParsed) {
|
|
208
|
-
const { featureName: dispFeature, phase: dispPhase } = dispatchParsed;
|
|
209
|
-
if (dispPhase === 'implement') {
|
|
210
|
-
injectContext(
|
|
211
|
-
`✓ Agent dispatch recorded for '${dispFeature}' (implement phase).\n` +
|
|
212
|
-
`You may now start tasks with: morph-spec task start ${dispFeature} <taskId>\n` +
|
|
213
|
-
`Use the dispatch config above to assign tasks to the correct typed agents.`
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
logHookActivity('phase-dispatch', 'PostToolUse', 'dispatch_agents_confirmed');
|
|
217
|
-
pass();
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// morph-spec approve <feature> <gate>
|
|
221
|
-
const approveMatch = command.match(/morph-spec\s+approve\s+(\S+)\s+(\S+)/);
|
|
222
|
-
if (approveMatch) {
|
|
223
|
-
// Approval handled by CLI — just pass through
|
|
224
|
-
pass();
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// No match — exit cleanly
|
|
228
|
-
pass();
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Build phase chain context text (returns null if not applicable).
|
|
233
|
-
* Replaces the old evaluatePhaseChain that called injectContext directly.
|
|
234
|
-
*
|
|
235
|
-
* @param {string} featureName
|
|
236
|
-
* @returns {string|null}
|
|
237
|
-
*/
|
|
238
|
-
function buildPhaseChainContext(featureName) {
|
|
239
|
-
try {
|
|
240
|
-
const feature = getFeature(featureName);
|
|
241
|
-
if (!feature) return null;
|
|
242
|
-
|
|
243
|
-
const workflowId = feature.workflow;
|
|
244
|
-
if (!workflowId || workflowId === 'auto') return null;
|
|
245
|
-
|
|
246
|
-
const configPath = resolve(
|
|
247
|
-
process.cwd(),
|
|
248
|
-
`.morph/framework/workflows/configs/${workflowId}.json`
|
|
249
|
-
);
|
|
250
|
-
if (!existsSync(configPath)) return null;
|
|
251
|
-
|
|
252
|
-
let workflowConfig;
|
|
253
|
-
try {
|
|
254
|
-
workflowConfig = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
255
|
-
} catch {
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (!workflowConfig?.phaseChain?.enabled) return null;
|
|
260
|
-
|
|
261
|
-
return (
|
|
262
|
-
`Phase advance complete. Continue the phase chain:\n` +
|
|
263
|
-
` morph-spec phase run ${featureName}\n\n` +
|
|
264
|
-
`The phase runner will check eligibility and auto-advance if all gates pass. ` +
|
|
265
|
-
`It will pause automatically on blocked tasks, low pass rate, or trust gates.`
|
|
266
|
-
);
|
|
267
|
-
} catch {
|
|
268
|
-
return null;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Extract validator dispatch from tool output and build injection context.
|
|
274
|
-
*
|
|
275
|
-
* Strategy:
|
|
276
|
-
* 1. Try to parse VALIDATION DISPATCH JSON from the tool_result (stdout of phase advance)
|
|
277
|
-
* 2. If not found, fallback to running dispatch-agents CLI in validate mode
|
|
278
|
-
*
|
|
279
|
-
* @param {string} featureName
|
|
280
|
-
* @param {object} payload - PostToolUse hook payload
|
|
281
|
-
* @returns {string|null} - Formatted injection text or null
|
|
282
|
-
*/
|
|
283
|
-
async function buildValidatorDispatchContext(featureName, payload) {
|
|
284
|
-
try {
|
|
285
|
-
// Strategy 1: Extract from tool output
|
|
286
|
-
const toolOutput = getToolOutput(payload);
|
|
287
|
-
const dispatch = extractValidationDispatch(toolOutput);
|
|
288
|
-
if (dispatch) return formatValidatorInstruction(dispatch);
|
|
289
|
-
|
|
290
|
-
// Strategy 2: Direct import of buildDispatchConfig (no subprocess)
|
|
291
|
-
return await buildDispatchDirect(featureName);
|
|
292
|
-
} catch {
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Fallback: build validator dispatch config via direct import (no subprocess).
|
|
299
|
-
*
|
|
300
|
-
* Attempts to import buildDispatchConfig from the installed package or local
|
|
301
|
-
* framework. Falls back to null on any error (fail-open).
|
|
302
|
-
*
|
|
303
|
-
* @param {string} featureName
|
|
304
|
-
* @returns {Promise<string|null>}
|
|
305
|
-
*/
|
|
306
|
-
async function buildDispatchDirect(featureName) {
|
|
307
|
-
try {
|
|
308
|
-
const phase = getFeaturePhase(featureName);
|
|
309
|
-
if (!phase) return null;
|
|
310
|
-
|
|
311
|
-
// Try local project path first, then package path
|
|
312
|
-
let buildDispatchConfig;
|
|
313
|
-
const localPath = resolve(process.cwd(), 'src/commands/agents/dispatch-agents.js');
|
|
314
|
-
const pkgPath = resolve(process.cwd(), 'node_modules/@polymorphism-tech/morph-spec/src/commands/agents/dispatch-agents.js');
|
|
315
|
-
|
|
316
|
-
if (existsSync(localPath)) {
|
|
317
|
-
({ buildDispatchConfig } = await import(`file://${localPath.replace(/\\/g, '/')}`));
|
|
318
|
-
} else if (existsSync(pkgPath)) {
|
|
319
|
-
({ buildDispatchConfig } = await import(`file://${pkgPath.replace(/\\/g, '/')}`));
|
|
320
|
-
} else {
|
|
321
|
-
return null;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const config = await buildDispatchConfig(process.cwd(), featureName, phase, { mode: 'validate' });
|
|
325
|
-
const validators = config.agents?.filter(a => a.tier === 4);
|
|
326
|
-
if (!validators || validators.length === 0) return null;
|
|
327
|
-
|
|
328
|
-
const agentsJsonPath = resolve(process.cwd(), 'framework/agents.json');
|
|
329
|
-
const pkgAgentsPath = resolve(process.cwd(), 'node_modules/@polymorphism-tech/morph-spec/framework/agents.json');
|
|
330
|
-
const agentsPath = existsSync(agentsJsonPath) ? agentsJsonPath : pkgAgentsPath;
|
|
331
|
-
|
|
332
|
-
let allAgents = {};
|
|
333
|
-
if (existsSync(agentsPath)) {
|
|
334
|
-
allAgents = JSON.parse(readFileSync(agentsPath, 'utf8')).agents || {};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const dispatch = {
|
|
338
|
-
validators: validators.map(v => {
|
|
339
|
-
const agentData = allAgents[v.id];
|
|
340
|
-
return {
|
|
341
|
-
id: v.id,
|
|
342
|
-
title: v.title,
|
|
343
|
-
taskPrompt: v.taskPrompt,
|
|
344
|
-
severity: agentData?.hook_behavior?.severity || 'error',
|
|
345
|
-
blocksOnFail: agentData?.hook_behavior?.blocks_on_fail ?? true,
|
|
346
|
-
checks: agentData?.hook_behavior?.validates || [],
|
|
347
|
-
};
|
|
348
|
-
}),
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
return formatValidatorInstruction(dispatch);
|
|
352
|
-
} catch {
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function run(cmd) {
|
|
358
|
-
try {
|
|
359
|
-
execSync(cmd, { stdio: 'pipe', cwd: process.cwd(), timeout: 30000 });
|
|
360
|
-
} catch {
|
|
361
|
-
// Non-blocking
|
|
362
|
-
}
|
|
363
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* PostToolUseFailure Hook: Log Tool Failures + Rollback Awareness
|
|
5
|
-
*
|
|
6
|
-
* Event: PostToolUseFailure
|
|
7
|
-
*
|
|
8
|
-
* Runs when any tool fails. Appends a structured JSON entry to
|
|
9
|
-
* .morph/logs/tool-failures.log for debugging and pattern analysis.
|
|
10
|
-
*
|
|
11
|
-
* If a critical failure occurs and there are unstaged changes,
|
|
12
|
-
* suggests git stash as a recovery option.
|
|
13
|
-
*
|
|
14
|
-
* Fail-open: exits 0 on any error.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { appendFileSync, mkdirSync } from 'fs';
|
|
18
|
-
import { join } from 'path';
|
|
19
|
-
import { execSync } from 'child_process';
|
|
20
|
-
import { readStdin } from '../../shared/stdin-reader.js';
|
|
21
|
-
import { stateExists } from '../../shared/state-reader.js';
|
|
22
|
-
import { injectContext } from '../../shared/hook-response.js';
|
|
23
|
-
import { logHookActivity, logEvent } from '../../shared/activity-logger.js';
|
|
24
|
-
|
|
25
|
-
const CRITICAL_ERROR_PATTERNS = ['ENOENT', 'EPERM', 'EACCES', 'segfault', 'out of memory', 'ENOMEM'];
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
// Only log in morph-spec projects
|
|
29
|
-
if (!stateExists()) {
|
|
30
|
-
process.exit(0);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const input = await readStdin();
|
|
34
|
-
|
|
35
|
-
const logDir = join(process.cwd(), '.morph', 'logs');
|
|
36
|
-
mkdirSync(logDir, { recursive: true });
|
|
37
|
-
|
|
38
|
-
const entry = {
|
|
39
|
-
timestamp: new Date().toISOString(),
|
|
40
|
-
tool: input?.tool_name ?? 'unknown',
|
|
41
|
-
error: input?.error ?? null,
|
|
42
|
-
input: input?.tool_input ?? null,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
appendFileSync(join(logDir, 'tool-failures.log'), JSON.stringify(entry) + '\n');
|
|
46
|
-
logHookActivity('handle-tool-failure', 'PostToolUseFailure', `logged(${entry.tool})`);
|
|
47
|
-
|
|
48
|
-
// Rollback awareness: check for unstaged changes after critical failures
|
|
49
|
-
const errorStr = JSON.stringify(input?.error || '');
|
|
50
|
-
const isCritical = CRITICAL_ERROR_PATTERNS.some(p => errorStr.includes(p));
|
|
51
|
-
|
|
52
|
-
logEvent('tool_failure', { tool: entry.tool, critical: isCritical });
|
|
53
|
-
|
|
54
|
-
if (isCritical) {
|
|
55
|
-
try {
|
|
56
|
-
const gitStatus = execSync('git status --porcelain', {
|
|
57
|
-
cwd: process.cwd(),
|
|
58
|
-
encoding: 'utf-8',
|
|
59
|
-
timeout: 5000,
|
|
60
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
61
|
-
}).trim();
|
|
62
|
-
|
|
63
|
-
if (gitStatus) {
|
|
64
|
-
injectContext(
|
|
65
|
-
`⚠️ [morph-spec] Critical tool failure (${entry.tool}). ` +
|
|
66
|
-
`Unstaged changes detected:\n${gitStatus.split('\n').slice(0, 5).join('\n')}\n\n` +
|
|
67
|
-
`Consider: git stash to save current state before retrying.`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
} catch {
|
|
71
|
-
// git not available or not a git repo — ignore
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
} catch {
|
|
75
|
-
// Fail-open: logging is non-critical
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
process.exit(0);
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* PostToolUse Hook: Validator Feedback
|
|
5
|
-
*
|
|
6
|
-
* Event: PostToolUse | Matcher: Bash
|
|
7
|
-
*
|
|
8
|
-
* Fires after `morph-spec task done` completes.
|
|
9
|
-
* Reads validationHistory for the completed task and injects
|
|
10
|
-
* remediation context when validation failed.
|
|
11
|
-
*
|
|
12
|
-
* This closes the Builder-Validator loop:
|
|
13
|
-
* task done → runValidation → validationHistory → hook injects context
|
|
14
|
-
* → LLM sees remediation prompt before next step → auto-fix attempt
|
|
15
|
-
*
|
|
16
|
-
* Fail-open: exits 0 on any error.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { readStdin } from '../../shared/stdin-reader.js';
|
|
20
|
-
import { stateExists, loadState } from '../../shared/state-reader.js';
|
|
21
|
-
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
22
|
-
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
23
|
-
import { parseTaskDoneCommand } from '../../shared/dispatch-helpers.js';
|
|
24
|
-
|
|
25
|
-
// Standard IDs referenced in remediation messages
|
|
26
|
-
const STANDARD_REFS = {
|
|
27
|
-
'di': 'dependency injection best practices',
|
|
28
|
-
'async': 'async/await and CancellationToken patterns',
|
|
29
|
-
'security': 'security best practices',
|
|
30
|
-
'packages': 'NuGet package compatibility',
|
|
31
|
-
'design-system': 'frontend/design-system/naming.md#css-class-conventions',
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
if (!stateExists()) pass();
|
|
36
|
-
|
|
37
|
-
const payload = await readStdin();
|
|
38
|
-
if (!payload) pass();
|
|
39
|
-
|
|
40
|
-
const command = payload?.tool_input?.command || '';
|
|
41
|
-
if (!command) pass();
|
|
42
|
-
|
|
43
|
-
const parsed = parseTaskDoneCommand(command);
|
|
44
|
-
if (!parsed) pass();
|
|
45
|
-
|
|
46
|
-
const { featureName, taskId } = parsed;
|
|
47
|
-
|
|
48
|
-
// Load state and find validationHistory for this task
|
|
49
|
-
const state = loadState();
|
|
50
|
-
if (!state) pass();
|
|
51
|
-
|
|
52
|
-
const feature = state?.features?.[featureName];
|
|
53
|
-
if (!feature) pass();
|
|
54
|
-
|
|
55
|
-
const taskHistory = feature.validationHistory?.[taskId];
|
|
56
|
-
if (!taskHistory) pass();
|
|
57
|
-
|
|
58
|
-
// Only act on failed validations (passed = no feedback needed)
|
|
59
|
-
if (taskHistory.status === 'passed') {
|
|
60
|
-
logHookActivity('validator-feedback', 'PostToolUse', 'passed');
|
|
61
|
-
pass();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Build remediation context based on validation issues
|
|
65
|
-
logHookActivity('validator-feedback', 'PostToolUse', `remediation_injected(attempt_${taskHistory.attempt || 1})`);
|
|
66
|
-
buildRemediationContext(featureName, taskId, taskHistory);
|
|
67
|
-
} catch {
|
|
68
|
-
// Fail-open
|
|
69
|
-
process.exit(0);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Build and inject remediation context for a failed validation.
|
|
74
|
-
*
|
|
75
|
-
* @param {string} featureName
|
|
76
|
-
* @param {string} taskId
|
|
77
|
-
* @param {Object} taskHistory - validationHistory[taskId]
|
|
78
|
-
*/
|
|
79
|
-
function buildRemediationContext(featureName, taskId, taskHistory) {
|
|
80
|
-
const attempt = taskHistory.attempt || 1;
|
|
81
|
-
const validators = taskHistory.validators || {};
|
|
82
|
-
const scores = taskHistory.scores || null;
|
|
83
|
-
|
|
84
|
-
// Collect all issues across all validators
|
|
85
|
-
const allIssues = [];
|
|
86
|
-
for (const [validatorName, result] of Object.entries(validators)) {
|
|
87
|
-
if (result.passed) continue;
|
|
88
|
-
const issues = result.issues || [];
|
|
89
|
-
for (const issue of issues) {
|
|
90
|
-
allIssues.push({ validator: validatorName, ...issue });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (allIssues.length === 0 && taskHistory.status !== 'blocked') return;
|
|
95
|
-
|
|
96
|
-
const lines = [];
|
|
97
|
-
|
|
98
|
-
if (taskHistory.status === 'blocked') {
|
|
99
|
-
// Max attempts reached OR score-based escalation
|
|
100
|
-
const scoreInfo = scores ? ` (score: ${scores.score}/10)` : '';
|
|
101
|
-
lines.push(`⛔ ESCALATION REQUIRED — Task ${taskId} in feature '${featureName}'${scoreInfo}`);
|
|
102
|
-
if (scores?.action === 'escalate') {
|
|
103
|
-
lines.push(` Score below retry threshold — immediate escalation.`);
|
|
104
|
-
} else {
|
|
105
|
-
lines.push(` Validation failed ${attempt} times (max attempts reached).`);
|
|
106
|
-
}
|
|
107
|
-
lines.push(` Human review required before proceeding.`);
|
|
108
|
-
if (scores) {
|
|
109
|
-
lines.push(``);
|
|
110
|
-
lines.push(` Score breakdown:`);
|
|
111
|
-
appendScoreBreakdown(lines, scores);
|
|
112
|
-
}
|
|
113
|
-
lines.push(` Last failures:`);
|
|
114
|
-
for (const issue of allIssues.slice(0, 5)) {
|
|
115
|
-
lines.push(` • [${issue.validator}] ${issue.message} ${issue.file ? `(${issue.file}${issue.line ? ':' + issue.line : ''})` : ''}`);
|
|
116
|
-
}
|
|
117
|
-
} else {
|
|
118
|
-
// Active remediation — attempt N of 3
|
|
119
|
-
const scoreInfo = scores ? ` | score: ${scores.score}/10` : '';
|
|
120
|
-
lines.push(`🔄 REMEDIATION REQUIRED — Task ${taskId} (attempt ${attempt}/3${scoreInfo})`);
|
|
121
|
-
lines.push(` Validation failed. Fix the following issues and re-run:`);
|
|
122
|
-
lines.push(` morph-spec task done ${featureName} ${taskId}`);
|
|
123
|
-
lines.push(``);
|
|
124
|
-
|
|
125
|
-
// Score-based prioritized feedback (when available)
|
|
126
|
-
if (scores?.breakdown) {
|
|
127
|
-
lines.push(` Priority fixes (by impact):`);
|
|
128
|
-
const sorted = Object.entries(scores.breakdown)
|
|
129
|
-
.filter(([, cat]) => (cat.issues?.length || 0) > 0)
|
|
130
|
-
.sort((a, b) => ((10 - b[1].score) * b[1].weight) - ((10 - a[1].score) * a[1].weight));
|
|
131
|
-
|
|
132
|
-
for (const [category, cat] of sorted) {
|
|
133
|
-
const topIssue = cat.issues[0];
|
|
134
|
-
const location = topIssue?.file ? ` (${topIssue.file}${topIssue.line ? ':' + topIssue.line : ''})` : '';
|
|
135
|
-
const msg = topIssue?.message || 'Issues detected';
|
|
136
|
-
lines.push(` ${Math.round(cat.score * 10) / 10}/10 [${category}] ${msg}${location}`);
|
|
137
|
-
if (cat.issues.length > 1) {
|
|
138
|
-
lines.push(` ... and ${cat.issues.length - 1} more issue(s)`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
lines.push(``);
|
|
142
|
-
appendScoreBreakdown(lines, scores);
|
|
143
|
-
} else {
|
|
144
|
-
// Fallback: original issue-by-issue format
|
|
145
|
-
for (const issue of allIssues) {
|
|
146
|
-
const refKey = getRefKey(issue.rule || issue.validator);
|
|
147
|
-
const ref = refKey ? ` — Ref: ${STANDARD_REFS[refKey] || refKey}` : '';
|
|
148
|
-
const location = issue.file
|
|
149
|
-
? ` (${issue.file}${issue.line ? ':' + issue.line : ''})`
|
|
150
|
-
: '';
|
|
151
|
-
lines.push(` • [${issue.validator}] ${issue.message}${location}${ref}`);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (attempt >= 2) {
|
|
156
|
-
lines.push(``);
|
|
157
|
-
lines.push(` ⚠️ If this fails again, it will be escalated (attempt ${attempt + 1} = final).`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
injectContext(lines.join('\n'));
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Append score breakdown lines to output.
|
|
166
|
-
* @param {string[]} lines - Output array
|
|
167
|
-
* @param {Object} scores - { score, action, breakdown }
|
|
168
|
-
*/
|
|
169
|
-
function appendScoreBreakdown(lines, scores) {
|
|
170
|
-
if (!scores?.breakdown) return;
|
|
171
|
-
const approveThreshold = scores.approveThreshold ?? 8.0;
|
|
172
|
-
for (const [category, data] of Object.entries(scores.breakdown)) {
|
|
173
|
-
if (category === '_unmapped') continue; // skip internal unmapped category
|
|
174
|
-
const pct = Math.round(data.weight * 100);
|
|
175
|
-
const indicator = data.score >= approveThreshold ? '\u2713' : data.score >= 6 ? '\u2193' : '\u2193\u2193';
|
|
176
|
-
lines.push(` ${category}: ${Math.round(data.score * 10) / 10}/10 (weight ${pct}%) ${indicator}`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Map a rule/validator name to a STANDARD_REFS key.
|
|
182
|
-
* @param {string} ruleOrValidator
|
|
183
|
-
* @returns {string|null}
|
|
184
|
-
*/
|
|
185
|
-
function getRefKey(ruleOrValidator) {
|
|
186
|
-
if (!ruleOrValidator) return null;
|
|
187
|
-
const lower = ruleOrValidator.toLowerCase();
|
|
188
|
-
if (lower.includes('di') || lower.includes('dependency') || lower.includes('injection')) return 'di';
|
|
189
|
-
if (lower.includes('async') || lower.includes('await') || lower.includes('cancellation')) return 'async';
|
|
190
|
-
if (lower.includes('security') || lower.includes('sql') || lower.includes('xss')) return 'security';
|
|
191
|
-
if (lower.includes('package') || lower.includes('nuget')) return 'packages';
|
|
192
|
-
if (lower.includes('design') || lower.includes('css') || lower.includes('naming')) return 'design-system';
|
|
193
|
-
return null;
|
|
194
|
-
}
|