artifact-chain-assistant 0.8.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/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +11 -0
- package/.codex-plugin/plugin.json +27 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +26 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +19 -0
- package/.github/pull_request_template.md +14 -0
- package/.github/workflows/ci.yml +23 -0
- package/CHANGELOG.md +196 -0
- package/CODE_OF_CONDUCT.md +11 -0
- package/CONTRIBUTING.md +24 -0
- package/EXTENDED-ARTIFACT-CATALOG.md +312 -0
- package/EXTENDED-ARTIFACT-CATALOG.zh-CN.md +289 -0
- package/INSTALL.md +1340 -0
- package/LICENSE +186 -0
- package/NOTICE +18 -0
- package/README.md +426 -0
- package/README.zh-CN.md +409 -0
- package/SECURITY.md +19 -0
- package/adapters/claude/.claude-plugin/plugin.json +11 -0
- package/adapters/claude/EXTENDED-ARTIFACT-CATALOG.md +312 -0
- package/adapters/claude/EXTENDED-ARTIFACT-CATALOG.zh-CN.md +289 -0
- package/adapters/claude/INSTALL.md +1340 -0
- package/adapters/claude/agent-methods/catalog.yaml +260 -0
- package/adapters/claude/bin/version-lock-audit.sh +21 -0
- package/adapters/claude/bin/version-lock-refresh.sh +21 -0
- package/adapters/claude/commands/version-lock-audit.md +9 -0
- package/adapters/claude/commands/version-lock-refresh.md +9 -0
- package/adapters/claude/compatibility.json +9 -0
- package/adapters/claude/hooks/hooks.json +16 -0
- package/adapters/claude/hooks/version-lock-stop.mjs +77 -0
- package/adapters/claude/schemas/artifact-workflow-profile.schema.json +72 -0
- package/adapters/claude/scripts/batch-merge.mjs +276 -0
- package/adapters/claude/scripts/batch-split.mjs +184 -0
- package/adapters/claude/scripts/check-workflow-profile.mjs +64 -0
- package/adapters/claude/scripts/doctor.mjs +44 -0
- package/adapters/claude/scripts/lib/artifact-graph-runtime.mjs +189 -0
- package/adapters/claude/scripts/lib/compatibility-policy.mjs +435 -0
- package/adapters/claude/scripts/lib/inline-yaml-parser.mjs +216 -0
- package/adapters/claude/scripts/lib/method-registry-version.mjs +7 -0
- package/adapters/claude/scripts/lib/workflow-profile.mjs +645 -0
- package/adapters/claude/scripts/run-artifact-workflow.mjs +366 -0
- package/adapters/claude/skills/artifact-audit/SKILL.md +80 -0
- package/adapters/claude/skills/artifact-batch/SKILL.md +115 -0
- package/adapters/claude/skills/artifact-chain-bootstrap/SKILL.md +343 -0
- package/adapters/claude/skills/artifact-chain-maintainer/SKILL.md +167 -0
- package/adapters/claude/skills/artifact-generate/SKILL.md +89 -0
- package/adapters/claude/skills/artifact-repair/SKILL.md +124 -0
- package/adapters/claude/skills/artifact-review/SKILL.md +122 -0
- package/adapters/claude/skills/artifact-workflow-worker/SKILL.md +90 -0
- package/adapters/claude/skills/prd-feature/SKILL.md +68 -0
- package/adapters/claude/skills/prd-feature/author/SKILL.md +92 -0
- package/adapters/claude/skills/prd-feature/references/compose.md +43 -0
- package/adapters/claude/skills/prd-feature/references/inspect.md +94 -0
- package/adapters/claude/skills/prd-feature/references/validate.md +65 -0
- package/adapters/claude/skills/prd-feature/repair/SKILL.md +135 -0
- package/adapters/claude/skills/prd-feature/review/SKILL.md +134 -0
- package/adapters/claude/skills/scenario-script/SKILL.md +76 -0
- package/adapters/claude/skills/scenario-script/author/SKILL.md +111 -0
- package/adapters/claude/skills/scenario-script/references/compose.md +43 -0
- package/adapters/claude/skills/scenario-script/references/inspect.md +105 -0
- package/adapters/claude/skills/scenario-script/references/validate.md +72 -0
- package/adapters/claude/skills/scenario-script/repair/SKILL.md +135 -0
- package/adapters/claude/skills/scenario-script/review/SKILL.md +137 -0
- package/adapters/claude/skills/where-am-i/SKILL.md +233 -0
- package/adapters/claude/templates/README.md +159 -0
- package/adapters/claude/templates/core/decision/review-checklist.md +35 -0
- package/adapters/claude/templates/core/decision/starter.md +66 -0
- package/adapters/claude/templates/core/design/review-checklist.md +41 -0
- package/adapters/claude/templates/core/design/starter.md +84 -0
- package/adapters/claude/templates/core/e2e_test/review-checklist.md +43 -0
- package/adapters/claude/templates/core/e2e_test/starter.md +70 -0
- package/adapters/claude/templates/core/feature/review-checklist.md +29 -0
- package/adapters/claude/templates/core/feature/starter.md +43 -0
- package/adapters/claude/templates/core/scenario/review-checklist.md +29 -0
- package/adapters/claude/templates/core/scenario/starter.md +57 -0
- package/adapters/claude/templates/core/test/review-checklist.md +37 -0
- package/adapters/claude/templates/core/test/starter.md +70 -0
- package/adapters/claude/templates/extended/ADOPTION-GUIDE.md +263 -0
- package/adapters/claude/templates/extended/README.md +261 -0
- package/adapters/claude/templates/extended/agent/agent_skill/review-checklist.md +51 -0
- package/adapters/claude/templates/extended/agent/agent_skill/starter.md +133 -0
- package/adapters/claude/templates/extended/agent/hook_policy/review-checklist.md +58 -0
- package/adapters/claude/templates/extended/agent/hook_policy/starter.md +134 -0
- package/adapters/claude/templates/extended/agent/prompt_packet/review-checklist.md +54 -0
- package/adapters/claude/templates/extended/agent/prompt_packet/starter.md +106 -0
- package/adapters/claude/templates/extended/contracts/api_contract/review-checklist.md +43 -0
- package/adapters/claude/templates/extended/contracts/api_contract/starter.md +92 -0
- package/adapters/claude/templates/extended/contracts/batch_job_contract/review-checklist.md +82 -0
- package/adapters/claude/templates/extended/contracts/batch_job_contract/starter.md +158 -0
- package/adapters/claude/templates/extended/contracts/cli_contract/review-checklist.md +43 -0
- package/adapters/claude/templates/extended/contracts/cli_contract/starter.md +93 -0
- package/adapters/claude/templates/extended/contracts/data_contract/review-checklist.md +45 -0
- package/adapters/claude/templates/extended/contracts/data_contract/starter.md +88 -0
- package/adapters/claude/templates/extended/contracts/integration_contract/review-checklist.md +77 -0
- package/adapters/claude/templates/extended/contracts/integration_contract/starter.md +158 -0
- package/adapters/claude/templates/extended/contracts/ipc_contract/review-checklist.md +78 -0
- package/adapters/claude/templates/extended/contracts/ipc_contract/starter.md +214 -0
- package/adapters/claude/templates/extended/contracts/report_contract/review-checklist.md +61 -0
- package/adapters/claude/templates/extended/contracts/report_contract/starter.md +97 -0
- package/adapters/claude/templates/extended/contracts/ui_contract/review-checklist.md +70 -0
- package/adapters/claude/templates/extended/contracts/ui_contract/starter.md +150 -0
- package/adapters/claude/templates/extended/domain/database_migration/review-checklist.md +51 -0
- package/adapters/claude/templates/extended/domain/database_migration/starter.md +115 -0
- package/adapters/claude/templates/extended/domain/domain_model/review-checklist.md +43 -0
- package/adapters/claude/templates/extended/domain/domain_model/starter.md +83 -0
- package/adapters/claude/templates/extended/governance/oss_compliance/review-checklist.md +51 -0
- package/adapters/claude/templates/extended/governance/oss_compliance/starter.md +84 -0
- package/adapters/claude/templates/extended/governance/publish_skill/review-checklist.md +58 -0
- package/adapters/claude/templates/extended/governance/publish_skill/starter.md +172 -0
- package/adapters/claude/templates/extended/governance/release_policy/review-checklist.md +57 -0
- package/adapters/claude/templates/extended/governance/release_policy/starter.md +131 -0
- package/adapters/claude/templates/extended/ops/deployment_manifest/review-checklist.md +51 -0
- package/adapters/claude/templates/extended/ops/deployment_manifest/starter.md +117 -0
- package/adapters/claude/templates/extended/ops/migration_plan/review-checklist.md +63 -0
- package/adapters/claude/templates/extended/ops/migration_plan/starter.md +117 -0
- package/adapters/claude/templates/extended/ops/performance_budget/review-checklist.md +50 -0
- package/adapters/claude/templates/extended/ops/performance_budget/starter.md +117 -0
- package/adapters/claude/templates/extended/ops/runbook/review-checklist.md +51 -0
- package/adapters/claude/templates/extended/ops/runbook/starter.md +101 -0
- package/adapters/claude/templates/extended/ops/security_review/review-checklist.md +56 -0
- package/adapters/claude/templates/extended/ops/security_review/starter.md +125 -0
- package/adapters/codex/.codex-plugin/plugin.json +27 -0
- package/adapters/codex/EXTENDED-ARTIFACT-CATALOG.md +312 -0
- package/adapters/codex/EXTENDED-ARTIFACT-CATALOG.zh-CN.md +289 -0
- package/adapters/codex/INSTALL.md +1340 -0
- package/adapters/codex/agent-methods/catalog.yaml +260 -0
- package/adapters/codex/compatibility.json +9 -0
- package/adapters/codex/schemas/artifact-workflow-profile.schema.json +72 -0
- package/adapters/codex/scripts/batch-merge.mjs +276 -0
- package/adapters/codex/scripts/batch-split.mjs +184 -0
- package/adapters/codex/scripts/check-workflow-profile.mjs +64 -0
- package/adapters/codex/scripts/doctor.mjs +44 -0
- package/adapters/codex/scripts/lib/artifact-graph-runtime.mjs +189 -0
- package/adapters/codex/scripts/lib/compatibility-policy.mjs +435 -0
- package/adapters/codex/scripts/lib/inline-yaml-parser.mjs +216 -0
- package/adapters/codex/scripts/lib/method-registry-version.mjs +7 -0
- package/adapters/codex/scripts/lib/workflow-profile.mjs +645 -0
- package/adapters/codex/scripts/run-artifact-workflow.mjs +366 -0
- package/adapters/codex/skills/artifact-audit/SKILL.md +80 -0
- package/adapters/codex/skills/artifact-batch/SKILL.md +115 -0
- package/adapters/codex/skills/artifact-chain-bootstrap/SKILL.md +343 -0
- package/adapters/codex/skills/artifact-chain-maintainer/SKILL.md +167 -0
- package/adapters/codex/skills/artifact-generate/SKILL.md +89 -0
- package/adapters/codex/skills/artifact-repair/SKILL.md +124 -0
- package/adapters/codex/skills/artifact-review/SKILL.md +122 -0
- package/adapters/codex/skills/artifact-workflow-worker/SKILL.md +90 -0
- package/adapters/codex/skills/prd-feature/SKILL.md +68 -0
- package/adapters/codex/skills/prd-feature/author/SKILL.md +92 -0
- package/adapters/codex/skills/prd-feature/references/compose.md +43 -0
- package/adapters/codex/skills/prd-feature/references/inspect.md +94 -0
- package/adapters/codex/skills/prd-feature/references/validate.md +65 -0
- package/adapters/codex/skills/prd-feature/repair/SKILL.md +135 -0
- package/adapters/codex/skills/prd-feature/review/SKILL.md +134 -0
- package/adapters/codex/skills/scenario-script/SKILL.md +76 -0
- package/adapters/codex/skills/scenario-script/author/SKILL.md +111 -0
- package/adapters/codex/skills/scenario-script/references/compose.md +43 -0
- package/adapters/codex/skills/scenario-script/references/inspect.md +105 -0
- package/adapters/codex/skills/scenario-script/references/validate.md +72 -0
- package/adapters/codex/skills/scenario-script/repair/SKILL.md +135 -0
- package/adapters/codex/skills/scenario-script/review/SKILL.md +137 -0
- package/adapters/codex/skills/where-am-i/SKILL.md +233 -0
- package/adapters/codex/templates/README.md +159 -0
- package/adapters/codex/templates/core/decision/review-checklist.md +35 -0
- package/adapters/codex/templates/core/decision/starter.md +66 -0
- package/adapters/codex/templates/core/design/review-checklist.md +41 -0
- package/adapters/codex/templates/core/design/starter.md +84 -0
- package/adapters/codex/templates/core/e2e_test/review-checklist.md +43 -0
- package/adapters/codex/templates/core/e2e_test/starter.md +70 -0
- package/adapters/codex/templates/core/feature/review-checklist.md +29 -0
- package/adapters/codex/templates/core/feature/starter.md +43 -0
- package/adapters/codex/templates/core/scenario/review-checklist.md +29 -0
- package/adapters/codex/templates/core/scenario/starter.md +57 -0
- package/adapters/codex/templates/core/test/review-checklist.md +37 -0
- package/adapters/codex/templates/core/test/starter.md +70 -0
- package/adapters/codex/templates/extended/ADOPTION-GUIDE.md +263 -0
- package/adapters/codex/templates/extended/README.md +261 -0
- package/adapters/codex/templates/extended/agent/agent_skill/review-checklist.md +51 -0
- package/adapters/codex/templates/extended/agent/agent_skill/starter.md +133 -0
- package/adapters/codex/templates/extended/agent/hook_policy/review-checklist.md +58 -0
- package/adapters/codex/templates/extended/agent/hook_policy/starter.md +134 -0
- package/adapters/codex/templates/extended/agent/prompt_packet/review-checklist.md +54 -0
- package/adapters/codex/templates/extended/agent/prompt_packet/starter.md +106 -0
- package/adapters/codex/templates/extended/contracts/api_contract/review-checklist.md +43 -0
- package/adapters/codex/templates/extended/contracts/api_contract/starter.md +92 -0
- package/adapters/codex/templates/extended/contracts/batch_job_contract/review-checklist.md +82 -0
- package/adapters/codex/templates/extended/contracts/batch_job_contract/starter.md +158 -0
- package/adapters/codex/templates/extended/contracts/cli_contract/review-checklist.md +43 -0
- package/adapters/codex/templates/extended/contracts/cli_contract/starter.md +93 -0
- package/adapters/codex/templates/extended/contracts/data_contract/review-checklist.md +45 -0
- package/adapters/codex/templates/extended/contracts/data_contract/starter.md +88 -0
- package/adapters/codex/templates/extended/contracts/integration_contract/review-checklist.md +77 -0
- package/adapters/codex/templates/extended/contracts/integration_contract/starter.md +158 -0
- package/adapters/codex/templates/extended/contracts/ipc_contract/review-checklist.md +78 -0
- package/adapters/codex/templates/extended/contracts/ipc_contract/starter.md +214 -0
- package/adapters/codex/templates/extended/contracts/report_contract/review-checklist.md +61 -0
- package/adapters/codex/templates/extended/contracts/report_contract/starter.md +97 -0
- package/adapters/codex/templates/extended/contracts/ui_contract/review-checklist.md +70 -0
- package/adapters/codex/templates/extended/contracts/ui_contract/starter.md +150 -0
- package/adapters/codex/templates/extended/domain/database_migration/review-checklist.md +51 -0
- package/adapters/codex/templates/extended/domain/database_migration/starter.md +115 -0
- package/adapters/codex/templates/extended/domain/domain_model/review-checklist.md +43 -0
- package/adapters/codex/templates/extended/domain/domain_model/starter.md +83 -0
- package/adapters/codex/templates/extended/governance/oss_compliance/review-checklist.md +51 -0
- package/adapters/codex/templates/extended/governance/oss_compliance/starter.md +84 -0
- package/adapters/codex/templates/extended/governance/publish_skill/review-checklist.md +58 -0
- package/adapters/codex/templates/extended/governance/publish_skill/starter.md +172 -0
- package/adapters/codex/templates/extended/governance/release_policy/review-checklist.md +57 -0
- package/adapters/codex/templates/extended/governance/release_policy/starter.md +131 -0
- package/adapters/codex/templates/extended/ops/deployment_manifest/review-checklist.md +51 -0
- package/adapters/codex/templates/extended/ops/deployment_manifest/starter.md +117 -0
- package/adapters/codex/templates/extended/ops/migration_plan/review-checklist.md +63 -0
- package/adapters/codex/templates/extended/ops/migration_plan/starter.md +117 -0
- package/adapters/codex/templates/extended/ops/performance_budget/review-checklist.md +50 -0
- package/adapters/codex/templates/extended/ops/performance_budget/starter.md +117 -0
- package/adapters/codex/templates/extended/ops/runbook/review-checklist.md +51 -0
- package/adapters/codex/templates/extended/ops/runbook/starter.md +101 -0
- package/adapters/codex/templates/extended/ops/security_review/review-checklist.md +56 -0
- package/adapters/codex/templates/extended/ops/security_review/starter.md +125 -0
- package/agent-methods/catalog.yaml +260 -0
- package/compatibility.json +9 -0
- package/package.json +85 -0
- package/runtime/claude/version-lock-stop.mjs +77 -0
- package/schemas/artifact-workflow-profile.schema.json +72 -0
- package/scripts/batch-merge.mjs +276 -0
- package/scripts/batch-split.mjs +184 -0
- package/scripts/build-adapters.mjs +195 -0
- package/scripts/build-runtime-bundles.mjs +134 -0
- package/scripts/check-compatibility.mjs +19 -0
- package/scripts/check-method-registry.mjs +461 -0
- package/scripts/check-templates.mjs +348 -0
- package/scripts/check-workflow-profile.mjs +64 -0
- package/scripts/doctor.mjs +44 -0
- package/scripts/install-git-hooks.mjs +21 -0
- package/scripts/lib/artifact-graph-runtime.mjs +189 -0
- package/scripts/lib/compatibility-policy.mjs +435 -0
- package/scripts/lib/inline-yaml-parser.mjs +216 -0
- package/scripts/lib/method-registry-version.mjs +7 -0
- package/scripts/lib/workflow-profile.mjs +645 -0
- package/scripts/run-artifact-workflow.mjs +366 -0
- package/scripts/sync-skills.mjs +149 -0
- package/skills-src/artifact-audit/SKILL.md +80 -0
- package/skills-src/artifact-batch/SKILL.md +115 -0
- package/skills-src/artifact-chain-bootstrap/SKILL.md +343 -0
- package/skills-src/artifact-chain-maintainer/SKILL.md +167 -0
- package/skills-src/artifact-generate/SKILL.md +89 -0
- package/skills-src/artifact-repair/SKILL.md +124 -0
- package/skills-src/artifact-review/SKILL.md +122 -0
- package/skills-src/artifact-workflow-worker/SKILL.md +90 -0
- package/skills-src/prd-feature/SKILL.md +68 -0
- package/skills-src/prd-feature/author/SKILL.md +92 -0
- package/skills-src/prd-feature/references/compose.md +43 -0
- package/skills-src/prd-feature/references/inspect.md +94 -0
- package/skills-src/prd-feature/references/validate.md +65 -0
- package/skills-src/prd-feature/repair/SKILL.md +135 -0
- package/skills-src/prd-feature/review/SKILL.md +134 -0
- package/skills-src/scenario-script/SKILL.md +76 -0
- package/skills-src/scenario-script/author/SKILL.md +111 -0
- package/skills-src/scenario-script/references/compose.md +43 -0
- package/skills-src/scenario-script/references/inspect.md +105 -0
- package/skills-src/scenario-script/references/validate.md +72 -0
- package/skills-src/scenario-script/repair/SKILL.md +135 -0
- package/skills-src/scenario-script/review/SKILL.md +137 -0
- package/skills-src/where-am-i/SKILL.md.tpl +233 -0
- package/templates/README.md +159 -0
- package/templates/claude/marketplace.json.tpl +20 -0
- package/templates/claude/plugin.json.tpl +11 -0
- package/templates/claude/settings.json.tpl +16 -0
- package/templates/codex/marketplace.json.tpl +20 -0
- package/templates/codex/plugin.json.tpl +27 -0
- package/templates/core/decision/review-checklist.md +35 -0
- package/templates/core/decision/starter.md +66 -0
- package/templates/core/design/review-checklist.md +41 -0
- package/templates/core/design/starter.md +84 -0
- package/templates/core/e2e_test/review-checklist.md +43 -0
- package/templates/core/e2e_test/starter.md +70 -0
- package/templates/core/feature/review-checklist.md +29 -0
- package/templates/core/feature/starter.md +43 -0
- package/templates/core/scenario/review-checklist.md +29 -0
- package/templates/core/scenario/starter.md +57 -0
- package/templates/core/test/review-checklist.md +37 -0
- package/templates/core/test/starter.md +70 -0
- package/templates/extended/ADOPTION-GUIDE.md +263 -0
- package/templates/extended/README.md +261 -0
- package/templates/extended/agent/agent_skill/review-checklist.md +51 -0
- package/templates/extended/agent/agent_skill/starter.md +133 -0
- package/templates/extended/agent/hook_policy/review-checklist.md +58 -0
- package/templates/extended/agent/hook_policy/starter.md +134 -0
- package/templates/extended/agent/prompt_packet/review-checklist.md +54 -0
- package/templates/extended/agent/prompt_packet/starter.md +106 -0
- package/templates/extended/contracts/api_contract/review-checklist.md +43 -0
- package/templates/extended/contracts/api_contract/starter.md +92 -0
- package/templates/extended/contracts/batch_job_contract/review-checklist.md +82 -0
- package/templates/extended/contracts/batch_job_contract/starter.md +158 -0
- package/templates/extended/contracts/cli_contract/review-checklist.md +43 -0
- package/templates/extended/contracts/cli_contract/starter.md +93 -0
- package/templates/extended/contracts/data_contract/review-checklist.md +45 -0
- package/templates/extended/contracts/data_contract/starter.md +88 -0
- package/templates/extended/contracts/integration_contract/review-checklist.md +77 -0
- package/templates/extended/contracts/integration_contract/starter.md +158 -0
- package/templates/extended/contracts/ipc_contract/review-checklist.md +78 -0
- package/templates/extended/contracts/ipc_contract/starter.md +214 -0
- package/templates/extended/contracts/report_contract/review-checklist.md +61 -0
- package/templates/extended/contracts/report_contract/starter.md +97 -0
- package/templates/extended/contracts/ui_contract/review-checklist.md +70 -0
- package/templates/extended/contracts/ui_contract/starter.md +150 -0
- package/templates/extended/domain/database_migration/review-checklist.md +51 -0
- package/templates/extended/domain/database_migration/starter.md +115 -0
- package/templates/extended/domain/domain_model/review-checklist.md +43 -0
- package/templates/extended/domain/domain_model/starter.md +83 -0
- package/templates/extended/governance/oss_compliance/review-checklist.md +51 -0
- package/templates/extended/governance/oss_compliance/starter.md +84 -0
- package/templates/extended/governance/publish_skill/review-checklist.md +58 -0
- package/templates/extended/governance/publish_skill/starter.md +172 -0
- package/templates/extended/governance/release_policy/review-checklist.md +57 -0
- package/templates/extended/governance/release_policy/starter.md +131 -0
- package/templates/extended/ops/deployment_manifest/review-checklist.md +51 -0
- package/templates/extended/ops/deployment_manifest/starter.md +117 -0
- package/templates/extended/ops/migration_plan/review-checklist.md +63 -0
- package/templates/extended/ops/migration_plan/starter.md +117 -0
- package/templates/extended/ops/performance_budget/review-checklist.md +50 -0
- package/templates/extended/ops/performance_budget/starter.md +117 -0
- package/templates/extended/ops/runbook/review-checklist.md +51 -0
- package/templates/extended/ops/runbook/starter.md +101 -0
- package/templates/extended/ops/security_review/review-checklist.md +56 -0
- package/templates/extended/ops/security_review/starter.md +125 -0
- package/templates/git-hooks/pre-commit.sh +43 -0
- package/templates/git-hooks/pre-push.sh +22 -0
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
// @scenario S-51
|
|
2
|
+
// @scenario S-53
|
|
3
|
+
// @scenario S-54
|
|
4
|
+
// Single workflow-profile resolver for source and generated adapter runtimes.
|
|
5
|
+
import {
|
|
6
|
+
existsSync,
|
|
7
|
+
lstatSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
realpathSync,
|
|
10
|
+
statSync,
|
|
11
|
+
} from 'node:fs';
|
|
12
|
+
import { readFile } from 'node:fs/promises';
|
|
13
|
+
import { spawnSync } from 'node:child_process';
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
import {
|
|
16
|
+
dirname,
|
|
17
|
+
extname,
|
|
18
|
+
isAbsolute,
|
|
19
|
+
join,
|
|
20
|
+
relative,
|
|
21
|
+
resolve,
|
|
22
|
+
sep,
|
|
23
|
+
} from 'node:path';
|
|
24
|
+
import { parseMinimalYaml } from './inline-yaml-parser.mjs';
|
|
25
|
+
|
|
26
|
+
const PLUGIN_ROOT = resolve(import.meta.dirname, '..', '..');
|
|
27
|
+
const SCHEMA_PATH = join(PLUGIN_ROOT, 'schemas', 'artifact-workflow-profile.schema.json');
|
|
28
|
+
const YAML_PROFILE = 'artifact-profiles/project.yaml';
|
|
29
|
+
const LEGACY_JSON = '.artifact-review.json';
|
|
30
|
+
const VALIDATOR_TIMEOUT_MS = 30_000;
|
|
31
|
+
const VALID_ACTIONS = new Set(['generate', 'review', 'repair', 'audit', 'batch']);
|
|
32
|
+
const VALID_DOMAIN = /^[a-z][a-z0-9-]*$/u;
|
|
33
|
+
const PUBLIC_AUDIT_DOMAINS = new Set(['health', 'capability']);
|
|
34
|
+
const BLOCKED_PATTERN = /blocked|unsafe|absolute|traversal|escape|symlink|dangling|duplicate|regular file|not a file|cannot (?:read|resolve|start)|invalid (?:legacy|worker|validator)|conflict|inconsistent|reserved|kebab|directory separator|timed out|signal|exited|prototype|pollution|dangerous|__proto__/i;
|
|
35
|
+
let schemaCache;
|
|
36
|
+
|
|
37
|
+
function loadSchema() {
|
|
38
|
+
schemaCache ??= JSON.parse(readFileSync(SCHEMA_PATH, 'utf8'));
|
|
39
|
+
return schemaCache;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function own(value, key) {
|
|
43
|
+
return Object.hasOwn(value, key);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isObject(value) {
|
|
47
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function contained(root, target) {
|
|
51
|
+
const rel = relative(root, target);
|
|
52
|
+
return rel === '' || (!rel.startsWith(`..${sep}`) && rel !== '..' && !isAbsolute(rel));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function realpathOrResolved(path) {
|
|
56
|
+
try {
|
|
57
|
+
return realpathSync(path);
|
|
58
|
+
} catch {
|
|
59
|
+
return resolve(path);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function hasTraversalSegment(path) {
|
|
64
|
+
return path.split(/[\\/]+/u).includes('..');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function lstatOrNull(path) {
|
|
68
|
+
try {
|
|
69
|
+
return lstatSync(path);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error?.code === 'ENOENT') return null;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Minimal deterministic validator for the committed workflow-profile schema. */
|
|
77
|
+
export function validateAgainstSchema(data, schema, path = '') {
|
|
78
|
+
const errors = [];
|
|
79
|
+
if (schema.type) {
|
|
80
|
+
const actual = Array.isArray(data) ? 'array' : data === null ? 'null' : typeof data;
|
|
81
|
+
if (schema.type === 'integer') {
|
|
82
|
+
if (actual !== 'number' || !Number.isInteger(data)) {
|
|
83
|
+
return { valid: false, errors: [`${path || '/'}: expected type integer, got ${actual}`] };
|
|
84
|
+
}
|
|
85
|
+
} else if (actual !== schema.type) {
|
|
86
|
+
return { valid: false, errors: [`${path || '/'}: expected type ${schema.type}, got ${actual}`] };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (schema.const !== undefined && data !== schema.const) {
|
|
90
|
+
errors.push(`${path || '/'}: expected const ${JSON.stringify(schema.const)}, got ${JSON.stringify(data)}`);
|
|
91
|
+
}
|
|
92
|
+
if (schema.required && isObject(data)) {
|
|
93
|
+
for (const key of schema.required) {
|
|
94
|
+
if (!own(data, key)) errors.push(`${path}/${key}: required property missing`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (schema.additionalProperties === false && isObject(data)) {
|
|
98
|
+
const allowed = new Set(Object.keys(schema.properties ?? {}));
|
|
99
|
+
for (const key of Object.keys(data)) {
|
|
100
|
+
if (!allowed.has(key)) errors.push(`${path}/${key}: additional property not allowed`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (schema.propertyNames?.enum && isObject(data)) {
|
|
104
|
+
const allowedNames = new Set(schema.propertyNames.enum);
|
|
105
|
+
for (const key of Object.keys(data)) {
|
|
106
|
+
if (!allowedNames.has(key)) errors.push(`${path}/${key}: unsupported property name`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (schema.properties && isObject(data)) {
|
|
110
|
+
for (const [key, childSchema] of Object.entries(schema.properties)) {
|
|
111
|
+
if (own(data, key)) errors.push(...validateAgainstSchema(data[key], childSchema, `${path}/${key}`).errors);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (isObject(schema.additionalProperties) && isObject(data)) {
|
|
115
|
+
const defined = new Set(Object.keys(schema.properties ?? {}));
|
|
116
|
+
for (const [key, value] of Object.entries(data)) {
|
|
117
|
+
if (!defined.has(key)) errors.push(...validateAgainstSchema(value, schema.additionalProperties, `${path}/${key}`).errors);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (schema.items && Array.isArray(data)) {
|
|
121
|
+
data.forEach((value, index) => {
|
|
122
|
+
errors.push(...validateAgainstSchema(value, schema.items, `${path}/${index}`).errors);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (schema.minLength !== undefined && typeof data === 'string' && data.length < schema.minLength) {
|
|
126
|
+
errors.push(`${path}: string length ${data.length} is less than minLength ${schema.minLength}`);
|
|
127
|
+
}
|
|
128
|
+
if (schema.pattern !== undefined && typeof data === 'string' && !new RegExp(schema.pattern).test(data)) {
|
|
129
|
+
errors.push(`${path}: string does not match pattern ${schema.pattern}`);
|
|
130
|
+
}
|
|
131
|
+
return { valid: errors.length === 0, errors };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Validate resource paths once and return the only resolved paths consumers may use.
|
|
136
|
+
* details preserves input order so categories can be reconstructed without resolve() reuse.
|
|
137
|
+
*/
|
|
138
|
+
export function validateResourcePaths(paths, root) {
|
|
139
|
+
const surfaceRoot = resolve(root);
|
|
140
|
+
const realRoot = realpathOrResolved(root);
|
|
141
|
+
const errors = [];
|
|
142
|
+
const resolved = [];
|
|
143
|
+
const details = [];
|
|
144
|
+
const seen = new Set();
|
|
145
|
+
paths.forEach((source, index) => {
|
|
146
|
+
const reject = (error, kind = 'dangerous') => {
|
|
147
|
+
errors.push(error);
|
|
148
|
+
details.push({ index, source, resolved: null, error, kind });
|
|
149
|
+
};
|
|
150
|
+
if (typeof source !== 'string' || source.length === 0) {
|
|
151
|
+
reject(`Invalid resource path: ${String(source)}`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (isAbsolute(source)) {
|
|
155
|
+
reject(`Absolute path rejected: ${source}`);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (hasTraversalSegment(source)) {
|
|
159
|
+
reject(`Path traversal rejected: ${source}`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const surface = resolve(surfaceRoot, source);
|
|
163
|
+
if (!contained(surfaceRoot, surface)) {
|
|
164
|
+
reject(`Path escapes root: ${source}`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
let surfaceStat;
|
|
168
|
+
try {
|
|
169
|
+
surfaceStat = lstatOrNull(surface);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
reject(`Cannot inspect resource: ${source}: ${error.message}`);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (!surfaceStat) {
|
|
175
|
+
reject(`Path not found: ${source}`, 'missing');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (surfaceStat.isSymbolicLink() && !existsSync(surface)) {
|
|
179
|
+
reject(`Dangling resource symlink: ${source}`);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
let stat;
|
|
183
|
+
try {
|
|
184
|
+
stat = statSync(surface);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
reject(`Cannot read resource: ${source}: ${error.message}`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!stat.isFile()) {
|
|
190
|
+
reject(`Not a file: ${source}`);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
let real;
|
|
194
|
+
try {
|
|
195
|
+
real = realpathSync(surface);
|
|
196
|
+
readFileSync(real);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
reject(`Cannot read resource: ${source}: ${error.message}`);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (!contained(realRoot, real)) {
|
|
202
|
+
reject(`Symlink escapes root: ${source}`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (seen.has(real)) {
|
|
206
|
+
reject(`Duplicate resource: ${source}`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
seen.add(real);
|
|
210
|
+
resolved.push(surface);
|
|
211
|
+
details.push({ index, source, resolved: surface, error: null, kind: null });
|
|
212
|
+
});
|
|
213
|
+
return { safe: errors.length === 0, errors, resolved, details };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function validateSkillName(name, projectId, { legacy = false } = {}) {
|
|
217
|
+
if (typeof name !== 'string' || name.length === 0) return { valid: false, error: 'worker.skill is required' };
|
|
218
|
+
if (isAbsolute(name) || hasTraversalSegment(name) || name.includes('/') || name.includes('\\')) {
|
|
219
|
+
return { valid: false, error: `Invalid worker skill name or path: "${name}"` };
|
|
220
|
+
}
|
|
221
|
+
if (!/^[a-z][a-z0-9-]*$/u.test(name)) {
|
|
222
|
+
return { valid: false, error: `Worker skill must be lowercase kebab-case: "${name}"` };
|
|
223
|
+
}
|
|
224
|
+
if (legacy) return { valid: true, error: null };
|
|
225
|
+
if (name.startsWith('artifact-')) {
|
|
226
|
+
return { valid: false, error: `Worker skill collides with reserved public prefix "artifact-": "${name}"` };
|
|
227
|
+
}
|
|
228
|
+
if (!name.startsWith('project-') && !name.startsWith(`${projectId}-`)) {
|
|
229
|
+
return { valid: false, error: `Worker skill must start with "project-" or "${projectId}-": "${name}"` };
|
|
230
|
+
}
|
|
231
|
+
return { valid: true, error: null };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Validate explicit and auto-discovered profiles with one containment policy. */
|
|
235
|
+
export function validateProfilePathSafety(profilePath, root) {
|
|
236
|
+
const surfaceRoot = resolve(root);
|
|
237
|
+
const realProjectRoot = realpathOrResolved(root);
|
|
238
|
+
const surface = isAbsolute(profilePath) ? resolve(profilePath) : resolve(surfaceRoot, profilePath);
|
|
239
|
+
if (!contained(surfaceRoot, surface)) {
|
|
240
|
+
return { safe: false, error: `Profile path outside project root: "${profilePath}"`, resolved: null };
|
|
241
|
+
}
|
|
242
|
+
if (!['.yaml', '.yml', '.json'].includes(extname(surface).toLowerCase())) {
|
|
243
|
+
return { safe: false, error: `Profile must be .yaml, .yml, or .json: "${profilePath}"`, resolved: null };
|
|
244
|
+
}
|
|
245
|
+
let surfaceStat;
|
|
246
|
+
try {
|
|
247
|
+
surfaceStat = lstatOrNull(surface);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
return { safe: false, error: `Cannot inspect profile: "${profilePath}": ${error.message}`, resolved: null };
|
|
250
|
+
}
|
|
251
|
+
if (!surfaceStat) return { safe: true, error: null, resolved: surface };
|
|
252
|
+
if (surfaceStat.isSymbolicLink() && !existsSync(surface)) {
|
|
253
|
+
return { safe: false, error: `Dangling profile symlink: "${profilePath}"`, resolved: null };
|
|
254
|
+
}
|
|
255
|
+
let stat;
|
|
256
|
+
let real;
|
|
257
|
+
try {
|
|
258
|
+
stat = statSync(surface);
|
|
259
|
+
real = realpathSync(surface);
|
|
260
|
+
readFileSync(real);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
return { safe: false, error: `Cannot read profile: "${profilePath}": ${error.message}`, resolved: null };
|
|
263
|
+
}
|
|
264
|
+
if (!stat.isFile()) return { safe: false, error: `Profile path is not a regular file: "${profilePath}"`, resolved: null };
|
|
265
|
+
if (!contained(realProjectRoot, real)) {
|
|
266
|
+
return { safe: false, error: `Profile symlink escapes project root: "${profilePath}"`, resolved: null };
|
|
267
|
+
}
|
|
268
|
+
return { safe: true, error: null, resolved: surface };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Resolve a private project worker and fail closed on every present host candidate. */
|
|
272
|
+
export function resolveWorkerPath(projectRoot, skillName) {
|
|
273
|
+
const candidates = [];
|
|
274
|
+
const realProjectRoot = realpathOrResolved(projectRoot);
|
|
275
|
+
for (const base of ['.agents/skills', '.claude/skills']) {
|
|
276
|
+
const skillsRoot = join(projectRoot, base);
|
|
277
|
+
let rootSurface;
|
|
278
|
+
try {
|
|
279
|
+
rootSurface = lstatOrNull(skillsRoot);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
return { path: null, error: `Cannot inspect worker skills root: ${skillsRoot}: ${error.message}` };
|
|
282
|
+
}
|
|
283
|
+
if (!rootSurface) continue;
|
|
284
|
+
if (rootSurface.isSymbolicLink() && !existsSync(skillsRoot)) {
|
|
285
|
+
return { path: null, error: `Dangling worker skills root symlink: ${skillsRoot}` };
|
|
286
|
+
}
|
|
287
|
+
let realSkillsRoot;
|
|
288
|
+
try {
|
|
289
|
+
if (!statSync(skillsRoot).isDirectory()) {
|
|
290
|
+
return { path: null, error: `Worker skills root is not a directory: ${skillsRoot}` };
|
|
291
|
+
}
|
|
292
|
+
realSkillsRoot = realpathSync(skillsRoot);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
return { path: null, error: `Cannot resolve worker skills root: ${skillsRoot}: ${error.message}` };
|
|
295
|
+
}
|
|
296
|
+
if (!contained(realProjectRoot, realSkillsRoot)) {
|
|
297
|
+
return { path: null, error: `Worker skills root escapes project root: ${skillsRoot}` };
|
|
298
|
+
}
|
|
299
|
+
const candidate = join(skillsRoot, skillName, 'SKILL.md');
|
|
300
|
+
let candidateSurface;
|
|
301
|
+
try {
|
|
302
|
+
candidateSurface = lstatOrNull(candidate);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
return { path: null, error: `Cannot inspect worker candidate: ${candidate}: ${error.message}` };
|
|
305
|
+
}
|
|
306
|
+
if (!candidateSurface) continue;
|
|
307
|
+
if (candidateSurface.isSymbolicLink() && !existsSync(candidate)) {
|
|
308
|
+
return { path: null, error: `Dangling worker candidate symlink: ${candidate}` };
|
|
309
|
+
}
|
|
310
|
+
let stat;
|
|
311
|
+
let real;
|
|
312
|
+
let bytes;
|
|
313
|
+
try {
|
|
314
|
+
stat = statSync(candidate);
|
|
315
|
+
if (!stat.isFile()) return { path: null, error: `Worker candidate is not a regular file: ${candidate}` };
|
|
316
|
+
real = realpathSync(candidate);
|
|
317
|
+
if (!contained(realSkillsRoot, real)) {
|
|
318
|
+
return { path: null, error: `Worker SKILL.md escapes its skills root: ${candidate}` };
|
|
319
|
+
}
|
|
320
|
+
bytes = readFileSync(real);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
return { path: null, error: `Cannot read worker candidate: ${candidate}: ${error.message}` };
|
|
323
|
+
}
|
|
324
|
+
candidates.push({ path: candidate, real, bytes });
|
|
325
|
+
}
|
|
326
|
+
if (candidates.length === 0) return { path: null, error: `Worker skill not found: ${skillName}`, missing: true };
|
|
327
|
+
if (candidates.length === 2) {
|
|
328
|
+
const hashes = candidates.map(({ bytes }) => createHash('sha256').update(bytes).digest('hex'));
|
|
329
|
+
if (hashes[0] !== hashes[1]) {
|
|
330
|
+
return { path: null, error: `Inconsistent worker skill content between .agents/skills and .claude/skills: ${skillName}` };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return { path: candidates[0].path, error: null, missing: false };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function dangerousLegacyKey(value, path = '$') {
|
|
337
|
+
if (!isObject(value) && !Array.isArray(value)) return null;
|
|
338
|
+
for (const key of Object.keys(value)) {
|
|
339
|
+
const next = `${path}.${key}`;
|
|
340
|
+
if (key === '__proto__' || key === 'prototype' || key === 'constructor') return next;
|
|
341
|
+
const nested = dangerousLegacyKey(value[key], next);
|
|
342
|
+
if (nested) return nested;
|
|
343
|
+
}
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function legacyShapeError(data) {
|
|
348
|
+
if (!isObject(data)) return 'Invalid legacy profile shape: root must be an object';
|
|
349
|
+
const dangerous = dangerousLegacyKey(data);
|
|
350
|
+
if (dangerous) return `Invalid legacy profile: dangerous prototype key at ${dangerous}`;
|
|
351
|
+
for (const top of ['workflows', 'workers']) {
|
|
352
|
+
if (!own(data, top)) continue;
|
|
353
|
+
if (!isObject(data[top])) return `Invalid legacy ${top} shape: expected object`;
|
|
354
|
+
for (const [intent, domains] of Object.entries(data[top])) {
|
|
355
|
+
if (!isObject(domains)) return `Invalid legacy ${top}.${intent} shape: expected object`;
|
|
356
|
+
for (const [domain, value] of Object.entries(domains)) {
|
|
357
|
+
if (top === 'workers' && typeof value !== 'string') {
|
|
358
|
+
return `Invalid legacy workers.${intent}.${domain} shape: expected skill string`;
|
|
359
|
+
}
|
|
360
|
+
if (top === 'workflows') {
|
|
361
|
+
if (!isObject(value)) return `Invalid legacy workflows.${intent}.${domain} shape: expected object`;
|
|
362
|
+
for (const key of ['checklists', 'validators', 'templates']) {
|
|
363
|
+
if (own(value, key) && (!Array.isArray(value[key]) || value[key].some(item => typeof item !== 'string'))) {
|
|
364
|
+
return `Invalid legacy workflows.${intent}.${domain}.${key} shape: expected string array`;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function normalizeLegacy(data) {
|
|
375
|
+
const workflows = Object.create(null);
|
|
376
|
+
for (const [intent, domains] of Object.entries(data.workflows ?? {})) {
|
|
377
|
+
workflows[intent] = Object.create(null);
|
|
378
|
+
for (const [domain, config] of Object.entries(domains)) workflows[intent][domain] = { ...config };
|
|
379
|
+
}
|
|
380
|
+
for (const [intent, domains] of Object.entries(data.workers ?? {})) {
|
|
381
|
+
workflows[intent] ??= Object.create(null);
|
|
382
|
+
for (const [domain, skill] of Object.entries(domains)) {
|
|
383
|
+
workflows[intent][domain] ??= {};
|
|
384
|
+
workflows[intent][domain].worker = { skill };
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return { schema_version: 1, project: { id: 'legacy', language: 'legacy' }, workflows };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function loadYaml(path) {
|
|
391
|
+
try {
|
|
392
|
+
const data = parseMinimalYaml(await readFile(path, 'utf8'));
|
|
393
|
+
const result = validateAgainstSchema(data, loadSchema());
|
|
394
|
+
return result.valid
|
|
395
|
+
? { path, data, format: 'yaml', deprecated: false, error: null }
|
|
396
|
+
: { path, data: null, format: 'yaml', deprecated: false, error: `Schema validation failed: ${result.errors.join('; ')}` };
|
|
397
|
+
} catch (error) {
|
|
398
|
+
return { path, data: null, format: 'yaml', deprecated: false, error: `Failed to load YAML profile: ${error.message}` };
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function loadLegacy(path) {
|
|
403
|
+
try {
|
|
404
|
+
const raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
405
|
+
const error = legacyShapeError(raw);
|
|
406
|
+
if (error) return { path, data: null, format: 'json', deprecated: true, error };
|
|
407
|
+
return { path, data: normalizeLegacy(raw), format: 'json', deprecated: true, error: null };
|
|
408
|
+
} catch (error) {
|
|
409
|
+
return { path, data: null, format: 'json', deprecated: true, error: `Invalid legacy profile JSON: ${error.message}` };
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function profileFailure(path, error) {
|
|
414
|
+
return { path, data: null, format: null, deprecated: false, error, diagnostics: [error] };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export async function loadProfile({ root, profilePath }) {
|
|
418
|
+
const projectRoot = resolve(root);
|
|
419
|
+
const candidates = profilePath
|
|
420
|
+
? [{ path: profilePath, explicit: true }]
|
|
421
|
+
: [{ path: YAML_PROFILE, explicit: false }, { path: LEGACY_JSON, explicit: false }];
|
|
422
|
+
for (const candidate of candidates) {
|
|
423
|
+
const surface = isAbsolute(candidate.path) ? resolve(candidate.path) : resolve(projectRoot, candidate.path);
|
|
424
|
+
if (!candidate.explicit && !lstatOrNull(surface)) continue;
|
|
425
|
+
const safety = validateProfilePathSafety(candidate.path, projectRoot);
|
|
426
|
+
if (!safety.safe) return profileFailure(surface, safety.error);
|
|
427
|
+
if (!existsSync(safety.resolved)) {
|
|
428
|
+
return profileFailure(safety.resolved, `Explicit profile not found: ${safety.resolved}`);
|
|
429
|
+
}
|
|
430
|
+
const result = ['.yaml', '.yml'].includes(extname(safety.resolved).toLowerCase())
|
|
431
|
+
? await loadYaml(safety.resolved)
|
|
432
|
+
: loadLegacy(safety.resolved);
|
|
433
|
+
const diagnostics = [];
|
|
434
|
+
if (result.deprecated) diagnostics.push('Legacy .artifact-review.json format is deprecated and will be removed in 0.6.0; migrate to artifact-profiles/project.yaml');
|
|
435
|
+
if (result.error) diagnostics.push(result.error);
|
|
436
|
+
return { ...result, diagnostics };
|
|
437
|
+
}
|
|
438
|
+
return { path: null, data: null, format: null, deprecated: false, error: null, diagnostics: [] };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function resolveExecutionMode(profile, intent, domain) {
|
|
442
|
+
return profile?.workflows?.[intent]?.[domain]?.worker?.skill ? 'project-worker' : 'public-worker';
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function publicSkillPath(intent, domain) {
|
|
446
|
+
const skillsRoot = existsSync(join(PLUGIN_ROOT, 'skills-src')) ? join(PLUGIN_ROOT, 'skills-src') : join(PLUGIN_ROOT, 'skills');
|
|
447
|
+
let skill = 'artifact-workflow-worker';
|
|
448
|
+
if (intent === 'audit') {
|
|
449
|
+
skill = 'artifact-audit';
|
|
450
|
+
} else if (intent === 'batch') {
|
|
451
|
+
skill = 'artifact-batch';
|
|
452
|
+
} else if (domain === 'prd-feature' || domain === 'scenario-script') {
|
|
453
|
+
const leaf = intent === 'generate' ? 'author' : intent;
|
|
454
|
+
if (['author', 'review', 'repair'].includes(leaf)) skill = join(domain, leaf);
|
|
455
|
+
}
|
|
456
|
+
const path = join(skillsRoot, skill, 'SKILL.md');
|
|
457
|
+
try {
|
|
458
|
+
if (!statSync(path).isFile()) return { path: null, error: `Public worker is not a regular file: ${path}` };
|
|
459
|
+
readFileSync(path);
|
|
460
|
+
return { path, error: null };
|
|
461
|
+
} catch (error) {
|
|
462
|
+
return { path: null, error: `Cannot read public worker: ${path}: ${error.message}` };
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function runValidators(paths, { root, intent, domain, target }) {
|
|
467
|
+
const results = [];
|
|
468
|
+
const diagnostics = [];
|
|
469
|
+
const exitCodes = [];
|
|
470
|
+
for (const path of paths) {
|
|
471
|
+
if (!['.mjs', '.js', '.cjs'].includes(extname(path).toLowerCase())) {
|
|
472
|
+
diagnostics.push(`Invalid validator extension: ${path}`);
|
|
473
|
+
results.push({ path, command: `${process.execPath} ${path}`, exit_code: null, stdout: '', stderr: 'Unsupported validator extension' });
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
const command = `${process.execPath} ${path}${target ? ` ${target}` : ''}`;
|
|
477
|
+
const child = spawnSync(process.execPath, [path, target].filter(Boolean), {
|
|
478
|
+
cwd: root,
|
|
479
|
+
encoding: 'utf8',
|
|
480
|
+
timeout: VALIDATOR_TIMEOUT_MS,
|
|
481
|
+
env: {
|
|
482
|
+
...process.env,
|
|
483
|
+
ARTIFACT_WORKFLOW_INTENT: intent,
|
|
484
|
+
ARTIFACT_WORKFLOW_DOMAIN: domain ?? '',
|
|
485
|
+
ARTIFACT_WORKFLOW_ROOT: root,
|
|
486
|
+
ARTIFACT_WORKFLOW_TARGET: target ?? '',
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
const exitCode = Number.isInteger(child.status) ? child.status : null;
|
|
490
|
+
exitCodes.push(exitCode);
|
|
491
|
+
results.push({
|
|
492
|
+
path,
|
|
493
|
+
command,
|
|
494
|
+
exit_code: exitCode,
|
|
495
|
+
stdout: child.stdout ?? '',
|
|
496
|
+
stderr: child.stderr ?? '',
|
|
497
|
+
...(child.signal ? { signal: child.signal } : {}),
|
|
498
|
+
});
|
|
499
|
+
if (child.error) diagnostics.push(`Validator cannot start or timed out: ${path}: ${child.error.message}`);
|
|
500
|
+
else if (child.signal) diagnostics.push(`Validator terminated by signal ${child.signal}: ${path}`);
|
|
501
|
+
else if (exitCode === 2) diagnostics.push(`Validator needs input: ${path}: exit 2`);
|
|
502
|
+
else if (exitCode !== 0) diagnostics.push(`Validator exited ${String(exitCode)}: ${path}`);
|
|
503
|
+
}
|
|
504
|
+
return { results, diagnostics, exitCodes };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function fixedOutput({ status, profile, executionMode, workerPath, checklistPaths, validators, templatePaths, diagnostics }) {
|
|
508
|
+
return {
|
|
509
|
+
status,
|
|
510
|
+
schema: profile?.format === 'yaml' ? 'artifact-workflow-profile' : profile?.format === 'json' ? 'legacy-artifact-review' : null,
|
|
511
|
+
profile_path: profile?.path ?? null,
|
|
512
|
+
execution_mode: executionMode,
|
|
513
|
+
worker_path: workerPath,
|
|
514
|
+
checklist_paths: checklistPaths,
|
|
515
|
+
validators,
|
|
516
|
+
template_paths: templatePaths,
|
|
517
|
+
diagnostics,
|
|
518
|
+
next: status === 'OK'
|
|
519
|
+
? 'Invoke the resolved workflow.'
|
|
520
|
+
: status === 'BLOCKED'
|
|
521
|
+
? 'Fix blocked profile, worker, resource, or validator failures and run this check again.'
|
|
522
|
+
: 'Add the missing project markers, profile mapping, or resource and run this check again.',
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** The single readiness resolver used by source and both adapters. */
|
|
527
|
+
export async function buildCheckerOutput({ root, profilePath, intent = 'review', domain, runValidatorCommands = true, target }) {
|
|
528
|
+
const projectRoot = resolve(root);
|
|
529
|
+
if (!VALID_ACTIONS.has(intent)) {
|
|
530
|
+
return fixedOutput({
|
|
531
|
+
status: 'BLOCKED', profile: null, executionMode: null, workerPath: null,
|
|
532
|
+
checklistPaths: [], validators: [], templatePaths: [],
|
|
533
|
+
diagnostics: [`Unsupported action: ${String(intent)}`],
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
if (typeof domain !== 'string' || !VALID_DOMAIN.test(domain)) {
|
|
537
|
+
return fixedOutput({
|
|
538
|
+
status: 'BLOCKED', profile: null, executionMode: null, workerPath: null,
|
|
539
|
+
checklistPaths: [], validators: [], templatePaths: [],
|
|
540
|
+
diagnostics: [`Invalid or missing domain: ${String(domain)}`],
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
const executionRoot = realpathOrResolved(root);
|
|
544
|
+
const warnings = [];
|
|
545
|
+
const problems = [];
|
|
546
|
+
const hasConfig = existsSync(join(projectRoot, 'artifact-graph.config.yaml'));
|
|
547
|
+
const hasArtifacts = existsSync(join(projectRoot, 'artifacts')) && statSync(join(projectRoot, 'artifacts')).isDirectory();
|
|
548
|
+
if (!hasConfig && !hasArtifacts) problems.push('Missing artifact-graph.config.yaml and artifacts/');
|
|
549
|
+
|
|
550
|
+
const profile = await loadProfile({ root: projectRoot, profilePath });
|
|
551
|
+
if (profile.deprecated) warnings.push(...profile.diagnostics.filter(item => /deprecated/i.test(item)));
|
|
552
|
+
if (profile.error) problems.push(profile.error);
|
|
553
|
+
|
|
554
|
+
const noProfileAudit = intent === 'audit' && PUBLIC_AUDIT_DOMAINS.has(domain);
|
|
555
|
+
if (noProfileAudit && (!hasConfig || !hasArtifacts)) problems.push('Audit health/capability requires artifact-graph.config.yaml and artifacts/');
|
|
556
|
+
const domainConfig = profile.data?.workflows?.[intent]?.[domain];
|
|
557
|
+
if (!profile.data && !noProfileAudit && !profile.error) problems.push('No workflow profile found; create artifact-profiles/project.yaml');
|
|
558
|
+
if (profile.data && !domainConfig && !noProfileAudit) problems.push(`Missing workflows.${intent}.${domain ?? '(missing)'} profile mapping`);
|
|
559
|
+
if (intent === 'audit' && domain === 'release-gate' && !domainConfig) {
|
|
560
|
+
problems.push('Audit release-gate requires workflows.audit.release-gate configuration');
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
let executionMode = null;
|
|
564
|
+
let workerPath = null;
|
|
565
|
+
let checklistPaths = [];
|
|
566
|
+
let templatePaths = [];
|
|
567
|
+
let validators = [];
|
|
568
|
+
let needsInputExit = false;
|
|
569
|
+
|
|
570
|
+
if (domainConfig || noProfileAudit) {
|
|
571
|
+
executionMode = domainConfig?.worker?.skill ? 'project-worker' : 'public-worker';
|
|
572
|
+
if (executionMode === 'project-worker') {
|
|
573
|
+
const skill = domainConfig.worker.skill;
|
|
574
|
+
const name = validateSkillName(skill, profile.data.project.id, { legacy: profile.format === 'json' });
|
|
575
|
+
if (!name.valid) problems.push(name.error);
|
|
576
|
+
else {
|
|
577
|
+
const worker = resolveWorkerPath(projectRoot, skill);
|
|
578
|
+
if (worker.error) problems.push(worker.error);
|
|
579
|
+
else workerPath = worker.path;
|
|
580
|
+
}
|
|
581
|
+
} else {
|
|
582
|
+
const worker = publicSkillPath(intent, domain);
|
|
583
|
+
if (worker.error) problems.push(worker.error);
|
|
584
|
+
else workerPath = worker.path;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (domainConfig) {
|
|
588
|
+
const checklistCount = domainConfig.checklists?.length ?? 0;
|
|
589
|
+
const templateCount = domainConfig.templates?.length ?? 0;
|
|
590
|
+
const validatorCount = domainConfig.validators?.length ?? 0;
|
|
591
|
+
const resources = [
|
|
592
|
+
...(domainConfig.checklists ?? []),
|
|
593
|
+
...(domainConfig.templates ?? []),
|
|
594
|
+
...(domainConfig.validators ?? []),
|
|
595
|
+
];
|
|
596
|
+
const checked = validateResourcePaths(resources, projectRoot);
|
|
597
|
+
problems.push(...checked.errors);
|
|
598
|
+
checklistPaths = checked.details.slice(0, checklistCount).flatMap(item => item.resolved ? [item.resolved] : []);
|
|
599
|
+
templatePaths = checked.details.slice(checklistCount, checklistCount + templateCount).flatMap(item => item.resolved ? [item.resolved] : []);
|
|
600
|
+
const validatorPaths = checked.details.slice(checklistCount + templateCount, checklistCount + templateCount + validatorCount).flatMap(item => item.resolved ? [item.resolved] : []);
|
|
601
|
+
|
|
602
|
+
const needsProfileResources = executionMode === 'public-worker' || profile.format !== 'json';
|
|
603
|
+
if (needsProfileResources && ['review', 'repair'].includes(intent) && checklistPaths.length === 0) {
|
|
604
|
+
problems.push('Review/repair requires at least one checklist');
|
|
605
|
+
}
|
|
606
|
+
if (needsProfileResources && intent === 'generate' && templatePaths.length === 0) {
|
|
607
|
+
problems.push('Generate requires at least one template');
|
|
608
|
+
}
|
|
609
|
+
if (executionMode === 'public-worker' && intent === 'audit' && domain === 'release-gate'
|
|
610
|
+
&& checklistPaths.length === 0 && validatorPaths.length === 0) {
|
|
611
|
+
problems.push('Audit release-gate public worker requires at least one checklist or validator');
|
|
612
|
+
}
|
|
613
|
+
if (runValidatorCommands) {
|
|
614
|
+
const validatorRun = runValidators(validatorPaths, { root: executionRoot, intent, domain, target });
|
|
615
|
+
validators = validatorRun.results;
|
|
616
|
+
problems.push(...validatorRun.diagnostics);
|
|
617
|
+
needsInputExit = validatorRun.exitCodes.some(code => code === 2);
|
|
618
|
+
} else {
|
|
619
|
+
validators = validatorPaths.map(path => ({
|
|
620
|
+
path,
|
|
621
|
+
command: `${process.execPath} ${path}`,
|
|
622
|
+
exit_code: null,
|
|
623
|
+
stdout: '',
|
|
624
|
+
stderr: '',
|
|
625
|
+
}));
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// @feature ACA17
|
|
631
|
+
// @decision D-ACA-17
|
|
632
|
+
// BLOCKED issues (non-0/2 exit, signal, timeout, security, path) always override exit 2 (NEEDS_INPUT).
|
|
633
|
+
const isBlocked = problems.some(item => BLOCKED_PATTERN.test(item));
|
|
634
|
+
const status = problems.length === 0 ? 'OK' : isBlocked ? 'BLOCKED' : needsInputExit ? 'NEEDS_INPUT' : 'NEEDS_INPUT';
|
|
635
|
+
return fixedOutput({
|
|
636
|
+
status,
|
|
637
|
+
profile,
|
|
638
|
+
executionMode,
|
|
639
|
+
workerPath,
|
|
640
|
+
checklistPaths,
|
|
641
|
+
validators,
|
|
642
|
+
templatePaths,
|
|
643
|
+
diagnostics: [...warnings, ...problems],
|
|
644
|
+
});
|
|
645
|
+
}
|