@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
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { buildRecap, changeSummary, recapScenes, renderRecapHtml } from '../../lib/recap/recap.js';
|
|
6
|
+
import { narrateWithPiper, resolvePiper } from '../../lib/recap/piper.js';
|
|
7
|
+
|
|
8
|
+
export const RECAP_FILES = { html: 'recap.html', data: 'recap.json' };
|
|
9
|
+
|
|
10
|
+
/** Writes `recap.html` + `recap.json` into `targetDir` from the live feature folder. Never throws. */
|
|
11
|
+
export function writeRecap(featureDir, targetDir, feature, { cwd }) {
|
|
12
|
+
try {
|
|
13
|
+
const data = buildRecap(featureDir, feature, { changes: changeSummary(cwd) });
|
|
14
|
+
writeFileSync(join(targetDir, RECAP_FILES.data), JSON.stringify(data, null, 2));
|
|
15
|
+
writeFileSync(join(targetDir, RECAP_FILES.html), renderRecapHtml(data));
|
|
16
|
+
return { ok: true, path: join(targetDir, RECAP_FILES.html) };
|
|
17
|
+
} catch (err) {
|
|
18
|
+
return { ok: false, reason: err.message };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Active feature: rebuilt from its folder and the branch diff. Archived: the data the finish froze,
|
|
23
|
+
// since the archive keeps neither the reports nor the branch.
|
|
24
|
+
export function recapCommand(feature, options = {}) {
|
|
25
|
+
const cwd = options.path || process.cwd();
|
|
26
|
+
const active = join(cwd, '.morph', 'features', feature);
|
|
27
|
+
const archived = join(cwd, '.morph', 'archive', feature);
|
|
28
|
+
let data;
|
|
29
|
+
let dir;
|
|
30
|
+
if (existsSync(active)) {
|
|
31
|
+
dir = active;
|
|
32
|
+
data = buildRecap(active, feature, { changes: changeSummary(cwd) });
|
|
33
|
+
writeFileSync(join(dir, RECAP_FILES.data), JSON.stringify(data, null, 2));
|
|
34
|
+
} else if (existsSync(join(archived, RECAP_FILES.data))) {
|
|
35
|
+
dir = archived;
|
|
36
|
+
data = JSON.parse(readFileSync(join(archived, RECAP_FILES.data), 'utf8'));
|
|
37
|
+
} else {
|
|
38
|
+
const payload = { ok: false, reason: existsSync(archived) ? 'archived-without-recap' : 'feature-not-found' };
|
|
39
|
+
if (options.json) console.log(JSON.stringify(payload));
|
|
40
|
+
else console.error(chalk.yellow(`\n○ ${payload.reason === 'feature-not-found' ? `Feature "${feature}" não existe.` : `"${feature}" foi arquivada antes do recap existir.`}\n`));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let narration = 'sistema';
|
|
45
|
+
let path = join(dir, RECAP_FILES.html);
|
|
46
|
+
let note = null;
|
|
47
|
+
if (options.piper) {
|
|
48
|
+
const piper = resolvePiper();
|
|
49
|
+
const audio = piper.ok ? narrateWithPiper(recapScenes(data), piper) : null;
|
|
50
|
+
if (audio) {
|
|
51
|
+
// Audio is megabytes: it goes to a temp file, never into the committed archive.
|
|
52
|
+
path = join(tmpdir(), `morph-recap-${feature}.html`);
|
|
53
|
+
writeFileSync(path, renderRecapHtml(data, { audio }));
|
|
54
|
+
narration = 'piper';
|
|
55
|
+
} else {
|
|
56
|
+
note = piper.ok ? 'o piper falhou numa cena — ficou a voz do sistema' : piper.reason;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (narration === 'sistema') writeFileSync(path, renderRecapHtml(data));
|
|
60
|
+
|
|
61
|
+
if (options.json) {
|
|
62
|
+
console.log(JSON.stringify({ ok: true, path, narration, note }));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
console.log(chalk.green(`\n✅ Recap de ${feature}: ${path}`));
|
|
66
|
+
console.log(chalk.gray(` narração: ${narration === 'piper' ? 'Piper (áudio embutido)' : 'voz do sistema, pelo navegador'}${note ? ` — ${note}` : ''}\n`));
|
|
67
|
+
}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
// Judged from observed evidence, never the agent's word: did the WORK land, independently of
|
|
2
|
-
// the REPORT? The `tree` block is information only and never changes the verdict.
|
|
3
|
-
|
|
4
1
|
import chalk from 'chalk';
|
|
5
|
-
import { existsSync, readFileSync
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
6
3
|
import { join } from 'path';
|
|
7
|
-
import {
|
|
8
|
-
import { sliceReportPath, inspectReport, REPORT_MARKERS } from '../../../framework/hooks/shared/slice-progress.js';
|
|
9
|
-
import { loadTelemetryAllTrees, dedupeEvents } from '../../lib/telemetry/telemetry-reader.js';
|
|
4
|
+
import { implementProgress, parseReport, reportPath } from '../../../framework/hooks/shared/report-progress.js';
|
|
10
5
|
import { treeState, snapshotTree } from '../../lib/git/tree-state.js';
|
|
11
6
|
import { atomicWriteJson } from '../../core/state/state-manager.js';
|
|
12
7
|
import { minimatch } from 'minimatch';
|
|
@@ -15,9 +10,10 @@ import { verifierModelFor } from '../../lib/risk-detector.js';
|
|
|
15
10
|
import { changedPathsAgainst, detectDefaultBranch } from '../../lib/git/default-branch.js';
|
|
16
11
|
import { readMorphConfig } from '../../lib/tasks/test-runner.js';
|
|
17
12
|
import { readPlanChecks } from '../../lib/plan/checks.js';
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
import { isTestPath } from '../../lib/graph/sanitize.js';
|
|
14
|
+
import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
15
|
+
import { collectReviewRequirement } from './gate-check.js';
|
|
16
|
+
import { featureGeneratesMedia } from '../../lib/plan/media.js';
|
|
21
17
|
|
|
22
18
|
/** Each review report: complete when it carries a readable verdict and is not partial. */
|
|
23
19
|
export function inspectReviewReports(cwd, feature) {
|
|
@@ -30,8 +26,12 @@ export function inspectReviewReports(cwd, feature) {
|
|
|
30
26
|
return out;
|
|
31
27
|
}
|
|
32
28
|
|
|
29
|
+
function readOrEmpty(path) {
|
|
30
|
+
try { return existsSync(path) ? readFileSync(path, 'utf8') : ''; } catch { return ''; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
33
|
// The verifier routes to opus on prompt/model or media; security is dispatched only when required.
|
|
34
|
-
function reviewDispatch(cwd, feature
|
|
34
|
+
function reviewDispatch(cwd, feature) {
|
|
35
35
|
let changedFiles = [];
|
|
36
36
|
try { changedFiles = changedPathsAgainst(detectDefaultBranch(cwd), cwd); } catch { changedFiles = []; }
|
|
37
37
|
const watch = readMorphConfig(cwd)?.project?.evals?.watch;
|
|
@@ -43,139 +43,45 @@ function reviewDispatch(cwd, feature, slices) {
|
|
|
43
43
|
required,
|
|
44
44
|
verifier: verifierModelFor({
|
|
45
45
|
promptModelTouched: changedFiles.some((f) => globs.some((g) => minimatch(f, g))),
|
|
46
|
-
generatesMedia: featureGeneratesMedia(
|
|
46
|
+
generatesMedia: featureGeneratesMedia(readOrEmpty(getAbsoluteOutputPath(cwd, feature, 'plan'))),
|
|
47
47
|
}),
|
|
48
48
|
reviewer: { model: 'sonnet', reasons: [] },
|
|
49
49
|
security: { required: security.required, model: 'opus', reasons: security.reasons },
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// Two clocks: the report mtime comes from the filesystem, the event `ts` from the hook. Two
|
|
54
|
-
// seconds absorb their skew and are far less than any real work done after reporting.
|
|
55
|
-
export const STALE_TOLERANCE_MS = 2000;
|
|
56
|
-
|
|
57
53
|
const slash = (p) => String(p || '').replace(/\\/g, '/');
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const reportSuffix = (feature, sliceId) => `.morph/features/${feature}/3-implement/reports/${sliceId}.md`.toLowerCase();
|
|
68
|
-
|
|
69
|
-
function loadWrites(cwd) {
|
|
70
|
-
let trees = [];
|
|
71
|
-
try { trees = loadTelemetryAllTrees(cwd); } catch { trees = []; }
|
|
72
|
-
const events = [];
|
|
73
|
-
for (const tree of trees) {
|
|
74
|
-
for (const e of tree.events) {
|
|
75
|
-
if (e?.tool !== 'Write' && e?.tool !== 'Edit') continue;
|
|
76
|
-
if (isFailedToolCall(e)) continue;
|
|
77
|
-
const ts = Date.parse(e?.ts ?? '');
|
|
78
|
-
if (!Number.isFinite(ts)) continue;
|
|
79
|
-
events.push({ ...e, rel: toTreeRelative(e.target, tree.path), tsMs: ts });
|
|
80
|
-
}
|
|
54
|
+
const isTestRef = (p) => isTestPath(p) || /\.(test|spec)\.[a-z]+$|Tests?\.cs$/i.test(p);
|
|
55
|
+
|
|
56
|
+
/** Cn whose evidence in the report cites tests in more than one file, as `{ id, files }`. */
|
|
57
|
+
export function checksSpread(reportText) {
|
|
58
|
+
const spread = [];
|
|
59
|
+
for (const [id, { evidence }] of parseReport(reportText)) {
|
|
60
|
+
const files = [...new Set((evidence.match(/[\w@./\\-]+\.[A-Za-z]+/g) ?? []).map(slash).filter(isTestRef))];
|
|
61
|
+
if (files.length > 1) spread.push({ id, files });
|
|
81
62
|
}
|
|
82
|
-
return
|
|
63
|
+
return spread;
|
|
83
64
|
}
|
|
84
65
|
|
|
85
|
-
// The owning dispatch is the latest sub-agent write of the report; a later orchestrator edit
|
|
86
|
-
// does not hide it. Without a report, any write to one of the slice files says the work landed.
|
|
87
|
-
function observedEvidence(telemetry, feature, slice) {
|
|
88
|
-
if (!telemetry.available) return { available: false };
|
|
89
|
-
const suffix = reportSuffix(feature, slice.id);
|
|
90
|
-
const isReport = (w) => slash(w.target).toLowerCase().endsWith(suffix);
|
|
91
|
-
const declared = new Set((slice.files ?? []).map((f) => slash(f).replace(/^\.\//, '').toLowerCase()));
|
|
92
|
-
const isSliceFile = (w) => declared.has(w.rel.toLowerCase());
|
|
93
|
-
|
|
94
|
-
const reportWrites = telemetry.writes.filter((w) => isReport(w) && w.agentId);
|
|
95
|
-
const agentId = reportWrites.length ? reportWrites[reportWrites.length - 1].agentId : null;
|
|
96
|
-
if (!agentId) {
|
|
97
|
-
return { available: true, agentId: null, sliceWrites: telemetry.writes.filter(isSliceFile).length };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const work = telemetry.writes.filter((w) => w.agentId === agentId && !isReport(w) && !w.rel.includes('.morph/'));
|
|
101
|
-
const observed = [...new Set(work.map((w) => w.rel))];
|
|
102
|
-
const observedKeys = new Set(observed.map((f) => f.toLowerCase()));
|
|
103
|
-
return {
|
|
104
|
-
available: true,
|
|
105
|
-
agentId,
|
|
106
|
-
observed,
|
|
107
|
-
lastWriteAt: work.length ? new Date(Math.max(...work.map((w) => w.tsMs))).toISOString() : null,
|
|
108
|
-
undeclared: observed.filter((f) => !declared.has(f.toLowerCase())),
|
|
109
|
-
unobserved: (slice.files ?? []).filter((f) => !observedKeys.has(slash(f).replace(/^\.\//, '').toLowerCase())),
|
|
110
|
-
sliceWrites: work.filter(isSliceFile).length,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function isReportStale(reportFsPath, observed) {
|
|
115
|
-
if (!observed?.agentId || !observed.lastWriteAt) return false;
|
|
116
|
-
try {
|
|
117
|
-
return statSync(reportFsPath).mtimeMs + STALE_TOLERANCE_MS < Date.parse(observed.lastWriteAt);
|
|
118
|
-
} catch {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function decide(report, observed, stale) {
|
|
124
|
-
if (report.exists && report.complete) {
|
|
125
|
-
// Structural integrity answers "is the report complete?", never "is it
|
|
126
|
-
// still true?": an agent can write it early, keep working and be cut off.
|
|
127
|
-
return stale
|
|
128
|
-
? { verdict: 'report-stale', action: 'refresh-from-evidence' }
|
|
129
|
-
: { verdict: 'ok', action: 'proceed' };
|
|
130
|
-
}
|
|
131
|
-
// PARCIAL: the agent stopped short (turn ceiling) and resumes from its last
|
|
132
|
-
// `wip` commit — completing it DEGRADADO would stamp unfinished work as done.
|
|
133
|
-
if (report.exists && report.blocked) return { verdict: 'report-blocked', action: 'ask-human' };
|
|
134
|
-
if (report.exists && report.partial) return { verdict: 'report-incomplete', action: 'resume' };
|
|
135
|
-
if (report.exists) return { verdict: 'report-incomplete', action: 'complete-degraded' };
|
|
136
|
-
if (!observed.available) return { verdict: 'report-missing-evidence-unknown', action: 'redispatch' };
|
|
137
|
-
return observed.sliceWrites > 0
|
|
138
|
-
? { verdict: 'work-landed-report-missing', action: 'accept-degraded' }
|
|
139
|
-
: { verdict: 'no-work-no-report', action: 'redispatch' };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function checkSlice(cwd, feature, slice, telemetry) {
|
|
143
|
-
const fsPath = sliceReportPath(join(cwd, '.morph', 'features', feature), slice.id);
|
|
144
|
-
const report = inspectReport(fsPath, { covers: slice.covers ?? [] });
|
|
145
|
-
const observed = observedEvidence(telemetry, feature, slice);
|
|
146
|
-
const stale = report.exists && isReportStale(fsPath, observed);
|
|
147
|
-
return { slice: slice.id, report, observed, ...decide(report, observed, stale) };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const ACTION_HINT = {
|
|
151
|
-
proceed: 'relatório íntegro — siga o fluxo normal.',
|
|
152
|
-
'refresh-from-evidence': 'o relatório está COMPLETO mas VELHO: o agente da fatia seguiu escrevendo arquivos depois de gravá-lo, então ele descreve um estado já ultrapassado. NÃO re-despache e não confie no texto — confira os arquivos em `observed` (evidência real, não auto-declarada) e atualize o relatório a partir deles.',
|
|
153
|
-
'complete-degraded': 'complete os itens faltantes a partir da mensagem de retorno do sub-agent, carimbe o relatório como DEGRADADO e registre nele a falha de protocolo.',
|
|
154
|
-
resume: 'relatório PARCIAL: o sub-agent parou antes do fim (limite de turnos) e a fatia NÃO está pronta. Não carimbe DEGRADADO — retome o MESMO agente por `SendMessage` (cole no recado `tree.dirty` e `tree.commits` desta saída e o que falta) ou re-despache só o que falta, a partir do último commit `wip`.',
|
|
155
|
-
'ask-human': 'relatório BLOQUEADO: o sub-agent parou numa decisão que não é dele (requisito que contradiz o plano ou um ADR, escolha do humano) e o relatório traz a pergunta e as opções. NÃO re-despache — a mesma pergunta volta. Se o plano ou um ADR já decide, decida você e registre no relatório; senão, faça a pergunta por `AskUserQuestion`. Depois retome o MESMO agente por `SendMessage` com a resposta.',
|
|
156
|
-
'accept-degraded':'o TRABALHO entrou — não re-despache. Grave você mesmo o relatório da fatia a partir da mensagem de retorno, carimbado como DEGRADADO, com a falha de protocolo registrada nele.',
|
|
157
|
-
redispatch: 're-despache a fatia UMA vez com o mesmo prompt. Segunda ausência sem trabalho entregue = falha real: escale ao humano.',
|
|
158
|
-
};
|
|
159
|
-
|
|
160
66
|
function snapshotPath(cwd, feature) {
|
|
161
67
|
return join(cwd, '.morph', 'features', feature, '4-review', 'tree-before-eval.json');
|
|
162
68
|
}
|
|
163
69
|
|
|
164
70
|
// `beforeEval` snapshots after comparing, so the output still shows what changed since the last one.
|
|
165
|
-
function treeFor(cwd, feature, beforeEval
|
|
71
|
+
function treeFor(cwd, feature, beforeEval) {
|
|
166
72
|
const path = snapshotPath(cwd, feature);
|
|
167
73
|
let snapshot = null;
|
|
168
74
|
try { if (existsSync(path)) snapshot = JSON.parse(readFileSync(path, 'utf8')); } catch { snapshot = null; }
|
|
169
75
|
const tree = treeState(cwd, { snapshot });
|
|
170
76
|
if (!beforeEval) return { tree };
|
|
171
|
-
const reviewModels = reviewDispatch(cwd, feature
|
|
77
|
+
const reviewModels = reviewDispatch(cwd, feature);
|
|
172
78
|
const next = snapshotTree(cwd);
|
|
173
79
|
if (!next) return { tree, beforeEval: null, reviewModels };
|
|
174
80
|
atomicWriteJson(path, next);
|
|
175
81
|
return { tree, beforeEval: { at: next.at, files: Object.keys(next.files).length }, reviewModels };
|
|
176
82
|
}
|
|
177
83
|
|
|
178
|
-
export function reportCheckCommand(feature,
|
|
84
|
+
export function reportCheckCommand(feature, options = {}) {
|
|
179
85
|
const cwd = options.cwd || process.cwd();
|
|
180
86
|
const emit = (obj, code) => {
|
|
181
87
|
if (options.json) console.log(JSON.stringify(obj, null, 2));
|
|
@@ -187,20 +93,15 @@ export function reportCheckCommand(feature, sliceIds, options = {}) {
|
|
|
187
93
|
if (!checks) {
|
|
188
94
|
emit({ error: `feature "${feature}" sem 2-plan/checks.json legível (${status}) — rode morph-spec plan-check ${feature}`, feature }, 1);
|
|
189
95
|
}
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return { ...r, hint: ACTION_HINT[r.action] };
|
|
200
|
-
});
|
|
201
|
-
const clean = results.every((r) => r.verdict === 'ok');
|
|
202
|
-
const reviews = ids.length ? undefined : inspectReviewReports(cwd, feature);
|
|
203
|
-
emit({ feature, results, ...(reviews ? { reviews } : {}), clean, ...treeFor(cwd, feature, options.beforeEval, slices) }, clean ? 0 : 1);
|
|
96
|
+
const featureDir = join(cwd, '.morph', 'features', feature);
|
|
97
|
+
const progress = implementProgress(featureDir);
|
|
98
|
+
const warnings = checksSpread(readOrEmpty(reportPath(featureDir))).map(({ id, files }) =>
|
|
99
|
+
`${id} cita testes em ${files.length} arquivos (${files.join(', ')}): um teste por Cn, na camada mais baixa que o prova`);
|
|
100
|
+
const implement = progress
|
|
101
|
+
? { total: progress.total, done: progress.done, pending: progress.checks.filter((c) => c.status === 'pendente').map((c) => c.id),
|
|
102
|
+
blocked: progress.checks.filter((c) => c.status === 'bloqueado').map((c) => c.id), legacy: progress.legacy, warnings }
|
|
103
|
+
: null;
|
|
104
|
+
emit({ feature, implement, reviews: inspectReviewReports(cwd, feature), ...treeFor(cwd, feature, options.beforeEval) }, 0);
|
|
204
105
|
}
|
|
205
106
|
|
|
206
107
|
function printHuman(obj) {
|
|
@@ -209,17 +110,12 @@ function printHuman(obj) {
|
|
|
209
110
|
return;
|
|
210
111
|
}
|
|
211
112
|
console.log(chalk.bold(`\n📄 report-check ${obj.feature}`));
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
if (r.report.exists && r.report.missing.length) {
|
|
219
|
-
console.log(chalk.gray(` faltando no relatório: ${r.report.missing.join(', ')}`));
|
|
220
|
-
}
|
|
221
|
-
console.log(chalk.gray(` → ${r.hint}`));
|
|
222
|
-
}
|
|
113
|
+
const impl = obj.implement;
|
|
114
|
+
if (impl) {
|
|
115
|
+
const open = [...impl.pending, ...impl.blocked];
|
|
116
|
+
console.log(` ${open.length ? chalk.yellow('⚠') : chalk.green('✓')} report.md ${impl.done}/${impl.total} cenários feitos`
|
|
117
|
+
+ `${impl.pending.length ? ` · pendentes: ${impl.pending.join(', ')}` : ''}${impl.blocked.length ? ` · bloqueados: ${impl.blocked.join(', ')}` : ''}`);
|
|
118
|
+
for (const w of impl.warnings) console.log(chalk.yellow(` ⚠ ${w}`));
|
|
223
119
|
}
|
|
224
120
|
for (const [role, r] of Object.entries(obj.reviews ?? {})) {
|
|
225
121
|
if (!r.exists && role === 'security') continue;
|
|
@@ -245,9 +141,6 @@ function printTree(tree) {
|
|
|
245
141
|
console.log(` árvore ${tree.branch ?? '-'}@${tree.head ?? '-'}: ${tree.dirtyCount} arquivo(s) não commitado(s) fora de .morph/${stat}`);
|
|
246
142
|
for (const d of tree.dirty.slice(0, 10)) console.log(chalk.gray(` ${d.status.padEnd(2)} ${d.path}`));
|
|
247
143
|
if (tree.dirtyCount > 10) console.log(chalk.gray(` … e mais ${tree.dirtyCount - 10}`));
|
|
248
|
-
if (tree.markers.length) {
|
|
249
|
-
console.log(chalk.yellow(` ⚠ marcador de mutação esquecido: ${tree.markers.map((m) => `${m.file}:${m.line}`).join(', ')}`));
|
|
250
|
-
}
|
|
251
144
|
if (tree.commits?.length) console.log(chalk.gray(` commits sobre a default: ${tree.commits.join(' · ')}`));
|
|
252
145
|
const since = tree.sinceBeforeEval;
|
|
253
146
|
if (since) {
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { atomicWriteJson } from '../../core/state/state-manager.js';
|
|
5
5
|
import { isGateApproved } from '../../../framework/hooks/shared/state-reader.js';
|
|
6
6
|
import { getAbsoluteOutputPath } from '../../core/paths/output-schema.js';
|
|
7
7
|
import { parsePlan } from '../../lib/plan/plan-parser.js';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { renderPlanHtml } from '../../lib/plan/plan-html.js';
|
|
9
|
+
import { buildChecks, planHash, planRuleOptions, validatePlan } from '../../lib/plan/checks.js';
|
|
10
10
|
import { revocationWarnings } from '../../lib/specs/adr-index.js';
|
|
11
11
|
import { assertOwnerTree, ownerTreeRefusalLines } from '../../lib/worktree/feature-truth.js';
|
|
12
12
|
import { seedDotnetBuildIgnores } from '../../lib/stack/dotnet-build-ignores.js';
|
|
13
13
|
import { reportDotnetIgnores } from '../state/advance.js';
|
|
14
|
+
import { syncStackRules } from '../../lib/installers/stack-sync.js';
|
|
14
15
|
|
|
15
16
|
function readJson(path) {
|
|
16
17
|
try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
|
|
@@ -21,17 +22,14 @@ export function runPlanCheck(featureName, cwd = process.cwd()) {
|
|
|
21
22
|
const planPath = getAbsoluteOutputPath(cwd, featureName, 'plan');
|
|
22
23
|
const checksPath = getAbsoluteOutputPath(cwd, featureName, 'checks');
|
|
23
24
|
const base = { feature: featureName, plan: planPath, checksPath };
|
|
24
|
-
if (!existsSync(planPath)) return { ...base, ok: false, exit: 2, error: 'plan-not-found', errors: [], warnings: []
|
|
25
|
+
if (!existsSync(planPath)) return { ...base, ok: false, exit: 2, error: 'plan-not-found', errors: [], warnings: [] };
|
|
25
26
|
|
|
26
27
|
const text = readFileSync(planPath, 'utf8');
|
|
27
28
|
const plan = parsePlan(text);
|
|
28
|
-
const { errors, warnings } = validatePlan(plan, {
|
|
29
|
-
personas: knownPersonas(cwd), surfaces: knownSurfaces(cwd), ...planRuleOptions(cwd, featureName),
|
|
30
|
-
});
|
|
29
|
+
const { errors, warnings } = validatePlan(plan, { ...planRuleOptions(cwd, featureName), text });
|
|
31
30
|
warnings.push(...revocationWarnings(cwd, plan));
|
|
32
|
-
const groups = errors.length === 0 ? parallelGroups(plan.slices) : [];
|
|
33
31
|
const hash = planHash(text);
|
|
34
|
-
const result = { ...base, planHash: hash, errors, warnings,
|
|
32
|
+
const result = { ...base, planHash: hash, errors, warnings, written: false };
|
|
35
33
|
if (errors.length > 0) return { ...result, ok: false, exit: 1 };
|
|
36
34
|
|
|
37
35
|
const approved = isGateApproved(featureName, 'plan', cwd);
|
|
@@ -41,7 +39,9 @@ export function runPlanCheck(featureName, cwd = process.cwd()) {
|
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
atomicWriteJson(checksPath, buildChecks(featureName, text, plan));
|
|
44
|
-
|
|
42
|
+
const viewPath = getAbsoluteOutputPath(cwd, featureName, 'planView');
|
|
43
|
+
writeFileSync(viewPath, renderPlanHtml(featureName, plan, { planHash: hash }));
|
|
44
|
+
return { ...result, ok: true, exit: 0, written: true, viewPath };
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export function planCheckCommand(featureName, options = {}) {
|
|
@@ -54,8 +54,13 @@ export function planCheckCommand(featureName, options = {}) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const result = runPlanCheck(featureName);
|
|
57
|
+
// After the plan validates: in a greenfield project the plan is where the stack is born.
|
|
58
|
+
const stackSync = result.written
|
|
59
|
+
? syncStackRules(process.cwd(), { log: (line) => console.error(chalk.gray(line)) })
|
|
60
|
+
: null;
|
|
57
61
|
const gitignoreAdded = result.written ? seedDotnetBuildIgnores(process.cwd(), { featureName }) : [];
|
|
58
62
|
if (options.json) {
|
|
63
|
+
if (stackSync?.added.length) result.rulesAdded = stackSync.added;
|
|
59
64
|
if (gitignoreAdded.length) result.gitignoreAdded = gitignoreAdded;
|
|
60
65
|
if (gitignoreAdded.skipped) result.gitignoreSkipped = gitignoreAdded.skipped;
|
|
61
66
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -77,7 +82,7 @@ export function planCheckCommand(featureName, options = {}) {
|
|
|
77
82
|
console.log(chalk.red(`✗ plan-check: ${result.errors.length} erro(s) em ${result.plan} — checks.json não foi gravado`));
|
|
78
83
|
} else {
|
|
79
84
|
console.log(chalk.green(`✓ plan-check: ${join('2-plan', 'checks.json')} gravado (${result.planHash.slice(0, 12)})`));
|
|
80
|
-
|
|
85
|
+
console.log(chalk.gray(` visão do plano: ${result.viewPath}`));
|
|
81
86
|
reportDotnetIgnores(gitignoreAdded);
|
|
82
87
|
}
|
|
83
88
|
process.exit(result.exit);
|
|
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { parsePlan } from '../../lib/plan/plan-parser.js';
|
|
5
|
-
import {
|
|
5
|
+
import { planHash, planRuleOptions, validatePlan } from '../../lib/plan/checks.js';
|
|
6
6
|
|
|
7
7
|
// Matched against section markers (headings and line-start bold labels), never whole-file
|
|
8
8
|
// substrings. required → ERROR, recommended → WARNING, optional → never penalized; an array is any-of.
|
|
@@ -117,7 +117,7 @@ function validatePlanOutput(content, planDir, result) {
|
|
|
117
117
|
const plan = parsePlan(content);
|
|
118
118
|
const root = path.resolve(planDir, '..', '..', '..', '..');
|
|
119
119
|
const feature = path.basename(path.resolve(planDir, '..'));
|
|
120
|
-
const { errors, warnings } = validatePlan(plan, {
|
|
120
|
+
const { errors, warnings } = validatePlan(plan, { ...planRuleOptions(root, feature), text: content });
|
|
121
121
|
result.errors.push(...errors);
|
|
122
122
|
result.warnings.push(...warnings);
|
|
123
123
|
result.sections.found.push(...plan.sections.filter((x) => x.key).map((x) => x.name));
|
|
@@ -137,7 +137,7 @@ function validateChecksOutput(content, planDir, result) {
|
|
|
137
137
|
result.errors.push('checks.json is stale — plan.md changed after it was generated; run `morph-spec plan-check`');
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
|
-
result.sections.found.push(`${doc.checks?.length ?? 0} check(s)
|
|
140
|
+
result.sections.found.push(`${doc.checks?.length ?? 0} check(s)`);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
function validateMarkdownSections(content, sections, result) {
|
|
@@ -14,6 +14,7 @@ import { afterHeavyRun } from '../../lib/heavy/cleanup.js';
|
|
|
14
14
|
import { memoryBusyHint } from '../../lib/heavy/live-stacks.js';
|
|
15
15
|
import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js';
|
|
16
16
|
import { runCiVerify, CiUsageError, reusableLocalStamp } from '../../lib/ci/ci-verify.js';
|
|
17
|
+
import { syncStackRules } from '../../lib/installers/stack-sync.js';
|
|
17
18
|
|
|
18
19
|
function statusIcon(status) {
|
|
19
20
|
if (status === 'pass') return chalk.green('✓');
|
|
@@ -57,12 +58,6 @@ function formatVerifyResult(result) {
|
|
|
57
58
|
for (const line of result.feedback) lines.push(` ${line}`);
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
if (result.traceability?.warnings?.length > 0) {
|
|
61
|
-
lines.push('');
|
|
62
|
-
lines.push(chalk.yellow('Traceability warnings (advisory — never fails verify):'));
|
|
63
|
-
for (const w of result.traceability.warnings) lines.push(` ⚠ ${w}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
61
|
lines.push('');
|
|
67
62
|
return lines.join('\n');
|
|
68
63
|
}
|
|
@@ -180,6 +175,8 @@ export async function verifyCommand(featureName, options = {}) {
|
|
|
180
175
|
process.exit(2);
|
|
181
176
|
}
|
|
182
177
|
|
|
178
|
+
syncStackRules(process.cwd(), { persistTags: false, log: (line) => console.error(chalk.gray(line)) });
|
|
179
|
+
|
|
183
180
|
if (options.ci) {
|
|
184
181
|
const conflict = CI_EXCLUSIVE_FLAGS.find(([flag]) => options[flag])?.[1];
|
|
185
182
|
if (conflict) {
|
|
@@ -309,6 +306,9 @@ export async function verifyCommand(featureName, options = {}) {
|
|
|
309
306
|
for (const u of result.baseline.updates) {
|
|
310
307
|
console.log(chalk.green(` baseline ${u.id}: ${u.from ?? '—'} → ${u.to}`));
|
|
311
308
|
}
|
|
309
|
+
for (const id of result.baseline.removed ?? []) {
|
|
310
|
+
console.log(chalk.gray(` baseline ${id}: removida (runner sem baseline, só o piso zero)`));
|
|
311
|
+
}
|
|
312
312
|
console.log(chalk.gray(`Gravada em ${result.baseline.path}
|
|
313
313
|
`));
|
|
314
314
|
}
|
|
@@ -3,12 +3,12 @@ import { join } from 'path';
|
|
|
3
3
|
export const OUTPUT_SCHEMA = {
|
|
4
4
|
plan: { filename: 'plan.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
5
5
|
checks: { filename: 'checks.json', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
|
|
6
|
+
planCritique: { filename: 'critica.md', phaseDir: '2-plan', phase: 'plan', protected: false },
|
|
7
|
+
planView: { filename: 'plan.html', phaseDir: '2-plan', phase: 'plan', protected: false },
|
|
6
8
|
|
|
7
|
-
uiDesignSystem: { filename: 'design-system.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
8
9
|
uiMockups: { filename: 'mockups.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
9
|
-
uiComponents: { filename: 'components.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
10
|
-
uiFlows: { filename: 'flows.md', phaseDir: '1-design', phase: 'uiux', protected: true, approvalGate: 'uiux' },
|
|
11
10
|
|
|
11
|
+
implementReport: { filename: 'report.md', phaseDir: '3-implement', phase: 'implement', protected: false },
|
|
12
12
|
evidencias: { dirname: 'evidencias', filenamePattern: '^C\\d+\\.md$', displayFilename: '{Cn}.md',
|
|
13
13
|
phaseDir: '3-implement', phase: 'implement', protected: false, multi: true },
|
|
14
14
|
|
|
@@ -450,10 +450,8 @@ export async function markOutput(featureName, outputType) {
|
|
|
450
450
|
if (suggestion) {
|
|
451
451
|
errorMsg += `\n\nDid you mean '${suggestion}'?`;
|
|
452
452
|
const kebabAlternative = {
|
|
453
|
-
'uiDesignSystem': 'ui-design-system',
|
|
454
453
|
'uiMockups': 'ui-mockups',
|
|
455
|
-
'
|
|
456
|
-
'uiFlows': 'ui-flows'
|
|
454
|
+
'implementReport': 'implement-report'
|
|
457
455
|
}[suggestion];
|
|
458
456
|
if (kebabAlternative) {
|
|
459
457
|
errorMsg += ` (also accepts '${kebabAlternative}')`;
|
|
@@ -462,7 +460,7 @@ export async function markOutput(featureName, outputType) {
|
|
|
462
460
|
|
|
463
461
|
errorMsg += `\n\nValid types:\n`;
|
|
464
462
|
errorMsg += ` ${validCamelCaseTypes.join(', ')}\n`;
|
|
465
|
-
errorMsg += `\nNote: kebab-case is also accepted (e.g., 'ui-
|
|
463
|
+
errorMsg += `\nNote: kebab-case is also accepted (e.g., 'ui-mockups', 'implement-report')`;
|
|
466
464
|
|
|
467
465
|
throw new Error(errorMsg);
|
|
468
466
|
}
|
|
@@ -8,7 +8,7 @@ import { useMorphQuery } from '../hooks/useMorphQuery.js';
|
|
|
8
8
|
import { apiBase, projectHref, featureHref } from '../lib/project.js';
|
|
9
9
|
|
|
10
10
|
const KIND_LABEL = {
|
|
11
|
-
page: 'Página', project: 'Projeto', feature: 'Feature',
|
|
11
|
+
page: 'Página', project: 'Projeto', feature: 'Feature', skill: 'Skill', persona: 'Persona',
|
|
12
12
|
standard: 'Standard', rule: 'Rule', hook: 'Hook', guide: 'Guia',
|
|
13
13
|
};
|
|
14
14
|
|
|
@@ -46,9 +46,6 @@ function featureIndex(project, features, projectName = null) {
|
|
|
46
46
|
const where = (text) => (projectName ? (text ? `${projectName}: ${text}` : projectName) : text);
|
|
47
47
|
for (const f of features || []) {
|
|
48
48
|
out.push({ kind: 'feature', title: f.name, hint: where(f.description), to: featureHref(project, f.name), hay: `${f.name} ${f.description || ''} ${projectName || ''}` });
|
|
49
|
-
for (const sl of f.sliceIndex || []) {
|
|
50
|
-
out.push({ kind: 'slice', title: `${sl.id} ${sl.title}`, hint: where(f.name), to: featureHref(project, f.name, '?tab=slices'), hay: `${sl.id} ${sl.title} ${f.name} ${projectName || ''}` });
|
|
51
|
-
}
|
|
52
49
|
}
|
|
53
50
|
return out;
|
|
54
51
|
}
|
|
@@ -112,7 +109,7 @@ export default function CommandPalette({ open, onClose, portfolio, project }) {
|
|
|
112
109
|
value={query}
|
|
113
110
|
onChange={(e) => setQuery(e.target.value)}
|
|
114
111
|
onKeyDown={onKey}
|
|
115
|
-
placeholder="Buscar features,
|
|
112
|
+
placeholder="Buscar features, skills, personas, standards, seções do guia…"
|
|
116
113
|
className="w-full bg-transparent text-base text-[var(--color-fg)] outline-none focus-visible:outline-none placeholder:text-[var(--color-faint)]"
|
|
117
114
|
aria-controls="palette-results"
|
|
118
115
|
aria-activedescendant={results[active] ? `palette-${active}` : undefined}
|