aios-core 4.2.14 → 4.3.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/.aios-core/cli/commands/validate/index.js +1 -1
- package/.aios-core/core/code-intel/helpers/creation-helper.js +183 -0
- package/.aios-core/core/code-intel/helpers/devops-helper.js +166 -0
- package/.aios-core/core/code-intel/helpers/planning-helper.js +248 -0
- package/.aios-core/core/code-intel/helpers/qa-helper.js +187 -0
- package/.aios-core/core/code-intel/helpers/story-helper.js +146 -0
- package/.aios-core/core/config/schemas/framework-config.schema.json +155 -7
- package/.aios-core/core/config/schemas/project-config.schema.json +329 -15
- package/.aios-core/core/config/template-overrides.js +84 -0
- package/.aios-core/core/docs/troubleshooting-guide.md +1 -1
- package/.aios-core/core/doctor/checks/agent-memory.js +63 -0
- package/.aios-core/core/doctor/checks/claude-md.js +56 -0
- package/.aios-core/core/doctor/checks/code-intel.js +57 -0
- package/.aios-core/core/doctor/checks/commands-count.js +81 -0
- package/.aios-core/core/doctor/checks/core-config.js +53 -0
- package/.aios-core/core/doctor/checks/entity-registry.js +53 -0
- package/.aios-core/core/doctor/checks/git-hooks.js +50 -0
- package/.aios-core/core/doctor/checks/graph-dashboard.js +48 -0
- package/.aios-core/core/doctor/checks/hooks-claude-count.js +107 -0
- package/.aios-core/core/doctor/checks/ide-sync.js +68 -0
- package/.aios-core/core/doctor/checks/index.js +46 -0
- package/.aios-core/core/doctor/checks/node-version.js +33 -0
- package/.aios-core/core/doctor/checks/npm-packages.js +35 -0
- package/.aios-core/core/doctor/checks/rules-files.js +61 -0
- package/.aios-core/core/doctor/checks/settings-json.js +121 -0
- package/.aios-core/core/doctor/checks/skills-count.js +72 -0
- package/.aios-core/core/doctor/fix-handler.js +165 -0
- package/.aios-core/core/doctor/formatters/json.js +14 -0
- package/.aios-core/core/doctor/formatters/text.js +59 -0
- package/.aios-core/core/doctor/index.js +94 -0
- package/.aios-core/core/graph-dashboard/cli.js +361 -0
- package/.aios-core/core/graph-dashboard/data-sources/code-intel-source.js +234 -0
- package/.aios-core/core/graph-dashboard/data-sources/metrics-source.js +95 -0
- package/.aios-core/core/graph-dashboard/data-sources/registry-source.js +106 -0
- package/.aios-core/core/graph-dashboard/formatters/dot-formatter.js +45 -0
- package/.aios-core/core/graph-dashboard/formatters/html-formatter.js +1437 -0
- package/.aios-core/core/graph-dashboard/formatters/json-formatter.js +13 -0
- package/.aios-core/core/graph-dashboard/formatters/mermaid-formatter.js +59 -0
- package/.aios-core/core/graph-dashboard/index.js +21 -0
- package/.aios-core/core/graph-dashboard/renderers/stats-renderer.js +217 -0
- package/.aios-core/core/graph-dashboard/renderers/status-renderer.js +125 -0
- package/.aios-core/core/graph-dashboard/renderers/tree-renderer.js +119 -0
- package/.aios-core/core/health-check/base-check.js +1 -1
- package/.aios-core/core/health-check/check-registry.js +1 -1
- package/.aios-core/core/health-check/checks/deployment/build-config.js +1 -1
- package/.aios-core/core/health-check/checks/deployment/ci-config.js +1 -1
- package/.aios-core/core/health-check/checks/deployment/deployment-readiness.js +1 -1
- package/.aios-core/core/health-check/checks/deployment/docker-config.js +1 -1
- package/.aios-core/core/health-check/checks/deployment/env-file.js +1 -1
- package/.aios-core/core/health-check/checks/deployment/index.js +1 -1
- package/.aios-core/core/health-check/checks/index.js +1 -1
- package/.aios-core/core/health-check/checks/local/disk-space.js +1 -1
- package/.aios-core/core/health-check/checks/local/environment-vars.js +1 -1
- package/.aios-core/core/health-check/checks/local/git-install.js +1 -1
- package/.aios-core/core/health-check/checks/local/ide-detection.js +1 -1
- package/.aios-core/core/health-check/checks/local/index.js +1 -1
- package/.aios-core/core/health-check/checks/local/memory.js +1 -1
- package/.aios-core/core/health-check/checks/local/network.js +1 -1
- package/.aios-core/core/health-check/checks/local/npm-install.js +1 -1
- package/.aios-core/core/health-check/checks/local/shell-environment.js +1 -1
- package/.aios-core/core/health-check/checks/project/agent-config.js +1 -1
- package/.aios-core/core/health-check/checks/project/aios-directory.js +1 -1
- package/.aios-core/core/health-check/checks/project/dependencies.js +1 -1
- package/.aios-core/core/health-check/checks/project/framework-config.js +1 -1
- package/.aios-core/core/health-check/checks/project/index.js +1 -1
- package/.aios-core/core/health-check/checks/project/node-version.js +1 -1
- package/.aios-core/core/health-check/checks/project/package-json.js +1 -1
- package/.aios-core/core/health-check/checks/project/task-definitions.js +1 -1
- package/.aios-core/core/health-check/checks/project/workflow-dependencies.js +1 -1
- package/.aios-core/core/health-check/checks/repository/branch-protection.js +1 -1
- package/.aios-core/core/health-check/checks/repository/commit-history.js +1 -1
- package/.aios-core/core/health-check/checks/repository/conflicts.js +1 -1
- package/.aios-core/core/health-check/checks/repository/git-repo.js +1 -1
- package/.aios-core/core/health-check/checks/repository/git-status.js +1 -1
- package/.aios-core/core/health-check/checks/repository/gitignore.js +1 -1
- package/.aios-core/core/health-check/checks/repository/index.js +1 -1
- package/.aios-core/core/health-check/checks/repository/large-files.js +1 -1
- package/.aios-core/core/health-check/checks/repository/lockfile-integrity.js +1 -1
- package/.aios-core/core/health-check/checks/services/api-endpoints.js +1 -1
- package/.aios-core/core/health-check/checks/services/claude-code.js +1 -1
- package/.aios-core/core/health-check/checks/services/gemini-cli.js +1 -1
- package/.aios-core/core/health-check/checks/services/github-cli.js +1 -1
- package/.aios-core/core/health-check/checks/services/index.js +1 -1
- package/.aios-core/core/health-check/checks/services/mcp-integration.js +1 -1
- package/.aios-core/core/health-check/engine.js +1 -1
- package/.aios-core/core/health-check/healers/backup-manager.js +1 -1
- package/.aios-core/core/health-check/healers/index.js +1 -1
- package/.aios-core/core/health-check/index.js +9 -2
- package/.aios-core/core/health-check/reporters/console.js +1 -1
- package/.aios-core/core/health-check/reporters/index.js +1 -1
- package/.aios-core/core/health-check/reporters/json.js +1 -1
- package/.aios-core/core/health-check/reporters/markdown.js +1 -1
- package/.aios-core/core/ids/layer-classifier.js +65 -0
- package/.aios-core/core/ids/registry-updater.js +49 -0
- package/.aios-core/core/index.esm.js +1 -1
- package/.aios-core/core/index.js +1 -1
- package/.aios-core/core/session/context-detector.js +2 -7
- package/.aios-core/core/synapse/context/context-tracker.js +9 -1
- package/.aios-core/core/synapse/engine.js +33 -13
- package/.aios-core/core/synapse/runtime/hook-runtime.js +40 -2
- package/.aios-core/core/synapse/session/session-manager.js +3 -2
- package/.aios-core/core/synapse/utils/atomic-write.js +79 -0
- package/.aios-core/core-config.yaml +34 -1
- package/.aios-core/data/aios-kb.md +2 -2
- package/.aios-core/data/capability-detection.js +290 -0
- package/.aios-core/data/entity-registry.yaml +10424 -2127
- package/.aios-core/data/mcp-discipline.js +166 -0
- package/.aios-core/data/mcp-tool-examples.yaml +215 -0
- package/.aios-core/data/tok2-validation.js +168 -0
- package/.aios-core/data/tok3-token-comparison.js +123 -0
- package/.aios-core/data/tool-registry.yaml +648 -0
- package/.aios-core/data/tool-search-validation.js +174 -0
- package/.aios-core/development/agents/analyst/MEMORY.md +33 -0
- package/.aios-core/development/agents/architect/MEMORY.md +39 -0
- package/.aios-core/development/agents/data-engineer/MEMORY.md +32 -0
- package/.aios-core/development/agents/dev/MEMORY.md +46 -0
- package/.aios-core/development/agents/dev.md +1 -1
- package/.aios-core/development/agents/devops/MEMORY.md +39 -0
- package/.aios-core/development/agents/devops.md +22 -0
- package/.aios-core/development/agents/pm/MEMORY.md +38 -0
- package/.aios-core/development/agents/po/MEMORY.md +45 -0
- package/.aios-core/development/agents/qa/MEMORY.md +42 -0
- package/.aios-core/development/agents/qa.md +1 -1
- package/.aios-core/development/agents/sm/MEMORY.md +31 -0
- package/.aios-core/development/agents/ux/MEMORY.md +31 -0
- package/.aios-core/development/checklists/issue-triage-checklist.md +35 -0
- package/.aios-core/development/checklists/memory-audit-checklist.md +53 -0
- package/.aios-core/development/scripts/issue-triage.js +171 -0
- package/.aios-core/development/scripts/populate-entity-registry.js +412 -19
- package/.aios-core/development/scripts/unified-activation-pipeline.js +31 -10
- package/.aios-core/development/tasks/analyze-project-structure.md +48 -0
- package/.aios-core/development/tasks/brownfield-create-epic.md +41 -0
- package/.aios-core/development/tasks/create-doc.md +44 -0
- package/.aios-core/development/tasks/create-next-story.md +10 -0
- package/.aios-core/development/tasks/dev-develop-story.md +1 -1
- package/.aios-core/development/tasks/github-devops-github-pr-automation.md +49 -0
- package/.aios-core/development/tasks/github-devops-pre-push-quality-gate.md +63 -0
- package/.aios-core/development/tasks/github-issue-triage.md +118 -0
- package/.aios-core/development/tasks/health-check.yaml +206 -171
- package/.aios-core/development/tasks/kb-mode-interaction.md +3 -3
- package/.aios-core/development/tasks/plan-create-context.md +47 -1
- package/.aios-core/development/tasks/plan-create-implementation.md +55 -0
- package/.aios-core/development/tasks/pr-automation.md +5 -5
- package/.aios-core/development/tasks/qa-gate.md +48 -0
- package/.aios-core/development/tasks/qa-review-story.md +24 -1
- package/.aios-core/development/tasks/resolve-github-issue.md +608 -0
- package/.aios-core/development/tasks/review-contributor-pr.md +152 -0
- package/.aios-core/development/tasks/setup-llm-routing.md +1 -1
- package/.aios-core/development/tasks/spec-research-dependencies.md +4 -0
- package/.aios-core/development/tasks/triage-github-issues.md +356 -0
- package/.aios-core/development/tasks/validate-agents.md +4 -0
- package/.aios-core/development/tasks/validate-next-story.md +10 -0
- package/.aios-core/development/templates/agent-handoff-tmpl.yaml +48 -0
- package/.aios-core/development/templates/code-intel-integration-pattern.md +199 -0
- package/.aios-core/development/templates/ptc-entity-validation.md +113 -0
- package/.aios-core/development/templates/ptc-qa-gate.md +100 -0
- package/.aios-core/development/templates/ptc-research-aggregation.md +94 -0
- package/.aios-core/development/templates/service-template/README.md.hbs +158 -158
- package/.aios-core/development/templates/service-template/__tests__/index.test.ts.hbs +237 -237
- package/.aios-core/development/templates/service-template/client.ts.hbs +403 -403
- package/.aios-core/development/templates/service-template/errors.ts.hbs +182 -182
- package/.aios-core/development/templates/service-template/index.ts.hbs +120 -120
- package/.aios-core/development/templates/service-template/package.json.hbs +87 -87
- package/.aios-core/development/templates/service-template/types.ts.hbs +145 -145
- package/.aios-core/development/templates/squad/agent-template.md +11 -0
- package/.aios-core/development/templates/squad/task-template.md +21 -0
- package/.aios-core/development/templates/squad-template/LICENSE +21 -21
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-COMPLETE.md +1 -1
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.2-SUMMARY.md +1 -1
- package/.aios-core/framework-config.yaml +8 -0
- package/.aios-core/index.esm.js +1 -1
- package/.aios-core/index.js +1 -1
- package/.aios-core/infrastructure/integrations/ai-providers/index.js +1 -1
- package/.aios-core/infrastructure/schemas/task-v3-schema.json +6 -0
- package/.aios-core/infrastructure/scripts/collect-tool-usage.js +311 -0
- package/.aios-core/infrastructure/scripts/generate-optimization-report.js +497 -0
- package/.aios-core/infrastructure/scripts/generate-settings-json.js +300 -0
- package/.aios-core/infrastructure/scripts/git-config-detector.js +65 -9
- package/.aios-core/infrastructure/scripts/ide-sync/index.js +3 -1
- package/.aios-core/infrastructure/scripts/ide-sync/transformers/github-copilot.js +184 -0
- package/.aios-core/infrastructure/scripts/repository-detector.js +3 -3
- package/.aios-core/infrastructure/templates/aios-sync.yaml.template +182 -182
- package/.aios-core/infrastructure/templates/coderabbit.yaml.template +279 -279
- package/.aios-core/infrastructure/templates/github-workflows/ci.yml.template +169 -169
- package/.aios-core/infrastructure/templates/github-workflows/pr-automation.yml.template +330 -330
- package/.aios-core/infrastructure/templates/github-workflows/release.yml.template +196 -196
- package/.aios-core/infrastructure/templates/gitignore/gitignore-aios-base.tmpl +63 -63
- package/.aios-core/infrastructure/templates/gitignore/gitignore-brownfield-merge.tmpl +18 -18
- package/.aios-core/infrastructure/templates/gitignore/gitignore-node.tmpl +85 -85
- package/.aios-core/infrastructure/templates/gitignore/gitignore-python.tmpl +145 -145
- package/.aios-core/install-manifest.yaml +541 -249
- package/.aios-core/lib/build.json +1 -0
- package/.aios-core/local-config.yaml.template +71 -71
- package/.aios-core/monitor/hooks/lib/__init__.py +1 -1
- package/.aios-core/monitor/hooks/lib/enrich.py +58 -58
- package/.aios-core/monitor/hooks/lib/send_event.py +47 -47
- package/.aios-core/monitor/hooks/notification.py +29 -29
- package/.aios-core/monitor/hooks/post_tool_use.py +45 -45
- package/.aios-core/monitor/hooks/pre_compact.py +29 -29
- package/.aios-core/monitor/hooks/pre_tool_use.py +40 -40
- package/.aios-core/monitor/hooks/stop.py +29 -29
- package/.aios-core/monitor/hooks/subagent_stop.py +29 -29
- package/.aios-core/monitor/hooks/user_prompt_submit.py +38 -38
- package/.aios-core/product/templates/adr.hbs +125 -125
- package/.aios-core/product/templates/dbdr.hbs +241 -241
- package/.aios-core/product/templates/epic.hbs +212 -212
- package/.aios-core/product/templates/ide-rules/claude-rules.md +77 -0
- package/.aios-core/product/templates/pmdr.hbs +186 -186
- package/.aios-core/product/templates/prd-v2.0.hbs +216 -216
- package/.aios-core/product/templates/prd.hbs +201 -201
- package/.aios-core/product/templates/story.hbs +263 -263
- package/.aios-core/product/templates/task.hbs +170 -170
- package/.aios-core/product/templates/tmpl-comment-on-examples.sql +158 -158
- package/.aios-core/product/templates/tmpl-migration-script.sql +91 -91
- package/.aios-core/product/templates/tmpl-rls-granular-policies.sql +104 -104
- package/.aios-core/product/templates/tmpl-rls-kiss-policy.sql +10 -10
- package/.aios-core/product/templates/tmpl-rls-roles.sql +135 -135
- package/.aios-core/product/templates/tmpl-rls-simple.sql +77 -77
- package/.aios-core/product/templates/tmpl-rls-tenant.sql +152 -152
- package/.aios-core/product/templates/tmpl-rollback-script.sql +77 -77
- package/.aios-core/product/templates/tmpl-seed-data.sql +140 -140
- package/.aios-core/product/templates/tmpl-smoke-test.sql +16 -16
- package/.aios-core/product/templates/tmpl-staging-copy-merge.sql +139 -139
- package/.aios-core/product/templates/tmpl-stored-proc.sql +140 -140
- package/.aios-core/product/templates/tmpl-trigger.sql +152 -152
- package/.aios-core/product/templates/tmpl-view-materialized.sql +133 -133
- package/.aios-core/product/templates/tmpl-view.sql +177 -177
- package/.aios-core/scripts/pm.sh +0 -0
- package/.aios-core/user-guide.md +15 -15
- package/.aios-core/utils/filters/constants.js +10 -0
- package/.aios-core/utils/filters/content-filter.js +223 -0
- package/.aios-core/utils/filters/field-filter.js +126 -0
- package/.aios-core/utils/filters/index.js +180 -0
- package/.aios-core/utils/filters/schema-filter.js +157 -0
- package/.claude/CLAUDE.md +62 -0
- package/.claude/hooks/enforce-architecture-first.py +196 -196
- package/.claude/hooks/enforce-git-push-authority.sh +33 -0
- package/.claude/hooks/mind-clone-governance.py +192 -192
- package/.claude/hooks/read-protection.py +151 -151
- package/.claude/hooks/slug-validation.py +176 -176
- package/.claude/hooks/sql-governance.py +182 -182
- package/.claude/hooks/synapse-engine.cjs +28 -5
- package/.claude/hooks/write-path-validation.py +194 -194
- package/.claude/rules/agent-authority.md +105 -0
- package/.claude/rules/agent-handoff.md +97 -0
- package/.claude/rules/agent-memory-imports.md +15 -0
- package/.claude/rules/coderabbit-integration.md +101 -0
- package/.claude/rules/ids-principles.md +119 -0
- package/.claude/rules/story-lifecycle.md +145 -0
- package/.claude/rules/tool-examples.md +64 -0
- package/.claude/rules/tool-response-filtering.md +57 -0
- package/.claude/rules/workflow-execution.md +150 -0
- package/LICENSE +33 -33
- package/bin/aios-graph.js +9 -0
- package/bin/aios-init.js +2 -2
- package/bin/aios-minimal.js +0 -0
- package/bin/aios.js +17 -221
- package/bin/utils/detect-fsmonitor.js +70 -0
- package/bin/utils/framework-guard.js +238 -0
- package/bin/utils/validate-publish.js +108 -0
- package/package.json +6 -3
- package/packages/aios-install/bin/aios-install.js +0 -0
- package/packages/aios-install/bin/edmcp.js +0 -0
- package/packages/aios-pro-cli/bin/aios-pro.js +2 -0
- package/packages/installer/src/installer/brownfield-upgrader.js +68 -5
- package/packages/installer/src/merger/index.js +3 -0
- package/packages/installer/src/merger/strategies/index.js +6 -0
- package/packages/installer/src/merger/strategies/yaml-merger.js +181 -0
- package/packages/installer/src/updater/index.js +4 -4
- package/packages/installer/src/wizard/i18n.js +321 -3
- package/packages/installer/src/wizard/ide-config-generator.js +152 -25
- package/packages/installer/src/wizard/index.js +119 -1
- package/packages/installer/src/wizard/pro-setup.js +137 -121
- package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +261 -0
- package/packages/installer/tests/unit/claude-md-template-v5/claude-md-template-v5.test.js +192 -0
- package/packages/installer/tests/unit/doctor/doctor-checks.test.js +551 -0
- package/packages/installer/tests/unit/doctor/doctor-orchestrator.test.js +134 -0
- package/packages/installer/tests/unit/entity-registry-bootstrap.test.js +186 -0
- package/packages/installer/tests/unit/generate-settings-json/generate-settings-json.test.js +309 -0
- package/packages/installer/tests/unit/ide-sync-integration/ide-sync-integration.test.js +230 -0
- package/packages/installer/tests/unit/merger/strategies.test.js +2 -2
- package/packages/installer/tests/unit/merger/yaml-merger.test.js +327 -0
- package/scripts/check-markdown-links.py +352 -352
- package/scripts/dashboard-parallel-dev.sh +0 -0
- package/scripts/dashboard-parallel-phase3.sh +0 -0
- package/scripts/dashboard-parallel-phase4.sh +0 -0
- package/scripts/install-monitor-hooks.sh +0 -0
- package/scripts/package-synapse.js +2 -1
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests: Doctor Check Modules
|
|
3
|
+
* Story INS-4.1: aios doctor rewrite
|
|
4
|
+
* Story INS-4.8: 3 new checks (skills-count, commands-count, hooks-claude-count)
|
|
5
|
+
*
|
|
6
|
+
* Tests all 15 check modules individually with mocked filesystem.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
|
|
12
|
+
// Mock fs for controlled test scenarios
|
|
13
|
+
jest.mock('fs');
|
|
14
|
+
|
|
15
|
+
const nodeVersionCheck = require('../../../../../.aios-core/core/doctor/checks/node-version');
|
|
16
|
+
const npmPackagesCheck = require('../../../../../.aios-core/core/doctor/checks/npm-packages');
|
|
17
|
+
const settingsJsonCheck = require('../../../../../.aios-core/core/doctor/checks/settings-json');
|
|
18
|
+
const rulesFilesCheck = require('../../../../../.aios-core/core/doctor/checks/rules-files');
|
|
19
|
+
const agentMemoryCheck = require('../../../../../.aios-core/core/doctor/checks/agent-memory');
|
|
20
|
+
const entityRegistryCheck = require('../../../../../.aios-core/core/doctor/checks/entity-registry');
|
|
21
|
+
const gitHooksCheck = require('../../../../../.aios-core/core/doctor/checks/git-hooks');
|
|
22
|
+
const coreConfigCheck = require('../../../../../.aios-core/core/doctor/checks/core-config');
|
|
23
|
+
const claudeMdCheck = require('../../../../../.aios-core/core/doctor/checks/claude-md');
|
|
24
|
+
const graphDashboardCheck = require('../../../../../.aios-core/core/doctor/checks/graph-dashboard');
|
|
25
|
+
const codeIntelCheck = require('../../../../../.aios-core/core/doctor/checks/code-intel');
|
|
26
|
+
const ideSyncCheck = require('../../../../../.aios-core/core/doctor/checks/ide-sync');
|
|
27
|
+
const skillsCountCheck = require('../../../../../.aios-core/core/doctor/checks/skills-count');
|
|
28
|
+
const commandsCountCheck = require('../../../../../.aios-core/core/doctor/checks/commands-count');
|
|
29
|
+
const hooksClaudeCountCheck = require('../../../../../.aios-core/core/doctor/checks/hooks-claude-count');
|
|
30
|
+
const { loadChecks } = require('../../../../../.aios-core/core/doctor/checks');
|
|
31
|
+
|
|
32
|
+
const mockContext = {
|
|
33
|
+
projectRoot: '/mock/project',
|
|
34
|
+
frameworkRoot: '/mock/framework',
|
|
35
|
+
options: { fix: false, json: false, dryRun: false, quiet: false },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
jest.clearAllMocks();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('node-version check', () => {
|
|
43
|
+
it('should PASS for current Node.js version (>=18)', async () => {
|
|
44
|
+
const result = await nodeVersionCheck.run(mockContext);
|
|
45
|
+
expect(result.check).toBe('node-version');
|
|
46
|
+
expect(result.status).toBe('PASS');
|
|
47
|
+
expect(result.message).toContain('Node.js');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('npm-packages check', () => {
|
|
52
|
+
it('should PASS when node_modules exists', async () => {
|
|
53
|
+
fs.existsSync.mockReturnValue(true);
|
|
54
|
+
const result = await npmPackagesCheck.run(mockContext);
|
|
55
|
+
expect(result.status).toBe('PASS');
|
|
56
|
+
expect(result.message).toBe('node_modules present');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should FAIL when node_modules missing', async () => {
|
|
60
|
+
fs.existsSync.mockReturnValue(false);
|
|
61
|
+
const result = await npmPackagesCheck.run(mockContext);
|
|
62
|
+
expect(result.status).toBe('FAIL');
|
|
63
|
+
expect(result.fixCommand).toBe('npm install');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('settings-json check', () => {
|
|
68
|
+
it('should PASS with valid settings and sufficient deny rules', async () => {
|
|
69
|
+
fs.existsSync.mockReturnValue(true);
|
|
70
|
+
const mockSettings = {
|
|
71
|
+
permissions: {
|
|
72
|
+
deny: new Array(50).fill('Edit(.aios-core/core/)'),
|
|
73
|
+
allow: ['Edit(docs/)'],
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
const coreConfig = 'boundary:\n protected:\n - .aios-core/core/**\n exceptions:\n - agents/MEMORY.md';
|
|
77
|
+
fs.readFileSync.mockImplementation((p) => {
|
|
78
|
+
if (p.includes('settings.json')) return JSON.stringify(mockSettings);
|
|
79
|
+
if (p.includes('core-config')) return coreConfig;
|
|
80
|
+
return '';
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const result = await settingsJsonCheck.run(mockContext);
|
|
84
|
+
expect(result.status).toBe('PASS');
|
|
85
|
+
expect(result.message).toContain('50 rules');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should FAIL when settings.json not found', async () => {
|
|
89
|
+
fs.existsSync.mockReturnValue(false);
|
|
90
|
+
const result = await settingsJsonCheck.run(mockContext);
|
|
91
|
+
expect(result.status).toBe('FAIL');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should WARN when deny rules below threshold', async () => {
|
|
95
|
+
fs.existsSync.mockReturnValue(true);
|
|
96
|
+
const mockSettings = { permissions: { deny: ['one'], allow: [] } };
|
|
97
|
+
fs.readFileSync.mockReturnValue(JSON.stringify(mockSettings));
|
|
98
|
+
|
|
99
|
+
const result = await settingsJsonCheck.run(mockContext);
|
|
100
|
+
expect(result.status).toBe('WARN');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should WARN when boundary paths not covered by deny rules', async () => {
|
|
104
|
+
fs.existsSync.mockReturnValue(true);
|
|
105
|
+
const mockSettings = {
|
|
106
|
+
permissions: {
|
|
107
|
+
deny: new Array(50).fill('Edit(docs/)'),
|
|
108
|
+
allow: [],
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
const coreConfig = 'boundary:\n protected:\n - .aios-core/core/**\n - bin/aios.js\n exceptions:\n - test';
|
|
112
|
+
fs.readFileSync.mockImplementation((p) => {
|
|
113
|
+
if (p.includes('settings.json')) return JSON.stringify(mockSettings);
|
|
114
|
+
if (p.includes('core-config')) return coreConfig;
|
|
115
|
+
return '';
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const result = await settingsJsonCheck.run(mockContext);
|
|
119
|
+
expect(result.status).toBe('WARN');
|
|
120
|
+
expect(result.message).toContain('boundary coverage');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('rules-files check', () => {
|
|
125
|
+
it('should PASS when all 7 rules files exist', async () => {
|
|
126
|
+
fs.existsSync.mockReturnValue(true);
|
|
127
|
+
const result = await rulesFilesCheck.run(mockContext);
|
|
128
|
+
expect(result.status).toBe('PASS');
|
|
129
|
+
expect(result.message).toContain('7');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should FAIL when rules directory missing', async () => {
|
|
133
|
+
fs.existsSync.mockReturnValue(false);
|
|
134
|
+
const result = await rulesFilesCheck.run(mockContext);
|
|
135
|
+
expect(result.status).toBe('FAIL');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should WARN when some rules missing', async () => {
|
|
139
|
+
fs.existsSync.mockImplementation((p) => {
|
|
140
|
+
// Directory exists
|
|
141
|
+
if (p.endsWith('rules')) return true;
|
|
142
|
+
// Most files exist except 2
|
|
143
|
+
if (p.includes('agent-authority') || p.includes('workflow-execution')) return false;
|
|
144
|
+
return true;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const result = await rulesFilesCheck.run(mockContext);
|
|
148
|
+
expect(result.status).toBe('WARN');
|
|
149
|
+
expect(result.message).toContain('Missing 2');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('agent-memory check', () => {
|
|
154
|
+
it('should PASS when all 10 MEMORY.md files exist', async () => {
|
|
155
|
+
fs.existsSync.mockReturnValue(true);
|
|
156
|
+
const result = await agentMemoryCheck.run(mockContext);
|
|
157
|
+
expect(result.status).toBe('PASS');
|
|
158
|
+
expect(result.message).toContain('10/10');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should WARN when some MEMORY.md files missing', async () => {
|
|
162
|
+
fs.existsSync.mockImplementation((p) => {
|
|
163
|
+
if (p.endsWith('agents')) return true;
|
|
164
|
+
if (p.includes('analyst') || p.includes('ux')) return false;
|
|
165
|
+
return true;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const result = await agentMemoryCheck.run(mockContext);
|
|
169
|
+
expect(result.status).toBe('WARN');
|
|
170
|
+
expect(result.message).toContain('8/10');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('entity-registry check', () => {
|
|
175
|
+
it('should PASS when registry exists and is fresh', async () => {
|
|
176
|
+
fs.existsSync.mockReturnValue(true);
|
|
177
|
+
fs.statSync.mockReturnValue({ mtimeMs: Date.now() - 1000 });
|
|
178
|
+
fs.readFileSync.mockReturnValue('line1\nline2\nline3');
|
|
179
|
+
|
|
180
|
+
const result = await entityRegistryCheck.run(mockContext);
|
|
181
|
+
expect(result.status).toBe('PASS');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should FAIL when registry not found', async () => {
|
|
185
|
+
fs.existsSync.mockReturnValue(false);
|
|
186
|
+
const result = await entityRegistryCheck.run(mockContext);
|
|
187
|
+
expect(result.status).toBe('FAIL');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should WARN when registry is stale (>48h)', async () => {
|
|
191
|
+
fs.existsSync.mockReturnValue(true);
|
|
192
|
+
fs.statSync.mockReturnValue({ mtimeMs: Date.now() - 72 * 60 * 60 * 1000 });
|
|
193
|
+
fs.readFileSync.mockReturnValue('line1\nline2');
|
|
194
|
+
|
|
195
|
+
const result = await entityRegistryCheck.run(mockContext);
|
|
196
|
+
expect(result.status).toBe('WARN');
|
|
197
|
+
expect(result.message).toContain('72h');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('git-hooks check', () => {
|
|
202
|
+
it('should PASS when both hooks exist', async () => {
|
|
203
|
+
fs.existsSync.mockReturnValue(true);
|
|
204
|
+
const result = await gitHooksCheck.run(mockContext);
|
|
205
|
+
expect(result.status).toBe('PASS');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should WARN when .husky directory missing', async () => {
|
|
209
|
+
fs.existsSync.mockReturnValue(false);
|
|
210
|
+
const result = await gitHooksCheck.run(mockContext);
|
|
211
|
+
expect(result.status).toBe('WARN');
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('core-config check', () => {
|
|
216
|
+
it('should PASS when config has all required sections', async () => {
|
|
217
|
+
fs.existsSync.mockReturnValue(true);
|
|
218
|
+
fs.readFileSync.mockReturnValue('boundary:\n test: true\nproject:\n name: test\nide:\n sync: true');
|
|
219
|
+
|
|
220
|
+
const result = await coreConfigCheck.run(mockContext);
|
|
221
|
+
expect(result.status).toBe('PASS');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should FAIL when config missing', async () => {
|
|
225
|
+
fs.existsSync.mockReturnValue(false);
|
|
226
|
+
const result = await coreConfigCheck.run(mockContext);
|
|
227
|
+
expect(result.status).toBe('FAIL');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should FAIL when missing required sections', async () => {
|
|
231
|
+
fs.existsSync.mockReturnValue(true);
|
|
232
|
+
fs.readFileSync.mockReturnValue('project:\n name: test');
|
|
233
|
+
|
|
234
|
+
const result = await coreConfigCheck.run(mockContext);
|
|
235
|
+
expect(result.status).toBe('FAIL');
|
|
236
|
+
expect(result.message).toContain('boundary');
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('claude-md check', () => {
|
|
241
|
+
it('should PASS when all sections present', async () => {
|
|
242
|
+
fs.existsSync.mockReturnValue(true);
|
|
243
|
+
fs.readFileSync.mockReturnValue(
|
|
244
|
+
'## Constitution\n## Framework vs Project Boundary\n## Sistema de Agentes',
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const result = await claudeMdCheck.run(mockContext);
|
|
248
|
+
expect(result.status).toBe('PASS');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should WARN when sections missing', async () => {
|
|
252
|
+
fs.existsSync.mockReturnValue(true);
|
|
253
|
+
fs.readFileSync.mockReturnValue('## Constitution\nSome content');
|
|
254
|
+
|
|
255
|
+
const result = await claudeMdCheck.run(mockContext);
|
|
256
|
+
expect(result.status).toBe('WARN');
|
|
257
|
+
expect(result.message).toContain('Missing sections');
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe('graph-dashboard check', () => {
|
|
262
|
+
it('should PASS when directory has .js files', async () => {
|
|
263
|
+
fs.existsSync.mockReturnValue(true);
|
|
264
|
+
fs.readdirSync.mockReturnValue(['index.js', 'cli.js']);
|
|
265
|
+
|
|
266
|
+
const result = await graphDashboardCheck.run(mockContext);
|
|
267
|
+
expect(result.status).toBe('PASS');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should WARN when directory missing', async () => {
|
|
271
|
+
fs.existsSync.mockReturnValue(false);
|
|
272
|
+
const result = await graphDashboardCheck.run(mockContext);
|
|
273
|
+
expect(result.status).toBe('WARN');
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe('code-intel check', () => {
|
|
278
|
+
it('should return INFO when not configured', async () => {
|
|
279
|
+
fs.existsSync.mockReturnValue(false);
|
|
280
|
+
const result = await codeIntelCheck.run(mockContext);
|
|
281
|
+
expect(result.status).toBe('INFO');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should PASS when configured in core-config', async () => {
|
|
285
|
+
fs.existsSync.mockReturnValue(true);
|
|
286
|
+
fs.readFileSync.mockReturnValue('codeIntel:\n provider: test');
|
|
287
|
+
|
|
288
|
+
const result = await codeIntelCheck.run(mockContext);
|
|
289
|
+
expect(result.status).toBe('PASS');
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('ide-sync check', () => {
|
|
294
|
+
it('should PASS when counts match', async () => {
|
|
295
|
+
fs.existsSync.mockReturnValue(true);
|
|
296
|
+
fs.readdirSync.mockImplementation((p) => {
|
|
297
|
+
if (p.includes('commands')) return ['dev.md', 'qa.md'];
|
|
298
|
+
return ['dev', 'qa'];
|
|
299
|
+
});
|
|
300
|
+
fs.statSync.mockReturnValue({ isDirectory: () => true });
|
|
301
|
+
|
|
302
|
+
const result = await ideSyncCheck.run(mockContext);
|
|
303
|
+
expect(result.status).toBe('PASS');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should WARN when counts mismatch', async () => {
|
|
307
|
+
fs.existsSync.mockReturnValue(true);
|
|
308
|
+
fs.readdirSync.mockImplementation((p) => {
|
|
309
|
+
if (p.includes('commands')) return ['dev.md', 'qa.md', 'pm.md'];
|
|
310
|
+
return ['dev', 'qa'];
|
|
311
|
+
});
|
|
312
|
+
fs.statSync.mockReturnValue({ isDirectory: () => true });
|
|
313
|
+
|
|
314
|
+
const result = await ideSyncCheck.run(mockContext);
|
|
315
|
+
expect(result.status).toBe('WARN');
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// === INS-4.8: New checks ===
|
|
320
|
+
|
|
321
|
+
describe('skills-count check', () => {
|
|
322
|
+
it('should PASS when >=7 skills directories with SKILL.md', async () => {
|
|
323
|
+
fs.existsSync.mockReturnValue(true);
|
|
324
|
+
const dirs = Array.from({ length: 8 }, (_, i) => ({
|
|
325
|
+
name: `skill-${i}`,
|
|
326
|
+
isDirectory: () => true,
|
|
327
|
+
isFile: () => false,
|
|
328
|
+
}));
|
|
329
|
+
fs.readdirSync.mockReturnValue(dirs);
|
|
330
|
+
|
|
331
|
+
const result = await skillsCountCheck.run(mockContext);
|
|
332
|
+
expect(result.check).toBe('skills-count');
|
|
333
|
+
expect(result.status).toBe('PASS');
|
|
334
|
+
expect(result.message).toContain('8');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should WARN when <7 skills found', async () => {
|
|
338
|
+
fs.existsSync.mockImplementation((p) => {
|
|
339
|
+
if (p.includes('skills') && !p.includes('SKILL.md')) return true;
|
|
340
|
+
if (p.includes('SKILL.md')) return true;
|
|
341
|
+
return true;
|
|
342
|
+
});
|
|
343
|
+
const dirs = Array.from({ length: 3 }, (_, i) => ({
|
|
344
|
+
name: `skill-${i}`,
|
|
345
|
+
isDirectory: () => true,
|
|
346
|
+
isFile: () => false,
|
|
347
|
+
}));
|
|
348
|
+
fs.readdirSync.mockReturnValue(dirs);
|
|
349
|
+
|
|
350
|
+
const result = await skillsCountCheck.run(mockContext);
|
|
351
|
+
expect(result.status).toBe('WARN');
|
|
352
|
+
expect(result.message).toContain('3/7');
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should FAIL when 0 skills found', async () => {
|
|
356
|
+
fs.existsSync.mockImplementation((p) => {
|
|
357
|
+
if (p.includes('SKILL.md')) return false;
|
|
358
|
+
return true;
|
|
359
|
+
});
|
|
360
|
+
const dirs = [{ name: 'empty', isDirectory: () => true, isFile: () => false }];
|
|
361
|
+
fs.readdirSync.mockReturnValue(dirs);
|
|
362
|
+
|
|
363
|
+
const result = await skillsCountCheck.run(mockContext);
|
|
364
|
+
expect(result.status).toBe('FAIL');
|
|
365
|
+
expect(result.fixCommand).toBe('npx aios-core install --force');
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should FAIL when skills directory missing', async () => {
|
|
369
|
+
fs.existsSync.mockReturnValue(false);
|
|
370
|
+
const result = await skillsCountCheck.run(mockContext);
|
|
371
|
+
expect(result.status).toBe('FAIL');
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
describe('commands-count check', () => {
|
|
376
|
+
it('should PASS when >=20 command files', async () => {
|
|
377
|
+
fs.existsSync.mockReturnValue(true);
|
|
378
|
+
const files = Array.from({ length: 22 }, (_, i) => ({
|
|
379
|
+
name: `cmd-${i}.md`,
|
|
380
|
+
isDirectory: () => false,
|
|
381
|
+
isFile: () => true,
|
|
382
|
+
}));
|
|
383
|
+
fs.readdirSync.mockReturnValue(files);
|
|
384
|
+
|
|
385
|
+
const result = await commandsCountCheck.run(mockContext);
|
|
386
|
+
expect(result.check).toBe('commands-count');
|
|
387
|
+
expect(result.status).toBe('PASS');
|
|
388
|
+
expect(result.message).toContain('22');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('should WARN when 12-19 command files', async () => {
|
|
392
|
+
fs.existsSync.mockReturnValue(true);
|
|
393
|
+
const files = Array.from({ length: 15 }, (_, i) => ({
|
|
394
|
+
name: `cmd-${i}.md`,
|
|
395
|
+
isDirectory: () => false,
|
|
396
|
+
isFile: () => true,
|
|
397
|
+
}));
|
|
398
|
+
fs.readdirSync.mockReturnValue(files);
|
|
399
|
+
|
|
400
|
+
const result = await commandsCountCheck.run(mockContext);
|
|
401
|
+
expect(result.status).toBe('WARN');
|
|
402
|
+
expect(result.message).toContain('15/20');
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('should FAIL when <12 command files', async () => {
|
|
406
|
+
fs.existsSync.mockReturnValue(true);
|
|
407
|
+
const files = Array.from({ length: 5 }, (_, i) => ({
|
|
408
|
+
name: `cmd-${i}.md`,
|
|
409
|
+
isDirectory: () => false,
|
|
410
|
+
isFile: () => true,
|
|
411
|
+
}));
|
|
412
|
+
fs.readdirSync.mockReturnValue(files);
|
|
413
|
+
|
|
414
|
+
const result = await commandsCountCheck.run(mockContext);
|
|
415
|
+
expect(result.status).toBe('FAIL');
|
|
416
|
+
expect(result.message).toContain('5');
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('should FAIL when commands directory missing', async () => {
|
|
420
|
+
fs.existsSync.mockReturnValue(false);
|
|
421
|
+
const result = await commandsCountCheck.run(mockContext);
|
|
422
|
+
expect(result.status).toBe('FAIL');
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe('hooks-claude-count check', () => {
|
|
427
|
+
it('should PASS when >=2 hook files and registered', async () => {
|
|
428
|
+
fs.existsSync.mockReturnValue(true);
|
|
429
|
+
const hookFiles = [
|
|
430
|
+
{ name: 'enforce-git-push.cjs', isFile: () => true, isDirectory: () => false },
|
|
431
|
+
{ name: 'pre-commit-check.cjs', isFile: () => true, isDirectory: () => false },
|
|
432
|
+
];
|
|
433
|
+
fs.readdirSync.mockReturnValue(hookFiles);
|
|
434
|
+
const settingsLocal = {
|
|
435
|
+
hooks: {
|
|
436
|
+
PreToolUse: [{ command: 'node .claude/hooks/enforce-git-push.cjs' }],
|
|
437
|
+
PostToolUse: [{ command: 'node .claude/hooks/pre-commit-check.cjs' }],
|
|
438
|
+
},
|
|
439
|
+
};
|
|
440
|
+
fs.readFileSync.mockReturnValue(JSON.stringify(settingsLocal));
|
|
441
|
+
|
|
442
|
+
const result = await hooksClaudeCountCheck.run(mockContext);
|
|
443
|
+
expect(result.check).toBe('hooks-claude-count');
|
|
444
|
+
expect(result.status).toBe('PASS');
|
|
445
|
+
expect(result.message).toContain('2');
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('should WARN when hooks present but not registered', async () => {
|
|
449
|
+
fs.existsSync.mockImplementation((p) => {
|
|
450
|
+
if (p.includes('settings.local.json')) return true;
|
|
451
|
+
return true;
|
|
452
|
+
});
|
|
453
|
+
const hookFiles = [
|
|
454
|
+
{ name: 'hook-a.cjs', isFile: () => true, isDirectory: () => false },
|
|
455
|
+
{ name: 'hook-b.cjs', isFile: () => true, isDirectory: () => false },
|
|
456
|
+
];
|
|
457
|
+
fs.readdirSync.mockReturnValue(hookFiles);
|
|
458
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({ hooks: {} }));
|
|
459
|
+
|
|
460
|
+
const result = await hooksClaudeCountCheck.run(mockContext);
|
|
461
|
+
expect(result.status).toBe('WARN');
|
|
462
|
+
expect(result.message).toContain('not registered');
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('should WARN when <2 hook files', async () => {
|
|
466
|
+
fs.existsSync.mockReturnValue(true);
|
|
467
|
+
const hookFiles = [
|
|
468
|
+
{ name: 'single-hook.cjs', isFile: () => true, isDirectory: () => false },
|
|
469
|
+
];
|
|
470
|
+
fs.readdirSync.mockReturnValue(hookFiles);
|
|
471
|
+
fs.readFileSync.mockReturnValue(JSON.stringify({ hooks: {} }));
|
|
472
|
+
|
|
473
|
+
const result = await hooksClaudeCountCheck.run(mockContext);
|
|
474
|
+
expect(result.status).toBe('WARN');
|
|
475
|
+
expect(result.message).toContain('1/2');
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('should FAIL when no hook files found', async () => {
|
|
479
|
+
fs.existsSync.mockReturnValue(true);
|
|
480
|
+
fs.readdirSync.mockReturnValue([]);
|
|
481
|
+
|
|
482
|
+
const result = await hooksClaudeCountCheck.run(mockContext);
|
|
483
|
+
expect(result.status).toBe('FAIL');
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('should FAIL when hooks directory missing', async () => {
|
|
487
|
+
fs.existsSync.mockReturnValue(false);
|
|
488
|
+
const result = await hooksClaudeCountCheck.run(mockContext);
|
|
489
|
+
expect(result.status).toBe('FAIL');
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// === INS-4.8: Registry and task validation ===
|
|
494
|
+
|
|
495
|
+
describe('check registry (INS-4.8)', () => {
|
|
496
|
+
it('should load 15 checks total', () => {
|
|
497
|
+
// loadChecks is the real function (not mocked) — verifies registration
|
|
498
|
+
const checks = loadChecks();
|
|
499
|
+
expect(checks).toHaveLength(15);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('should include all 3 new checks', () => {
|
|
503
|
+
const checks = loadChecks();
|
|
504
|
+
const names = checks.map((c) => c.name);
|
|
505
|
+
expect(names).toContain('skills-count');
|
|
506
|
+
expect(names).toContain('commands-count');
|
|
507
|
+
expect(names).toContain('hooks-claude-count');
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
describe('health-check.yaml task (INS-4.8)', () => {
|
|
512
|
+
it('should NOT have *doctor alias', () => {
|
|
513
|
+
const realFs = jest.requireActual('fs');
|
|
514
|
+
const yaml = realFs.readFileSync(
|
|
515
|
+
path.join(__dirname, '..', '..', '..', '..', '..', '.aios-core', 'development', 'tasks', 'health-check.yaml'),
|
|
516
|
+
'utf8',
|
|
517
|
+
);
|
|
518
|
+
// Verify *doctor is not in the aliases list (only *hc should be)
|
|
519
|
+
const aliasMatch = yaml.match(/aliases:\s*\n((?:\s+-\s+.*\n)*)/);
|
|
520
|
+
expect(aliasMatch).toBeTruthy();
|
|
521
|
+
expect(aliasMatch[1]).not.toContain('*doctor');
|
|
522
|
+
expect(aliasMatch[1]).toContain('*hc');
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should reference aios doctor --json in instructions', () => {
|
|
526
|
+
const realFs = jest.requireActual('fs');
|
|
527
|
+
const yaml = realFs.readFileSync(
|
|
528
|
+
path.join(__dirname, '..', '..', '..', '..', '..', '.aios-core', 'development', 'tasks', 'health-check.yaml'),
|
|
529
|
+
'utf8',
|
|
530
|
+
);
|
|
531
|
+
expect(yaml).toContain('aios doctor --json');
|
|
532
|
+
expect(yaml).toContain('node bin/aios.js doctor --json');
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('should have governance_map with all 15 checks', () => {
|
|
536
|
+
const realFs = jest.requireActual('fs');
|
|
537
|
+
const yaml = realFs.readFileSync(
|
|
538
|
+
path.join(__dirname, '..', '..', '..', '..', '..', '.aios-core', 'development', 'tasks', 'health-check.yaml'),
|
|
539
|
+
'utf8',
|
|
540
|
+
);
|
|
541
|
+
const expectedChecks = [
|
|
542
|
+
'settings-json', 'rules-files', 'agent-memory', 'entity-registry',
|
|
543
|
+
'git-hooks', 'core-config', 'claude-md', 'ide-sync', 'graph-dashboard',
|
|
544
|
+
'code-intel', 'node-version', 'npm-packages', 'skills-count',
|
|
545
|
+
'commands-count', 'hooks-claude-count',
|
|
546
|
+
];
|
|
547
|
+
for (const check of expectedChecks) {
|
|
548
|
+
expect(yaml).toContain(`${check}:`);
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests: Doctor Orchestrator
|
|
3
|
+
* Story INS-4.1: aios doctor rewrite
|
|
4
|
+
*
|
|
5
|
+
* Tests for options forwarding, output format, and fix/dry-run behavior.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
// Use real modules for orchestrator testing (checks will hit real filesystem)
|
|
11
|
+
const { runDoctorChecks, DOCTOR_VERSION } = require('../../../../../.aios-core/core/doctor');
|
|
12
|
+
|
|
13
|
+
const projectRoot = path.resolve(__dirname, '..', '..', '..', '..', '..');
|
|
14
|
+
|
|
15
|
+
describe('Doctor Orchestrator', () => {
|
|
16
|
+
describe('version', () => {
|
|
17
|
+
it('should export DOCTOR_VERSION as 2.0.0', () => {
|
|
18
|
+
expect(DOCTOR_VERSION).toBe('2.0.0');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('options forwarding (AC1)', () => {
|
|
23
|
+
it('should accept and use options object', async () => {
|
|
24
|
+
const result = await runDoctorChecks({ projectRoot });
|
|
25
|
+
expect(result).toHaveProperty('formatted');
|
|
26
|
+
expect(result).toHaveProperty('data');
|
|
27
|
+
expect(result.data).toHaveProperty('version', '2.0.0');
|
|
28
|
+
expect(result.data).toHaveProperty('summary');
|
|
29
|
+
expect(result.data).toHaveProperty('checks');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should produce JSON when json option is true', async () => {
|
|
33
|
+
const result = await runDoctorChecks({ json: true, projectRoot });
|
|
34
|
+
expect(result.formatted).toBeTruthy();
|
|
35
|
+
|
|
36
|
+
// Should be valid JSON
|
|
37
|
+
const parsed = JSON.parse(result.formatted);
|
|
38
|
+
expect(parsed).toHaveProperty('version');
|
|
39
|
+
expect(parsed).toHaveProperty('summary');
|
|
40
|
+
expect(parsed).toHaveProperty('checks');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should produce text when json option is false', async () => {
|
|
44
|
+
const result = await runDoctorChecks({ json: false, projectRoot });
|
|
45
|
+
expect(result.formatted).toContain('AIOS Doctor');
|
|
46
|
+
expect(result.formatted).toContain('Summary:');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('15 checks (AC2 + INS-4.8)', () => {
|
|
51
|
+
it('should run exactly 15 checks', async () => {
|
|
52
|
+
const result = await runDoctorChecks({ projectRoot });
|
|
53
|
+
expect(result.data.checks).toHaveLength(15);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should return valid status for each check', async () => {
|
|
57
|
+
const result = await runDoctorChecks({ projectRoot });
|
|
58
|
+
const validStatuses = ['PASS', 'WARN', 'FAIL', 'INFO'];
|
|
59
|
+
|
|
60
|
+
for (const check of result.data.checks) {
|
|
61
|
+
expect(validStatuses).toContain(check.status);
|
|
62
|
+
expect(check).toHaveProperty('check');
|
|
63
|
+
expect(check).toHaveProperty('message');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should include all expected check names', async () => {
|
|
68
|
+
const result = await runDoctorChecks({ projectRoot });
|
|
69
|
+
const checkNames = result.data.checks.map((c) => c.check);
|
|
70
|
+
|
|
71
|
+
expect(checkNames).toContain('settings-json');
|
|
72
|
+
expect(checkNames).toContain('rules-files');
|
|
73
|
+
expect(checkNames).toContain('agent-memory');
|
|
74
|
+
expect(checkNames).toContain('entity-registry');
|
|
75
|
+
expect(checkNames).toContain('git-hooks');
|
|
76
|
+
expect(checkNames).toContain('core-config');
|
|
77
|
+
expect(checkNames).toContain('claude-md');
|
|
78
|
+
expect(checkNames).toContain('ide-sync');
|
|
79
|
+
expect(checkNames).toContain('graph-dashboard');
|
|
80
|
+
expect(checkNames).toContain('code-intel');
|
|
81
|
+
expect(checkNames).toContain('node-version');
|
|
82
|
+
expect(checkNames).toContain('npm-packages');
|
|
83
|
+
expect(checkNames).toContain('skills-count');
|
|
84
|
+
expect(checkNames).toContain('commands-count');
|
|
85
|
+
expect(checkNames).toContain('hooks-claude-count');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('summary (AC3)', () => {
|
|
90
|
+
it('should have pass/warn/fail/info counts that sum to 15', async () => {
|
|
91
|
+
const result = await runDoctorChecks({ projectRoot });
|
|
92
|
+
const { pass, warn, fail, info } = result.data.summary;
|
|
93
|
+
expect(pass + warn + fail + info).toBe(15);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should include Summary line in text output', async () => {
|
|
97
|
+
const result = await runDoctorChecks({ projectRoot });
|
|
98
|
+
expect(result.formatted).toMatch(/Summary: \d+ PASS \| \d+ WARN \| \d+ FAIL \| \d+ INFO/);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('--dry-run (AC4)', () => {
|
|
103
|
+
it('should include fixResults when dryRun is true', async () => {
|
|
104
|
+
const result = await runDoctorChecks({ dryRun: true, projectRoot });
|
|
105
|
+
expect(result.data).toHaveProperty('fixResults');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should not produce file changes with dryRun', async () => {
|
|
109
|
+
const result = await runDoctorChecks({ dryRun: true, projectRoot });
|
|
110
|
+
if (result.data.fixResults) {
|
|
111
|
+
for (const fr of result.data.fixResults) {
|
|
112
|
+
expect(fr.applied).toBe(false);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('JSON output schema (AC3)', () => {
|
|
119
|
+
it('should match expected JSON schema', async () => {
|
|
120
|
+
const result = await runDoctorChecks({ json: true, projectRoot });
|
|
121
|
+
const parsed = JSON.parse(result.formatted);
|
|
122
|
+
|
|
123
|
+
expect(parsed).toHaveProperty('version');
|
|
124
|
+
expect(parsed).toHaveProperty('timestamp');
|
|
125
|
+
expect(parsed).toHaveProperty('summary');
|
|
126
|
+
expect(parsed.summary).toHaveProperty('pass');
|
|
127
|
+
expect(parsed.summary).toHaveProperty('warn');
|
|
128
|
+
expect(parsed.summary).toHaveProperty('fail');
|
|
129
|
+
expect(parsed.summary).toHaveProperty('info');
|
|
130
|
+
expect(parsed).toHaveProperty('checks');
|
|
131
|
+
expect(Array.isArray(parsed.checks)).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|