@sun-asterisk/sungen 3.3.0-alpha.1 → 3.3.0-alpha.10
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/dist/capabilities/discover.d.ts +4 -0
- package/dist/capabilities/discover.d.ts.map +1 -1
- package/dist/capabilities/discover.js +16 -2
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/cli/case-triage-hint.d.ts +14 -0
- package/dist/cli/case-triage-hint.d.ts.map +1 -0
- package/dist/cli/case-triage-hint.js +23 -0
- package/dist/cli/case-triage-hint.js.map +1 -0
- package/dist/cli/commands/audit.js +1 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/contract-derive-command.d.ts +4 -2
- package/dist/cli/commands/contract-derive-command.d.ts.map +1 -1
- package/dist/cli/commands/contract-derive-command.js +19 -5
- package/dist/cli/commands/contract-derive-command.js.map +1 -1
- package/dist/cli/commands/contract.d.ts +3 -0
- package/dist/cli/commands/contract.d.ts.map +1 -1
- package/dist/cli/commands/contract.js +40 -7
- package/dist/cli/commands/contract.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +59 -18
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/gate.d.ts.map +1 -1
- package/dist/cli/commands/gate.js +2 -1
- package/dist/cli/commands/gate.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +14 -2
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/journey.d.ts.map +1 -1
- package/dist/cli/commands/journey.js +6 -4
- package/dist/cli/commands/journey.js.map +1 -1
- package/dist/cli/commands/next.d.ts +7 -2
- package/dist/cli/commands/next.d.ts.map +1 -1
- package/dist/cli/commands/next.js +41 -10
- package/dist/cli/commands/next.js.map +1 -1
- package/dist/cli/commands/triage.d.ts +2 -1
- package/dist/cli/commands/triage.d.ts.map +1 -1
- package/dist/cli/commands/triage.js +45 -3
- package/dist/cli/commands/triage.js.map +1 -1
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +6 -2
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +5 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/json-error-on-exit.d.ts +34 -0
- package/dist/cli/json-error-on-exit.d.ts.map +1 -0
- package/dist/cli/json-error-on-exit.js +63 -0
- package/dist/cli/json-error-on-exit.js.map +1 -0
- package/dist/cli/project-sungen-invocation.d.ts +13 -0
- package/dist/cli/project-sungen-invocation.d.ts.map +1 -0
- package/dist/cli/project-sungen-invocation.js +95 -0
- package/dist/cli/project-sungen-invocation.js.map +1 -0
- package/dist/cli/run-command-hints.d.ts +9 -0
- package/dist/cli/run-command-hints.d.ts.map +1 -0
- package/dist/cli/run-command-hints.js +84 -0
- package/dist/cli/run-command-hints.js.map +1 -0
- package/dist/cli/waiting-for-you-block.d.ts +18 -0
- package/dist/cli/waiting-for-you-block.d.ts.map +1 -0
- package/dist/cli/waiting-for-you-block.js +22 -0
- package/dist/cli/waiting-for-you-block.js.map +1 -0
- package/dist/contract/accept-opaque-steps.d.ts +3 -0
- package/dist/contract/accept-opaque-steps.d.ts.map +1 -0
- package/dist/contract/accept-opaque-steps.js +114 -0
- package/dist/contract/accept-opaque-steps.js.map +1 -0
- package/dist/contract/approve-contract.d.ts +8 -1
- package/dist/contract/approve-contract.d.ts.map +1 -1
- package/dist/contract/approve-contract.js +6 -0
- package/dist/contract/approve-contract.js.map +1 -1
- package/dist/contract/build-contract-verdict.js +2 -2
- package/dist/contract/build-contract-verdict.js.map +1 -1
- package/dist/contract/build-trace-conformance.d.ts +5 -1
- package/dist/contract/build-trace-conformance.d.ts.map +1 -1
- package/dist/contract/build-trace-conformance.js +12 -14
- package/dist/contract/build-trace-conformance.js.map +1 -1
- package/dist/contract/classify-contract-change.d.ts +3 -2
- package/dist/contract/classify-contract-change.d.ts.map +1 -1
- package/dist/contract/classify-contract-change.js.map +1 -1
- package/dist/contract/classify-trace-conformance.d.ts +18 -3
- package/dist/contract/classify-trace-conformance.d.ts.map +1 -1
- package/dist/contract/classify-trace-conformance.js +64 -17
- package/dist/contract/classify-trace-conformance.js.map +1 -1
- package/dist/contract/contract-schema.d.ts +21 -0
- package/dist/contract/contract-schema.d.ts.map +1 -1
- package/dist/contract/contract-schema.js +25 -3
- package/dist/contract/contract-schema.js.map +1 -1
- package/dist/contract/contracts-lock-file.d.ts.map +1 -1
- package/dist/contract/contracts-lock-file.js +1 -0
- package/dist/contract/contracts-lock-file.js.map +1 -1
- package/dist/contract/derive-free-feature.d.ts +19 -0
- package/dist/contract/derive-free-feature.d.ts.map +1 -1
- package/dist/contract/derive-free-feature.js +40 -2
- package/dist/contract/derive-free-feature.js.map +1 -1
- package/dist/contract/project-contract-blockers.d.ts.map +1 -1
- package/dist/contract/project-contract-blockers.js +4 -2
- package/dist/contract/project-contract-blockers.js.map +1 -1
- package/dist/contract/record-contract-approval.d.ts +16 -1
- package/dist/contract/record-contract-approval.d.ts.map +1 -1
- package/dist/contract/record-contract-approval.js +29 -1
- package/dist/contract/record-contract-approval.js.map +1 -1
- package/dist/contract/render-free-gherkin.d.ts +2 -0
- package/dist/contract/render-free-gherkin.d.ts.map +1 -1
- package/dist/contract/render-free-gherkin.js +6 -0
- package/dist/contract/render-free-gherkin.js.map +1 -1
- package/dist/contract/resolve-unit-trace-identities.d.ts +12 -0
- package/dist/contract/resolve-unit-trace-identities.d.ts.map +1 -1
- package/dist/contract/resolve-unit-trace-identities.js +33 -49
- package/dist/contract/resolve-unit-trace-identities.js.map +1 -1
- package/dist/contract/trace/collect-trace-evidence.d.ts +16 -5
- package/dist/contract/trace/collect-trace-evidence.d.ts.map +1 -1
- package/dist/contract/trace/collect-trace-evidence.js +101 -43
- package/dist/contract/trace/collect-trace-evidence.js.map +1 -1
- package/dist/contract/trace/read-file-once.d.ts +20 -0
- package/dist/contract/trace/read-file-once.d.ts.map +1 -0
- package/dist/contract/trace/read-file-once.js +135 -0
- package/dist/contract/trace/read-file-once.js.map +1 -0
- package/dist/contract/trace/scenario-id-claims.d.ts +11 -0
- package/dist/contract/trace/scenario-id-claims.d.ts.map +1 -0
- package/dist/contract/trace/scenario-id-claims.js +89 -0
- package/dist/contract/trace/scenario-id-claims.js.map +1 -0
- package/dist/contract/trace/select-final-attempt.d.ts +4 -2
- package/dist/contract/trace/select-final-attempt.d.ts.map +1 -1
- package/dist/contract/trace/select-final-attempt.js +7 -4
- package/dist/contract/trace/select-final-attempt.js.map +1 -1
- package/dist/contract/trace/test-step-join.d.ts +3 -3
- package/dist/contract/trace/test-step-join.d.ts.map +1 -1
- package/dist/contract/trace/test-step-join.js +2 -2
- package/dist/contract/trace/test-step-join.js.map +1 -1
- package/dist/contract/trace/trace-binding.d.ts +3 -2
- package/dist/contract/trace/trace-binding.d.ts.map +1 -1
- package/dist/contract/trace/trace-binding.js +6 -3
- package/dist/contract/trace/trace-binding.js.map +1 -1
- package/dist/contract/trace/trace-file-reader.d.ts +14 -2
- package/dist/contract/trace/trace-file-reader.d.ts.map +1 -1
- package/dist/contract/trace/trace-file-reader.js +27 -45
- package/dist/contract/trace/trace-file-reader.js.map +1 -1
- package/dist/contract/trace/trace-freshness.d.ts +27 -0
- package/dist/contract/trace/trace-freshness.d.ts.map +1 -0
- package/dist/contract/trace/trace-freshness.js +146 -0
- package/dist/contract/trace/trace-freshness.js.map +1 -0
- package/dist/contract/trace/trace-identity-resolution.d.ts +2 -0
- package/dist/contract/trace/trace-identity-resolution.d.ts.map +1 -1
- package/dist/contract/trace/trace-identity-resolution.js +6 -3
- package/dist/contract/trace/trace-identity-resolution.js.map +1 -1
- package/dist/contract/trace/trace-run-record.d.ts +8 -3
- package/dist/contract/trace/trace-run-record.d.ts.map +1 -1
- package/dist/contract/trace/trace-run-record.js +42 -22
- package/dist/contract/trace/trace-run-record.js.map +1 -1
- package/dist/contract/trace/trace-steps.js +1 -1
- package/dist/contract/trace/trace-steps.js.map +1 -1
- package/dist/contract/trace/trace-zip-limits.d.ts +5 -1
- package/dist/contract/trace/trace-zip-limits.d.ts.map +1 -1
- package/dist/contract/trace/trace-zip-limits.js +6 -2
- package/dist/contract/trace/trace-zip-limits.js.map +1 -1
- package/dist/contract/verify-approval-decisions.d.ts.map +1 -1
- package/dist/contract/verify-approval-decisions.js +9 -4
- package/dist/contract/verify-approval-decisions.js.map +1 -1
- package/dist/decisions/confirmed-decision-commands.d.ts +11 -0
- package/dist/decisions/confirmed-decision-commands.d.ts.map +1 -1
- package/dist/decisions/confirmed-decision-commands.js +21 -3
- package/dist/decisions/confirmed-decision-commands.js.map +1 -1
- package/dist/decisions/decision-schema.d.ts +2 -1
- package/dist/decisions/decision-schema.d.ts.map +1 -1
- package/dist/decisions/decision-schema.js +2 -1
- package/dist/decisions/decision-schema.js.map +1 -1
- package/dist/decisions/git-head-trust.d.ts +12 -0
- package/dist/decisions/git-head-trust.d.ts.map +1 -1
- package/dist/decisions/git-head-trust.js +52 -0
- package/dist/decisions/git-head-trust.js.map +1 -1
- package/dist/decisions/human-confirmation.d.ts +2 -0
- package/dist/decisions/human-confirmation.d.ts.map +1 -1
- package/dist/decisions/human-confirmation.js +6 -0
- package/dist/decisions/human-confirmation.js.map +1 -1
- package/dist/decisions/legacy-waiver-migration.d.ts +8 -1
- package/dist/decisions/legacy-waiver-migration.d.ts.map +1 -1
- package/dist/decisions/legacy-waiver-migration.js +13 -4
- package/dist/decisions/legacy-waiver-migration.js.map +1 -1
- package/dist/exporters/csv-exporter.d.ts +11 -2
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +8 -4
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/matrix/export.d.ts +6 -1
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +11 -4
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/xlsx-cells.js +1 -1
- package/dist/exporters/matrix/xlsx-cells.js.map +1 -1
- package/dist/exporters/xlsx-exporter.d.ts +3 -2
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.js +4 -4
- package/dist/exporters/xlsx-exporter.js.map +1 -1
- package/dist/graph/board-human-actions.d.ts +5 -0
- package/dist/graph/board-human-actions.d.ts.map +1 -0
- package/dist/graph/board-human-actions.js +83 -0
- package/dist/graph/board-human-actions.js.map +1 -0
- package/dist/graph/evaluate-graph.d.ts +21 -1
- package/dist/graph/evaluate-graph.d.ts.map +1 -1
- package/dist/graph/evaluate-graph.js +37 -9
- package/dist/graph/evaluate-graph.js.map +1 -1
- package/dist/graph/human-action.d.ts +21 -0
- package/dist/graph/human-action.d.ts.map +1 -0
- package/dist/graph/human-action.js +3 -0
- package/dist/graph/human-action.js.map +1 -0
- package/dist/graph/manual-triage-decisions.d.ts +19 -0
- package/dist/graph/manual-triage-decisions.d.ts.map +1 -0
- package/dist/graph/manual-triage-decisions.js +53 -0
- package/dist/graph/manual-triage-decisions.js.map +1 -0
- package/dist/graph/node-decl.d.ts +16 -0
- package/dist/graph/node-decl.d.ts.map +1 -1
- package/dist/graph/node-decl.js +4 -1
- package/dist/graph/node-decl.js.map +1 -1
- package/dist/graph/nodes/test-case-contract-nodes.d.ts +3 -1
- package/dist/graph/nodes/test-case-contract-nodes.d.ts.map +1 -1
- package/dist/graph/nodes/test-case-contract-nodes.js +22 -6
- package/dist/graph/nodes/test-case-contract-nodes.js.map +1 -1
- package/dist/graph/nodes/test-case-nodes.d.ts +3 -0
- package/dist/graph/nodes/test-case-nodes.d.ts.map +1 -1
- package/dist/graph/nodes/test-case-nodes.js +33 -4
- package/dist/graph/nodes/test-case-nodes.js.map +1 -1
- package/dist/graph/nodes/unit-audit-nodes.d.ts.map +1 -1
- package/dist/graph/nodes/unit-audit-nodes.js +1 -0
- package/dist/graph/nodes/unit-audit-nodes.js.map +1 -1
- package/dist/graph/nodes/unit-contract-node.d.ts +2 -1
- package/dist/graph/nodes/unit-contract-node.d.ts.map +1 -1
- package/dist/graph/nodes/unit-contract-node.js +16 -1
- package/dist/graph/nodes/unit-contract-node.js.map +1 -1
- package/dist/graph/nodes/unit-design-nodes.d.ts.map +1 -1
- package/dist/graph/nodes/unit-design-nodes.js +2 -1
- package/dist/graph/nodes/unit-design-nodes.js.map +1 -1
- package/dist/graph/test-case-results.d.ts +1 -0
- package/dist/graph/test-case-results.d.ts.map +1 -1
- package/dist/graph/test-case-results.js +27 -3
- package/dist/graph/test-case-results.js.map +1 -1
- package/dist/graph/test-case-satisfied.d.ts +5 -4
- package/dist/graph/test-case-satisfied.d.ts.map +1 -1
- package/dist/graph/test-case-satisfied.js +4 -0
- package/dist/graph/test-case-satisfied.js.map +1 -1
- package/dist/graph/test-case-state-shape.d.ts +7 -1
- package/dist/graph/test-case-state-shape.d.ts.map +1 -1
- package/dist/graph/unit-artifact-probes.d.ts.map +1 -1
- package/dist/graph/unit-artifact-probes.js +3 -1
- package/dist/graph/unit-artifact-probes.js.map +1 -1
- package/dist/graph/unit-results-freshness.d.ts +29 -0
- package/dist/graph/unit-results-freshness.d.ts.map +1 -0
- package/dist/graph/unit-results-freshness.js +213 -0
- package/dist/graph/unit-results-freshness.js.map +1 -0
- package/dist/harness/audit.d.ts +3 -2
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +4 -17
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/catalog/gate-rules.yaml +3 -3
- package/dist/harness/defect-draft.d.ts.map +1 -1
- package/dist/harness/defect-draft.js +12 -2
- package/dist/harness/defect-draft.js.map +1 -1
- package/dist/harness/defects.d.ts +14 -0
- package/dist/harness/defects.d.ts.map +1 -1
- package/dist/harness/defects.js +14 -10
- package/dist/harness/defects.js.map +1 -1
- package/dist/harness/flow-check.d.ts +9 -0
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +11 -0
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/gates/audit-gate-verdict.d.ts.map +1 -1
- package/dist/harness/gates/audit-gate-verdict.js +2 -1
- package/dist/harness/gates/audit-gate-verdict.js.map +1 -1
- package/dist/harness/gates/audit-input-hash.d.ts +2 -0
- package/dist/harness/gates/audit-input-hash.d.ts.map +1 -1
- package/dist/harness/gates/audit-input-hash.js +30 -2
- package/dist/harness/gates/audit-input-hash.js.map +1 -1
- package/dist/harness/gates/audit-report-verification.d.ts +17 -6
- package/dist/harness/gates/audit-report-verification.d.ts.map +1 -1
- package/dist/harness/gates/audit-report-verification.js +55 -21
- package/dist/harness/gates/audit-report-verification.js.map +1 -1
- package/dist/harness/gates/delivery-checklist.d.ts +3 -0
- package/dist/harness/gates/delivery-checklist.d.ts.map +1 -1
- package/dist/harness/gates/delivery-checklist.js +39 -6
- package/dist/harness/gates/delivery-checklist.js.map +1 -1
- package/dist/harness/gates/delivery-decision-gate.d.ts +4 -0
- package/dist/harness/gates/delivery-decision-gate.d.ts.map +1 -1
- package/dist/harness/gates/delivery-decision-gate.js +26 -1
- package/dist/harness/gates/delivery-decision-gate.js.map +1 -1
- package/dist/harness/gates/delivery-decision-step.d.ts +4 -1
- package/dist/harness/gates/delivery-decision-step.d.ts.map +1 -1
- package/dist/harness/gates/delivery-decision-step.js +17 -2
- package/dist/harness/gates/delivery-decision-step.js.map +1 -1
- package/dist/harness/gates/in-run-decision-check.d.ts.map +1 -1
- package/dist/harness/gates/in-run-decision-check.js +40 -12
- package/dist/harness/gates/in-run-decision-check.js.map +1 -1
- package/dist/harness/gates/migrated-waiver-import-bounds.d.ts +13 -0
- package/dist/harness/gates/migrated-waiver-import-bounds.d.ts.map +1 -0
- package/dist/harness/gates/migrated-waiver-import-bounds.js +141 -0
- package/dist/harness/gates/migrated-waiver-import-bounds.js.map +1 -0
- package/dist/harness/gates/migrated-waiver-review.d.ts +14 -4
- package/dist/harness/gates/migrated-waiver-review.d.ts.map +1 -1
- package/dist/harness/gates/migrated-waiver-review.js +78 -80
- package/dist/harness/gates/migrated-waiver-review.js.map +1 -1
- package/dist/harness/journey.d.ts +40 -0
- package/dist/harness/journey.d.ts.map +1 -1
- package/dist/harness/journey.js +155 -22
- package/dist/harness/journey.js.map +1 -1
- package/dist/harness/next-step.d.ts +19 -0
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +21 -0
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/provenance.d.ts +29 -0
- package/dist/harness/provenance.d.ts.map +1 -1
- package/dist/harness/provenance.js +109 -9
- package/dist/harness/provenance.js.map +1 -1
- package/dist/harness/triage-manual-classification.d.ts +32 -0
- package/dist/harness/triage-manual-classification.d.ts.map +1 -0
- package/dist/harness/triage-manual-classification.js +56 -0
- package/dist/harness/triage-manual-classification.js.map +1 -0
- package/dist/harness/unit-paths.d.ts +8 -0
- package/dist/harness/unit-paths.d.ts.map +1 -1
- package/dist/harness/unit-paths.js +23 -1
- package/dist/harness/unit-paths.js.map +1 -1
- package/dist/hooks/bash-read-only-segment.d.ts +9 -0
- package/dist/hooks/bash-read-only-segment.d.ts.map +1 -0
- package/dist/hooks/bash-read-only-segment.js +168 -0
- package/dist/hooks/bash-read-only-segment.js.map +1 -0
- package/dist/hooks/bash-write-targets.d.ts +12 -2
- package/dist/hooks/bash-write-targets.d.ts.map +1 -1
- package/dist/hooks/bash-write-targets.js +122 -29
- package/dist/hooks/bash-write-targets.js.map +1 -1
- package/dist/hooks/guard-protected-paths.d.ts +4 -0
- package/dist/hooks/guard-protected-paths.d.ts.map +1 -1
- package/dist/hooks/guard-protected-paths.js +13 -3
- package/dist/hooks/guard-protected-paths.js.map +1 -1
- package/dist/hooks/guard-rules.d.ts +0 -17
- package/dist/hooks/guard-rules.d.ts.map +1 -1
- package/dist/hooks/guard-rules.js +79 -10
- package/dist/hooks/guard-rules.js.map +1 -1
- package/dist/hooks/guard-tty-spoof-rule.d.ts +3 -1
- package/dist/hooks/guard-tty-spoof-rule.d.ts.map +1 -1
- package/dist/hooks/guard-tty-spoof-rule.js +3 -2
- package/dist/hooks/guard-tty-spoof-rule.js.map +1 -1
- package/dist/hooks/hand-back-core.d.ts +9 -4
- package/dist/hooks/hand-back-core.d.ts.map +1 -1
- package/dist/hooks/hand-back-core.js +24 -11
- package/dist/hooks/hand-back-core.js.map +1 -1
- package/dist/hooks/session-start-unit-state-hook.d.ts +2 -1
- package/dist/hooks/session-start-unit-state-hook.d.ts.map +1 -1
- package/dist/hooks/session-start-unit-state-hook.js +9 -4
- package/dist/hooks/session-start-unit-state-hook.js.map +1 -1
- package/dist/hooks/stop-next-step-hook.d.ts.map +1 -1
- package/dist/hooks/stop-next-step-hook.js +1 -0
- package/dist/hooks/stop-next-step-hook.js.map +1 -1
- package/dist/orchestrator/assets-drift.d.ts +5 -0
- package/dist/orchestrator/assets-drift.d.ts.map +1 -1
- package/dist/orchestrator/assets-drift.js +12 -3
- package/dist/orchestrator/assets-drift.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +1 -0
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +6 -3
- package/dist/orchestrator/templates/ai-src/commands/delivery.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/review.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +1 -1
- package/dist/orchestrator/templates/ai-src/partials/waiting-for-you.claude.md +11 -0
- package/dist/orchestrator/templates/ai-src/partials/waiting-for-you.codex.md +11 -0
- package/dist/orchestrator/templates/ai-src/partials/waiting-for-you.copilot.md +11 -0
- package/package.json +3 -3
- package/src/capabilities/discover.ts +15 -2
- package/src/cli/case-triage-hint.ts +31 -0
- package/src/cli/commands/audit.ts +1 -1
- package/src/cli/commands/contract-derive-command.ts +20 -8
- package/src/cli/commands/contract.ts +41 -7
- package/src/cli/commands/delivery.ts +56 -18
- package/src/cli/commands/gate.ts +2 -1
- package/src/cli/commands/generate.ts +13 -2
- package/src/cli/commands/journey.ts +8 -6
- package/src/cli/commands/next.ts +41 -12
- package/src/cli/commands/triage.ts +41 -3
- package/src/cli/commands/update.ts +6 -2
- package/src/cli/index.ts +5 -1
- package/src/cli/json-error-on-exit.ts +72 -0
- package/src/cli/project-sungen-invocation.ts +59 -0
- package/src/cli/run-command-hints.ts +47 -0
- package/src/cli/waiting-for-you-block.ts +27 -0
- package/src/contract/accept-opaque-steps.ts +114 -0
- package/src/contract/approve-contract.ts +10 -3
- package/src/contract/build-contract-verdict.ts +2 -2
- package/src/contract/build-trace-conformance.ts +11 -8
- package/src/contract/classify-contract-change.ts +3 -3
- package/src/contract/classify-trace-conformance.ts +76 -22
- package/src/contract/contract-schema.ts +31 -2
- package/src/contract/contracts-lock-file.ts +1 -0
- package/src/contract/derive-free-feature.ts +46 -3
- package/src/contract/project-contract-blockers.ts +4 -2
- package/src/contract/record-contract-approval.ts +41 -1
- package/src/contract/render-free-gherkin.ts +5 -0
- package/src/contract/resolve-unit-trace-identities.ts +35 -12
- package/src/contract/trace/collect-trace-evidence.ts +105 -36
- package/src/contract/trace/read-file-once.ts +95 -0
- package/src/contract/trace/scenario-id-claims.ts +60 -0
- package/src/contract/trace/select-final-attempt.ts +12 -5
- package/src/contract/trace/test-step-join.ts +6 -5
- package/src/contract/trace/trace-binding.ts +7 -4
- package/src/contract/trace/trace-file-reader.ts +34 -13
- package/src/contract/trace/trace-freshness.ts +110 -0
- package/src/contract/trace/trace-identity-resolution.ts +7 -4
- package/src/contract/trace/trace-run-record.ts +37 -15
- package/src/contract/trace/trace-steps.ts +1 -1
- package/src/contract/trace/trace-zip-limits.ts +6 -2
- package/src/contract/verify-approval-decisions.ts +9 -5
- package/src/decisions/confirmed-decision-commands.ts +25 -4
- package/src/decisions/decision-schema.ts +2 -1
- package/src/decisions/git-head-trust.ts +44 -0
- package/src/decisions/human-confirmation.ts +5 -0
- package/src/decisions/legacy-waiver-migration.ts +14 -3
- package/src/exporters/csv-exporter.ts +12 -4
- package/src/exporters/matrix/export.ts +11 -4
- package/src/exporters/matrix/xlsx-cells.ts +1 -1
- package/src/exporters/xlsx-exporter.ts +5 -4
- package/src/graph/board-human-actions.ts +80 -0
- package/src/graph/evaluate-graph.ts +45 -10
- package/src/graph/human-action.ts +33 -0
- package/src/graph/manual-triage-decisions.ts +55 -0
- package/src/graph/node-decl.ts +18 -0
- package/src/graph/nodes/test-case-contract-nodes.ts +28 -7
- package/src/graph/nodes/test-case-nodes.ts +37 -5
- package/src/graph/nodes/unit-audit-nodes.ts +2 -1
- package/src/graph/nodes/unit-contract-node.ts +20 -2
- package/src/graph/nodes/unit-design-nodes.ts +2 -1
- package/src/graph/test-case-results.ts +25 -4
- package/src/graph/test-case-satisfied.ts +7 -4
- package/src/graph/test-case-state-shape.ts +7 -2
- package/src/graph/unit-artifact-probes.ts +3 -1
- package/src/graph/unit-results-freshness.ts +165 -0
- package/src/harness/audit.ts +4 -13
- package/src/harness/catalog/gate-rules.yaml +3 -3
- package/src/harness/defect-draft.ts +13 -2
- package/src/harness/defects.ts +22 -9
- package/src/harness/flow-check.ts +11 -0
- package/src/harness/gates/audit-gate-verdict.ts +2 -1
- package/src/harness/gates/audit-input-hash.ts +27 -3
- package/src/harness/gates/audit-report-verification.ts +57 -24
- package/src/harness/gates/delivery-checklist.ts +39 -7
- package/src/harness/gates/delivery-decision-gate.ts +29 -1
- package/src/harness/gates/delivery-decision-step.ts +19 -3
- package/src/harness/gates/in-run-decision-check.ts +38 -13
- package/src/harness/gates/migrated-waiver-import-bounds.ts +103 -0
- package/src/harness/gates/migrated-waiver-review.ts +90 -37
- package/src/harness/journey.ts +174 -21
- package/src/harness/next-step.ts +34 -1
- package/src/harness/provenance.ts +99 -11
- package/src/harness/triage-manual-classification.ts +76 -0
- package/src/harness/unit-paths.ts +19 -0
- package/src/hooks/bash-read-only-segment.ts +130 -0
- package/src/hooks/bash-write-targets.ts +109 -23
- package/src/hooks/guard-protected-paths.ts +13 -3
- package/src/hooks/guard-rules.ts +37 -18
- package/src/hooks/guard-tty-spoof-rule.ts +6 -3
- package/src/hooks/hand-back-core.ts +24 -12
- package/src/hooks/session-start-unit-state-hook.ts +9 -4
- package/src/hooks/stop-next-step-hook.ts +1 -0
- package/src/orchestrator/assets-drift.ts +12 -5
- package/src/orchestrator/project-initializer.ts +1 -0
- package/src/orchestrator/templates/ai-src/commands/create-test.md +6 -3
- package/src/orchestrator/templates/ai-src/commands/delivery.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/review.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/run-test.md +1 -1
- package/src/orchestrator/templates/ai-src/partials/waiting-for-you.claude.md +11 -0
- package/src/orchestrator/templates/ai-src/partials/waiting-for-you.codex.md +11 -0
- package/src/orchestrator/templates/ai-src/partials/waiting-for-you.copilot.md +11 -0
|
@@ -1,15 +1,44 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { resolveUnit } from '../resolve-unit';
|
|
4
|
+
import { withProjectInvocation, withProjectInvocationInText } from '../project-sungen-invocation';
|
|
5
|
+
import { processIO } from '../../decisions/human-confirmation';
|
|
6
|
+
import { manualClassifications } from '../../graph/manual-triage-decisions';
|
|
7
|
+
import { testCaseStates } from '../../graph/test-case-states';
|
|
4
8
|
import { triageUnit, writeTriageReport } from '../../harness/triage-unit';
|
|
9
|
+
import { classifyRedCase } from '../../harness/triage-manual-classification';
|
|
5
10
|
import type { TriageClass } from '../../harness/triage';
|
|
6
11
|
|
|
7
12
|
/** Display order: what needs a decision first, green last. */
|
|
8
13
|
const ORDER: TriageClass[] = ['product-candidate', 'unclassified', 'test-defect', 'environment', 'blocked-capability', 'known-defect', 'flaky'];
|
|
9
14
|
|
|
15
|
+
interface TriageOptions { screen?: string; api?: string; area?: string; json?: boolean; case?: string; as?: string; reason?: string }
|
|
16
|
+
|
|
17
|
+
/** The classifications by hand in force for the unit (they win over the automatic class of the same red). */
|
|
18
|
+
function printManual(cwd: string, unitDir: string, name: string): void {
|
|
19
|
+
const inForce = testCaseStates(cwd, name, { unitDir, auditFindings: [] }).filter((c) => c.triage?.ruleId === 'manual');
|
|
20
|
+
if (!inForce.length) return;
|
|
21
|
+
const why = new Map(manualClassifications(cwd, unitDir).map((m) => [m.decision, m]));
|
|
22
|
+
console.log(`\n classified by hand (${inForce.length}) — these win over the automatic class`);
|
|
23
|
+
for (const c of inForce) {
|
|
24
|
+
const m = why.get(c.triage!.decision ?? '');
|
|
25
|
+
console.log(` • ${c.id} → ${c.triage!.class}${m ? ` (${m.decision}, by ${m.by}): ${m.reason}` : ''}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** `--case <id> --as <class> --reason "…"`: record one classification by hand (harness/triage-manual-classification.ts). */
|
|
30
|
+
async function classifyByHand(cwd: string, name: string, o: TriageOptions): Promise<void> {
|
|
31
|
+
if (!o.case || !o.as) throw new Error(o.as ? '--as needs --case <id>' : '--case needs --as product|test-defect|environment and --reason "…"');
|
|
32
|
+
const r = await classifyRedCase({ projectRoot: cwd, unit: name, caseId: o.case, as: o.as, reason: o.reason, io: processIO() });
|
|
33
|
+
if (o.json) { console.log(JSON.stringify({ status: r.status, caseId: r.caseId, class: r.class, path: path.relative(cwd, r.path), entry: r.entry }, null, 2)); return; }
|
|
34
|
+
console.log(`\n${r.status === 'created' ? '✓ Recorded' : '= Already recorded'} ${r.entry.id}: ${r.caseId} → ${r.class} (classified by hand) → ${path.relative(cwd, r.path)}`);
|
|
35
|
+
console.log(' It holds until the case runs again. Commit it through review with the run it judged.\n');
|
|
36
|
+
}
|
|
37
|
+
|
|
10
38
|
/**
|
|
11
39
|
* `sungen triage` — classify every red or flaky scenario of the latest run (see harness/triage.ts).
|
|
12
|
-
* Reports, never gates: always exits 0 once the unit is found.
|
|
40
|
+
* Reports, never gates: always exits 0 once the unit is found. With `--case --as` it records one
|
|
41
|
+
* classification by hand instead (exit 1 when refused or invalid).
|
|
13
42
|
*/
|
|
14
43
|
export function registerTriageCommand(program: Command): void {
|
|
15
44
|
program
|
|
@@ -18,14 +47,18 @@ export function registerTriageCommand(program: Command): void {
|
|
|
18
47
|
.option('-s, --screen <name>', 'Screen or flow name')
|
|
19
48
|
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
20
49
|
.option('--area <name>', 'Alias of --api')
|
|
50
|
+
.option('--case <id>', 'Classify one red test case by hand (its VP-id) — with --as and --reason')
|
|
51
|
+
.option('--as <class>', 'product (anyone may record it) | test-defect | environment (test-defect and environment: a human types a confirmation on a TTY)')
|
|
52
|
+
.option('--reason <text>', 'Why the red is that class (mandatory with --as)')
|
|
21
53
|
.option('--json', 'Output the raw JSON report')
|
|
22
|
-
.action((o:
|
|
54
|
+
.action(async (o: TriageOptions) => {
|
|
23
55
|
try {
|
|
24
56
|
const name = o.screen || o.api || o.area;
|
|
25
57
|
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
26
58
|
const cwd = process.cwd();
|
|
27
59
|
const unit = resolveUnit(name, cwd);
|
|
28
60
|
if (!unit) throw new Error(`Not found: qa/screens/${name}, qa/flows/${name}, or qa/api/${name}`);
|
|
61
|
+
if (o.case || o.as) { await classifyByHand(cwd, name, o); return; }
|
|
29
62
|
|
|
30
63
|
const report = triageUnit(cwd, unit);
|
|
31
64
|
if (report.resultFiles.length === 0) {
|
|
@@ -48,9 +81,14 @@ export function registerTriageCommand(program: Command): void {
|
|
|
48
81
|
L(` ${r.ruleId}: ${r.evidence}`);
|
|
49
82
|
}
|
|
50
83
|
}
|
|
84
|
+
printManual(cwd, unit.dir, name);
|
|
85
|
+
if (report.rows.some((r) => r.class === 'unclassified')) {
|
|
86
|
+
L(`\n Unclassified: read the evidence (trace, error), then classify by hand:\n ${withProjectInvocation(`sungen triage --${unit.kind === 'api' ? 'api' : 'screen'} ${name} --case <id> --as product|test-defect|environment --reason "…"`, cwd)}`);
|
|
87
|
+
}
|
|
51
88
|
L(`\n Report: ${path.relative(cwd, saved)}\n`);
|
|
52
89
|
} catch (error) {
|
|
53
|
-
|
|
90
|
+
// A refusal names the command a human runs instead: as this project runs sungen (#963).
|
|
91
|
+
console.error('Error:', error instanceof Error ? withProjectInvocationInText(error.message, process.cwd()) : error);
|
|
54
92
|
process.exitCode = 1;
|
|
55
93
|
}
|
|
56
94
|
});
|
|
@@ -26,7 +26,9 @@ import { syncProjectSungenDeps } from '../../orchestrator/project-deps-sync';
|
|
|
26
26
|
*
|
|
27
27
|
* The public `--skip-npm-install` flag is still available for users who
|
|
28
28
|
* already installed the binary by other means (CI pipelines, corporate
|
|
29
|
-
* mirror, manual download)
|
|
29
|
+
* mirror, manual download). It skips only step 1: the project sync still
|
|
30
|
+
* re-pins package.json (security overrides included) and runs `npm install`
|
|
31
|
+
* there — `--skip-project` skips that (#939).
|
|
30
32
|
*/
|
|
31
33
|
|
|
32
34
|
const SKIP_NPM_ENV = 'SUNGEN_UPDATE_SKIP_NPM';
|
|
@@ -70,9 +72,11 @@ export function registerUpdateCommand(program: Command): void {
|
|
|
70
72
|
'--tag <dist-tag>',
|
|
71
73
|
'Install a specific npm dist-tag (e.g. latest, beta, alpha, next). Overrides --beta / --alpha.',
|
|
72
74
|
)
|
|
75
|
+
// #939: it skips only the GLOBAL install — the project is still synced, so the help says so.
|
|
73
76
|
.option(
|
|
74
77
|
'--skip-npm-install',
|
|
75
|
-
'Skip the global npm install step
|
|
78
|
+
'Skip the global npm install step. The project is still synced: its sungen packages are re-pinned in package.json '
|
|
79
|
+
+ '(security `overrides` included) and `npm install` runs in the project — add --skip-project to skip that too',
|
|
76
80
|
false,
|
|
77
81
|
)
|
|
78
82
|
.option(
|
package/src/cli/index.ts
CHANGED
|
@@ -45,6 +45,7 @@ import { capabilityRegistry } from '../capabilities/registry';
|
|
|
45
45
|
import { discoverAndRegisterCapabilities } from '../capabilities/discover';
|
|
46
46
|
import { warnAssetsDrift } from '../orchestrator/assets-drift';
|
|
47
47
|
import { attachCliHandBack } from '../hooks/hand-back-core';
|
|
48
|
+
import { attachJsonErrorOnExit, commandSupportsJson } from './json-error-on-exit';
|
|
48
49
|
import { registerHookCommand } from '../hooks/hook-cli-command';
|
|
49
50
|
|
|
50
51
|
// Read version from package.json so `--version` never drifts from the released version.
|
|
@@ -56,10 +57,12 @@ async function main() {
|
|
|
56
57
|
// process exit (not postAction) because several commands exit with a MEANINGFUL code that skips
|
|
57
58
|
// commander's hooks (`depth-lint` → 2). It points at `sungen next` instead of restating a step,
|
|
58
59
|
// so it cannot drift from the project state. A failure prints it on stderr after the error;
|
|
59
|
-
// `--json`/`--grep` keep it off stdout (#836); `
|
|
60
|
+
// `--json`/`--grep` keep it off stdout (#836); `hook` never prints one, `next` only when it fails (#937). The line and
|
|
60
61
|
// those rules live in the hook core (src/hooks/hand-back-core.ts) — this file only wires it.
|
|
61
62
|
const handBack = attachCliHandBack(process.argv, process.cwd());
|
|
62
63
|
const program = new Command();
|
|
64
|
+
// #939: a failed `--json` command prints {"error":{code,message}} on stdout (json-error-on-exit.ts).
|
|
65
|
+
const jsonErrors = attachJsonErrorOnExit(process.argv, (args) => commandSupportsJson(program, args));
|
|
63
66
|
|
|
64
67
|
program
|
|
65
68
|
.name('sungen')
|
|
@@ -130,6 +133,7 @@ registerSetupMobileEnvCommand(program);
|
|
|
130
133
|
// The action knows its unit and output mode; the hand-back default above is refined here.
|
|
131
134
|
program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
132
135
|
handBack.onAction(actionCommand.name(), actionCommand.opts() as Record<string, unknown>, actionCommand.args);
|
|
136
|
+
jsonErrors.onAction();
|
|
133
137
|
});
|
|
134
138
|
|
|
135
139
|
await program.parseAsync(process.argv);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--json` failures speak JSON too (#939 item 7). A command that supports `--json` and fails used to
|
|
3
|
+
* print its error on stderr and NOTHING on stdout, so a caller parsing stdout got an empty string. Now,
|
|
4
|
+
* when such a command exits non-zero without having written stdout, the exit prints one object:
|
|
5
|
+
*
|
|
6
|
+
* {"error":{"code":"command-failed" | "usage-error","message":"<the error it printed>"}}
|
|
7
|
+
*
|
|
8
|
+
* command-failed the action ran and failed (`next nope --json`)
|
|
9
|
+
* usage-error commander refused the arguments before any action ran (`audit --json --bogus`)
|
|
10
|
+
* The human error stays on stderr, unchanged. A command that printed its own JSON keeps it, whatever
|
|
11
|
+
* its exit code (`contract check --json` exits 2 with the verdict); a command without `--json` is never
|
|
12
|
+
* touched — commander's own "unknown option '--json'" is its usage error, not a JSON caller's.
|
|
13
|
+
* Like the hand-back, it hangs off process exit: commands exit from inside their actions.
|
|
14
|
+
*/
|
|
15
|
+
import type { Command } from 'commander';
|
|
16
|
+
|
|
17
|
+
export type JsonErrorCode = 'command-failed' | 'usage-error';
|
|
18
|
+
export interface JsonError { error: { code: JsonErrorCode; message: string } }
|
|
19
|
+
|
|
20
|
+
const ANSI = /\u001b\[[0-9;]*m/g;
|
|
21
|
+
const ERROR_LINE = /^(?:Error|error|Fatal error|❌[^:]*):\s*/;
|
|
22
|
+
const MAX_CAPTURE = 64 * 1024;
|
|
23
|
+
|
|
24
|
+
/** The error object for what the command printed on stderr: from its first error line on, the hand-back dropped. */
|
|
25
|
+
export function jsonErrorPayload(stderr: string, actionRan: boolean, exitCode: number): JsonError {
|
|
26
|
+
const lines = stderr.replace(ANSI, '').split('\n').map((l) => l.trimEnd()).filter((l) => l && !/^Next: /.test(l));
|
|
27
|
+
const at = lines.findIndex((l) => ERROR_LINE.test(l));
|
|
28
|
+
const picked = at >= 0 ? lines.slice(at) : lines.slice(-1);
|
|
29
|
+
const message = picked.join('\n').replace(ERROR_LINE, '').trim() || `exited with code ${exitCode}`;
|
|
30
|
+
return { error: { code: actionRan ? 'command-failed' : 'usage-error', message } };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The (sub)command `args` name, walked down from `program`, defines `--json`. */
|
|
34
|
+
export function commandSupportsJson(program: Command, args: string[]): boolean {
|
|
35
|
+
let cmd: Command = program;
|
|
36
|
+
for (const a of args) {
|
|
37
|
+
if (a.startsWith('-')) continue;
|
|
38
|
+
const sub = cmd.commands.find((c) => c.name() === a || c.aliases().includes(a));
|
|
39
|
+
if (!sub) break;
|
|
40
|
+
cmd = sub;
|
|
41
|
+
}
|
|
42
|
+
return cmd !== program && cmd.options.some((o) => o.long === '--json');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Wire it to process exit for `argv` (a no-op without `--json`). `supportsJson` is asked at exit, once
|
|
47
|
+
* every command is registered; `onAction` marks that an action ran (a failure then is not a usage error).
|
|
48
|
+
*/
|
|
49
|
+
export function attachJsonErrorOnExit(argv: string[], supportsJson: (args: string[]) => boolean): { onAction: () => void } {
|
|
50
|
+
const args = argv.slice(2);
|
|
51
|
+
if (!args.includes('--json')) return { onAction: () => undefined };
|
|
52
|
+
let wroteStdout = false;
|
|
53
|
+
let stderr = '';
|
|
54
|
+
let actionRan = false;
|
|
55
|
+
const writeOut = process.stdout.write.bind(process.stdout);
|
|
56
|
+
const writeErr = process.stderr.write.bind(process.stderr);
|
|
57
|
+
process.stdout.write = ((chunk: unknown, ...rest: unknown[]) => {
|
|
58
|
+
if (String(chunk).length) wroteStdout = true;
|
|
59
|
+
return (writeOut as (...a: unknown[]) => boolean)(chunk, ...rest);
|
|
60
|
+
}) as typeof process.stdout.write;
|
|
61
|
+
process.stderr.write = ((chunk: unknown, ...rest: unknown[]) => {
|
|
62
|
+
if (stderr.length < MAX_CAPTURE) stderr += String(chunk);
|
|
63
|
+
return (writeErr as (...a: unknown[]) => boolean)(chunk, ...rest);
|
|
64
|
+
}) as typeof process.stderr.write;
|
|
65
|
+
process.on('exit', (code) => {
|
|
66
|
+
try {
|
|
67
|
+
if (code === 0 || wroteStdout || !supportsJson(args)) return;
|
|
68
|
+
writeOut(`${JSON.stringify(jsonErrorPayload(stderr, actionRan, code))}\n`);
|
|
69
|
+
} catch { /* the exit code is the contract; this object is a courtesy */ }
|
|
70
|
+
});
|
|
71
|
+
return { onAction: () => { actionRan = true; } };
|
|
72
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a hint names sungen inside a project (#939, items 1–2).
|
|
3
|
+
*
|
|
4
|
+
* A project that installs its own sungen (`npm i -D @sun-asterisk/sungen`) runs it with `npx sungen`;
|
|
5
|
+
* a bare `sungen` is whatever global install the machine has — in the field test an older 3.2.24-beta
|
|
6
|
+
* beside the project's 3.3.0-alpha. And `sungen update` without a flag switches to the stable
|
|
7
|
+
* channel, so on an alpha or beta install the update hint must keep the channel it runs on.
|
|
8
|
+
*
|
|
9
|
+
* sungenInvocation(root) 'npx sungen' when <root>/node_modules/@sun-asterisk/sungen exists, else 'sungen'
|
|
10
|
+
* withProjectInvocation(cmd, root) a canonical `sungen …` command as this project runs it
|
|
11
|
+
* withProjectInvocationInText(text, root)
|
|
12
|
+
* every `sungen …` command inside printed text (a refusal, a hint, a
|
|
13
|
+
* step's reason) as this project runs it (#963); prose mentions stay
|
|
14
|
+
* updateCommand(root, version?) `<invocation> update` plus `--alpha` / `--beta` (`--tag <id>` for another prerelease)
|
|
15
|
+
*
|
|
16
|
+
* Only the project root itself is looked at — where `npm i` puts a QA project's install — so a
|
|
17
|
+
* monorepo checkout above a fixture does not turn its hints into `npx`.
|
|
18
|
+
*/
|
|
19
|
+
import * as fs from 'fs';
|
|
20
|
+
import * as path from 'path';
|
|
21
|
+
import { getPackageVersion } from '../exporters/package-info';
|
|
22
|
+
|
|
23
|
+
export function sungenInvocation(projectRoot: string): string {
|
|
24
|
+
return fs.existsSync(path.join(projectRoot, 'node_modules', '@sun-asterisk', 'sungen', 'package.json')) ? 'npx sungen' : 'sungen';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** `sungen audit --screen x` → `npx sungen audit --screen x` in a project with its own install; anything else unchanged. */
|
|
28
|
+
export function withProjectInvocation(command: string, projectRoot: string): string {
|
|
29
|
+
const inv = sungenInvocation(projectRoot);
|
|
30
|
+
return inv !== 'sungen' && /^sungen\b/.test(command) ? `${inv}${command.slice('sungen'.length)}` : command;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A `sungen` that starts a command inside text: at the start of a line (after indentation), after a
|
|
35
|
+
* backtick or `(`, after ": ", "run " or "then " — and followed by a subcommand or an option. A prose
|
|
36
|
+
* mention ("written by sungen 3.3.0", "an older sungen", "sungen: …") is none of these.
|
|
37
|
+
*/
|
|
38
|
+
const COMMAND_IN_TEXT = /(^[ \t]*|[`(]|:[ \t]+|\b(?:[Rr]un|then)[ \t]+)sungen(?= [a-z-])/gm;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Printed text (a refusal, a hint, a step's reason) with every `sungen …` command in it as this project
|
|
42
|
+
* runs sungen (#963): the human-only commands a QA copies from a refusal must run the project's own
|
|
43
|
+
* install, not an older global one. Text for machines (`--json`) stays canonical; this is for the screen.
|
|
44
|
+
*/
|
|
45
|
+
export function withProjectInvocationInText(text: string, projectRoot: string): string {
|
|
46
|
+
const inv = sungenInvocation(projectRoot);
|
|
47
|
+
return inv === 'sungen' ? text : text.replace(COMMAND_IN_TEXT, (_m, before: string) => `${before}${inv}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The `sungen update` flag that keeps the channel of `version` (its prerelease id); '' for stable. */
|
|
51
|
+
export function updateChannelFlag(version: string = getPackageVersion()): string {
|
|
52
|
+
const id = version.match(/^\d+\.\d+\.\d+-([0-9A-Za-z-]+)/)?.[1];
|
|
53
|
+
if (!id) return '';
|
|
54
|
+
return id === 'alpha' || id === 'beta' ? ` --${id}` : ` --tag ${id}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function updateCommand(projectRoot: string, version: string = getPackageVersion()): string {
|
|
58
|
+
return `${sungenInvocation(projectRoot)} update${updateChannelFlag(version)}`;
|
|
59
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The run commands sungen suggests, each writing its results where the reader that asks for them
|
|
3
|
+
* looks (#964). A hint whose results nobody reads leaves a passing run invisible: the plain
|
|
4
|
+
* `npx playwright test` wrote `test-result.json` at the project root, which `sungen next` never reads.
|
|
5
|
+
*
|
|
6
|
+
* generatedSubdir(kind, unit) `specs/generated/<unit>` · `…/flows/<flow>` · `…/api/<area>` — where
|
|
7
|
+
* `generate` writes a unit's specs and `next` / `delivery` read its results
|
|
8
|
+
* plainRunCommand(genRel, feature) one compiled feature's spec with the JSON reporter pointed at
|
|
9
|
+
* `<genRel>/<feature>-test-result.json` (graph/unit-artifact-probes RESULT_FILE)
|
|
10
|
+
* freeRunCommand(cwd) hand-written @free / impl:code tests: the project's free-test config, which
|
|
11
|
+
* writes into its own outputDir `test-results-free/` (#941)
|
|
12
|
+
* freeRunHasResults(cwd) that run left its results (or a sungen run record) there
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { FREE_RUN_DIR } from '../contract/trace/trace-run-record';
|
|
17
|
+
|
|
18
|
+
export type RunUnitKind = 'screen' | 'flow' | 'api';
|
|
19
|
+
|
|
20
|
+
export function generatedSubdir(kind: RunUnitKind, unit: string): string {
|
|
21
|
+
return path.posix.join('specs', 'generated', ...(kind === 'flow' ? ['flows'] : kind === 'api' ? ['api'] : []), unit);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** `[SUNGEN_ENV=<env> ]PLAYWRIGHT_JSON_OUTPUT_NAME=<genRel>/<feature>-test-result.json npx playwright test <genRel>/<feature>.spec.ts` */
|
|
25
|
+
export function plainRunCommand(genRel: string, featureBaseName: string, env?: string): string {
|
|
26
|
+
return `${env ? `SUNGEN_ENV=${env} ` : ''}PLAYWRIGHT_JSON_OUTPUT_NAME=${genRel}/${featureBaseName}-test-result.json npx playwright test ${genRel}/${featureBaseName}.spec.ts`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const FREE_CONFIG = /^playwright\.free\.config\.(?:ts|mts|cts|js|mjs|cjs)$/;
|
|
30
|
+
|
|
31
|
+
/** The free run: `npx playwright test -c <playwright.free.config.*>` when the project has one, else what that config holds. */
|
|
32
|
+
export function freeRunCommand(cwd: string): string {
|
|
33
|
+
let config: string | undefined;
|
|
34
|
+
try { config = fs.readdirSync(cwd).find((f) => FREE_CONFIG.test(f)); } catch { /* no project dir: the generic form */ }
|
|
35
|
+
return config
|
|
36
|
+
? `npx playwright test -c ${config}`
|
|
37
|
+
: `run the hand-written tests with the project's free-test config (outputDir '${FREE_RUN_DIR}', trace: 'on', the sungen run-record reporter — "Free tests" in the sungen docs)`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The free run left its JSON results or a sungen run record in `test-results-free/`. */
|
|
41
|
+
export function freeRunHasResults(cwd: string): boolean {
|
|
42
|
+
try {
|
|
43
|
+
return fs.readdirSync(path.join(cwd, FREE_RUN_DIR)).some((f) => f === 'results.json' || /^sungen-run-record.*\.json$/.test(f));
|
|
44
|
+
} catch {
|
|
45
|
+
return false; // never run
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The "Waiting for you" block (#954) — every pending human-only action of a unit, as ready-to-copy
|
|
3
|
+
* commands with the reason under each. One renderer for the three places that show it: `sungen next`
|
|
4
|
+
* (text) and the Stop / SessionStart hook context. Commands — and those inside a reason (#963) — as the
|
|
5
|
+
* project runs sungen (`npx sungen` with a local install, #939); `--json` keeps `command` canonical in
|
|
6
|
+
* `humanActions` and adds `rendered`. An instruction (a commit through review, a `git checkout --`) is
|
|
7
|
+
* marked `✎ … (by hand, not a command)`, never shown as one to copy.
|
|
8
|
+
*/
|
|
9
|
+
import type { HumanAction } from '../graph/human-action';
|
|
10
|
+
import { withProjectInvocation, withProjectInvocationInText } from './project-sungen-invocation';
|
|
11
|
+
|
|
12
|
+
export const WAITING_FOR_YOU_HEAD = '⏳ Waiting for you — a human runs these in their own terminal (in Claude Code: ! <command>); an agent never runs them:';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The block's lines (indented two spaces), or none when nothing waits for a human and nothing holds the
|
|
16
|
+
* rest. `heldBy`: the title of the blocking step that cut the frontier — the block is not exhaustive then,
|
|
17
|
+
* and says what holds the rest, with that step's command (`heldByCommand`) when given (#966). A held
|
|
18
|
+
* block with no item of its own still says so, rather than vanishing (a QA read "nothing is waiting").
|
|
19
|
+
*/
|
|
20
|
+
export function waitingForYouLines(actions: readonly HumanAction[] | undefined, projectRoot: string, heldBy?: string, heldByCommand?: string): string[] {
|
|
21
|
+
const holds = heldBy ? `"${heldBy}" clears${heldByCommand ? ` (the step above: ${withProjectInvocationInText(heldByCommand, projectRoot)})` : ''}` : '';
|
|
22
|
+
if (!actions?.length) return heldBy ? [` ⏳ Waiting for you: nothing yet — more appear once ${holds}.`] : [];
|
|
23
|
+
return [` ${WAITING_FOR_YOU_HEAD}`, ...actions.flatMap((a) => [
|
|
24
|
+
a.instruction ? ` ✎ ${a.command} (by hand, not a command)` : ` ${withProjectInvocation(a.command, projectRoot)}`,
|
|
25
|
+
` ${withProjectInvocationInText(a.reason, projectRoot)}`,
|
|
26
|
+
]), ...(heldBy ? [` More appear once ${holds}.`] : [])];
|
|
27
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sungen contract approve --screen <u> --scenario <id> --accept-opaque` — a QA accepts the reviewed
|
|
3
|
+
* opaque steps of ONE approved free / impl:code scenario (#923). An opaque step (a check on a JS value,
|
|
4
|
+
* an API request, an evaluate) is never compared by code, so without this an approved test that has one
|
|
5
|
+
* stays in review for good: re-approving changes nothing and contract.opaque-step allows no override.
|
|
6
|
+
* In order, like `approve` (approve-contract.ts), so a human never confirms something then refused:
|
|
7
|
+
*
|
|
8
|
+
* 1. the scenario must be approved AS IT READS NOW — its lock entry human-approved, intact, at the
|
|
9
|
+
* current hash, and vouched for by its decision — else: approve it first
|
|
10
|
+
* 2. its verdict is rebuilt (buildContractVerdict, as `contract check`): the opaque steps must be the
|
|
11
|
+
* ONLY thing holding it; anything else (changed, unproven, inconclusive, a `low` identity…) refuses
|
|
12
|
+
* with its own fix. Every opaque step already accepted → nothing to accept, no prompt
|
|
13
|
+
* 3. each opaque step is shown verbatim with its file:line; on a TTY the human types the scenario id
|
|
14
|
+
* and a one-time code — without one: refused with the exact command (an agent never runs this)
|
|
15
|
+
* 4. everything is read AGAIN: if the trace, the feature or the lock moved meanwhile → refused
|
|
16
|
+
* 5. a contract-approval decision records the scenario at its hash WITH the accepted steps (step + sha)
|
|
17
|
+
* and the lock entry points at it (record-contract-approval.ts)
|
|
18
|
+
*
|
|
19
|
+
* The acceptance is bound to each step's sha (opaqueStepSha): a change to an accepted step, or a new
|
|
20
|
+
* approval of the scenario, brings it back to review.
|
|
21
|
+
*
|
|
22
|
+
* Trust boundary: the TTY confirmation guards this command, not the files. A lock entry AND a matching
|
|
23
|
+
* decision forged together and committed through review are trusted like any approval — git review
|
|
24
|
+
* is that boundary, here as for `approve`.
|
|
25
|
+
*/
|
|
26
|
+
import { gitUserName } from '../decisions/decision-source';
|
|
27
|
+
import { formatCommand, requireTypedConfirmation } from '../decisions/human-confirmation';
|
|
28
|
+
import type { ApproveInput, ApproveResult } from './approve-contract';
|
|
29
|
+
import { buildContractVerdict } from './build-contract-verdict';
|
|
30
|
+
import { LOW_IDENTITY_FINDING } from './classify-trace-conformance';
|
|
31
|
+
import { canonicalJson } from './contract-hash';
|
|
32
|
+
import { ContractScenario, isHumanApproved, OpaqueStep } from './contract-schema';
|
|
33
|
+
import { resolveContractUnit } from './contract-units';
|
|
34
|
+
import { lockPathFor, ReadLock, readLockFile } from './contracts-lock-file';
|
|
35
|
+
import { loadContractScenarios } from './normalize-feature-steps';
|
|
36
|
+
import { recordOpaqueAcceptance } from './record-contract-approval';
|
|
37
|
+
import { unverifiedApprovals } from './verify-approval-decisions';
|
|
38
|
+
|
|
39
|
+
/** Accepting reads the traces with `contract check`'s generous deadline, never the guard's ~3 s. */
|
|
40
|
+
const ACCEPT_TRACE_BUDGET_MS = 120_000;
|
|
41
|
+
/** Findings that never hold a scenario back may stand beside the opaque steps. */
|
|
42
|
+
const HARMLESS = new Set(['strengthened', 'nondeterministic']);
|
|
43
|
+
|
|
44
|
+
interface Plan { unitDir: string; unit: string; scenario: ContractScenario; lock: ReadLock; decisionId: string; opaque: OpaqueStep[]; fingerprint: string }
|
|
45
|
+
|
|
46
|
+
const isLow = (detail: string): boolean => detail.startsWith(LOW_IDENTITY_FINDING);
|
|
47
|
+
|
|
48
|
+
function plan(i: ApproveInput, id: string): Plan {
|
|
49
|
+
const unit = resolveContractUnit(i.projectRoot, i.unit);
|
|
50
|
+
const lock = readLockFile(lockPathFor(unit.dir));
|
|
51
|
+
const matches = loadContractScenarios(unit.dir, unit.name).filter((s) => s.id === id);
|
|
52
|
+
if (!matches.length) throw new Error(`--scenario ${id} names no scenario of ${i.unit}`);
|
|
53
|
+
if (matches.length > 1) throw new Error(`Refused: scenario id ${id} is used by ${matches.length} scenarios — an acceptance must name exactly one. Give each its own ID.`);
|
|
54
|
+
const sc = matches[0];
|
|
55
|
+
if (sc.implementation === 'compiled') throw new Error(`Refused: ${id} is compiled — only a free / impl:code test has trace steps to accept.`);
|
|
56
|
+
const entry = lock?.scenarios[id];
|
|
57
|
+
if (!lock || !isHumanApproved(entry) || lock.tampered.includes(id) || entry.hash !== sc.hash) {
|
|
58
|
+
throw new Error(`Refused: ${id} is not approved as it reads now — approve it first (\`sungen contract approve --screen ${i.unit} --scenario ${id}\`), re-run it with a trace, then accept its opaque steps.`);
|
|
59
|
+
}
|
|
60
|
+
// The entry an acceptance re-points must be QA's own: its decision (on disk) vouches for it as it is.
|
|
61
|
+
const [unvouched] = unverifiedApprovals(i.projectRoot, unit.dir, { schema: lock.schema, scenarios: { [id]: entry } }, new Set(), { root: i.projectRoot, timeoutMs: 0, blobs: null });
|
|
62
|
+
if (unvouched) throw new Error(`Refused: the lock entry of ${id} is not vouched for by its decision (${unvouched.detail}) — restore the lock from git, or approve the scenario again.`);
|
|
63
|
+
|
|
64
|
+
const v = buildContractVerdict(i.projectRoot, unit, { traceBudgetMs: ACCEPT_TRACE_BUDGET_MS });
|
|
65
|
+
const bad = v.uncommitted.find((u) => u.id === id && (u.change === 'tampered' || u.change === 'unverified'));
|
|
66
|
+
if (bad) throw new Error(`Refused: the lock entry of ${id} is ${bad.change} (${bad.detail}) — restore it from git before accepting anything.`);
|
|
67
|
+
const r = v.scenarios.find((s) => s.id === id);
|
|
68
|
+
const blocking = (r?.findings ?? []).filter((f) => !HARMLESS.has(f.class) && !(f.class === 'opaque' && !isLow(f.detail)));
|
|
69
|
+
if (blocking.length) {
|
|
70
|
+
const fix = blocking.some((f) => isLow(f.detail))
|
|
71
|
+
? `run \`sungen contract check --screen ${i.unit} --trace\` (a real browser resolves the identity) first`
|
|
72
|
+
: `settle that first — \`sungen contract check --screen ${i.unit}\` names the fix`;
|
|
73
|
+
throw new Error(`Refused: accepting clears only opaque steps, and ${id} is also held by: ${blocking.map((f) => `${f.class}${f.step ? ` (step ${f.step})` : ''} — ${f.detail}`).join('; ')}. ${fix[0].toUpperCase()}${fix.slice(1)}.`);
|
|
74
|
+
}
|
|
75
|
+
const opaque = r?.trace?.opaque ?? [];
|
|
76
|
+
if (!opaque.length) throw new Error(`Refused: the trace of ${id} has no opaque step — nothing to accept (${r?.class ?? 'not judged'}).`);
|
|
77
|
+
return { unitDir: unit.dir, unit: unit.name, scenario: sc, lock, decisionId: entry.decisionId, opaque, fingerprint: canonicalJson({ lock, hash: sc.hash, opaque }) };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const describe = (o: OpaqueStep): string =>
|
|
81
|
+
`${o.accepted ? '=' : '◐'} step ${o.step} opaque ${o.kind === 'assert' ? 'check' : 'step'} "${o.verbatim}"${o.where ? ` at ${o.where}` : ''}`
|
|
82
|
+
+ `${o.replaces ? ` — replaces approved ${o.replaces}` : ''}${o.accepted ? ' (accepted before)' : ''}`;
|
|
83
|
+
|
|
84
|
+
export async function acceptOpaqueSteps(i: ApproveInput): Promise<ApproveResult> {
|
|
85
|
+
const id = i.scenario;
|
|
86
|
+
if (!id) throw new Error('--accept-opaque needs --scenario <id>: an acceptance names the one scenario whose opaque steps QA reviewed');
|
|
87
|
+
const before = plan(i, id);
|
|
88
|
+
const lockPath = lockPathFor(before.unitDir);
|
|
89
|
+
if (before.opaque.every((o) => o.accepted)) return { status: 'nothing-to-accept', lockPath, approved: [], removed: [] };
|
|
90
|
+
|
|
91
|
+
const pending = before.opaque.filter((o) => !o.accepted);
|
|
92
|
+
await requireTypedConfirmation({
|
|
93
|
+
what: `accept ${pending.length} reviewed opaque step(s) of ${id} (${i.unit}) — steps sungen cannot compare, so they are never checked by code`,
|
|
94
|
+
phrase: id,
|
|
95
|
+
command: formatCommand(['sungen', 'contract', 'approve', '--screen', i.unit, '--scenario', id, '--accept-opaque', ...(i.reason ? ['--reason', i.reason] : [])]),
|
|
96
|
+
summary: [
|
|
97
|
+
...before.opaque.map(describe),
|
|
98
|
+
`bound to each step as it reads now: a change to it, or a new approval of ${id}, brings it back to review`,
|
|
99
|
+
`${id} stays approved at hash ${before.scenario.hash}; its lock entry moves from decision ${before.decisionId} to the new one`,
|
|
100
|
+
`recorded by: ${gitUserName(i.projectRoot)}`,
|
|
101
|
+
],
|
|
102
|
+
io: i.io,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const after = plan(i, id);
|
|
106
|
+
if (after.fingerprint !== before.fingerprint) {
|
|
107
|
+
throw new Error('Refused: the trace, the feature or the lock changed while you were confirming — nothing was written. Run the command again and review the new summary.');
|
|
108
|
+
}
|
|
109
|
+
const { decisionId } = recordOpaqueAcceptance({
|
|
110
|
+
projectRoot: i.projectRoot, unitDir: after.unitDir, unit: after.unit, scenario: after.scenario, reason: i.reason, lock: after.lock,
|
|
111
|
+
accepted: after.opaque.map((o) => ({ step: o.step, sha: o.sha })),
|
|
112
|
+
});
|
|
113
|
+
return { status: 'accepted', lockPath, decisionId, approved: [id], removed: [], accepted: after.opaque };
|
|
114
|
+
}
|
|
@@ -11,25 +11,31 @@
|
|
|
11
11
|
* 5. read everything AGAIN: if the feature or the lock moved while the human was reading, what
|
|
12
12
|
* they confirmed is not what would be written — refuse (L5)
|
|
13
13
|
* 6. record the decision and the lock (record-contract-approval.ts)
|
|
14
|
+
*
|
|
15
|
+
* `--accept-opaque` (with `--scenario`) is the other human act of this command: accepting the reviewed
|
|
16
|
+
* opaque trace steps of one approved free / impl:code scenario — accept-opaque-steps.ts (#923).
|
|
14
17
|
*/
|
|
15
18
|
import { ConfirmationIO, formatCommand, requireTypedConfirmation } from '../decisions/human-confirmation';
|
|
16
19
|
import { gitUserName } from '../decisions/decision-source';
|
|
17
20
|
import { classifyScenario } from './classify-contract-change';
|
|
18
21
|
import { canonicalJson } from './contract-hash';
|
|
19
|
-
import {
|
|
22
|
+
import { acceptOpaqueSteps } from './accept-opaque-steps';
|
|
23
|
+
import { ContractScenario, isHumanApproved, OpaqueStep } from './contract-schema';
|
|
20
24
|
import { resolveContractUnit } from './contract-units';
|
|
21
25
|
import { lockPathFor, ReadLock, readLockFile } from './contracts-lock-file';
|
|
22
26
|
import { dropProposedTags } from './free-feature-blocks';
|
|
23
27
|
import { contractFeatureFiles, loadContractScenarios } from './normalize-feature-steps';
|
|
24
28
|
import { ApprovalWrite, checkApprovalSecrets, recordContractApproval } from './record-contract-approval';
|
|
25
29
|
|
|
26
|
-
export interface ApproveInput { projectRoot: string; unit: string; scenario?: string; reason?: string; io: ConfirmationIO }
|
|
30
|
+
export interface ApproveInput { projectRoot: string; unit: string; scenario?: string; reason?: string; io: ConfirmationIO; acceptOpaque?: boolean }
|
|
27
31
|
export interface ApproveResult {
|
|
28
|
-
status: 'approved' | 'nothing-to-approve';
|
|
32
|
+
status: 'approved' | 'nothing-to-approve' | 'accepted' | 'nothing-to-accept';
|
|
29
33
|
lockPath: string;
|
|
30
34
|
decisionId?: string;
|
|
31
35
|
approved: string[];
|
|
32
36
|
removed: string[];
|
|
37
|
+
/** `--accept-opaque`: the scenario's opaque steps, all accepted by the new decision. */
|
|
38
|
+
accepted?: OpaqueStep[];
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
interface Plan { write: ApprovalWrite; lock: ReadLock | null; fingerprint: string }
|
|
@@ -62,6 +68,7 @@ function describe(sc: ContractScenario, lock: ReadLock | null): string {
|
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
export async function approveContract(i: ApproveInput): Promise<ApproveResult> {
|
|
71
|
+
if (i.acceptOpaque) return acceptOpaqueSteps(i);
|
|
65
72
|
const before = plan(i);
|
|
66
73
|
const { approve, removed } = before.write;
|
|
67
74
|
const lockPath = lockPathFor(before.write.unitDir);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* (added · changed · removed vs HEAD, tampered, unverified), or an APPROVED free /
|
|
10
10
|
* impl:code scenario with no admissible trace (unproven, also past the deadline) exit 2
|
|
11
11
|
* review any hash-changed (changed, not weaker — QA re-approves), opaque (a check no code
|
|
12
|
-
* can compare, or a target identity that is only `low`), or inconclusive (an
|
|
12
|
+
* can compare and QA has not accepted, or a target identity that is only `low`), or inconclusive (an
|
|
13
13
|
* approved free scenario's final attempt did not pass); blocks delivery exit 0
|
|
14
14
|
* pass only match / strengthened / proposed / nondeterministic exit 0
|
|
15
15
|
*
|
|
@@ -64,7 +64,7 @@ function withTrace(results: ScenarioResult[], outcomes: Map<string, TraceOutcome
|
|
|
64
64
|
const o = outcomes.get(r.id);
|
|
65
65
|
if (!o) return r;
|
|
66
66
|
const cls = severityOf(o.class) > severityOf(r.class) ? o.class : r.class;
|
|
67
|
-
return { ...r, class: cls, findings: [...r.findings, ...o.findings], trace: { hashes: o.hashes, runs: o.runs } };
|
|
67
|
+
return { ...r, class: cls, findings: [...r.findings, ...o.findings], trace: { hashes: o.hashes, runs: o.runs, ...(o.opaque.length ? { opaque: o.opaque } : {}) } };
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
|
|
@@ -12,17 +12,17 @@
|
|
|
12
12
|
* Bounded (review H4, M2): a deadline (`budgetMs`; the guard's default is ~3 s) — a scenario not
|
|
13
13
|
* judged in time is `unproven` when approved, a warning while proposed: fail CLOSED. An input that
|
|
14
14
|
* cannot be read while judging (test-data, a trace) makes that scenario unproven with its reason,
|
|
15
|
-
* never a thrown verdict. A trace older than the unit's
|
|
15
|
+
* never a thrown verdict. A trace older than the unit's `.feature` / lock is refused (stale) — judged
|
|
16
|
+
* by their content, so a touch or a fresh clone stales nothing (#967, trace/trace-freshness.ts).
|
|
16
17
|
*/
|
|
17
18
|
import { createHash } from 'crypto';
|
|
18
19
|
import * as fs from 'fs';
|
|
19
20
|
import { readCapabilities } from '../harness/capability';
|
|
20
21
|
import { classifyTraceRuns, notJudged, TraceOutcome } from './classify-trace-conformance';
|
|
21
22
|
import { ContractLock, ContractScenario, isHumanApproved, LockEntry } from './contract-schema';
|
|
22
|
-
import { lockPathFor } from './contracts-lock-file';
|
|
23
23
|
import { loadTestDataIndex, TestDataIndex } from './data-normalize';
|
|
24
|
-
import { contractFeatureFiles } from './normalize-feature-steps';
|
|
25
24
|
import { renderTraceSteps, RenderedStep } from './render-free-gherkin';
|
|
25
|
+
import { unitContract } from './trace/trace-freshness';
|
|
26
26
|
import { collectScenarioEvidence, OVER_BUDGET, ScenarioEvidence, TraceRun } from './trace/collect-trace-evidence';
|
|
27
27
|
|
|
28
28
|
export { OVER_BUDGET } from './trace/collect-trace-evidence';
|
|
@@ -33,16 +33,20 @@ import { traceIntentSteps } from './trace/trace-steps';
|
|
|
33
33
|
|
|
34
34
|
export interface UnitTraceConformance { outcomes: Map<string, TraceOutcome>; warnings: string[]; inputs: unknown }
|
|
35
35
|
|
|
36
|
-
const mtimeMs = (f: string): number => { try { return fs.statSync(f).mtimeMs; } catch { return 0; } };
|
|
37
36
|
const sha = (s: string | Buffer): string => createHash('sha256').update(s).digest('hex');
|
|
38
37
|
const why = (e: unknown): string => (e instanceof Error ? e.message : String(e)).split('\n')[0].slice(0, 300);
|
|
39
38
|
|
|
40
39
|
export const MOBILE_NOT_SUPPORTED = 'trace conformance is web-only: free / impl:code scenarios of a mobile project are not supported yet (not judged)';
|
|
41
|
-
/** The guard
|
|
40
|
+
/** The Claude Code guard's deadline for the trace part of one unit's verdict (its hook has ~5 s in all). */
|
|
41
|
+
export const GUARD_TRACE_BUDGET_MS = 3_000;
|
|
42
|
+
/** The default deadline (`next`, the guard) — override: SUNGEN_TRACE_VERDICT_BUDGET_MS. */
|
|
42
43
|
export function defaultTraceBudgetMs(env: Record<string, string | undefined> = process.env): number {
|
|
43
44
|
const v = Number(env.SUNGEN_TRACE_VERDICT_BUDGET_MS);
|
|
44
|
-
return Number.isFinite(v) && v > 0 ? v :
|
|
45
|
+
return Number.isFinite(v) && v > 0 ? v : GUARD_TRACE_BUDGET_MS;
|
|
45
46
|
}
|
|
47
|
+
/** Inside the guard path the override may only SHORTEN the deadline (#926): the hook cannot wait longer. */
|
|
48
|
+
export const guardTraceBudgetMs = (env: Record<string, string | undefined> = process.env): number =>
|
|
49
|
+
Math.min(defaultTraceBudgetMs(env), GUARD_TRACE_BUDGET_MS);
|
|
46
50
|
|
|
47
51
|
export function traceConformanceForUnit(
|
|
48
52
|
projectRoot: string, unit: { name: string; dir: string }, current: ContractScenario[], lock: ContractLock | null,
|
|
@@ -60,8 +64,7 @@ export function traceConformanceForUnit(
|
|
|
60
64
|
let index: TestDataIndex = { byValue: new Map() };
|
|
61
65
|
let setupError = '';
|
|
62
66
|
try {
|
|
63
|
-
|
|
64
|
-
evidence = collectScenarioEvidence(projectRoot, new Set(traced.map((s) => s.id)), notBefore, deadline);
|
|
67
|
+
evidence = collectScenarioEvidence(projectRoot, new Set(traced.map((s) => s.id)), unitContract(projectRoot, unit), deadline, new Map(traced.map((s) => [s.id, s.source.name])));
|
|
65
68
|
index = loadTestDataIndex(unit.dir, unit.name);
|
|
66
69
|
} catch (e) { setupError = `trace conformance could not run: ${why(e)}`; }
|
|
67
70
|
const { cache, problem } = readIdentityCache(projectRoot);
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
* still `hash-changed`: equal hashes are the only road to `match`.
|
|
15
15
|
*/
|
|
16
16
|
import { sameJson } from './contract-hash';
|
|
17
|
-
import { ContractScenario, ContractStep, FindingClass, LadderClass, LockEntry, RULE_OF, ScenarioClass, TraceClass } from './contract-schema';
|
|
17
|
+
import { ContractScenario, ContractStep, FindingClass, LadderClass, LockEntry, OpaqueStep, RULE_OF, ScenarioClass, TraceClass } from './contract-schema';
|
|
18
18
|
import { compareAsserts } from './oracle-strength';
|
|
19
19
|
|
|
20
20
|
export interface ContractFinding { class: FindingClass; rule?: string; detail: string; step?: number }
|
|
21
|
-
/** What a free / impl:code scenario's traces fingerprinted to (DOS-14.6) — output only. */
|
|
22
|
-
export interface TraceSummary { hashes: string[]; runs: string[] }
|
|
21
|
+
/** What a free / impl:code scenario's traces fingerprinted to (DOS-14.6), and their opaque steps (#923) — output only. */
|
|
22
|
+
export interface TraceSummary { hashes: string[]; runs: string[]; opaque?: OpaqueStep[] }
|
|
23
23
|
export interface ScenarioResult { id: string; class: ScenarioClass; findings: ContractFinding[]; file?: string; trace?: TraceSummary }
|
|
24
24
|
|
|
25
25
|
const SEVERITY: Record<ScenarioClass, number> = {
|