@polymorphism-tech/morph-spec 8.46.0 → 8.47.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 +5 -5
- package/README.md +2 -2
- package/bin/morph-spec.js +1 -4
- package/bin/validate.js +4 -25
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +2 -2
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +1 -1
- package/framework/MORPH.md +8 -8
- package/framework/commands/create-umbraco-lp.md +2 -0
- package/framework/commands/migrate-lp-to-umbraco.md +2 -0
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-hotfix.md +1 -1
- package/framework/commands/morph-infra.md +17 -203
- package/framework/commands/morph-media.md +3 -0
- package/framework/commands/morph-preflight.md +5 -28
- package/framework/commands/morph-proposal.md +2 -2
- package/framework/commands/morph-troubleshoot.md +2 -14
- package/framework/docs/CLI.md +3 -3
- package/framework/hooks/claude-code/core/state-sync.js +13 -1
- package/framework/hooks/shared/e2e-check.js +3 -4
- package/framework/hooks/shared/state-writer.js +1 -0
- package/framework/rules/ai-agents-standards.md +18 -16
- package/framework/rules/asaas-standards.md +3 -1
- package/framework/rules/csharp-standards.md +10 -8
- package/framework/rules/data-standards.md +5 -5
- package/framework/rules/dotnet-templates.md +1 -1
- package/framework/rules/frontend-standards.md +4 -2
- package/framework/rules/gsap-standards.md +10 -5
- package/framework/rules/hangfire-standards.md +3 -1
- package/framework/rules/infrastructure-standards.md +4 -2
- package/framework/rules/morph-workflow.md +2 -2
- package/framework/rules/neon-auth-standards.md +3 -1
- package/framework/rules/neon-standards.md +6 -4
- package/framework/rules/nextjs-standards.md +13 -11
- package/framework/rules/nextjs-templates.md +1 -1
- package/framework/rules/resend-standards.md +3 -1
- package/framework/schemas/feature.schema.json +1 -3
- package/framework/skills/README.md +1 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
- package/framework/skills/core/morph-apply/SKILL.md +3 -3
- package/framework/skills/core/morph-brainstorming/SKILL.md +3 -3
- package/framework/skills/core/morph-hotfix/SKILL.md +4 -4
- package/framework/skills/core/morph-implement/SKILL.md +3 -3
- package/framework/skills/core/morph-machine/SKILL.md +3 -3
- package/framework/skills/core/morph-media/SKILL.md +3 -3
- package/framework/skills/core/morph-plan/SKILL.md +3 -3
- package/framework/skills/core/morph-proposal/SKILL.md +3 -3
- package/framework/skills/core/morph-review/SKILL.md +3 -3
- package/framework/skills/core/morph-stack-scan/SKILL.md +3 -3
- package/framework/skills/core/morph-standards/SKILL.md +6 -6
- package/framework/skills/core/morph-uiux/SKILL.md +3 -3
- package/framework/standards/STANDARDS.json +59 -56
- package/framework/standards/ai-agents/evals-with-cache.md +1 -1
- package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
- package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
- package/framework/standards/ai-agents/prompt-sources.md +6 -4
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
- package/framework/standards/ai-agents/setup.md +1 -1
- package/framework/standards/ai-agents/structured-output.md +1 -1
- package/framework/standards/ai-agents/sweet-spot.md +1 -1
- package/framework/standards/architecture/code-comments.md +2 -2
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
- package/framework/standards/backend/api/validation.md +135 -47
- package/framework/standards/backend/database/postgresql/database.md +11 -7
- package/framework/standards/backend/database/vector-search-rag.md +207 -324
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
- package/framework/standards/backend/dotnet/result-pattern.md +40 -6
- package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
- package/framework/standards/backend/dotnet/scrutor.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
- package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
- package/framework/standards/data/nosql/cache/redis.md +120 -44
- package/framework/standards/data/nosql/cosmos-db.md +114 -71
- package/framework/standards/frontend/design-system/animations.md +1 -1
- package/framework/standards/frontend/design-system/naming.md +1 -1
- package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
- package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
- package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
- package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
- package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
- package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
- package/framework/templates/infrastructure/github/README.md +2 -1
- package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
- package/package.json +1 -1
- package/src/commands/observability/telemetry.js +3 -5
- package/src/commands/project/init.js +2 -1
- package/src/commands/project/update.js +16 -18
- package/src/commands/state/approve.js +17 -0
- package/src/commands/state/gate-check.js +4 -14
- package/src/commands/validation/verify.js +3 -12
- package/src/core/paths/output-schema.js +1 -1
- package/src/dashboard/app/App.jsx +0 -2
- package/src/dashboard/app/components/CommandPalette.jsx +2 -3
- package/src/dashboard/app/components/EntityLink.jsx +0 -1
- package/src/dashboard/app/components/Markdown.jsx +1 -1
- package/src/dashboard/app/components/Sidebar.jsx +1 -2
- package/src/dashboard/app/pages/RulesMap.jsx +3 -3
- package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
- package/src/dashboard/scanner/activity-scanner.js +1 -1
- package/src/dashboard/scanner/docs-scanner.js +5 -23
- package/src/dashboard/scanner/features-scanner.js +1 -1
- package/src/dashboard/scanner/index.js +0 -2
- package/src/dashboard/scanner/rules-scanner.js +15 -10
- package/src/dashboard/scanner/skills-scanner.js +2 -3
- package/src/lib/evaluator-report.js +13 -0
- package/src/lib/risk-detector.js +8 -0
- package/src/lib/stack/stack-profile.js +1 -1
- package/src/lib/validators/reuse/reuse-validator.js +2 -3
- package/src/lib/validators/reuse/symbol-scanner.js +2 -4
- package/src/lib/validators/validation-runner.js +8 -88
- package/src/lib/validators/verify-runner.js +22 -72
- package/src/scripts/setup-infra.js +33 -22
- package/src/utils/agents-installer.js +20 -8
- package/src/utils/file-copier.js +7 -5
- package/framework/commands/commit.md +0 -166
- package/framework/rubrics/backend.json +0 -41
- package/framework/rubrics/default.json +0 -36
- package/framework/rubrics/frontend.json +0 -36
- package/framework/templates/project/validate.js +0 -97
- package/src/dashboard/app/pages/Rubrics.jsx +0 -97
- package/src/dashboard/scanner/rubrics-scanner.js +0 -12
- package/src/lib/validators/architecture/architecture-validator.js +0 -270
- package/src/lib/validators/architecture/index.js +0 -7
- package/src/lib/validators/css/css-validator.js +0 -340
- package/src/lib/validators/css/index.js +0 -7
- package/src/lib/validators/design-system/design-system-validator.js +0 -237
- package/src/lib/validators/design-system/index.js +0 -7
- package/src/lib/validators/score-aggregator.js +0 -244
- package/src/lib/validators/ui/index.js +0 -7
- package/src/lib/validators/ui/ui-contrast-validator.js +0 -422
|
@@ -39,7 +39,8 @@ infrastructure/github/
|
|
|
39
39
|
- Push to container registry (GHCR or Docker Hub)
|
|
40
40
|
|
|
41
41
|
### Deploy Coolify (`deploy-coolify.yml.hbs`)
|
|
42
|
-
- SSHes into the VPS and calls the local Coolify API (`localhost:8000/api/v1/deploy?uuid=...`) after Docker push
|
|
42
|
+
- SSHes into the VPS and calls the local Coolify API (`POST localhost:8000/api/v1/deploy?uuid=...`) after Docker push; the step fails unless the response carries a `deployment_uuid`
|
|
43
|
+
- The token is read on the VPS from `~/<token-file>` (secret `COOLIFY_TOKEN_FILE` holds the file name) and never leaves the SSH session
|
|
43
44
|
- Runs health check after deployment
|
|
44
45
|
- Supports staging + production environments
|
|
45
46
|
|
|
@@ -25,7 +25,8 @@ jobs:
|
|
|
25
25
|
- name: Trigger Coolify Deploy (via SSH — the Coolify API is not publicly exposed)
|
|
26
26
|
run: |
|
|
27
27
|
ssh -i ~/.ssh/id_ed25519 ${{{{ secrets.COOLIFY_SSH_USER }}}}@${{{{ secrets.COOLIFY_SSH_HOST }}}} \
|
|
28
|
-
'T=$(cat
|
|
28
|
+
'T=$(cat ~/${{{{ secrets.COOLIFY_TOKEN_FILE }}}}); curl -s --fail-with-body -X POST -H "Authorization: Bearer $T" "http://localhost:8000/api/v1/deploy?uuid=${{{{ secrets.COOLIFY_APPLICATION_UUID }}}}"' \
|
|
29
|
+
| grep -q deployment_uuid
|
|
29
30
|
|
|
30
31
|
- name: Health Check
|
|
31
32
|
run: |
|
package/package.json
CHANGED
|
@@ -12,12 +12,10 @@
|
|
|
12
12
|
import chalk from 'chalk';
|
|
13
13
|
import { loadTelemetry, aggregateTelemetry } from '../../lib/telemetry/telemetry-reader.js';
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// Claude Code's rules system, not via the Read tool, so no hook ever sees them.
|
|
18
|
-
// A low count here means "few on-demand reads", NOT "standards barely used".
|
|
15
|
+
// A rule injects its standards' digests through Claude Code's rules system,
|
|
16
|
+
// not the Read tool, so no hook sees them: a low count means "few full-text reads".
|
|
19
17
|
const STANDARDS_CAVEAT =
|
|
20
|
-
'Só reads explícitos (via Read) são observáveis;
|
|
18
|
+
'Só reads explícitos (via Read) são observáveis; o resumo que uma rule injeta ao tocar arquivo casado por `paths:` não passa por tool call: não observável aqui.';
|
|
21
19
|
|
|
22
20
|
function section(title, rows, fmt) {
|
|
23
21
|
if (!rows.length) return;
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
setupStitch,
|
|
29
29
|
loadMcpRegistry
|
|
30
30
|
} from '../../lib/installers/mcp-installer.js';
|
|
31
|
-
import { setupInfra, installRulesForStack } from '../../scripts/setup-infra.js';
|
|
31
|
+
import { setupInfra, installRulesForStack, installCommandsForStack } from '../../scripts/setup-infra.js';
|
|
32
32
|
import { installSkills } from '../../utils/skills-installer.js';
|
|
33
33
|
import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
|
|
34
34
|
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
@@ -263,6 +263,7 @@ export async function initCommand(options) {
|
|
|
263
263
|
const rulesSrc = join(fwSrcDir, 'rules');
|
|
264
264
|
const rulesDest = join(targetPath, '.claude', 'rules');
|
|
265
265
|
installRulesForStack(rulesSrc, rulesDest, stackTags);
|
|
266
|
+
installCommandsForStack(join(fwSrcDir, 'commands'), join(targetPath, '.claude', 'commands'), stackTags);
|
|
266
267
|
|
|
267
268
|
// Skills
|
|
268
269
|
await installSkills(targetPath, { projectTags: stackTags });
|
|
@@ -30,7 +30,7 @@ import { installSkills } from '../../utils/skills-installer.js';
|
|
|
30
30
|
import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
|
|
31
31
|
import { injectMorphImport } from '../../utils/claude-md-injector.js';
|
|
32
32
|
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
33
|
-
import { updateClaudeMdAgentsBlock, installRulesForStack } from '../../scripts/setup-infra.js';
|
|
33
|
+
import { updateClaudeMdAgentsBlock, installRulesForStack, installCommandsForStack } from '../../scripts/setup-infra.js';
|
|
34
34
|
import { reconcileLegacyTemplatesCopy } from '../../lib/installers/templates-reconcile.js';
|
|
35
35
|
import { createEffects } from '../../lib/installers/effects.js';
|
|
36
36
|
import { applyEditorSettings } from '../../lib/worktree/vscode-settings.js';
|
|
@@ -84,6 +84,7 @@ export function frameworkDirsToClean(morphPath, targetPath) {
|
|
|
84
84
|
join(morphPath, 'framework', 'standards'),
|
|
85
85
|
join(morphPath, 'framework', 'hooks'),
|
|
86
86
|
join(morphPath, 'framework', 'schemas'),
|
|
87
|
+
// Não é mais instalado: fica na lista para apagar o de instalações antigas.
|
|
87
88
|
join(morphPath, 'framework', 'rubrics'),
|
|
88
89
|
join(morphPath, 'framework', 'evals'),
|
|
89
90
|
join(morphPath, 'config'),
|
|
@@ -594,10 +595,8 @@ export async function updateCommand(options = {}) {
|
|
|
594
595
|
fx.copyFile(agentsSrc, agentsDest);
|
|
595
596
|
}
|
|
596
597
|
|
|
597
|
-
//
|
|
598
|
-
|
|
599
|
-
// the installed charter/rubrics/eval assets stale forever.
|
|
600
|
-
updateSpinner.text = 'Updating charter, schemas, rubrics and evals...';
|
|
598
|
+
// Paridade com setup-infra 7c/8b/8c/8d: sem isto o charter e os evals instalados ficam velhos.
|
|
599
|
+
updateSpinner.text = 'Updating charter, schemas and evals...';
|
|
601
600
|
const canonicalFrameworkDir = join(__dirname, '..', '..', '..', 'framework');
|
|
602
601
|
for (const file of ['MORPH.md', 'phases.json']) {
|
|
603
602
|
const src = join(canonicalFrameworkDir, file);
|
|
@@ -605,7 +604,7 @@ export async function updateCommand(options = {}) {
|
|
|
605
604
|
fx.copyFile(src, join(morphPath, 'framework', file));
|
|
606
605
|
}
|
|
607
606
|
}
|
|
608
|
-
for (const dir of ['schemas', '
|
|
607
|
+
for (const dir of ['schemas', 'evals']) {
|
|
609
608
|
const src = join(canonicalFrameworkDir, dir);
|
|
610
609
|
if (await pathExists(src)) {
|
|
611
610
|
fx.copyDir(src, join(morphPath, 'framework', dir));
|
|
@@ -644,18 +643,6 @@ export async function updateCommand(options = {}) {
|
|
|
644
643
|
const frameworkDir = join(__dirname, '..', '..', '..', 'framework');
|
|
645
644
|
const claudeDest = join(targetPath, '.claude');
|
|
646
645
|
|
|
647
|
-
let commandsCopied = false;
|
|
648
|
-
|
|
649
|
-
// Copy commands directory (slash commands): framework/commands/ → .claude/commands/
|
|
650
|
-
const commandsSrc = join(frameworkDir, 'commands');
|
|
651
|
-
const commandsDest = join(claudeDest, 'commands');
|
|
652
|
-
if (await pathExists(commandsSrc)) {
|
|
653
|
-
fx.copyDir(commandsSrc, commandsDest);
|
|
654
|
-
commandsCopied = true;
|
|
655
|
-
} else {
|
|
656
|
-
logger.warn(' ⚠ framework/commands/ source missing — commands not updated');
|
|
657
|
-
}
|
|
658
|
-
|
|
659
646
|
// Compute project tags for stack-aware installation of skills, agents, and rules.
|
|
660
647
|
// MUST come from the pre-clean backup: cleanFrameworkDirs() deleted .morph/config/
|
|
661
648
|
// above and restoreUserConfig() only runs at the end — re-reading the file here
|
|
@@ -666,6 +653,17 @@ export async function updateCommand(options = {}) {
|
|
|
666
653
|
if (configBackup) projectTags = getProjectTags(configBackup);
|
|
667
654
|
} catch { /* ignore — malformed config */ }
|
|
668
655
|
|
|
656
|
+
let commandsCopied = false;
|
|
657
|
+
const commandsSrc = join(frameworkDir, 'commands');
|
|
658
|
+
if (await pathExists(commandsSrc)) {
|
|
659
|
+
const commandsDest = join(claudeDest, 'commands');
|
|
660
|
+
fx.ensureDir(commandsDest);
|
|
661
|
+
installCommandsForStack(commandsSrc, commandsDest, projectTags, fx);
|
|
662
|
+
commandsCopied = true;
|
|
663
|
+
} else {
|
|
664
|
+
logger.warn(' ⚠ framework/commands/ source missing — commands not updated');
|
|
665
|
+
}
|
|
666
|
+
|
|
669
667
|
// Sync morph skills to .claude/skills/ for native Claude Code discovery
|
|
670
668
|
updateSpinner.text = 'Syncing morph skills to .claude/skills/...';
|
|
671
669
|
const skillsResult = await installSkills(targetPath, { projectTags, fx });
|
|
@@ -6,6 +6,9 @@ import { join } from 'path';
|
|
|
6
6
|
import { getFeature, setApprovalGate, getApprovalGate, getApprovalHistory, derivePhase } from '../../core/state/state-manager.js';
|
|
7
7
|
import { unmetPrereqs } from '../../core/state/gate-order.js';
|
|
8
8
|
import { evaluatorReportState, VERDICT_TOKEN } from '../../lib/evaluator-report.js';
|
|
9
|
+
import { shouldPauseGate } from '../../lib/risk-detector.js';
|
|
10
|
+
import { collectSignals } from './gate-check.js';
|
|
11
|
+
import { readTrustFromConfig } from './gate-decision.js';
|
|
9
12
|
import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
|
|
10
13
|
import { uiuxAutoApprovalBlock } from '../../lib/specs/screen-checklist.js';
|
|
11
14
|
import { treeMovedSince } from '../../lib/git/evidence-staleness.js';
|
|
@@ -351,6 +354,20 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
351
354
|
selfAssessed = true;
|
|
352
355
|
}
|
|
353
356
|
|
|
357
|
+
// Sem gate-check prévio não há `gateDecisions.review` para o backstop acima morder; em
|
|
358
|
+
// campo, ships passaram assim com verify vermelho ou ausente. A auto-aprovação recalcula
|
|
359
|
+
// a decisão pela MESMA régua do gate-check. --self-assessed já dispensou o veredito.
|
|
360
|
+
if (approvedBy === 'auto') {
|
|
361
|
+
const signals = collectSignals(featureName, feature, process.cwd(), 'review');
|
|
362
|
+
if (selfAssessed) signals.evaluatorVerdict = { status: 'ok', verdict: 'SHIP', blockers: 0, untyped: 0 };
|
|
363
|
+
const { pause, reason } = shouldPauseGate({ trust: readTrustFromConfig() }, signals);
|
|
364
|
+
if (pause) {
|
|
365
|
+
console.error(chalk.red(`\n❌ Gate 3 não pode ser auto-aprovado — ${reason}.`));
|
|
366
|
+
console.error(chalk.gray(' Corrija e rode de novo, ou leve a decisão ao humano (--approver <nome>).\n'));
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
354
371
|
if (report.status === 'ok' && report.superseded.length > 0) {
|
|
355
372
|
console.log(chalk.yellow(`\n⚠️ O relatório do avaliador tem ${report.superseded.length + 1} vereditos (append-only).`));
|
|
356
373
|
console.log(chalk.gray(` Vigente: ${report.verdict}, ${report.blockers.length} bloqueador(es). Superados: ${report.superseded.join(', ')}\n`));
|
|
@@ -3,7 +3,7 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
3
3
|
import { getFeature } from '../../core/state/state-manager.js';
|
|
4
4
|
import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
5
5
|
import { shouldPauseGate, getRiskSignals, classifyE2eSkip } from '../../lib/risk-detector.js';
|
|
6
|
-
import { evaluatorReportState, evaluatorMediaSection } from '../../lib/evaluator-report.js';
|
|
6
|
+
import { evaluatorReportState, evaluatorMediaSection, verdictSignal } from '../../lib/evaluator-report.js';
|
|
7
7
|
import { collectScreenChecklist } from '../../lib/specs/screen-checklist.js';
|
|
8
8
|
import { persistGateDecision, VALID_GATES, readTrustFromConfig } from './gate-decision.js';
|
|
9
9
|
import { minimatch } from 'minimatch';
|
|
@@ -151,7 +151,7 @@ function collectPromptModel(cwd, defaultBranch) {
|
|
|
151
151
|
* @param {string} gateName
|
|
152
152
|
* @returns {object}
|
|
153
153
|
*/
|
|
154
|
-
function collectSignals(featureName, feature, cwd, gateName) {
|
|
154
|
+
export function collectSignals(featureName, feature, cwd, gateName) {
|
|
155
155
|
const tasks = readTasks(featureName);
|
|
156
156
|
const defaultBranch = detectDefaultBranch(cwd);
|
|
157
157
|
const sha = getHeadSha(cwd);
|
|
@@ -179,17 +179,7 @@ function collectSignals(featureName, feature, cwd, gateName) {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
if (gateName === 'review') {
|
|
182
|
-
|
|
183
|
-
signals.evaluatorVerdict = report.status === 'ok'
|
|
184
|
-
? {
|
|
185
|
-
status: 'ok',
|
|
186
|
-
verdict: report.verdict,
|
|
187
|
-
blockers: report.blockers.length,
|
|
188
|
-
blockerIds: report.blockers.map((b) => b.id),
|
|
189
|
-
untyped: report.untyped.length,
|
|
190
|
-
superseded: report.superseded,
|
|
191
|
-
}
|
|
192
|
-
: { status: report.status };
|
|
182
|
+
signals.evaluatorVerdict = verdictSignal(evaluatorReportState(cwd, featureName));
|
|
193
183
|
signals.mediaClaims = {
|
|
194
184
|
section: evaluatorMediaSection(cwd, featureName),
|
|
195
185
|
featureGeneratesMedia: featureGeneratesMedia(tasks),
|
|
@@ -269,7 +259,7 @@ export async function gateCheckCommand(featureName, gateName, options = {}) {
|
|
|
269
259
|
process.exit(1);
|
|
270
260
|
}
|
|
271
261
|
|
|
272
|
-
// Owner-tree guard: gate-check reads workType/tasks/
|
|
262
|
+
// Owner-tree guard: gate-check reads workType/tasks/verdict/git off cwd
|
|
273
263
|
// AND writes the resulting decision through persistGateDecision — both
|
|
274
264
|
// halves are wrong from a tree that isn't this feature's own, so refuse
|
|
275
265
|
// rather than compute a decision from stale reads and then fail to save it.
|
|
@@ -43,18 +43,13 @@ function selectionLines(runs = []) {
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/**
|
|
47
|
-
* Human-readable rendering of a runVerify() result — mirrors the
|
|
48
|
-
* ✓/✗-per-node + feedback-list style of formatScoreBreakdown() in
|
|
49
|
-
* score-aggregator.js, adapted to verify's build/tests/validators nodes.
|
|
50
|
-
*/
|
|
51
46
|
function formatVerifyResult(result) {
|
|
52
47
|
const lines = [];
|
|
53
48
|
const scopeLabel = result.scope === 'task' ? `${result.feature}/${result.task}` : result.feature;
|
|
54
49
|
|
|
55
50
|
lines.push('');
|
|
56
51
|
lines.push(chalk.bold(`Verify — ${scopeLabel}`));
|
|
57
|
-
lines.push(chalk.gray(`
|
|
52
|
+
if (result.stack) lines.push(chalk.gray(`stack: ${result.stack}`));
|
|
58
53
|
lines.push('');
|
|
59
54
|
lines.push(result.status === 'pass' ? chalk.green.bold('✓ PASS') : chalk.red.bold('✗ FAIL'));
|
|
60
55
|
lines.push('');
|
|
@@ -69,8 +64,6 @@ function formatVerifyResult(result) {
|
|
|
69
64
|
let detail;
|
|
70
65
|
if (node.status === 'skip') {
|
|
71
66
|
detail = node.reason || '';
|
|
72
|
-
} else if (name === 'validators' && node.score != null) {
|
|
73
|
-
detail = `score ${node.score}/10`;
|
|
74
67
|
} else if (node.reason) {
|
|
75
68
|
// A green e2e still carries meaning worth showing: smoke-only vs specs,
|
|
76
69
|
// warm reuse vs rebuild. Without it, "pass" hides how much was proven.
|
|
@@ -200,7 +193,6 @@ const CI_EXCLUSIVE_FLAGS = [
|
|
|
200
193
|
async function runLocalVerify(featureName, taskId, options) {
|
|
201
194
|
try {
|
|
202
195
|
return await runVerify(process.cwd(), featureName, taskId || null, {
|
|
203
|
-
rubric: options.rubric,
|
|
204
196
|
timeout: options.timeout,
|
|
205
197
|
skipTests: options.skipTests,
|
|
206
198
|
skipBuild: options.skipBuild,
|
|
@@ -251,8 +243,8 @@ async function runLocalVerify(featureName, taskId, options) {
|
|
|
251
243
|
* Exit 2 is "the command cannot mean what you typed", and there are four
|
|
252
244
|
* paths to it — never a verdict, because a verdict about nothing is worse
|
|
253
245
|
* than a refusal:
|
|
254
|
-
* - unknown feature, or unknown task id (the task's `group`
|
|
255
|
-
*
|
|
246
|
+
* - unknown feature, or unknown task id (the task's `group` and `outputs`
|
|
247
|
+
* decide the scope, so it is a hard usage error here);
|
|
256
248
|
* - `--filter` that matches no declared node (a filtered-to-empty run is
|
|
257
249
|
* green and proves nothing);
|
|
258
250
|
* - `--record-baseline` with nowhere to write (no `config.json`), or with a
|
|
@@ -345,7 +337,6 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
345
337
|
compact = {
|
|
346
338
|
status: result.status,
|
|
347
339
|
nodes: nodeStatuses(result),
|
|
348
|
-
score: result.nodes.validators.score ?? null,
|
|
349
340
|
attempts: (prevEntry?.attempts ?? 0) + 1,
|
|
350
341
|
updatedAt: now,
|
|
351
342
|
};
|
|
@@ -74,7 +74,7 @@ export const OUTPUT_SCHEMA = {
|
|
|
74
74
|
// (morph-plan §5). Runtime evidence: input, model, output, verdict — pointing
|
|
75
75
|
// at the `runner: "evals"` node when the stack declares one. CONDITIONAL by
|
|
76
76
|
// construction: never in phases.json requiredOutputs. Readers: morph-review
|
|
77
|
-
// dispatch prompt
|
|
77
|
+
// dispatch prompt.
|
|
78
78
|
evidencias: { dirname: 'evidencias', filenamePattern: '^T\\d+\\.md$', displayFilename: '{taskId}.md',
|
|
79
79
|
phaseDir: '3-implement', phase: 'implement', protected: false, multi: true },
|
|
80
80
|
|
|
@@ -14,7 +14,6 @@ import SkillsLibrary from './pages/SkillsLibrary.jsx';
|
|
|
14
14
|
import RulesMap from './pages/RulesMap.jsx';
|
|
15
15
|
import Standards from './pages/Standards.jsx';
|
|
16
16
|
import Guide from './pages/Guide.jsx';
|
|
17
|
-
import Rubrics from './pages/Rubrics.jsx';
|
|
18
17
|
import { useNow } from './hooks/usePref.js';
|
|
19
18
|
import { useProjects, useCurrentProject, projectHref } from './lib/project.js';
|
|
20
19
|
|
|
@@ -77,7 +76,6 @@ export default function App() {
|
|
|
77
76
|
<Route path="/p/:project/activity" element={<Activity />} />
|
|
78
77
|
<Route path="/guide" element={<Guide />} />
|
|
79
78
|
<Route path="/guide/:doc" element={<Guide />} />
|
|
80
|
-
<Route path="/rubrics" element={<Rubrics />} />
|
|
81
79
|
<Route path="/agents" element={<AgentMap />} />
|
|
82
80
|
<Route path="/phases" element={<PhasePipeline />} />
|
|
83
81
|
<Route path="/hooks" element={<HookInspector />} />
|
|
@@ -9,12 +9,12 @@ import { apiBase, projectHref, featureHref } from '../lib/project.js';
|
|
|
9
9
|
|
|
10
10
|
const KIND_LABEL = {
|
|
11
11
|
page: 'Página', project: 'Projeto', feature: 'Feature', task: 'Task', skill: 'Skill', persona: 'Persona',
|
|
12
|
-
standard: 'Standard', rule: 'Rule', hook: 'Hook', guide: 'Guia',
|
|
12
|
+
standard: 'Standard', rule: 'Rule', hook: 'Hook', guide: 'Guia',
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const PAGES = [
|
|
16
16
|
['Portfólio', '/'], ['Guia', '/guide'], ['Fases', '/phases'], ['Skills', '/skills'],
|
|
17
|
-
['Personas', '/agents'], ['Standards', '/standards'], ['Rules', '/rules'], ['Hooks', '/hooks'],
|
|
17
|
+
['Personas', '/agents'], ['Standards', '/standards'], ['Rules', '/rules'], ['Hooks', '/hooks'],
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
/** Everything the framework knows that has a page — built once. */
|
|
@@ -35,7 +35,6 @@ function knowledgeIndex() {
|
|
|
35
35
|
out.push({ kind: 'guide', title: h.text, hint: d.title, to: `/guide/${d.id}#${h.slug}`, hay: `${h.text} ${d.title}` });
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
for (const r of morphData.docs?.rubrics || []) out.push({ kind: 'rubric', title: r.title, to: `/rubrics?sel=${encodeURIComponent(`dim:${r.id}`)}`, hay: r.title });
|
|
39
38
|
return out;
|
|
40
39
|
}
|
|
41
40
|
|
|
@@ -7,7 +7,7 @@ const REMARK = [remarkGfm];
|
|
|
7
7
|
const REHYPE = [rehypeSlug];
|
|
8
8
|
|
|
9
9
|
/** Frontmatter keys worth showing, in the order a reader wants them. */
|
|
10
|
-
const META_ORDER = ['name', 'description', '
|
|
10
|
+
const META_ORDER = ['name', 'description', 'when_to_use', 'user-invocable', 'stacks', 'version', 'cliVersion'];
|
|
11
11
|
|
|
12
12
|
function MetaBlock({ meta }) {
|
|
13
13
|
const keys = Object.keys(meta).sort((a, b) => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NavLink, useLocation, useNavigate } from 'react-router-dom';
|
|
2
2
|
import {
|
|
3
|
-
MagnifyingGlass, SquaresFour, Kanban, Pulse, BookOpen, FlowArrow, PuzzlePiece, UsersThree, Books, Ruler, Plug,
|
|
3
|
+
MagnifyingGlass, SquaresFour, Kanban, Pulse, BookOpen, FlowArrow, PuzzlePiece, UsersThree, Books, Ruler, Plug,
|
|
4
4
|
} from '@phosphor-icons/react';
|
|
5
5
|
import { projectHref } from '../lib/project.js';
|
|
6
6
|
|
|
@@ -12,7 +12,6 @@ const KNOWLEDGE = [
|
|
|
12
12
|
{ to: '/standards', label: 'Standards', icon: Books },
|
|
13
13
|
{ to: '/rules', label: 'Rules', icon: Ruler },
|
|
14
14
|
{ to: '/hooks', label: 'Hooks', icon: Plug },
|
|
15
|
-
{ to: '/rubrics', label: 'Rubricas', icon: Scales },
|
|
16
15
|
];
|
|
17
16
|
|
|
18
17
|
function Badge({ pulse }) {
|
|
@@ -15,7 +15,7 @@ const NODE_W = 260;
|
|
|
15
15
|
// ─── Standard resolution ──────────────────────────────────────────────────────
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* A rule's
|
|
18
|
+
* A rule's route → the standard it names in the registry. `id` is what the
|
|
19
19
|
* Standards page selects by; with no registry match the path itself is passed,
|
|
20
20
|
* so the link still says which file the rule meant.
|
|
21
21
|
*/
|
|
@@ -196,7 +196,7 @@ function RuleDetail({ rule, byPath }) {
|
|
|
196
196
|
)}
|
|
197
197
|
</Section>
|
|
198
198
|
|
|
199
|
-
<Section title="Standards
|
|
199
|
+
<Section title="Standards roteados (resumo)">
|
|
200
200
|
{rule.standardRefs.length > 0 ? (
|
|
201
201
|
<div className="flex flex-wrap gap-1.5">
|
|
202
202
|
{rule.standardRefs.map((ref) => {
|
|
@@ -209,7 +209,7 @@ function RuleDetail({ rule, byPath }) {
|
|
|
209
209
|
})}
|
|
210
210
|
</div>
|
|
211
211
|
) : (
|
|
212
|
-
<p className="text-xs">Esta regra não
|
|
212
|
+
<p className="text-xs">Esta regra não roteia nenhum standard.</p>
|
|
213
213
|
)}
|
|
214
214
|
</Section>
|
|
215
215
|
|
|
@@ -315,10 +315,10 @@ export function SkillDetail({ skill, phases = morphData.phases?.list || [] }) {
|
|
|
315
315
|
{skill.description && <p className="leading-relaxed text-[var(--color-fg)]">{skill.description}</p>}
|
|
316
316
|
|
|
317
317
|
<dl className="grid grid-cols-[max-content_1fr] gap-x-4 gap-y-2 rounded-xl bg-white/[0.03] px-4 py-3 shadow-[inset_0_0_0_1px_var(--color-hair)]">
|
|
318
|
-
{skill.
|
|
318
|
+
{skill.whenToUse && (
|
|
319
319
|
<>
|
|
320
320
|
<dt className="text-xs leading-6 text-[var(--color-faint)]">Quando usar</dt>
|
|
321
|
-
<dd className="leading-6">{skill.
|
|
321
|
+
<dd className="leading-6">{skill.whenToUse}</dd>
|
|
322
322
|
</>
|
|
323
323
|
)}
|
|
324
324
|
<dt className="text-xs leading-6 text-[var(--color-faint)]">Fase</dt>
|
|
@@ -389,7 +389,7 @@ export default function SkillsLibrary() {
|
|
|
389
389
|
const isMatch = useCallback((s) => {
|
|
390
390
|
const q = search.trim().toLowerCase();
|
|
391
391
|
if (q) {
|
|
392
|
-
const hay = [s.id, s.name, s.description, s.
|
|
392
|
+
const hay = [s.id, s.name, s.description, s.whenToUse].filter(Boolean).join(' ').toLowerCase();
|
|
393
393
|
if (!hay.includes(q)) return false;
|
|
394
394
|
}
|
|
395
395
|
if (phaseFilter) {
|
|
@@ -18,7 +18,7 @@ import { isFailedToolCall } from '../../../framework/hooks/shared/telemetry-logg
|
|
|
18
18
|
export const DEFAULT_KINDS = ['prompt', 'question', 'skill', 'dispatch', 'handback', 'task', 'verify', 'gate', 'failure'];
|
|
19
19
|
export const ALL_KINDS = [...DEFAULT_KINDS, 'write', 'read', 'command', 'tool'];
|
|
20
20
|
|
|
21
|
-
const GATE_COMMAND = /\bmorph-spec\s+(approve|gate-check|gate-decision|advance|
|
|
21
|
+
const GATE_COMMAND = /\bmorph-spec\s+(approve|gate-check|gate-decision|advance|finish)\b/;
|
|
22
22
|
const MORPH_PERSONA = /^morph-(.+)$/;
|
|
23
23
|
const SHELL_TOOLS = new Set(['Bash', 'PowerShell']);
|
|
24
24
|
const SILENT_EVENTS = new Set(['ToolBatch', 'SessionUsage', 'AgentUsage']);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The reading material of the framework — the charter, the runtime
|
|
3
|
-
* instructions, the CLI reference and the
|
|
4
|
-
*
|
|
3
|
+
* instructions, the CLI reference and the Gate 3 evaluator checklist — for the
|
|
4
|
+
* dashboard's Guide page.
|
|
5
5
|
*/
|
|
6
|
-
import { readFileSync
|
|
6
|
+
import { readFileSync } from 'fs';
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
|
|
9
9
|
function read(path) {
|
|
@@ -23,26 +23,11 @@ export function headingsOf(content) {
|
|
|
23
23
|
return out;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function titleOf(content, fallback) {
|
|
27
|
-
const h = headingsOf(content || '').find((x) => x.depth === 1);
|
|
28
|
-
return h?.text || fallback;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function mdDir(dir) {
|
|
32
|
-
if (!existsSync(dir)) return [];
|
|
33
|
-
return readdirSync(dir)
|
|
34
|
-
.filter((f) => f.endsWith('.md'))
|
|
35
|
-
.sort()
|
|
36
|
-
.map((f) => {
|
|
37
|
-
const content = read(join(dir, f)) || '';
|
|
38
|
-
return { id: f.replace(/\.md$/, ''), file: f, title: titleOf(content, f.replace(/\.md$/, '')), content };
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
26
|
const GUIDE = [
|
|
43
27
|
{ id: 'morph', path: 'MORPH.md', title: 'Carta do harness (MORPH.md)', summary: 'Como o Claude trabalha: papéis, fases, gates, sub-agents. É a fonte única do comportamento.' },
|
|
44
28
|
{ id: 'runtime', path: 'CLAUDE.md', title: 'Instruções de runtime (CLAUDE.md)', summary: 'O que é injetado no CLAUDE.md do projeto: como começar cada sessão, regras críticas, comandos.' },
|
|
45
29
|
{ id: 'cli', path: 'docs/CLI.md', title: 'Referência do CLI', summary: 'Todos os comandos morph-spec: gates, verify, worktrees, grafo, telemetria.' },
|
|
30
|
+
{ id: 'review-checklist', path: 'evals/review-checklist.md', title: 'Checklist do avaliador (Gate 3)', summary: 'O que o avaliador independente procura antes do ship: veredito SHIP/FIX e bloqueador com evidência.' },
|
|
46
31
|
];
|
|
47
32
|
|
|
48
33
|
export function scanDocs(frameworkDir) {
|
|
@@ -51,8 +36,5 @@ export function scanDocs(frameworkDir) {
|
|
|
51
36
|
return { ...g, content, headings: headingsOf(content).filter((h) => h.depth <= 3) };
|
|
52
37
|
}).filter((g) => g.content);
|
|
53
38
|
|
|
54
|
-
return {
|
|
55
|
-
guide,
|
|
56
|
-
rubrics: mdDir(join(frameworkDir, 'evals')).filter((d) => d.id === 'review-checklist'),
|
|
57
|
-
};
|
|
39
|
+
return { guide };
|
|
58
40
|
}
|
|
@@ -310,7 +310,7 @@ export function scanFeatures(projectDir, { resolveTree = resolveFeatureTree } =
|
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
/**
|
|
313
|
-
* One feature in full: summary + tasks with their verify
|
|
313
|
+
* One feature in full: summary + tasks with their verify, the gate
|
|
314
314
|
* decisions, the verify nodes and the document tree.
|
|
315
315
|
*/
|
|
316
316
|
export function readFeatureDetail(projectDir, name, { resolveTree = resolveFeatureTree, known } = {}) {
|
|
@@ -4,7 +4,6 @@ import { scanHooks } from './hooks-scanner.js';
|
|
|
4
4
|
import { scanSkills } from './skills-scanner.js';
|
|
5
5
|
import { scanStandards } from './standards-scanner.js';
|
|
6
6
|
import { scanRules } from './rules-scanner.js';
|
|
7
|
-
import { scanRubrics } from './rubrics-scanner.js';
|
|
8
7
|
import { scanDocs } from './docs-scanner.js';
|
|
9
8
|
|
|
10
9
|
/**
|
|
@@ -21,7 +20,6 @@ export function scanAll(frameworkDir) {
|
|
|
21
20
|
skills: scanSkills(frameworkDir, phases.list),
|
|
22
21
|
standards: scanStandards(frameworkDir, agents.list),
|
|
23
22
|
rules: scanRules(frameworkDir),
|
|
24
|
-
rubrics: scanRubrics(frameworkDir),
|
|
25
23
|
docs: scanDocs(frameworkDir),
|
|
26
24
|
};
|
|
27
25
|
}
|
|
@@ -18,17 +18,22 @@ function parseFrontmatter(content) {
|
|
|
18
18
|
return { paths, stacks };
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const ROUTE_LINE = /^- \*\*([a-z0-9-]+)\*\* \(`([^`]+\.md)`\)/;
|
|
22
|
+
|
|
23
|
+
// Bullet form: - **<id>** (`<path under standards/>`) — <digest>. An `@path`
|
|
24
|
+
// import inside .claude/rules resolves relative to the rule file and never loads.
|
|
25
|
+
export function parseStandardRoutes(content) {
|
|
26
|
+
const routes = [];
|
|
27
|
+
for (const line of content.split(/\r?\n/)) {
|
|
28
|
+
const m = ROUTE_LINE.exec(line);
|
|
29
|
+
if (m) routes.push({ id: m[1], path: m[2] });
|
|
30
|
+
}
|
|
31
|
+
return routes;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// RulesMap keys one edge per ref, so a repeated path would collide.
|
|
26
35
|
export function extractStandardRefs(content) {
|
|
27
|
-
|
|
28
|
-
const regex = /@\.morph\/framework\/standards\/(.+\.md)/g;
|
|
29
|
-
let m;
|
|
30
|
-
while ((m = regex.exec(content)) !== null) refs.add(m[1]);
|
|
31
|
-
return [...refs];
|
|
36
|
+
return [...new Set(parseStandardRoutes(content).map((r) => r.path))];
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
export function scanRules(frameworkDir) {
|
|
@@ -84,13 +84,12 @@ export function scanSkills(frameworkDir, phases = []) {
|
|
|
84
84
|
if (!existsSync(skillPath)) continue;
|
|
85
85
|
const content = readFileSync(skillPath, 'utf8');
|
|
86
86
|
const fm = parseFrontmatter(content);
|
|
87
|
-
|
|
88
|
-
const invocable = fm['user-invokable'] ?? fm['user-invocable'];
|
|
87
|
+
const invocable = fm['user-invocable'];
|
|
89
88
|
skills.push({
|
|
90
89
|
id: skillDir.name,
|
|
91
90
|
name: fm.name || skillDir.name,
|
|
92
91
|
description: fm.description || '',
|
|
93
|
-
|
|
92
|
+
whenToUse: fm.when_to_use || '',
|
|
94
93
|
level: levelDir.name,
|
|
95
94
|
userInvocable: invocable === true || invocable === 'true',
|
|
96
95
|
stacks: typeof fm.stacks === 'string' ? fm.stacks.replace(/[[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean) : [],
|
|
@@ -79,6 +79,19 @@ export function evaluatorReportState(cwd, featureName) {
|
|
|
79
79
|
return { status: 'ok', path, ...parsed };
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** O sinal `evaluatorVerdict` que `verdictPause` (risk-detector) lê — gate-check e approve leem o mesmo. */
|
|
83
|
+
export function verdictSignal(report) {
|
|
84
|
+
if (report.status !== 'ok') return { status: report.status };
|
|
85
|
+
return {
|
|
86
|
+
status: 'ok',
|
|
87
|
+
verdict: report.verdict,
|
|
88
|
+
blockers: report.blockers.length,
|
|
89
|
+
blockerIds: report.blockers.map((b) => b.id),
|
|
90
|
+
untyped: report.untyped.length,
|
|
91
|
+
superseded: report.superseded,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
82
95
|
export function evaluatorMediaSection(cwd, featureName) {
|
|
83
96
|
const text = readReport(cwd, featureName);
|
|
84
97
|
return text !== null && MEDIA_SECTION_RE.test(text);
|
package/src/lib/risk-detector.js
CHANGED
|
@@ -244,6 +244,14 @@ export function shouldPauseGate(config, signals) {
|
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
// No ship, ausência de stamp não é "verde": uma feature foi aprovada em campo sem verify nenhum.
|
|
248
|
+
if (signals.gate === 'review' && !signals.verificationStatus) {
|
|
249
|
+
return {
|
|
250
|
+
pause: true,
|
|
251
|
+
reason: 'nenhum `morph-spec verify` carimbado nesta feature — rode `morph-spec verify {feature}` antes do ship',
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
247
255
|
// No gate de SHIP: um `verification` PRESENTE cujo escopo não é o da feature
|
|
248
256
|
// é um recorte parcial — de UMA task, nunca da app inteira. O stamp é
|
|
249
257
|
// SINGULAR, sobrescrito por qualquer verify de qualquer escopo, e vale
|
|
@@ -19,7 +19,7 @@ const NO_VALIDATOR_STACKS = new Set(['nodejs-cli', 'node-cli', 'cli']);
|
|
|
19
19
|
const PROFILES = {
|
|
20
20
|
dotnet: {
|
|
21
21
|
isDotnet: true, isNextjs: false,
|
|
22
|
-
defaultValidators: ['
|
|
22
|
+
defaultValidators: ['packages'],
|
|
23
23
|
buildCommand: 'dotnet build --no-restore',
|
|
24
24
|
},
|
|
25
25
|
nextjs: {
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reuse Validator — símbolo novo cujo nome já existe no codebase.
|
|
3
3
|
*
|
|
4
|
-
* A terceira camada do ciclo de reuso
|
|
4
|
+
* A terceira camada do ciclo de reuso:
|
|
5
5
|
*
|
|
6
6
|
* PROSA (morph-plan, personas) ──► ARTEFATO (`reuse` em tasks.json)
|
|
7
7
|
* ──► COBRANÇA (este arquivo)
|
|
8
8
|
*
|
|
9
9
|
* Prosa sozinha não segura — `morph-brainstorming/SKILL.md:39` já manda olhar
|
|
10
10
|
* o que existe, e o ProspectPRO redefiniu `SimpleBuilding` em cinco arquivos da
|
|
11
|
-
* MESMA pasta assim mesmo
|
|
12
|
-
* tokens) tem verificação em código; este é o equivalente para código.
|
|
11
|
+
* MESMA pasta assim mesmo: só a verificação em código segura o ciclo.
|
|
13
12
|
*
|
|
14
13
|
* Regras, na ordem em que reduzem ruído:
|
|
15
14
|
*
|