@polymorphism-tech/morph-spec 9.7.0 → 10.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 +11 -11
- package/README.md +4 -4
- package/bin/morph-spec.js +41 -44
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +24 -30
- package/framework/MORPH.md +47 -123
- package/framework/agents.json +6 -374
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-archive.md +4 -3
- package/framework/commands/morph-hotfix.md +8 -30
- package/framework/commands/morph-infra.md +2 -2
- package/framework/commands/morph-media.md +10 -9
- package/framework/commands/morph-review.md +15 -0
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +100 -102
- package/framework/evals/review-checklist.md +46 -54
- package/framework/hooks/README.md +4 -4
- package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
- package/framework/hooks/claude-code/core/README.md +1 -1
- package/framework/hooks/claude-code/core/denylist.json +4 -4
- package/framework/hooks/claude-code/core/gate-guard.js +9 -0
- package/framework/hooks/claude-code/core/masked-exit.js +49 -0
- package/framework/hooks/claude-code/core/state-sync.js +4 -1
- package/framework/hooks/claude-code/core/telemetry-log.js +1 -2
- package/framework/hooks/claude-code/core/test-budget.js +10 -70
- package/framework/hooks/claude-code/core/validate-completion.js +11 -24
- package/framework/hooks/claude-code/core/worktree-context.js +5 -22
- package/framework/hooks/claude-code/core/worktree-create.js +6 -45
- package/framework/hooks/claude-code/core/worktree-env-inject.js +2 -10
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/gate-event.js +2 -2
- package/framework/hooks/shared/graph-refresh.js +20 -3
- package/framework/hooks/shared/heavy-command.js +32 -1
- package/framework/hooks/shared/heavy-config.js +1 -1
- package/framework/hooks/shared/heavy-slots.js +41 -4
- package/framework/hooks/shared/next-step.js +63 -24
- package/framework/hooks/shared/phase-utils.js +6 -9
- package/framework/hooks/shared/report-progress.js +86 -0
- package/framework/hooks/shared/state-reader.js +3 -2
- package/framework/hooks/statusline.js +332 -0
- package/framework/phases.json +32 -10
- package/framework/rules/ai-agents-standards.md +9 -1
- package/framework/rules/csharp-standards.md +4 -0
- package/framework/rules/ef-standards.md +20 -0
- package/framework/rules/frontend-standards.md +1 -1
- package/framework/rules/implement-report.md +25 -0
- package/framework/rules/infrastructure-standards.md +2 -0
- package/framework/rules/nextjs-standards.md +7 -0
- package/framework/rules/plan-grammar.md +53 -0
- package/framework/rules/ui-design.md +27 -0
- package/framework/schemas/config.schema.json +12 -19
- package/framework/schemas/feature.schema.json +7 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/create-umbraco-lp/references/umbraco-lp-patterns.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +2 -2
- package/framework/skills/core/morph-apply/SKILL.md +38 -166
- package/framework/skills/core/morph-apply/references/debugging.md +8 -0
- package/framework/skills/core/morph-apply/references/hotfix.md +42 -0
- package/framework/skills/core/morph-apply/references/machine.md +29 -0
- package/framework/skills/core/{morph-media/SKILL.md → morph-apply/references/media.md} +11 -74
- package/framework/skills/core/morph-apply/references/review.md +65 -0
- package/framework/skills/core/morph-plan/SKILL.md +42 -108
- package/framework/skills/core/morph-plan/references/critique.md +40 -0
- package/framework/skills/core/morph-plan/references/interview.md +25 -0
- package/framework/skills/core/morph-uiux/SKILL.md +47 -64
- package/framework/skills/core/morph-uiux/surfaces.json +32 -0
- package/framework/standards/STANDARDS.json +32 -5
- package/framework/standards/STANDARDS.meta.json +5 -1
- package/framework/standards/ai-agents/agent-session.md +2 -3
- package/framework/standards/ai-agents/context-providers.md +2 -2
- package/framework/standards/ai-agents/durable-workflows-hitl.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +2 -2
- package/framework/standards/ai-agents/rag-custom-pgvector.md +6 -6
- package/framework/standards/ai-agents/setup.md +1 -1
- package/framework/standards/ai-agents/tool-adapters.md +2 -2
- package/framework/standards/architecture/stack-baseline.md +47 -0
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -4
- package/framework/standards/frontend/design-system/ai-image-generation.md +51 -12
- package/framework/standards/frontend/design-system/ai-video-generation.md +50 -4
- package/framework/standards/frontend/design-system/art-direction.md +2 -2
- package/framework/standards/frontend/scroll-driven/frame-scrub.md +12 -2
- package/framework/standards/frontend/scroll-driven/scroll-components.md +76 -4
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +1 -2
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/context/ARCHITECTURE.md +1 -1
- package/framework/templates/docs/plan.md +12 -12
- package/framework/templates/docs/ui-mockups.md +17 -105
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/e2e/e2e.js +88 -23
- package/src/commands/graph/diff.js +74 -0
- package/src/commands/graph/status.js +1 -1
- package/src/commands/observability/cost.js +2 -4
- package/src/commands/observability/telemetry.js +1 -1
- package/src/commands/project/doctor.js +23 -39
- package/src/commands/project/fleet.js +5 -6
- package/src/commands/project/init.js +63 -36
- package/src/commands/project/stack-scan.js +3 -42
- package/src/commands/project/stack-set.js +26 -0
- package/src/commands/project/status.js +12 -13
- package/src/commands/project/update.js +39 -3
- package/src/commands/project/worktree.js +27 -16
- package/src/commands/state/advance.js +17 -17
- package/src/commands/state/approve.js +0 -6
- package/src/commands/state/archive.js +27 -1
- package/src/commands/state/auto-approval.js +1 -1
- package/src/commands/state/create.js +0 -5
- package/src/commands/state/finish.js +43 -1
- package/src/commands/state/gate-check.js +37 -30
- package/src/commands/state/gate-decision.js +3 -2
- package/src/commands/state/next.js +1 -0
- package/src/commands/state/recap.js +67 -0
- package/src/commands/state/report-check.js +39 -146
- package/src/commands/validation/plan-check.js +16 -11
- package/src/commands/validation/validate-feature.js +3 -3
- package/src/commands/validation/verify.js +6 -6
- package/src/core/paths/output-schema.js +3 -3
- package/src/core/state/state-manager.js +2 -4
- package/src/dashboard/app/components/CommandPalette.jsx +2 -5
- package/src/dashboard/app/components/GatePauses.jsx +116 -0
- package/src/dashboard/app/components/SessionMap.jsx +77 -0
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/Activity.jsx +4 -0
- package/src/dashboard/app/pages/AgentMap.jsx +1 -2
- package/src/dashboard/app/pages/FeatureDetail.jsx +129 -186
- package/src/dashboard/app/pages/Features.jsx +7 -7
- package/src/dashboard/app/pages/PhasePipeline.jsx +1 -1
- package/src/dashboard/app/pages/Portfolio.jsx +40 -5
- package/src/dashboard/scanner/activity-scanner.js +29 -0
- package/src/dashboard/scanner/agents-scanner.js +11 -14
- package/src/dashboard/scanner/features-scanner.js +45 -24
- package/src/dashboard/scanner/project-api.js +11 -2
- package/src/dashboard/scanner/skills-scanner.js +11 -1
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/e2e/applicability.js +3 -1
- package/src/lib/git/tree-state.js +1 -16
- package/src/lib/graph/diff-tree.js +127 -0
- package/src/lib/graph/graphify-runtime.js +12 -5
- package/src/lib/heavy/gate.js +22 -1
- package/src/lib/installers/memory-installer.js +2 -2
- package/src/lib/installers/rules-installer.js +57 -0
- package/src/lib/installers/stack-assets.js +17 -0
- package/src/lib/installers/stack-sync.js +219 -0
- package/src/lib/plan/checks.js +55 -110
- package/src/lib/plan/critique.js +28 -0
- package/src/lib/plan/media.js +42 -0
- package/src/lib/plan/plan-html.js +125 -0
- package/src/lib/plan/plan-parser.js +34 -45
- package/src/lib/recap/piper.js +36 -0
- package/src/lib/recap/recap.js +232 -0
- package/src/lib/review-reports.js +33 -7
- package/src/lib/risk-detector.js +90 -11
- package/src/lib/specs/screen-checklist.js +33 -48
- package/src/lib/stack/dotnet-build-ignores.js +8 -9
- package/src/lib/tasks/test-nodes.js +24 -6
- package/src/lib/tasks/test-runner.js +40 -1
- package/src/lib/telemetry/metrics.js +27 -29
- package/src/lib/validators/reuse/reuse-validator.js +1 -9
- package/src/lib/validators/verify-runner.js +50 -19
- package/src/lib/worktree/fleet-scanner.js +7 -20
- package/src/lib/worktree/worktree-audit.js +0 -37
- package/src/scripts/setup-infra.js +15 -50
- package/src/utils/agents-installer.js +27 -201
- package/src/utils/hooks-installer.js +11 -95
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +0 -23
- package/framework/hooks/claude-code/statusline.py +0 -699
- package/framework/hooks/claude-code/statusline.sh +0 -11
- package/framework/hooks/shared/agent-reap.js +0 -74
- package/framework/hooks/shared/slice-progress.js +0 -110
- package/framework/skills/core/morph-hotfix/SKILL.md +0 -133
- package/framework/skills/core/morph-machine/SKILL.md +0 -46
- package/framework/skills/core/morph-review/SKILL.md +0 -129
- package/framework/skills/core/morph-standards/SKILL.md +0 -52
- package/framework/standards/PROVENANCE.json +0 -119
- package/src/commands/project/worktree-reap.js +0 -34
- package/src/commands/state/handoff.js +0 -65
- package/src/commands/validation/mutate.js +0 -263
- package/src/commands/validation/scout-check.js +0 -28
- package/src/lib/generators/handoff-generator.js +0 -172
- package/src/lib/mutation/adapters.js +0 -79
- package/src/lib/mutation/commands.js +0 -61
- package/src/lib/mutation/dotnet-introspection.js +0 -281
- package/src/lib/mutation/manifest.js +0 -144
- package/src/lib/mutation/plan.js +0 -44
- package/src/lib/mutation/report.js +0 -68
- package/src/lib/mutation/runner.js +0 -301
- package/src/lib/mutation/schemata.js +0 -60
- package/src/lib/mutation/snapshot.js +0 -65
- package/src/lib/mutation/subset.js +0 -109
- package/src/lib/mutation/trx.js +0 -50
- package/src/lib/scout/scout-check.js +0 -293
- package/src/lib/validators/traceability-check.js +0 -103
- package/src/lib/worktree/agent-reaper.js +0 -247
|
@@ -18,12 +18,12 @@ import {
|
|
|
18
18
|
import { listClaims } from '../../../framework/hooks/shared/claim-registry.js';
|
|
19
19
|
import { buildPrIndex } from '../git/pr-index.js';
|
|
20
20
|
import { pendingPrFeatures } from '../git/pending-prs.js';
|
|
21
|
-
import {
|
|
21
|
+
import { implementProgress, implementProgressFrom } from '../../../framework/hooks/shared/report-progress.js';
|
|
22
22
|
|
|
23
23
|
const counts = (progress) => (progress ? { done: progress.done, total: progress.total } : null);
|
|
24
24
|
|
|
25
|
-
// A root-resident feature's
|
|
26
|
-
function
|
|
25
|
+
// A root-resident feature's report lives on `morph/{f}`; the root tree only has the pre-work copy.
|
|
26
|
+
function scenarioCounts(featureDir, { root, feature, featureBranch } = {}) {
|
|
27
27
|
if (featureBranch && root && feature) {
|
|
28
28
|
const show = (rel) => {
|
|
29
29
|
try {
|
|
@@ -32,21 +32,10 @@ function sliceCounts(featureDir, { root, feature, featureBranch } = {}) {
|
|
|
32
32
|
return null;
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
|
-
const onBranch =
|
|
35
|
+
const onBranch = implementProgressFrom(show('2-plan/checks.json'), show('3-implement/report.md'), (id) => show(`3-implement/reports/${id}.md`));
|
|
36
36
|
if (onBranch) return counts(onBranch);
|
|
37
37
|
}
|
|
38
|
-
return counts(
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function handoffInfo(featureDir) {
|
|
42
|
-
try {
|
|
43
|
-
const path = join(featureDir, 'handoff.md');
|
|
44
|
-
if (!existsSync(path)) return { exists: false, generatedAt: null };
|
|
45
|
-
const generatedAt = readFileSync(path, 'utf8').match(/gerado em (\d{4}-\d{2}-\d{2}T[\d:.]+Z)/)?.[1] || null;
|
|
46
|
-
return { exists: true, generatedAt };
|
|
47
|
-
} catch {
|
|
48
|
-
return { exists: false, generatedAt: null };
|
|
49
|
-
}
|
|
38
|
+
return counts(implementProgress(featureDir));
|
|
50
39
|
}
|
|
51
40
|
|
|
52
41
|
function lastCommitRelative(root, branch) {
|
|
@@ -132,11 +121,10 @@ function buildEntry(feature, { location, path, branch, root, allocations, blockS
|
|
|
132
121
|
location,
|
|
133
122
|
path,
|
|
134
123
|
branch,
|
|
135
|
-
|
|
124
|
+
scenarios: scenarioCounts(featureDir, { root, feature, featureBranch }),
|
|
136
125
|
env: alloc && Number.isInteger(alloc.base)
|
|
137
126
|
? { base: alloc.base, range: [alloc.base, alloc.base + blockSize - 1] }
|
|
138
127
|
: null,
|
|
139
|
-
handoff: handoffInfo(featureDir),
|
|
140
128
|
lastCommit: location === 'worktree' && branch ? lastCommitRelative(root, branch) : null,
|
|
141
129
|
dirty: location === 'worktree' ? dirtyCount(path) : null,
|
|
142
130
|
remote: buildRemote(feature, branch, heads, prs),
|
|
@@ -221,9 +209,8 @@ export function scanFleet(cwd = process.cwd()) {
|
|
|
221
209
|
location: 'pr-pending',
|
|
222
210
|
path: null,
|
|
223
211
|
branch: p.branch,
|
|
224
|
-
|
|
212
|
+
scenarios: null,
|
|
225
213
|
env: null,
|
|
226
|
-
handoff: { exists: false, generatedAt: null },
|
|
227
214
|
lastCommit: null,
|
|
228
215
|
dirty: null,
|
|
229
216
|
remote: { branch: p.branch, pushed: true, sha: null, pr: { number: p.number, state: p.state } },
|
|
@@ -13,40 +13,8 @@ import { buildPrIndex } from '../git/pr-index.js';
|
|
|
13
13
|
import { classifyBranch, commitsAhead, resolveBaseRef } from '../git/branch-classifier.js';
|
|
14
14
|
import { auditDockerStacks } from './docker-audit.js';
|
|
15
15
|
import { auditFinishBackups } from './finish-backup.js';
|
|
16
|
-
import { planAgentReap } from './agent-reaper.js';
|
|
17
16
|
|
|
18
17
|
const norm = (p) => resolve(p).replace(/\\/g, '/');
|
|
19
|
-
const AGENT_TASK = /^agent-[0-9a-f]+$/;
|
|
20
|
-
|
|
21
|
-
const AGENT_FIX = {
|
|
22
|
-
'stopped-clean': 'morph-spec worktree reap (ou `morph-spec doctor --worktrees --prune`)',
|
|
23
|
-
'orphan-branch': 'morph-spec worktree reap (ou `morph-spec doctor --worktrees --prune`)',
|
|
24
|
-
'directory-missing': 'morph-spec doctor --worktrees --prune',
|
|
25
|
-
locked: 'a árvore está com `git worktree lock`; `git worktree unlock <path>` libera, se o lock já não vale',
|
|
26
|
-
unknown: 'não foi possível provar que é seguro remover — deixe como está',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
function agentLeftoverIssue(item) {
|
|
30
|
-
const where = item.path ?? item.branch;
|
|
31
|
-
const reapable = item.state === 'reapable';
|
|
32
|
-
const fix = AGENT_FIX[item.reason]
|
|
33
|
-
?? (item.reason === 'exclusive-commits'
|
|
34
|
-
? `integre a branch (\`git merge --no-ff ${item.branch}\` no orquestrador) antes de remover — ela é o único exemplar`
|
|
35
|
-
: item.reason === 'dirty'
|
|
36
|
-
? `confira o trabalho na árvore (commite ou descarte) e rode \`morph-spec worktree remove --target "${item.path}"\``
|
|
37
|
-
: `o harness não registrou o fim do agente; se ele já terminou, \`morph-spec worktree remove --target "${item.path}"\``);
|
|
38
|
-
return {
|
|
39
|
-
type: 'agent-worktree-leftover',
|
|
40
|
-
severity: 'info',
|
|
41
|
-
...(item.path ? { path: item.path } : {}),
|
|
42
|
-
branch: item.branch,
|
|
43
|
-
reapable,
|
|
44
|
-
detail: reapable
|
|
45
|
-
? `${where} é sobra de sub-agent que já parou, sem trabalho pendente`
|
|
46
|
-
: `${where} é sobra de sub-agent com trabalho a preservar (${item.reason}${item.detail ? `: ${item.detail}` : ''})`,
|
|
47
|
-
fix,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
18
|
|
|
51
19
|
function knownFeatures(root) {
|
|
52
20
|
const names = new Set();
|
|
@@ -197,7 +165,6 @@ export function auditWorktrees(cwd = process.cwd(), { exec } = {}) {
|
|
|
197
165
|
}
|
|
198
166
|
|
|
199
167
|
if (task) {
|
|
200
|
-
if (AGENT_TASK.test(task)) continue; // reported once, by the agent plan below
|
|
201
168
|
const work = unintegratedWork(root, entry.branch, prIndex);
|
|
202
169
|
if (work.unintegrated) {
|
|
203
170
|
issues.push({
|
|
@@ -262,10 +229,6 @@ export function auditWorktrees(cwd = process.cwd(), { exec } = {}) {
|
|
|
262
229
|
}
|
|
263
230
|
}
|
|
264
231
|
|
|
265
|
-
for (const item of planAgentReap(root, { cwd }).items) {
|
|
266
|
-
if (item.state === 'reapable' || item.state === 'preserved') issues.push(agentLeftoverIssue(item));
|
|
267
|
-
}
|
|
268
|
-
|
|
269
232
|
for (const d of dirsOnDisk(root)) {
|
|
270
233
|
if (registeredPaths.has(d.path)) continue;
|
|
271
234
|
issues.push({
|
|
@@ -18,10 +18,11 @@ import { injectMorphImport } from '../utils/claude-md-injector.js';
|
|
|
18
18
|
import { saveProjectMorphVersion, getInstalledCLIVersion, getProjectMorphVersion } from '../utils/version-checker.js';
|
|
19
19
|
import { installClaudeHooks, installGlobalStatusline } from '../utils/claude-settings-manager.js';
|
|
20
20
|
import { installSkills } from '../utils/skills-installer.js';
|
|
21
|
-
import { installAgents
|
|
21
|
+
import { installAgents } from '../utils/agents-installer.js';
|
|
22
22
|
import { resolvePrimaryRoot } from '../commands/project/worktree.js';
|
|
23
23
|
import { reconcileLegacyTemplatesCopy } from '../lib/installers/templates-reconcile.js';
|
|
24
24
|
import { realEffects } from '../lib/installers/effects.js';
|
|
25
|
+
import { installRulesForStack } from '../lib/installers/rules-installer.js';
|
|
25
26
|
|
|
26
27
|
const FRAMEWORK_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'framework');
|
|
27
28
|
|
|
@@ -41,31 +42,7 @@ async function installRequiredPlugins(log, exec) {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
export function installRulesForStack(rulesSrc, rulesDest, projectTags, fx = realEffects()) {
|
|
46
|
-
const sourceFiles = readdirSync(rulesSrc).filter(f => f.endsWith('.md'));
|
|
47
|
-
const installed = new Set();
|
|
48
|
-
|
|
49
|
-
for (const file of sourceFiles) {
|
|
50
|
-
const content = readFileSync(join(rulesSrc, file), 'utf-8');
|
|
51
|
-
const stacks = parseStacks(content);
|
|
52
|
-
if (shouldInstall(stacks, projectTags)) {
|
|
53
|
-
fx.copyFile(join(rulesSrc, file), join(rulesDest, file));
|
|
54
|
-
installed.add(file);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Calado em `dryRun`: `cleanFrameworkDirs()` já apagou `.claude/rules/` inteiro antes da execução real.
|
|
59
|
-
if (!fx.dryRun) {
|
|
60
|
-
for (const file of readdirSync(rulesDest).filter(f => f.endsWith('.md'))) {
|
|
61
|
-
if (!installed.has(file)) {
|
|
62
|
-
fx.remove(join(rulesDest, file));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return { installed: installed.size };
|
|
68
|
-
}
|
|
45
|
+
export { installRulesForStack };
|
|
69
46
|
|
|
70
47
|
/** Clean-slate: removes only a framework command the stack filtered out, never one the user wrote. */
|
|
71
48
|
export function installCommandsForStack(commandsSrc, commandsDest, projectTags, fx = realEffects()) {
|
|
@@ -118,10 +95,7 @@ async function installStackAwareAssets(targetPath, log) {
|
|
|
118
95
|
log('Installing agents...');
|
|
119
96
|
const agentCounts = await installAgents(targetPath, FRAMEWORK_DIR, { projectTags });
|
|
120
97
|
|
|
121
|
-
|
|
122
|
-
const domainCounts = await installDomainAgents(targetPath, FRAMEWORK_DIR, { projectTags });
|
|
123
|
-
|
|
124
|
-
return { agentCounts, domainCounts, projectTags };
|
|
98
|
+
return { agentCounts, projectTags };
|
|
125
99
|
}
|
|
126
100
|
|
|
127
101
|
/**
|
|
@@ -138,9 +112,9 @@ export async function updateClaudeMdAgentsBlock(claudeMdPath, agents = [], optio
|
|
|
138
112
|
|
|
139
113
|
const list = agents.length > 0
|
|
140
114
|
? agents.map(a => `- \`${a.name}\` — ${a.title}${a.domains?.length ? ` (${a.domains.join(', ')})` : ''}`).join('\n')
|
|
141
|
-
: '
|
|
115
|
+
: '_Nenhum agente instalado — rode `morph-spec setup-infra --reinstall`._';
|
|
142
116
|
|
|
143
|
-
const block = `${START}\n**
|
|
117
|
+
const block = `${START}\n**Agentes do Gate 3** (derivado de agents.json na última instalação):\n\n${list}\n${END}`;
|
|
144
118
|
const updated = content.replace(
|
|
145
119
|
new RegExp(`${START}[\\s\\S]*?${END}`),
|
|
146
120
|
block
|
|
@@ -178,16 +152,10 @@ export async function setupInfra(targetPath, {
|
|
|
178
152
|
|
|
179
153
|
if (reinstall) {
|
|
180
154
|
log('setup-infra (reinstall): re-installing stack-aware assets...');
|
|
181
|
-
const { agentCounts
|
|
155
|
+
const { agentCounts } = await installStackAwareAssets(targetPath, log);
|
|
182
156
|
await updateClaudeMdAgentsBlock(join(targetPath, '.claude', 'CLAUDE.md'), agentCounts?.agents ?? []);
|
|
183
157
|
log('setup-infra (reinstall): complete.');
|
|
184
|
-
return {
|
|
185
|
-
agents: {
|
|
186
|
-
tier1: agentCounts?.tier1 ?? 0,
|
|
187
|
-
tier2: agentCounts?.tier2 ?? 0,
|
|
188
|
-
specialists: domainCounts?.specialists ?? 0,
|
|
189
|
-
},
|
|
190
|
-
};
|
|
158
|
+
return { agents: { installed: agentCounts?.installed ?? 0 } };
|
|
191
159
|
}
|
|
192
160
|
|
|
193
161
|
await installRequiredPlugins(log, _exec);
|
|
@@ -316,7 +284,7 @@ export async function setupInfra(targetPath, {
|
|
|
316
284
|
const claudeDest = join(targetPath, '.claude');
|
|
317
285
|
|
|
318
286
|
log('Steps 10-13: Installing stack-aware assets...');
|
|
319
|
-
const { agentCounts,
|
|
287
|
+
const { agentCounts, projectTags } = await installStackAwareAssets(targetPath, log);
|
|
320
288
|
|
|
321
289
|
log('Step 14: Installing .claude/CLAUDE.md...');
|
|
322
290
|
const runtimeClaudeMdDest = join(claudeDest, 'CLAUDE.md');
|
|
@@ -374,7 +342,9 @@ export async function setupInfra(targetPath, {
|
|
|
374
342
|
log(` ✓ graphify ${installed} já instalado`);
|
|
375
343
|
} else {
|
|
376
344
|
const { kind } = detectInstaller();
|
|
377
|
-
if (
|
|
345
|
+
if (process.env.MORPH_NO_TOOL_INSTALL === '1') {
|
|
346
|
+
log(` ○ graphify não instalado (MORPH_NO_TOOL_INSTALL=1). Depois: uv tool install ${GRAPHIFY_PACKAGE}==${GRAPHIFY_VERSION}`);
|
|
347
|
+
} else if (!kind) {
|
|
378
348
|
log(` ⚠ Sem uv/pipx/python — grafo indisponível. Depois: uv tool install ${GRAPHIFY_PACKAGE}==${GRAPHIFY_VERSION}`);
|
|
379
349
|
} else {
|
|
380
350
|
// Sem o extra [sql] os arquivos .sql são ignorados em silêncio.
|
|
@@ -382,7 +352,8 @@ export async function setupInfra(targetPath, {
|
|
|
382
352
|
const { cmd, args } = installArgs(kind, { withSql });
|
|
383
353
|
try {
|
|
384
354
|
_exec(`${cmd} ${args.join(' ')}`, { stdio: 'ignore' });
|
|
385
|
-
log(` ✓ graphify ${GRAPHIFY_VERSION}${withSql ? ' [sql]' : ''} via ${kind}`);
|
|
355
|
+
if (probe() === GRAPHIFY_VERSION) log(` ✓ graphify ${GRAPHIFY_VERSION}${withSql ? ' [sql]' : ''} via ${kind}`);
|
|
356
|
+
else log(` ⚠ ${kind} instalou o graphify, mas o binário não responde (nem no PATH nem no venv da ferramenta) — rode ${cmd} ${args.join(' ')} --force no seu terminal`);
|
|
386
357
|
} catch {
|
|
387
358
|
log(` ⚠ Falha ao instalar o graphify via ${kind} — manualmente: ${cmd} ${args.join(' ')}`);
|
|
388
359
|
}
|
|
@@ -414,11 +385,5 @@ export async function setupInfra(targetPath, {
|
|
|
414
385
|
|
|
415
386
|
log('setup-infra: complete.');
|
|
416
387
|
|
|
417
|
-
return {
|
|
418
|
-
agents: {
|
|
419
|
-
tier1: agentCounts?.tier1 ?? 0,
|
|
420
|
-
tier2: agentCounts?.tier2 ?? 0,
|
|
421
|
-
specialists: domainCounts?.specialists ?? 0,
|
|
422
|
-
},
|
|
423
|
-
};
|
|
388
|
+
return { agents: { installed: agentCounts?.installed ?? 0 } };
|
|
424
389
|
}
|
|
@@ -4,15 +4,8 @@ import { shouldInstall } from '../lib/stack-filter.js';
|
|
|
4
4
|
import { realEffects } from '../lib/installers/effects.js';
|
|
5
5
|
import { REVIEW_ROLES, VERDICT_TOKEN, REVIEWED_SHA_TOKEN, VERDICTS } from '../lib/review-reports.js';
|
|
6
6
|
|
|
7
|
-
export const DEFAULT_AGENT_TOOLS = 'Read,
|
|
7
|
+
export const DEFAULT_AGENT_TOOLS = 'Read, Glob, Grep, Bash';
|
|
8
8
|
|
|
9
|
-
/** Derived from the effective tool scope (Write or Edit), so a persona made read-only stops being one. */
|
|
10
|
-
export function isImplementer(agent) {
|
|
11
|
-
const tools = String(agent?.tools ?? DEFAULT_AGENT_TOOLS).split(',').map((t) => t.trim());
|
|
12
|
-
return tools.includes('Write') || tools.includes('Edit');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// The value hardcoded before the field existed: an agents.json without `maxTurns` renders byte-for-byte as before.
|
|
16
9
|
export const DEFAULT_MAX_TURNS = 20;
|
|
17
10
|
|
|
18
11
|
const CHECKPOINT_PERCENT = 70;
|
|
@@ -43,84 +36,12 @@ export function resolveMaxTurns(agent) {
|
|
|
43
36
|
return agent.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
44
37
|
}
|
|
45
38
|
|
|
46
|
-
export const VALID_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
47
|
-
|
|
48
|
-
// Absent: no `effort:` line, so the sub-agent inherits the session effort.
|
|
49
|
-
export function effortProblem(value) {
|
|
50
|
-
if (value === undefined) return null;
|
|
51
|
-
return VALID_EFFORTS.includes(value) ? null : `esperado um de ${VALID_EFFORTS.join('|')}`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function effortError(id, value) {
|
|
55
|
-
const problem = effortProblem(value);
|
|
56
|
-
if (!problem) return null;
|
|
57
|
-
return `agents.json: persona "${id}" tem effort inválido (${JSON.stringify(value)}) — ${problem}. ` +
|
|
58
|
-
'Remova o campo para herdar o effort da sessão.';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const SURFACE_ID_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
62
|
-
|
|
63
|
-
const SURFACE_KEYS = new Set(['when', 'standards']);
|
|
64
|
-
|
|
65
|
-
// A standard both universal and in a surface is refused: the persona would keep
|
|
66
|
-
// loading it on every surface without anyone noticing.
|
|
67
|
-
export function surfacesProblem(surfaces, standards = []) {
|
|
68
|
-
if (surfaces === undefined) return null;
|
|
69
|
-
if (surfaces === null || typeof surfaces !== 'object' || Array.isArray(surfaces)) {
|
|
70
|
-
return 'esperado objeto { "<superfície>": { when, standards } }';
|
|
71
|
-
}
|
|
72
|
-
const entries = Object.entries(surfaces);
|
|
73
|
-
if (entries.length === 0) return 'esperado ao menos uma superfície';
|
|
74
|
-
|
|
75
|
-
const universal = new Set(Array.isArray(standards) ? standards : []);
|
|
76
|
-
for (const [name, def] of entries) {
|
|
77
|
-
if (!SURFACE_ID_RE.test(name)) return `superfície "${name}" — o id é kebab-case (ex.: "admin-app")`;
|
|
78
|
-
if (def === null || typeof def !== 'object' || Array.isArray(def)) {
|
|
79
|
-
return `superfície "${name}" — esperado { when, standards }`;
|
|
80
|
-
}
|
|
81
|
-
const unknown = Object.keys(def).filter((k) => !SURFACE_KEYS.has(k));
|
|
82
|
-
if (unknown.length > 0) return `superfície "${name}" — campo desconhecido: ${unknown.join(', ')}`;
|
|
83
|
-
if (typeof def.when !== 'string' || def.when.trim() === '') {
|
|
84
|
-
return `superfície "${name}" — "when" precisa dizer que telas são dessa superfície`;
|
|
85
|
-
}
|
|
86
|
-
const ids = def.standards;
|
|
87
|
-
if (!Array.isArray(ids) || ids.length === 0 || ids.some((s) => typeof s !== 'string' || s.trim() === '')) {
|
|
88
|
-
return `superfície "${name}" — "standards" precisa ser uma lista não vazia de ids`;
|
|
89
|
-
}
|
|
90
|
-
const repeated = ids.filter((s, i) => ids.indexOf(s) !== i);
|
|
91
|
-
if (repeated.length > 0) return `superfície "${name}" repete ${[...new Set(repeated)].join(', ')}`;
|
|
92
|
-
const overlap = ids.filter((s) => universal.has(s));
|
|
93
|
-
if (overlap.length > 0) {
|
|
94
|
-
return `superfície "${name}" repete standard da lista universal (${overlap.join(', ')}) — ` +
|
|
95
|
-
'um standard é universal OU de uma superfície';
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function surfacesError(id, surfaces, standards) {
|
|
102
|
-
const problem = surfacesProblem(surfaces, standards);
|
|
103
|
-
if (!problem) return null;
|
|
104
|
-
return `agents.json: persona "${id}" tem surfaces inválido — ${problem}. ` +
|
|
105
|
-
'Remova o campo para a persona ler só `standards`.';
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** @returns {string[]} unique ids of `standards` plus every surface's, universal first */
|
|
109
39
|
export function personaStandardIds(persona) {
|
|
110
|
-
|
|
111
|
-
const surfaces = persona?.surfaces;
|
|
112
|
-
if (surfaces && typeof surfaces === 'object' && !Array.isArray(surfaces)) {
|
|
113
|
-
for (const def of Object.values(surfaces)) {
|
|
114
|
-
if (Array.isArray(def?.standards)) ids.push(...def.standards);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return [...new Set(ids)];
|
|
40
|
+
return [...new Set(Array.isArray(persona?.standards) ? persona.standards : [])];
|
|
118
41
|
}
|
|
119
42
|
|
|
120
43
|
function personaError(agent) {
|
|
121
|
-
return maxTurnsError(agent.id, agent.maxTurns)
|
|
122
|
-
?? surfacesError(agent.id, agent.surfaces, agent.standards)
|
|
123
|
-
?? effortError(agent.id, agent.effort);
|
|
44
|
+
return maxTurnsError(agent.id, agent.maxTurns);
|
|
124
45
|
}
|
|
125
46
|
|
|
126
47
|
// `tools:` is a strict allowlist: without Skill no skill runs, without ToolSearch no deferred MCP tool loads.
|
|
@@ -165,6 +86,8 @@ const MANAGED_HEADER =
|
|
|
165
86
|
' Do NOT edit by hand: changes are overwritten on `morph-spec update` / `setup-infra --reinstall`.\n' +
|
|
166
87
|
' To change this persona, edit agents.json and regenerate. Drift is flagged by `morph-spec doctor`. -->';
|
|
167
88
|
|
|
89
|
+
const MANAGED_HEADER_MARKER = MANAGED_HEADER.split('\n')[0];
|
|
90
|
+
|
|
168
91
|
export async function installAgents(projectDir, frameworkDir = 'framework', options = {}) {
|
|
169
92
|
const { projectTags = [], fx = realEffects() } = options;
|
|
170
93
|
|
|
@@ -213,24 +136,30 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
|
|
|
213
136
|
// Silent in dryRun: the real update's cleanFrameworkDirs() already emptied the dir.
|
|
214
137
|
if (!fx.dryRun) {
|
|
215
138
|
for (const file of readdirSync(targetDir)) {
|
|
216
|
-
if (
|
|
217
|
-
|
|
218
|
-
|
|
139
|
+
if (installedFilenames.has(file)) continue;
|
|
140
|
+
const path = join(targetDir, file);
|
|
141
|
+
if (allPossible.has(file) || isManagedOrphan(file, path)) fx.remove(path);
|
|
219
142
|
}
|
|
220
143
|
}
|
|
221
144
|
|
|
222
145
|
return { installed, agents: installedAgents };
|
|
223
146
|
}
|
|
224
147
|
|
|
148
|
+
// Only the managed header proves the file is ours: a hand-written morph-*.md is never deleted.
|
|
149
|
+
function isManagedOrphan(file, path) {
|
|
150
|
+
if (!file.startsWith('morph-') || !file.endsWith('.md')) return false;
|
|
151
|
+
try {
|
|
152
|
+
return readFileSync(path, 'utf-8').includes(MANAGED_HEADER_MARKER);
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
225
158
|
// Deterministic: the doctor drift check compares files byte-for-byte against this output.
|
|
226
159
|
export function renderAgentMd(agent, knownMcpIds = null) {
|
|
227
160
|
const description = buildDescription(agent);
|
|
228
161
|
// One value for the frontmatter and the body: the sub-agent never sees its own frontmatter.
|
|
229
162
|
const maxTurns = resolveMaxTurns(agent);
|
|
230
|
-
const surfaces = surfacesError(agent.id, agent.surfaces, agent.standards);
|
|
231
|
-
if (surfaces) throw new Error(surfaces);
|
|
232
|
-
const effort = effortError(agent.id, agent.effort);
|
|
233
|
-
if (effort) throw new Error(effort);
|
|
234
163
|
const frontmatter = buildFrontmatter(agent, description, knownMcpIds, maxTurns);
|
|
235
164
|
const body = buildBody(agent, maxTurns);
|
|
236
165
|
return `---\n${frontmatter}---\n\n${MANAGED_HEADER}\n\n${body}\n`;
|
|
@@ -251,10 +180,8 @@ function buildFrontmatter(agent, description, knownMcpIds, maxTurns) {
|
|
|
251
180
|
`description: ${description}`,
|
|
252
181
|
`model: ${agent.model ?? 'inherit'}`,
|
|
253
182
|
`tools: ${buildToolsLine(agent, knownMcpIds)}`,
|
|
254
|
-
// Position fixed so an agents.json without the field renders byte-for-byte as before.
|
|
255
183
|
`maxTurns: ${maxTurns}`,
|
|
256
184
|
];
|
|
257
|
-
if (agent.effort) lines.push(`effort: ${agent.effort}`);
|
|
258
185
|
if (agent.color) lines.push(`color: ${agent.color}`);
|
|
259
186
|
lines.push(`memory: project`, '');
|
|
260
187
|
return lines.join('\n');
|
|
@@ -267,58 +194,9 @@ function buildDescription(agent) {
|
|
|
267
194
|
return desc.length > 200 ? desc.slice(0, 197) + '...' : desc;
|
|
268
195
|
}
|
|
269
196
|
|
|
270
|
-
const NEON_CAUTION = 'NEON: o MCP `neon` inspeciona schema e ensaia migração numa branch descartável ' +
|
|
271
|
-
'(prepare_database_migration → complete_database_migration); ele também APAGA branch e projeto — ' +
|
|
272
|
-
'nunca chame tool destrutiva sem confirmação humana explícita.';
|
|
273
|
-
|
|
274
197
|
const STANDARDS_HINT = 'Digest de cada um: `morph-spec standards --find <id>`; só a seção que importa: ' +
|
|
275
198
|
'`morph-spec standards --show <id>#<âncora>` (`--show <id>` lista as âncoras).';
|
|
276
199
|
|
|
277
|
-
export const DISPATCH_CONTRACT = [
|
|
278
|
-
'## Contrato de dispatch',
|
|
279
|
-
'',
|
|
280
|
-
'O dispatch traz só o que muda por fatia: id e título, `cobre` com o texto de cada cenário, `arquivos`, `standards`, ' +
|
|
281
|
-
'o caminho do relatório e, se houver, o worktree. O resto é este contrato.',
|
|
282
|
-
'',
|
|
283
|
-
'1. RELATÓRIO: GRAVE o arquivo de relatório que o dispatch nomeia (`.morph/features/{feature}/3-implement/reports/F{n}.md`) ' +
|
|
284
|
-
'ANTES de encerrar — é o retorno autoritativo, não a sua mensagem final.',
|
|
285
|
-
'2. ESTADO: NÃO toque em `plan.md`, `checks.json`, `feature.json` nem `state.json`: o estado é do orquestrador.',
|
|
286
|
-
'3. TESTE: a prova é do orquestrador (`morph-spec verify {feature}`, depois do seu retorno), não sua. Escreva os testes pelo orçamento: ' +
|
|
287
|
-
'um por Cn `prova: teste`, variantes em tabela (`[Theory]`/`it.each`); `leitura` sem teste. NÃO rode teste a cada marco nem faça ciclo red→green: ' +
|
|
288
|
-
'escreva código e testes, compile, e rode SÓ o filtro dos seus testes UMA vez, em primeiro plano, antes do relatório ' +
|
|
289
|
-
'(`vitest related <arquivos da fatia> --run`; `dotnet test --filter "FullyQualifiedName~<Classe>" --no-build` após o build); ' +
|
|
290
|
-
'se falhar, corrija e rode só os que falharam. Teto: 3 rodadas (`verify --filter` conta, exit 3 não); ' +
|
|
291
|
-
'o hook nega a 4ª e o `verify` sem `--filter`: grave `PARCIAL` com o que ainda falha e devolva. ' +
|
|
292
|
-
'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. ' +
|
|
293
|
-
'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.',
|
|
294
|
-
'4. SOBREVIVÊNCIA: o limite de turnos corta sem aviso: commite `chore(F{n}): wip …` a cada marco (um arquivo ou uma fatia escrita), não só ao fim — ' +
|
|
295
|
-
'e só os seus arquivos (`git add <paths>`, nunca diretório). Sem commit, a retomada recomeça do zero.',
|
|
296
|
-
'5. PROCESSOS: encerre dev server pelo PID que você mesmo subiu. Build e suíte pesados rodam por `morph-spec heavy run -- <comando>`, em primeiro plano; ' +
|
|
297
|
-
'exit 3 é fila cheia: não repita nem espere em laço, diga no relatório o que não rodou.',
|
|
298
|
-
'6. REUSO: antes de criar tipo, componente, hook ou serviço NOVO, verifique se já existe: `morph-spec graph explain <nome>` e ' +
|
|
299
|
-
'`graph affected <conceito> --uses-only`; sem grafo (`no-graph`), caia para `grep -rn "<nome>"` e siga — a ausência de grafo nunca bloqueia. ' +
|
|
300
|
-
'Se criar mesmo assim, diga por que em uma linha no relatório.',
|
|
301
|
-
'7. MUTAÇÃO: nunca altere, reverta, mova ou apague código de produção para ver um teste falhar; a prova de que o teste morde é do verificador.',
|
|
302
|
-
'8. ESCRITA: código e teste do projeto só por Write/Edit, nunca pelo Bash (heredoc, `sed -i`, script que grava): os guards só enxergam Write/Edit.',
|
|
303
|
-
'9. BLOQUEIO: decisão que não é sua (requisito contra o plano ou um ADR, escolha deixada ao humano) → `BLOQUEADO` na 1ª linha, ' +
|
|
304
|
-
'com a pergunta e as opções. Dificuldade técnica não é bloqueio.',
|
|
305
|
-
'',
|
|
306
|
-
'Formato do relatório (o `report-check` lê estas marcas):',
|
|
307
|
-
'',
|
|
308
|
-
'```',
|
|
309
|
-
'# F1 — <título>',
|
|
310
|
-
'O que foi feito, em 2-3 frases: o comportamento entregue e onde ele mora, sem repetir a lista de arquivos.',
|
|
311
|
-
'**Arquivos:** `src/a.cs`, `src/a.test.cs`',
|
|
312
|
-
'**Checks:**',
|
|
313
|
-
'- C1 — provado: `a.test.cs` › nome do teste (ou `arquivo:linha`)',
|
|
314
|
-
'- C2 — não provado: por quê',
|
|
315
|
-
'```',
|
|
316
|
-
'',
|
|
317
|
-
'`**Arquivos:**` lista TODO arquivo criado ou modificado, inclusive artefato auxiliar fora do plano (factory, seed, config gerada): ' +
|
|
318
|
-
'arquivo em produção sem dono é falha de protocolo que o Gate 3 aponta. Todo cenário que a fatia `cobre` aparece em `**Checks:**`. ' +
|
|
319
|
-
'Problemas e como foram resolvidos vão em prosa depois.',
|
|
320
|
-
];
|
|
321
|
-
|
|
322
200
|
export const REVIEW_REPORT_CONTRACT = [
|
|
323
201
|
'## Relatório do Gate 3',
|
|
324
202
|
'',
|
|
@@ -346,57 +224,20 @@ function buildBody(agent, maxTurns) {
|
|
|
346
224
|
'',
|
|
347
225
|
];
|
|
348
226
|
|
|
349
|
-
// The shape without effort or surfaces renders byte-for-byte as the old fixture.
|
|
350
|
-
const legacyShape = !agent.effort && !agent.surfaces && isImplementer(agent);
|
|
351
|
-
|
|
352
|
-
if (legacyShape && agent.domains?.length) {
|
|
353
|
-
lines.push('## Domains', ...agent.domains.map(d => `- ${d}`), '');
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (!legacyShape && agent.mcp?.includes('neon')) {
|
|
357
|
-
lines.push(NEON_CAUTION, '');
|
|
358
|
-
}
|
|
359
|
-
|
|
360
227
|
if (agent.standards?.length) {
|
|
361
|
-
lines.push('## Standards to consult on demand');
|
|
362
|
-
if (!legacyShape) lines.push(STANDARDS_HINT);
|
|
228
|
+
lines.push('## Standards to consult on demand', STANDARDS_HINT);
|
|
363
229
|
for (const s of agent.standards) lines.push(`- ${s}`);
|
|
364
230
|
lines.push('');
|
|
365
231
|
}
|
|
366
232
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
'',
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
);
|
|
376
|
-
for (const [name, def] of Object.entries(agent.surfaces)) {
|
|
377
|
-
lines.push(`### ${name}`, def.when, ...def.standards.map((s) => `- ${s}`), '');
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if (agent.effort && isImplementer(agent)) {
|
|
382
|
-
lines.push(...DISPATCH_CONTRACT, '');
|
|
383
|
-
} else if (isImplementer(agent)) {
|
|
384
|
-
lines.push(
|
|
385
|
-
'## How you operate',
|
|
386
|
-
'',
|
|
387
|
-
'You are a domain persona dispatched per task. Read the spec/tasks/standards passed to you, do the work, and report each doneCriteria with its evidence. Refer to MORPH.md section 6 for the dispatch contract.',
|
|
388
|
-
'',
|
|
389
|
-
);
|
|
390
|
-
} else {
|
|
391
|
-
lines.push(
|
|
392
|
-
'## Como você opera',
|
|
393
|
-
'',
|
|
394
|
-
'Você é read-only: nunca escreva, edite nem reverta código, testes, o plano ou `feature.json` (nada de `git checkout --`). ' +
|
|
395
|
-
'O entregável é o arquivo de relatório que o dispatch nomeia; sem arquivo nomeado, é a sua mensagem final (no scout, o mapa).',
|
|
396
|
-
'',
|
|
397
|
-
);
|
|
398
|
-
if (Object.hasOwn(REVIEW_ROLES, agent.id)) lines.push(...REVIEW_REPORT_CONTRACT, '');
|
|
399
|
-
}
|
|
233
|
+
lines.push(
|
|
234
|
+
'## Como você opera',
|
|
235
|
+
'',
|
|
236
|
+
'Você é read-only: nunca escreva, edite nem reverta código, testes, o plano ou `feature.json` (nada de `git checkout --`). ' +
|
|
237
|
+
'O entregável é o arquivo de relatório que o dispatch nomeia; sem arquivo nomeado, é a sua mensagem final.',
|
|
238
|
+
'',
|
|
239
|
+
);
|
|
240
|
+
if (Object.hasOwn(REVIEW_ROLES, agent.id)) lines.push(...REVIEW_REPORT_CONTRACT, '');
|
|
400
241
|
|
|
401
242
|
lines.push(
|
|
402
243
|
// `PARCIAL` on line 1 is what report-check reads; "no verdict" keeps a partial
|
|
@@ -409,17 +250,6 @@ function buildBody(agent, maxTurns) {
|
|
|
409
250
|
'`PARCIAL` on line 1, done, remaining, files, no verdict (final report only) — then continue or stop, your call; the final report replaces it.',
|
|
410
251
|
);
|
|
411
252
|
|
|
412
|
-
// The only text every implementer dispatch is guaranteed to carry.
|
|
413
|
-
if (agent.effort && isImplementer(agent)) {
|
|
414
|
-
lines.push(
|
|
415
|
-
'',
|
|
416
|
-
'## Working fast',
|
|
417
|
-
'',
|
|
418
|
-
'Independent tool calls go in the SAME response: read every listed file at once, fire the searches together. ' +
|
|
419
|
-
'Check a library API in the `context7` MCP before guessing (load a deferred `mcp__` tool with ToolSearch).',
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
253
|
return lines.join('\n');
|
|
424
254
|
}
|
|
425
255
|
|
|
@@ -428,10 +258,6 @@ function checkpointTurn(maxTurns) {
|
|
|
428
258
|
return Math.max(1, Math.floor((maxTurns * CHECKPOINT_PERCENT) / 100));
|
|
429
259
|
}
|
|
430
260
|
|
|
431
|
-
export async function installDomainAgents(projectDir, frameworkDir, options) {
|
|
432
|
-
return { specialists: 0 };
|
|
433
|
-
}
|
|
434
|
-
|
|
435
261
|
export function checkMissingAgents(projectDir, frameworkDir, projectTags = []) {
|
|
436
262
|
const targetDir = join(projectDir, '.claude', 'agents');
|
|
437
263
|
const installedFiles = new Set();
|