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,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
7
|
+
export async function readHookEvent(input) {
|
|
8
|
+
let source = '';
|
|
9
|
+
for await (const chunk of input) {
|
|
10
|
+
source += chunk;
|
|
11
|
+
}
|
|
12
|
+
if (source.trim() === '') return {};
|
|
13
|
+
|
|
14
|
+
const event = JSON.parse(source);
|
|
15
|
+
if (event === null || Array.isArray(event) || typeof event !== 'object') {
|
|
16
|
+
throw new Error('Claude hook input must be a JSON object');
|
|
17
|
+
}
|
|
18
|
+
return event;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function buildCliCandidates(root, env) {
|
|
22
|
+
const candidates = [
|
|
23
|
+
{ command: join(root, 'node_modules', '.bin', 'artifact-graph'), prefix: [] },
|
|
24
|
+
{ command: 'artifact-graph', prefix: [] },
|
|
25
|
+
];
|
|
26
|
+
const legacyCli = env.ARTIFACT_GRAPH_LEGACY_CLI;
|
|
27
|
+
if (legacyCli) {
|
|
28
|
+
const legacyPath = isAbsolute(legacyCli) ? legacyCli : resolve(root, legacyCli);
|
|
29
|
+
if (existsSync(legacyPath)) {
|
|
30
|
+
candidates.push({ command: process.execPath, prefix: [legacyPath] });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return candidates;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function runStopAudit({
|
|
37
|
+
input = process.stdin,
|
|
38
|
+
env = process.env,
|
|
39
|
+
cwd = process.cwd(),
|
|
40
|
+
spawn = spawnSync,
|
|
41
|
+
stderr = (text) => process.stderr.write(text),
|
|
42
|
+
} = {}) {
|
|
43
|
+
let event;
|
|
44
|
+
try {
|
|
45
|
+
event = await readHookEvent(input);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
stderr(`artifact-chain-assistant: invalid Claude hook input: ${error.message}\n`);
|
|
48
|
+
return 2;
|
|
49
|
+
}
|
|
50
|
+
if (event.stop_hook_active === true) return 0;
|
|
51
|
+
|
|
52
|
+
const root = resolve(env.CLAUDE_PROJECT_DIR || cwd);
|
|
53
|
+
if (!existsSync(join(root, 'artifact-graph.config.yaml')) &&
|
|
54
|
+
!existsSync(join(root, 'artifacts', 'traceability-version-lock.json'))) {
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const candidate of buildCliCandidates(root, env)) {
|
|
59
|
+
const result = spawn(candidate.command, [
|
|
60
|
+
...candidate.prefix,
|
|
61
|
+
'version-lock', 'audit', '--root', root, '--strict-missing-lock',
|
|
62
|
+
], { cwd: root, env, stdio: ['ignore', 'ignore', 'inherit'] });
|
|
63
|
+
if (result.error?.code === 'ENOENT') continue;
|
|
64
|
+
if (result.error || result.status !== 0) {
|
|
65
|
+
stderr('artifact-chain-assistant: version-lock audit failed; repair the lock before stopping.\n');
|
|
66
|
+
return 2;
|
|
67
|
+
}
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
stderr('artifact-chain-assistant: artifact-graph CLI not found.\n');
|
|
72
|
+
return 2;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
76
|
+
process.exitCode = await runStopAudit();
|
|
77
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://artifact-graph.dev/schemas/artifact-workflow-profile.schema.json",
|
|
4
|
+
"title": "Artifact Workflow Profile",
|
|
5
|
+
"description": "Project workflow profile for artifact-chain-assistant",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["schema_version", "project", "workflows"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"schema_version": {
|
|
11
|
+
"type": "integer",
|
|
12
|
+
"const": 1
|
|
13
|
+
},
|
|
14
|
+
"project": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"required": ["id", "language"],
|
|
17
|
+
"additionalProperties": false,
|
|
18
|
+
"properties": {
|
|
19
|
+
"id": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"minLength": 1,
|
|
22
|
+
"pattern": "^[a-z][a-z0-9-]*$"
|
|
23
|
+
},
|
|
24
|
+
"language": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"minLength": 1
|
|
27
|
+
},
|
|
28
|
+
"name": {
|
|
29
|
+
"type": "string"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"workflows": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"propertyNames": {
|
|
36
|
+
"enum": ["generate", "review", "repair", "audit", "batch"]
|
|
37
|
+
},
|
|
38
|
+
"additionalProperties": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"additionalProperties": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"additionalProperties": false,
|
|
43
|
+
"properties": {
|
|
44
|
+
"checklists": {
|
|
45
|
+
"type": "array",
|
|
46
|
+
"items": { "type": "string" }
|
|
47
|
+
},
|
|
48
|
+
"validators": {
|
|
49
|
+
"type": "array",
|
|
50
|
+
"items": { "type": "string" }
|
|
51
|
+
},
|
|
52
|
+
"templates": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"items": { "type": "string" }
|
|
55
|
+
},
|
|
56
|
+
"worker": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"additionalProperties": false,
|
|
59
|
+
"properties": {
|
|
60
|
+
"skill": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"minLength": 1,
|
|
63
|
+
"description": "Project workflow skill name, not a filesystem path"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// @feature ACA16 @scenario S-49
|
|
2
|
+
// Deterministic batch result merging for artifact review workflows.
|
|
3
|
+
|
|
4
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Severity ordering for status/decision aggregation.
|
|
9
|
+
* Lower index = less severe.
|
|
10
|
+
*/
|
|
11
|
+
const STATUS_SEVERITY = ['SKIPPED', 'SUCCEEDED', 'NEEDS_INPUT', 'BLOCKED', 'FAILED'];
|
|
12
|
+
const DECISION_SEVERITY = ['NOT_APPLICABLE', 'PASS', 'PASS_WITH_RESIDUAL_MINOR', 'NEEDS_INPUT', 'BLOCKED', 'FAIL'];
|
|
13
|
+
const EXECUTORS = new Set(['script', 'worker', 'agent', 'manual', 'cli']);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the most severe value from a list using a severity ordering.
|
|
17
|
+
*/
|
|
18
|
+
function mostSevere(values, severityOrder) {
|
|
19
|
+
let maxIdx = -1;
|
|
20
|
+
let result = null;
|
|
21
|
+
for (const v of values) {
|
|
22
|
+
const idx = severityOrder.indexOf(v);
|
|
23
|
+
if (idx > maxIdx) {
|
|
24
|
+
maxIdx = idx;
|
|
25
|
+
result = v;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Validate a single result object against Review Result Protocol v1.0.
|
|
33
|
+
* Returns an error string if invalid, or null if valid.
|
|
34
|
+
*/
|
|
35
|
+
function validateResult(result, filePath) {
|
|
36
|
+
if (!result || typeof result !== 'object' || Array.isArray(result)) return `${filePath}: root must be an object`;
|
|
37
|
+
if (!result.schema_version) {
|
|
38
|
+
return `${filePath}: missing schema_version`;
|
|
39
|
+
}
|
|
40
|
+
if (result.schema_version !== '1.0') {
|
|
41
|
+
return `${filePath}: unsupported schema_version "${result.schema_version}" (expected "1.0")`;
|
|
42
|
+
}
|
|
43
|
+
if (!result.run_id) {
|
|
44
|
+
return `${filePath}: missing run_id`;
|
|
45
|
+
}
|
|
46
|
+
if (!result.status) {
|
|
47
|
+
return `${filePath}: missing status`;
|
|
48
|
+
}
|
|
49
|
+
if (!STATUS_SEVERITY.includes(result.status)) return `${filePath}: invalid status`;
|
|
50
|
+
if (!result.decision) {
|
|
51
|
+
return `${filePath}: missing decision`;
|
|
52
|
+
}
|
|
53
|
+
if (!DECISION_SEVERITY.includes(result.decision)) return `${filePath}: invalid decision`;
|
|
54
|
+
if (typeof result.summary !== 'string') return `${filePath}: invalid summary`;
|
|
55
|
+
if (result.producer !== undefined) {
|
|
56
|
+
if (!result.producer || typeof result.producer !== 'object' || Array.isArray(result.producer)) return `${filePath}: invalid producer`;
|
|
57
|
+
if (!EXECUTORS.has(result.producer.executor) || typeof result.producer.name !== 'string') return `${filePath}: invalid producer`;
|
|
58
|
+
}
|
|
59
|
+
if (result.review?.metrics !== undefined) {
|
|
60
|
+
if (!result.review.metrics || typeof result.review.metrics !== 'object' || Array.isArray(result.review.metrics)) return `${filePath}: invalid metrics`;
|
|
61
|
+
for (const value of Object.values(result.review.metrics)) {
|
|
62
|
+
if (!Number.isInteger(value) || value < 0) return `${filePath}: invalid metric`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Merge findings arrays, deduplicating by finding id.
|
|
70
|
+
* When duplicates exist, the first occurrence wins (deterministic by file sort order).
|
|
71
|
+
*/
|
|
72
|
+
function mergeFindings(allFindings) {
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
const merged = [];
|
|
75
|
+
|
|
76
|
+
for (const finding of allFindings) {
|
|
77
|
+
if (finding.id && seen.has(finding.id)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (finding.id) {
|
|
81
|
+
seen.add(finding.id);
|
|
82
|
+
}
|
|
83
|
+
merged.push(finding);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return merged;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Sum numeric metrics across all results.
|
|
91
|
+
*/
|
|
92
|
+
function sumMetrics(allMetrics) {
|
|
93
|
+
const sums = {};
|
|
94
|
+
|
|
95
|
+
for (const metrics of allMetrics) {
|
|
96
|
+
if (!metrics) continue;
|
|
97
|
+
for (const [key, value] of Object.entries(metrics)) {
|
|
98
|
+
if (typeof value === 'number') {
|
|
99
|
+
sums[key] = (sums[key] || 0) + value;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return sums;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Collect all findings from result objects.
|
|
109
|
+
*/
|
|
110
|
+
function collectFindings(results) {
|
|
111
|
+
const allFindings = [];
|
|
112
|
+
|
|
113
|
+
for (const result of results) {
|
|
114
|
+
const findings = result.review?.findings || [];
|
|
115
|
+
allFindings.push(...findings);
|
|
116
|
+
|
|
117
|
+
// Also include resolved_findings for completeness
|
|
118
|
+
const resolved = result.review?.resolved_findings || [];
|
|
119
|
+
allFindings.push(...resolved);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return allFindings;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Collect all metrics from result objects.
|
|
127
|
+
*/
|
|
128
|
+
function collectMetrics(results) {
|
|
129
|
+
return results
|
|
130
|
+
.map(r => r.review?.metrics)
|
|
131
|
+
.filter(Boolean);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Collect all batches from result objects.
|
|
136
|
+
*/
|
|
137
|
+
function collectBatches(results) {
|
|
138
|
+
const batches = [];
|
|
139
|
+
|
|
140
|
+
for (const result of results) {
|
|
141
|
+
if (result.review?.batches) {
|
|
142
|
+
batches.push(...result.review.batches);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return batches;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Determine merged run_id from results.
|
|
151
|
+
* Uses the first result's run_id as the base, or the provided override.
|
|
152
|
+
*/
|
|
153
|
+
function determineRunId(results, overrideRunId) {
|
|
154
|
+
if (overrideRunId) return overrideRunId;
|
|
155
|
+
return results[0].run_id;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Deterministic batch result merging.
|
|
160
|
+
*
|
|
161
|
+
* @param {string} resultsDir - Directory containing batch result JSON files
|
|
162
|
+
* @param {object} options
|
|
163
|
+
* @param {string} [options.runId] - Override run_id for merged result
|
|
164
|
+
* @returns {Promise<object>} Merged Review Result Protocol v1.0
|
|
165
|
+
* @throws {Error} If validation fails or no results found
|
|
166
|
+
*/
|
|
167
|
+
export async function batchMerge(resultsDir, options = {}) {
|
|
168
|
+
const base = resolve(resultsDir);
|
|
169
|
+
|
|
170
|
+
// Read all JSON files from results directory
|
|
171
|
+
const entries = await readdir(base).catch(() => {
|
|
172
|
+
throw new Error(`Cannot read results directory: ${base}`);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const jsonFiles = entries
|
|
176
|
+
.filter(e => e.endsWith('.json'))
|
|
177
|
+
.sort(); // Deterministic sort by filename
|
|
178
|
+
|
|
179
|
+
if (jsonFiles.length === 0) {
|
|
180
|
+
throw new Error(`No JSON files found in ${base}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Load and validate all results
|
|
184
|
+
const results = [];
|
|
185
|
+
const errors = [];
|
|
186
|
+
|
|
187
|
+
for (const fileName of jsonFiles) {
|
|
188
|
+
const filePath = join(base, fileName);
|
|
189
|
+
const content = await readFile(filePath, 'utf8');
|
|
190
|
+
|
|
191
|
+
let parsed;
|
|
192
|
+
try {
|
|
193
|
+
parsed = JSON.parse(content);
|
|
194
|
+
} catch (e) {
|
|
195
|
+
errors.push(`${fileName}: invalid JSON - ${e.message}`);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const validationError = validateResult(parsed, fileName);
|
|
200
|
+
if (validationError) {
|
|
201
|
+
errors.push(validationError);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
results.push(parsed);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (errors.length > 0) {
|
|
209
|
+
throw new Error(`Validation errors:\n${errors.join('\n')}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (results.length === 0) {
|
|
213
|
+
throw new Error(`No valid results found in ${base}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Sort results by stage_id for deterministic ordering
|
|
217
|
+
results.sort((a, b) => {
|
|
218
|
+
const aKey = a.stage_id || '';
|
|
219
|
+
const bKey = b.stage_id || '';
|
|
220
|
+
return aKey.localeCompare(bKey);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Merge components
|
|
224
|
+
const allFindings = collectFindings(results);
|
|
225
|
+
const mergedFindings = mergeFindings(allFindings);
|
|
226
|
+
const mergedMetrics = sumMetrics(collectMetrics(results));
|
|
227
|
+
const mergedBatches = collectBatches(results);
|
|
228
|
+
|
|
229
|
+
// Aggregate status and decision
|
|
230
|
+
const statuses = results.map(r => r.status);
|
|
231
|
+
const decisions = results.map(r => r.decision);
|
|
232
|
+
const mergedStatus = statuses.every(s => s === 'SUCCEEDED') ? 'SUCCEEDED' : mostSevere(statuses, STATUS_SEVERITY);
|
|
233
|
+
const mergedDecision = decisions.includes('FAIL') ? 'FAIL' : decisions.includes('BLOCKED') ? 'BLOCKED' : mostSevere(decisions, DECISION_SEVERITY);
|
|
234
|
+
|
|
235
|
+
// Build merged result
|
|
236
|
+
const merged = {
|
|
237
|
+
schema_version: '1.0',
|
|
238
|
+
run_id: determineRunId(results, options.runId),
|
|
239
|
+
status: mergedStatus,
|
|
240
|
+
decision: mergedDecision,
|
|
241
|
+
summary: `Batch completed: ${mergedBatches.length || results.length} batches, ${mergedMetrics.files_scanned || 0} files, ${mergedFindings.length} findings.`,
|
|
242
|
+
review: {
|
|
243
|
+
batches: mergedBatches,
|
|
244
|
+
metrics: mergedMetrics,
|
|
245
|
+
findings: mergedFindings,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return merged;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// CLI entry point
|
|
253
|
+
const isMain = import.meta.url === `file://${process.argv[1]}`;
|
|
254
|
+
if (isMain) {
|
|
255
|
+
const args = process.argv.slice(2);
|
|
256
|
+
const resultsDir = args[0];
|
|
257
|
+
|
|
258
|
+
if (!resultsDir) {
|
|
259
|
+
console.error('Usage: node batch-merge.mjs <results-dir> [--run-id <id>]');
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let runId;
|
|
264
|
+
const runIdIdx = args.indexOf('--run-id');
|
|
265
|
+
if (runIdIdx !== -1 && args[runIdIdx + 1]) {
|
|
266
|
+
runId = args[runIdIdx + 1];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const merged = await batchMerge(resultsDir, { runId });
|
|
271
|
+
console.log(JSON.stringify(merged, null, 2));
|
|
272
|
+
} catch (err) {
|
|
273
|
+
console.error(`Error: ${err.message}`);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// @feature ACA16 @scenario S-49
|
|
2
|
+
// Deterministic batch splitting for artifact review workflows.
|
|
3
|
+
|
|
4
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
5
|
+
import { join, relative, resolve } from 'node:path';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Recursively collect all .md files under a directory.
|
|
9
|
+
*/
|
|
10
|
+
async function collectMarkdownFiles(dir, base) {
|
|
11
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
12
|
+
const files = [];
|
|
13
|
+
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
const fullPath = join(dir, entry.name);
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
// Skip hidden directories and node_modules
|
|
18
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
19
|
+
files.push(...await collectMarkdownFiles(fullPath, base));
|
|
20
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
21
|
+
const relativePath = relative(base, fullPath);
|
|
22
|
+
const content = await readFile(fullPath, 'utf8');
|
|
23
|
+
files.push({
|
|
24
|
+
path: relativePath,
|
|
25
|
+
chars: content.length,
|
|
26
|
+
content,
|
|
27
|
+
// For grouping: detect part-*.md files that belong to a batch-*.md
|
|
28
|
+
groupKey: extractGroupKey(entry.name, relativePath),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return files;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extract a group key for related-file co-location.
|
|
38
|
+
* Files like batch-001-part-1.md, batch-001-part-2.md share the key "batch-001".
|
|
39
|
+
* Standalone files get a unique key based on their path.
|
|
40
|
+
*/
|
|
41
|
+
function extractGroupKey(fileName, relativePath) {
|
|
42
|
+
// Match batch-*.md pattern for grouping parts
|
|
43
|
+
const batchMatch = fileName.match(/^(batch-[^/]+?)(?:-part-\d+)?\.md$/);
|
|
44
|
+
if (batchMatch) {
|
|
45
|
+
return `batch:${batchMatch[1]}`;
|
|
46
|
+
}
|
|
47
|
+
// Each standalone file is its own group
|
|
48
|
+
return `file:${relativePath}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Sort files deterministically by path.
|
|
53
|
+
*/
|
|
54
|
+
function sortFiles(files) {
|
|
55
|
+
return files.sort((a, b) => a.path.localeCompare(b.path));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Build batches from sorted files.
|
|
60
|
+
*
|
|
61
|
+
* Strategy:
|
|
62
|
+
* 1. Group files by groupKey to keep related files together.
|
|
63
|
+
* 2. For each group, if adding the group to the current batch exceeds batch-size,
|
|
64
|
+
* and the current batch is non-empty, flush it and start a new one.
|
|
65
|
+
* 3. If a single group exceeds batch-size, it gets its own batch.
|
|
66
|
+
* 4. Sort groups by their first file's path for determinism.
|
|
67
|
+
*/
|
|
68
|
+
function buildBatches(files, batchSize) {
|
|
69
|
+
// Group files by groupKey
|
|
70
|
+
const groups = new Map();
|
|
71
|
+
for (const file of files) {
|
|
72
|
+
if (!groups.has(file.groupKey)) {
|
|
73
|
+
groups.set(file.groupKey, []);
|
|
74
|
+
}
|
|
75
|
+
groups.get(file.groupKey).push(file);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Sort groups by their first file's path for determinism
|
|
79
|
+
const sortedGroups = [...groups.entries()].sort(([, a], [, b]) =>
|
|
80
|
+
a[0].path.localeCompare(b[0].path)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const batches = [];
|
|
84
|
+
let currentBatchFiles = [];
|
|
85
|
+
let currentBatchChars = 0;
|
|
86
|
+
|
|
87
|
+
for (const [, groupFiles] of sortedGroups) {
|
|
88
|
+
const groupChars = groupFiles.reduce((sum, f) => sum + f.chars, 0);
|
|
89
|
+
|
|
90
|
+
// If the group alone exceeds batch-size, it gets its own batch
|
|
91
|
+
if (groupChars >= batchSize) {
|
|
92
|
+
// Flush current batch if non-empty
|
|
93
|
+
if (currentBatchFiles.length > 0) {
|
|
94
|
+
batches.push(currentBatchFiles);
|
|
95
|
+
currentBatchFiles = [];
|
|
96
|
+
currentBatchChars = 0;
|
|
97
|
+
}
|
|
98
|
+
// This group is its own batch
|
|
99
|
+
batches.push(groupFiles);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// If adding this group would exceed batch-size, flush current batch
|
|
104
|
+
if (currentBatchChars + groupChars > batchSize && currentBatchFiles.length > 0) {
|
|
105
|
+
batches.push(currentBatchFiles);
|
|
106
|
+
currentBatchFiles = [];
|
|
107
|
+
currentBatchChars = 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Add group to current batch
|
|
111
|
+
currentBatchFiles.push(...groupFiles);
|
|
112
|
+
currentBatchChars += groupChars;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Flush remaining files
|
|
116
|
+
if (currentBatchFiles.length > 0) {
|
|
117
|
+
batches.push(currentBatchFiles);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return batches;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Deterministic batch splitting.
|
|
125
|
+
*
|
|
126
|
+
* @param {string} targetDir - Directory to scan for .md files
|
|
127
|
+
* @param {object} options
|
|
128
|
+
* @param {number} [options.batchSize=40000] - Maximum characters per batch
|
|
129
|
+
* @returns {Promise<Array<{id: string, files: string[], chars: number}>>}
|
|
130
|
+
*/
|
|
131
|
+
export async function batchSplit(targetDir, options = {}) {
|
|
132
|
+
const { batchSize = 40000 } = options;
|
|
133
|
+
const base = resolve(targetDir);
|
|
134
|
+
|
|
135
|
+
// Verify target directory exists
|
|
136
|
+
const targetStat = await stat(base).catch(() => null);
|
|
137
|
+
if (!targetStat || !targetStat.isDirectory()) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Collect and sort files
|
|
142
|
+
const rawFiles = await collectMarkdownFiles(base, base);
|
|
143
|
+
const files = sortFiles(rawFiles);
|
|
144
|
+
|
|
145
|
+
// Build batches
|
|
146
|
+
const batchArrays = buildBatches(files, batchSize);
|
|
147
|
+
|
|
148
|
+
// Format output
|
|
149
|
+
return batchArrays.map((batchFiles, index) => ({
|
|
150
|
+
id: `batch-${String(index + 1).padStart(3, '0')}`,
|
|
151
|
+
files: batchFiles.map(f => f.path),
|
|
152
|
+
chars: batchFiles.reduce((sum, f) => sum + f.chars, 0),
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// CLI entry point
|
|
157
|
+
const isMain = import.meta.url === `file://${process.argv[1]}`;
|
|
158
|
+
if (isMain) {
|
|
159
|
+
const args = process.argv.slice(2);
|
|
160
|
+
const targetDir = args[0];
|
|
161
|
+
|
|
162
|
+
if (!targetDir) {
|
|
163
|
+
console.error('Usage: node batch-split.mjs <target-dir> [--batch-size N]');
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let batchSize = 40000;
|
|
168
|
+
const sizeIdx = args.indexOf('--batch-size');
|
|
169
|
+
if (sizeIdx !== -1 && args[sizeIdx + 1]) {
|
|
170
|
+
batchSize = parseInt(args[sizeIdx + 1], 10);
|
|
171
|
+
if (isNaN(batchSize) || batchSize <= 0) {
|
|
172
|
+
console.error('Error: --batch-size must be a positive integer');
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const batches = await batchSplit(targetDir, { batchSize });
|
|
179
|
+
console.log(JSON.stringify(batches, null, 2));
|
|
180
|
+
} catch (err) {
|
|
181
|
+
console.error(`Error: ${err.message}`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|