@polymorphism-tech/morph-spec 8.2.1 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +70 -99
  2. package/bin/morph-spec.js +12 -0
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +76 -135
  5. package/docs/QUICKSTART.md +35 -51
  6. package/framework/CLAUDE.md +1 -0
  7. package/framework/agents.json +3 -1
  8. package/framework/hooks/README.md +3 -0
  9. package/framework/hooks/claude-code/core/telemetry-log.js +102 -0
  10. package/framework/hooks/shared/telemetry-logger.js +75 -0
  11. package/framework/rules/ai-agents-standards.md +1 -0
  12. package/framework/schemas/feature.schema.json +0 -5
  13. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  14. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  15. package/framework/skills/core/morph-apply/SKILL.md +1 -1
  16. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  17. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  18. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  19. package/framework/skills/core/morph-implement/SKILL.md +1 -1
  20. package/framework/skills/core/morph-plan/SKILL.md +1 -1
  21. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  22. package/framework/skills/core/morph-review/SKILL.md +1 -1
  23. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  24. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  25. package/framework/standards/STANDARDS.json +43 -1
  26. package/framework/standards/ai-agents/observability-patterns.md +156 -0
  27. package/framework/standards/ai-agents/production.md +3 -47
  28. package/framework/templates/REGISTRY.json +0 -93
  29. package/package.json +1 -1
  30. package/src/commands/observability/telemetry.js +75 -0
  31. package/src/core/state/state-manager.js +0 -128
  32. package/src/dashboard/app/pages/LiveState.jsx +0 -43
  33. package/src/lib/detectors/design-system-detector.js +0 -43
  34. package/src/lib/generators/index.js +0 -1
  35. package/src/lib/telemetry/telemetry-reader.js +103 -0
  36. package/src/lib/validators/validation-runner.js +9 -73
  37. package/src/utils/hooks-installer.js +27 -3
  38. package/framework/templates/context/CONTEXT-FEATURE.md +0 -269
  39. package/framework/templates/context/CONTEXT.md +0 -181
  40. package/src/lib/agents/metrics-aggregator.js +0 -56
  41. package/src/lib/agents/metrics-calculator.js +0 -125
  42. package/src/lib/generators/metadata-extractor.js +0 -385
@@ -150,49 +150,6 @@ export function detectDesignSystem(projectPath, featureName = null) {
150
150
  return result;
151
151
  }
152
152
 
153
- /**
154
- * Check if UI agents are active for a feature
155
- * @param {string} projectPath - Root path of the project
156
- * @param {string} featureName - Feature name
157
- * @returns {boolean} True if UI agents are active
158
- */
159
- export function hasUIAgentsActive(projectPath, featureName) {
160
- try {
161
- const statePath = join(projectPath, '.morph/state.json');
162
-
163
- if (!existsSync(statePath)) {
164
- return false;
165
- }
166
-
167
- const state = JSON.parse(readFileSync(statePath, 'utf8'));
168
-
169
- // Handle both array and object format
170
- let feature;
171
- if (Array.isArray(state.features)) {
172
- feature = state.features.find(f => f.name === featureName);
173
- } else if (state.features && typeof state.features === 'object') {
174
- feature = state.features[featureName];
175
- }
176
-
177
- if (!feature || !feature.activeAgents) {
178
- return false;
179
- }
180
-
181
- // UI design agents that require a design system before implement phase.
182
- // Implementation-only agents (nextjs-expert, css-specialist)
183
- // build from existing designs and do NOT trigger this gate.
184
- const uiAgents = [
185
- 'ui-designer',
186
- 'uiux-designer',
187
- 'ui-ux-designer',
188
- ];
189
-
190
- return feature.activeAgents.some(agentId => uiAgents.includes(agentId));
191
- } catch {
192
- return false;
193
- }
194
- }
195
-
196
153
  /**
197
154
  * Get recommended design system locations based on project type
198
155
  * @param {string} projectPath - Root path of the project
@@ -6,6 +6,5 @@
6
6
 
7
7
  export * from './design-system-generator.js';
8
8
  export * from './handoff-generator.js';
9
- export * from './metadata-extractor.js';
10
9
  export * from './architecture-scanner.js';
11
10
  export * from './scaffold-generator.js';
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Telemetry aggregation — reads the append-only harness event stream
3
+ * (.morph/logs/events.jsonl, written by the telemetry-log hook) and summarizes
4
+ * the whole execution path: which skills ran, which personas were dispatched,
5
+ * which standards (knowledge base) were read, the tool mix, and the prompts.
6
+ *
7
+ * Pure aggregation (`aggregateTelemetry`) is separated from disk I/O
8
+ * (`loadTelemetry`) so the summary logic is unit-testable without fixtures —
9
+ * the same split fleet-scanner uses. CLI-side; runs in the target project cwd.
10
+ */
11
+
12
+ import { readFileSync, existsSync } from 'fs';
13
+ import { join } from 'path';
14
+
15
+ const EVENTS_FILE = join('.morph', 'logs', 'events.jsonl');
16
+
17
+ /**
18
+ * Read the raw event stream from a project root. A corrupt/truncated line is
19
+ * skipped, not fatal. Returns [] when there is no log.
20
+ *
21
+ * @param {string} [projectPath]
22
+ * @returns {Object[]}
23
+ */
24
+ export function loadTelemetry(projectPath) {
25
+ try {
26
+ const p = join(projectPath || process.cwd(), EVENTS_FILE);
27
+ if (!existsSync(p)) return [];
28
+ const events = [];
29
+ for (const line of readFileSync(p, 'utf-8').split('\n')) {
30
+ const trimmed = line.trim();
31
+ if (!trimmed) continue;
32
+ try { events.push(JSON.parse(trimmed)); } catch { /* skip corrupt line */ }
33
+ }
34
+ return events;
35
+ } catch {
36
+ return [];
37
+ }
38
+ }
39
+
40
+ function rank(map) {
41
+ return [...map.entries()]
42
+ .map(([name, count]) => ({ name, count }))
43
+ .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
44
+ }
45
+
46
+ /**
47
+ * Summarize an event stream.
48
+ *
49
+ * @param {Object[]} events - raw events (from loadTelemetry)
50
+ * @param {{ feature?: string }} [opts] - optional feature filter
51
+ * @returns {{
52
+ * total: number,
53
+ * skills: Array<{name:string,count:number}>,
54
+ * personas: Array<{name:string,count:number}>,
55
+ * standards: Array<{path:string,count:number}>,
56
+ * tools: Array<{name:string,count:number}>,
57
+ * prompts: Array<{ts:string,source:'user'|'agent',feature:?string,phase:?string,text:string}>,
58
+ * features: string[]
59
+ * }}
60
+ */
61
+ export function aggregateTelemetry(events, opts = {}) {
62
+ const scoped = opts.feature
63
+ ? (events || []).filter(e => e.feature === opts.feature)
64
+ : (events || []);
65
+
66
+ const skills = new Map();
67
+ const personas = new Map();
68
+ const standards = new Map();
69
+ const tools = new Map();
70
+ const features = new Set();
71
+ const prompts = [];
72
+
73
+ for (const e of scoped) {
74
+ if (e.tool) tools.set(e.tool, (tools.get(e.tool) || 0) + 1);
75
+ if (e.feature) features.add(e.feature);
76
+
77
+ if (e.tool === 'Skill' && e.skill) {
78
+ skills.set(e.skill, (skills.get(e.skill) || 0) + 1);
79
+ } else if ((e.tool === 'Agent' || e.tool === 'Task') && e.persona) {
80
+ personas.set(e.persona, (personas.get(e.persona) || 0) + 1);
81
+ }
82
+
83
+ if (e.kind === 'standard' && e.target) {
84
+ standards.set(e.target, (standards.get(e.target) || 0) + 1);
85
+ }
86
+
87
+ if (e.tool === 'UserPrompt' && typeof e.prompt === 'string') {
88
+ prompts.push({ ts: e.ts, source: 'user', feature: e.feature ?? null, phase: e.phase ?? null, text: e.prompt });
89
+ } else if ((e.tool === 'Agent' || e.tool === 'Task') && typeof e.prompt === 'string' && e.prompt) {
90
+ prompts.push({ ts: e.ts, source: 'agent', feature: e.feature ?? null, phase: e.phase ?? null, text: e.prompt });
91
+ }
92
+ }
93
+
94
+ return {
95
+ total: scoped.length,
96
+ skills: rank(skills),
97
+ personas: rank(personas),
98
+ standards: rank(standards).map(({ name, count }) => ({ path: name, count })),
99
+ tools: rank(tools),
100
+ prompts,
101
+ features: [...features],
102
+ };
103
+ }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * MORPH-SPEC Validation Runner
3
3
  *
4
- * Unified orchestrator that runs the right validators based on the feature's
5
- * active agents and decisions. Called by task-done before marking tasks complete.
4
+ * Unified orchestrator that runs the right validators based on the project's
5
+ * stack profile. Called by task-done before marking tasks complete.
6
6
  *
7
7
  * MORPH-SPEC 3.0 - Phase A: Active Validation Pipeline
8
8
  */
@@ -10,16 +10,10 @@
10
10
  import { existsSync, readFileSync } from 'fs';
11
11
  import { join } from 'path';
12
12
  import chalk from 'chalk';
13
- import { loadState } from '../../core/state/state-manager.js';
14
13
  import { getAbsoluteOutputPath, resolveContractsOutputType } from '../../core/paths/output-schema.js';
15
14
  import { getStackProfile } from '../stack/stack-profile.js';
16
15
  import { aggregateScores, loadRubric } from './score-aggregator.js';
17
16
 
18
- /** Validators that only make sense for .NET projects. */
19
- const DOTNET_ONLY_VALIDATORS = new Set([
20
- 'architecture', 'packages', 'contract-compliance'
21
- ]);
22
-
23
17
  /**
24
18
  * Heavy validators skipped in 'light' mode (checkpoint-only).
25
19
  * Light mode runs only spec, content, and stack-specific lightweight validators.
@@ -29,31 +23,6 @@ const HEAVY_VALIDATORS = new Set([
29
23
  'css', 'design-system'
30
24
  ]);
31
25
 
32
- /**
33
- * Load agent → validators map from agents.json (data-driven)
34
- * @param {string} projectPath - Root path of the project
35
- * @returns {Object} Map of agent IDs to validator arrays
36
- */
37
- function loadAgentValidatorMap(projectPath) {
38
- try {
39
- const agentsPath = join(projectPath, '.morph/framework/agents.json');
40
- const agentsConfig = JSON.parse(readFileSync(agentsPath, 'utf8'));
41
-
42
- const map = {};
43
- Object.entries(agentsConfig.agents || {}).forEach(([agentId, agent]) => {
44
- if (!agentId.startsWith('_comment') && agent.validators && agent.validators.length > 0) {
45
- map[agentId] = agent.validators;
46
- }
47
- });
48
-
49
- return map;
50
- } catch (err) {
51
- console.warn(chalk.yellow('⚠️ Failed to load agents.json - no validators will run'));
52
- console.warn(chalk.gray(` Run 'morph-spec init' to set up MORPH-SPEC configuration`));
53
- return {}; // Empty map = honest failure
54
- }
55
- }
56
-
57
26
  /**
58
27
  * Run all relevant validators for a feature
59
28
  *
@@ -61,7 +30,7 @@ function loadAgentValidatorMap(projectPath) {
61
30
  * @param {string} featureName - Feature name from state.json
62
31
  * @param {Object} options - Options
63
32
  * @param {boolean} options.verbose - Show detailed output
64
- * @param {string[]} options.validators - Specific validators to run (overrides agent-based detection)
33
+ * @param {string[]} options.validators - Specific validators to run (overrides stack-based detection)
65
34
  * @returns {Object} { passed, errors, warnings, issues, summary }
66
35
  */
67
36
  export async function runValidation(projectPath, featureName, options = {}) {
@@ -81,7 +50,7 @@ export async function runValidation(projectPath, featureName, options = {}) {
81
50
  };
82
51
 
83
52
  // Determine which validators to run
84
- const validatorIds = options.validators || detectValidators(featureName, projectPath);
53
+ const validatorIds = options.validators || detectValidators(projectPath);
85
54
 
86
55
  // Filter by mode: 'light' skips heavy validators, 'full' (default) runs all
87
56
  const mode = options.mode || 'full';
@@ -98,17 +67,8 @@ export async function runValidation(projectPath, featureName, options = {}) {
98
67
  validatorIds.push(...constraints.enabledValidators);
99
68
 
100
69
  if (validatorIds.length === 0) {
101
- // Distinguish: no agents vs. no validators mapped
102
- const state = loadState(false);
103
- const activeAgents = state?.features?.[featureName]?.activeAgents || [];
104
- if (activeAgents.length === 0) {
105
- result.summary = 'No validators ran — feature has no active agents';
106
- console.log(chalk.gray('\n ℹ️ No validators configured (no active agents in state.json)'));
107
- console.log(chalk.gray(' Add agents: morph-spec state add-agent ' + featureName + ' <agent-id>'));
108
- } else {
109
- result.summary = `No validators mapped for active agents: ${activeAgents.join(', ')}`;
110
- console.log(chalk.gray(`\n ℹ️ Agents [${activeAgents.join(', ')}] have no validators in agents.json`));
111
- }
70
+ result.summary = 'No validators ran — no validators configured for this stack';
71
+ console.log(chalk.gray('\n ℹ️ No validators configured for this stack'));
112
72
  result._skipReason = result.summary;
113
73
  return result;
114
74
  }
@@ -191,37 +151,13 @@ export async function runValidation(projectPath, featureName, options = {}) {
191
151
  }
192
152
 
193
153
  /**
194
- * Detect which validators to run based on feature's active agents
195
- * @param {string} featureName - Feature name
154
+ * Detect which validators to run, driven by the project's stack profile.
196
155
  * @param {string} projectPath - Project root path (default: current directory)
197
156
  * @returns {string[]} Array of validator IDs to run
198
157
  */
199
- function detectValidators(featureName, projectPath = '.') {
158
+ function detectValidators(projectPath = '.') {
200
159
  const profile = getStackProfile(projectPath);
201
- const state = loadState(false);
202
-
203
- if (!state || !state.features[featureName]) {
204
- return [...profile.defaultValidators]; // stack-aware defaults
205
- }
206
-
207
- const feature = state.features[featureName];
208
- const agents = feature.activeAgents || [];
209
- const validatorSet = new Set(profile.defaultValidators); // stack defaults first
210
-
211
- const agentValidatorMap = loadAgentValidatorMap(projectPath);
212
- for (const agentId of agents) {
213
- const validators = agentValidatorMap[agentId] || [];
214
- validators.forEach(v => validatorSet.add(v));
215
- }
216
-
217
- // Filter out .NET-only validators for non-.NET projects
218
- if (!profile.isDotnet) {
219
- for (const v of DOTNET_ONLY_VALIDATORS) {
220
- validatorSet.delete(v);
221
- }
222
- }
223
-
224
- return Array.from(validatorSet);
160
+ return [...profile.defaultValidators];
225
161
  }
226
162
 
227
163
  /**
@@ -15,7 +15,7 @@ import { homedir } from 'os';
15
15
  import { execSync } from 'child_process';
16
16
 
17
17
  /** Current hooks schema version — bump when hook definitions change */
18
- const HOOKS_VERSION = '3.3.0';
18
+ const HOOKS_VERSION = '3.4.0';
19
19
 
20
20
  /** Marker for old dispatch.js (v1) */
21
21
  const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
@@ -165,13 +165,37 @@ const MORPH_HOOKS = [
165
165
  ]
166
166
  },
167
167
 
168
- // === PostToolUse: All — loop detection ===
168
+ // === PostToolUse: All — telemetry capture (silent) + loop detection ===
169
+ // telemetry-log is the single generic vantage point of an NLH: it appends
170
+ // every tool call (skill, sub-agent dispatch, knowledge-base read, …) to
171
+ // .morph/logs/events.jsonl. Silent and fail-open — never blocks, never
172
+ // injects. `morph-spec telemetry` reads the stream back.
169
173
  {
170
174
  event: 'PostToolUse',
171
175
  matcher: null,
176
+ hooks: [
177
+ {
178
+ type: 'command',
179
+ command: 'node framework/hooks/claude-code/core/telemetry-log.js',
180
+ timeout: 5000
181
+ },
182
+ {
183
+ type: 'command',
184
+ command: 'node framework/hooks/claude-code/core/loop-detect.js',
185
+ timeout: 5000
186
+ }
187
+ ]
188
+ },
189
+
190
+ // === UserPromptSubmit — telemetry capture of the user's prompt (silent) ===
191
+ // The only vantage point on the user's own prompt; completes the execution
192
+ // path alongside sub-agent prompts captured on PostToolUse.
193
+ {
194
+ event: 'UserPromptSubmit',
195
+ matcher: null,
172
196
  hooks: [{
173
197
  type: 'command',
174
- command: 'node framework/hooks/claude-code/core/loop-detect.js',
198
+ command: 'node framework/hooks/claude-code/core/telemetry-log.js',
175
199
  timeout: 5000
176
200
  }]
177
201
  },
@@ -1,269 +0,0 @@
1
- # {{FEATURE_NAME}} - Feature Context
2
-
3
- > Auto-generated by MORPH-SPEC Framework
4
- > Last updated: {{TIMESTAMP}}
5
-
6
- ## Feature Overview
7
-
8
- **Name:** {{FEATURE_NAME}}
9
- **Phase:** {{PHASE}}
10
- **Status:** {{STATUS}}
11
- **Complexity:** {{COMPLEXITY}}
12
-
13
- {{#if DESCRIPTION}}
14
- {{DESCRIPTION}}
15
- {{/if}}
16
-
17
- ---
18
-
19
- ## Progress Tracking
20
-
21
- ### Current Phase: {{PHASE}}
22
-
23
- {{PHASE_DESCRIPTION}}
24
-
25
- **Completed Phases:**
26
- {{#each COMPLETED_PHASES}}
27
- - ✅ {{name}} - {{completedAt}}
28
- {{/each}}
29
-
30
- **Next Phases:**
31
- {{#each UPCOMING_PHASES}}
32
- - ⏭️ {{name}}
33
- {{/each}}
34
-
35
- ### Task Progress
36
-
37
- **Total Tasks:** {{TASKS_TOTAL}}
38
- **Completed:** {{TASKS_COMPLETED}} ({{TASKS_PROGRESS}}%)
39
- **In Progress:** {{TASKS_IN_PROGRESS}}
40
- **Blocked:** {{TASKS_BLOCKED}}
41
-
42
- {{#if CURRENT_TASK}}
43
- **Current Task:** {{CURRENT_TASK.id}} - {{CURRENT_TASK.title}}
44
- {{/if}}
45
-
46
- ---
47
-
48
- ## Active Agents ({{ACTIVE_AGENTS_COUNT}})
49
-
50
- ### Team Structure
51
-
52
- {{#if TEAM_LEAD}}
53
- **Team Lead:** {{TEAM_LEAD.title}} (`{{TEAM_LEAD.id}}`)
54
- {{/if}}
55
-
56
- {{#each SQUADS}}
57
- ### {{leader.title}} Squad
58
- **Leader:** `{{leader.id}}`
59
- **Members ({{members.length}}):**
60
- {{#each members}}
61
- - {{title}} (`{{id}}`) {{#if status}}[{{status}}]{{/if}}
62
- {{/each}}
63
- {{/each}}
64
-
65
- ### Validators (Run in Hooks)
66
- {{#each VALIDATORS}}
67
- - {{title}} (`{{id}}`)
68
- {{/each}}
69
-
70
- ---
71
-
72
- ## Agent Teams
73
-
74
- {{#if AGENT_TEAMS_DECISION}}
75
- {{#if AGENT_TEAMS_DECISION.useAgentTeams}}
76
- ✅ **Agent Teams Recommended**
77
-
78
- **Reason:** {{AGENT_TEAMS_DECISION.reason}}
79
- **Display Mode:** {{AGENT_TEAMS_DECISION.displayMode}}
80
- **Total Teammates:** {{AGENT_TEAMS_DECISION.totalTeammates}}
81
-
82
- **Team Composition:**
83
- - Team Lead: {{AGENT_TEAMS_DECISION.teamLead}}
84
- - Domain Leaders: {{AGENT_TEAMS_DECISION.squads}}
85
- - Validators (hooks): {{AGENT_TEAMS_DECISION.validators}}
86
-
87
- {{else}}
88
- ❌ **Single Session Recommended**
89
-
90
- **Reason:** {{AGENT_TEAMS_DECISION.reason}}
91
- **Recommendation:** {{AGENT_TEAMS_DECISION.recommendation}}
92
- {{/if}}
93
- {{/if}}
94
-
95
- ---
96
-
97
- ## Outputs Generated
98
-
99
- {{#each OUTPUTS}}
100
- ### {{type}}
101
- **Status:** {{#if generated}}✅ Generated{{else}}⏳ Pending{{/if}}
102
- {{#if path}}**Path:** `{{path}}`{{/if}}
103
- {{#if generatedAt}}**Generated:** {{generatedAt}}{{/if}}
104
- {{/each}}
105
-
106
- ---
107
-
108
- ## Decisions Log
109
-
110
- {{#each DECISIONS}}
111
- ### ADR-{{id}}: {{title}}
112
-
113
- **Status:** {{status}}
114
- **Date:** {{date}}
115
- **Decided By:** {{decidedBy}}
116
-
117
- **Context:** {{context}}
118
-
119
- **Decision:** {{decision}}
120
-
121
- {{#if constraints}}
122
- **Constraints for Validators:**
123
- ```json
124
- {{constraints}}
125
- ```
126
- {{/if}}
127
-
128
- {{#if alternatives}}
129
- **Alternatives Considered:**
130
- {{#each alternatives}}
131
- - {{name}}: {{rationale}}
132
- {{/each}}
133
- {{/if}}
134
-
135
- ---
136
- {{/each}}
137
-
138
- {{#unless DECISIONS}}
139
- No decisions documented yet. Decisions will be logged in `decisions.md` during Design phase.
140
- {{/unless}}
141
-
142
- ---
143
-
144
- ## Validation Status
145
-
146
- {{#if VALIDATION_RESULTS}}
147
- ### Latest Validation ({{VALIDATION_TIMESTAMP}})
148
-
149
- **Overall:** {{#if VALIDATION_PASSED}}✅ PASSED{{else}}❌ FAILED{{/if}}
150
-
151
- {{#each VALIDATION_RESULTS}}
152
- #### {{validator}}
153
- **Status:** {{#if passed}}✅ PASSED{{else}}❌ FAILED{{/if}}
154
- {{#if issues}}
155
- **Issues ({{issues.length}}):**
156
- {{#each issues}}
157
- - [{{severity}}] {{message}} ({{file}}:{{line}})
158
- {{/each}}
159
- {{/if}}
160
- {{/each}}
161
-
162
- {{else}}
163
- No validation runs yet. Validators run automatically via checkpoints.
164
- {{/if}}
165
-
166
- ---
167
-
168
- ## Tasks Breakdown
169
-
170
- {{#each TASKS}}
171
- ### {{id}}: {{title}}
172
-
173
- **Status:** {{status}}
174
- {{#if priority}}**Priority:** {{priority}}{{/if}}
175
- {{#if estimatedFiles}}**Estimated Files:** {{estimatedFiles}}{{/if}}
176
- {{#if dependencies}}**Dependencies:** {{dependencies}}{{/if}}
177
-
178
- {{#if description}}
179
- {{description}}
180
- {{/if}}
181
-
182
- {{#if agent}}**Assigned Agent:** `{{agent}}`{{/if}}
183
-
184
- {{#if checkpointAfter}}🔖 **Checkpoint after this task**{{/if}}
185
-
186
- {{#if completedAt}}✅ Completed: {{completedAt}}{{/if}}
187
- {{#if blockedReason}}❌ Blocked: {{blockedReason}}{{/if}}
188
-
189
- ---
190
- {{/each}}
191
-
192
- ---
193
-
194
- ## Checkpoints
195
-
196
- {{#each CHECKPOINTS}}
197
- ### {{timestamp}}
198
- **Phase:** {{phase}}
199
- **Tasks Completed:** {{tasksCompleted}}/{{tasksTotal}}
200
-
201
- {{#if note}}
202
- **Note:** {{note}}
203
- {{/if}}
204
-
205
- {{#if validation}}
206
- **Validation:** {{validation.passed}}/{{validation.total}} checks passed
207
- {{#if validation.issues}}
208
- - Issues: {{validation.issues}}
209
- {{/if}}
210
- {{/if}}
211
-
212
- ---
213
- {{/each}}
214
-
215
- ---
216
-
217
- ## Related Standards
218
-
219
- {{#each RELATED_STANDARDS}}
220
- ### {{name}}
221
- **Path:** `{{path}}`
222
- {{#if relevantSections}}
223
- **Relevant Sections:** {{relevantSections}}
224
- {{/if}}
225
- {{/each}}
226
-
227
- ---
228
-
229
- ## Quick Actions
230
-
231
- ### Continue Implementation
232
- During the implement phase, track your work with the native `TaskCreate`/`TaskUpdate`
233
- tools. Persist progress by writing `tasks.json` (`2-plan/tasks.json`), touching ONLY
234
- the mutable fields `status`, `outputs`, and `notes` — via the `Write` tool or an `Edit`
235
- that touches only those fields. The plan contract (`title`, `description`,
236
- `dependencies`, `effort`, `doneCriteria`, `group`) is frozen after Gate 2. The recap is
237
- written by the implement skill; state syncs automatically via the `state-sync` hook.
238
-
239
- ### Advance Phase
240
- ```bash
241
- # Approve the current gate and scaffold the next phase folder in one step
242
- morph-spec advance {{FEATURE_NAME}}
243
-
244
- # Validate feature outputs
245
- morph-spec validate-feature {{FEATURE_NAME}}
246
- ```
247
-
248
- ### Validation
249
- ```bash
250
- # Validate this feature's outputs against active standards
251
- morph-spec validate-feature {{FEATURE_NAME}}
252
- ```
253
-
254
- ---
255
-
256
- ## File Paths
257
-
258
- - **Outputs Directory:** `.morph/features/{{FEATURE_NAME}}/`
259
- - **Proposal:** `{{PROPOSAL_PATH}}`
260
- - **Spec:** `{{SPEC_PATH}}`
261
- - **Contracts:** `{{CONTRACTS_PATH}}`
262
- - **Tasks:** `{{TASKS_PATH}}`
263
- - **Decisions:** `{{DECISIONS_PATH}}`
264
- - **Recap:** `{{RECAP_PATH}}`
265
-
266
- ---
267
-
268
- *Generated by MORPH-SPEC Framework v{{MORPH_VERSION}}*
269
- *Feature ID: {{FEATURE_ID}}*