ws-spec-kit 0.1.0-beta.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/LICENSE +201 -0
- package/README.md +79 -0
- package/dist/adapters/cli/output.d.ts +15 -0
- package/dist/adapters/cli/output.js +21 -0
- package/dist/adapters/cli/output.js.map +1 -0
- package/dist/adapters/cli/workflow.d.ts +25 -0
- package/dist/adapters/cli/workflow.js +131 -0
- package/dist/adapters/cli/workflow.js.map +1 -0
- package/dist/adapters/connectors/git-native.d.ts +23 -0
- package/dist/adapters/connectors/git-native.js +521 -0
- package/dist/adapters/connectors/git-native.js.map +1 -0
- package/dist/adapters/connectors/github-cli.d.ts +23 -0
- package/dist/adapters/connectors/github-cli.js +213 -0
- package/dist/adapters/connectors/github-cli.js.map +1 -0
- package/dist/adapters/connectors/gitlab-cli.d.ts +23 -0
- package/dist/adapters/connectors/gitlab-cli.js +199 -0
- package/dist/adapters/connectors/gitlab-cli.js.map +1 -0
- package/dist/adapters/connectors/lark-cli.d.ts +24 -0
- package/dist/adapters/connectors/lark-cli.js +296 -0
- package/dist/adapters/connectors/lark-cli.js.map +1 -0
- package/dist/adapters/process/redaction.d.ts +2 -0
- package/dist/adapters/process/redaction.js +51 -0
- package/dist/adapters/process/redaction.js.map +1 -0
- package/dist/adapters/process/spawn-json.d.ts +37 -0
- package/dist/adapters/process/spawn-json.js +312 -0
- package/dist/adapters/process/spawn-json.js.map +1 -0
- package/dist/adapters/skills/claude.d.ts +1 -0
- package/dist/adapters/skills/claude.js +3 -0
- package/dist/adapters/skills/claude.js.map +1 -0
- package/dist/adapters/skills/codex.d.ts +1 -0
- package/dist/adapters/skills/codex.js +3 -0
- package/dist/adapters/skills/codex.js.map +1 -0
- package/dist/adapters/skills/cursor.d.ts +1 -0
- package/dist/adapters/skills/cursor.js +3 -0
- package/dist/adapters/skills/cursor.js.map +1 -0
- package/dist/adapters/skills/generic.d.ts +1 -0
- package/dist/adapters/skills/generic.js +2 -0
- package/dist/adapters/skills/generic.js.map +1 -0
- package/dist/adapters/skills/install.d.ts +28 -0
- package/dist/adapters/skills/install.js +571 -0
- package/dist/adapters/skills/install.js.map +1 -0
- package/dist/application/acquire.d.ts +60 -0
- package/dist/application/acquire.js +964 -0
- package/dist/application/acquire.js.map +1 -0
- package/dist/application/application.d.ts +21 -0
- package/dist/application/application.js +31 -0
- package/dist/application/application.js.map +1 -0
- package/dist/application/artifact-reader.d.ts +1 -0
- package/dist/application/artifact-reader.js +150 -0
- package/dist/application/artifact-reader.js.map +1 -0
- package/dist/application/artifact-writer.d.ts +1 -0
- package/dist/application/artifact-writer.js +268 -0
- package/dist/application/artifact-writer.js.map +1 -0
- package/dist/application/artifact.d.ts +28 -0
- package/dist/application/artifact.js +523 -0
- package/dist/application/artifact.js.map +1 -0
- package/dist/application/decide.d.ts +12 -0
- package/dist/application/decide.js +168 -0
- package/dist/application/decide.js.map +1 -0
- package/dist/application/doctor-connectors.d.ts +34 -0
- package/dist/application/doctor-connectors.js +99 -0
- package/dist/application/doctor-connectors.js.map +1 -0
- package/dist/application/external-action.d.ts +164 -0
- package/dist/application/external-action.js +670 -0
- package/dist/application/external-action.js.map +1 -0
- package/dist/application/initial-stages.d.ts +3 -0
- package/dist/application/initial-stages.js +7 -0
- package/dist/application/initial-stages.js.map +1 -0
- package/dist/application/inspect.d.ts +2 -0
- package/dist/application/inspect.js +31 -0
- package/dist/application/inspect.js.map +1 -0
- package/dist/application/profile.d.ts +32 -0
- package/dist/application/profile.js +70 -0
- package/dist/application/profile.js.map +1 -0
- package/dist/application/snapshot.d.ts +75 -0
- package/dist/application/snapshot.js +338 -0
- package/dist/application/snapshot.js.map +1 -0
- package/dist/application/start.d.ts +43 -0
- package/dist/application/start.js +360 -0
- package/dist/application/start.js.map +1 -0
- package/dist/application/state.d.ts +17 -0
- package/dist/application/state.js +94 -0
- package/dist/application/state.js.map +1 -0
- package/dist/application/submit.d.ts +18 -0
- package/dist/application/submit.js +1090 -0
- package/dist/application/submit.js.map +1 -0
- package/dist/cli/commands/core.d.ts +2 -0
- package/dist/cli/commands/core.js +205 -0
- package/dist/cli/commands/core.js.map +1 -0
- package/dist/cli/commands/public-contract.d.ts +12 -0
- package/dist/cli/commands/public-contract.js +47 -0
- package/dist/cli/commands/public-contract.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +23 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/domain/artifacts.d.ts +52 -0
- package/dist/domain/artifacts.js +241 -0
- package/dist/domain/artifacts.js.map +1 -0
- package/dist/domain/digests.d.ts +11 -0
- package/dist/domain/digests.js +106 -0
- package/dist/domain/digests.js.map +1 -0
- package/dist/domain/external-receipt.d.ts +48 -0
- package/dist/domain/external-receipt.js +138 -0
- package/dist/domain/external-receipt.js.map +1 -0
- package/dist/domain/ids.d.ts +7 -0
- package/dist/domain/ids.js +5 -0
- package/dist/domain/ids.js.map +1 -0
- package/dist/domain/repository-path.d.ts +3 -0
- package/dist/domain/repository-path.js +87 -0
- package/dist/domain/repository-path.js.map +1 -0
- package/dist/domain/states.d.ts +38 -0
- package/dist/domain/states.js +69 -0
- package/dist/domain/states.js.map +1 -0
- package/dist/domain/work-package-identity.d.ts +2 -0
- package/dist/domain/work-package-identity.js +10 -0
- package/dist/domain/work-package-identity.js.map +1 -0
- package/dist/domain/workflow.d.ts +58 -0
- package/dist/domain/workflow.js +2 -0
- package/dist/domain/workflow.js.map +1 -0
- package/dist/engine/actor-roles.d.ts +15 -0
- package/dist/engine/actor-roles.js +53 -0
- package/dist/engine/actor-roles.js.map +1 -0
- package/dist/engine/approvals.d.ts +44 -0
- package/dist/engine/approvals.js +239 -0
- package/dist/engine/approvals.js.map +1 -0
- package/dist/engine/archive.d.ts +24 -0
- package/dist/engine/archive.js +442 -0
- package/dist/engine/archive.js.map +1 -0
- package/dist/engine/compiler.d.ts +25 -0
- package/dist/engine/compiler.js +828 -0
- package/dist/engine/compiler.js.map +1 -0
- package/dist/engine/control/condition.d.ts +10 -0
- package/dist/engine/control/condition.js +49 -0
- package/dist/engine/control/condition.js.map +1 -0
- package/dist/engine/control/loop.d.ts +23 -0
- package/dist/engine/control/loop.js +96 -0
- package/dist/engine/control/loop.js.map +1 -0
- package/dist/engine/control/retry.d.ts +15 -0
- package/dist/engine/control/retry.js +54 -0
- package/dist/engine/control/retry.js.map +1 -0
- package/dist/engine/docs-integrity.d.ts +13 -0
- package/dist/engine/docs-integrity.js +34 -0
- package/dist/engine/docs-integrity.js.map +1 -0
- package/dist/engine/expressions/ast.d.ts +18 -0
- package/dist/engine/expressions/ast.js +9 -0
- package/dist/engine/expressions/ast.js.map +1 -0
- package/dist/engine/expressions/evaluate.d.ts +7 -0
- package/dist/engine/expressions/evaluate.js +47 -0
- package/dist/engine/expressions/evaluate.js.map +1 -0
- package/dist/engine/expressions/parser.d.ts +2 -0
- package/dist/engine/expressions/parser.js +173 -0
- package/dist/engine/expressions/parser.js.map +1 -0
- package/dist/engine/external-effects/authorization.d.ts +146 -0
- package/dist/engine/external-effects/authorization.js +222 -0
- package/dist/engine/external-effects/authorization.js.map +1 -0
- package/dist/engine/external-effects/idempotency.d.ts +14 -0
- package/dist/engine/external-effects/idempotency.js +26 -0
- package/dist/engine/external-effects/idempotency.js.map +1 -0
- package/dist/engine/external-effects/reconciliation.d.ts +11 -0
- package/dist/engine/external-effects/reconciliation.js +18 -0
- package/dist/engine/external-effects/reconciliation.js.map +1 -0
- package/dist/engine/results.d.ts +14 -0
- package/dist/engine/results.js +88 -0
- package/dist/engine/results.js.map +1 -0
- package/dist/engine/scheduler.d.ts +60 -0
- package/dist/engine/scheduler.js +225 -0
- package/dist/engine/scheduler.js.map +1 -0
- package/dist/engine/tdd/green-gate.d.ts +6 -0
- package/dist/engine/tdd/green-gate.js +72 -0
- package/dist/engine/tdd/green-gate.js.map +1 -0
- package/dist/engine/tdd/red-gate.d.ts +30 -0
- package/dist/engine/tdd/red-gate.js +531 -0
- package/dist/engine/tdd/red-gate.js.map +1 -0
- package/dist/engine/tdd/types.d.ts +89 -0
- package/dist/engine/tdd/types.js +38 -0
- package/dist/engine/tdd/types.js.map +1 -0
- package/dist/engine/verification.d.ts +56 -0
- package/dist/engine/verification.js +196 -0
- package/dist/engine/verification.js.map +1 -0
- package/dist/policy/profile.d.ts +24 -0
- package/dist/policy/profile.js +23 -0
- package/dist/policy/profile.js.map +1 -0
- package/dist/policy/risk.d.ts +33 -0
- package/dist/policy/risk.js +138 -0
- package/dist/policy/risk.js.map +1 -0
- package/dist/protocol/application.d.ts +196 -0
- package/dist/protocol/application.js +2 -0
- package/dist/protocol/application.js.map +1 -0
- package/dist/protocol/public-contract.d.ts +81 -0
- package/dist/protocol/public-contract.js +231 -0
- package/dist/protocol/public-contract.js.map +1 -0
- package/dist/protocol/work-package.d.ts +55 -0
- package/dist/protocol/work-package.js +2 -0
- package/dist/protocol/work-package.js.map +1 -0
- package/dist/registry/connectors/external-executor.d.ts +4 -0
- package/dist/registry/connectors/external-executor.js +249 -0
- package/dist/registry/connectors/external-executor.js.map +1 -0
- package/dist/registry/connectors/feishu-document.d.ts +21 -0
- package/dist/registry/connectors/feishu-document.js +197 -0
- package/dist/registry/connectors/feishu-document.js.map +1 -0
- package/dist/registry/connectors/git-commit.d.ts +32 -0
- package/dist/registry/connectors/git-commit.js +122 -0
- package/dist/registry/connectors/git-commit.js.map +1 -0
- package/dist/registry/connectors/issue.d.ts +68 -0
- package/dist/registry/connectors/issue.js +301 -0
- package/dist/registry/connectors/issue.js.map +1 -0
- package/dist/registry/connectors/knowledge-publish.d.ts +30 -0
- package/dist/registry/connectors/knowledge-publish.js +157 -0
- package/dist/registry/connectors/knowledge-publish.js.map +1 -0
- package/dist/registry/connectors/local-requirement.d.ts +19 -0
- package/dist/registry/connectors/local-requirement.js +177 -0
- package/dist/registry/connectors/local-requirement.js.map +1 -0
- package/dist/registry/connectors/manifest.d.ts +6 -0
- package/dist/registry/connectors/manifest.js +164 -0
- package/dist/registry/connectors/manifest.js.map +1 -0
- package/dist/registry/connectors/registry.d.ts +11 -0
- package/dist/registry/connectors/registry.js +34 -0
- package/dist/registry/connectors/registry.js.map +1 -0
- package/dist/registry/connectors/requirement-source.d.ts +46 -0
- package/dist/registry/connectors/requirement-source.js +513 -0
- package/dist/registry/connectors/requirement-source.js.map +1 -0
- package/dist/registry/connectors/runtime.d.ts +38 -0
- package/dist/registry/connectors/runtime.js +127 -0
- package/dist/registry/connectors/runtime.js.map +1 -0
- package/dist/registry/connectors/secret-detector.d.ts +19 -0
- package/dist/registry/connectors/secret-detector.js +85 -0
- package/dist/registry/connectors/secret-detector.js.map +1 -0
- package/dist/registry/connectors/semver.d.ts +8 -0
- package/dist/registry/connectors/semver.js +57 -0
- package/dist/registry/connectors/semver.js.map +1 -0
- package/dist/registry/connectors/types.d.ts +45 -0
- package/dist/registry/connectors/types.js +2 -0
- package/dist/registry/connectors/types.js.map +1 -0
- package/dist/registry/executors/registry.d.ts +15 -0
- package/dist/registry/executors/registry.js +68 -0
- package/dist/registry/executors/registry.js.map +1 -0
- package/dist/registry/executors/types.d.ts +19 -0
- package/dist/registry/executors/types.js +2 -0
- package/dist/registry/executors/types.js.map +1 -0
- package/dist/registry/skills/lock.d.ts +3 -0
- package/dist/registry/skills/lock.js +278 -0
- package/dist/registry/skills/lock.js.map +1 -0
- package/dist/registry/skills/resolver.d.ts +16 -0
- package/dist/registry/skills/resolver.js +353 -0
- package/dist/registry/skills/resolver.js.map +1 -0
- package/dist/registry/skills/types.d.ts +75 -0
- package/dist/registry/skills/types.js +9 -0
- package/dist/registry/skills/types.js.map +1 -0
- package/dist/resources/catalog.d.ts +20 -0
- package/dist/resources/catalog.js +111 -0
- package/dist/resources/catalog.js.map +1 -0
- package/dist/schemas/definitions.d.ts +5 -0
- package/dist/schemas/definitions.js +958 -0
- package/dist/schemas/definitions.js.map +1 -0
- package/dist/schemas/generate.d.ts +1 -0
- package/dist/schemas/generate.js +26 -0
- package/dist/schemas/generate.js.map +1 -0
- package/dist/schemas/index.d.ts +11 -0
- package/dist/schemas/index.js +54 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/storage/control-plane.d.ts +136 -0
- package/dist/storage/control-plane.js +688 -0
- package/dist/storage/control-plane.js.map +1 -0
- package/dist/storage/events.d.ts +36 -0
- package/dist/storage/events.js +185 -0
- package/dist/storage/events.js.map +1 -0
- package/dist/storage/external-action-payload.d.ts +33 -0
- package/dist/storage/external-action-payload.js +190 -0
- package/dist/storage/external-action-payload.js.map +1 -0
- package/dist/storage/files.d.ts +13 -0
- package/dist/storage/files.js +45 -0
- package/dist/storage/files.js.map +1 -0
- package/dist/storage/git.d.ts +4 -0
- package/dist/storage/git.js +21 -0
- package/dist/storage/git.js.map +1 -0
- package/dist/storage/project-config.d.ts +11 -0
- package/dist/storage/project-config.js +60 -0
- package/dist/storage/project-config.js.map +1 -0
- package/dist/storage/repository.d.ts +14 -0
- package/dist/storage/repository.js +151 -0
- package/dist/storage/repository.js.map +1 -0
- package/dist/storage/work-items.d.ts +77 -0
- package/dist/storage/work-items.js +373 -0
- package/dist/storage/work-items.js.map +1 -0
- package/dist/storage/workflow-trust.d.ts +38 -0
- package/dist/storage/workflow-trust.js +310 -0
- package/dist/storage/workflow-trust.js.map +1 -0
- package/dist/workflow-package/loader.d.ts +12 -0
- package/dist/workflow-package/loader.js +249 -0
- package/dist/workflow-package/loader.js.map +1 -0
- package/dist/workflow-package/lock.d.ts +6 -0
- package/dist/workflow-package/lock.js +12 -0
- package/dist/workflow-package/lock.js.map +1 -0
- package/dist/workflow-package/path-boundary.d.ts +5 -0
- package/dist/workflow-package/path-boundary.js +38 -0
- package/dist/workflow-package/path-boundary.js.map +1 -0
- package/dist/workflow-package/trust.d.ts +27 -0
- package/dist/workflow-package/trust.js +61 -0
- package/dist/workflow-package/trust.js.map +1 -0
- package/dist/workflow-package/types.d.ts +176 -0
- package/dist/workflow-package/types.js +9 -0
- package/dist/workflow-package/types.js.map +1 -0
- package/dist/workflow-package/workflow-v1.d.ts +3 -0
- package/dist/workflow-package/workflow-v1.js +274 -0
- package/dist/workflow-package/workflow-v1.js.map +1 -0
- package/package.json +40 -0
- package/resources/catalog.yaml +37 -0
- package/resources/connectors/github-cli.yaml +24 -0
- package/resources/connectors/gitlab-cli.yaml +24 -0
- package/resources/connectors/lark-cli.yaml +28 -0
- package/resources/skills/bug-fixing/SKILL.md +16 -0
- package/resources/skills/code-review/SKILL.md +16 -0
- package/resources/skills/documentation-editing/SKILL.md +16 -0
- package/resources/skills/documentation-exploration/SKILL.md +16 -0
- package/resources/skills/documentation-review/SKILL.md +16 -0
- package/resources/skills/requirement-exploration/SKILL.md +16 -0
- package/resources/skills/solution-design/SKILL.md +16 -0
- package/resources/skills/task-planning/SKILL.md +16 -0
- package/resources/skills/tdd-implementation/SKILL.md +16 -0
- package/resources/workflows/documentation-delivery/manifest.yaml +8 -0
- package/resources/workflows/documentation-delivery/profiles/governed.yaml +23 -0
- package/resources/workflows/documentation-delivery/profiles/quick.yaml +23 -0
- package/resources/workflows/documentation-delivery/profiles/standard.yaml +21 -0
- package/resources/workflows/documentation-delivery/workflow.yaml +109 -0
- package/resources/workflows/feature-delivery/manifest.yaml +8 -0
- package/resources/workflows/feature-delivery/profiles/governed.yaml +27 -0
- package/resources/workflows/feature-delivery/profiles/quick.yaml +25 -0
- package/resources/workflows/feature-delivery/profiles/standard.yaml +26 -0
- package/resources/workflows/feature-delivery/workflow.yaml +150 -0
- package/schemas/builtin-agent-action-v1.schema.json +509 -0
- package/schemas/builtin-application-acquire-input-v1.schema.json +25 -0
- package/schemas/builtin-application-artifact-create-input-v1.schema.json +49 -0
- package/schemas/builtin-application-decision-input-v1.schema.json +284 -0
- package/schemas/builtin-application-inspect-input-v1.schema.json +20 -0
- package/schemas/builtin-application-project-config-snapshot-v1.schema.json +289 -0
- package/schemas/builtin-application-project-config-v1.schema.json +293 -0
- package/schemas/builtin-application-start-input-v1.schema.json +92 -0
- package/schemas/builtin-application-submit-input-v1.schema.json +145 -0
- package/schemas/builtin-artifact-v1.schema.json +48 -0
- package/schemas/builtin-evidence-v1.schema.json +65 -0
- package/schemas/builtin-external-action-grant-v1.schema.json +298 -0
- package/schemas/builtin-external-action-request-v1.schema.json +288 -0
- package/schemas/builtin-external-binding-v1.schema.json +59 -0
- package/schemas/builtin-external-receipt-v1.schema.json +109 -0
- package/schemas/builtin-external-write-receipt-v1.schema.json +253 -0
- package/schemas/builtin-source-artifact-v1.schema.json +293 -0
- package/schemas/builtin-submit-result-v1.schema.json +111 -0
- package/schemas/builtin-tdd-cycle-evidence-v1.schema.json +119 -0
- package/schemas/builtin-tdd-node-test-report-v1.schema.json +103 -0
- package/schemas/builtin-tdd-trusted-evidence-v1.schema.json +184 -0
- package/schemas/builtin-work-item-v1.schema.json +201 -0
- package/schemas/builtin-work-package-v1.schema.json +254 -0
- package/schemas/builtin-workflow-selection-v1.schema.json +40 -0
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
import { sha256 } from "../domain/digests.js";
|
|
2
|
+
import { isRepositoryRelativePattern, matchesRepositoryPath } from "../domain/repository-path.js";
|
|
3
|
+
import { ExpressionError } from "./expressions/ast.js";
|
|
4
|
+
import { parseExpression } from "./expressions/parser.js";
|
|
5
|
+
const executors = Object.freeze({
|
|
6
|
+
"agent.execute": Object.freeze({ securityClass: "agent", capability: "agent-execution", authorizationRequired: false }),
|
|
7
|
+
"command.execute": Object.freeze({
|
|
8
|
+
securityClass: "local-write",
|
|
9
|
+
capability: "command-execution",
|
|
10
|
+
authorizationRequired: false,
|
|
11
|
+
actions: Object.freeze({
|
|
12
|
+
"quality.test": Object.freeze({ securityClass: "local-write", capability: "command-execution", authorizationRequired: false }),
|
|
13
|
+
"quality.verify": Object.freeze({ securityClass: "local-write", capability: "command-execution", authorizationRequired: false }),
|
|
14
|
+
"quality.docs.integrity": Object.freeze({ securityClass: "local-read", capability: "command-execution", authorizationRequired: false }),
|
|
15
|
+
}),
|
|
16
|
+
}),
|
|
17
|
+
"connector.execute": Object.freeze({
|
|
18
|
+
securityClass: "external-read",
|
|
19
|
+
capability: "connector-execution",
|
|
20
|
+
authorizationRequired: false,
|
|
21
|
+
actions: Object.freeze({
|
|
22
|
+
"requirement.capture": Object.freeze({ securityClass: "external-read", capability: "connector-execution", connector: "requirement", authorizationRequired: false }),
|
|
23
|
+
"git.commit": Object.freeze({ securityClass: "local-write", capability: "connector-execution", connector: "git", externalSideEffect: "git-commit", authorizationRequired: true }),
|
|
24
|
+
"issue.update": Object.freeze({ securityClass: "external-write", capability: "connector-execution", connector: "issue", externalSideEffect: "issue-update", authorizationRequired: true }),
|
|
25
|
+
"knowledge.publish": Object.freeze({ securityClass: "external-write", capability: "connector-execution", connector: "knowledge", externalSideEffect: "knowledge-publish", authorizationRequired: true }),
|
|
26
|
+
"issue.close": Object.freeze({ securityClass: "external-write", capability: "connector-execution", connector: "issue", externalSideEffect: "issue-close", authorizationRequired: true }),
|
|
27
|
+
}),
|
|
28
|
+
}),
|
|
29
|
+
"control.loop": Object.freeze({ securityClass: "control", capability: "control-flow", authorizationRequired: false }),
|
|
30
|
+
"control.close": Object.freeze({ securityClass: "control", capability: "control-flow", authorizationRequired: false }),
|
|
31
|
+
});
|
|
32
|
+
const defaultDocumentationPaths = Object.freeze([
|
|
33
|
+
"README*.md",
|
|
34
|
+
"CHANGELOG*.md",
|
|
35
|
+
"docs/**/*.md",
|
|
36
|
+
"docs/**/*.mdx",
|
|
37
|
+
"docs/**/*.txt",
|
|
38
|
+
]);
|
|
39
|
+
const workflowStepKeys = new Set(["id", "uses", "actorRole", "needs", "when", "retry", "loop", "approval", "inputs", "outputs", "skills", "action", "objective", "expectedOutcome", "until", "maxIterations", "steps"]);
|
|
40
|
+
const profileStepKeys = new Set(["enabled", "approval", "artifactLevel", "artifacts", "gates", "maxIterations", "independentReviewActor"]);
|
|
41
|
+
const resolvedSkillKeys = new Set(["requestedRef", "ref", "source", "provider", "rootId", "entrypoint", "digest", "candidates", "required", "usedFallback", "primary", "fallback"]);
|
|
42
|
+
export class CompileError extends Error {
|
|
43
|
+
code;
|
|
44
|
+
path;
|
|
45
|
+
constructor(code, path, message) {
|
|
46
|
+
super(`${code} ${path}: ${message}`);
|
|
47
|
+
this.code = code;
|
|
48
|
+
this.path = path;
|
|
49
|
+
this.name = "CompileError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function fail(code, path, message) {
|
|
53
|
+
throw new CompileError(code, path, message);
|
|
54
|
+
}
|
|
55
|
+
function record(value, path, keys, code) {
|
|
56
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
57
|
+
fail(code, path, "必须是对象。");
|
|
58
|
+
const source = value;
|
|
59
|
+
if (Object.keys(source).some((key) => !keys.has(key)))
|
|
60
|
+
fail(code, path, "包含无权覆盖的字段。");
|
|
61
|
+
return source;
|
|
62
|
+
}
|
|
63
|
+
function validateProfile(profile, workflowId, profileId, stepIds) {
|
|
64
|
+
record(profile, "/profile", new Set(["version", "profile", "steps", "publishing", "audit"]), "WSSPEC_COMPILE_PROFILE_OVERRIDE_FORBIDDEN");
|
|
65
|
+
const identity = record(profile.profile, "/profile/profile", new Set(["id", "workflow"]), "WSSPEC_COMPILE_PROFILE_OVERRIDE_FORBIDDEN");
|
|
66
|
+
if (identity.workflow !== workflowId || identity.id !== profileId)
|
|
67
|
+
fail("WSSPEC_COMPILE_PROFILE_MISMATCH", "/profile/profile", "Profile 身份未绑定当前 Workflow 和所选 Profile。");
|
|
68
|
+
record(profile.publishing, "/profile/publishing", new Set(["issueRequired", "knowledgeRequired", "readBackRequired"]), "WSSPEC_COMPILE_PROFILE_OVERRIDE_FORBIDDEN");
|
|
69
|
+
record(profile.audit, "/profile/audit", new Set(["level", "retention", "recordDecisions", "recordApprovals", "recordActors", "recordPublishing"]), "WSSPEC_COMPILE_PROFILE_OVERRIDE_FORBIDDEN");
|
|
70
|
+
for (const [id, overlay] of Object.entries(profile.steps)) {
|
|
71
|
+
if (!stepIds.has(id))
|
|
72
|
+
fail("WSSPEC_COMPILE_PROFILE_STEP_UNKNOWN", `/profile/steps/${id}`, "Profile 引用了未知 Step。");
|
|
73
|
+
record(overlay, `/profile/steps/${id}`, profileStepKeys, "WSSPEC_COMPILE_PROFILE_OVERRIDE_FORBIDDEN");
|
|
74
|
+
if (overlay.artifacts !== undefined) {
|
|
75
|
+
for (const [artifact, definition] of Object.entries(overlay.artifacts)) {
|
|
76
|
+
record(definition, `/profile/steps/${id}/artifacts/${artifact}`, new Set(["required", "contentLevel"]), "WSSPEC_COMPILE_PROFILE_OVERRIDE_FORBIDDEN");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const profileOrder = ["quick", "standard", "governed"];
|
|
82
|
+
function profileSafetyFailure(profileId, path, message) {
|
|
83
|
+
fail("WSSPEC_COMPILE_PROFILE_SAFETY_DOWNGRADE", `/profiles/${profileId}${path}`, message);
|
|
84
|
+
}
|
|
85
|
+
function effectiveApproval(step, profile) {
|
|
86
|
+
return profile.steps[step.id]?.approval ?? (step.approval === true || step.approval === "required");
|
|
87
|
+
}
|
|
88
|
+
function effectiveEnabled(step, profile) {
|
|
89
|
+
return profile.steps[step.id]?.enabled ?? true;
|
|
90
|
+
}
|
|
91
|
+
function effectiveIterations(step, profile) {
|
|
92
|
+
return profile.steps[step.id]?.maxIterations ?? step.maxIterations ?? step.loop?.maxIterations;
|
|
93
|
+
}
|
|
94
|
+
function reviewFixLoops(sourceSteps) {
|
|
95
|
+
return sourceSteps.filter((step) => {
|
|
96
|
+
if (step.uses !== "control.loop")
|
|
97
|
+
return false;
|
|
98
|
+
const descendants = allSourceSteps(step.steps ?? []);
|
|
99
|
+
return descendants.some(({ actorRole }) => actorRole === "review")
|
|
100
|
+
&& descendants.some(({ actorRole }) => actorRole === "fix");
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function isSubset(left, right) {
|
|
104
|
+
const rightSet = new Set(right);
|
|
105
|
+
return left.every((value) => rightSet.has(value));
|
|
106
|
+
}
|
|
107
|
+
function auditLevel(profile) {
|
|
108
|
+
return profile.audit.level === "complete" ? 1 : 0;
|
|
109
|
+
}
|
|
110
|
+
function retentionLevel(profile) {
|
|
111
|
+
return profile.audit.retention === "extended" ? 1 : 0;
|
|
112
|
+
}
|
|
113
|
+
function contentLevel(value) {
|
|
114
|
+
if (value === "complete")
|
|
115
|
+
return 2;
|
|
116
|
+
if (value === "compact")
|
|
117
|
+
return 1;
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
function validateProfileSafety(packageRef, workflowId, sourceSteps, profiles) {
|
|
121
|
+
for (const [profileId, profile] of profiles) {
|
|
122
|
+
for (const step of sourceSteps) {
|
|
123
|
+
const overlay = profile.steps[step.id];
|
|
124
|
+
const executor = executorContractFor(step, `/steps/${step.id}`);
|
|
125
|
+
const baseApproval = step.approval === true || step.approval === "required";
|
|
126
|
+
if (executor.authorizationRequired && baseApproval && overlay?.approval === false) {
|
|
127
|
+
profileSafetyFailure(profileId, `/steps/${step.id}/approval`, "Profile 不能关闭受保护动作的基础审批。");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const review of reviewFixLoops(sourceSteps)) {
|
|
131
|
+
const baseIterations = review.maxIterations ?? review.loop?.maxIterations;
|
|
132
|
+
const iterations = effectiveIterations(review, profile);
|
|
133
|
+
if (profileId === "quick" && iterations !== 1)
|
|
134
|
+
profileSafetyFailure(profileId, `/steps/${review.id}/maxIterations`, "Quick Review-Fix 必须限制为 1 轮。");
|
|
135
|
+
if ((profileId === "standard" || profileId === "governed") && baseIterations !== undefined && (iterations === undefined || iterations < baseIterations)) {
|
|
136
|
+
profileSafetyFailure(profileId, `/steps/${review.id}/maxIterations`, "Standard/Governed 不能降低基础 Review-Fix 轮数。");
|
|
137
|
+
}
|
|
138
|
+
if (profileId === "governed" && profile.steps[review.id]?.independentReviewActor !== true) {
|
|
139
|
+
profileSafetyFailure(profileId, `/steps/${review.id}/independentReviewActor`, "Governed 必须要求独立 Review Actor。");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const ordered = profileOrder.flatMap((id) => {
|
|
144
|
+
const profile = profiles.get(id);
|
|
145
|
+
return profile === undefined ? [] : [{ id, profile }];
|
|
146
|
+
});
|
|
147
|
+
for (let index = 1; index < ordered.length; index += 1) {
|
|
148
|
+
const lower = ordered[index - 1];
|
|
149
|
+
const higher = ordered[index];
|
|
150
|
+
for (const step of sourceSteps) {
|
|
151
|
+
if (effectiveEnabled(step, lower.profile) && !effectiveEnabled(step, higher.profile))
|
|
152
|
+
profileSafetyFailure(higher.id, `/steps/${step.id}/enabled`, "高强度 Profile 不能禁用低强度 Profile 已启用的 Step。");
|
|
153
|
+
if (effectiveApproval(step, lower.profile) && !effectiveApproval(step, higher.profile))
|
|
154
|
+
profileSafetyFailure(higher.id, `/steps/${step.id}/approval`, "高强度 Profile 不能降低审批要求。");
|
|
155
|
+
const lowerIterations = effectiveIterations(step, lower.profile);
|
|
156
|
+
const higherIterations = effectiveIterations(step, higher.profile);
|
|
157
|
+
if (lowerIterations !== undefined && (higherIterations === undefined || higherIterations < lowerIterations))
|
|
158
|
+
profileSafetyFailure(higher.id, `/steps/${step.id}/maxIterations`, "高强度 Profile 不能降低循环上限。");
|
|
159
|
+
if (lower.profile.steps[step.id]?.independentReviewActor === true && higher.profile.steps[step.id]?.independentReviewActor !== true)
|
|
160
|
+
profileSafetyFailure(higher.id, `/steps/${step.id}/independentReviewActor`, "高强度 Profile 不能关闭独立执行主体要求。");
|
|
161
|
+
if (!isSubset(lower.profile.steps[step.id]?.gates ?? [], higher.profile.steps[step.id]?.gates ?? []))
|
|
162
|
+
profileSafetyFailure(higher.id, `/steps/${step.id}/gates`, "高强度 Profile 必须保留低强度 Profile 的 Gate。");
|
|
163
|
+
for (const output of step.outputs ?? []) {
|
|
164
|
+
const outputId = typeof output === "string" ? output : output.outputId;
|
|
165
|
+
const lowerArtifact = lower.profile.steps[step.id]?.artifacts?.[outputId];
|
|
166
|
+
const higherArtifact = higher.profile.steps[step.id]?.artifacts?.[outputId];
|
|
167
|
+
if ((lowerArtifact?.required ?? true) && !(higherArtifact?.required ?? true))
|
|
168
|
+
profileSafetyFailure(higher.id, `/steps/${step.id}/artifacts/${outputId}/required`, "高强度 Profile 不能降低 Artifact 必需性。");
|
|
169
|
+
if (contentLevel(lowerArtifact?.contentLevel) > contentLevel(higherArtifact?.contentLevel))
|
|
170
|
+
profileSafetyFailure(higher.id, `/steps/${step.id}/artifacts/${outputId}/contentLevel`, "高强度 Profile 不能降低 Artifact 内容级别。");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const field of ["issueRequired", "knowledgeRequired", "readBackRequired"]) {
|
|
174
|
+
if ((lower.profile.publishing[field] ?? false) && !(higher.profile.publishing[field] ?? false))
|
|
175
|
+
profileSafetyFailure(higher.id, `/publishing/${field}`, "高强度 Profile 不能降低发布要求。");
|
|
176
|
+
}
|
|
177
|
+
if (auditLevel(lower.profile) > auditLevel(higher.profile))
|
|
178
|
+
profileSafetyFailure(higher.id, "/audit/level", "高强度 Profile 不能降低审计级别。");
|
|
179
|
+
if (retentionLevel(lower.profile) > retentionLevel(higher.profile))
|
|
180
|
+
profileSafetyFailure(higher.id, "/audit/retention", "高强度 Profile 不能降低审计保留要求。");
|
|
181
|
+
for (const field of ["recordDecisions", "recordApprovals", "recordActors", "recordPublishing"]) {
|
|
182
|
+
if ((lower.profile.audit[field] ?? false) && !(higher.profile.audit[field] ?? false))
|
|
183
|
+
profileSafetyFailure(higher.id, `/audit/${field}`, "高强度 Profile 不能关闭审计记录。");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
validateBuiltinProfileMatrix(packageRef, workflowId, sourceSteps, profiles);
|
|
187
|
+
}
|
|
188
|
+
function validateBuiltinProfileMatrix(packageRef, workflowId, sourceSteps, profiles) {
|
|
189
|
+
if (packageRef !== `builtin://workflows/${workflowId}` || (workflowId !== "feature-delivery" && workflowId !== "documentation-delivery"))
|
|
190
|
+
return;
|
|
191
|
+
const byId = new Map(sourceSteps.map((step) => [step.id, step]));
|
|
192
|
+
const profile = (id) => {
|
|
193
|
+
const definition = profiles.get(id);
|
|
194
|
+
if (definition === undefined)
|
|
195
|
+
profileSafetyFailure(id, "", "Builtin Workflow 必须提供 quick、standard 和 governed Profile。");
|
|
196
|
+
return definition;
|
|
197
|
+
};
|
|
198
|
+
const step = (id) => {
|
|
199
|
+
const definition = byId.get(id);
|
|
200
|
+
if (definition === undefined)
|
|
201
|
+
profileSafetyFailure("standard", `/steps/${id}`, "Builtin Workflow 缺少规范 Step。");
|
|
202
|
+
return definition;
|
|
203
|
+
};
|
|
204
|
+
const requireEnabled = (profileId, stepId) => {
|
|
205
|
+
if (!effectiveEnabled(step(stepId), profile(profileId)))
|
|
206
|
+
profileSafetyFailure(profileId, `/steps/${stepId}/enabled`, "Builtin Profile 不能关闭规范必需 Step。");
|
|
207
|
+
};
|
|
208
|
+
const requireApproval = (profileId, stepId) => {
|
|
209
|
+
if (!effectiveApproval(step(stepId), profile(profileId)))
|
|
210
|
+
profileSafetyFailure(profileId, `/steps/${stepId}/approval`, "Builtin Profile 缺少规范审批。");
|
|
211
|
+
};
|
|
212
|
+
const requireArtifact = (profileId, stepId, artifact, expectedContentLevel) => {
|
|
213
|
+
const artifactOverlay = profile(profileId).steps[stepId]?.artifacts?.[artifact];
|
|
214
|
+
if (artifactOverlay?.required !== true || artifactOverlay.contentLevel !== expectedContentLevel)
|
|
215
|
+
profileSafetyFailure(profileId, `/steps/${stepId}/artifacts/${artifact}`, `Builtin Profile 必须声明 ${expectedContentLevel} 且必需的 ${artifact}。`);
|
|
216
|
+
};
|
|
217
|
+
const requireGovernedPolicy = () => {
|
|
218
|
+
const governed = profile("governed");
|
|
219
|
+
if (!governed.publishing.issueRequired || !governed.publishing.knowledgeRequired || governed.publishing.readBackRequired !== true)
|
|
220
|
+
profileSafetyFailure("governed", "/publishing", "Governed Builtin Profile 必须要求 Issue、Knowledge 和发布回读。");
|
|
221
|
+
if (governed.audit.level !== "complete" || governed.audit.retention !== "extended")
|
|
222
|
+
profileSafetyFailure("governed", "/audit", "Governed Builtin Profile 必须使用 complete/extended 审计。");
|
|
223
|
+
for (const field of ["recordDecisions", "recordApprovals", "recordActors", "recordPublishing"]) {
|
|
224
|
+
if (governed.audit[field] !== true)
|
|
225
|
+
profileSafetyFailure("governed", `/audit/${field}`, "Governed Builtin Profile 必须记录完整治理证据。");
|
|
226
|
+
}
|
|
227
|
+
if (governed.steps["review-fix"]?.independentReviewActor !== true)
|
|
228
|
+
profileSafetyFailure("governed", "/steps/review-fix/independentReviewActor", "Governed Builtin Profile 必须要求独立 Review Actor。");
|
|
229
|
+
};
|
|
230
|
+
for (const profileId of ["standard", "governed"]) {
|
|
231
|
+
if (effectiveIterations(step("review-fix"), profile(profileId)) !== 5)
|
|
232
|
+
profileSafetyFailure(profileId, "/steps/review-fix/maxIterations", "Standard/Governed Builtin Profile 的 Review-Fix 上限必须为 5 轮。");
|
|
233
|
+
}
|
|
234
|
+
if (workflowId === "feature-delivery") {
|
|
235
|
+
if (effectiveEnabled(step("design"), profile("quick")))
|
|
236
|
+
profileSafetyFailure("quick", "/steps/design/enabled", "Feature Quick 必须跳过独立 design。");
|
|
237
|
+
for (const id of sourceSteps.map(({ id }) => id).filter((id) => id !== "design"))
|
|
238
|
+
requireEnabled("quick", id);
|
|
239
|
+
for (const profileId of ["standard", "governed"])
|
|
240
|
+
for (const { id } of sourceSteps)
|
|
241
|
+
requireEnabled(profileId, id);
|
|
242
|
+
requireArtifact("quick", "clarify", "specification", "compact");
|
|
243
|
+
requireArtifact("quick", "plan", "tasks", "compact");
|
|
244
|
+
requireApproval("standard", "clarify");
|
|
245
|
+
requireApproval("standard", "design");
|
|
246
|
+
requireArtifact("standard", "clarify", "specification", "complete");
|
|
247
|
+
requireArtifact("standard", "design", "design", "complete");
|
|
248
|
+
requireArtifact("standard", "plan", "tasks", "complete");
|
|
249
|
+
for (const id of ["clarify", "design", "plan"])
|
|
250
|
+
requireApproval("governed", id);
|
|
251
|
+
requireArtifact("governed", "clarify", "specification", "complete");
|
|
252
|
+
requireArtifact("governed", "design", "design", "complete");
|
|
253
|
+
requireArtifact("governed", "plan", "tasks", "complete");
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
for (const id of ["intake", "explore", "clarify", "plan", "edit-document", "verify-document", "review-fix", "commit", "close"])
|
|
257
|
+
requireEnabled("quick", id);
|
|
258
|
+
for (const profileId of ["standard", "governed"])
|
|
259
|
+
for (const { id } of sourceSteps)
|
|
260
|
+
requireEnabled(profileId, id);
|
|
261
|
+
requireArtifact("quick", "clarify", "specification", "compact");
|
|
262
|
+
requireArtifact("quick", "plan", "tasks", "compact");
|
|
263
|
+
requireArtifact("standard", "clarify", "specification", "complete");
|
|
264
|
+
requireArtifact("standard", "plan", "tasks", "complete");
|
|
265
|
+
for (const id of ["clarify", "plan"])
|
|
266
|
+
requireApproval("governed", id);
|
|
267
|
+
requireArtifact("governed", "clarify", "specification", "complete");
|
|
268
|
+
requireArtifact("governed", "plan", "tasks", "complete");
|
|
269
|
+
}
|
|
270
|
+
requireGovernedPolicy();
|
|
271
|
+
}
|
|
272
|
+
function validateStepShape(step, path, insideLoop = false) {
|
|
273
|
+
if (insideLoop && step.uses === "control.loop") {
|
|
274
|
+
fail("WSSPEC_COMPILE_NESTED_LOOP_UNSUPPORTED", `${path}/uses`, "control.loop 暂不支持嵌套。");
|
|
275
|
+
}
|
|
276
|
+
if (Object.hasOwn(step, "securityClass"))
|
|
277
|
+
fail("WSSPEC_COMPILE_SECURITY_OVERRIDE", `${path}/securityClass`, "安全类别只能来自 Executor Registry。");
|
|
278
|
+
record(step, path, workflowStepKeys, "WSSPEC_COMPILE_STEP_INVALID");
|
|
279
|
+
const childrenInsideLoop = insideLoop || step.uses === "control.loop";
|
|
280
|
+
for (const [index, child] of (step.steps ?? []).entries())
|
|
281
|
+
validateStepShape(child, `${path}/steps/${index}`, childrenInsideLoop);
|
|
282
|
+
}
|
|
283
|
+
function executorContractFor(step, path) {
|
|
284
|
+
const manifest = executors[step.uses];
|
|
285
|
+
if (manifest === undefined)
|
|
286
|
+
fail("WSSPEC_EXECUTOR_NOT_FOUND", `${path}/uses`, `未注册 Executor ${step.uses}。`);
|
|
287
|
+
if (manifest.actions === undefined) {
|
|
288
|
+
if (step.action !== undefined)
|
|
289
|
+
fail("WSSPEC_EXECUTOR_ACTION_NOT_FOUND", `${path}/action`, `${step.uses} 不接受 action。`);
|
|
290
|
+
return manifest;
|
|
291
|
+
}
|
|
292
|
+
if (step.action === undefined || manifest.actions[step.action] === undefined) {
|
|
293
|
+
fail("WSSPEC_EXECUTOR_ACTION_NOT_FOUND", `${path}/action`, `${step.uses} 未注册 action ${step.action ?? "<missing>"}。`);
|
|
294
|
+
}
|
|
295
|
+
return manifest.actions[step.action];
|
|
296
|
+
}
|
|
297
|
+
function normalizeInput(input) {
|
|
298
|
+
return typeof input === "string"
|
|
299
|
+
? { outputId: input, required: true }
|
|
300
|
+
: { outputId: input.outputId, required: input.required ?? true };
|
|
301
|
+
}
|
|
302
|
+
function normalizeOutput(output) {
|
|
303
|
+
return typeof output === "string"
|
|
304
|
+
? { outputId: output, artifactType: output }
|
|
305
|
+
: { outputId: output.outputId, artifactType: output.artifactType };
|
|
306
|
+
}
|
|
307
|
+
function validateResolvedSkill(skill, index) {
|
|
308
|
+
record(skill, `/resolvedSkills/${index}`, resolvedSkillKeys, "WSSPEC_COMPILE_SKILL_POLICY_OVERRIDE");
|
|
309
|
+
}
|
|
310
|
+
function resolveStepSkills(step, available, path) {
|
|
311
|
+
const result = [];
|
|
312
|
+
for (const [bindingIndex, binding] of (step.skills ?? []).entries()) {
|
|
313
|
+
const matches = available.filter(({ requestedRef }) => requestedRef === binding.ref);
|
|
314
|
+
if (matches.length > 1)
|
|
315
|
+
fail("WSSPEC_COMPILE_SKILL_AMBIGUOUS", `${path}/skills/${bindingIndex}`, `Skill ${binding.ref} 有多个解析结果。`);
|
|
316
|
+
const resolved = matches[0];
|
|
317
|
+
const required = binding.required ?? true;
|
|
318
|
+
if (resolved === undefined) {
|
|
319
|
+
if (required)
|
|
320
|
+
fail("WSSPEC_COMPILE_REQUIRED_SKILL_MISSING", `${path}/skills/${bindingIndex}`, `缺少必需 Skill ${binding.ref}。`);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (resolved.required !== required)
|
|
324
|
+
fail("WSSPEC_COMPILE_SKILL_MISMATCH", `${path}/skills/${bindingIndex}`, `Skill ${binding.ref} 的 required 状态与绑定不一致。`);
|
|
325
|
+
result.push(resolved);
|
|
326
|
+
}
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
function compileStep(step, overlays, availableSkills, path) {
|
|
330
|
+
const overlay = overlays[step.id];
|
|
331
|
+
const executor = executorContractFor(step, path);
|
|
332
|
+
const normalizedOutputs = (step.outputs ?? []).map(normalizeOutput);
|
|
333
|
+
const declaredOutputs = new Set(normalizedOutputs.map(({ outputId }) => outputId));
|
|
334
|
+
if (declaredOutputs.size !== normalizedOutputs.length) {
|
|
335
|
+
fail("WSSPEC_COMPILE_STEP_INVALID", `${path}/outputs`, "Step outputId 必须唯一。");
|
|
336
|
+
}
|
|
337
|
+
for (const artifact of Object.keys(overlay?.artifacts ?? {})) {
|
|
338
|
+
if (!declaredOutputs.has(artifact))
|
|
339
|
+
fail("WSSPEC_COMPILE_PROFILE_ARTIFACT_UNKNOWN", `${path}/outputs/${artifact}`, "Profile 引用了 Step 未声明的输出。");
|
|
340
|
+
}
|
|
341
|
+
const outputs = normalizedOutputs.map(({ outputId, artifactType }) => {
|
|
342
|
+
const artifactOverlay = overlay?.artifacts?.[outputId];
|
|
343
|
+
return {
|
|
344
|
+
outputId,
|
|
345
|
+
artifact: artifactType,
|
|
346
|
+
required: artifactOverlay?.required ?? true,
|
|
347
|
+
...(artifactOverlay?.contentLevel === undefined ? {} : { contentLevel: artifactOverlay.contentLevel }),
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
const compiled = {
|
|
351
|
+
id: step.id,
|
|
352
|
+
uses: step.uses,
|
|
353
|
+
...(step.actorRole === undefined ? {} : { actorRole: step.actorRole }),
|
|
354
|
+
securityClass: executor.securityClass,
|
|
355
|
+
needs: [...(step.needs ?? [])],
|
|
356
|
+
enabled: overlay?.enabled ?? true,
|
|
357
|
+
skills: resolveStepSkills(step, availableSkills, path),
|
|
358
|
+
inputs: (step.inputs ?? []).map(normalizeInput),
|
|
359
|
+
outputs,
|
|
360
|
+
gates: [...(overlay?.gates ?? [])],
|
|
361
|
+
approval: overlay?.approval ?? (step.approval === true || step.approval === "required"),
|
|
362
|
+
authorizationRequired: executor.authorizationRequired,
|
|
363
|
+
steps: (step.steps ?? []).map((child, index) => compileStep(child, overlays, availableSkills, `${path}/steps/${index}`)),
|
|
364
|
+
};
|
|
365
|
+
if (overlay?.artifactLevel !== undefined)
|
|
366
|
+
compiled.artifactLevel = overlay.artifactLevel;
|
|
367
|
+
if (step.action !== undefined)
|
|
368
|
+
compiled.action = step.action;
|
|
369
|
+
if (step.objective !== undefined)
|
|
370
|
+
compiled.objective = step.objective;
|
|
371
|
+
if (step.expectedOutcome !== undefined)
|
|
372
|
+
compiled.expectedOutcome = step.expectedOutcome;
|
|
373
|
+
if (step.when !== undefined)
|
|
374
|
+
compiled.when = step.when;
|
|
375
|
+
const until = step.until ?? step.loop?.until;
|
|
376
|
+
if (until !== undefined)
|
|
377
|
+
compiled.until = until;
|
|
378
|
+
if (step.retry !== undefined)
|
|
379
|
+
compiled.retry = { ...step.retry };
|
|
380
|
+
const maxIterations = overlay?.maxIterations ?? step.maxIterations ?? step.loop?.maxIterations;
|
|
381
|
+
if (maxIterations !== undefined)
|
|
382
|
+
compiled.maxIterations = maxIterations;
|
|
383
|
+
if (overlay?.independentReviewActor !== undefined)
|
|
384
|
+
compiled.independentReviewActor = overlay.independentReviewActor;
|
|
385
|
+
return compiled;
|
|
386
|
+
}
|
|
387
|
+
function flatten(steps) {
|
|
388
|
+
return steps.flatMap((step) => [step, ...flatten(step.steps)]);
|
|
389
|
+
}
|
|
390
|
+
function topologicalOrder(steps, byId, path) {
|
|
391
|
+
const visiting = new Set();
|
|
392
|
+
const visited = new Set();
|
|
393
|
+
const order = [];
|
|
394
|
+
const visit = (id) => {
|
|
395
|
+
if (visiting.has(id))
|
|
396
|
+
fail("WSSPEC_COMPILE_CYCLE", `${path}/${id}/needs`, "Workflow DAG 包含循环依赖。");
|
|
397
|
+
if (visited.has(id))
|
|
398
|
+
return;
|
|
399
|
+
visiting.add(id);
|
|
400
|
+
for (const dependency of byId.get(id).needs)
|
|
401
|
+
visit(dependency);
|
|
402
|
+
visiting.delete(id);
|
|
403
|
+
visited.add(id);
|
|
404
|
+
order.push(id);
|
|
405
|
+
};
|
|
406
|
+
for (const step of steps)
|
|
407
|
+
visit(step.id);
|
|
408
|
+
return order;
|
|
409
|
+
}
|
|
410
|
+
function validateDag(steps, path) {
|
|
411
|
+
const byId = new Map(steps.map((step) => [step.id, step]));
|
|
412
|
+
for (const current of steps) {
|
|
413
|
+
for (const dependency of current.needs)
|
|
414
|
+
if (!byId.has(dependency))
|
|
415
|
+
fail("WSSPEC_COMPILE_UNKNOWN_DEPENDENCY", `${path}/${current.id}/needs/${dependency}`, `未知依赖 ${dependency}。`);
|
|
416
|
+
validateDag(current.steps, `${path}/${current.id}/steps`);
|
|
417
|
+
}
|
|
418
|
+
return topologicalOrder(steps, byId, path);
|
|
419
|
+
}
|
|
420
|
+
function dependencyClosure(id, byId) {
|
|
421
|
+
const result = new Set();
|
|
422
|
+
const visit = (candidate) => {
|
|
423
|
+
for (const dependency of byId.get(candidate)?.needs ?? [])
|
|
424
|
+
if (!result.has(dependency)) {
|
|
425
|
+
result.add(dependency);
|
|
426
|
+
visit(dependency);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
visit(id);
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
function producedArtifacts(step, parentEnabled = true, parentGuaranteed = true) {
|
|
433
|
+
const enabled = parentEnabled && step.enabled;
|
|
434
|
+
const guaranteedPath = parentGuaranteed && enabled && step.when === undefined;
|
|
435
|
+
return [
|
|
436
|
+
...step.outputs.map(({ outputId, required }) => ({ outputId, enabled, guaranteed: guaranteedPath && required })),
|
|
437
|
+
...step.steps.flatMap((child) => producedArtifacts(child, enabled, guaranteedPath)),
|
|
438
|
+
];
|
|
439
|
+
}
|
|
440
|
+
function validateArtifactDependencies(steps, path) {
|
|
441
|
+
const byId = new Map(steps.map((step) => [step.id, step]));
|
|
442
|
+
for (const current of steps) {
|
|
443
|
+
if (!current.enabled)
|
|
444
|
+
continue;
|
|
445
|
+
const ancestors = dependencyClosure(current.id, byId);
|
|
446
|
+
const produced = [...ancestors].flatMap((id) => producedArtifacts(byId.get(id)));
|
|
447
|
+
for (const input of current.inputs) {
|
|
448
|
+
if (!input.required)
|
|
449
|
+
continue;
|
|
450
|
+
if (input.outputId === "requirement-source")
|
|
451
|
+
continue;
|
|
452
|
+
const candidates = produced.filter(({ outputId }) => outputId === input.outputId);
|
|
453
|
+
if (candidates.some(({ guaranteed }) => guaranteed))
|
|
454
|
+
continue;
|
|
455
|
+
if (candidates.some(({ enabled }) => enabled))
|
|
456
|
+
fail("WSSPEC_COMPILE_OUTPUT_NOT_GUARANTEED", `${path}/${current.id}/inputs/${input.outputId}`, "必需输入只能由无条件且必需的输出满足。");
|
|
457
|
+
if (candidates.length > 0)
|
|
458
|
+
fail("WSSPEC_COMPILE_DISABLED_OUTPUT_REQUIRED", `${path}/${current.id}/inputs/${input.outputId}`, "启用 Step 消费了已跳过 Step 的必需输出。");
|
|
459
|
+
fail("WSSPEC_COMPILE_MISSING_ARTIFACT_PRODUCER", `${path}/${current.id}/inputs/${input.outputId}`, "必需输入没有依赖闭包内的生产者。");
|
|
460
|
+
}
|
|
461
|
+
validateArtifactDependencies(current.steps, `${path}/${current.id}/steps`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function artifactPropertyType(artifactType, property) {
|
|
465
|
+
if (property === "exists")
|
|
466
|
+
return "boolean";
|
|
467
|
+
if (property === "status" || property === "digest")
|
|
468
|
+
return "string";
|
|
469
|
+
if (artifactType === "review-result" && property === "approved")
|
|
470
|
+
return "boolean";
|
|
471
|
+
return undefined;
|
|
472
|
+
}
|
|
473
|
+
function effectiveOutputs(step, parentEnabled) {
|
|
474
|
+
const enabled = parentEnabled && step.enabled;
|
|
475
|
+
if (!enabled)
|
|
476
|
+
return new Set();
|
|
477
|
+
return new Set([
|
|
478
|
+
...step.outputs.map(({ outputId }) => outputId),
|
|
479
|
+
...step.steps.flatMap((child) => [...effectiveOutputs(child, enabled)]),
|
|
480
|
+
]);
|
|
481
|
+
}
|
|
482
|
+
function addAll(target, values) {
|
|
483
|
+
for (const value of values)
|
|
484
|
+
target.add(value);
|
|
485
|
+
}
|
|
486
|
+
function validateExpressions(sourceSteps, compiledSteps) {
|
|
487
|
+
const declaredOutputs = new Map(flatten(compiledSteps).flatMap((step) => {
|
|
488
|
+
return step.outputs.map(({ outputId, artifact }) => [outputId, artifact]);
|
|
489
|
+
}));
|
|
490
|
+
const declaredSteps = new Set(flatten(compiledSteps).map((step) => step.id));
|
|
491
|
+
const validate = (expression, path, availableArtifacts, availableSteps, allSteps) => {
|
|
492
|
+
let ast;
|
|
493
|
+
try {
|
|
494
|
+
ast = parseExpression(expression);
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
if (error instanceof ExpressionError)
|
|
498
|
+
fail("WSSPEC_COMPILE_EXPRESSION_INVALID", path, "表达式不属于 Workflow Language v1 有限语法。");
|
|
499
|
+
throw error;
|
|
500
|
+
}
|
|
501
|
+
const typeOf = (node) => {
|
|
502
|
+
if (node.kind === "literal") {
|
|
503
|
+
if (node.value === null)
|
|
504
|
+
return "null";
|
|
505
|
+
if (typeof node.value === "boolean")
|
|
506
|
+
return "boolean";
|
|
507
|
+
if (typeof node.value === "number")
|
|
508
|
+
return "number";
|
|
509
|
+
return "string";
|
|
510
|
+
}
|
|
511
|
+
if (node.kind === "binary") {
|
|
512
|
+
const left = typeOf(node.left);
|
|
513
|
+
const right = typeOf(node.right);
|
|
514
|
+
if (node.op === "==" || node.op === "!=") {
|
|
515
|
+
if (left !== right)
|
|
516
|
+
fail("WSSPEC_COMPILE_EXPRESSION_TYPE_MISMATCH", path, "表达式两侧的比较类型必须一致。");
|
|
517
|
+
return "boolean";
|
|
518
|
+
}
|
|
519
|
+
if (left !== "boolean" || right !== "boolean")
|
|
520
|
+
fail("WSSPEC_COMPILE_EXPRESSION_TYPE_MISMATCH", path, "逻辑表达式的两侧必须是布尔值。");
|
|
521
|
+
return "boolean";
|
|
522
|
+
}
|
|
523
|
+
const [target, property, ...rest] = node.segments;
|
|
524
|
+
if (rest.length > 0 || target === undefined || property === undefined)
|
|
525
|
+
fail("WSSPEC_COMPILE_EXPRESSION_PROPERTY_UNKNOWN", path, "表达式路径不属于已声明的静态属性。 ");
|
|
526
|
+
if (node.root === "bindings") {
|
|
527
|
+
if (!((target === "issue" || target === "knowledge") && property === "exists"))
|
|
528
|
+
fail("WSSPEC_COMPILE_EXPRESSION_REFERENCE_UNKNOWN", path, "表达式引用了未知 Binding 状态。");
|
|
529
|
+
return "boolean";
|
|
530
|
+
}
|
|
531
|
+
if (node.root === "artifacts") {
|
|
532
|
+
if (!declaredOutputs.has(target))
|
|
533
|
+
fail("WSSPEC_COMPILE_EXPRESSION_REFERENCE_UNKNOWN", path, `表达式引用了未声明输出 ${target}。`);
|
|
534
|
+
if (!availableArtifacts.has(target))
|
|
535
|
+
fail("WSSPEC_COMPILE_EXPRESSION_REFERENCE_UNAVAILABLE", path, `表达式引用了当前 Step 不可达或未启用的输出 ${target}。`);
|
|
536
|
+
const valueType = artifactPropertyType(declaredOutputs.get(target), property);
|
|
537
|
+
if (valueType === undefined)
|
|
538
|
+
fail("WSSPEC_COMPILE_EXPRESSION_PROPERTY_UNKNOWN", path, `输出 ${target} 没有属性 ${property}。`);
|
|
539
|
+
return valueType;
|
|
540
|
+
}
|
|
541
|
+
if (!allSteps.has(target))
|
|
542
|
+
fail("WSSPEC_COMPILE_EXPRESSION_REFERENCE_UNKNOWN", path, `表达式引用了未知 Step ${target}。`);
|
|
543
|
+
if (!availableSteps.has(target))
|
|
544
|
+
fail("WSSPEC_COMPILE_EXPRESSION_REFERENCE_UNAVAILABLE", path, `表达式引用了当前 Step 不可达或未启用的 Step ${target}。`);
|
|
545
|
+
if (property !== "status")
|
|
546
|
+
fail("WSSPEC_COMPILE_EXPRESSION_PROPERTY_UNKNOWN", path, `Step ${target} 没有属性 ${property}。`);
|
|
547
|
+
return "string";
|
|
548
|
+
};
|
|
549
|
+
if (typeOf(ast) !== "boolean")
|
|
550
|
+
fail("WSSPEC_COMPILE_EXPRESSION_TYPE_MISMATCH", path, "表达式必须解析为布尔值。");
|
|
551
|
+
};
|
|
552
|
+
const visit = (sources, compiled, path, inherited, inheritedSteps, nested, parentEnabled) => {
|
|
553
|
+
const byId = new Map(compiled.map((step) => [step.id, step]));
|
|
554
|
+
const preceding = new Set();
|
|
555
|
+
const precedingSteps = new Set();
|
|
556
|
+
for (const [index, step] of sources.entries()) {
|
|
557
|
+
const current = compiled[index];
|
|
558
|
+
const stepPath = `${path}/${index}`;
|
|
559
|
+
const available = new Set(inherited);
|
|
560
|
+
const availableSteps = new Set(inheritedSteps);
|
|
561
|
+
if (nested)
|
|
562
|
+
addAll(available, preceding);
|
|
563
|
+
if (nested)
|
|
564
|
+
addAll(availableSteps, precedingSteps);
|
|
565
|
+
for (const dependency of dependencyClosure(current.id, byId)) {
|
|
566
|
+
addAll(available, effectiveOutputs(byId.get(dependency), parentEnabled));
|
|
567
|
+
availableSteps.add(dependency);
|
|
568
|
+
}
|
|
569
|
+
if (step.when !== undefined)
|
|
570
|
+
validate(step.when, `${stepPath}/when`, available, availableSteps, declaredSteps);
|
|
571
|
+
const untilAvailable = new Set(available);
|
|
572
|
+
const untilSteps = new Set(availableSteps);
|
|
573
|
+
for (const child of current.steps) {
|
|
574
|
+
addAll(untilAvailable, effectiveOutputs(child, parentEnabled && current.enabled));
|
|
575
|
+
untilSteps.add(child.id);
|
|
576
|
+
}
|
|
577
|
+
if (step.until !== undefined)
|
|
578
|
+
validate(step.until, `${stepPath}/until`, untilAvailable, untilSteps, declaredSteps);
|
|
579
|
+
if (step.loop?.until !== undefined)
|
|
580
|
+
validate(step.loop.until, `${stepPath}/loop/until`, untilAvailable, untilSteps, declaredSteps);
|
|
581
|
+
visit(step.steps ?? [], current.steps, `${stepPath}/steps`, available, availableSteps, true, parentEnabled && current.enabled);
|
|
582
|
+
if (nested)
|
|
583
|
+
addAll(preceding, effectiveOutputs(current, parentEnabled));
|
|
584
|
+
if (nested)
|
|
585
|
+
precedingSteps.add(current.id);
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
visit(sourceSteps, compiledSteps, "/steps", new Set(), new Set(), false, true);
|
|
589
|
+
}
|
|
590
|
+
function validateGates(workflow, steps) {
|
|
591
|
+
const gates = new Map(workflow.gates.map((gate) => [gate.id, gate]));
|
|
592
|
+
if (gates.size !== workflow.gates.length)
|
|
593
|
+
fail("WSSPEC_COMPILE_DUPLICATE_GATE", "/gates", "Gate ID 必须唯一。");
|
|
594
|
+
for (const step of flatten(steps)) {
|
|
595
|
+
for (const gate of step.gates)
|
|
596
|
+
if (!gates.has(gate))
|
|
597
|
+
fail("WSSPEC_COMPILE_UNKNOWN_GATE", `/steps/${step.id}/gates/${gate}`, "Profile 引用了未知 Gate。");
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
function validateProjectGatePolicy(workflow, policy) {
|
|
601
|
+
const source = record(policy, "/gatePolicy", new Set(["requiredGateIds", "configuredGateIds"]), "WSSPEC_COMPILE_GATE_POLICY_INVALID");
|
|
602
|
+
const parseIds = (field) => {
|
|
603
|
+
const value = source[field];
|
|
604
|
+
if (!Array.isArray(value) || value.some((id) => typeof id !== "string" || id === ""))
|
|
605
|
+
fail("WSSPEC_COMPILE_GATE_POLICY_INVALID", `/gatePolicy/${field}`, `${field} 必须是 Gate ID 数组。`);
|
|
606
|
+
const ids = value;
|
|
607
|
+
if (new Set(ids).size !== ids.length)
|
|
608
|
+
fail("WSSPEC_COMPILE_GATE_POLICY_INVALID", `/gatePolicy/${field}`, `${field} 不能包含重复 Gate ID。`);
|
|
609
|
+
return [...ids];
|
|
610
|
+
};
|
|
611
|
+
const requiredGateIds = parseIds("requiredGateIds");
|
|
612
|
+
const configuredGateIds = parseIds("configuredGateIds");
|
|
613
|
+
const registry = new Set(workflow.gates.map(({ id }) => id));
|
|
614
|
+
for (const [field, ids] of [["requiredGateIds", requiredGateIds], ["configuredGateIds", configuredGateIds]]) {
|
|
615
|
+
for (const [index, id] of ids.entries()) {
|
|
616
|
+
if (!registry.has(id))
|
|
617
|
+
fail("WSSPEC_COMPILE_GATE_POLICY_UNKNOWN", `/gatePolicy/${field}/${index}`, `Gate policy 引用了 Workflow registry 中不存在的 ${id}。`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const configured = new Set(configuredGateIds);
|
|
621
|
+
for (const [index, id] of requiredGateIds.entries()) {
|
|
622
|
+
if (!configured.has(id))
|
|
623
|
+
fail("WSSPEC_COMPILE_GATE_POLICY_INVALID", `/gatePolicy/requiredGateIds/${index}`, `必需 Gate ${id} 不在 configuredGateIds 中。`);
|
|
624
|
+
}
|
|
625
|
+
return { requiredGateIds, configuredGateIds };
|
|
626
|
+
}
|
|
627
|
+
function validateProfileGatePolicy(profileId, steps, policy) {
|
|
628
|
+
if (profileId !== "standard" && profileId !== "governed")
|
|
629
|
+
return;
|
|
630
|
+
const effectiveGates = new Set(flatten(steps)
|
|
631
|
+
.filter((step) => step.enabled
|
|
632
|
+
&& step.uses === "command.execute"
|
|
633
|
+
&& step.action?.startsWith("quality.") === true
|
|
634
|
+
&& step.expectedOutcome !== "test-failure")
|
|
635
|
+
.flatMap(({ gates }) => gates));
|
|
636
|
+
const required = profileId === "standard" ? policy.requiredGateIds : policy.configuredGateIds;
|
|
637
|
+
const code = profileId === "standard" ? "WSSPEC_COMPILE_REQUIRED_GATE_MISSING" : "WSSPEC_COMPILE_CONFIGURED_GATE_MISSING";
|
|
638
|
+
for (const gateId of required) {
|
|
639
|
+
if (!effectiveGates.has(gateId))
|
|
640
|
+
fail(code, `/profiles/${profileId}/gates/${gateId}`, `${profileId} 的有效验证集合缺少 Gate ${gateId}。`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
function validateManifestDeclarations(pkg, steps) {
|
|
644
|
+
const capabilities = new Set(pkg.manifest.capabilities);
|
|
645
|
+
const connectors = new Set(pkg.manifest.connectors);
|
|
646
|
+
const sideEffects = new Set(pkg.manifest.externalSideEffects);
|
|
647
|
+
for (const step of flatten(steps)) {
|
|
648
|
+
const contract = executorContractFor(step, `/steps/${step.id}`);
|
|
649
|
+
if (!capabilities.has(contract.capability))
|
|
650
|
+
fail("WSSPEC_COMPILE_MANIFEST_CAPABILITY_MISSING", `/manifest/capabilities/${contract.capability}`, `Manifest 未声明 Step ${step.id} 使用的能力 ${contract.capability}。`);
|
|
651
|
+
if (contract.connector !== undefined && !connectors.has(contract.connector))
|
|
652
|
+
fail("WSSPEC_COMPILE_MANIFEST_CONNECTOR_MISSING", `/manifest/connectors/${contract.connector}`, `Manifest 未声明 Step ${step.id} 使用的 Connector ${contract.connector}。`);
|
|
653
|
+
if (contract.externalSideEffect !== undefined && !sideEffects.has(contract.externalSideEffect))
|
|
654
|
+
fail("WSSPEC_COMPILE_MANIFEST_SIDE_EFFECT_MISSING", `/manifest/externalSideEffects/${contract.externalSideEffect}`, `Manifest 未声明 Step ${step.id} 的外部副作用 ${contract.externalSideEffect}。`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
function hasAncestor(stepId, ancestorId, byId) {
|
|
658
|
+
return dependencyClosure(stepId, byId).has(ancestorId);
|
|
659
|
+
}
|
|
660
|
+
function validateFeatureSafety(workflow, packageRef, profileId, steps, byId) {
|
|
661
|
+
const safetyIds = ["write-tests", "verify-red", "implement", "verify-green"];
|
|
662
|
+
const safetySteps = new Map(safetyIds.map((id) => [id, byId.get(id)]));
|
|
663
|
+
if ([...safetySteps.values()].some((step) => step === undefined || !step.enabled))
|
|
664
|
+
fail("WSSPEC_COMPILE_TDD_REQUIRED", "/steps", "功能 Workflow 不能关闭 Red/Green 安全内核。");
|
|
665
|
+
const writeTests = safetySteps.get("write-tests");
|
|
666
|
+
const verifyRed = safetySteps.get("verify-red");
|
|
667
|
+
const implement = safetySteps.get("implement");
|
|
668
|
+
const verifyGreen = safetySteps.get("verify-green");
|
|
669
|
+
if (writeTests.uses !== "agent.execute" || !writeTests.skills.some(({ requestedRef }) => requestedRef === "builtin://skills/tdd-implementation"))
|
|
670
|
+
fail("WSSPEC_COMPILE_TDD_REQUIRED", "/steps/write-tests", "write-tests 必须绑定 TDD Skill。");
|
|
671
|
+
if (!hasAncestor("verify-red", "write-tests", byId) || verifyRed.uses !== "command.execute" || verifyRed.action !== "quality.test" || verifyRed.expectedOutcome !== "test-failure" || !verifyRed.outputs.some(({ artifact, required }) => artifact === "red-evidence" && required))
|
|
672
|
+
fail("WSSPEC_COMPILE_TDD_REQUIRED", "/steps/verify-red", "verify-red 必须在 write-tests 后形成可信 Red Evidence。");
|
|
673
|
+
if (!implement.inputs.some(({ outputId, required }) => outputId === "tasks" && required))
|
|
674
|
+
fail("WSSPEC_COMPILE_PLAN_REQUIRED", "/steps/implement/inputs", "implement 必须显式消费 tasks。");
|
|
675
|
+
if (!hasAncestor("implement", "verify-red", byId) || implement.uses !== "agent.execute" || !implement.skills.some(({ requestedRef }) => requestedRef === "builtin://skills/tdd-implementation"))
|
|
676
|
+
fail("WSSPEC_COMPILE_TDD_REQUIRED", "/steps/implement", "implement 必须位于有效 Red Evidence 之后并绑定 TDD Skill。");
|
|
677
|
+
if (!hasAncestor("verify-green", "implement", byId) || verifyGreen.uses !== "command.execute" || verifyGreen.action !== "quality.test" || verifyGreen.expectedOutcome !== "success" || !verifyGreen.inputs.some(({ outputId, required }) => outputId === "red-evidence" && required) || !verifyGreen.outputs.some(({ artifact, required }) => artifact === "tdd-evidence" && required) || !verifyGreen.gates.includes("test"))
|
|
678
|
+
fail("WSSPEC_COMPILE_TDD_REQUIRED", "/steps/verify-green", "verify-green 必须绑定 Red Evidence、可信 test Gate 和 TDD Evidence。");
|
|
679
|
+
const gates = new Map(workflow.gates.map((gate) => [gate.id, gate]));
|
|
680
|
+
for (const gate of ["test", "verify-red", "verify-green"])
|
|
681
|
+
if (gates.get(gate)?.evidence !== "trusted")
|
|
682
|
+
fail("WSSPEC_COMPILE_TDD_REQUIRED", `/gates/${gate}`, "功能 Workflow 缺少可信 Red/Green Gate。");
|
|
683
|
+
if (profileId === "quick" && packageRef === "builtin://workflows/feature-delivery") {
|
|
684
|
+
const disabled = flatten(steps).filter(({ enabled }) => !enabled).map(({ id }) => id);
|
|
685
|
+
if (disabled.some((id) => id !== "design") || byId.get("plan")?.enabled !== true || byId.get("plan")?.artifactLevel !== "compact")
|
|
686
|
+
fail("WSSPEC_COMPILE_QUICK_PROFILE_INVALID", "/profile/steps", "Quick 只能跳过 design,且必须保留 compact plan。");
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function validateDocumentationSafety(workflow, steps, byId) {
|
|
690
|
+
const verify = byId.get("verify-document");
|
|
691
|
+
const gate = workflow.gates.find(({ id }) => id === "docs.integrity");
|
|
692
|
+
if (verify === undefined || !verify.enabled || !verify.gates.includes("docs.integrity") || gate?.evidence !== "trusted")
|
|
693
|
+
fail("WSSPEC_COMPILE_DOCUMENTATION_GATE_REQUIRED", "/steps/verify-document", "文档 Workflow 必须包含可信 docs.integrity Gate。");
|
|
694
|
+
if (steps.some(({ id }) => ["write-tests", "verify-red", "implement", "verify-green"].includes(id)))
|
|
695
|
+
fail("WSSPEC_COMPILE_DOCUMENTATION_TDD_FORBIDDEN", "/steps", "文档 Workflow 不得伪造功能 TDD Cycle。");
|
|
696
|
+
}
|
|
697
|
+
function validRelativePathPattern(pattern) {
|
|
698
|
+
return isRepositoryRelativePattern(pattern);
|
|
699
|
+
}
|
|
700
|
+
function documentationPathClass(pattern) {
|
|
701
|
+
if (/^README[^/]*\.md$/i.test(pattern))
|
|
702
|
+
return "readme";
|
|
703
|
+
if (/^CHANGELOG[^/]*\.md$/i.test(pattern))
|
|
704
|
+
return "changelog";
|
|
705
|
+
if (/^docs\/.+\.md$/i.test(pattern))
|
|
706
|
+
return "docs-md";
|
|
707
|
+
if (/^docs\/.+\.mdx$/i.test(pattern))
|
|
708
|
+
return "docs-mdx";
|
|
709
|
+
if (/^docs\/.+\.txt$/i.test(pattern))
|
|
710
|
+
return "docs-txt";
|
|
711
|
+
return undefined;
|
|
712
|
+
}
|
|
713
|
+
function containsPattern(parent, child) {
|
|
714
|
+
if (parent === child)
|
|
715
|
+
return true;
|
|
716
|
+
if (documentationPathClass(parent) !== documentationPathClass(child))
|
|
717
|
+
return false;
|
|
718
|
+
if (defaultDocumentationPaths.includes(parent))
|
|
719
|
+
return true;
|
|
720
|
+
if (!/[?*]/.test(child))
|
|
721
|
+
return matchesRepositoryPath(parent, child);
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
function validateDocumentationPaths(paths, path) {
|
|
725
|
+
const result = [...new Set(paths)];
|
|
726
|
+
for (const [index, allowed] of result.entries()) {
|
|
727
|
+
if (!validRelativePathPattern(allowed) || documentationPathClass(allowed) === undefined)
|
|
728
|
+
fail("WSSPEC_CHANGE_POLICY_PATH_INVALID", `${path}/${index}`, "文档路径必须位于五类默认文档范围内。");
|
|
729
|
+
}
|
|
730
|
+
return result;
|
|
731
|
+
}
|
|
732
|
+
export function resolveChangePolicy(input) {
|
|
733
|
+
record(input, "/changePolicy", new Set(["workflowId", "policy", "documentationAllowedPaths"]), "WSSPEC_CHANGE_POLICY_OVERRIDE_FORBIDDEN");
|
|
734
|
+
const kind = input.policy?.kind ?? (input.workflowId === "documentation-delivery" ? "documentation-only" : "feature");
|
|
735
|
+
let allowedPaths;
|
|
736
|
+
if (kind === "documentation-only") {
|
|
737
|
+
const declared = input.policy?.allowedPaths ?? [];
|
|
738
|
+
const upperBound = validateDocumentationPaths(declared.length === 0 ? defaultDocumentationPaths : declared, "/changePolicy/allowedPaths");
|
|
739
|
+
for (const candidate of upperBound)
|
|
740
|
+
if (!defaultDocumentationPaths.some((parent) => containsPattern(parent, candidate)))
|
|
741
|
+
fail("WSSPEC_CHANGE_POLICY_EXPANSION", "/changePolicy/allowedPaths", "Workflow 文档路径扩大了内置上界。");
|
|
742
|
+
if (input.documentationAllowedPaths === undefined)
|
|
743
|
+
allowedPaths = upperBound;
|
|
744
|
+
else {
|
|
745
|
+
if (input.documentationAllowedPaths.length === 0)
|
|
746
|
+
fail("WSSPEC_CHANGE_POLICY_PATH_INVALID", "/changePolicy/documentationAllowedPaths", "项目文档路径不能为空。");
|
|
747
|
+
const narrowed = validateDocumentationPaths(input.documentationAllowedPaths, "/changePolicy/documentationAllowedPaths");
|
|
748
|
+
for (const candidate of narrowed)
|
|
749
|
+
if (!upperBound.some((parent) => containsPattern(parent, candidate)))
|
|
750
|
+
fail("WSSPEC_CHANGE_POLICY_EXPANSION", "/changePolicy/documentationAllowedPaths", "项目文档路径只能收窄 Workflow 范围。");
|
|
751
|
+
allowedPaths = narrowed;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
if (input.documentationAllowedPaths !== undefined)
|
|
756
|
+
fail("WSSPEC_CHANGE_POLICY_OVERRIDE_FORBIDDEN", "/changePolicy/documentationAllowedPaths", "功能 Workflow 不接受文档路径策略。");
|
|
757
|
+
allowedPaths = [...new Set(input.policy?.allowedPaths ?? ["**"])].sort();
|
|
758
|
+
if (allowedPaths.length === 0 || allowedPaths.some((allowed) => !validRelativePathPattern(allowed)))
|
|
759
|
+
fail("WSSPEC_CHANGE_POLICY_PATH_INVALID", "/changePolicy/allowedPaths", "功能修改路径必须是仓库相对 glob。");
|
|
760
|
+
}
|
|
761
|
+
const frozenPaths = Object.freeze([...allowedPaths]);
|
|
762
|
+
return Object.freeze({ kind, allowedPaths: frozenPaths, digest: sha256(`${JSON.stringify({ version: 1, kind, allowedPaths })}\n`) });
|
|
763
|
+
}
|
|
764
|
+
export function compileWorkflow(pkg, selected, projectGatePolicy) {
|
|
765
|
+
const gatePolicy = validateProjectGatePolicy(pkg.workflow, projectGatePolicy);
|
|
766
|
+
for (const [index, skill] of selected.skills.entries())
|
|
767
|
+
validateResolvedSkill(skill, index);
|
|
768
|
+
for (const [index, step] of pkg.workflow.steps.entries())
|
|
769
|
+
validateStepShape(step, `/steps/${index}`);
|
|
770
|
+
const sourceSteps = pkg.workflow.steps.flatMap((step) => [step, ...allSourceSteps(step.steps ?? [])]);
|
|
771
|
+
const sourceIds = new Set();
|
|
772
|
+
for (const step of sourceSteps) {
|
|
773
|
+
if (sourceIds.has(step.id))
|
|
774
|
+
fail("WSSPEC_COMPILE_DUPLICATE_STEP", `/steps/${step.id}`, `Step ID ${step.id} 重复。`);
|
|
775
|
+
sourceIds.add(step.id);
|
|
776
|
+
}
|
|
777
|
+
for (const [profileId, definition] of pkg.profiles)
|
|
778
|
+
validateProfile(definition, pkg.workflow.workflow.id, profileId, sourceIds);
|
|
779
|
+
const definition = pkg.profiles.get(selected.id);
|
|
780
|
+
if (definition === undefined)
|
|
781
|
+
fail("WSSPEC_COMPILE_PROFILE_NOT_FOUND", `/profiles/${selected.id}`, "Workflow Package 不包含所选 Profile。");
|
|
782
|
+
const changePolicy = resolveChangePolicy({ workflowId: pkg.workflow.workflow.id, ...(pkg.workflow.changePolicy === undefined ? {} : { policy: pkg.workflow.changePolicy }), ...(selected.documentationAllowedPaths === undefined ? {} : { documentationAllowedPaths: selected.documentationAllowedPaths }) });
|
|
783
|
+
let selectedSteps;
|
|
784
|
+
let selectedOrder;
|
|
785
|
+
let manifestValidated = false;
|
|
786
|
+
const profilesToCompile = [
|
|
787
|
+
[selected.id, definition],
|
|
788
|
+
...[...pkg.profiles].filter(([profileId]) => profileId !== selected.id),
|
|
789
|
+
];
|
|
790
|
+
for (const [profileId, profile] of profilesToCompile) {
|
|
791
|
+
const compiledSteps = pkg.workflow.steps.map((step, index) => compileStep(step, profile.steps, selected.skills, `/steps/${index}`));
|
|
792
|
+
const byId = new Map(compiledSteps.map((step) => [step.id, step]));
|
|
793
|
+
const order = validateDag(compiledSteps, "/steps");
|
|
794
|
+
validateGates(pkg.workflow, compiledSteps);
|
|
795
|
+
if (!manifestValidated) {
|
|
796
|
+
validateManifestDeclarations(pkg, compiledSteps);
|
|
797
|
+
manifestValidated = true;
|
|
798
|
+
}
|
|
799
|
+
if (changePolicy.kind === "feature")
|
|
800
|
+
validateFeatureSafety(pkg.workflow, pkg.ref, profileId, compiledSteps, byId);
|
|
801
|
+
else
|
|
802
|
+
validateDocumentationSafety(pkg.workflow, compiledSteps, byId);
|
|
803
|
+
validateProfileGatePolicy(profileId, compiledSteps, gatePolicy);
|
|
804
|
+
validateExpressions(pkg.workflow.steps, compiledSteps);
|
|
805
|
+
validateArtifactDependencies(compiledSteps, "/steps");
|
|
806
|
+
if (profileId === selected.id) {
|
|
807
|
+
selectedSteps = compiledSteps;
|
|
808
|
+
selectedOrder = order;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
validateProfileSafety(pkg.ref, pkg.workflow.workflow.id, sourceSteps, pkg.profiles);
|
|
812
|
+
if (selectedSteps === undefined || selectedOrder === undefined)
|
|
813
|
+
fail("WSSPEC_COMPILE_PROFILE_NOT_FOUND", `/profiles/${selected.id}`, "Workflow Package 不包含所选 Profile。");
|
|
814
|
+
return {
|
|
815
|
+
version: 1,
|
|
816
|
+
id: pkg.workflow.workflow.id,
|
|
817
|
+
packageRef: pkg.ref,
|
|
818
|
+
packageDigest: pkg.contentDigest,
|
|
819
|
+
profile: { id: selected.id, publishing: structuredClone(definition.publishing), audit: structuredClone(definition.audit) },
|
|
820
|
+
steps: selectedSteps,
|
|
821
|
+
order: selectedOrder,
|
|
822
|
+
changePolicy,
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
function allSourceSteps(steps) {
|
|
826
|
+
return steps.flatMap((step) => [step, ...allSourceSteps(step.steps ?? [])]);
|
|
827
|
+
}
|
|
828
|
+
//# sourceMappingURL=compiler.js.map
|