@polymorphism-tech/morph-spec 8.47.0 → 9.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 +48 -39
- package/README.md +3 -4
- package/bin/morph-spec.js +37 -51
- package/bin/validate.js +3 -111
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +75 -18
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +1 -1
- package/framework/commands/morph-preflight.md +2 -3
- package/framework/commands/morph-proposal.md +2 -50
- package/framework/commands/morph-status.md +3 -3
- package/framework/docs/CLI.md +585 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- package/framework/hooks/claude-code/core/comment-guard.js +1 -1
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
- package/framework/hooks/claude-code/core/gate-guard.js +2 -11
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
- package/framework/hooks/claude-code/core/state-sync.js +7 -21
- package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
- package/framework/hooks/claude-code/core/validate-completion.js +33 -97
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
- package/framework/hooks/claude-code/core/worktree-context.js +5 -4
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/shared/activity-logger.js +1 -1
- package/framework/hooks/shared/build-check.js +10 -6
- package/framework/hooks/shared/command-tokens.js +169 -0
- package/framework/hooks/shared/comment-rules.js +1 -1
- package/framework/hooks/shared/comment-scanner.js +10 -23
- package/framework/hooks/shared/comment-stripper.js +1 -1
- package/framework/hooks/shared/e2e-check.js +64 -27
- package/framework/hooks/shared/errata.js +72 -0
- package/framework/hooks/shared/feature-locator.js +1 -1
- package/framework/hooks/shared/frontend-locations.js +6 -0
- package/framework/hooks/shared/gate-question.js +18 -0
- package/framework/hooks/shared/phase-utils.js +15 -32
- package/framework/hooks/shared/slice-progress.js +118 -0
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +61 -87
- package/framework/hooks/shared/state-writer.js +2 -3
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -50
- package/framework/skills/README.md +3 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +149 -222
- package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
- package/framework/skills/core/morph-machine/SKILL.md +2 -2
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +137 -299
- package/framework/skills/core/morph-review/SKILL.md +111 -136
- package/framework/skills/core/morph-standards/SKILL.md +27 -39
- package/framework/skills/core/morph-uiux/SKILL.md +77 -152
- package/framework/standards/STANDARDS.json +86 -1218
- package/framework/standards/STANDARDS.meta.json +1305 -0
- package/framework/standards/ai-agents/evals-with-cache.md +1 -2
- package/framework/standards/ai-agents/setup.md +1 -1
- package/framework/standards/architecture/code-comments.md +6 -6
- package/framework/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +6 -170
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
- package/package.json +1 -1
- package/src/commands/e2e/e2e.js +2 -2
- package/src/commands/observability/cost.js +18 -60
- package/src/commands/project/doctor-procs.js +1 -1
- package/src/commands/project/doctor.js +9 -9
- package/src/commands/project/fleet.js +4 -4
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +4 -0
- package/src/commands/project/setup-infra-cmd.js +2 -1
- package/src/commands/project/stack-scan.js +58 -0
- package/src/commands/project/standards.js +79 -25
- package/src/commands/project/status.js +29 -69
- package/src/commands/project/update.js +17 -1
- package/src/commands/project/worktree.js +1 -1
- package/src/commands/state/advance.js +62 -46
- package/src/commands/state/approve.js +81 -141
- package/src/commands/state/archive.js +20 -84
- package/src/commands/state/auto-approval.js +68 -0
- package/src/commands/state/create.js +28 -1
- package/src/commands/state/delete.js +1 -1
- package/src/commands/state/finish.js +11 -23
- package/src/commands/state/gate-check.js +85 -84
- package/src/commands/state/gate-decision.js +6 -4
- package/src/commands/state/handoff.js +1 -1
- package/src/commands/state/list.js +9 -9
- package/src/commands/state/report-check.js +157 -311
- package/src/commands/state/retype.js +9 -3
- package/src/commands/validation/mutate.js +77 -14
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +60 -135
- package/src/commands/validation/verify.js +40 -72
- package/src/core/paths/output-schema.js +19 -66
- package/src/core/state/gate-order.js +5 -6
- package/src/core/state/lifecycle-audit.js +2 -2
- package/src/core/state/state-manager.js +17 -98
- package/src/core/templates/template-registry.js +2 -2
- package/src/core/templates/template-seed.js +22 -0
- package/src/core/work-types.js +10 -10
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
- package/src/dashboard/app/components/CommandPalette.jsx +5 -5
- package/src/dashboard/app/components/EntityLink.jsx +1 -1
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
- package/src/dashboard/app/lib/phases.js +2 -4
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
- package/src/dashboard/app/pages/Features.jsx +9 -9
- package/src/dashboard/app/pages/Guide.jsx +2 -2
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
- package/src/dashboard/app/pages/Standards.jsx +1 -1
- package/src/dashboard/scanner/activity-scanner.js +5 -15
- package/src/dashboard/scanner/agents-scanner.js +1 -1
- package/src/dashboard/scanner/docs-scanner.js +2 -2
- package/src/dashboard/scanner/features-scanner.js +95 -60
- package/src/dashboard/scanner/project-api.js +1 -1
- package/src/dashboard/scanner/standards-scanner.js +2 -1
- package/src/lib/ci/ci-verify.js +17 -1
- package/src/lib/ci/github-ci.js +1 -1
- package/src/lib/detectors/index.js +3 -8
- package/src/lib/generators/handoff-generator.js +34 -65
- package/src/lib/generators/settings-generator.js +11 -89
- package/src/lib/git/evidence-staleness.js +1 -1
- package/src/lib/git/tree-fingerprint.js +19 -7
- package/src/lib/git/tree-state.js +2 -2
- package/src/lib/heavy/cleanup.js +1 -5
- package/src/lib/installers/memory-installer.js +6 -8
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +46 -9
- package/src/lib/plan/artifact-vocabulary.js +60 -0
- package/src/lib/plan/checks.js +234 -0
- package/src/lib/plan/plan-parser.js +209 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/review-reports.js +222 -0
- package/src/lib/risk-detector.js +131 -140
- package/src/lib/specs/adr-index.js +50 -69
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +44 -0
- package/src/lib/specs/gfm-table.js +17 -0
- package/src/lib/specs/old-format.js +68 -0
- package/src/lib/specs/revocations.js +37 -95
- package/src/lib/specs/screen-checklist.js +18 -17
- package/src/lib/stack/dotnet-build-ignores.js +47 -0
- package/src/lib/stack/stack-scan.js +267 -0
- package/src/lib/standards/registry.js +43 -0
- package/src/lib/standards/sections.js +58 -0
- package/src/lib/tasks/eval-node.js +4 -12
- package/src/lib/tasks/test-nodes.js +1 -74
- package/src/lib/tasks/test-runner.js +75 -117
- package/src/lib/telemetry/metrics.js +8 -69
- package/src/lib/telemetry/telemetry-reader.js +5 -118
- package/src/lib/telemetry/verify-event.js +5 -7
- package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
- package/src/lib/validators/reuse/reuse-validator.js +22 -45
- package/src/lib/validators/traceability-check.js +85 -129
- package/src/lib/validators/verify-runner.js +118 -235
- package/src/lib/worktree/docker-audit.js +1 -2
- package/src/lib/worktree/fleet-scanner.js +25 -38
- package/src/scripts/setup-infra.js +9 -0
- package/src/utils/agents-installer.js +76 -9
- package/src/utils/claude-settings-manager.js +0 -35
- package/src/utils/file-copier.js +27 -1
- package/src/utils/hooks-installer.js +16 -9
- package/src/utils/skills-installer.js +1 -2
- package/framework/evals/trace-schema.md +0 -67
- package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
- package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
- package/framework/hooks/git/pre-commit/specs.sh +0 -50
- package/framework/hooks/shared/tasks-validator.js +0 -111
- package/framework/rules/morph-task-tracking.md +0 -54
- package/framework/rules/morph-workflow.md +0 -244
- package/framework/schemas/tasks.schema.json +0 -79
- package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
- package/framework/skills/core/morph-implement/SKILL.md +0 -194
- package/framework/skills/core/morph-proposal/SKILL.md +0 -152
- package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
- package/framework/templates/docs/proposal.md +0 -192
- package/framework/templates/docs/spec.md +0 -57
- package/framework/templates/docs/ui-components.md +0 -52
- package/framework/templates/docs/ui-design-system.md +0 -76
- package/framework/templates/docs/ui-flows.md +0 -167
- package/framework/templates/examples/spec-examples.md +0 -91
- package/framework/templates/feature/decisions.md +0 -124
- package/framework/templates/feature/recap.md +0 -145
- package/framework/templates/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/evaluator-report.js +0 -104
- package/src/lib/specs/acervo-format.js +0 -70
- package/src/lib/specs/acervo-lifecycle.js +0 -264
- package/src/lib/specs/done-criteria-check.js +0 -200
- package/src/lib/specs/usecase-check.js +0 -301
- package/src/lib/specs/usecase-promoter.js +0 -301
- package/src/lib/specs/usecase-registry.js +0 -237
- package/src/lib/tasks/dispatch-planner.js +0 -319
- package/src/lib/tasks/mermaid-renderer.js +0 -132
- package/src/lib/tasks/task-classifier.js +0 -30
- package/src/lib/tasks/task-parser.js +0 -209
- package/src/lib/tasks/tasks-json-parser.js +0 -296
- package/src/lib/tasks/test-scope.js +0 -597
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* operator loses track of "what is each session doing, what is waiting on me".
|
|
7
7
|
* This scanner answers that from the primary root's vantage point: one entry
|
|
8
8
|
* per active feature, worktree or root-resident, with the operational facts
|
|
9
|
-
* (phase, pending gates,
|
|
9
|
+
* (phase, pending gates, slice counts, port block, handoff freshness, last
|
|
10
10
|
* commit, dirtiness) resolved per entry.
|
|
11
11
|
*
|
|
12
12
|
* Read-only and fail-open per FIELD: a corrupt feature.json or a git hiccup
|
|
@@ -24,52 +24,39 @@ import {
|
|
|
24
24
|
getFeature,
|
|
25
25
|
derivePhaseForFeature,
|
|
26
26
|
derivePhaseFrom,
|
|
27
|
+
fileSetArtifactProbe,
|
|
27
28
|
getPendingGates,
|
|
28
29
|
pendingGatesFrom,
|
|
29
30
|
} from '../../../framework/hooks/shared/state-reader.js';
|
|
30
31
|
import { listClaims } from '../../../framework/hooks/shared/claim-registry.js';
|
|
31
32
|
import { buildPrIndex } from '../git/pr-index.js';
|
|
32
33
|
import { pendingPrFeatures } from '../git/pending-prs.js';
|
|
34
|
+
import { sliceProgress, sliceProgressFrom } from '../../../framework/hooks/shared/slice-progress.js';
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
const data = JSON.parse(raw);
|
|
36
|
-
if (!Array.isArray(data.tasks)) return null;
|
|
37
|
-
return { done: data.tasks.filter((t) => t.status === 'done').length, total: data.tasks.length };
|
|
38
|
-
}
|
|
36
|
+
const counts = (progress) => (progress ? { done: progress.done, total: progress.total } : null);
|
|
39
37
|
|
|
40
38
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* directly. The real mechanism is which COPY of that file is on disk. Since
|
|
46
|
-
* v8.4 every feature is born on `morph/{f}` and its tasks.json is COMMITTED
|
|
47
|
-
* there; the primary root's working tree only ever holds the copy of the
|
|
48
|
-
* CHECKED-OUT branch. An operator sitting on the default therefore read the
|
|
49
|
-
* pre-work copy — the reported `2/2` for a feature whose branch already had
|
|
50
|
-
* T1..T3 all done.
|
|
39
|
+
* Slice progress for one feature, read from the copy that holds the work: a
|
|
40
|
+
* root-resident feature's plan checks and reports are committed on its own
|
|
41
|
+
* `morph/{f}` branch, while the root working tree only holds the checked-out
|
|
42
|
+
* branch's (pre-work) copy.
|
|
51
43
|
*
|
|
52
44
|
* @param {string} featureDir - the feature dir inside `path`
|
|
53
45
|
* @param {{ root: string, feature: string, featureBranch: string|null }} ctx
|
|
54
46
|
*/
|
|
55
|
-
function
|
|
47
|
+
function sliceCounts(featureDir, { root, feature, featureBranch } = {}) {
|
|
56
48
|
if (featureBranch && root && feature) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
'git',
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
return countFrom(readFileSync(join(featureDir, '2-plan', 'tasks.json'), 'utf8'));
|
|
70
|
-
} catch {
|
|
71
|
-
return null;
|
|
49
|
+
const show = (rel) => {
|
|
50
|
+
try {
|
|
51
|
+
return execFileSync('git', ['show', `${featureBranch}:.morph/features/${feature}/${rel}`], { cwd: root, stdio: 'pipe' }).toString();
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const onBranch = sliceProgressFrom(show('2-plan/checks.json'), (id) => show(`3-implement/reports/${id}.md`));
|
|
57
|
+
if (onBranch) return counts(onBranch);
|
|
72
58
|
}
|
|
59
|
+
return counts(sliceProgress(featureDir));
|
|
73
60
|
}
|
|
74
61
|
|
|
75
62
|
function handoffInfo(featureDir) {
|
|
@@ -164,15 +151,15 @@ function buildRemote(feature, branch, heads, prs) {
|
|
|
164
151
|
function buildEntry(feature, { location, path, branch, root, allocations, blockSize, heads, prs, claims }) {
|
|
165
152
|
let workType = null;
|
|
166
153
|
let status = 'unknown';
|
|
167
|
-
let phase = '
|
|
154
|
+
let phase = 'plan';
|
|
168
155
|
let pendingGates = [];
|
|
169
156
|
|
|
170
|
-
// Resolved once and shared with
|
|
157
|
+
// Resolved once and shared with sliceCounts — see branchToReadFrom.
|
|
171
158
|
const featureBranch = branchToReadFrom(root, feature, location, branch);
|
|
172
159
|
|
|
173
160
|
try {
|
|
174
161
|
// B3b: phase and gates suffered the same branch-staleness B3 fixed for
|
|
175
|
-
//
|
|
162
|
+
// progress. They read `.morph/features/{f}/` through the working tree, so a
|
|
176
163
|
// root parked on the default reported the PRE-WORK phase for a feature
|
|
177
164
|
// whose branch was already at implement — and re-asked for a gate the
|
|
178
165
|
// feature branch had already approved.
|
|
@@ -180,7 +167,7 @@ function buildEntry(feature, { location, path, branch, root, allocations, blockS
|
|
|
180
167
|
if (view) {
|
|
181
168
|
workType = view.fj?.workType || null;
|
|
182
169
|
status = view.fj?.status || 'unknown';
|
|
183
|
-
phase = derivePhaseFrom(view.fj, (
|
|
170
|
+
phase = derivePhaseFrom(view.fj, fileSetArtifactProbe(view.files));
|
|
184
171
|
pendingGates = pendingGatesFrom(view.fj);
|
|
185
172
|
} else {
|
|
186
173
|
const fj = getFeature(feature, path);
|
|
@@ -202,7 +189,7 @@ function buildEntry(feature, { location, path, branch, root, allocations, blockS
|
|
|
202
189
|
location,
|
|
203
190
|
path,
|
|
204
191
|
branch,
|
|
205
|
-
|
|
192
|
+
slices: sliceCounts(featureDir, { root, feature, featureBranch }),
|
|
206
193
|
env: alloc && Number.isInteger(alloc.base)
|
|
207
194
|
? { base: alloc.base, range: [alloc.base, alloc.base + blockSize - 1] }
|
|
208
195
|
: null,
|
|
@@ -316,7 +303,7 @@ export function scanFleet(cwd = process.cwd()) {
|
|
|
316
303
|
location: 'pr-pending',
|
|
317
304
|
path: null,
|
|
318
305
|
branch: p.branch,
|
|
319
|
-
|
|
306
|
+
slices: null,
|
|
320
307
|
env: null,
|
|
321
308
|
handoff: { exists: false, generatedAt: null },
|
|
322
309
|
lastCommit: null,
|
|
@@ -32,6 +32,7 @@ import { installClaudeHooks, installGlobalStatusline } from '../utils/claude-set
|
|
|
32
32
|
import { installSkills } from '../utils/skills-installer.js';
|
|
33
33
|
import { installAgents, installDomainAgents } from '../utils/agents-installer.js';
|
|
34
34
|
import { resolvePrimaryRoot } from '../commands/project/worktree.js';
|
|
35
|
+
import { findOldFormatFeatures, oldFormatGuardLines } from '../lib/specs/old-format.js';
|
|
35
36
|
import { reconcileLegacyTemplatesCopy } from '../lib/installers/templates-reconcile.js';
|
|
36
37
|
import { realEffects } from '../lib/installers/effects.js';
|
|
37
38
|
|
|
@@ -241,6 +242,14 @@ export async function setupInfra(targetPath, {
|
|
|
241
242
|
}
|
|
242
243
|
}
|
|
243
244
|
|
|
245
|
+
// Same refusal as `update`, and neither `force` nor `reinstall` waives it:
|
|
246
|
+
// `init --force` and `setup-infra` overwrite `.morph/` without passing through it.
|
|
247
|
+
const oldFormat = findOldFormatFeatures(targetPath);
|
|
248
|
+
if (oldFormat.length) {
|
|
249
|
+
for (const line of oldFormatGuardLines(oldFormat, targetPath)) log(line);
|
|
250
|
+
return { refused: true, reason: 'old-format-features', features: oldFormat };
|
|
251
|
+
}
|
|
252
|
+
|
|
244
253
|
// --- Reinstall mode: only re-install stack-aware assets ---
|
|
245
254
|
if (reinstall) {
|
|
246
255
|
log('setup-infra (reinstall): re-installing stack-aware assets...');
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* In v5 the harness has 8 personas (no tiers) across two axes — domain
|
|
17
17
|
* (dotnet-senior, ef-modeler, maf-expert, nextjs-expert, ui-designer,
|
|
18
|
-
* infra-engineer,
|
|
18
|
+
* infra-engineer, verifier, reviewer, security) and stage (scout, read-only diagnosis before
|
|
19
19
|
* implementation). Each persona is a subagent the Specialist (Claude in
|
|
20
20
|
* implementer role) can dispatch. The 3 roles (Orchestrator/Specialist/
|
|
21
21
|
* Evaluator) are assumed by Claude itself per MORPH.md, not as separate agents.
|
|
@@ -294,7 +294,7 @@ export function loadKnownMcpIds(frameworkDir) {
|
|
|
294
294
|
* with NO known stack tags (greenfield / install before config.json exists)
|
|
295
295
|
* EVERY persona is generated — a project that dispatches `morph-{id}` must
|
|
296
296
|
* find the subagent registered, and an install that only lands
|
|
297
|
-
* morph-
|
|
297
|
+
* morph-verifier forces every domain dispatch into the general-purpose
|
|
298
298
|
* fallback. Stack filtering only applies when the project's stack is KNOWN
|
|
299
299
|
* (tags non-empty): the fallback stays reserved for stacks the project
|
|
300
300
|
* genuinely does not have. Note this deliberately differs from rules/skills
|
|
@@ -431,7 +431,7 @@ function buildFrontmatter(agent, description, knownMcpIds, maxTurns) {
|
|
|
431
431
|
// Canonical 1:1 naming: frontmatter name === filename === subagent_type.
|
|
432
432
|
`name: morph-${agent.id}`,
|
|
433
433
|
`description: ${description}`,
|
|
434
|
-
// The
|
|
434
|
+
// The verifier is sonnet here; the dispatch routes it to opus when the risk
|
|
435
435
|
// detector flags the diff. A persona without the field inherits the session model.
|
|
436
436
|
`model: ${agent.model ?? 'inherit'}`,
|
|
437
437
|
`tools: ${buildToolsLine(agent, knownMcpIds)}`,
|
|
@@ -453,6 +453,63 @@ function buildDescription(agent) {
|
|
|
453
453
|
return desc.length > 200 ? desc.slice(0, 197) + '...' : desc;
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
+
/**
|
|
457
|
+
* The fixed half of every implementer dispatch. The orchestrator paraphrases
|
|
458
|
+
* a prompt template in the field; the generated body is the only text a
|
|
459
|
+
* dispatch is guaranteed to carry, so the contract lives here and the
|
|
460
|
+
* dispatch carries only what changes per slice.
|
|
461
|
+
*/
|
|
462
|
+
const NEON_CAUTION = 'NEON: o MCP `neon` inspeciona schema e ensaia migração numa branch descartável ' +
|
|
463
|
+
'(prepare_database_migration → complete_database_migration); ele também APAGA branch e projeto — ' +
|
|
464
|
+
'nunca chame tool destrutiva sem confirmação humana explícita.';
|
|
465
|
+
|
|
466
|
+
const STANDARDS_HINT = 'Digest de cada um: `morph-spec standards --find <id>`; só a seção que importa: ' +
|
|
467
|
+
'`morph-spec standards --show <id>#<âncora>` (`--show <id>` lista as âncoras).';
|
|
468
|
+
|
|
469
|
+
export const DISPATCH_CONTRACT = [
|
|
470
|
+
'## Contrato de dispatch',
|
|
471
|
+
'',
|
|
472
|
+
'O dispatch traz só o que muda por fatia: id e título, `cobre` com o texto de cada cenário, `arquivos`, `standards`, ' +
|
|
473
|
+
'o caminho do relatório e, se houver, o worktree. O resto é este contrato.',
|
|
474
|
+
'',
|
|
475
|
+
'1. RELATÓRIO: GRAVE o arquivo de relatório que o dispatch nomeia (`.morph/features/{feature}/3-implement/reports/F{n}.md`) ' +
|
|
476
|
+
'ANTES de encerrar — é o retorno autoritativo, não a sua mensagem final. `SendMessage` não é substituto do relatório e não deve ser chamado.',
|
|
477
|
+
'2. ESTADO: NÃO toque em `plan.md`, `checks.json`, `feature.json`, `state.json` nem no board de sessão — todo estado é gravado exclusivamente pelo orquestrador.',
|
|
478
|
+
'3. TESTE: a prova é do orquestrador (`morph-spec verify {feature}`, depois do seu retorno), não sua. Escreva os testes pelo orçamento: ' +
|
|
479
|
+
'um por pós-condição, um de regressão por bug, variantes numa tabela (`[Theory]`/`it.each`). NÃO rode teste a cada marco nem faça ciclo red→green: ' +
|
|
480
|
+
'escreva código e testes, compile, e rode SÓ o filtro dos seus testes UMA vez, em primeiro plano, antes do relatório; se falhar, corrija e rode só os que falharam. ' +
|
|
481
|
+
'Grave a saída em arquivo (`… > .morph/logs/test-last.log 2>&1`, sem nada depois) e leia o arquivo — um pipe troca o exit do teste pelo do último comando. ' +
|
|
482
|
+
'Em batch paralelo SEM isolamento, só compile. Com `isolation: "worktree"`, o mesmo vale no seu worktree, e o último ato é o commit na branch dele.',
|
|
483
|
+
'4. ESCOPO: uma fatia, ~4 arquivos (nunca mais de 8). Escopo maior falha e volta — fatie antes, não depois.',
|
|
484
|
+
'5. SOBREVIVÊNCIA: o limite de turnos corta sem aviso: commite `wip(F{n})` a cada marco (um arquivo ou uma fatia coerente escrita), não só ao fim — ' +
|
|
485
|
+
'e só os seus arquivos (`git add <paths>`, nunca `-A` nem diretório). Sem commit, a retomada recomeça do zero.',
|
|
486
|
+
'6. PROCESSOS: NUNCA `taskkill /IM node.exe` (derruba os MCPs de todas as sessões). Encerre dev server pelo PID que você mesmo subiu. ' +
|
|
487
|
+
'Build e suíte pesados rodam por `morph-spec heavy run -- <comando>`, em primeiro plano. Exit 3 é fila cheia depois da espera da própria fila: ' +
|
|
488
|
+
'não repita nem espere em laço — diga no relatório o que não rodou.',
|
|
489
|
+
'7. REUSO: antes de criar tipo, componente, hook ou serviço NOVO, verifique se já existe: `morph-spec graph explain <nome>` e ' +
|
|
490
|
+
'`graph affected <conceito> --uses-only`; sem grafo (`no-graph`), caia para `grep -rn "<nome>"` e siga — a ausência de grafo nunca bloqueia. ' +
|
|
491
|
+
'Se criar mesmo assim, diga por que em uma linha no relatório.',
|
|
492
|
+
'8. MUTAÇÃO: nunca altere, reverta, mova ou apague código de produção para ver um teste falhar (`sed`, `mv`, `git show HEAD:… >`, `git stash`, ' +
|
|
493
|
+
'`git checkout --`). A prova de que o teste morde é do verificador (Gate 3).',
|
|
494
|
+
'9. ESCRITA: código e teste do projeto só por Write/Edit, nunca pelo Bash — heredoc, redirecionamento, `sed -i` ou script python/node que grava (`cat > X.cs <<EOF`, `sed -i … X.cs`): ' +
|
|
495
|
+
'os guards de escrita só enxergam Write/Edit.',
|
|
496
|
+
'',
|
|
497
|
+
'Formato do relatório (o `report-check` lê estas marcas):',
|
|
498
|
+
'',
|
|
499
|
+
'```',
|
|
500
|
+
'# F1 — <título>',
|
|
501
|
+
'O que foi feito, em 2-3 frases: o comportamento entregue e onde ele mora, sem repetir a lista de arquivos.',
|
|
502
|
+
'**Arquivos:** `src/a.cs`, `src/a.test.cs`',
|
|
503
|
+
'**Checks:**',
|
|
504
|
+
'- C1 — provado: `a.test.cs` › nome do teste (ou `arquivo:linha`)',
|
|
505
|
+
'- C2 — não provado: por quê',
|
|
506
|
+
'```',
|
|
507
|
+
'',
|
|
508
|
+
'`**Arquivos:**` lista TODO arquivo criado ou modificado, inclusive artefato auxiliar fora do plano (factory, seed, config gerada): ' +
|
|
509
|
+
'arquivo em produção sem dono é falha de protocolo que o Gate 3 aponta. Todo cenário que a fatia `cobre` aparece em `**Checks:**`. ' +
|
|
510
|
+
'Problemas e como foram resolvidos vão em prosa depois.',
|
|
511
|
+
];
|
|
512
|
+
|
|
456
513
|
function buildBody(agent, maxTurns) {
|
|
457
514
|
const lines = [
|
|
458
515
|
`# ${agent.title ?? agent.id}`,
|
|
@@ -461,12 +518,20 @@ function buildBody(agent, maxTurns) {
|
|
|
461
518
|
'',
|
|
462
519
|
];
|
|
463
520
|
|
|
464
|
-
|
|
521
|
+
// The 5.3.0 shape (no effort, no surfaces, implementer) renders byte-for-byte as before.
|
|
522
|
+
const legacyShape = !agent.effort && !agent.surfaces && isImplementer(agent);
|
|
523
|
+
|
|
524
|
+
if (legacyShape && agent.domains?.length) {
|
|
465
525
|
lines.push('## Domains', ...agent.domains.map(d => `- ${d}`), '');
|
|
466
526
|
}
|
|
467
527
|
|
|
528
|
+
if (!legacyShape && agent.mcp?.includes('neon')) {
|
|
529
|
+
lines.push(NEON_CAUTION, '');
|
|
530
|
+
}
|
|
531
|
+
|
|
468
532
|
if (agent.standards?.length) {
|
|
469
533
|
lines.push('## Standards to consult on demand');
|
|
534
|
+
if (!legacyShape) lines.push(STANDARDS_HINT);
|
|
470
535
|
for (const s of agent.standards) lines.push(`- ${s}`);
|
|
471
536
|
lines.push('');
|
|
472
537
|
}
|
|
@@ -485,7 +550,9 @@ function buildBody(agent, maxTurns) {
|
|
|
485
550
|
}
|
|
486
551
|
}
|
|
487
552
|
|
|
488
|
-
if (isImplementer(agent)) {
|
|
553
|
+
if (agent.effort && isImplementer(agent)) {
|
|
554
|
+
lines.push(...DISPATCH_CONTRACT, '');
|
|
555
|
+
} else if (isImplementer(agent)) {
|
|
489
556
|
lines.push(
|
|
490
557
|
'## How you operate',
|
|
491
558
|
'',
|
|
@@ -493,11 +560,11 @@ function buildBody(agent, maxTurns) {
|
|
|
493
560
|
'',
|
|
494
561
|
);
|
|
495
562
|
} else {
|
|
496
|
-
// A read-only persona has no
|
|
563
|
+
// A read-only persona has no slice to implement nor scenario to report.
|
|
497
564
|
lines.push(
|
|
498
565
|
'## Como você opera',
|
|
499
566
|
'',
|
|
500
|
-
'Você é read-only: nunca escreva, edite nem reverta código, testes,
|
|
567
|
+
'Você é read-only: nunca escreva, edite nem reverta código, testes, o plano ou `feature.json` (nada de `git checkout --`). ' +
|
|
501
568
|
'O entregável é o arquivo de relatório que o dispatch nomeia; sem arquivo nomeado, é a sua mensagem final (no scout, o mapa).',
|
|
502
569
|
'',
|
|
503
570
|
);
|
|
@@ -505,7 +572,7 @@ function buildBody(agent, maxTurns) {
|
|
|
505
572
|
|
|
506
573
|
lines.push(
|
|
507
574
|
// `PARCIAL` on line 1 is what report-check reads; "no verdict" keeps an
|
|
508
|
-
//
|
|
575
|
+
// review partial from carrying a Verdict the gate would read as final.
|
|
509
576
|
'## Turn budget',
|
|
510
577
|
'',
|
|
511
578
|
// Pasted into EVERY dispatch of every persona — kept to the minimum that
|
|
@@ -529,7 +596,7 @@ function buildBody(agent, maxTurns) {
|
|
|
529
596
|
'Use Read/Grep/Glob, never grep/sed/cat/find via Bash. Check a library API in the `context7` MCP before guessing ' +
|
|
530
597
|
'(load a deferred `mcp__` tool with ToolSearch). Write the tests the budget asks for, then run only your own ' +
|
|
531
598
|
'filter ONCE before the report — no red→green cycles, no `morph-spec verify` (the orchestrator proves it). ' +
|
|
532
|
-
'Never alter, revert or move production code to watch a test fail: mutation proof belongs to the Gate 3
|
|
599
|
+
'Never alter, revert or move production code to watch a test fail: mutation proof belongs to the Gate 3 verifier.',
|
|
533
600
|
);
|
|
534
601
|
}
|
|
535
602
|
|
|
@@ -24,41 +24,6 @@ export { installAgents } from './agents-installer.js';
|
|
|
24
24
|
// Extended settings management
|
|
25
25
|
// ============================================================================
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
* Install permission rules into .claude/settings.local.json
|
|
29
|
-
* @param {string} targetPath - Project root directory
|
|
30
|
-
* @param {Array<string>} permissions - Permission patterns to add
|
|
31
|
-
* @returns {Promise<Object>} Summary of changes
|
|
32
|
-
*/
|
|
33
|
-
export async function installPermissions(targetPath, permissions) {
|
|
34
|
-
const settings = await loadSettings(targetPath);
|
|
35
|
-
|
|
36
|
-
settings.permissions = settings.permissions || [];
|
|
37
|
-
|
|
38
|
-
const added = [];
|
|
39
|
-
for (const perm of permissions) {
|
|
40
|
-
if (!settings.permissions.includes(perm)) {
|
|
41
|
-
settings.permissions.push(perm);
|
|
42
|
-
added.push(perm);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Track in _morph_managed
|
|
47
|
-
settings[MORPH_MANAGED_KEY] = settings[MORPH_MANAGED_KEY] || {};
|
|
48
|
-
settings[MORPH_MANAGED_KEY].permissions_added =
|
|
49
|
-
settings[MORPH_MANAGED_KEY].permissions_added || [];
|
|
50
|
-
for (const perm of added) {
|
|
51
|
-
if (!settings[MORPH_MANAGED_KEY].permissions_added.includes(perm)) {
|
|
52
|
-
settings[MORPH_MANAGED_KEY].permissions_added.push(perm);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
settings[MORPH_MANAGED_KEY].generated_at = new Date().toISOString();
|
|
56
|
-
|
|
57
|
-
await saveSettings(targetPath, settings);
|
|
58
|
-
|
|
59
|
-
return { added, total: settings.permissions.length };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
27
|
/**
|
|
63
28
|
* Install environment variables into .claude/settings.local.json
|
|
64
29
|
* @param {string} targetPath - Project root directory
|
package/src/utils/file-copier.js
CHANGED
|
@@ -156,7 +156,6 @@ export const MORPH_GITIGNORE_ENTRIES = [
|
|
|
156
156
|
'.morph/memory/',
|
|
157
157
|
'.morph/plans/',
|
|
158
158
|
'.morph/analytics/',
|
|
159
|
-
'.morph/traces/',
|
|
160
159
|
// Per-instance activity/session logs (activity-logger.js). Each worktree
|
|
161
160
|
// writes its own — committing this guarantees a merge conflict between
|
|
162
161
|
// feature branches.
|
|
@@ -240,6 +239,33 @@ export async function updateGitignore(projectPath, fx = realEffects()) {
|
|
|
240
239
|
if (content !== original) fx.writeFile(gitignorePath, content);
|
|
241
240
|
}
|
|
242
241
|
|
|
242
|
+
const DOTNET_BUILD_DIRS = ['bin', 'obj'];
|
|
243
|
+
|
|
244
|
+
const ignoredDir = (line) => line.trim()
|
|
245
|
+
.replace(/^\/|^\*\*\//, '')
|
|
246
|
+
.replace(/\/(\*\*)?$/, '')
|
|
247
|
+
.replace(/^\[(\w)(\w)\]/, (_, a, b) => (a.toLowerCase() === b.toLowerCase() ? a : `[${a}${b}]`))
|
|
248
|
+
.toLowerCase();
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Appends `bin/` and `obj/` to the project `.gitignore` when no line already
|
|
252
|
+
* ignores them. Never rewrites an existing line.
|
|
253
|
+
*
|
|
254
|
+
* @returns {string[]} the entries added
|
|
255
|
+
*/
|
|
256
|
+
export function ensureDotnetBuildIgnores(projectPath) {
|
|
257
|
+
const gitignorePath = join(projectPath, '.gitignore');
|
|
258
|
+
const content = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf8') : '';
|
|
259
|
+
const present = new Set(content.split(/\r?\n/).filter((l) => !l.trim().startsWith('#')).map(ignoredDir));
|
|
260
|
+
const missing = DOTNET_BUILD_DIRS.filter((dir) => !present.has(dir)).map((dir) => `${dir}/`);
|
|
261
|
+
if (!missing.length) return [];
|
|
262
|
+
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
|
263
|
+
const lead = content && !content.endsWith('\n') ? eol : '';
|
|
264
|
+
const block = [...(content ? [''] : []), '# .NET build output', ...missing, ''].join(eol);
|
|
265
|
+
fs.writeFileSync(gitignorePath, content + lead + block);
|
|
266
|
+
return missing;
|
|
267
|
+
}
|
|
268
|
+
|
|
243
269
|
/**
|
|
244
270
|
* Install a MORPH-SPEC .gitattributes section pinning morph state files to LF.
|
|
245
271
|
*
|
|
@@ -55,8 +55,11 @@ import { realEffects } from '../lib/installers/effects.js';
|
|
|
55
55
|
*
|
|
56
56
|
* 3.12.0 adds heavy-gate to the Bash PreToolUse chain and heavy-release to the
|
|
57
57
|
* PostToolUse and PostToolUseFailure chains: the opt-in `heavy.gateBash`.
|
|
58
|
+
*
|
|
59
|
+
* 3.13.0 adds warn-bash-source-write to the Bash PreToolUse chain: a warning,
|
|
60
|
+
* never a block, when a command writes project source through a redirect.
|
|
58
61
|
*/
|
|
59
|
-
const HOOKS_VERSION = '3.
|
|
62
|
+
const HOOKS_VERSION = '3.13.0';
|
|
60
63
|
|
|
61
64
|
/** Marker for old dispatch.js (v1) */
|
|
62
65
|
const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
|
|
@@ -93,21 +96,24 @@ const MORPH_DENY = [
|
|
|
93
96
|
*
|
|
94
97
|
* Pre-authorizes operations the agent legitimately performs during the
|
|
95
98
|
* morph-spec flow, so the user does not get permission prompts on every
|
|
96
|
-
*
|
|
99
|
+
* plan.md / slice report write.
|
|
97
100
|
*
|
|
98
101
|
* `Edit(path)` ONLY, for the same reason as MORPH_DENY: the `Edit` rule already
|
|
99
102
|
* covers Write, and a `Write(path)` rule is inert noise.
|
|
100
103
|
*/
|
|
101
104
|
const MORPH_ALLOW = [
|
|
102
|
-
// Feature artifacts — the agent's working directory during
|
|
105
|
+
// Feature artifacts — the agent's working directory during plan/uiux/implement/review
|
|
103
106
|
'Edit(.morph/features/**)',
|
|
104
107
|
// state.json — fallback path when state-sync hook can't cover a case
|
|
105
108
|
'Edit(.morph/state.json)',
|
|
106
|
-
// Long-lived architectural context — agent may reconcile during
|
|
109
|
+
// Long-lived architectural context — agent may reconcile during planning
|
|
107
110
|
'Edit(.morph/context/**)',
|
|
108
111
|
// Folder creation under .morph
|
|
109
112
|
'Bash(mkdir:.morph/**)',
|
|
110
113
|
'Bash(ls:.morph/**)',
|
|
114
|
+
// Gate 3 mutation round: restores bytes itself and has --restore; an auto-mode deny pauses the gate
|
|
115
|
+
'Bash(morph-spec mutate:*)',
|
|
116
|
+
'Bash(npx morph-spec mutate:*)',
|
|
111
117
|
];
|
|
112
118
|
|
|
113
119
|
// Backwards-compat alias used by older tests
|
|
@@ -168,6 +174,8 @@ const MORPH_HOOKS = [
|
|
|
168
174
|
// on someone having read MORPH.md. block-raw-worktree-add is its way-IN
|
|
169
175
|
// counterpart (a raw `git worktree add` builds a tree with no junctions, no
|
|
170
176
|
// ports, no identity) — static, so it costs nothing after the first two.
|
|
177
|
+
// warn-bash-source-write only adds context (source written by redirect skips
|
|
178
|
+
// the Write/Edit guards); after the guards, so a blocked command is not warned.
|
|
171
179
|
// heavy-gate comes after every guard — it takes a slot, and a command a guard
|
|
172
180
|
// blocks must not hold one — and before the rewriter.
|
|
173
181
|
// worktree-env-inject is LAST: it rewrites launcher commands (updatedInput)
|
|
@@ -179,7 +187,7 @@ const MORPH_HOOKS = [
|
|
|
179
187
|
hooks: [{
|
|
180
188
|
type: 'command',
|
|
181
189
|
command: 'node framework/hooks/claude-code/core/chain.js',
|
|
182
|
-
args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'heavy-gate', 'worktree-env-inject']
|
|
190
|
+
args: ['pre-bash-denylist', 'block-destructive-worktree-removal', 'block-raw-worktree-add', 'warn-bash-source-write', 'heavy-gate', 'worktree-env-inject']
|
|
183
191
|
}]
|
|
184
192
|
},
|
|
185
193
|
|
|
@@ -214,15 +222,14 @@ const MORPH_HOOKS = [
|
|
|
214
222
|
}]
|
|
215
223
|
},
|
|
216
224
|
|
|
217
|
-
// === PostToolUse: Write|Edit —
|
|
218
|
-
//
|
|
225
|
+
// === PostToolUse: Write|Edit — comment-guard, solo: it blocks the edit that
|
|
226
|
+
// adds a forbidden comment. ===
|
|
219
227
|
{
|
|
220
228
|
event: 'PostToolUse',
|
|
221
229
|
matcher: 'Write|Edit',
|
|
222
230
|
hooks: [{
|
|
223
231
|
type: 'command',
|
|
224
|
-
command: 'node framework/hooks/claude-code/core/
|
|
225
|
-
args: ['tasks-json-guard', 'trace-autogen', 'comment-guard']
|
|
232
|
+
command: 'node framework/hooks/claude-code/core/comment-guard.js'
|
|
226
233
|
}]
|
|
227
234
|
},
|
|
228
235
|
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Copies framework skills to .claude/skills/ during morph-spec init.
|
|
5
5
|
* Installs only the `core/` skill set — the canonical NLH skills referenced by
|
|
6
|
-
* MORPH.md (morph-
|
|
7
|
-
* morph-review, morph-standards). The 3-tier system was collapsed
|
|
6
|
+
* MORPH.md (morph-plan, morph-uiux, morph-apply, morph-review, morph-standards…). The 3-tier system was collapsed
|
|
8
7
|
* into a single source of truth in framework/skills/core/.
|
|
9
8
|
*
|
|
10
9
|
* Source skills are directory-based: framework/skills/core/{skill-name}/SKILL.md
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# Trace Schema — morph-spec NLH
|
|
2
|
-
|
|
3
|
-
## Local de armazenamento
|
|
4
|
-
|
|
5
|
-
`.morph/traces/YYYY-MM-DD-{feature}-{phase}.json`
|
|
6
|
-
|
|
7
|
-
Um arquivo por fase. Se a fase for executada em múltiplas sessões, o mesmo arquivo é atualizado incrementalmente.
|
|
8
|
-
|
|
9
|
-
## Formato JSON
|
|
10
|
-
|
|
11
|
-
```json
|
|
12
|
-
{
|
|
13
|
-
"schema_version": "1.0",
|
|
14
|
-
"session_id": "YYYY-MM-DD-HH-MM-SS",
|
|
15
|
-
"feature": "nome-da-feature",
|
|
16
|
-
"phase": "implement | plan | review",
|
|
17
|
-
"started_at": "ISO 8601",
|
|
18
|
-
"completed_at": "ISO 8601",
|
|
19
|
-
"tasks": [
|
|
20
|
-
{
|
|
21
|
-
"id": "T1",
|
|
22
|
-
"title": "descrição da task",
|
|
23
|
-
"status": "completed | failed | skipped",
|
|
24
|
-
"standards_consulted": ["id-do-standard-1"],
|
|
25
|
-
"agent_persona": "dotnet-senior | nextjs-expert | etc",
|
|
26
|
-
"sub_agent_dispatched": false,
|
|
27
|
-
"files_modified": ["path/relativo/arquivo.cs"],
|
|
28
|
-
"duration_seconds": null
|
|
29
|
-
}
|
|
30
|
-
],
|
|
31
|
-
"phase_summary": {
|
|
32
|
-
"tasks_completed": 3,
|
|
33
|
-
"tasks_failed": 0,
|
|
34
|
-
"standards_most_used": ["architecture-vertical-slice"],
|
|
35
|
-
"sub_agents_dispatched": 0
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Como o trace é gerado (automático — não escreva à mão)
|
|
41
|
-
|
|
42
|
-
O trace é **gerado pelo hook `trace-autogen`** (PostToolUse em Write|Edit) a cada
|
|
43
|
-
write de `2-plan/tasks.json` — o último write da cerimônia de cada task. As skills
|
|
44
|
-
(`morph-implement`, `morph-review`) **não escrevem o trace**.
|
|
45
|
-
|
|
46
|
-
| Fonte | O que alimenta |
|
|
47
|
-
|-------|----------------|
|
|
48
|
-
| `tasks.json` | conjunto de tasks, `status` (done→completed, skipped→skipped, blocked→failed; pending/in_progress ficam fora), `outputs` → `files_modified` |
|
|
49
|
-
| `recap.md` | `**Standard:**` → `standards_consulted`; `**Persona:**` → `agent_persona` + `sub_agent_dispatched` (`morph-{id}`/`general-purpose` = true) |
|
|
50
|
-
|
|
51
|
-
`completed_at` e `phase_summary` são preenchidos quando nenhuma task resta
|
|
52
|
-
`pending`/`in_progress`. O arquivo existente da mesma feature+fase é atualizado
|
|
53
|
-
in place (`session_id`/`started_at` preservados entre sessões).
|
|
54
|
-
|
|
55
|
-
**Se o trace parecer incompleto, corrija a FONTE** (um recap sem o bloco `## T{N}`), nunca o trace.
|
|
56
|
-
|
|
57
|
-
## Regras de geração
|
|
58
|
-
|
|
59
|
-
- Se um campo não tiver valor disponível, o hook usa `null` — nunca omite o campo.
|
|
60
|
-
- Estrutura completa mantida para compatibilidade com análise de meta-harness.
|
|
61
|
-
- `session_id` usa o formato `YYYY-MM-DD-HH-MM-SS` da primeira geração.
|
|
62
|
-
- `standards_consulted` contém os `id`s do STANDARDS.json, não os paths.
|
|
63
|
-
- `files_modified` usa paths relativos à raiz do repositório (vem de `outputs`).
|
|
64
|
-
|
|
65
|
-
## Motivação
|
|
66
|
-
|
|
67
|
-
Raw traces preservam o sinal completo de execução. Summaries comprimem informação e perdem sinais que o meta-harness usa para otimização. Regra: logar o trace completo — nunca substituir por resumo.
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* tasks-json-guard.js — PostToolUse hook (validate-and-warn, never block)
|
|
4
|
-
*
|
|
5
|
-
* Event: PostToolUse | Matcher: Write|Edit
|
|
6
|
-
*
|
|
7
|
-
* After any Write/Edit that touches a tasks.json under .morph/features/,
|
|
8
|
-
* re-reads the file from disk and validates the schema (status enum, effort
|
|
9
|
-
* enum). Invalid → injects a warning so the LLM fixes the file immediately;
|
|
10
|
-
* valid → logs one activity line and stays silent.
|
|
11
|
-
*
|
|
12
|
-
* The production failure this guards: a leaf sub-agent wrote
|
|
13
|
-
* status:"completed" (not in the enum — the schema accepts "done") into
|
|
14
|
-
* tasks.json and nothing flagged it. The blocking pre-write validation for
|
|
15
|
-
* post-Gate-2 edits lives in protect-spec-files.js (PreToolUse); this
|
|
16
|
-
* PostToolUse pass also covers pre-gate authoring (morph-plan) and anything
|
|
17
|
-
* that landed on disk regardless.
|
|
18
|
-
*
|
|
19
|
-
* Fail-open: exits 0 on any error.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { readFileSync } from 'fs';
|
|
23
|
-
import { isAbsolute, join } from 'path';
|
|
24
|
-
import { stateExists } from '../../shared/state-reader.js';
|
|
25
|
-
import { contextResult, runStandalone } from '../../shared/hook-response.js';
|
|
26
|
-
import { logHookActivity } from '../../shared/activity-logger.js';
|
|
27
|
-
import { getFilePath } from '../../shared/payload-utils.js';
|
|
28
|
-
import { isFeaturePath, getBasename } from '../../shared/phase-utils.js';
|
|
29
|
-
import { VALID_TASK_STATUS, validateTasksDoc } from '../../shared/tasks-validator.js';
|
|
30
|
-
|
|
31
|
-
function projectDir() {
|
|
32
|
-
return process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @param {object} payload - parsed PostToolUse stdin payload (Write|Edit)
|
|
37
|
-
* @returns {Promise<object|null>} a hookSpecificOutput warning when tasks.json
|
|
38
|
-
* is unparseable or schema-invalid, or null (pass/silent).
|
|
39
|
-
*/
|
|
40
|
-
export async function run(payload) {
|
|
41
|
-
try {
|
|
42
|
-
const cwd = projectDir();
|
|
43
|
-
if (!stateExists(cwd)) return null;
|
|
44
|
-
|
|
45
|
-
const filePath = getFilePath(payload);
|
|
46
|
-
if (!filePath) return null;
|
|
47
|
-
if (!isFeaturePath(filePath)) return null;
|
|
48
|
-
if (getBasename(filePath) !== 'tasks.json') return null;
|
|
49
|
-
|
|
50
|
-
const absPath = isAbsolute(filePath) ? filePath : join(cwd, filePath);
|
|
51
|
-
|
|
52
|
-
let doc;
|
|
53
|
-
try {
|
|
54
|
-
doc = JSON.parse(readFileSync(absPath, 'utf-8'));
|
|
55
|
-
} catch {
|
|
56
|
-
logHookActivity('tasks-json-guard', 'PostToolUse', 'invalid', cwd);
|
|
57
|
-
return contextResult(
|
|
58
|
-
'⚠️ [morph-spec] tasks.json INVÁLIDO — o arquivo gravado não parseia como JSON. ' +
|
|
59
|
-
'Corrija o arquivo antes de continuar.',
|
|
60
|
-
'PostToolUse'
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const { valid, errors } = validateTasksDoc(doc);
|
|
65
|
-
if (!valid) {
|
|
66
|
-
logHookActivity('tasks-json-guard', 'PostToolUse', 'invalid', cwd);
|
|
67
|
-
return contextResult(
|
|
68
|
-
'⚠️ [morph-spec] tasks.json com schema inválido após o write:\n' +
|
|
69
|
-
errors.map(e => ` • ${e}`).join('\n') +
|
|
70
|
-
`\nStatus válidos: ${VALID_TASK_STATUS.join(', ')}. Corrija antes de marcar a task como done.`,
|
|
71
|
-
'PostToolUse'
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
logHookActivity('tasks-json-guard', 'PostToolUse', 'ok', cwd);
|
|
76
|
-
return null;
|
|
77
|
-
} catch {
|
|
78
|
-
// Fail-open
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
await runStandalone(run);
|