@polymorphism-tech/morph-spec 9.0.0 → 9.2.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 +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +11 -63
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -1
- package/framework/MORPH.md +3 -3
- package/framework/agents.json +19 -11
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +22 -31
- package/framework/evals/review-checklist.md +12 -2
- package/framework/hooks/README.md +8 -44
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +12 -68
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +1 -42
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/rules/nextjs-standards.md +1 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +4 -4
- package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +9 -7
- package/framework/skills/core/morph-review/SKILL.md +9 -6
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +36 -14
- package/framework/standards/STANDARDS.meta.json +16 -2
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +1 -1
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +10 -4
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/frontend/nextjs/testing.md +10 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +9 -9
- 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/docs/plan.md +3 -1
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- 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/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- 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/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +17 -104
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +5 -57
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +15 -97
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +11 -11
- package/src/commands/validation/plan-check.js +4 -2
- package/src/commands/validation/tests-audit.js +7 -0
- package/src/commands/validation/validate-feature.js +10 -97
- package/src/commands/validation/verify.js +30 -113
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +2 -6
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +4 -19
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +14 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/applicability.js +37 -0
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +1 -0
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +3 -15
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/adapters.js +6 -17
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/runner.js +23 -75
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/mutation/trx.js +0 -1
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +34 -33
- package/src/lib/plan/plan-parser.js +7 -8
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +11 -26
- package/src/lib/risk-detector.js +41 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/tests-audit/audit.js +96 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +21 -158
- package/src/lib/validators/verify-runner.js +85 -376
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +13 -95
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -147
- package/src/utils/agents-installer.js +29 -276
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
- package/framework/hooks/git/pre-commit/agents.sh +0 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
- package/framework/hooks/git/pre-push/run-tests.sh +0 -44
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,28 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* de um projeto .NET, para responder "o que este projeto declara sobre pacotes de IA?".
|
|
4
|
-
*
|
|
5
|
-
* Três invariantes deste módulo, todas cobradas por canário:
|
|
6
|
-
*
|
|
7
|
-
* 1. **Nada aqui escreve.** Nenhum import de `fs` que mute, nenhum `spawn`.
|
|
8
|
-
* A auditoria não pode inventar o estado que reporta — é a lição do T10 da
|
|
9
|
-
* #90, onde a auditoria de portas ia ALOCAR um bloco ao auditar.
|
|
10
|
-
* 2. **`null` é DESCONHECIDO, `[]` é "perguntei e não achei".** Colapsar os
|
|
11
|
-
* dois é a doutrina que `src/lib/git/pr-index.js` já pinou neste repositório.
|
|
12
|
-
* Aqui: `scanAiPackageRefs` devolve `null` quando havia arquivos candidatos
|
|
13
|
-
* e NENHUM foi legível; devolve um resultado com `projectFiles: []` quando
|
|
14
|
-
* varreu e não achou `.csproj` nenhum.
|
|
15
|
-
* 3. **Worktree é o checkout de OUTRA feature.** `worktrees/` e `.worktrees/`
|
|
16
|
-
* ficam fora da varredura pela mesma razão pinada em `test-runner.js`:
|
|
17
|
-
* varrê-los reporta sobre código fora da árvore avaliada. Os nomes vêm de
|
|
18
|
-
* `src/lib/worktree/dirs.js`, nunca duplicados aqui.
|
|
19
|
-
*/
|
|
1
|
+
// Nada aqui escreve: a auditoria não pode inventar o estado que reporta. `null` é DESCONHECIDO,
|
|
2
|
+
// `[]` é "perguntei e não achei". Worktrees ficam fora: são o checkout de outra feature.
|
|
20
3
|
|
|
21
4
|
import { readdirSync, readFileSync, statSync, existsSync } from 'node:fs';
|
|
22
5
|
import { join, basename } from 'node:path';
|
|
23
6
|
import { WORKTREE_DIR_NAMES } from '../worktree/dirs.js';
|
|
24
7
|
|
|
25
|
-
/** Diretórios que a varredura nunca desce. */
|
|
26
8
|
export const SCAN_IGNORED_DIRS = new Set([
|
|
27
9
|
'node_modules',
|
|
28
10
|
'bin',
|
|
@@ -31,14 +13,8 @@ export const SCAN_IGNORED_DIRS = new Set([
|
|
|
31
13
|
...WORKTREE_DIR_NAMES,
|
|
32
14
|
]);
|
|
33
15
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*
|
|
37
|
-
* Deliberadamente curto: a lista existe para decidir se há assunto (condição 3
|
|
38
|
-
* do silêncio do `doctor --ai`), não para catalogar o ecossistema. Um pacote de
|
|
39
|
-
* IA fora desta lista aparece na varredura como pacote comum e, se estiver no
|
|
40
|
-
* pin, `pin-drift` o pega mesmo assim — o pin é a outra metade do predicado.
|
|
41
|
-
*/
|
|
16
|
+
// Deliberadamente curto: decide se há assunto, não cataloga o ecossistema; um pacote de IA fora daqui
|
|
17
|
+
// que esteja no pin ainda é pego por `pin-drift`.
|
|
42
18
|
export const AI_PACKAGE_PREFIXES = [
|
|
43
19
|
'Microsoft.Agents.AI',
|
|
44
20
|
'Microsoft.Extensions.AI',
|
|
@@ -50,10 +26,6 @@ export const AI_PACKAGE_PREFIXES = [
|
|
|
50
26
|
'Anthropic.',
|
|
51
27
|
];
|
|
52
28
|
|
|
53
|
-
/**
|
|
54
|
-
* @param {string} name - nome do pacote NuGet
|
|
55
|
-
* @returns {boolean} true quando o nome casa com um dos prefixos de IA
|
|
56
|
-
*/
|
|
57
29
|
export function isAiPackage(name) {
|
|
58
30
|
if (typeof name !== 'string' || name.length === 0) return false;
|
|
59
31
|
return AI_PACKAGE_PREFIXES.some(
|
|
@@ -61,22 +33,11 @@ export function isAiPackage(name) {
|
|
|
61
33
|
);
|
|
62
34
|
}
|
|
63
35
|
|
|
64
|
-
/** Remove o BOM UTF-8 do início do texto (mesmo cuidado de `doctor.js` e `env.js`). */
|
|
65
36
|
function stripBom(text) {
|
|
66
37
|
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
67
38
|
}
|
|
68
39
|
|
|
69
|
-
/**
|
|
70
|
-
* Checagem de boa-formação de XML suficiente para MSBuild, sem dependência nova.
|
|
71
|
-
*
|
|
72
|
-
* Não valida schema: valida que as tags fecham na ordem certa e que todo `<`
|
|
73
|
-
* tem o seu `>`. É o predicado que separa "li o arquivo" de "não pude
|
|
74
|
-
* perguntar" — e é ele que faz um `.csproj` truncado virar `unreadable` em vez
|
|
75
|
-
* de virar "sem drift".
|
|
76
|
-
*
|
|
77
|
-
* @param {string} xml
|
|
78
|
-
* @returns {boolean}
|
|
79
|
-
*/
|
|
40
|
+
/** Tags fecham na ordem e todo `<` tem `>`: um `.csproj` truncado vira `unreadable`, não "sem drift". */
|
|
80
41
|
export function isWellFormedXml(xml) {
|
|
81
42
|
const stack = [];
|
|
82
43
|
let i = 0;
|
|
@@ -121,7 +82,6 @@ export function isWellFormedXml(xml) {
|
|
|
121
82
|
return stack.length === 0;
|
|
122
83
|
}
|
|
123
84
|
|
|
124
|
-
/** Todas as tags de um nome, com os atributos crus. */
|
|
125
85
|
function* elements(xml, tagName) {
|
|
126
86
|
const re = new RegExp(`<${tagName}\\b([^>]*?)(/?)>`, 'g');
|
|
127
87
|
let m;
|
|
@@ -130,17 +90,13 @@ function* elements(xml, tagName) {
|
|
|
130
90
|
}
|
|
131
91
|
}
|
|
132
92
|
|
|
133
|
-
/** Valor de um atributo dentro do texto cru de atributos de uma tag. */
|
|
134
93
|
function attr(attrs, name) {
|
|
135
94
|
const m = new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`, 'i').exec(attrs)
|
|
136
95
|
|| new RegExp(`\\b${name}\\s*=\\s*'([^']*)'`, 'i').exec(attrs);
|
|
137
96
|
return m ? m[1].trim() : null;
|
|
138
97
|
}
|
|
139
98
|
|
|
140
|
-
|
|
141
|
-
* Elemento filho `<Version>x</Version>` de um `<PackageReference>` não
|
|
142
|
-
* auto-fechado. Recebe o XML e o índice logo após a tag de abertura.
|
|
143
|
-
*/
|
|
99
|
+
// Recebe o índice logo após a tag de abertura de um `<PackageReference>` não auto-fechado.
|
|
144
100
|
function childVersion(xml, startIndex, tagName) {
|
|
145
101
|
const close = xml.indexOf(`</${tagName}>`, startIndex);
|
|
146
102
|
if (close === -1) return null;
|
|
@@ -149,18 +105,7 @@ function childVersion(xml, startIndex, tagName) {
|
|
|
149
105
|
return m ? m[1].trim() : null;
|
|
150
106
|
}
|
|
151
107
|
|
|
152
|
-
/**
|
|
153
|
-
* Classifica a string de versão declarada no projeto.
|
|
154
|
-
*
|
|
155
|
-
* - `fixed` — `1.20.0` ou a forma de igualdade exata `[1.20.0]`
|
|
156
|
-
* - `floating` — contém `*`, ou é intervalo (`[1.0,)`, `[1.0,2.0)`, `(,2.0]`)
|
|
157
|
-
* - `unresolved` — referência a propriedade MSBuild (`$(MafVersion)`): a
|
|
158
|
-
* varredura NÃO resolve propriedades, e chutar seria inventar o dado
|
|
159
|
-
* - `absent` — sem versão declarada e sem entrada de CPM
|
|
160
|
-
*
|
|
161
|
-
* @param {string|null} raw
|
|
162
|
-
* @returns {{ kind: 'fixed'|'floating'|'unresolved'|'absent', value: string|null }}
|
|
163
|
-
*/
|
|
108
|
+
/** `unresolved` é referência a propriedade MSBuild (`$(X)`): a varredura não resolve, e chutar inventaria o dado. */
|
|
164
109
|
export function classifyVersion(raw) {
|
|
165
110
|
if (raw === null || raw === undefined || String(raw).trim() === '') {
|
|
166
111
|
return { kind: 'absent', value: null };
|
|
@@ -175,18 +120,7 @@ export function classifyVersion(raw) {
|
|
|
175
120
|
return { kind: 'fixed', value };
|
|
176
121
|
}
|
|
177
122
|
|
|
178
|
-
/**
|
|
179
|
-
* Coleta recursiva de arquivos que casam com um predicado, respeitando as
|
|
180
|
-
* exclusões de `SCAN_IGNORED_DIRS`.
|
|
181
|
-
*
|
|
182
|
-
* Exportada porque `registry-audit.js` e `code-audit.js` precisam da MESMA
|
|
183
|
-
* travessia com as MESMAS exclusões. Uma segunda cópia da lista de diretórios
|
|
184
|
-
* ignorados é como um dos três volta a varrer `worktrees/` sozinho.
|
|
185
|
-
*
|
|
186
|
-
* @param {string} root
|
|
187
|
-
* @param {(name: string, fullPath: string) => boolean} matches
|
|
188
|
-
* @returns {string[]}
|
|
189
|
-
*/
|
|
123
|
+
/** Exportada para que registry-audit e code-audit usem a mesma travessia com as mesmas exclusões. */
|
|
190
124
|
export function collectProjectFiles(root, matches) {
|
|
191
125
|
const out = [];
|
|
192
126
|
if (!root || !existsSync(root)) return out;
|
|
@@ -194,7 +128,6 @@ export function collectProjectFiles(root, matches) {
|
|
|
194
128
|
return out;
|
|
195
129
|
}
|
|
196
130
|
|
|
197
|
-
/** Coleta recursiva de arquivos que casam com um predicado, respeitando as exclusões. */
|
|
198
131
|
function collectFiles(root, matches, out, depth = 0) {
|
|
199
132
|
if (depth > 24) return;
|
|
200
133
|
let entries;
|
|
@@ -208,8 +141,7 @@ function collectFiles(root, matches, out, depth = 0) {
|
|
|
208
141
|
const full = join(root, name);
|
|
209
142
|
let isDir = entry.isDirectory();
|
|
210
143
|
if (entry.isSymbolicLink()) {
|
|
211
|
-
// Junction/symlink
|
|
212
|
-
// worktree do Windows enxerga `.claude/`). Não descemos.
|
|
144
|
+
// Junction/symlink pode apontar para fora da árvore (assim um worktree do Windows enxerga `.claude/`).
|
|
213
145
|
continue;
|
|
214
146
|
}
|
|
215
147
|
if (isDir) {
|
|
@@ -221,23 +153,8 @@ function collectFiles(root, matches, out, depth = 0) {
|
|
|
221
153
|
}
|
|
222
154
|
}
|
|
223
155
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
*
|
|
227
|
-
* Sem limite de 10 arquivos (o de `scanStackDependencies`, que é do relatório
|
|
228
|
-
* de arquitetura e tem outro consumidor): uma solução .NET real tem dezenas de
|
|
229
|
-
* `.csproj`, e cortar em 10 é reportar sobre uma amostra chamando-a de todo.
|
|
230
|
-
*
|
|
231
|
-
* @param {string} root - raiz da varredura (o projeto)
|
|
232
|
-
* @returns {null | {
|
|
233
|
-
* projectFiles: string[],
|
|
234
|
-
* cpmFiles: string[],
|
|
235
|
-
* unreadable: string[],
|
|
236
|
-
* packages: Array<{ name: string, file: string, ai: boolean,
|
|
237
|
-
* version: string|null, versionKind: string,
|
|
238
|
-
* declared: string|null, fromCpm: boolean }>
|
|
239
|
-
* }} `null` = não pude perguntar (havia candidatos e nenhum era legível).
|
|
240
|
-
*/
|
|
156
|
+
// Sem limite de arquivos: cortar uma solução real é reportar sobre uma amostra chamando-a de todo.
|
|
157
|
+
// `null` = havia candidatos e nenhum era legível.
|
|
241
158
|
export function scanAiPackageRefs(root) {
|
|
242
159
|
if (!root || !existsSync(root)) return null;
|
|
243
160
|
try {
|
|
@@ -280,7 +197,7 @@ export function scanAiPackageRefs(root) {
|
|
|
280
197
|
}
|
|
281
198
|
|
|
282
199
|
// Central Package Management primeiro: um PackageReference sem Version num
|
|
283
|
-
// projeto com CPM não é achado de versão ausente
|
|
200
|
+
// projeto com CPM não é achado de versão ausente.
|
|
284
201
|
const cpm = new Map();
|
|
285
202
|
for (const file of cpmFiles) {
|
|
286
203
|
const xml = readable.get(file);
|
|
@@ -319,28 +236,17 @@ export function scanAiPackageRefs(root) {
|
|
|
319
236
|
}
|
|
320
237
|
}
|
|
321
238
|
|
|
322
|
-
//
|
|
323
|
-
// .csproj que o referencie ainda declara a intenção — mas não é uso, e este
|
|
324
|
-
// módulo reporta declaração de uso. Fica de fora, de propósito.
|
|
239
|
+
// PackageVersion de IA sem .csproj que o referencie declara intenção, não uso: fica de fora.
|
|
325
240
|
|
|
326
241
|
return { projectFiles, cpmFiles, unreadable, packages };
|
|
327
242
|
}
|
|
328
243
|
|
|
329
|
-
/**
|
|
330
|
-
* Versão declarada de um pacote específico, ou `null` quando o projeto não a
|
|
331
|
-
* declara de forma resolvível. Usado pelo gate do `removed-api` (só roda com
|
|
332
|
-
* `Microsoft.Agents.AI` >= 1.0) e pelo relatório.
|
|
333
|
-
*
|
|
334
|
-
* @param {ReturnType<typeof scanAiPackageRefs>} scan
|
|
335
|
-
* @param {string} name
|
|
336
|
-
* @returns {{ name: string, version: string|null, versionKind: string, declared: string|null, file: string } | null}
|
|
337
|
-
*/
|
|
244
|
+
/** `null` quando o projeto não declara o pacote de forma resolvível. */
|
|
338
245
|
export function findPackage(scan, name) {
|
|
339
246
|
if (!scan || !Array.isArray(scan.packages)) return null;
|
|
340
247
|
return scan.packages.find((p) => p.name === name) ?? null;
|
|
341
248
|
}
|
|
342
249
|
|
|
343
|
-
/** Nomes de arquivo (sem diretório) dos projetos varridos — para mensagens. */
|
|
344
250
|
export function projectFileNames(scan) {
|
|
345
251
|
if (!scan) return [];
|
|
346
252
|
return scan.projectFiles.map((f) => basename(f));
|
package/src/lib/ai/code-audit.js
CHANGED
|
@@ -1,26 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* (a) `removed-api` — nomes renomeados ANTES do 1.0 GA do `Microsoft.Agents.AI`
|
|
5
|
-
* (b) `prompt-source` — `Instructions` recebendo literal de string no ponto de
|
|
6
|
-
* construção do agente
|
|
7
|
-
*
|
|
8
|
-
* Nenhuma das duas é grep cego, e as duas são `warn`:
|
|
9
|
-
*
|
|
10
|
-
* - A #93 apurou, e o standard `ai-agents/agent-session.md` registra, que
|
|
11
|
-
* `AgentThread` → `AgentSession` e `GetNewThread()` → `CreateSessionAsync()`
|
|
12
|
-
* aconteceram no ciclo PRÉ-GA. Dentro do `Microsoft.Agents.AI` >= 1.0 os
|
|
13
|
-
* nomes antigos são nomes mortos — mas o MESMO identificador pertence a
|
|
14
|
-
* outros SDKs de agente na mesma solução, e é por isso que o achado é aviso
|
|
15
|
-
* e a mensagem manda CONFERIR a qual cliente a chamada pertence, em vez de
|
|
16
|
-
* afirmar defeito. Sem o pacote declarado em >= 1.0 a checagem nem roda.
|
|
17
|
-
*
|
|
18
|
-
* - O `prompt-source` é deliberadamente conservador: acusa só o literal no
|
|
19
|
-
* ponto de construção. Leitura de arquivo, banco, configuração ou constante
|
|
20
|
-
* nomeada não é acusada. Falso negativo é aceitável; falso positivo não —
|
|
21
|
-
* uma checagem que grita em código correto é desligada pelo usuário na
|
|
22
|
-
* primeira semana.
|
|
23
|
-
*/
|
|
1
|
+
// As duas checagens são `warn`: o nome renomeado no ciclo pré-GA é o mesmo identificador de outros SDKs
|
|
2
|
+
// de agente, então a mensagem manda conferir. `prompt-source` acusa só o literal no ponto de construção:
|
|
3
|
+
// falso negativo é aceitável, falso positivo faz o usuário desligar a checagem.
|
|
24
4
|
|
|
25
5
|
import { readFileSync } from 'node:fs';
|
|
26
6
|
import { relative } from 'node:path';
|
|
@@ -40,22 +20,11 @@ function lineOf(text, index) {
|
|
|
40
20
|
return line;
|
|
41
21
|
}
|
|
42
22
|
|
|
43
|
-
/** Todos os `.cs` do projeto, com as mesmas exclusões da varredura de `.csproj`. */
|
|
44
23
|
export function collectCsharpFiles(root) {
|
|
45
24
|
return collectProjectFiles(root, (name) => name.toLowerCase().endsWith('.cs'));
|
|
46
25
|
}
|
|
47
26
|
|
|
48
|
-
/**
|
|
49
|
-
* Menor major que a versão declarada pode assumir, ou `null` quando a
|
|
50
|
-
* declaração não permite decidir.
|
|
51
|
-
*
|
|
52
|
-
* `1.0.*` e `[1.0,)` têm limite inferior 1 — sobem, nunca descem. `$(Prop)` e
|
|
53
|
-
* versão ausente NÃO permitem decidir, e decidir mesmo assim seria inventar o
|
|
54
|
-
* dado que a checagem reporta.
|
|
55
|
-
*
|
|
56
|
-
* @param {{ declared: string|null, versionKind: string }|null} pkg
|
|
57
|
-
* @returns {number|null}
|
|
58
|
-
*/
|
|
27
|
+
/** `null` quando a declaração não permite decidir (`$(Prop)`, ausente): decidir inventaria o dado. */
|
|
59
28
|
export function lowerBoundMajor(pkg) {
|
|
60
29
|
if (!pkg) return null;
|
|
61
30
|
if (pkg.versionKind === 'unresolved' || pkg.versionKind === 'absent') return null;
|
|
@@ -66,12 +35,7 @@ export function lowerBoundMajor(pkg) {
|
|
|
66
35
|
return m ? Number(m[1]) : null;
|
|
67
36
|
}
|
|
68
37
|
|
|
69
|
-
/**
|
|
70
|
-
* Por que o `removed-api` NÃO roda, quando não roda.
|
|
71
|
-
*
|
|
72
|
-
* @param {{ declared: string|null, versionKind: string }|null} mafPackage
|
|
73
|
-
* @returns {string|null} razão do "não verificado", ou `null` quando pode rodar
|
|
74
|
-
*/
|
|
38
|
+
/** A razão do "não verificado", ou `null` quando pode rodar. */
|
|
75
39
|
export function renamedApiGate(mafPackage) {
|
|
76
40
|
if (!mafPackage) {
|
|
77
41
|
return 'o projeto não declara `Microsoft.Agents.AI` — os nomes antigos não pertencem a este código';
|
|
@@ -88,14 +52,7 @@ export function renamedApiGate(mafPackage) {
|
|
|
88
52
|
return null;
|
|
89
53
|
}
|
|
90
54
|
|
|
91
|
-
/**
|
|
92
|
-
* (a) Uso de API renomeada antes do 1.0 GA.
|
|
93
|
-
*
|
|
94
|
-
* @param {string} root
|
|
95
|
-
* @param {{ mafPackage?: object|null }} [options]
|
|
96
|
-
* @returns {null | Array<{ id: string, severity: 'warn', symbol: string, file: string, line: number, message: string }>}
|
|
97
|
-
* `null` = não pude perguntar (gate fechado ou raiz ilegível).
|
|
98
|
-
*/
|
|
55
|
+
/** `null` = não pude perguntar (gate fechado ou raiz ilegível). */
|
|
99
56
|
export function auditRenamedApi(root, { mafPackage = null } = {}) {
|
|
100
57
|
if (renamedApiGate(mafPackage) !== null) return null;
|
|
101
58
|
|
|
@@ -131,22 +88,9 @@ export function auditRenamedApi(root, { mafPackage = null } = {}) {
|
|
|
131
88
|
return findings;
|
|
132
89
|
}
|
|
133
90
|
|
|
134
|
-
|
|
135
|
-
* `Instructions` recebendo literal de string no ponto de construção.
|
|
136
|
-
*
|
|
137
|
-
* Casa `Instructions = "…"`, `Instructions = @"…"`, `Instructions = $"…"`,
|
|
138
|
-
* `Instructions = """…"""` e o argumento nomeado `instructions: "…"`.
|
|
139
|
-
* NÃO casa atribuição de variável, chamada, `await`, constante nomeada nem
|
|
140
|
-
* literal dentro de comentário.
|
|
141
|
-
*/
|
|
91
|
+
// NÃO casa atribuição de variável, chamada, `await`, constante nomeada nem literal dentro de comentário.
|
|
142
92
|
const INSTRUCTIONS_LITERAL_RE = /(?<![\w."])(?:Instructions\s*=(?!=)|instructions\s*:)\s*(?:[$@]){0,2}"/g;
|
|
143
93
|
|
|
144
|
-
/**
|
|
145
|
-
* (b) Prompt sem fonte declarada.
|
|
146
|
-
*
|
|
147
|
-
* @param {string} root
|
|
148
|
-
* @returns {null | Array<{ id: string, severity: 'warn', file: string, line: number, message: string }>}
|
|
149
|
-
*/
|
|
150
94
|
export function auditPromptSource(root) {
|
|
151
95
|
const files = collectCsharpFiles(root);
|
|
152
96
|
if (files.length === 0) return null;
|
package/src/lib/ai/pin-drift.js
CHANGED
|
@@ -1,70 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* o `.csproj` do projeto declara.
|
|
4
|
-
*
|
|
5
|
-
* É a ÚNICA das cinco checagens do `doctor --ai` que produz `error`, e só na
|
|
6
|
-
* faixa flutuante. O motivo é prejuízo medido, não estética: uma faixa `2.*`
|
|
7
|
-
* sobe sozinha para uma versão com assinatura mudada e o `MissingMethodException`
|
|
8
|
-
* chega DEPOIS de a chamada ao modelo ter sido paga. As outras quatro checagens
|
|
9
|
-
* informam — uma checagem que grita em código correto é desligada pelo usuário
|
|
10
|
-
* na primeira semana.
|
|
11
|
-
*
|
|
12
|
-
* O pin é lido por `readAiPin` (#92), nunca por um segundo leitor: duas cópias
|
|
13
|
-
* do mesmo fato divergem, e é para isso que a Dependency Matrix existe.
|
|
14
|
-
*/
|
|
1
|
+
// Única checagem que produz `error`, e só na faixa flutuante: `2.*` sobe sozinha para uma assinatura mudada
|
|
2
|
+
// e o `MissingMethodException` chega depois de a chamada ao modelo ter sido paga.
|
|
15
3
|
|
|
16
4
|
import { join } from 'node:path';
|
|
17
5
|
import { readAiPin } from '../standards/verified-against.js';
|
|
18
6
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*
|
|
22
|
-
* Ordem: (1) `<target>/.morph/framework/ai-pin.json` — a régua com que aquele
|
|
23
|
-
* projeto foi instalado; (2) o pin do pacote da CLI. Nenhum dos dois resolve →
|
|
24
|
-
* `null`, que o relatório traduz em "não verificado". Falta de régua é ausência
|
|
25
|
-
* de dado, nunca defeito do projeto: é literalmente o estado de todo projeto
|
|
26
|
-
* cujo framework é anterior ao pin.
|
|
27
|
-
*
|
|
28
|
-
* @param {string} targetPath - raiz do projeto
|
|
29
|
-
* @returns {{ pinnedAt: string, packages: Record<string, {version: string}> } | null}
|
|
30
|
-
*/
|
|
7
|
+
// O pin instalado no projeto, senão o do pacote da CLI; nenhum resolve → `null` ("não verificado"):
|
|
8
|
+
// falta de régua é ausência de dado, nunca defeito do projeto.
|
|
31
9
|
export function resolveAiPin(targetPath) {
|
|
32
10
|
const roots = [];
|
|
33
11
|
if (targetPath) roots.push(join(targetPath, '.morph'));
|
|
34
|
-
roots.push(undefined);
|
|
12
|
+
roots.push(undefined);
|
|
35
13
|
|
|
36
14
|
for (const root of roots) {
|
|
37
15
|
try {
|
|
38
16
|
const pin = root === undefined ? readAiPin() : readAiPin(root);
|
|
39
17
|
if (pin && pin.packages && Object.keys(pin.packages).length > 0) return pin;
|
|
40
|
-
} catch {
|
|
41
|
-
// Ausente ou inválido nesta raiz — tenta a próxima.
|
|
42
|
-
}
|
|
18
|
+
} catch {}
|
|
43
19
|
}
|
|
44
20
|
return null;
|
|
45
21
|
}
|
|
46
22
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*
|
|
50
|
-
* Três classes de achado, e a diferença entre elas é de quem é o defeito:
|
|
51
|
-
*
|
|
52
|
-
* | classe | severidade | de quem é |
|
|
53
|
-
* |---|---|---|
|
|
54
|
-
* | pacote do pin declarado com faixa flutuante | `error` | do projeto |
|
|
55
|
-
* | pacote do pin declarado com versão fixa ≠ pin | `warn` | do projeto |
|
|
56
|
-
* | pacote de IA presente no projeto e ausente do pin | `warn` | **do pin** (lacuna) |
|
|
57
|
-
*
|
|
58
|
-
* A terceira classe é descrita como lacuna do PIN de propósito: medido,
|
|
59
|
-
* `Microsoft.Agents.AI.OpenAI` aparece em três repositórios do acervo e não
|
|
60
|
-
* está no pin. Acusar o projeto ali seria acusar o certo pelo errado.
|
|
61
|
-
*
|
|
62
|
-
* @param {object} params
|
|
63
|
-
* @param {ReturnType<import('./ai-project-scan.js').scanAiPackageRefs>} params.scan
|
|
64
|
-
* @param {ReturnType<typeof resolveAiPin>} params.pin
|
|
65
|
-
* @returns {null | Array<{ id: string, severity: 'error'|'warn', package: string, message: string }>}
|
|
66
|
-
* `null` = não pude perguntar (sem varredura ou sem pin).
|
|
67
|
-
*/
|
|
23
|
+
// Pacote de IA presente no projeto e ausente do pin é lacuna do PIN, não do projeto.
|
|
24
|
+
// `null` = não pude perguntar (sem varredura ou sem pin).
|
|
68
25
|
export function auditPinDrift({ scan, pin } = {}) {
|
|
69
26
|
if (!scan || !pin || !pin.packages) return null;
|
|
70
27
|
|
|
@@ -1,22 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* protocolo (`api`) por alias.
|
|
4
|
-
*
|
|
5
|
-
* As duas perguntas saem do standard `ai-agents/providers/model-registry.md`,
|
|
6
|
-
* não da memória de quem implementa:
|
|
7
|
-
*
|
|
8
|
-
* - **tarifa por alias** — um alias sem tarifa é um custo que ninguém
|
|
9
|
-
* consegue prever antes de a chamada ser paga. O caso medido no acervo é o
|
|
10
|
-
* GHLBrain, cujo mapa `Pricing.Models` é literalmente `{}`.
|
|
11
|
-
* - **`api` por alias** — o standard mede, sobre o pin, que
|
|
12
|
-
* `AgentApi.Responses = 0`, ou seja `default(AgentApi)` é Responses, e que
|
|
13
|
-
* `ModelRegistry.ReadApi` devolve `AgentApi.Responses` quando o campo está
|
|
14
|
-
* ausente. A ausência do campo, portanto, NÃO é defeito: é uma decisão
|
|
15
|
-
* tomada por omissão. O achado NOMEIA o default assumido e pede
|
|
16
|
-
* confirmação — nunca afirma defeito.
|
|
17
|
-
*
|
|
18
|
-
* Severidade: `warn` nas duas. Só `pin-drift` produz `error`.
|
|
19
|
-
*/
|
|
1
|
+
// Ausência de `api` não é defeito: o default medido sobre o pin é Responses, então o achado nomeia o
|
|
2
|
+
// default e pede confirmação. Severidade `warn`; só `pin-drift` produz `error`.
|
|
20
3
|
|
|
21
4
|
import { readFileSync } from 'node:fs';
|
|
22
5
|
import { existsSync } from 'node:fs';
|
|
@@ -24,22 +7,10 @@ import { isAbsolute, join, basename } from 'node:path';
|
|
|
24
7
|
import { collectProjectFiles } from './ai-project-scan.js';
|
|
25
8
|
import { jsoncToJson } from '../../../framework/hooks/shared/comment-stripper.js';
|
|
26
9
|
|
|
27
|
-
/** Limite de tamanho para um JSON candidato (bytes de texto) — evita ler dumps. */
|
|
28
10
|
const MAX_JSON_CHARS = 512 * 1024;
|
|
29
11
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*
|
|
33
|
-
* Não é indulgência: é paridade com o leitor de produção. O exemplar canônico
|
|
34
|
-
* `framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json`
|
|
35
|
-
* — que diz na primeira linha "copie para `config/model-registry.json` no
|
|
36
|
-
* projeto" — tem 20 linhas de `//`, e `ModelRegistry.FromJson` do kit declara
|
|
37
|
-
* `CommentHandling = JsonCommentHandling.Skip` + `AllowTrailingCommas = true`.
|
|
38
|
-
* Com `JSON.parse` cru, as duas checagens de registry saíam `skip` sobre a
|
|
39
|
-
* ÚNICA forma que existem para auditar — e `skip` não muda o código de saída,
|
|
40
|
-
* então o silêncio era total. A auditoria tem de enxergar a mesma forma que o
|
|
41
|
-
* leitor de produção enxerga.
|
|
42
|
-
*/
|
|
12
|
+
// JSONC por paridade com o leitor de produção (`ModelRegistry.FromJson` aceita comentário e vírgula
|
|
13
|
+
// sobrando): com `JSON.parse` cru o exemplar canônico saía `skip`, e `skip` não muda o código de saída.
|
|
43
14
|
function readJsonSafe(file) {
|
|
44
15
|
try {
|
|
45
16
|
const raw = readFileSync(file, 'utf8');
|
|
@@ -67,7 +38,7 @@ function asAliasSource(file, data) {
|
|
|
67
38
|
return { file, aliases: data.aliases, defaultAlias: data.defaultAlias, data };
|
|
68
39
|
}
|
|
69
40
|
|
|
70
|
-
/** A
|
|
41
|
+
/** A seção `Pricing` de um `appsettings*.json`. */
|
|
71
42
|
function asPricingSource(file, data) {
|
|
72
43
|
if (!isPlainObject(data)) return null;
|
|
73
44
|
const pricing = data.Pricing ?? data.pricing;
|
|
@@ -77,19 +48,8 @@ function asPricingSource(file, data) {
|
|
|
77
48
|
return { file, models };
|
|
78
49
|
}
|
|
79
50
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
*
|
|
83
|
-
* Ordem, primeira ocorrência ganha (spec §3.5):
|
|
84
|
-
* 1. path declarado em `.morph/config/config.json → project.ai.modelRegistry`;
|
|
85
|
-
* 2. um `*.json` cujo objeto raiz tenha `defaultAlias` **e** um mapa `aliases`;
|
|
86
|
-
* 3. a seção `Pricing` de um `appsettings*.json`.
|
|
87
|
-
*
|
|
88
|
-
* @param {string} root - raiz do projeto
|
|
89
|
-
* @param {object|null} config - conteúdo de `.morph/config/config.json`, se houver
|
|
90
|
-
* @returns {null | { aliasSource: object|null, pricingSource: object|null }}
|
|
91
|
-
* `null` quando NENHUMA das três formas foi encontrada.
|
|
92
|
-
*/
|
|
51
|
+
// Primeira forma ganha: o path de `project.ai.modelRegistry`, um `*.json` com `defaultAlias` e `aliases`,
|
|
52
|
+
// ou a seção `Pricing` de um `appsettings*.json`. `null` sem nenhuma.
|
|
93
53
|
export function locateAiRegistry(root, config = null) {
|
|
94
54
|
let aliasSource = null;
|
|
95
55
|
let pricingSource = null;
|
|
@@ -132,7 +92,6 @@ export function locateAiRegistry(root, config = null) {
|
|
|
132
92
|
return { aliasSource, pricingSource };
|
|
133
93
|
}
|
|
134
94
|
|
|
135
|
-
/** Mapa de tarifas embutido no próprio registry de modelos, quando existir. */
|
|
136
95
|
function extractPricingFromRegistry(file, data) {
|
|
137
96
|
if (!isPlainObject(data)) return null;
|
|
138
97
|
const pricing = data.pricing ?? data.Pricing;
|
|
@@ -153,40 +112,14 @@ function aliasHasWaiver(def) {
|
|
|
153
112
|
return isPlainObject(def) && typeof def.pricingWaiver === 'string' && def.pricingWaiver.trim() !== '';
|
|
154
113
|
}
|
|
155
114
|
|
|
156
|
-
|
|
157
|
-
* O documento usa a forma **por alias** (`pricing` / `pricingWaiver` dentro de
|
|
158
|
-
* cada alias) em vez da forma de mapa no topo?
|
|
159
|
-
*
|
|
160
|
-
* Basta UM alias declarar qualquer uma das duas: um documento meio-preenchido
|
|
161
|
-
* ainda é um documento na forma v2, e os aliases que faltam é que são o achado.
|
|
162
|
-
*/
|
|
115
|
+
// Basta um alias declarar `pricing` ou `pricingWaiver`: documento meio-preenchido ainda é forma por alias.
|
|
163
116
|
export function usesPerAliasPricing(aliasSource) {
|
|
164
117
|
if (!aliasSource) return false;
|
|
165
118
|
return Object.values(aliasSource.aliases).some((d) => aliasHasPricing(d) || aliasHasWaiver(d));
|
|
166
119
|
}
|
|
167
120
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
*
|
|
171
|
-
* Duas formas, e a checagem cobra a que o documento usa:
|
|
172
|
-
*
|
|
173
|
-
* 1. **Por alias** (`model-registry.v2.json`, o exemplar que o framework manda
|
|
174
|
-
* copiar) — a REGRA DURA escrita no próprio exemplar e imposta por
|
|
175
|
-
* `ModelRegistry.ReadPricing`: *"todo alias declara `pricing` OU
|
|
176
|
-
* `pricingWaiver` — nunca os dois, nunca nenhum"*. Um `pricingWaiver` é uma
|
|
177
|
-
* tarifa **decidida**: a ausência está escrita, não esquecida, e acusá-la
|
|
178
|
-
* seria gritar em código correto.
|
|
179
|
-
* 2. **Mapa no topo** (`pricing` na raiz, ou a seção `Pricing.Models` de um
|
|
180
|
-
* `appsettings*.json` — a forma medida no GHLBrain, cujo mapa é
|
|
181
|
-
* literalmente `{}`).
|
|
182
|
-
*
|
|
183
|
-
* Os achados de MAPA (`pricing-map-missing` / `pricing-map-empty`) só existem
|
|
184
|
-
* na forma 2: num documento v2 não há mapa a faltar, e emitir o achado ali era
|
|
185
|
-
* o primeiro dos seis falsos positivos sobre o exemplar canônico.
|
|
186
|
-
*
|
|
187
|
-
* @param {ReturnType<typeof locateAiRegistry>} registry
|
|
188
|
-
* @returns {null | Array<{ id: string, severity: 'warn', alias?: string, message: string }>}
|
|
189
|
-
*/
|
|
121
|
+
// Por alias, todo alias declara `pricing` OU `pricingWaiver`, nunca os dois, nunca nenhum (um waiver é
|
|
122
|
+
// tarifa decidida). Os achados de mapa só existem na forma de mapa no topo.
|
|
190
123
|
export function auditModelPricing(registry) {
|
|
191
124
|
if (!registry) return null;
|
|
192
125
|
const { aliasSource, pricingSource } = registry;
|
|
@@ -253,19 +186,7 @@ export function auditModelPricing(registry) {
|
|
|
253
186
|
return findings;
|
|
254
187
|
}
|
|
255
188
|
|
|
256
|
-
/**
|
|
257
|
-
* Todo alias declara `api`?
|
|
258
|
-
*
|
|
259
|
-
* O default do standard é **`responses`**: medido sobre o pin, `AgentApi.Responses = 0`
|
|
260
|
-
* e `ModelRegistry.ReadApi` devolve Responses quando o campo está ausente. A mensagem
|
|
261
|
-
* nomeia esse default e PEDE CONFIRMAÇÃO — a ausência do campo é uma decisão por
|
|
262
|
-
* omissão, não um defeito. É a pergunta que o `ModelRegistry.cs` do ProspectPRO
|
|
263
|
-
* documenta ter custado um bug.
|
|
264
|
-
*
|
|
265
|
-
* @param {ReturnType<typeof locateAiRegistry>} registry
|
|
266
|
-
* @returns {null | Array<{ id: string, severity: 'warn', alias: string, message: string }>}
|
|
267
|
-
* `null` quando não há aliases para perguntar.
|
|
268
|
-
*/
|
|
189
|
+
/** `null` quando não há aliases para perguntar. */
|
|
269
190
|
export function auditApiPerAlias(registry) {
|
|
270
191
|
if (!registry || !registry.aliasSource) return null;
|
|
271
192
|
const findings = [];
|
package/src/lib/ci/ci-verify.js
CHANGED
|
@@ -47,13 +47,8 @@ function dispatchWorkflow(root, workflow, branch) {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* since it by the gate's own staleness rule and nothing is uncommitted (treeMovedSince sees only
|
|
53
|
-
* commits). A null sha or HEAD would read as "not moved", so both are required.
|
|
54
|
-
*
|
|
55
|
-
* @returns {object|null} the stamp to reuse, or null = run local
|
|
56
|
-
*/
|
|
50
|
+
// The last green stamp still proves the tree if nothing moved since it and nothing is uncommitted
|
|
51
|
+
// (treeMovedSince sees only commits). A null sha or HEAD would read as "not moved", so both are required.
|
|
57
52
|
export function reusableLocalStamp(root, stamp) {
|
|
58
53
|
if (stamp?.status !== 'pass' || !stamp.sha) return null;
|
|
59
54
|
const head = getHeadSha(root);
|
|
@@ -63,18 +58,8 @@ export function reusableLocalStamp(root, stamp) {
|
|
|
63
58
|
return treeMovedSince(root, stamp.sha, head) ? null : stamp;
|
|
64
59
|
}
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
* Every way CI cannot answer is `mode: 'local'` with the reason, and the caller runs the local
|
|
69
|
-
* verify: no CI is never a pause. Only the uploaded `verify --json` becomes a result, so the
|
|
70
|
-
* gate judges a CI run by the same stamp and the same rules as a local one.
|
|
71
|
-
*
|
|
72
|
-
* @returns {Promise<
|
|
73
|
-
* { mode: 'local', reason: string } |
|
|
74
|
-
* { mode: 'pending', reason: string, run: object|null } |
|
|
75
|
-
* { mode: 'ci', result: object, run: object, created: string|null }>}
|
|
76
|
-
* @throws {CiUsageError} code CI would not see (uncommitted work)
|
|
77
|
-
*/
|
|
61
|
+
// Every way CI cannot answer is `mode: 'local'` with the reason: no CI is never a pause. Only the
|
|
62
|
+
// uploaded `verify --json` becomes a result, judged by the same stamp and rules as a local run.
|
|
78
63
|
export async function runCiVerify(root, featureName, config, { log = () => {}, waitMs = CI_WAIT_MS, wait = waitForRun } = {}) {
|
|
79
64
|
const avail = ciAvailability(root, featureName, config);
|
|
80
65
|
if (!avail.ok) return { mode: 'local', reason: avail.reason };
|