@polymorphism-tech/morph-spec 8.45.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 +6 -5
- package/README.md +2 -2
- package/bin/morph-spec.js +2 -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 +2 -2
- package/framework/MORPH.md +9 -9
- 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 +8 -7
- 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/config.schema.json +4 -0
- package/framework/schemas/feature.schema.json +4 -4
- 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 +30 -17
- 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 +99 -57
- 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/ci/ci-verify.js +118 -0
- package/src/lib/ci/github-ci.js +192 -0
- package/src/lib/ci/workflow-scaffold.js +122 -0
- 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/telemetry/verify-event.js +3 -2
- 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/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
- 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.
|
|
@@ -4,7 +4,7 @@ import { getFeature, updateFeature } from '../../core/state/state-manager.js';
|
|
|
4
4
|
import { runVerify, readTask, VerifyUsageError } from '../../lib/validators/verify-runner.js';
|
|
5
5
|
import { resolvePrimaryRoot } from '../project/worktree.js';
|
|
6
6
|
import { resolveFeatureTree } from '../../lib/worktree/feature-truth.js';
|
|
7
|
-
import { canonicalPath } from '../../lib/tasks/test-runner.js';
|
|
7
|
+
import { canonicalPath, readMorphConfig } from '../../lib/tasks/test-runner.js';
|
|
8
8
|
import { getHeadSha } from '../../lib/git/default-branch.js';
|
|
9
9
|
import { EVAL_RUNNER } from '../../lib/tasks/eval-node.js';
|
|
10
10
|
import { buildVerifyEvent, recordVerifyEvent, findRepeatedVerify } from '../../lib/telemetry/verify-event.js';
|
|
@@ -12,6 +12,7 @@ import { treeFingerprint } from '../../lib/git/tree-fingerprint.js';
|
|
|
12
12
|
import { HeavyBusyError } from '../../lib/heavy/gate.js';
|
|
13
13
|
import { afterHeavyRun } from '../../lib/heavy/cleanup.js';
|
|
14
14
|
import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js';
|
|
15
|
+
import { runCiVerify, CiUsageError } from '../../lib/ci/ci-verify.js';
|
|
15
16
|
|
|
16
17
|
/** ✓ pass · ✗ fail · – skip */
|
|
17
18
|
function statusIcon(status) {
|
|
@@ -42,18 +43,13 @@ function selectionLines(runs = []) {
|
|
|
42
43
|
});
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
/**
|
|
46
|
-
* Human-readable rendering of a runVerify() result — mirrors the
|
|
47
|
-
* ✓/✗-per-node + feedback-list style of formatScoreBreakdown() in
|
|
48
|
-
* score-aggregator.js, adapted to verify's build/tests/validators nodes.
|
|
49
|
-
*/
|
|
50
46
|
function formatVerifyResult(result) {
|
|
51
47
|
const lines = [];
|
|
52
48
|
const scopeLabel = result.scope === 'task' ? `${result.feature}/${result.task}` : result.feature;
|
|
53
49
|
|
|
54
50
|
lines.push('');
|
|
55
51
|
lines.push(chalk.bold(`Verify — ${scopeLabel}`));
|
|
56
|
-
lines.push(chalk.gray(`
|
|
52
|
+
if (result.stack) lines.push(chalk.gray(`stack: ${result.stack}`));
|
|
57
53
|
lines.push('');
|
|
58
54
|
lines.push(result.status === 'pass' ? chalk.green.bold('✓ PASS') : chalk.red.bold('✗ FAIL'));
|
|
59
55
|
lines.push('');
|
|
@@ -68,8 +64,6 @@ function formatVerifyResult(result) {
|
|
|
68
64
|
let detail;
|
|
69
65
|
if (node.status === 'skip') {
|
|
70
66
|
detail = node.reason || '';
|
|
71
|
-
} else if (name === 'validators' && node.score != null) {
|
|
72
|
-
detail = `score ${node.score}/10`;
|
|
73
67
|
} else if (node.reason) {
|
|
74
68
|
// A green e2e still carries meaning worth showing: smoke-only vs specs,
|
|
75
69
|
// warm reuse vs rebuild. Without it, "pass" hides how much was proven.
|
|
@@ -190,6 +184,53 @@ function evalsStatusOf(result) {
|
|
|
190
184
|
*/
|
|
191
185
|
const PARTIAL_RUN_FLAGS = [['filter', 'filtered'], ['skipTests', 'skip-tests'], ['skipBuild', 'skip-build']];
|
|
192
186
|
|
|
187
|
+
const CI_EXCLUSIVE_FLAGS = [
|
|
188
|
+
['filter', '--filter'], ['skipTests', '--skip-tests'], ['skipBuild', '--skip-build'], ['skipE2e', '--skip-e2e'],
|
|
189
|
+
['freshE2e', '--fresh-e2e'], ['linux', '--linux'], ['recordBaseline', '--record-baseline'], ['refreshCache', '--refresh-cache'],
|
|
190
|
+
['noPersist', '--no-persist'],
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
async function runLocalVerify(featureName, taskId, options) {
|
|
194
|
+
try {
|
|
195
|
+
return await runVerify(process.cwd(), featureName, taskId || null, {
|
|
196
|
+
timeout: options.timeout,
|
|
197
|
+
skipTests: options.skipTests,
|
|
198
|
+
skipBuild: options.skipBuild,
|
|
199
|
+
skipE2e: options.skipE2e,
|
|
200
|
+
freshE2e: options.freshE2e,
|
|
201
|
+
filter: options.filter,
|
|
202
|
+
recordBaseline: options.recordBaseline,
|
|
203
|
+
refreshCache: options.refreshCache,
|
|
204
|
+
linux: options.linux,
|
|
205
|
+
linuxImage: options.linuxImage,
|
|
206
|
+
// Task scope: switch the recorte off (#87). At feature scope nothing is
|
|
207
|
+
// ever narrowed, so the flag is a documented no-op there, not an error.
|
|
208
|
+
full: options.full,
|
|
209
|
+
});
|
|
210
|
+
} catch (err) {
|
|
211
|
+
// A wrong invocation is exit 2, like an unknown feature or task — never a
|
|
212
|
+
// verdict. `--filter` that selects nothing must not print green (or red):
|
|
213
|
+
// there is no verification to report, only a command to fix.
|
|
214
|
+
if (err instanceof VerifyUsageError) {
|
|
215
|
+
console.error(chalk.red(`\n❌ ${err.message}\n`));
|
|
216
|
+
process.exit(2);
|
|
217
|
+
}
|
|
218
|
+
// Ocupado não é veredito: nada foi verificado, então não há stamp nem
|
|
219
|
+
// evento, e o exit 3 nunca é "vermelho".
|
|
220
|
+
if (err instanceof HeavyBusyError) {
|
|
221
|
+
if (options.json) {
|
|
222
|
+
const holders = err.holders.map(({ pid, label, feature, node, since }) => ({ pid, label, feature, node, since }));
|
|
223
|
+
console.log(JSON.stringify({
|
|
224
|
+
status: 'busy', persisted: false, busy: { reason: err.reason, holders, freeGb: err.freeGb, waitedMs: err.waitedMs },
|
|
225
|
+
}, null, 2));
|
|
226
|
+
}
|
|
227
|
+
console.error(chalk.yellow(`\n⏳ verify não rodou: ${err.message}. Não é vermelho — a fila já esperou o prazo dela: não repita em laço — siga sem esta rodada e avise o usuário (\`morph-spec heavy status\` mostra quem segura os slots).\n`));
|
|
228
|
+
process.exit(3);
|
|
229
|
+
}
|
|
230
|
+
throw err;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
193
234
|
/**
|
|
194
235
|
* `morph-spec verify <feature> [task]` — the deterministic code-before-judge
|
|
195
236
|
* gate: build + tests + validators, BEFORE the LLM eval. See verify-runner.js
|
|
@@ -202,8 +243,8 @@ const PARTIAL_RUN_FLAGS = [['filter', 'filtered'], ['skipTests', 'skip-tests'],
|
|
|
202
243
|
* Exit 2 is "the command cannot mean what you typed", and there are four
|
|
203
244
|
* paths to it — never a verdict, because a verdict about nothing is worse
|
|
204
245
|
* than a refusal:
|
|
205
|
-
* - unknown feature, or unknown task id (the task's `group`
|
|
206
|
-
*
|
|
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);
|
|
207
248
|
* - `--filter` that matches no declared node (a filtered-to-empty run is
|
|
208
249
|
* green and proves nothing);
|
|
209
250
|
* - `--record-baseline` with nowhere to write (no `config.json`), or with a
|
|
@@ -223,6 +264,14 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
223
264
|
process.exit(2);
|
|
224
265
|
}
|
|
225
266
|
|
|
267
|
+
if (options.ci) {
|
|
268
|
+
const conflict = taskId ? `a task ${taskId} (o loop por task é local)` : CI_EXCLUSIVE_FLAGS.find(([flag]) => options[flag])?.[1];
|
|
269
|
+
if (conflict) {
|
|
270
|
+
console.error(chalk.red(`\n❌ --ci roda a suíte inteira da feature no CI; não combina com ${conflict}\n`));
|
|
271
|
+
process.exit(2);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
226
275
|
// The runner's own reader (one reader of tasks.json for verify, #87): null
|
|
227
276
|
// means tasks.json or the task is missing, and both are this usage error.
|
|
228
277
|
if (taskId) {
|
|
@@ -232,51 +281,36 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
232
281
|
}
|
|
233
282
|
}
|
|
234
283
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
refreshCache: options.refreshCache,
|
|
250
|
-
linux: options.linux,
|
|
251
|
-
linuxImage: options.linuxImage,
|
|
252
|
-
// Task scope: switch the recorte off (#87). At feature scope nothing is
|
|
253
|
-
// ever narrowed, so the flag is a documented no-op there, not an error.
|
|
254
|
-
full: options.full,
|
|
255
|
-
});
|
|
256
|
-
} catch (err) {
|
|
257
|
-
// A wrong invocation is exit 2, like an unknown feature or task — never a
|
|
258
|
-
// verdict. `--filter` that selects nothing must not print green (or red):
|
|
259
|
-
// there is no verification to report, only a command to fix.
|
|
260
|
-
if (err instanceof VerifyUsageError) {
|
|
261
|
-
console.error(chalk.red(`\n❌ ${err.message}\n`));
|
|
262
|
-
process.exit(2);
|
|
263
|
-
}
|
|
264
|
-
// Ocupado não é veredito: nada foi verificado, então não há stamp nem
|
|
265
|
-
// evento, e o exit 3 nunca é "vermelho".
|
|
266
|
-
if (err instanceof HeavyBusyError) {
|
|
267
|
-
if (options.json) {
|
|
268
|
-
const holders = err.holders.map(({ pid, label, feature, node, since }) => ({ pid, label, feature, node, since }));
|
|
269
|
-
console.log(JSON.stringify({
|
|
270
|
-
status: 'busy', persisted: false, busy: { reason: err.reason, holders, freeGb: err.freeGb, waitedMs: err.waitedMs },
|
|
271
|
-
}, null, 2));
|
|
284
|
+
let ci = null;
|
|
285
|
+
if (options.ci) {
|
|
286
|
+
try {
|
|
287
|
+
const outcome = await runCiVerify(process.cwd(), featureName, readMorphConfig(process.cwd()), {
|
|
288
|
+
log: (line) => console.error(chalk.gray(` ${line}`)),
|
|
289
|
+
});
|
|
290
|
+
if (outcome.mode === 'ci') {
|
|
291
|
+
ci = outcome;
|
|
292
|
+
} else if (outcome.mode === 'pending') {
|
|
293
|
+
if (options.json) console.log(JSON.stringify({ status: 'pending', persisted: false, reason: outcome.reason }, null, 2));
|
|
294
|
+
console.error(chalk.yellow(`\n⏳ ${outcome.reason}. Não é vermelho e não há stamp: não repita em laço — siga e deixe o gate-check decidir.\n`));
|
|
295
|
+
process.exit(3);
|
|
296
|
+
} else {
|
|
297
|
+
console.error(chalk.yellow(`\n↪ CI indisponível (${outcome.reason}) — verify local.\n`));
|
|
272
298
|
}
|
|
273
|
-
|
|
274
|
-
|
|
299
|
+
} catch (err) {
|
|
300
|
+
if (err instanceof CiUsageError) {
|
|
301
|
+
console.error(chalk.red(`\n❌ ${err.message}\n`));
|
|
302
|
+
process.exit(2);
|
|
303
|
+
}
|
|
304
|
+
throw err;
|
|
275
305
|
}
|
|
276
|
-
throw err;
|
|
277
306
|
}
|
|
278
307
|
|
|
279
|
-
|
|
308
|
+
// Taken before the run: it is the tree the run is about to read.
|
|
309
|
+
const fingerprint = treeFingerprint(process.cwd());
|
|
310
|
+
|
|
311
|
+
const result = ci ? ci.result : await runLocalVerify(featureName, taskId, options);
|
|
312
|
+
|
|
313
|
+
const cleanup = ci ? { status: 'skipped', reason: 'ci' } : afterHeavyRun({
|
|
280
314
|
projectPath: process.cwd(),
|
|
281
315
|
config: readHeavyConfig(process.cwd()),
|
|
282
316
|
scope: taskId ? 'task' : 'feature',
|
|
@@ -303,7 +337,6 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
303
337
|
compact = {
|
|
304
338
|
status: result.status,
|
|
305
339
|
nodes: nodeStatuses(result),
|
|
306
|
-
score: result.nodes.validators.score ?? null,
|
|
307
340
|
attempts: (prevEntry?.attempts ?? 0) + 1,
|
|
308
341
|
updatedAt: now,
|
|
309
342
|
};
|
|
@@ -329,9 +362,10 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
329
362
|
status: result.status,
|
|
330
363
|
nodes: nodeStatuses(result),
|
|
331
364
|
e2eReason: result.nodes.e2e.status === 'skip' ? (result.nodes.e2e.reason ?? null) : null,
|
|
332
|
-
sha: getHeadSha(process.cwd()),
|
|
365
|
+
sha: ci ? ci.run.headSha : getHeadSha(process.cwd()),
|
|
333
366
|
ts: now,
|
|
334
367
|
scope: taskId ? `task:${taskId}` : 'feature',
|
|
368
|
+
...(ci ? { source: 'ci', ciRun: ci.run.url } : {}),
|
|
335
369
|
};
|
|
336
370
|
await updateFeature(featureName, 'verification', stamp);
|
|
337
371
|
}
|
|
@@ -341,9 +375,9 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
341
375
|
let repeatOf = null;
|
|
342
376
|
try {
|
|
343
377
|
const cwd = process.cwd();
|
|
344
|
-
if (!taskId && persist) repeatOf = findRepeatedVerify(cwd, { feature: featureName, task: null, scope: result.scope, fingerprint });
|
|
378
|
+
if (!taskId && persist && !ci) repeatOf = findRepeatedVerify(cwd, { feature: featureName, task: null, scope: result.scope, fingerprint });
|
|
345
379
|
const tree = resolve(cwd).replace(/\\/g, '/') === resolvePrimaryRoot(cwd) ? 'root' : 'worktree';
|
|
346
|
-
recordVerifyEvent(cwd, buildVerifyEvent(result, { sha: getHeadSha(cwd), tree, persisted: persist, reason, fingerprint }));
|
|
380
|
+
recordVerifyEvent(cwd, buildVerifyEvent(result, { sha: ci ? ci.run.headSha : getHeadSha(cwd), tree, persisted: persist, reason, fingerprint, source: ci ? 'ci' : 'local' }));
|
|
347
381
|
} catch { /* telemetry never changes the verdict */ }
|
|
348
382
|
|
|
349
383
|
// The refusal always goes to stderr, so it survives --json (whose stdout has
|
|
@@ -355,10 +389,18 @@ export async function verifyCommand(featureName, taskId, options = {}) {
|
|
|
355
389
|
}
|
|
356
390
|
|
|
357
391
|
if (options.json) {
|
|
358
|
-
const output = {
|
|
392
|
+
const output = {
|
|
393
|
+
...result, persisted: persist, ...(persist ? {} : { reason }), repeatOf, cleanup,
|
|
394
|
+
...(ci ? { source: 'ci', ciRun: { id: ci.run.databaseId, url: ci.run.url, sha: ci.run.headSha }, workflowCreated: ci.created } : {}),
|
|
395
|
+
};
|
|
359
396
|
console.log(JSON.stringify(output, null, 2));
|
|
360
397
|
} else {
|
|
361
398
|
console.log(formatVerifyResult(result));
|
|
399
|
+
if (ci) {
|
|
400
|
+
console.log(chalk.cyan(`fonte: CI ${ci.run.url}`));
|
|
401
|
+
if (result.status === 'fail') console.log(chalk.gray(` log das falhas: gh run view ${ci.run.databaseId} --log-failed`));
|
|
402
|
+
console.log('');
|
|
403
|
+
}
|
|
362
404
|
if (cleanup.status !== 'skipped') {
|
|
363
405
|
const mark = cleanup.status === 'ran' ? chalk.green('✓') : chalk.yellow('⚠');
|
|
364
406
|
console.log(chalk.gray(`${mark} dotnet build-server shutdown: ${cleanup.status}${cleanup.reason ? ` (${cleanup.reason})` : ''}\n`));
|
|
@@ -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']);
|