@polymorphism-tech/morph-spec 4.22.0 → 5.0.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 +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,77 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* SessionStart Hook: Resume Context Orientation
|
|
5
|
-
*
|
|
6
|
-
* Event: SessionStart | Matcher: resume
|
|
7
|
-
*
|
|
8
|
-
* Fires ONLY when a session resumes via /resume.
|
|
9
|
-
* Injects a brief orientation message so Claude immediately knows
|
|
10
|
-
* the active feature, phase, in-progress tasks, and last checkpoint.
|
|
11
|
-
*
|
|
12
|
-
* Fail-open: exits 0 on any error.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { existsSync, readFileSync } from 'fs';
|
|
16
|
-
import { join } from 'path';
|
|
17
|
-
import {
|
|
18
|
-
stateExists, getActiveFeature, derivePhaseForFeature,
|
|
19
|
-
} from '../../shared/state-reader.js';
|
|
20
|
-
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
21
|
-
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
22
|
-
import { SPEC_SUMMARY_MAX_CHARS } from '../../shared/compact-restore.js';
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
if (!stateExists()) pass();
|
|
26
|
-
|
|
27
|
-
const active = getActiveFeature();
|
|
28
|
-
if (!active) pass();
|
|
29
|
-
|
|
30
|
-
const { name, feature } = active;
|
|
31
|
-
const phase = derivePhaseForFeature(name);
|
|
32
|
-
const cwd = process.cwd();
|
|
33
|
-
|
|
34
|
-
const lines = [
|
|
35
|
-
`🔄 RESUME — Você está continuando a feature "${name}" na fase ${phase}.`,
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
// In-progress tasks
|
|
39
|
-
const taskList = Array.isArray(feature.taskList) ? feature.taskList : [];
|
|
40
|
-
const inProg = taskList.filter(t => t.status === 'in_progress');
|
|
41
|
-
if (inProg.length > 0) {
|
|
42
|
-
const ids = inProg.map(t => t.id).join(', ');
|
|
43
|
-
lines.push(`Tasks em progresso: ${ids}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Task progress summary
|
|
47
|
-
const tasks = feature.tasks || {};
|
|
48
|
-
if (tasks.total > 0) {
|
|
49
|
-
lines.push(`Progresso: ${tasks.completed || 0}/${tasks.total} tasks concluídas`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Last checkpoint
|
|
53
|
-
const checkpoints = Array.isArray(feature.checkpoints) ? feature.checkpoints : [];
|
|
54
|
-
if (checkpoints.length > 0) {
|
|
55
|
-
const last = checkpoints[checkpoints.length - 1];
|
|
56
|
-
lines.push(`Último checkpoint: #${last.checkpointNum} (${last.passed ? 'passed' : 'failed'})`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Spec summary (truncated)
|
|
60
|
-
const specPath = join(cwd, `.morph/features/${name}/2-design/spec.md`);
|
|
61
|
-
if (existsSync(specPath)) {
|
|
62
|
-
try {
|
|
63
|
-
const raw = readFileSync(specPath, 'utf-8');
|
|
64
|
-
const summary = raw.length > SPEC_SUMMARY_MAX_CHARS
|
|
65
|
-
? raw.slice(0, SPEC_SUMMARY_MAX_CHARS) + '\n[... truncated]'
|
|
66
|
-
: raw;
|
|
67
|
-
lines.push('');
|
|
68
|
-
lines.push('Spec summary:');
|
|
69
|
-
lines.push(summary);
|
|
70
|
-
} catch { /* non-blocking */ }
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
logHookActivity('resume-context', 'SessionStart', 'resume_injected');
|
|
74
|
-
injectContext(lines.join('\n'));
|
|
75
|
-
} catch {
|
|
76
|
-
process.exit(0);
|
|
77
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* TeammateIdle Hook: Validate feature before teammate becomes idle
|
|
5
|
-
*
|
|
6
|
-
* Event: TeammateIdle
|
|
7
|
-
*
|
|
8
|
-
* When an Agent Team teammate finishes its turn and is about to go idle,
|
|
9
|
-
* this hook runs `morph-spec validate-feature` to check implementation quality.
|
|
10
|
-
*
|
|
11
|
-
* Exit codes:
|
|
12
|
-
* 0 = validation passed — teammate may become idle
|
|
13
|
-
* 2 = validation failed — send feedback to teammate to continue working
|
|
14
|
-
*
|
|
15
|
-
* Fail-open: exits 0 on any error (never blocks teammate incorrectly).
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { execSync } from 'child_process';
|
|
19
|
-
import { readStdin } from '../../../shared/stdin-reader.js';
|
|
20
|
-
import { loadState, stateExists, getActiveFeature } from '../../../shared/state-reader.js';
|
|
21
|
-
import { logHookActivity } from '../../../shared/activity-logger.js';
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
// Read hook payload (TeammateIdle provides teammate info)
|
|
25
|
-
const payload = await readStdin();
|
|
26
|
-
|
|
27
|
-
if (!stateExists()) process.exit(0);
|
|
28
|
-
|
|
29
|
-
// Try to extract feature name from payload or fall back to active feature
|
|
30
|
-
let featureName = payload?.feature_name || payload?.featureName;
|
|
31
|
-
|
|
32
|
-
if (!featureName) {
|
|
33
|
-
const active = getActiveFeature();
|
|
34
|
-
featureName = active?.name;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (!featureName) process.exit(0);
|
|
38
|
-
|
|
39
|
-
// Only validate during implement phase
|
|
40
|
-
const state = loadState();
|
|
41
|
-
const feature = state?.features?.[featureName];
|
|
42
|
-
if (!feature) process.exit(0);
|
|
43
|
-
|
|
44
|
-
// derive phase — only run validation during implement phase
|
|
45
|
-
const phase = feature.phase;
|
|
46
|
-
if (phase && phase !== 'implement') process.exit(0);
|
|
47
|
-
|
|
48
|
-
// Run validate-feature with --quiet flag (suppress non-error output)
|
|
49
|
-
let output = '';
|
|
50
|
-
let exitCode = 0;
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
output = execSync(
|
|
54
|
-
`morph-spec validate-feature "${featureName}" --phase implement --quiet`,
|
|
55
|
-
{
|
|
56
|
-
cwd: process.cwd(),
|
|
57
|
-
encoding: 'utf-8',
|
|
58
|
-
timeout: 30000,
|
|
59
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
60
|
-
}
|
|
61
|
-
);
|
|
62
|
-
} catch (err) {
|
|
63
|
-
// Non-zero exit from validate-feature means validation errors
|
|
64
|
-
exitCode = err.status || 1;
|
|
65
|
-
output = err.stdout || err.stderr || '';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (exitCode !== 0) {
|
|
69
|
-
// Exit 2: send feedback to teammate to continue working
|
|
70
|
-
logHookActivity('teammate-idle', 'TeammateIdle', 'validation_failed');
|
|
71
|
-
const feedback = [
|
|
72
|
-
'MORPH-SPEC validation found issues — please fix before going idle:',
|
|
73
|
-
'',
|
|
74
|
-
output.trim() || 'Run `morph-spec validate-feature to see details.',
|
|
75
|
-
'',
|
|
76
|
-
`Run: morph-spec validate-feature ${featureName} --phase implement`,
|
|
77
|
-
].join('\n');
|
|
78
|
-
|
|
79
|
-
// Claude Code reads additionalContext from stdout for exit code 2
|
|
80
|
-
process.stdout.write(JSON.stringify({ additionalContext: feedback }) + '\n');
|
|
81
|
-
process.exit(2);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Exit 0: validation passed, teammate may become idle
|
|
85
|
-
logHookActivity('teammate-idle', 'TeammateIdle', 'passed');
|
|
86
|
-
process.exit(0);
|
|
87
|
-
} catch {
|
|
88
|
-
// Fail-open: never block a teammate due to hook errors
|
|
89
|
-
process.exit(0);
|
|
90
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* UserPromptSubmit Hook: Enrich User Prompts with Context
|
|
5
|
-
*
|
|
6
|
-
* Event: UserPromptSubmit
|
|
7
|
-
*
|
|
8
|
-
* Scans user prompt for morph-spec keywords and injects relevant context:
|
|
9
|
-
* - Feature name mentioned → inject its current status/phase
|
|
10
|
-
* - "implement"/"code" while not in implement phase → warn about wrong phase
|
|
11
|
-
* - "approve"/"looks good" → inject approval command syntax
|
|
12
|
-
* - "next task" → inject current task list
|
|
13
|
-
*
|
|
14
|
-
* Fail-open: exits 0 on any error.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { readStdin } from '../../shared/stdin-reader.js';
|
|
18
|
-
import { stateExists, loadState, getActiveFeature, getFeature, getPendingGates, derivePhaseForFeature } from '../../shared/state-reader.js';
|
|
19
|
-
import { injectContext, pass } from '../../shared/hook-response.js';
|
|
20
|
-
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
if (!stateExists()) pass();
|
|
24
|
-
|
|
25
|
-
const payload = await readStdin();
|
|
26
|
-
if (!payload) pass();
|
|
27
|
-
|
|
28
|
-
const userPrompt = payload?.prompt || payload?.user_prompt || payload?.content || '';
|
|
29
|
-
if (!userPrompt || userPrompt.length < 3) pass();
|
|
30
|
-
|
|
31
|
-
const promptLower = userPrompt.toLowerCase();
|
|
32
|
-
const state = loadState();
|
|
33
|
-
if (!state?.features) pass();
|
|
34
|
-
|
|
35
|
-
const context = [];
|
|
36
|
-
|
|
37
|
-
// Check if a feature name is mentioned
|
|
38
|
-
for (const [featureName, feature] of Object.entries(state.features)) {
|
|
39
|
-
if (promptLower.includes(featureName.toLowerCase())) {
|
|
40
|
-
const featurePhase = feature.phase || derivePhaseForFeature(featureName);
|
|
41
|
-
context.push(`[morph-spec] Feature '${featureName}': phase=${featurePhase}, status=${feature.status}`);
|
|
42
|
-
const taskList = feature.taskList || [];
|
|
43
|
-
const activeTasks = taskList.filter(t => t.status !== 'expanded');
|
|
44
|
-
if (activeTasks.length > 0) {
|
|
45
|
-
const completed = activeTasks.filter(t => t.status === 'completed').length;
|
|
46
|
-
context.push(` Tasks: ${completed}/${activeTasks.length} completed`);
|
|
47
|
-
}
|
|
48
|
-
break; // Only inject for the first matched feature
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Check for implement/code intent while not in implement phase
|
|
53
|
-
const active = getActiveFeature();
|
|
54
|
-
if (active) {
|
|
55
|
-
const { name, feature } = active;
|
|
56
|
-
|
|
57
|
-
const activePhase = feature.phase || derivePhaseForFeature(name);
|
|
58
|
-
const codeKeywords = ['implement', 'code', 'start coding', 'write the code', 'build it', 'let\'s build'];
|
|
59
|
-
const wantsToCode = codeKeywords.some(kw => promptLower.includes(kw));
|
|
60
|
-
|
|
61
|
-
if (wantsToCode && activePhase !== 'implement' && activePhase !== 'sync') {
|
|
62
|
-
context.push(
|
|
63
|
-
`[morph-spec] WARNING: Feature '${name}' is in '${activePhase}' phase, not 'implement'.` +
|
|
64
|
-
` Complete the current phase first or advance: morph-spec phase advance ${name}`
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Coding intent during implement phase but no task in_progress
|
|
69
|
-
if (wantsToCode && activePhase === 'implement') {
|
|
70
|
-
const hasActiveTask = feature.taskList?.some(t => t.status === 'in_progress') ||
|
|
71
|
-
feature.tasks?.inProgress > 0; // fallback for legacy state
|
|
72
|
-
if (!hasActiveTask) {
|
|
73
|
-
context.push(
|
|
74
|
-
`[morph-spec] REMINDER: No task is currently in_progress for '${name}'.` +
|
|
75
|
-
` Start a task before coding:\n` +
|
|
76
|
-
` morph-spec task start ${name} <id> # mark it in progress`
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Check for approval intent
|
|
82
|
-
const approvalKeywords = ['approve', 'approved', 'looks good', 'lgtm', 'ship it'];
|
|
83
|
-
const wantsToApprove = approvalKeywords.some(kw => promptLower.includes(kw));
|
|
84
|
-
|
|
85
|
-
if (wantsToApprove) {
|
|
86
|
-
const pending = getPendingGates(name);
|
|
87
|
-
if (pending.length > 0) {
|
|
88
|
-
context.push(
|
|
89
|
-
`[morph-spec] To approve, use: morph-spec approve ${name} ${pending[0]}`
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Check for "next task" intent
|
|
95
|
-
if (promptLower.includes('next task') || promptLower.includes('what\'s next')) {
|
|
96
|
-
if (activePhase === 'implement') {
|
|
97
|
-
context.push(
|
|
98
|
-
`[morph-spec] Check tasks.md for the next task, then: morph-spec task start ${name} <id>`
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (context.length === 0) {
|
|
105
|
-
logHookActivity('enrich-prompt', 'UserPromptSubmit', 'ok');
|
|
106
|
-
pass();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
logHookActivity('enrich-prompt', 'UserPromptSubmit', 'context_injected');
|
|
110
|
-
injectContext(context.join('\n'));
|
|
111
|
-
} catch {
|
|
112
|
-
// Fail-open
|
|
113
|
-
process.exit(0);
|
|
114
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared helpers for post-compact context restoration.
|
|
3
|
-
*
|
|
4
|
-
* Used by:
|
|
5
|
-
* - pre-compact hook: buildRichContext() to enrich memory file
|
|
6
|
-
* - post-compact-restore hook: buildRestoreBlock() + findLatestMemoryFile() to inject context
|
|
7
|
-
*
|
|
8
|
-
* Pure functions — no I/O.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export const DECISIONS_MAX_CHARS = 3000;
|
|
12
|
-
export const MAX_PENDING_TASKS = 8;
|
|
13
|
-
export const SPEC_SUMMARY_MAX_CHARS = 3000;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Build the richContext object to embed in pre-compact memory file.
|
|
17
|
-
* @param {Object} feature - Feature state object (tasks, taskList, checkpoints)
|
|
18
|
-
* @param {string} phase - Current derived phase string
|
|
19
|
-
* @param {string} decisionsContent - Raw text of decisions.md (may be empty string)
|
|
20
|
-
* @param {string} [specContent=''] - Raw text of spec.md (may be empty string)
|
|
21
|
-
* @returns {Object} richContext
|
|
22
|
-
*/
|
|
23
|
-
export function buildRichContext(feature, phase, decisionsContent, specContent = '') {
|
|
24
|
-
const taskList = Array.isArray(feature.taskList) ? feature.taskList : [];
|
|
25
|
-
|
|
26
|
-
const inProgress = taskList
|
|
27
|
-
.filter(t => t.status === 'in_progress')
|
|
28
|
-
.map(t => t.id);
|
|
29
|
-
|
|
30
|
-
const nextPending = taskList
|
|
31
|
-
.filter(t => t.status === 'pending')
|
|
32
|
-
.slice(0, MAX_PENDING_TASKS)
|
|
33
|
-
.map(t => ({ id: t.id, title: t.title }));
|
|
34
|
-
|
|
35
|
-
const decisionsSnippet = decisionsContent.length > DECISIONS_MAX_CHARS
|
|
36
|
-
? decisionsContent.slice(0, DECISIONS_MAX_CHARS)
|
|
37
|
-
: decisionsContent;
|
|
38
|
-
|
|
39
|
-
const specSummary = specContent.length > SPEC_SUMMARY_MAX_CHARS
|
|
40
|
-
? specContent.slice(0, SPEC_SUMMARY_MAX_CHARS)
|
|
41
|
-
: specContent;
|
|
42
|
-
|
|
43
|
-
const checkpoints = Array.isArray(feature.checkpoints) ? feature.checkpoints : [];
|
|
44
|
-
const lastCheckpoint = checkpoints.length > 0
|
|
45
|
-
? { passed: checkpoints[checkpoints.length - 1].passed, checkpointNum: checkpoints[checkpoints.length - 1].checkpointNum }
|
|
46
|
-
: null;
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
phase,
|
|
50
|
-
tasks: feature.tasks || {},
|
|
51
|
-
decisionsSnippet,
|
|
52
|
-
inProgress,
|
|
53
|
-
nextPending,
|
|
54
|
-
specSummary,
|
|
55
|
-
lastCheckpoint,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Build the context block string to inject in session-start after compact.
|
|
61
|
-
* @param {Object} memorySnapshot - Parsed pre-compact-*.json file contents
|
|
62
|
-
* @returns {string|null} Block string, or null if no richContext present
|
|
63
|
-
*/
|
|
64
|
-
export function buildRestoreBlock(memorySnapshot) {
|
|
65
|
-
const rc = memorySnapshot?.richContext;
|
|
66
|
-
if (!rc) return null;
|
|
67
|
-
|
|
68
|
-
const { timestamp, activeFeature } = memorySnapshot;
|
|
69
|
-
const { phase, tasks, decisionsSnippet, inProgress, nextPending, specSummary, lastCheckpoint } = rc;
|
|
70
|
-
|
|
71
|
-
const done = tasks?.completed ?? '?';
|
|
72
|
-
const total = tasks?.total ?? '?';
|
|
73
|
-
|
|
74
|
-
const lines = [
|
|
75
|
-
`\uD83D\uDD04 POST-COMPACT RESTORE \u2014 context from pre-compact snapshot (${timestamp})`,
|
|
76
|
-
`Active feature: ${activeFeature} | Phase: ${phase} | Tasks: ${done}/${total}`,
|
|
77
|
-
];
|
|
78
|
-
|
|
79
|
-
if (lastCheckpoint) {
|
|
80
|
-
lines.push(`Last checkpoint: #${lastCheckpoint.checkpointNum} (${lastCheckpoint.passed ? 'passed' : 'failed'})`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (specSummary) {
|
|
84
|
-
lines.push('');
|
|
85
|
-
lines.push('Spec summary (at time of compact):');
|
|
86
|
-
lines.push(specSummary);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (decisionsSnippet) {
|
|
90
|
-
lines.push('');
|
|
91
|
-
lines.push('Key decisions (at time of compact):');
|
|
92
|
-
lines.push(decisionsSnippet);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (inProgress.length > 0) {
|
|
96
|
-
lines.push('');
|
|
97
|
-
lines.push(`In progress at compact time: [${inProgress.join(', ')}]`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (nextPending.length > 0) {
|
|
101
|
-
lines.push('');
|
|
102
|
-
lines.push('Next pending at compact time:');
|
|
103
|
-
for (const t of nextPending) {
|
|
104
|
-
lines.push(` [${t.id}] ${t.title}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return lines.join('\n');
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Find the most recent pre-compact memory file from a list of filenames.
|
|
113
|
-
* Files named pre-compact-{ISO-timestamp-sanitized}.json — sort descending picks latest.
|
|
114
|
-
* @param {string[]} filenames - Array of filenames in the memory directory
|
|
115
|
-
* @returns {string|null} Latest filename, or null if none found
|
|
116
|
-
*/
|
|
117
|
-
export function findLatestMemoryFile(filenames) {
|
|
118
|
-
const matches = filenames
|
|
119
|
-
.filter(f => f.startsWith('pre-compact-') && f.endsWith('.json'))
|
|
120
|
-
.sort()
|
|
121
|
-
.reverse();
|
|
122
|
-
return matches.length > 0 ? matches[0] : null;
|
|
123
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure helper functions for the dispatch PostToolUse hook.
|
|
3
|
-
*
|
|
4
|
-
* Extracted for testability — no I/O, no process.exit, no stdin/stdout.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Extract VALIDATION DISPATCH JSON from tool output string.
|
|
9
|
-
* Strips ANSI escape codes and parses the JSON block between delimiters.
|
|
10
|
-
*
|
|
11
|
-
* @param {string} toolOutput - Raw tool output (may contain ANSI codes)
|
|
12
|
-
* @returns {object|null} - Parsed dispatch object or null
|
|
13
|
-
*/
|
|
14
|
-
export function extractValidationDispatch(toolOutput) {
|
|
15
|
-
if (!toolOutput || typeof toolOutput !== 'string') return null;
|
|
16
|
-
|
|
17
|
-
// Strip ANSI escape codes (chalk colors)
|
|
18
|
-
const clean = toolOutput.replace(/\x1B\[[0-9;]*m/g, '');
|
|
19
|
-
|
|
20
|
-
const match = clean.match(/--- VALIDATION DISPATCH ---\s*\n([\s\S]*?)\n\s*--- END VALIDATION DISPATCH ---/);
|
|
21
|
-
if (!match) return null;
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const dispatch = JSON.parse(match[1]);
|
|
25
|
-
if (!dispatch?.validators?.length) return null;
|
|
26
|
-
return dispatch;
|
|
27
|
-
} catch {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Format a dispatch object as a mandatory instruction string for Claude.
|
|
34
|
-
*
|
|
35
|
-
* @param {object} dispatch - Dispatch object with validators array
|
|
36
|
-
* @returns {string} - Formatted injection text
|
|
37
|
-
*/
|
|
38
|
-
export function formatValidatorInstruction(dispatch) {
|
|
39
|
-
if (!dispatch?.validators?.length) return '';
|
|
40
|
-
|
|
41
|
-
const lines = [
|
|
42
|
-
'🔍 MANDATORY VALIDATOR DISPATCH',
|
|
43
|
-
'════════════════════════════════════════',
|
|
44
|
-
'You MUST dispatch each validator below as a READ-ONLY subagent using the Agent tool.',
|
|
45
|
-
'Do NOT skip this step. Do NOT proceed until all blocking validators pass.',
|
|
46
|
-
'',
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
for (const v of dispatch.validators) {
|
|
50
|
-
const blocks = v.blocksOnFail !== false;
|
|
51
|
-
const sev = v.severity || 'error';
|
|
52
|
-
lines.push(`### ${v.title} (${v.id})`);
|
|
53
|
-
lines.push(`Severity: ${sev} | Blocks: ${blocks}`);
|
|
54
|
-
if (v.checks?.length) lines.push(`Checks: ${v.checks.join(', ')}`);
|
|
55
|
-
lines.push('');
|
|
56
|
-
if (v.taskPrompt) {
|
|
57
|
-
lines.push('Subagent prompt:');
|
|
58
|
-
lines.push(v.taskPrompt);
|
|
59
|
-
}
|
|
60
|
-
lines.push('---');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
lines.push('');
|
|
64
|
-
lines.push('DISPATCH INSTRUCTIONS:');
|
|
65
|
-
lines.push('1. Use the Agent tool for each validator. Set subagent_type to the validator name shown above.');
|
|
66
|
-
lines.push('2. Use model="haiku" for efficiency. Each validator is read-only.');
|
|
67
|
-
lines.push('3. Each must return { "passed": boolean, "issues": [] }');
|
|
68
|
-
lines.push('4. If any blocking validator (Blocks: true) fails, fix issues before proceeding.');
|
|
69
|
-
|
|
70
|
-
return lines.join('\n');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Parse `morph-spec task done <feature> <taskId>` from a bash command string.
|
|
75
|
-
* Returns { featureName, taskId } or null if the command doesn't match.
|
|
76
|
-
*
|
|
77
|
-
* @param {string} command
|
|
78
|
-
* @returns {{ featureName: string, taskId: string } | null}
|
|
79
|
-
*/
|
|
80
|
-
export function parseTaskDoneCommand(command) {
|
|
81
|
-
if (!command || typeof command !== 'string') return null;
|
|
82
|
-
const match = command.match(/morph-spec\s+task\s+done\s+(\S+)\s+(\S+)/);
|
|
83
|
-
if (!match) return null;
|
|
84
|
-
return { featureName: match[1], taskId: match[2] };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Parse `morph-spec phase advance <feature>` from a bash command string.
|
|
89
|
-
* Returns { featureName } or null if the command doesn't match.
|
|
90
|
-
*
|
|
91
|
-
* @param {string} command
|
|
92
|
-
* @returns {{ featureName: string } | null}
|
|
93
|
-
*/
|
|
94
|
-
export function parsePhaseAdvanceCommand(command) {
|
|
95
|
-
if (!command || typeof command !== 'string') return null;
|
|
96
|
-
const match = command.match(/morph-spec\s+phase\s+advance\s+(\S+)/);
|
|
97
|
-
if (!match) return null;
|
|
98
|
-
return { featureName: match[1] };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Parse `morph-spec dispatch-agents <feature> <phase>` from a bash command string.
|
|
103
|
-
* Returns { featureName, phase } or null if the command doesn't match.
|
|
104
|
-
*
|
|
105
|
-
* @param {string} command
|
|
106
|
-
* @returns {{ featureName: string, phase: string } | null}
|
|
107
|
-
*/
|
|
108
|
-
export function parseDispatchAgentsCommand(command) {
|
|
109
|
-
if (!command || typeof command !== 'string') return null;
|
|
110
|
-
const match = command.match(/morph-spec\s+dispatch-agents\s+(\S+)\s+(\S+)/);
|
|
111
|
-
if (!match) return null;
|
|
112
|
-
return { featureName: match[1], phase: match[2] };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Convert a subagent_type string back to a morph agent ID.
|
|
117
|
-
* Reverses the mapping used when dispatching agents as subagents.
|
|
118
|
-
*
|
|
119
|
-
* Example: 'morph-nextjs-expert' → 'nextjs-expert'
|
|
120
|
-
*
|
|
121
|
-
* @param {string} subagentType - The subagent_type value (e.g. 'morph-nextjs-expert')
|
|
122
|
-
* @returns {string|null} - Agent ID without 'morph-' prefix, or null if invalid
|
|
123
|
-
*/
|
|
124
|
-
export function subagentTypeToAgentId(subagentType) {
|
|
125
|
-
if (!subagentType || typeof subagentType !== 'string') return null;
|
|
126
|
-
if (!subagentType.startsWith('morph-')) return null;
|
|
127
|
-
const agentId = subagentType.slice('morph-'.length);
|
|
128
|
-
if (!agentId) return null;
|
|
129
|
-
return agentId;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Extract the tool result string from a PostToolUse hook payload.
|
|
134
|
-
* Handles multiple possible formats.
|
|
135
|
-
*
|
|
136
|
-
* @param {object} payload - Hook payload
|
|
137
|
-
* @returns {string} - Tool output string (empty string if not found)
|
|
138
|
-
*/
|
|
139
|
-
export function getToolOutput(payload) {
|
|
140
|
-
if (!payload) return '';
|
|
141
|
-
const result = payload.tool_result;
|
|
142
|
-
if (typeof result === 'string') return result;
|
|
143
|
-
if (result && typeof result === 'object') {
|
|
144
|
-
return result.stdout || result.content || '';
|
|
145
|
-
}
|
|
146
|
-
return '';
|
|
147
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure helper functions for the skill-reminder PostToolUse hook.
|
|
3
|
-
* Extracted to shared/ so they can be unit-tested without running the hook entry point.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Parse `morph-spec task start <feature> <taskId>` from a bash command string.
|
|
8
|
-
* Returns { featureName, taskId } or null if the command doesn't match.
|
|
9
|
-
*
|
|
10
|
-
* @param {string} command
|
|
11
|
-
* @returns {{ featureName: string, taskId: string } | null}
|
|
12
|
-
*/
|
|
13
|
-
export function parseTaskStartCommand(command) {
|
|
14
|
-
if (!command || typeof command !== 'string') return null;
|
|
15
|
-
const match = command.match(/morph-spec\s+task\s+start\s+(\S+)\s+(\S+)/);
|
|
16
|
-
if (!match) return null;
|
|
17
|
-
return { featureName: match[1], taskId: match[2] };
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Build the mandatory skill reminder message for a task start event.
|
|
22
|
-
* Returns null if no relevant skills exist for this phase at task-start time.
|
|
23
|
-
*
|
|
24
|
-
* Relevant triggers:
|
|
25
|
-
* - beforeEachTask → invoke NOW before writing any code
|
|
26
|
-
* - beforeTaskDone → invoke BEFORE marking task done
|
|
27
|
-
* - onBugOrUnexpected → invoke IF a bug is encountered
|
|
28
|
-
*
|
|
29
|
-
* @param {string} featureName
|
|
30
|
-
* @param {string} taskId
|
|
31
|
-
* @param {string} phase
|
|
32
|
-
* @param {Object|null} phasesData - Parsed phases.json content
|
|
33
|
-
* @returns {string|null}
|
|
34
|
-
*/
|
|
35
|
-
export function buildSkillReminderMessage(featureName, taskId, phase, phasesData) {
|
|
36
|
-
try {
|
|
37
|
-
const allSkills = phasesData?.phases?.[phase]?.requiredSkills;
|
|
38
|
-
if (!allSkills || allSkills.length === 0) return null;
|
|
39
|
-
|
|
40
|
-
const nowSkills = allSkills.filter(s => s.trigger === 'beforeEachTask');
|
|
41
|
-
const laterSkills = allSkills.filter(s => s.trigger === 'beforeTaskDone');
|
|
42
|
-
const bugSkills = allSkills.filter(s => s.trigger === 'onBugOrUnexpected');
|
|
43
|
-
|
|
44
|
-
if (nowSkills.length === 0 && laterSkills.length === 0) return null;
|
|
45
|
-
|
|
46
|
-
const lines = [];
|
|
47
|
-
lines.push(`⚠️ MORPH-SPEC SKILL REMINDER — Task ${taskId} started (phase: ${phase})`);
|
|
48
|
-
lines.push('');
|
|
49
|
-
|
|
50
|
-
if (nowSkills.length > 0) {
|
|
51
|
-
lines.push('INVOKE NOW before writing any code:');
|
|
52
|
-
for (const s of nowSkills) {
|
|
53
|
-
lines.push(` → Skill(${s.skill})`);
|
|
54
|
-
}
|
|
55
|
-
lines.push('');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (laterSkills.length > 0) {
|
|
59
|
-
lines.push('INVOKE BEFORE marking task done:');
|
|
60
|
-
for (const s of laterSkills) {
|
|
61
|
-
lines.push(` → Skill(${s.skill})`);
|
|
62
|
-
}
|
|
63
|
-
lines.push('');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (bugSkills.length > 0) {
|
|
67
|
-
lines.push('IF you encounter a bug or unexpected behavior:');
|
|
68
|
-
for (const s of bugSkills) {
|
|
69
|
-
lines.push(` → Skill(${s.skill})`);
|
|
70
|
-
}
|
|
71
|
-
lines.push('');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
lines.push('These are MANDATORY. Use the Skill() tool. Do NOT skip.');
|
|
75
|
-
return lines.join('\n');
|
|
76
|
-
} catch {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stale Task Reset — shared hook utility
|
|
3
|
-
*
|
|
4
|
-
* Detects tasks stuck in `in_progress` for longer than STALE_MS and resets
|
|
5
|
-
* them to `pending`. Works for both v2 (tasks is array) and v3 (taskList)
|
|
6
|
-
* state formats. Pure function: mutates state in-place, returns reset log.
|
|
7
|
-
*
|
|
8
|
-
* Stale threshold: 1 hour (hardcoded — not user-configurable)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export const STALE_MS = 60 * 60 * 1000; // 1 hour
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Determine if a task is stale (in_progress for longer than STALE_MS).
|
|
15
|
-
* @param {Object} task
|
|
16
|
-
* @param {number} [now] - timestamp for testability (defaults to Date.now())
|
|
17
|
-
* @returns {boolean}
|
|
18
|
-
*/
|
|
19
|
-
export function isStaleTask(task, now = Date.now()) {
|
|
20
|
-
if (task.status !== 'in_progress') return false;
|
|
21
|
-
if (!task.startedAt) return true; // missing timestamp → treat as stale
|
|
22
|
-
return now - new Date(task.startedAt).getTime() > STALE_MS;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Reset all stale in_progress tasks across all features in state.
|
|
27
|
-
* Mutates state in-place. Returns log of reset task identifiers.
|
|
28
|
-
*
|
|
29
|
-
* @param {Object} state - Full state object (state.features)
|
|
30
|
-
* @param {number} [now] - Timestamp for testability
|
|
31
|
-
* @returns {string[]} resetLog — entries like "feature-name/T008"
|
|
32
|
-
*/
|
|
33
|
-
export function resetStaleTasks(state, now = Date.now()) {
|
|
34
|
-
const resetLog = [];
|
|
35
|
-
|
|
36
|
-
for (const [featureName, feature] of Object.entries(state.features || {})) {
|
|
37
|
-
// Resolve task list for both v2 (array) and v3 (taskList) formats
|
|
38
|
-
const isV2 = Array.isArray(feature.tasks);
|
|
39
|
-
const list = isV2 ? feature.tasks : (feature.taskList || []);
|
|
40
|
-
|
|
41
|
-
for (const task of list) {
|
|
42
|
-
if (isStaleTask(task, now)) {
|
|
43
|
-
task.status = 'pending';
|
|
44
|
-
delete task.startedAt;
|
|
45
|
-
resetLog.push(`${featureName}/${task.id ?? '(unknown)'}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Re-sync counters (v3 only — v2 tasks IS the list, no separate counters)
|
|
50
|
-
if (!isV2 && feature.tasks) {
|
|
51
|
-
feature.tasks.inProgress = list.filter(t => t.status === 'in_progress').length;
|
|
52
|
-
feature.tasks.pending = list.filter(t => t.status === 'pending').length;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return resetLog;
|
|
57
|
-
}
|