ai-delivery-workflow 0.2.2
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/LICENSE +21 -0
- package/README.md +112 -0
- package/bin/ai-delivery.mjs +138 -0
- package/bin/validate-skills.mjs +12 -0
- package/docs/DUAL-REPOSITORY-WORKSPACE.zh-CN.md +125 -0
- package/docs/EVOLUTION-MAINTENANCE.zh-CN.md +75 -0
- package/docs/EVOLUTION-USER-GUIDE.zh-CN.md +160 -0
- package/docs/FILE-REFERENCE.zh-CN.md +503 -0
- package/docs/PROJECT-MANUAL.zh-CN.md +931 -0
- package/docs/STATE-CLI-MAINTENANCE.zh-CN.md +135 -0
- package/docs/STATE-CLI-USER-GUIDE.zh-CN.md +186 -0
- package/docs/VIEWER-MAINTENANCE.zh-CN.md +373 -0
- package/docs/VIEWER-USER-GUIDE.zh-CN.md +235 -0
- package/lib/delivery-state.mjs +1220 -0
- package/lib/evolution.mjs +971 -0
- package/lib/project-bootstrap.mjs +927 -0
- package/lib/project-installer.mjs +1184 -0
- package/lib/skill-validator.mjs +154 -0
- package/lib/toml-hooks.mjs +97 -0
- package/lib/workspace.mjs +302 -0
- package/lib/yaml-runtime.mjs +18 -0
- package/package.json +39 -0
- package/skills/ai-delivery-assemble-release/SKILL.md +27 -0
- package/skills/ai-delivery-assemble-release/agents/openai.yaml +4 -0
- package/skills/ai-delivery-assemble-release/assets/release-candidate-manifest.yaml +22 -0
- package/skills/ai-delivery-assemble-release/assets/release-git-plan.yaml +9 -0
- package/skills/ai-delivery-assemble-release/assets/release-material-index.csv +2 -0
- package/skills/ai-delivery-assemble-release/assets/version-inclusion.csv +2 -0
- package/skills/ai-delivery-assemble-release/references/release-assembly-contract.md +7 -0
- package/skills/ai-delivery-assemble-release/scripts/plan-release.mjs +298 -0
- package/skills/ai-delivery-bootstrap/SKILL.md +59 -0
- package/skills/ai-delivery-bootstrap/agents/openai.yaml +4 -0
- package/skills/ai-delivery-bootstrap/references/bootstrap-contract.md +70 -0
- package/skills/ai-delivery-checkpoint-task/SKILL.md +63 -0
- package/skills/ai-delivery-checkpoint-task/agents/openai.yaml +4 -0
- package/skills/ai-delivery-checkpoint-task/assets/codex-hook-config.toml +92 -0
- package/skills/ai-delivery-checkpoint-task/assets/runtime-template/resume.md +15 -0
- package/skills/ai-delivery-checkpoint-task/assets/runtime-template/task.json +30 -0
- package/skills/ai-delivery-checkpoint-task/assets/runtime-template/version-archive.json +10 -0
- package/skills/ai-delivery-checkpoint-task/references/checkpoint-contract.md +95 -0
- package/skills/ai-delivery-checkpoint-task/references/checkpoint-recovery.zh-CN.md +111 -0
- package/skills/ai-delivery-checkpoint-task/scripts/hook-event.mjs +131 -0
- package/skills/ai-delivery-checkpoint-task/scripts/task-state.mjs +1073 -0
- package/skills/ai-delivery-close-version/SKILL.md +33 -0
- package/skills/ai-delivery-close-version/agents/openai.yaml +4 -0
- package/skills/ai-delivery-close-version/assets/line-advance-record.yaml +12 -0
- package/skills/ai-delivery-close-version/assets/version-lineage.yaml +12 -0
- package/skills/ai-delivery-close-version/assets/version-manifest.yaml +20 -0
- package/skills/ai-delivery-close-version/references/version-closeout-contract.md +20 -0
- package/skills/ai-delivery-define-product/SKILL.md +76 -0
- package/skills/ai-delivery-define-product/agents/openai.yaml +4 -0
- package/skills/ai-delivery-define-product/assets/product-template/acceptance-criteria.csv +1 -0
- package/skills/ai-delivery-define-product/assets/product-template/discovery-baseline.md +19 -0
- package/skills/ai-delivery-define-product/assets/product-template/feature-detail.md +19 -0
- package/skills/ai-delivery-define-product/assets/product-template/feature-terminals.csv +1 -0
- package/skills/ai-delivery-define-product/assets/product-template/features.csv +1 -0
- package/skills/ai-delivery-define-product/assets/product-template/product-baseline.yaml +8 -0
- package/skills/ai-delivery-define-product/assets/product-template/product-manual.md +24 -0
- package/skills/ai-delivery-define-product/assets/product-template/terminals.csv +1 -0
- package/skills/ai-delivery-define-product/assets/product-template/user-stories.csv +1 -0
- package/skills/ai-delivery-define-product/references/product-contract.md +57 -0
- package/skills/ai-delivery-deploy-production/SKILL.md +45 -0
- package/skills/ai-delivery-deploy-production/agents/openai.yaml +4 -0
- package/skills/ai-delivery-deploy-production/references/deployment-contract.md +7 -0
- package/skills/ai-delivery-design-architecture/SKILL.md +67 -0
- package/skills/ai-delivery-design-architecture/agents/openai.yaml +4 -0
- package/skills/ai-delivery-design-architecture/assets/architecture-template/architecture-baseline.md +25 -0
- package/skills/ai-delivery-design-architecture/assets/architecture-template/architecture-gate.yaml +15 -0
- package/skills/ai-delivery-design-architecture/assets/architecture-template/prototype-architecture-validation.yaml +58 -0
- package/skills/ai-delivery-design-architecture/references/architecture-contract.md +32 -0
- package/skills/ai-delivery-design-experience/SKILL.md +71 -0
- package/skills/ai-delivery-design-experience/agents/openai.yaml +4 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/canvas-catalog.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/canvas-pages.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/component-state-matrix.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/design-tokens.json +19 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/experience-baseline.yaml +17 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/experience-change-domains.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/experience-change-set.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/feature-screen-coverage.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/formal-ui-confirmation.yaml +33 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/interaction-contract.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/low-fidelity-confirmation.yaml +32 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/page-catalog.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/page-component-map.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/product-prototype-reconciliation.yaml +28 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-adjustment-log.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-confirmation.yaml +99 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-file-terminals.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-files.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-impact-assessment.yaml +25 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-manifest.yaml +74 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-product-coverage.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-comments.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-decision.yaml +29 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-review-sessions.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-set.yaml +22 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-tool-candidates.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-tool-selection.yaml +22 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/prototype-traceability.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/requirement-change-set.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/screen-states.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/ui-framework-candidates.csv +1 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/ui-framework-selection.yaml +18 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/ui-handoff.yaml +44 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/visual-direction.yaml +16 -0
- package/skills/ai-delivery-design-experience/assets/experience-template/visual-system-confirmation.yaml +31 -0
- package/skills/ai-delivery-design-experience/references/experience-contract.md +82 -0
- package/skills/ai-delivery-design-experience/references/prototype-management-contract.md +68 -0
- package/skills/ai-delivery-design-tests/SKILL.md +56 -0
- package/skills/ai-delivery-design-tests/agents/openai.yaml +4 -0
- package/skills/ai-delivery-design-tests/assets/test-design-template/iteration-test-contract.md +19 -0
- package/skills/ai-delivery-design-tests/assets/test-design-template/prototype-test-scope.csv +1 -0
- package/skills/ai-delivery-design-tests/assets/test-design-template/prototype-test-validation.yaml +73 -0
- package/skills/ai-delivery-design-tests/references/test-design-contract.md +31 -0
- package/skills/ai-delivery-develop-iteration/SKILL.md +66 -0
- package/skills/ai-delivery-develop-iteration/agents/openai.yaml +4 -0
- package/skills/ai-delivery-develop-iteration/assets/development-template/task-experience-change-traceability.csv +1 -0
- package/skills/ai-delivery-develop-iteration/assets/development-template/task-prototype-traceability.csv +1 -0
- package/skills/ai-delivery-develop-iteration/references/development-contract.md +39 -0
- package/skills/ai-delivery-evolve-workflow/SKILL.md +57 -0
- package/skills/ai-delivery-evolve-workflow/agents/openai.yaml +4 -0
- package/skills/ai-delivery-evolve-workflow/references/evolution-contract.md +42 -0
- package/skills/ai-delivery-execute-test/SKILL.md +34 -0
- package/skills/ai-delivery-execute-test/agents/openai.yaml +4 -0
- package/skills/ai-delivery-execute-test/references/test-execution-contract.md +7 -0
- package/skills/ai-delivery-execute-work-package/SKILL.md +53 -0
- package/skills/ai-delivery-execute-work-package/agents/openai.yaml +4 -0
- package/skills/ai-delivery-execute-work-package/references/work-package-contract.md +20 -0
- package/skills/ai-delivery-manage-git/SKILL.md +60 -0
- package/skills/ai-delivery-manage-git/agents/openai.yaml +4 -0
- package/skills/ai-delivery-manage-git/references/git-policy.md +42 -0
- package/skills/ai-delivery-manage-standards/SKILL.md +44 -0
- package/skills/ai-delivery-manage-standards/agents/openai.yaml +4 -0
- package/skills/ai-delivery-manage-standards/assets/standards-template/standard.md +25 -0
- package/skills/ai-delivery-manage-standards/references/standards-contract.md +20 -0
- package/skills/ai-delivery-orchestrate/SKILL.md +105 -0
- package/skills/ai-delivery-orchestrate/agents/openai.yaml +4 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.codex/config.toml +92 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.gitattributes +6 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/artifact-registry.yaml +3 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/project.yaml +6 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/standards-baseline.yaml +8 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/delivery/workflow-state.yaml +34 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/decisions/.gitkeep +0 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/experiments/.gitkeep +0 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/feedback/.gitkeep +0 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/index.yaml +7 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/promotion/.gitkeep +0 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/evolution/proposals/.gitkeep +0 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/extensions/.gitkeep +0 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/standards/STD-ARTIFACT-PATH-001.md +47 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/standards/STD-DOC-LANG-001.md +62 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/standards/STD-TEST-LOCATION-001.md +23 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/standards/index.yaml +36 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/bootstrap/bootstrap.mjs +33 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/evolution/evolve.mjs +15 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/hooks/hook-event.mjs +131 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/state/state.mjs +21 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/app.js +546 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/index.html +199 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/public/styles.css +529 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/server.mjs +780 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.cmd +4 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.ps1 +2 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/.workflow/tools/viewer/start.sh +4 -0
- package/skills/ai-delivery-orchestrate/assets/project-template/AGENTS.md +44 -0
- package/skills/ai-delivery-orchestrate/references/formal-state-contract.md +64 -0
- package/skills/ai-delivery-orchestrate/references/workflow-model.md +29 -0
- package/skills/ai-delivery-orchestrate-release/SKILL.md +33 -0
- package/skills/ai-delivery-orchestrate-release/agents/openai.yaml +4 -0
- package/skills/ai-delivery-orchestrate-release/assets/release-request.yaml +9 -0
- package/skills/ai-delivery-orchestrate-release/assets/version-selection.yaml +16 -0
- package/skills/ai-delivery-orchestrate-release/references/release-workflow-contract.md +11 -0
- package/skills/ai-delivery-plan-iteration/SKILL.md +57 -0
- package/skills/ai-delivery-plan-iteration/agents/openai.yaml +4 -0
- package/skills/ai-delivery-plan-iteration/assets/planning-template/backlog.csv +1 -0
- package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-dependencies.csv +1 -0
- package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-experience-change-traceability.csv +1 -0
- package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-prototype-traceability.csv +1 -0
- package/skills/ai-delivery-plan-iteration/assets/planning-template/slice-sources.csv +1 -0
- package/skills/ai-delivery-plan-iteration/assets/planning-template/vertical-slices.csv +1 -0
- package/skills/ai-delivery-plan-iteration/references/planning-contract.md +40 -0
- package/skills/ai-delivery-prepare-platform/SKILL.md +49 -0
- package/skills/ai-delivery-prepare-platform/agents/openai.yaml +4 -0
- package/skills/ai-delivery-prepare-platform/assets/platform-template/environment-ledger.csv +1 -0
- package/skills/ai-delivery-prepare-platform/references/platform-contract.md +18 -0
- package/skills/ai-delivery-prepare-release/SKILL.md +47 -0
- package/skills/ai-delivery-prepare-release/agents/openai.yaml +4 -0
- package/skills/ai-delivery-prepare-release/assets/release-template/delivery-items.csv +1 -0
- package/skills/ai-delivery-prepare-release/assets/release-template/release-manifest.yaml +18 -0
- package/skills/ai-delivery-prepare-release/references/release-contract.md +21 -0
- package/skills/ai-delivery-review-change/SKILL.md +40 -0
- package/skills/ai-delivery-review-change/agents/openai.yaml +4 -0
- package/skills/ai-delivery-review-change/references/review-contract.md +20 -0
- package/skills/ai-delivery-triage-failure/SKILL.md +45 -0
- package/skills/ai-delivery-triage-failure/agents/openai.yaml +4 -0
- package/skills/ai-delivery-triage-failure/references/failure-routing-contract.md +13 -0
- package/skills/ai-delivery-validate-artifacts/SKILL.md +37 -0
- package/skills/ai-delivery-validate-artifacts/agents/openai.yaml +4 -0
- package/skills/ai-delivery-validate-artifacts/references/artifact-contract.md +44 -0
- package/skills/ai-delivery-verify-candidate/SKILL.md +56 -0
- package/skills/ai-delivery-verify-candidate/agents/openai.yaml +4 -0
- package/skills/ai-delivery-verify-candidate/assets/verification-template/defect-artifacts.csv +1 -0
- package/skills/ai-delivery-verify-candidate/assets/verification-template/defects.csv +1 -0
- package/skills/ai-delivery-verify-candidate/assets/verification-template/test-work-packages.csv +1 -0
- package/skills/ai-delivery-verify-candidate/references/candidate-verification-contract.md +23 -0
- package/skills/ai-delivery-verify-production/SKILL.md +50 -0
- package/skills/ai-delivery-verify-production/agents/openai.yaml +4 -0
- package/skills/ai-delivery-verify-production/assets/production-verification-template/production-verification.md +15 -0
- package/skills/ai-delivery-verify-production/references/production-verification-contract.md +14 -0
|
@@ -0,0 +1,1184 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { isSeq, parse as parseYaml, parseDocument } from "yaml";
|
|
9
|
+
import { inspectHookConfig, mergeHookConfig } from "./toml-hooks.mjs";
|
|
10
|
+
import {
|
|
11
|
+
LEGACY_WORKSPACE_CONFIG,
|
|
12
|
+
WORKSPACE_CONFIG,
|
|
13
|
+
gitCommonDirectory,
|
|
14
|
+
gitTopLevel,
|
|
15
|
+
initializeGitWorkspace,
|
|
16
|
+
readWorkspaceConfiguration,
|
|
17
|
+
workspaceConfigurationText,
|
|
18
|
+
} from "./workspace.mjs";
|
|
19
|
+
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
const PACKAGE_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
22
|
+
const MANAGED_START = "<!-- ai-delivery:start -->";
|
|
23
|
+
const MANAGED_END = "<!-- ai-delivery:end -->";
|
|
24
|
+
const IGNORE_ENTRIES = [".workflow/delivery/runtime/"];
|
|
25
|
+
const GIT_ATTRIBUTE_ENTRIES = [
|
|
26
|
+
"/.workflow/** text=auto eol=lf",
|
|
27
|
+
"/.agents/skills/ai-delivery-*/** text=auto eol=lf",
|
|
28
|
+
"/.codex/config.toml text eol=lf",
|
|
29
|
+
"/AGENTS.md text eol=lf",
|
|
30
|
+
"/.gitignore text eol=lf",
|
|
31
|
+
"/.gitattributes text eol=lf",
|
|
32
|
+
];
|
|
33
|
+
const OBSOLETE_IGNORE_ENTRIES = new Set([".ai-delivery-task", ".ai-delivery/backups/"]);
|
|
34
|
+
const INSTALL_MANIFEST = path.join(".workflow", "config", "install-manifest.json");
|
|
35
|
+
const ACTIVE_TASK = path.join(".workflow", "delivery", "runtime", "active-task.json");
|
|
36
|
+
const LEGACY_ACTIVE_TASK = ".ai-delivery-task";
|
|
37
|
+
const WORKFLOW_CONTENT_DIRECTORIES = ["product", "architecture", "experience", "iterations", "releases"];
|
|
38
|
+
const CONTROL_DIRECTORIES = ["tasks", "checkpoints", "artifacts", "gates", "versions", "releases", "events", "archives"];
|
|
39
|
+
const ESSENTIAL_STARTUP_SKILLS = [
|
|
40
|
+
"ai-delivery-assemble-release",
|
|
41
|
+
"ai-delivery-bootstrap",
|
|
42
|
+
"ai-delivery-checkpoint-task",
|
|
43
|
+
"ai-delivery-close-version",
|
|
44
|
+
"ai-delivery-evolve-workflow",
|
|
45
|
+
"ai-delivery-orchestrate",
|
|
46
|
+
"ai-delivery-orchestrate-release",
|
|
47
|
+
];
|
|
48
|
+
const DELIVERY_SKILLS = Object.freeze([
|
|
49
|
+
"ai-delivery-assemble-release",
|
|
50
|
+
"ai-delivery-bootstrap",
|
|
51
|
+
"ai-delivery-checkpoint-task",
|
|
52
|
+
"ai-delivery-close-version",
|
|
53
|
+
"ai-delivery-define-product",
|
|
54
|
+
"ai-delivery-deploy-production",
|
|
55
|
+
"ai-delivery-design-architecture",
|
|
56
|
+
"ai-delivery-design-experience",
|
|
57
|
+
"ai-delivery-design-tests",
|
|
58
|
+
"ai-delivery-develop-iteration",
|
|
59
|
+
"ai-delivery-evolve-workflow",
|
|
60
|
+
"ai-delivery-execute-test",
|
|
61
|
+
"ai-delivery-execute-work-package",
|
|
62
|
+
"ai-delivery-manage-git",
|
|
63
|
+
"ai-delivery-manage-standards",
|
|
64
|
+
"ai-delivery-orchestrate",
|
|
65
|
+
"ai-delivery-orchestrate-release",
|
|
66
|
+
"ai-delivery-plan-iteration",
|
|
67
|
+
"ai-delivery-prepare-platform",
|
|
68
|
+
"ai-delivery-prepare-release",
|
|
69
|
+
"ai-delivery-review-change",
|
|
70
|
+
"ai-delivery-triage-failure",
|
|
71
|
+
"ai-delivery-validate-artifacts",
|
|
72
|
+
"ai-delivery-verify-candidate",
|
|
73
|
+
"ai-delivery-verify-production",
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
function comparePackageVersions(left, right) {
|
|
77
|
+
const parse = (value) => {
|
|
78
|
+
const match = String(value || "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/);
|
|
79
|
+
if (!match) return null;
|
|
80
|
+
return {
|
|
81
|
+
core: match.slice(1, 4).map(Number),
|
|
82
|
+
prerelease: match[4] ? match[4].split(".") : [],
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
const current = parse(left);
|
|
86
|
+
const previous = parse(right);
|
|
87
|
+
if (!current || !previous) return null;
|
|
88
|
+
for (let index = 0; index < 3; index += 1) {
|
|
89
|
+
if (current.core[index] !== previous.core[index]) {
|
|
90
|
+
return current.core[index] > previous.core[index] ? 1 : -1;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!current.prerelease.length && !previous.prerelease.length) return 0;
|
|
94
|
+
if (!current.prerelease.length) return 1;
|
|
95
|
+
if (!previous.prerelease.length) return -1;
|
|
96
|
+
const length = Math.max(current.prerelease.length, previous.prerelease.length);
|
|
97
|
+
for (let index = 0; index < length; index += 1) {
|
|
98
|
+
const currentPart = current.prerelease[index];
|
|
99
|
+
const previousPart = previous.prerelease[index];
|
|
100
|
+
if (currentPart === undefined) return -1;
|
|
101
|
+
if (previousPart === undefined) return 1;
|
|
102
|
+
if (currentPart === previousPart) continue;
|
|
103
|
+
const currentNumeric = /^\d+$/.test(currentPart);
|
|
104
|
+
const previousNumeric = /^\d+$/.test(previousPart);
|
|
105
|
+
if (currentNumeric && previousNumeric) return Number(currentPart) > Number(previousPart) ? 1 : -1;
|
|
106
|
+
if (currentNumeric !== previousNumeric) return currentNumeric ? -1 : 1;
|
|
107
|
+
return currentPart > previousPart ? 1 : -1;
|
|
108
|
+
}
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isInside(candidate, root) {
|
|
113
|
+
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
114
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function samePath(left, right) {
|
|
118
|
+
const normalize = (value) => path.resolve(value).replaceAll("\\", "/").toLowerCase();
|
|
119
|
+
return normalize(left) === normalize(right);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function validateTarget(target, options = {}) {
|
|
123
|
+
if (!fs.existsSync(target) && options.create) fs.mkdirSync(target, { recursive: true });
|
|
124
|
+
if (!fs.existsSync(target) || !fs.statSync(target).isDirectory()) {
|
|
125
|
+
throw new Error(`Project directory does not exist: ${target}`);
|
|
126
|
+
}
|
|
127
|
+
const home = path.resolve(os.homedir());
|
|
128
|
+
const globalRoots = [path.join(home, ".codex"), path.join(home, ".agents")];
|
|
129
|
+
if (samePath(target, home) || globalRoots.some((root) => isInside(target, root))) {
|
|
130
|
+
throw new Error(`Refusing a global installation target: ${target}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function validateManagedPath(target, destination) {
|
|
135
|
+
const realTarget = fs.realpathSync(target);
|
|
136
|
+
let existingAncestor = destination;
|
|
137
|
+
while (!fs.existsSync(existingAncestor)) {
|
|
138
|
+
const parent = path.dirname(existingAncestor);
|
|
139
|
+
if (parent === existingAncestor) break;
|
|
140
|
+
existingAncestor = parent;
|
|
141
|
+
}
|
|
142
|
+
const realAncestor = fs.realpathSync(existingAncestor);
|
|
143
|
+
if (!isInside(realAncestor, realTarget)) {
|
|
144
|
+
throw new Error(`Managed path resolves outside the project: ${destination} -> ${realAncestor}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function atomicWrite(file, content) {
|
|
149
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
150
|
+
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
151
|
+
fs.writeFileSync(temp, content, "utf8");
|
|
152
|
+
fs.renameSync(temp, file);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function filesUnder(root) {
|
|
156
|
+
if (!fs.existsSync(root)) return [];
|
|
157
|
+
const files = [];
|
|
158
|
+
const visit = (directory) => {
|
|
159
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
160
|
+
const absolute = path.join(directory, entry.name);
|
|
161
|
+
if (entry.isDirectory()) visit(absolute);
|
|
162
|
+
else if (entry.isFile()) files.push(absolute);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
visit(root);
|
|
166
|
+
return files.sort((left, right) => left.localeCompare(right));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function normalizedManagedContent(file) {
|
|
170
|
+
const content = fs.readFileSync(file);
|
|
171
|
+
return content.includes(0) ? content : Buffer.from(content.toString("utf8").replaceAll("\r\n", "\n"));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function managedFilesEqual(left, right) {
|
|
175
|
+
return normalizedManagedContent(left).equals(normalizedManagedContent(right));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function directoryDigest(root) {
|
|
179
|
+
const hash = crypto.createHash("sha256");
|
|
180
|
+
for (const file of filesUnder(root)) {
|
|
181
|
+
hash.update(path.relative(root, file).replaceAll("\\", "/"));
|
|
182
|
+
hash.update("\0");
|
|
183
|
+
hash.update(normalizedManagedContent(file));
|
|
184
|
+
hash.update("\0");
|
|
185
|
+
}
|
|
186
|
+
return hash.digest("hex");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function managedFileDigest(file) {
|
|
190
|
+
return crypto.createHash("sha256").update(normalizedManagedContent(file)).digest("hex");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function sourceSetDigest(sources) {
|
|
194
|
+
const hash = crypto.createHash("sha256");
|
|
195
|
+
for (const item of [...sources].sort((left, right) => left.relative.localeCompare(right.relative))) {
|
|
196
|
+
hash.update(item.relative.replaceAll("\\", "/"));
|
|
197
|
+
hash.update("\0");
|
|
198
|
+
hash.update(normalizedManagedContent(item.source));
|
|
199
|
+
hash.update("\0");
|
|
200
|
+
}
|
|
201
|
+
return hash.digest("hex");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function packagedSkillNames(packageRoot) {
|
|
205
|
+
const sourceSkills = path.join(packageRoot, "skills");
|
|
206
|
+
const actual = fs.readdirSync(sourceSkills, { withFileTypes: true })
|
|
207
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith("ai-delivery-"))
|
|
208
|
+
.map((entry) => entry.name)
|
|
209
|
+
.sort();
|
|
210
|
+
if (JSON.stringify(actual) !== JSON.stringify(DELIVERY_SKILLS)) {
|
|
211
|
+
throw new Error(`Package skill inventory must be the fixed ${DELIVERY_SKILLS.length} skills.`);
|
|
212
|
+
}
|
|
213
|
+
return [...DELIVERY_SKILLS];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function mergeManagedMarkdown(existing, managedTemplate) {
|
|
217
|
+
const body = managedTemplate.trim();
|
|
218
|
+
const block = `${MANAGED_START}\n${body}\n${MANAGED_END}`;
|
|
219
|
+
const start = existing.indexOf(MANAGED_START);
|
|
220
|
+
const end = existing.indexOf(MANAGED_END);
|
|
221
|
+
if (start === -1 && end === -1) return `${existing.trimEnd()}${existing.trim() ? "\n\n" : ""}${block}\n`;
|
|
222
|
+
if (start === -1 || end === -1 || end < start) {
|
|
223
|
+
throw new Error("AGENTS.md contains an incomplete ai-delivery managed block.");
|
|
224
|
+
}
|
|
225
|
+
return `${existing.slice(0, start)}${block}${existing.slice(end + MANAGED_END.length)}`.replace(/\s*$/, "\n");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function mergeGitignore(existing, requiredEntries = []) {
|
|
229
|
+
const lines = existing.split(/\r?\n/)
|
|
230
|
+
.filter((line) => !OBSOLETE_IGNORE_ENTRIES.has(line.trim()));
|
|
231
|
+
const additions = [...IGNORE_ENTRIES, ...requiredEntries]
|
|
232
|
+
.filter((entry) => !lines.some((line) => line.trim() === entry));
|
|
233
|
+
const retained = lines.join("\n").trimEnd();
|
|
234
|
+
if (!additions.length) return retained ? `${retained}\n` : "";
|
|
235
|
+
return `${retained}${retained ? "\n" : ""}${additions.join("\n")}\n`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function mergeGitattributes(existing) {
|
|
239
|
+
const lines = existing.split(/\r?\n/);
|
|
240
|
+
const additions = GIT_ATTRIBUTE_ENTRIES
|
|
241
|
+
.filter((entry) => !lines.some((line) => line.trim() === entry));
|
|
242
|
+
const retained = lines.join("\n").trimEnd();
|
|
243
|
+
if (!additions.length) return retained ? `${retained}\n` : "";
|
|
244
|
+
return `${retained}${retained ? "\n" : ""}${additions.join("\n")}\n`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function ignoresWholeWorkflow(existing) {
|
|
248
|
+
return existing.split(/\r?\n/)
|
|
249
|
+
.map((line) => line.trim())
|
|
250
|
+
.some((line) => new Set([".workflow", ".workflow/", "/.workflow", "/.workflow/"]).has(line));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function mergeStandardsIndex(existing, template) {
|
|
254
|
+
const defaults = parseYaml(template)?.standards;
|
|
255
|
+
if (!Array.isArray(defaults)) throw new Error("Packaged standards/index.yaml is invalid.");
|
|
256
|
+
const document = parseDocument(existing || "standards: []\n");
|
|
257
|
+
if (document.errors.length) throw new Error(`Existing standards/index.yaml is invalid: ${document.errors[0].message}`);
|
|
258
|
+
let standards = document.get("standards", true);
|
|
259
|
+
if (!standards) {
|
|
260
|
+
document.set("standards", []);
|
|
261
|
+
standards = document.get("standards", true);
|
|
262
|
+
}
|
|
263
|
+
if (!isSeq(standards)) throw new Error("Existing standards/index.yaml must contain a standards sequence.");
|
|
264
|
+
const existingValues = document.toJS().standards || [];
|
|
265
|
+
const added = [];
|
|
266
|
+
for (const standard of defaults) {
|
|
267
|
+
const current = existingValues.find((item) => item?.id === standard.id);
|
|
268
|
+
if (current) {
|
|
269
|
+
for (const key of ["version", "status", "path"]) {
|
|
270
|
+
if (current[key] !== standard[key]) {
|
|
271
|
+
throw new Error(`Standard ${standard.id} conflicts on ${key}; resolve it through standards governance.`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
standards.add(document.createNode(standard));
|
|
277
|
+
added.push(standard.id);
|
|
278
|
+
}
|
|
279
|
+
return { text: document.toString(), added };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function copyMissingTree(source, destination, actions, dryRun) {
|
|
283
|
+
for (const sourceFile of filesUnder(source)) {
|
|
284
|
+
const relative = path.relative(source, sourceFile);
|
|
285
|
+
const destinationFile = path.join(destination, relative);
|
|
286
|
+
if (fs.existsSync(destinationFile)) continue;
|
|
287
|
+
actions.push(`create ${path.relative(path.dirname(destination), destinationFile)}`);
|
|
288
|
+
if (!dryRun) {
|
|
289
|
+
fs.mkdirSync(path.dirname(destinationFile), { recursive: true });
|
|
290
|
+
fs.copyFileSync(sourceFile, destinationFile);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function backupAndRemove(target, destination, backupRoot, actions, dryRun) {
|
|
296
|
+
const relative = path.relative(target, destination);
|
|
297
|
+
if (!isInside(destination, target)) throw new Error(`Managed path escapes project: ${destination}`);
|
|
298
|
+
actions.push(`back up and replace ${relative}`);
|
|
299
|
+
if (dryRun) return false;
|
|
300
|
+
const backup = path.join(backupRoot, relative);
|
|
301
|
+
fs.mkdirSync(path.dirname(backup), { recursive: true });
|
|
302
|
+
fs.cpSync(destination, backup, { recursive: true });
|
|
303
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function backupAndReplace(target, destination, source, backupRoot, actions, dryRun) {
|
|
308
|
+
if (!backupAndRemove(target, destination, backupRoot, actions, dryRun)) return;
|
|
309
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
310
|
+
fs.cpSync(source, destination, { recursive: true });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function stateRuntimeSources(packageRoot, sourceTemplate) {
|
|
314
|
+
const yamlRoot = path.dirname(require.resolve("yaml/package.json"));
|
|
315
|
+
if (!fs.existsSync(yamlRoot)) {
|
|
316
|
+
throw new Error(`The packaged YAML runtime is missing: ${yamlRoot}`);
|
|
317
|
+
}
|
|
318
|
+
return [
|
|
319
|
+
{
|
|
320
|
+
source: path.join(sourceTemplate, ".workflow", "tools", "state", "state.mjs"),
|
|
321
|
+
relative: "state.mjs",
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
source: path.join(packageRoot, "lib", "delivery-state.mjs"),
|
|
325
|
+
relative: "delivery-state.mjs",
|
|
326
|
+
},
|
|
327
|
+
...filesUnder(yamlRoot).map((source) => ({
|
|
328
|
+
source,
|
|
329
|
+
relative: path.join("vendor", "yaml", path.relative(yamlRoot, source)),
|
|
330
|
+
})),
|
|
331
|
+
];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function evolutionRuntimeSources(packageRoot, sourceTemplate) {
|
|
335
|
+
return [
|
|
336
|
+
{
|
|
337
|
+
source: path.join(sourceTemplate, ".workflow", "tools", "evolution", "evolve.mjs"),
|
|
338
|
+
relative: "evolve.mjs",
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
source: path.join(packageRoot, "lib", "evolution.mjs"),
|
|
342
|
+
relative: "evolution-runtime.mjs",
|
|
343
|
+
},
|
|
344
|
+
];
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function bootstrapRuntimeSources(packageRoot, sourceTemplate) {
|
|
348
|
+
return [
|
|
349
|
+
{
|
|
350
|
+
source: path.join(sourceTemplate, ".workflow", "tools", "bootstrap", "bootstrap.mjs"),
|
|
351
|
+
relative: "bootstrap.mjs",
|
|
352
|
+
},
|
|
353
|
+
...["project-bootstrap.mjs", "workspace.mjs", "yaml-runtime.mjs", "delivery-state.mjs"].map((name) => ({
|
|
354
|
+
source: path.join(packageRoot, "lib", name),
|
|
355
|
+
relative: name,
|
|
356
|
+
})),
|
|
357
|
+
];
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function managedRuntimeMatches(destination, sources) {
|
|
361
|
+
if (!fs.existsSync(destination)) return false;
|
|
362
|
+
const expected = new Map(sources.map((item) => [
|
|
363
|
+
item.relative.replaceAll("\\", "/"),
|
|
364
|
+
item.source,
|
|
365
|
+
]));
|
|
366
|
+
const actualFiles = filesUnder(destination);
|
|
367
|
+
if (actualFiles.length !== expected.size) return false;
|
|
368
|
+
return actualFiles.every((file) => {
|
|
369
|
+
const relative = path.relative(destination, file).replaceAll("\\", "/");
|
|
370
|
+
const source = expected.get(relative);
|
|
371
|
+
return source && managedFilesEqual(file, source);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function installManagedRuntime(destination, sources) {
|
|
376
|
+
fs.mkdirSync(destination, { recursive: true });
|
|
377
|
+
for (const item of sources) {
|
|
378
|
+
const target = path.join(destination, item.relative);
|
|
379
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
380
|
+
fs.copyFileSync(item.source, target);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function planManagedRuntime({
|
|
385
|
+
target,
|
|
386
|
+
installPath,
|
|
387
|
+
sources,
|
|
388
|
+
reinstallManagedRuntime,
|
|
389
|
+
conflicts,
|
|
390
|
+
}) {
|
|
391
|
+
const destination = path.join(target, ...installPath.split("/"));
|
|
392
|
+
if (!fs.existsSync(destination)) return { type: "copy", installPath, destination, sources };
|
|
393
|
+
if (managedRuntimeMatches(destination, sources)) return null;
|
|
394
|
+
if (reinstallManagedRuntime) return { type: "replace", installPath, destination, sources };
|
|
395
|
+
conflicts.push(path.relative(target, destination));
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function applyManagedRuntimePlan(target, plan, backupRoot, actions, dryRun) {
|
|
400
|
+
if (!plan) return;
|
|
401
|
+
if (plan.type === "copy") {
|
|
402
|
+
actions.push(`install ${plan.installPath}`);
|
|
403
|
+
if (!dryRun) installManagedRuntime(plan.destination, plan.sources);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (backupAndRemove(target, plan.destination, backupRoot, actions, dryRun)) {
|
|
407
|
+
installManagedRuntime(plan.destination, plan.sources);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function initProject(packageRoot, target, options = {}) {
|
|
412
|
+
const { dryRun = false, force = false, packageVersion = "0.0.0" } = options;
|
|
413
|
+
validateTarget(target, { create: !dryRun });
|
|
414
|
+
const backupRoot = path.join(
|
|
415
|
+
target,
|
|
416
|
+
".workflow",
|
|
417
|
+
"delivery",
|
|
418
|
+
"runtime",
|
|
419
|
+
"backups",
|
|
420
|
+
new Date().toISOString().replaceAll(":", "-"),
|
|
421
|
+
);
|
|
422
|
+
const legacyRoot = path.join(target, ".ai-delivery");
|
|
423
|
+
const legacyActiveTask = path.join(target, LEGACY_ACTIVE_TASK);
|
|
424
|
+
const hasLegacyRoot = fs.existsSync(legacyRoot);
|
|
425
|
+
const hasLegacyActiveTask = fs.existsSync(legacyActiveTask);
|
|
426
|
+
let previousManifest = null;
|
|
427
|
+
try {
|
|
428
|
+
previousManifest = JSON.parse(fs.readFileSync(path.join(target, INSTALL_MANIFEST), "utf8"));
|
|
429
|
+
} catch {
|
|
430
|
+
previousManifest = null;
|
|
431
|
+
}
|
|
432
|
+
const packageVersionComparison = previousManifest?.schema_version >= 5
|
|
433
|
+
&& previousManifest.package === "ai-delivery-workflow"
|
|
434
|
+
? comparePackageVersions(packageVersion, previousManifest.version)
|
|
435
|
+
: 0;
|
|
436
|
+
if (packageVersionComparison === null && previousManifest.version !== packageVersion && !force) {
|
|
437
|
+
throw new Error(`Cannot compare installed package version ${previousManifest.version} with ${packageVersion}; use --force only after verifying the intended replacement.`);
|
|
438
|
+
}
|
|
439
|
+
if (packageVersionComparison < 0 && !force) {
|
|
440
|
+
throw new Error(`Refusing package downgrade from ${previousManifest.version} to ${packageVersion}; use --force only after explicit approval.`);
|
|
441
|
+
}
|
|
442
|
+
const packageVersionUpgrade = packageVersionComparison > 0;
|
|
443
|
+
const reinstallManagedRuntime = force || hasLegacyRoot || hasLegacyActiveTask
|
|
444
|
+
|| packageVersionUpgrade || (previousManifest && previousManifest.schema_version < 5);
|
|
445
|
+
if (!dryRun && hasLegacyRoot) {
|
|
446
|
+
const legacyBackup = path.join(backupRoot, "legacy-ai-delivery");
|
|
447
|
+
fs.mkdirSync(path.dirname(legacyBackup), { recursive: true });
|
|
448
|
+
fs.cpSync(legacyRoot, legacyBackup, { recursive: true });
|
|
449
|
+
}
|
|
450
|
+
if (!dryRun && hasLegacyActiveTask) {
|
|
451
|
+
const taskBackup = path.join(backupRoot, "legacy-active-task.json");
|
|
452
|
+
fs.mkdirSync(path.dirname(taskBackup), { recursive: true });
|
|
453
|
+
fs.copyFileSync(legacyActiveTask, taskBackup);
|
|
454
|
+
}
|
|
455
|
+
const currentWorkspace = readWorkspaceConfiguration(target, { optional: true });
|
|
456
|
+
const legacyWorkspaceFile = path.join(target, LEGACY_WORKSPACE_CONFIG);
|
|
457
|
+
const legacyWorkspace = !currentWorkspace && fs.existsSync(legacyWorkspaceFile)
|
|
458
|
+
? readWorkspaceConfiguration(target, { configurationPath: LEGACY_WORKSPACE_CONFIG })
|
|
459
|
+
: null;
|
|
460
|
+
const existingWorkspace = (currentWorkspace || legacyWorkspace)?.configuration;
|
|
461
|
+
const codePath = options.codePath || existingWorkspace?.code_repository?.path || "code";
|
|
462
|
+
const codeId = options.codeId || existingWorkspace?.code_repository?.repo_id || "app";
|
|
463
|
+
const sharedRemote = options.sharedRemote || null;
|
|
464
|
+
const codeRemote = sharedRemote ? null : options.codeRemote || existingWorkspace?.code_repository?.remote || null;
|
|
465
|
+
const workflowRemote = sharedRemote ? null : options.workflowRemote || existingWorkspace?.workflow_repository?.remote || null;
|
|
466
|
+
const workspaceId = options.workspaceId || existingWorkspace?.workspace_id || null;
|
|
467
|
+
const workspace = initializeGitWorkspace(target, {
|
|
468
|
+
codePath,
|
|
469
|
+
codeId,
|
|
470
|
+
codeRemote,
|
|
471
|
+
workflowRemote,
|
|
472
|
+
sharedRemote,
|
|
473
|
+
workspaceId,
|
|
474
|
+
dryRun,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const sourceSkills = path.join(packageRoot, "skills");
|
|
478
|
+
const sourceTemplate = path.join(sourceSkills, "ai-delivery-orchestrate", "assets", "project-template");
|
|
479
|
+
const sourceWorkflow = path.join(sourceTemplate, ".workflow");
|
|
480
|
+
const sourceHook = path.join(sourceSkills, "ai-delivery-checkpoint-task", "scripts", "hook-event.mjs");
|
|
481
|
+
const sourceConfig = path.join(sourceSkills, "ai-delivery-checkpoint-task", "assets", "codex-hook-config.toml");
|
|
482
|
+
const sourceViewer = path.join(sourceTemplate, ".workflow", "tools", "viewer");
|
|
483
|
+
const bootstrapSources = bootstrapRuntimeSources(packageRoot, sourceTemplate);
|
|
484
|
+
const stateSources = stateRuntimeSources(packageRoot, sourceTemplate);
|
|
485
|
+
const evolutionSources = evolutionRuntimeSources(packageRoot, sourceTemplate);
|
|
486
|
+
const sourceStandardsIndex = path.join(sourceWorkflow, "standards", "index.yaml");
|
|
487
|
+
const workflowRoot = path.join(target, ".workflow");
|
|
488
|
+
const standardsIndexFile = path.join(workflowRoot, "standards", "index.yaml");
|
|
489
|
+
const legacyIndicators = [
|
|
490
|
+
path.join(target, "delivery", "project.yaml"),
|
|
491
|
+
path.join(target, "delivery", "workflow-state.yaml"),
|
|
492
|
+
path.join(target, "delivery", "artifact-registry.yaml"),
|
|
493
|
+
path.join(target, "delivery", "standards-baseline.yaml"),
|
|
494
|
+
path.join(target, "standards", "index.yaml"),
|
|
495
|
+
].filter((candidate) => fs.existsSync(candidate));
|
|
496
|
+
if (fs.existsSync(workflowRoot) && legacyIndicators.length) {
|
|
497
|
+
throw new Error(
|
|
498
|
+
"Detected both .workflow and legacy delivery/standards directories. "
|
|
499
|
+
+ "Resolve the split workflow state before initialization.",
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
if (legacyIndicators.length) {
|
|
503
|
+
throw new Error(
|
|
504
|
+
"Legacy workflow directories detected at delivery/ or standards/. "
|
|
505
|
+
+ "Move them under .workflow/ before upgrading; refusing to create split workflow state.",
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
for (const managedPath of [
|
|
509
|
+
".agents", ".codex", ".workflow", ".ai-delivery", "AGENTS.md", ".gitignore", ".gitattributes",
|
|
510
|
+
]) {
|
|
511
|
+
validateManagedPath(target, path.join(target, managedPath));
|
|
512
|
+
}
|
|
513
|
+
const skillNames = packagedSkillNames(packageRoot);
|
|
514
|
+
if (!skillNames.length) throw new Error(`No packaged skills found under ${sourceSkills}`);
|
|
515
|
+
|
|
516
|
+
const actions = [];
|
|
517
|
+
const conflicts = [];
|
|
518
|
+
const skillPlans = [];
|
|
519
|
+
for (const name of skillNames) {
|
|
520
|
+
const source = path.join(sourceSkills, name);
|
|
521
|
+
const destination = path.join(target, ".agents", "skills", name);
|
|
522
|
+
if (!fs.existsSync(destination)) skillPlans.push({ type: "copy", name, source, destination });
|
|
523
|
+
else if (directoryDigest(source) !== directoryDigest(destination)) {
|
|
524
|
+
if (reinstallManagedRuntime) skillPlans.push({ type: "replace", name, source, destination });
|
|
525
|
+
else conflicts.push(path.relative(target, destination));
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const hookDestination = path.join(target, ".workflow", "tools", "hooks", "hook-event.mjs");
|
|
530
|
+
const hookConflict = fs.existsSync(hookDestination)
|
|
531
|
+
&& !managedFilesEqual(hookDestination, sourceHook);
|
|
532
|
+
if (hookConflict && !reinstallManagedRuntime) conflicts.push(path.relative(target, hookDestination));
|
|
533
|
+
const viewerDestination = path.join(target, ".workflow", "tools", "viewer");
|
|
534
|
+
let viewerPlan = null;
|
|
535
|
+
if (!fs.existsSync(viewerDestination)) {
|
|
536
|
+
viewerPlan = { type: "copy", source: sourceViewer, destination: viewerDestination };
|
|
537
|
+
} else if (directoryDigest(sourceViewer) !== directoryDigest(viewerDestination)) {
|
|
538
|
+
if (reinstallManagedRuntime) viewerPlan = { type: "replace", source: sourceViewer, destination: viewerDestination };
|
|
539
|
+
else conflicts.push(path.relative(target, viewerDestination));
|
|
540
|
+
}
|
|
541
|
+
const runtimePlans = [
|
|
542
|
+
[".workflow/tools/state", stateSources],
|
|
543
|
+
[".workflow/tools/evolution", evolutionSources],
|
|
544
|
+
[".workflow/tools/bootstrap", bootstrapSources],
|
|
545
|
+
].map(([installPath, sources]) => planManagedRuntime({
|
|
546
|
+
target,
|
|
547
|
+
installPath,
|
|
548
|
+
sources,
|
|
549
|
+
reinstallManagedRuntime,
|
|
550
|
+
conflicts,
|
|
551
|
+
}));
|
|
552
|
+
|
|
553
|
+
const packagedStandards = parseYaml(fs.readFileSync(sourceStandardsIndex, "utf8")).standards || [];
|
|
554
|
+
for (const standard of packagedStandards) {
|
|
555
|
+
const sourceStandard = path.join(path.dirname(sourceStandardsIndex), standard.path);
|
|
556
|
+
const targetStandard = path.join(workflowRoot, "standards", standard.path);
|
|
557
|
+
const normalizedText = (file) => fs.readFileSync(file, "utf8").replaceAll("\r\n", "\n");
|
|
558
|
+
if (fs.existsSync(targetStandard) && normalizedText(targetStandard) !== normalizedText(sourceStandard)) {
|
|
559
|
+
throw new Error(`Standard ${standard.id} body conflicts with the packaged approved version; resolve it through standards governance.`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (conflicts.length) {
|
|
564
|
+
throw new Error(`Managed files already exist with different content:\n- ${conflicts.join("\n- ")}\nRerun with --force to back up and replace them.`);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const configFile = path.join(target, ".codex", "config.toml");
|
|
568
|
+
const existingConfig = fs.existsSync(configFile) ? fs.readFileSync(configFile, "utf8") : "";
|
|
569
|
+
const mergedConfig = existingConfig
|
|
570
|
+
? mergeHookConfig(existingConfig, { force })
|
|
571
|
+
: { text: fs.readFileSync(sourceConfig, "utf8"), actions: ["create project Hook configuration"] };
|
|
572
|
+
const existingStandardsIndex = fs.existsSync(standardsIndexFile)
|
|
573
|
+
? fs.readFileSync(standardsIndexFile, "utf8")
|
|
574
|
+
: "standards: []\n";
|
|
575
|
+
const mergedStandardsIndex = mergeStandardsIndex(
|
|
576
|
+
existingStandardsIndex,
|
|
577
|
+
fs.readFileSync(sourceStandardsIndex, "utf8"),
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
const agentsTemplate = fs.readFileSync(path.join(sourceTemplate, "AGENTS.md"), "utf8");
|
|
581
|
+
const agentsFile = path.join(target, "AGENTS.md");
|
|
582
|
+
const agentsText = mergeManagedMarkdown(fs.existsSync(agentsFile) ? fs.readFileSync(agentsFile, "utf8") : "", agentsTemplate);
|
|
583
|
+
const gitignoreFile = path.join(target, ".gitignore");
|
|
584
|
+
const existingGitignore = fs.existsSync(gitignoreFile) ? fs.readFileSync(gitignoreFile, "utf8") : "";
|
|
585
|
+
if (ignoresWholeWorkflow(existingGitignore)) {
|
|
586
|
+
throw new Error(
|
|
587
|
+
"The project .gitignore excludes the complete .workflow directory. "
|
|
588
|
+
+ "Remove that rule and ignore only .workflow/delivery/runtime/.",
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
const gitignoreText = mergeGitignore(
|
|
592
|
+
existingGitignore,
|
|
593
|
+
[`/${workspace.configuration.code_repository.path}/`],
|
|
594
|
+
);
|
|
595
|
+
const gitattributesFile = path.join(target, ".gitattributes");
|
|
596
|
+
const existingGitattributes = fs.existsSync(gitattributesFile)
|
|
597
|
+
? fs.readFileSync(gitattributesFile, "utf8")
|
|
598
|
+
: "";
|
|
599
|
+
const gitattributesText = mergeGitattributes(existingGitattributes);
|
|
600
|
+
if (hasLegacyRoot || hasLegacyActiveTask) {
|
|
601
|
+
actions.push("migrate legacy .ai-delivery runtime into .workflow");
|
|
602
|
+
if (!dryRun) {
|
|
603
|
+
if (hasLegacyActiveTask) {
|
|
604
|
+
const activeTask = path.join(target, ACTIVE_TASK);
|
|
605
|
+
if (!fs.existsSync(activeTask)) {
|
|
606
|
+
fs.mkdirSync(path.dirname(activeTask), { recursive: true });
|
|
607
|
+
fs.copyFileSync(legacyActiveTask, activeTask);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
for (const plan of skillPlans) {
|
|
614
|
+
if (plan.type === "copy") {
|
|
615
|
+
actions.push(`install .agents/skills/${plan.name}`);
|
|
616
|
+
if (!dryRun) {
|
|
617
|
+
fs.mkdirSync(path.dirname(plan.destination), { recursive: true });
|
|
618
|
+
fs.cpSync(plan.source, plan.destination, { recursive: true });
|
|
619
|
+
}
|
|
620
|
+
} else {
|
|
621
|
+
backupAndReplace(target, plan.destination, plan.source, backupRoot, actions, dryRun);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (!fs.existsSync(hookDestination)) {
|
|
626
|
+
actions.push("install .workflow/tools/hooks/hook-event.mjs");
|
|
627
|
+
if (!dryRun) {
|
|
628
|
+
fs.mkdirSync(path.dirname(hookDestination), { recursive: true });
|
|
629
|
+
fs.copyFileSync(sourceHook, hookDestination);
|
|
630
|
+
}
|
|
631
|
+
} else if (hookConflict) {
|
|
632
|
+
backupAndReplace(target, hookDestination, sourceHook, backupRoot, actions, dryRun);
|
|
633
|
+
}
|
|
634
|
+
if (viewerPlan?.type === "copy") {
|
|
635
|
+
actions.push("install .workflow/tools/viewer");
|
|
636
|
+
if (!dryRun) {
|
|
637
|
+
fs.mkdirSync(path.dirname(viewerDestination), { recursive: true });
|
|
638
|
+
fs.cpSync(sourceViewer, viewerDestination, { recursive: true });
|
|
639
|
+
}
|
|
640
|
+
} else if (viewerPlan?.type === "replace") {
|
|
641
|
+
backupAndReplace(target, viewerDestination, sourceViewer, backupRoot, actions, dryRun);
|
|
642
|
+
}
|
|
643
|
+
for (const runtimePlan of runtimePlans) {
|
|
644
|
+
applyManagedRuntimePlan(target, runtimePlan, backupRoot, actions, dryRun);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
copyMissingTree(path.join(sourceWorkflow, "delivery"), path.join(workflowRoot, "delivery"), actions, dryRun);
|
|
648
|
+
copyMissingTree(path.join(sourceWorkflow, "standards"), path.join(workflowRoot, "standards"), actions, dryRun);
|
|
649
|
+
copyMissingTree(path.join(sourceWorkflow, "evolution"), path.join(workflowRoot, "evolution"), actions, dryRun);
|
|
650
|
+
copyMissingTree(path.join(sourceWorkflow, "extensions"), path.join(workflowRoot, "extensions"), actions, dryRun);
|
|
651
|
+
for (const directory of ["events", "pending", "staging"]) {
|
|
652
|
+
const destination = path.join(workflowRoot, "delivery", "runtime", "evolution", directory);
|
|
653
|
+
if (!fs.existsSync(destination)) {
|
|
654
|
+
actions.push(`create .workflow/delivery/runtime/evolution/${directory}/`);
|
|
655
|
+
if (!dryRun) fs.mkdirSync(destination, { recursive: true });
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
for (const directory of WORKFLOW_CONTENT_DIRECTORIES) {
|
|
659
|
+
const destination = path.join(workflowRoot, directory);
|
|
660
|
+
if (!fs.existsSync(destination)) {
|
|
661
|
+
actions.push(`create .workflow/${directory}/`);
|
|
662
|
+
if (!dryRun) fs.mkdirSync(destination, { recursive: true });
|
|
663
|
+
}
|
|
664
|
+
const marker = path.join(destination, ".gitkeep");
|
|
665
|
+
if (!fs.existsSync(marker)) {
|
|
666
|
+
actions.push(`create .workflow/${directory}/.gitkeep`);
|
|
667
|
+
if (!dryRun) atomicWrite(marker, "");
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
for (const directory of CONTROL_DIRECTORIES) {
|
|
671
|
+
const destination = path.join(workflowRoot, "control", directory);
|
|
672
|
+
if (!fs.existsSync(destination)) {
|
|
673
|
+
actions.push(`create .workflow/control/${directory}/`);
|
|
674
|
+
if (!dryRun) fs.mkdirSync(destination, { recursive: true });
|
|
675
|
+
}
|
|
676
|
+
const marker = path.join(destination, ".gitkeep");
|
|
677
|
+
if (!fs.existsSync(marker)) {
|
|
678
|
+
actions.push(`create .workflow/control/${directory}/.gitkeep`);
|
|
679
|
+
if (!dryRun) atomicWrite(marker, "");
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (existingStandardsIndex !== mergedStandardsIndex.text) {
|
|
683
|
+
actions.push(...mergedStandardsIndex.added.map((id) => `register ${id} in .workflow/standards/index.yaml`));
|
|
684
|
+
if (!dryRun) atomicWrite(standardsIndexFile, mergedStandardsIndex.text);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (!fs.existsSync(agentsFile) || fs.readFileSync(agentsFile, "utf8") !== agentsText) {
|
|
688
|
+
actions.push("merge AI delivery instructions into AGENTS.md");
|
|
689
|
+
if (!dryRun) atomicWrite(agentsFile, agentsText);
|
|
690
|
+
}
|
|
691
|
+
if (!fs.existsSync(gitignoreFile) || fs.readFileSync(gitignoreFile, "utf8") !== gitignoreText) {
|
|
692
|
+
actions.push("merge local runtime entries into .gitignore");
|
|
693
|
+
if (!dryRun) atomicWrite(gitignoreFile, gitignoreText);
|
|
694
|
+
}
|
|
695
|
+
if (!fs.existsSync(gitattributesFile)
|
|
696
|
+
|| fs.readFileSync(gitattributesFile, "utf8") !== gitattributesText) {
|
|
697
|
+
actions.push("merge byte-stable workflow rules into .gitattributes");
|
|
698
|
+
if (!dryRun) atomicWrite(gitattributesFile, gitattributesText);
|
|
699
|
+
}
|
|
700
|
+
if (existingConfig !== mergedConfig.text) {
|
|
701
|
+
actions.push(...mergedConfig.actions.map((item) => `${item} in .codex/config.toml`));
|
|
702
|
+
if (!dryRun) atomicWrite(configFile, mergedConfig.text);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const workspaceFile = path.join(target, WORKSPACE_CONFIG);
|
|
706
|
+
const workspaceText = workspaceConfigurationText(workspace.configuration);
|
|
707
|
+
if (!fs.existsSync(workspaceFile) || fs.readFileSync(workspaceFile, "utf8") !== workspaceText) {
|
|
708
|
+
actions.push(`write ${WORKSPACE_CONFIG.replaceAll("\\", "/")}`);
|
|
709
|
+
if (!dryRun) atomicWrite(workspaceFile, workspaceText);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const manifestFile = path.join(target, INSTALL_MANIFEST);
|
|
713
|
+
let existingManifest = null;
|
|
714
|
+
try {
|
|
715
|
+
existingManifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
|
|
716
|
+
} catch {
|
|
717
|
+
existingManifest = null;
|
|
718
|
+
}
|
|
719
|
+
const manifest = {
|
|
720
|
+
schema_version: 5,
|
|
721
|
+
package: "ai-delivery-workflow",
|
|
722
|
+
version: packageVersion,
|
|
723
|
+
scope: "workspace",
|
|
724
|
+
skill_root: ".agents/skills",
|
|
725
|
+
workflow_root: ".workflow",
|
|
726
|
+
viewer_root: ".workflow/tools/viewer",
|
|
727
|
+
bootstrap_cli: ".workflow/tools/bootstrap/bootstrap.mjs",
|
|
728
|
+
state_cli: ".workflow/tools/state/state.mjs",
|
|
729
|
+
evolution_cli: ".workflow/tools/evolution/evolve.mjs",
|
|
730
|
+
hook_protocol_version: 1,
|
|
731
|
+
hook_cli: ".workflow/tools/hooks/hook-event.mjs",
|
|
732
|
+
code_repository: workspace.configuration.code_repository,
|
|
733
|
+
viewer_start: {
|
|
734
|
+
windows_cmd: ".workflow/tools/viewer/start.cmd",
|
|
735
|
+
powershell: ".workflow/tools/viewer/start.ps1",
|
|
736
|
+
unix: ".workflow/tools/viewer/start.sh",
|
|
737
|
+
},
|
|
738
|
+
installed_skills: skillNames,
|
|
739
|
+
managed_assets: {
|
|
740
|
+
skills: Object.fromEntries(skillNames.map((name) => [
|
|
741
|
+
name,
|
|
742
|
+
directoryDigest(path.join(sourceSkills, name)),
|
|
743
|
+
])),
|
|
744
|
+
hook: {
|
|
745
|
+
path: ".workflow/tools/hooks/hook-event.mjs",
|
|
746
|
+
sha256: managedFileDigest(sourceHook),
|
|
747
|
+
},
|
|
748
|
+
state_runtime: {
|
|
749
|
+
path: ".workflow/tools/state",
|
|
750
|
+
sha256: sourceSetDigest(stateSources),
|
|
751
|
+
},
|
|
752
|
+
evolution_runtime: {
|
|
753
|
+
path: ".workflow/tools/evolution",
|
|
754
|
+
sha256: sourceSetDigest(evolutionSources),
|
|
755
|
+
},
|
|
756
|
+
bootstrap_runtime: {
|
|
757
|
+
path: ".workflow/tools/bootstrap",
|
|
758
|
+
sha256: sourceSetDigest(bootstrapSources),
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
installed_at: existingManifest?.installed_at || new Date().toISOString(),
|
|
762
|
+
};
|
|
763
|
+
const manifestText = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
764
|
+
if (!fs.existsSync(manifestFile) || fs.readFileSync(manifestFile, "utf8") !== manifestText) {
|
|
765
|
+
actions.push("write .workflow/config/install-manifest.json");
|
|
766
|
+
if (!dryRun) atomicWrite(manifestFile, manifestText);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (!dryRun && (hasLegacyRoot || hasLegacyActiveTask)) {
|
|
770
|
+
for (const required of [
|
|
771
|
+
path.join(target, WORKSPACE_CONFIG),
|
|
772
|
+
path.join(target, INSTALL_MANIFEST),
|
|
773
|
+
path.join(target, ".workflow", "tools", "state", "state.mjs"),
|
|
774
|
+
path.join(target, ".workflow", "tools", "hooks", "hook-event.mjs"),
|
|
775
|
+
]) {
|
|
776
|
+
if (!fs.existsSync(required)) {
|
|
777
|
+
throw new Error(`Legacy migration is incomplete; preserved .ai-delivery for recovery: ${required}`);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (hasLegacyRoot) fs.rmSync(legacyRoot, { recursive: true, force: true });
|
|
781
|
+
if (hasLegacyActiveTask) fs.rmSync(legacyActiveTask, { force: true });
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
return {
|
|
785
|
+
actions: [...workspace.actions, ...actions],
|
|
786
|
+
summary: `${dryRun ? "Dry run" : "Initialized"} ${skillNames.length} workspace-scoped AI delivery skills in ${target}`
|
|
787
|
+
+ (dryRun
|
|
788
|
+
? ""
|
|
789
|
+
: "\nWorkflow viewer: .workflow\\tools\\viewer\\start.cmd (Windows) or sh ./.workflow/tools/viewer/start.sh"),
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
export function doctorProject(target, options = {}) {
|
|
794
|
+
validateTarget(target);
|
|
795
|
+
for (const managedPath of [".agents", ".codex", ".workflow", "AGENTS.md"]) {
|
|
796
|
+
validateManagedPath(target, path.join(target, managedPath));
|
|
797
|
+
}
|
|
798
|
+
const checks = [];
|
|
799
|
+
let workspace = null;
|
|
800
|
+
try {
|
|
801
|
+
workspace = readWorkspaceConfiguration(target);
|
|
802
|
+
checks.push({ ok: true, message: "workspace configuration is valid" });
|
|
803
|
+
} catch (error) {
|
|
804
|
+
checks.push({ ok: false, message: error.message });
|
|
805
|
+
}
|
|
806
|
+
const manifestFile = path.join(target, INSTALL_MANIFEST);
|
|
807
|
+
let manifest = null;
|
|
808
|
+
try {
|
|
809
|
+
manifest = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
|
|
810
|
+
checks.push({
|
|
811
|
+
ok: manifest.scope === "workspace" && manifest.schema_version === 5,
|
|
812
|
+
message: "installation manifest uses workspace scope",
|
|
813
|
+
});
|
|
814
|
+
} catch (error) {
|
|
815
|
+
checks.push({ ok: false, message: `installation manifest is missing or invalid: ${error.message}` });
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const workflowGit = gitTopLevel(target);
|
|
819
|
+
const codeGit = workspace ? gitTopLevel(workspace.codeRoot) : null;
|
|
820
|
+
const workflowCommonGit = workflowGit ? gitCommonDirectory(target) : null;
|
|
821
|
+
const codeCommonGit = codeGit && workspace ? gitCommonDirectory(workspace.codeRoot) : null;
|
|
822
|
+
checks.push({
|
|
823
|
+
ok: Boolean(workflowGit) && path.resolve(workflowGit).toLowerCase() === path.resolve(target).toLowerCase(),
|
|
824
|
+
message: "workflow repository has an independent Git root",
|
|
825
|
+
});
|
|
826
|
+
const missingControlDirectories = CONTROL_DIRECTORIES.filter((directory) =>
|
|
827
|
+
!fs.existsSync(path.join(target, ".workflow", "control", directory)));
|
|
828
|
+
checks.push({
|
|
829
|
+
ok: missingControlDirectories.length === 0,
|
|
830
|
+
message: missingControlDirectories.length
|
|
831
|
+
? `shared control shard directories are missing: ${missingControlDirectories.join(", ")}`
|
|
832
|
+
: "shared task, artifact, Gate, version, and release shard directories are present",
|
|
833
|
+
});
|
|
834
|
+
checks.push({
|
|
835
|
+
ok: Boolean(codeGit)
|
|
836
|
+
&& workspace
|
|
837
|
+
&& path.resolve(codeGit).toLowerCase() === path.resolve(workspace.codeRoot).toLowerCase()
|
|
838
|
+
&& path.resolve(codeGit).toLowerCase() !== path.resolve(workflowGit || target).toLowerCase(),
|
|
839
|
+
message: "code repository has a distinct nested Git root",
|
|
840
|
+
});
|
|
841
|
+
checks.push({
|
|
842
|
+
ok: Boolean(workflowCommonGit) && Boolean(codeCommonGit)
|
|
843
|
+
&& path.resolve(workflowCommonGit).toLowerCase() !== path.resolve(codeCommonGit).toLowerCase(),
|
|
844
|
+
message: "workflow and code repositories use independent Git common directories",
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
const packageRoot = options.packageRoot || PACKAGE_ROOT;
|
|
848
|
+
let expectedSkills = ESSENTIAL_STARTUP_SKILLS;
|
|
849
|
+
let expectedSkillDigests = {};
|
|
850
|
+
let expectedHook = null;
|
|
851
|
+
let expectedBootstrapSources = [];
|
|
852
|
+
let expectedStateSources = [];
|
|
853
|
+
let expectedEvolutionSources = [];
|
|
854
|
+
try {
|
|
855
|
+
expectedSkills = packagedSkillNames(packageRoot);
|
|
856
|
+
expectedSkillDigests = Object.fromEntries(expectedSkills.map((name) => [
|
|
857
|
+
name,
|
|
858
|
+
directoryDigest(path.join(packageRoot, "skills", name)),
|
|
859
|
+
]));
|
|
860
|
+
expectedHook = path.join(packageRoot, "skills", "ai-delivery-checkpoint-task", "scripts", "hook-event.mjs");
|
|
861
|
+
const sourceTemplate = path.join(packageRoot, "skills", "ai-delivery-orchestrate", "assets", "project-template");
|
|
862
|
+
expectedBootstrapSources = bootstrapRuntimeSources(packageRoot, sourceTemplate);
|
|
863
|
+
expectedStateSources = stateRuntimeSources(packageRoot, sourceTemplate);
|
|
864
|
+
expectedEvolutionSources = evolutionRuntimeSources(packageRoot, sourceTemplate);
|
|
865
|
+
} catch (error) {
|
|
866
|
+
checks.push({ ok: false, message: `packaged managed-asset baseline is unavailable: ${error.message}` });
|
|
867
|
+
}
|
|
868
|
+
const manifestSkills = Array.isArray(manifest?.installed_skills) ? [...manifest.installed_skills].sort() : [];
|
|
869
|
+
checks.push({
|
|
870
|
+
ok: JSON.stringify(manifestSkills) === JSON.stringify(expectedSkills),
|
|
871
|
+
message: JSON.stringify(manifestSkills) === JSON.stringify(expectedSkills)
|
|
872
|
+
? `installation manifest declares the fixed ${expectedSkills.length}-skill inventory`
|
|
873
|
+
: `installation manifest skill inventory differs from the fixed ${expectedSkills.length}-skill package inventory`,
|
|
874
|
+
});
|
|
875
|
+
const missingSkills = expectedSkills.filter((name) => !fs.existsSync(path.join(target, ".agents", "skills", name, "SKILL.md")));
|
|
876
|
+
checks.push({
|
|
877
|
+
ok: expectedSkills.length > 0 && missingSkills.length === 0,
|
|
878
|
+
message: missingSkills.length ? `missing project skills: ${missingSkills.join(", ")}` : `${expectedSkills.length} project skills are present`,
|
|
879
|
+
});
|
|
880
|
+
const damagedSkills = expectedSkills.filter((name) => {
|
|
881
|
+
const installed = path.join(target, ".agents", "skills", name);
|
|
882
|
+
return !fs.existsSync(installed) || directoryDigest(installed) !== expectedSkillDigests[name];
|
|
883
|
+
});
|
|
884
|
+
const manifestSkillDigests = manifest?.managed_assets?.skills || {};
|
|
885
|
+
const inconsistentSkillDigests = expectedSkills.filter((name) =>
|
|
886
|
+
manifestSkillDigests[name] !== expectedSkillDigests[name]);
|
|
887
|
+
checks.push({
|
|
888
|
+
ok: damagedSkills.length === 0 && inconsistentSkillDigests.length === 0,
|
|
889
|
+
message: damagedSkills.length || inconsistentSkillDigests.length
|
|
890
|
+
? `managed skill integrity failed: damaged=${damagedSkills.join(", ") || "none"}; manifest=${inconsistentSkillDigests.join(", ") || "none"}`
|
|
891
|
+
: "managed skill integrity matches the fixed package baseline",
|
|
892
|
+
});
|
|
893
|
+
try {
|
|
894
|
+
const config = inspectHookConfig(fs.readFileSync(path.join(target, ".codex", "config.toml"), "utf8"));
|
|
895
|
+
checks.push({ ok: config.hooksEnabled, message: "project Codex hooks feature is enabled" });
|
|
896
|
+
checks.push({ ok: config.registeredEvents.length === 10, message: `${config.registeredEvents.length}/10 delivery Hook events are registered` });
|
|
897
|
+
} catch (error) {
|
|
898
|
+
checks.push({ ok: false, message: `project Hook configuration is invalid: ${error.message}` });
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
const installedHook = path.join(target, ".workflow", "tools", "hooks", "hook-event.mjs");
|
|
902
|
+
const expectedHookDigest = expectedHook && fs.existsSync(expectedHook) ? managedFileDigest(expectedHook) : null;
|
|
903
|
+
const hookIntegrity = Boolean(expectedHookDigest)
|
|
904
|
+
&& fs.existsSync(installedHook)
|
|
905
|
+
&& managedFileDigest(installedHook) === expectedHookDigest
|
|
906
|
+
&& manifest?.managed_assets?.hook?.sha256 === expectedHookDigest;
|
|
907
|
+
checks.push({
|
|
908
|
+
ok: hookIntegrity,
|
|
909
|
+
message: hookIntegrity ? "project Hook integrity matches the package baseline" : "project Hook integrity check failed",
|
|
910
|
+
});
|
|
911
|
+
const viewerFiles = [
|
|
912
|
+
"server.mjs",
|
|
913
|
+
"start.cmd",
|
|
914
|
+
"start.ps1",
|
|
915
|
+
"start.sh",
|
|
916
|
+
"public/index.html",
|
|
917
|
+
"public/app.js",
|
|
918
|
+
"public/styles.css",
|
|
919
|
+
];
|
|
920
|
+
const missingViewerFiles = viewerFiles.filter((file) =>
|
|
921
|
+
!fs.existsSync(path.join(target, ".workflow", "tools", "viewer", file)));
|
|
922
|
+
checks.push({
|
|
923
|
+
ok: missingViewerFiles.length === 0,
|
|
924
|
+
required: false,
|
|
925
|
+
scope: "viewer",
|
|
926
|
+
message: missingViewerFiles.length
|
|
927
|
+
? `workflow viewer is incomplete: ${missingViewerFiles.join(", ")}`
|
|
928
|
+
: "project workflow viewer and startup scripts are present",
|
|
929
|
+
});
|
|
930
|
+
const stateFiles = [
|
|
931
|
+
"state.mjs",
|
|
932
|
+
"delivery-state.mjs",
|
|
933
|
+
"vendor/yaml/package.json",
|
|
934
|
+
"vendor/yaml/dist/index.js",
|
|
935
|
+
];
|
|
936
|
+
const missingStateFiles = stateFiles.filter((file) =>
|
|
937
|
+
!fs.existsSync(path.join(target, ".workflow", "tools", "state", file)));
|
|
938
|
+
checks.push({
|
|
939
|
+
ok: missingStateFiles.length === 0,
|
|
940
|
+
message: missingStateFiles.length
|
|
941
|
+
? `formal state CLI is incomplete: ${missingStateFiles.join(", ")}`
|
|
942
|
+
: "project formal state CLI and vendored YAML runtime are present",
|
|
943
|
+
});
|
|
944
|
+
const installedStateRoot = path.join(target, ".workflow", "tools", "state");
|
|
945
|
+
const expectedStateDigest = expectedStateSources.length ? sourceSetDigest(expectedStateSources) : null;
|
|
946
|
+
const stateIntegrity = Boolean(expectedStateDigest)
|
|
947
|
+
&& fs.existsSync(installedStateRoot)
|
|
948
|
+
&& directoryDigest(installedStateRoot) === expectedStateDigest
|
|
949
|
+
&& manifest?.managed_assets?.state_runtime?.sha256 === expectedStateDigest;
|
|
950
|
+
checks.push({
|
|
951
|
+
ok: stateIntegrity,
|
|
952
|
+
message: stateIntegrity
|
|
953
|
+
? "formal state runtime integrity matches the package baseline"
|
|
954
|
+
: "formal state runtime integrity check failed",
|
|
955
|
+
});
|
|
956
|
+
const installedBootstrapRoot = path.join(target, ".workflow", "tools", "bootstrap");
|
|
957
|
+
const bootstrapFiles = [
|
|
958
|
+
"bootstrap.mjs",
|
|
959
|
+
"project-bootstrap.mjs",
|
|
960
|
+
"workspace.mjs",
|
|
961
|
+
"yaml-runtime.mjs",
|
|
962
|
+
"delivery-state.mjs",
|
|
963
|
+
];
|
|
964
|
+
const missingBootstrapFiles = bootstrapFiles.filter((file) =>
|
|
965
|
+
!fs.existsSync(path.join(installedBootstrapRoot, file)));
|
|
966
|
+
checks.push({
|
|
967
|
+
ok: missingBootstrapFiles.length === 0,
|
|
968
|
+
message: missingBootstrapFiles.length
|
|
969
|
+
? `project bootstrap CLI is incomplete: ${missingBootstrapFiles.join(", ")}`
|
|
970
|
+
: "project bootstrap inspect runtime is present",
|
|
971
|
+
});
|
|
972
|
+
const expectedBootstrapDigest = expectedBootstrapSources.length
|
|
973
|
+
? sourceSetDigest(expectedBootstrapSources)
|
|
974
|
+
: null;
|
|
975
|
+
const bootstrapIntegrity = Boolean(expectedBootstrapDigest)
|
|
976
|
+
&& fs.existsSync(installedBootstrapRoot)
|
|
977
|
+
&& directoryDigest(installedBootstrapRoot) === expectedBootstrapDigest
|
|
978
|
+
&& manifest?.managed_assets?.bootstrap_runtime?.sha256 === expectedBootstrapDigest;
|
|
979
|
+
checks.push({
|
|
980
|
+
ok: bootstrapIntegrity,
|
|
981
|
+
message: bootstrapIntegrity
|
|
982
|
+
? "project bootstrap runtime integrity matches the package baseline"
|
|
983
|
+
: "project bootstrap runtime integrity check failed",
|
|
984
|
+
});
|
|
985
|
+
const bootstrapSmoke = spawnSync(
|
|
986
|
+
process.execPath,
|
|
987
|
+
[path.join(installedBootstrapRoot, "bootstrap.mjs"), "inspect"],
|
|
988
|
+
{ cwd: target, encoding: "utf8", windowsHide: true },
|
|
989
|
+
);
|
|
990
|
+
let bootstrapOutput = null;
|
|
991
|
+
try { bootstrapOutput = JSON.parse(bootstrapSmoke.stdout); } catch { bootstrapOutput = null; }
|
|
992
|
+
checks.push({
|
|
993
|
+
ok: bootstrapSmoke.status === 0 && Boolean(bootstrapOutput?.state),
|
|
994
|
+
message: bootstrapSmoke.status === 0 && bootstrapOutput?.state
|
|
995
|
+
? "project bootstrap inspect smoke check passes"
|
|
996
|
+
: `project bootstrap inspect smoke check failed: ${(bootstrapSmoke.stderr || bootstrapSmoke.error?.message || "no valid output").trim()}`,
|
|
997
|
+
});
|
|
998
|
+
const smokeResults = ["inspect", "verify"].map((command) => {
|
|
999
|
+
const result = spawnSync(
|
|
1000
|
+
process.execPath,
|
|
1001
|
+
[path.join(installedStateRoot, "state.mjs"), command],
|
|
1002
|
+
{ cwd: target, encoding: "utf8", windowsHide: true },
|
|
1003
|
+
);
|
|
1004
|
+
let output = null;
|
|
1005
|
+
try {
|
|
1006
|
+
output = JSON.parse(result.stdout);
|
|
1007
|
+
} catch {
|
|
1008
|
+
output = null;
|
|
1009
|
+
}
|
|
1010
|
+
const validOutput = command === "verify" ? output?.valid === true : output?.command === "inspect";
|
|
1011
|
+
return { ...result, command, validOutput };
|
|
1012
|
+
});
|
|
1013
|
+
const smokeOk = smokeResults.every((result) => result.status === 0 && result.validOutput);
|
|
1014
|
+
checks.push({
|
|
1015
|
+
ok: smokeOk,
|
|
1016
|
+
message: smokeOk
|
|
1017
|
+
? "formal state CLI inspect/verify smoke checks pass"
|
|
1018
|
+
: `formal state CLI smoke check failed: ${smokeResults.find((result) => result.status !== 0 || !result.validOutput)?.stderr.trim() || "no valid output"}`,
|
|
1019
|
+
});
|
|
1020
|
+
const installedEvolutionRoot = path.join(target, ".workflow", "tools", "evolution");
|
|
1021
|
+
const expectedEvolutionDigest = expectedEvolutionSources.length ? sourceSetDigest(expectedEvolutionSources) : null;
|
|
1022
|
+
const evolutionIntegrity = Boolean(expectedEvolutionDigest)
|
|
1023
|
+
&& fs.existsSync(installedEvolutionRoot)
|
|
1024
|
+
&& directoryDigest(installedEvolutionRoot) === expectedEvolutionDigest
|
|
1025
|
+
&& manifest?.managed_assets?.evolution_runtime?.sha256 === expectedEvolutionDigest;
|
|
1026
|
+
checks.push({
|
|
1027
|
+
ok: evolutionIntegrity,
|
|
1028
|
+
message: evolutionIntegrity
|
|
1029
|
+
? "workflow evolution runtime integrity matches the package baseline"
|
|
1030
|
+
: "workflow evolution runtime integrity check failed",
|
|
1031
|
+
});
|
|
1032
|
+
const evolutionSmoke = spawnSync(
|
|
1033
|
+
process.execPath,
|
|
1034
|
+
[path.join(installedEvolutionRoot, "evolve.mjs"), "inspect"],
|
|
1035
|
+
{ cwd: target, encoding: "utf8", windowsHide: true },
|
|
1036
|
+
);
|
|
1037
|
+
let evolutionOutput = null;
|
|
1038
|
+
try { evolutionOutput = JSON.parse(evolutionSmoke.stdout); } catch { evolutionOutput = null; }
|
|
1039
|
+
checks.push({
|
|
1040
|
+
ok: evolutionSmoke.status === 0 && evolutionOutput?.schema_version === 1,
|
|
1041
|
+
message: evolutionSmoke.status === 0 && evolutionOutput?.schema_version === 1
|
|
1042
|
+
? "workflow evolution CLI verify smoke check passes"
|
|
1043
|
+
: `workflow evolution CLI smoke check failed: ${evolutionSmoke.stderr.trim() || "no valid output"}`,
|
|
1044
|
+
});
|
|
1045
|
+
const evolutionVerify = spawnSync(
|
|
1046
|
+
process.execPath,
|
|
1047
|
+
[path.join(installedEvolutionRoot, "evolve.mjs"), "verify"],
|
|
1048
|
+
{ cwd: target, encoding: "utf8", windowsHide: true },
|
|
1049
|
+
);
|
|
1050
|
+
let evolutionVerifyOutput = null;
|
|
1051
|
+
try { evolutionVerifyOutput = JSON.parse(evolutionVerify.stdout); } catch { evolutionVerifyOutput = null; }
|
|
1052
|
+
checks.push({
|
|
1053
|
+
ok: evolutionVerifyOutput?.ok === true,
|
|
1054
|
+
required: false,
|
|
1055
|
+
scope: "evolution-extensions",
|
|
1056
|
+
message: evolutionVerifyOutput?.ok === false
|
|
1057
|
+
? `workflow extensions require quarantine or repair: ${(evolutionVerifyOutput.issues || []).map((item) => `${item.extension_id}@${item.version}: ${item.message}`).join("; ")}`
|
|
1058
|
+
: "registered workflow extensions pass checksum and manifest validation",
|
|
1059
|
+
});
|
|
1060
|
+
checks.push({
|
|
1061
|
+
ok: fs.existsSync(path.join(target, ".workflow", "delivery", "project.yaml")),
|
|
1062
|
+
message: "project workflow state uses .workflow/delivery",
|
|
1063
|
+
});
|
|
1064
|
+
checks.push({
|
|
1065
|
+
ok: fs.existsSync(path.join(target, ".workflow", "standards", "index.yaml")),
|
|
1066
|
+
message: "project standards use .workflow/standards",
|
|
1067
|
+
});
|
|
1068
|
+
checks.push({
|
|
1069
|
+
ok: fs.existsSync(path.join(target, ".workflow", "releases")),
|
|
1070
|
+
message: "independent releases use .workflow/releases",
|
|
1071
|
+
});
|
|
1072
|
+
const gitignoreFile = path.join(target, ".gitignore");
|
|
1073
|
+
const gitignore = fs.existsSync(gitignoreFile) ? fs.readFileSync(gitignoreFile, "utf8") : "";
|
|
1074
|
+
checks.push({
|
|
1075
|
+
ok: !ignoresWholeWorkflow(gitignore),
|
|
1076
|
+
message: ignoresWholeWorkflow(gitignore)
|
|
1077
|
+
? "project .gitignore excludes the complete .workflow directory"
|
|
1078
|
+
: "project .gitignore keeps durable .workflow documents trackable",
|
|
1079
|
+
});
|
|
1080
|
+
const gitattributesFile = path.join(target, ".gitattributes");
|
|
1081
|
+
const gitattributes = fs.existsSync(gitattributesFile)
|
|
1082
|
+
? fs.readFileSync(gitattributesFile, "utf8")
|
|
1083
|
+
: "";
|
|
1084
|
+
const missingAttributes = GIT_ATTRIBUTE_ENTRIES.filter((entry) =>
|
|
1085
|
+
!gitattributes.split(/\r?\n/).some((line) => line.trim() === entry));
|
|
1086
|
+
const attributeTargets = [...new Set([
|
|
1087
|
+
".workflow/delivery/workflow-state.yaml",
|
|
1088
|
+
".agents/skills/ai-delivery-orchestrate/SKILL.md",
|
|
1089
|
+
".codex/config.toml",
|
|
1090
|
+
"AGENTS.md",
|
|
1091
|
+
".gitignore",
|
|
1092
|
+
".gitattributes",
|
|
1093
|
+
...filesUnder(path.join(target, ".workflow"))
|
|
1094
|
+
.map((file) => path.relative(target, file).replaceAll("\\", "/")),
|
|
1095
|
+
...filesUnder(path.join(target, ".agents", "skills"))
|
|
1096
|
+
.map((file) => path.relative(target, file).replaceAll("\\", "/"))
|
|
1097
|
+
.filter((file) => file.startsWith(".agents/skills/ai-delivery-")),
|
|
1098
|
+
])];
|
|
1099
|
+
const attributeValues = new Map();
|
|
1100
|
+
let effectiveAttributesFailed = false;
|
|
1101
|
+
for (let index = 0; index < attributeTargets.length; index += 100) {
|
|
1102
|
+
const batch = attributeTargets.slice(index, index + 100);
|
|
1103
|
+
const effectiveAttributes = spawnSync(
|
|
1104
|
+
"git",
|
|
1105
|
+
["-c", "core.quotePath=false", "check-attr", "text", "eol", "--", ...batch],
|
|
1106
|
+
{ cwd: target, encoding: "utf8", windowsHide: true },
|
|
1107
|
+
);
|
|
1108
|
+
if (effectiveAttributes.status !== 0) {
|
|
1109
|
+
effectiveAttributesFailed = true;
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1112
|
+
for (const line of effectiveAttributes.stdout.split(/\r?\n/).filter(Boolean)) {
|
|
1113
|
+
const match = line.match(/^(.*): (text|eol): (.*)$/);
|
|
1114
|
+
if (match) attributeValues.set(`${match[1]}:${match[2]}`, match[3]);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
const invalidEffectiveAttributes = !effectiveAttributesFailed
|
|
1118
|
+
? attributeTargets.filter((file) => {
|
|
1119
|
+
const text = attributeValues.get(`${file}:text`);
|
|
1120
|
+
return !new Set(["set", "auto"]).has(text) || attributeValues.get(`${file}:eol`) !== "lf";
|
|
1121
|
+
})
|
|
1122
|
+
: [...attributeTargets];
|
|
1123
|
+
checks.push({
|
|
1124
|
+
ok: missingAttributes.length === 0 && invalidEffectiveAttributes.length === 0,
|
|
1125
|
+
message: missingAttributes.length
|
|
1126
|
+
? `project .gitattributes is missing byte-stable workflow rules: ${missingAttributes.join(", ")}`
|
|
1127
|
+
: invalidEffectiveAttributes.length
|
|
1128
|
+
? `project .gitattributes is overridden for checksummed files: ${invalidEffectiveAttributes.join(", ")}`
|
|
1129
|
+
: "project .gitattributes keeps checksummed workflow files at LF",
|
|
1130
|
+
});
|
|
1131
|
+
if (workspace) {
|
|
1132
|
+
const expectedIgnore = `/${workspace.configuration.code_repository.path}/`;
|
|
1133
|
+
checks.push({
|
|
1134
|
+
ok: gitignore.split(/\r?\n/).some((line) => line.trim() === expectedIgnore),
|
|
1135
|
+
message: `workflow repository ignores ${expectedIgnore}`,
|
|
1136
|
+
});
|
|
1137
|
+
const codeRemote = spawnSync("git", ["remote", "get-url", "origin"], {
|
|
1138
|
+
cwd: workspace.codeRoot,
|
|
1139
|
+
encoding: "utf8",
|
|
1140
|
+
windowsHide: true,
|
|
1141
|
+
});
|
|
1142
|
+
const configuredCodeRemote = workspace.configuration.code_repository.remote || null;
|
|
1143
|
+
checks.push({
|
|
1144
|
+
ok: configuredCodeRemote
|
|
1145
|
+
? codeRemote.status === 0 && codeRemote.stdout.trim() === configuredCodeRemote
|
|
1146
|
+
: true,
|
|
1147
|
+
message: configuredCodeRemote
|
|
1148
|
+
? "code repository origin matches workspace configuration"
|
|
1149
|
+
: "code repository has no required configured remote",
|
|
1150
|
+
});
|
|
1151
|
+
const workflowRemote = spawnSync("git", ["remote", "get-url", "origin"], {
|
|
1152
|
+
cwd: target,
|
|
1153
|
+
encoding: "utf8",
|
|
1154
|
+
windowsHide: true,
|
|
1155
|
+
});
|
|
1156
|
+
const configuredWorkflowRemote = workspace.configuration.workflow_repository.remote || null;
|
|
1157
|
+
checks.push({
|
|
1158
|
+
ok: configuredWorkflowRemote
|
|
1159
|
+
? workflowRemote.status === 0 && workflowRemote.stdout.trim() === configuredWorkflowRemote
|
|
1160
|
+
: true,
|
|
1161
|
+
message: configuredWorkflowRemote
|
|
1162
|
+
? "workflow repository origin matches workspace configuration"
|
|
1163
|
+
: "workflow repository has no required configured remote",
|
|
1164
|
+
});
|
|
1165
|
+
const workflowBranch = spawnSync("git", ["branch", "--show-current"], {
|
|
1166
|
+
cwd: target, encoding: "utf8", windowsHide: true,
|
|
1167
|
+
}).stdout.trim();
|
|
1168
|
+
checks.push({
|
|
1169
|
+
ok: workflowBranch === workspace.configuration.workflow_repository.branch,
|
|
1170
|
+
message: `workflow repository uses ${workspace.configuration.workflow_repository.branch}`,
|
|
1171
|
+
});
|
|
1172
|
+
checks.push({
|
|
1173
|
+
ok: workspace.configuration.repository_layout === "independent-git-roots"
|
|
1174
|
+
&& workspace.configuration.code_repository?.default_branch === "main",
|
|
1175
|
+
message: "workspace declares independent Git roots and code main",
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
checks.push({
|
|
1179
|
+
ok: !fs.existsSync(path.join(target, ".ai-delivery")) && !fs.existsSync(path.join(target, LEGACY_ACTIVE_TASK)),
|
|
1180
|
+
message: "legacy .ai-delivery paths are absent",
|
|
1181
|
+
});
|
|
1182
|
+
checks.push({ ok: fs.existsSync(path.join(target, "AGENTS.md")), message: "project AGENTS.md is present" });
|
|
1183
|
+
return { checks, ok: checks.filter((check) => check.required !== false).every((check) => check.ok) };
|
|
1184
|
+
}
|