@khoavu882/doflow 0.0.0-stage → 2.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +132 -2
- package/bin/doflow.js +44 -0
- package/core/.antigravity-plugin/hooks.json +39 -0
- package/core/.antigravity-plugin/mcp_config.json +18 -0
- package/core/.antigravity-plugin/plugin.json +31 -0
- package/core/.claude-plugin/marketplace.json +20 -0
- package/core/.claude-plugin/plugin.json +21 -0
- package/core/.codex-plugin/marketplace.json +20 -0
- package/core/.codex-plugin/plugin.json +35 -0
- package/core/.plugin/plugin.json +19 -0
- package/core/harnesses/antigravity/hooks/pre-implementation-gate.sh +9 -0
- package/core/harnesses/antigravity/hooks/stop-check.sh +10 -0
- package/core/harnesses/claude/hooks/lib.sh +304 -0
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +20 -0
- package/core/harnesses/claude/hooks/permission-audit.sh +47 -0
- package/core/harnesses/claude/hooks/post-compact.sh +74 -0
- package/core/harnesses/claude/hooks/post-edit-lint.sh +36 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +47 -0
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +20 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +43 -0
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +21 -0
- package/core/harnesses/claude/hooks/session-end.sh +89 -0
- package/core/harnesses/claude/hooks/session-start.sh +5 -0
- package/core/harnesses/claude/hooks/skill-config-audit.sh +39 -0
- package/core/harnesses/claude/hooks/stop-check.sh +5 -0
- package/core/harnesses/claude/hooks/subagent-audit.sh +7 -0
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -0
- package/core/harnesses/claude/settings/keybindings.json +20 -0
- package/core/harnesses/claude/settings/settings.json +201 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +49 -0
- package/core/harnesses/codex/agents/core-implementer.toml +7 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +8 -0
- package/core/harnesses/codex/agents/research-writer.toml +8 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +8 -0
- package/core/harnesses/codex/agents/system-architect.toml +9 -0
- package/core/harnesses/codex/config/config.toml +10 -0
- package/core/harnesses/codex/hooks/hooks.json +122 -0
- package/core/harnesses/codex/hooks/lib.sh +335 -0
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +18 -0
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +52 -0
- package/core/harnesses/codex/hooks/post-edit-lint.sh +6 -0
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +18 -0
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +46 -0
- package/core/harnesses/codex/hooks/pre-compact.sh +9 -0
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +19 -0
- package/core/harnesses/codex/hooks/session-end.impl.sh +92 -0
- package/core/harnesses/codex/hooks/session-end.sh +5 -0
- package/core/harnesses/codex/hooks/session-start.sh +5 -0
- package/core/harnesses/codex/hooks/stop-check.sh +8 -0
- package/core/harnesses/codex/hooks/subagent-audit.sh +8 -0
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +6 -0
- package/core/harnesses/gemini/hooks/hooks.json +77 -0
- package/core/harnesses/gemini/hooks/lib.sh +302 -0
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +39 -0
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/pre-compact.sh +38 -0
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +22 -0
- package/core/harnesses/gemini/hooks/session-end.impl.sh +89 -0
- package/core/harnesses/gemini/hooks/session-end.sh +5 -0
- package/core/harnesses/gemini/hooks/session-start.sh +5 -0
- package/core/harnesses/gemini/settings/adapter-defaults.json +4 -0
- package/core/harnesses/kiro/agents/doflow.json +58 -0
- package/core/harnesses/kiro/hooks/doflow.json +53 -0
- package/core/harnesses/kiro/hooks/lib.sh +324 -0
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +9 -0
- package/core/harnesses/kiro/hooks/session-start.sh +9 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +43 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +39 -0
- package/core/harnesses/shared/hooks/adapters/antigravity.js +111 -0
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +19 -0
- package/core/harnesses/shared/hooks/adapters/gemini.js +114 -0
- package/core/harnesses/shared/hooks/hooks.json +47 -0
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +91 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +97 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +29 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +501 -0
- package/core/harnesses/shared/hooks/policies/mcp-policy.conf +21 -0
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +91 -0
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +328 -0
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +496 -0
- package/core/harnesses/shared/hooks/policies/prompt-nudge.jq +131 -0
- package/core/harnesses/shared/hooks/policies/session-context.sh +141 -0
- package/core/harnesses/shared/hooks/policies/stop-check.sh +264 -0
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +46 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +235 -0
- package/core/harnesses/shared/hooks/stream-hook-runner.js +590 -0
- package/core/harnesses/shared/locator/doflow-run +87 -0
- package/core/registry/advice.json +101 -0
- package/core/registry/assets.json +792 -0
- package/core/registry/capabilities.json +120 -0
- package/core/registry/contracts.json +189 -0
- package/core/registry/external-tools.json +191 -0
- package/core/registry/harnesses.json +2062 -0
- package/core/registry/lifecycle.json +231 -0
- package/core/registry/mcp.json +18 -0
- package/core/registry/models.json +79 -0
- package/core/registry/readiness-templates.json +118 -0
- package/core/registry/routes.json +47 -0
- package/core/registry/storage.json +1057 -0
- package/core/registry/verification.json +258 -0
- package/core/registry/workflows.json +790 -0
- package/core/shared/agent-specs/core-implementer.md +73 -0
- package/core/shared/agent-specs/quality-guardian.md +28 -0
- package/core/shared/agent-specs/research-writer.md +31 -0
- package/core/shared/agent-specs/spec-analyst.md +33 -0
- package/core/shared/agent-specs/system-architect.md +29 -0
- package/core/shared/guidance/DOFLOW_CORE.md +8 -0
- package/core/shared/guidance/FLAGS.md +25 -0
- package/core/shared/guidance/PRINCIPLES.md +61 -0
- package/core/shared/guidance/VERSION +1 -0
- package/core/shared/guidance/mcp/MCP_Context7.md +18 -0
- package/core/shared/guidance/mcp/MCP_Sequential.md +17 -0
- package/core/shared/guidance/modes/MODE_Brainstorming.md +46 -0
- package/core/shared/guidance/modes/MODE_DeepResearch.md +69 -0
- package/core/shared/guidance/modes/MODE_Introspection.md +33 -0
- package/core/shared/guidance/modes/MODE_Orchestration.md +57 -0
- package/core/shared/guidance/modes/MODE_Task_Management.md +74 -0
- package/core/shared/guidance/pointers/claude-gemini.md +2 -0
- package/core/shared/guidance/pointers/codex.md +6 -0
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +373 -0
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +104 -0
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +47 -0
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +161 -0
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +87 -0
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +93 -0
- package/core/shared/guidance/references/MODEL_SELECTION.md +74 -0
- package/core/shared/guidance/references/ORCHESTRATION.md +36 -0
- package/core/shared/guidance/references/RESEARCH_CONFIG.md +21 -0
- package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +81 -0
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +25 -0
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +23 -0
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +14 -0
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +46 -0
- package/core/shared/scripts/doflow/assets/mermaid-theme.json +46 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +167 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +546 -0
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +208 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +347 -0
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +66 -0
- package/core/shared/scripts/doflow/bash/do-review-package.sh +127 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +698 -0
- package/core/shared/scripts/doflow/bash/render-audit.sh +224 -0
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +177 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +223 -0
- package/core/shared/scripts/doflow/bash/sync-context.sh +66 -0
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +526 -0
- package/core/shared/scripts/doflow/bin/doflow-run +592 -0
- package/core/shared/skills/do/SKILL.md +128 -0
- package/core/shared/skills/do/references/estimation.md +13 -0
- package/core/shared/skills/do/references/pm_routing.md +25 -0
- package/core/shared/skills/do/references/tool_matrix.md +52 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +252 -0
- package/core/shared/skills/do-code-review/README.md +49 -0
- package/core/shared/skills/do-code-review/SKILL.md +333 -0
- package/core/shared/skills/do-code-review/assets/sample_c_clean.c +71 -0
- package/core/shared/skills/do-code-review/assets/sample_c_smells.c +66 -0
- package/core/shared/skills/do-code-review/assets/sample_cpp_shift.cpp +19 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_clean.cs +100 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_smells.cs +78 -0
- package/core/shared/skills/do-code-review/assets/sample_java_clean.java +55 -0
- package/core/shared/skills/do-code-review/assets/sample_java_smells.java +56 -0
- package/core/shared/skills/do-code-review/assets/sample_json_clean.json +7 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_clean.md +30 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_smells.md +29 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_heredoc.sh +38 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_smells.sh +15 -0
- package/core/shared/skills/do-code-review/assets/sample_typescript_strings.ts +36 -0
- package/core/shared/skills/do-code-review/assets/sample_yaml_smells.yaml +6 -0
- package/core/shared/skills/do-code-review/content-types/config.md +83 -0
- package/core/shared/skills/do-code-review/content-types/markdown.md +132 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_clean_quality.json +79 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_smells_quality.json +169 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_cpp_shift_quality.json +49 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_clean_quality.json +149 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_smells_quality.json +157 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_clean_quality.json +67 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_smells_quality.json +97 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_json_clean_quality.json +33 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_clean_quality.json +1 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_smells_quality.json +16 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_heredoc_quality.json +54 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_smells_quality.json +91 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_typescript_strings_quality.json +60 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_yaml_smells_quality.json +46 -0
- package/core/shared/skills/do-code-review/languages/c.md +103 -0
- package/core/shared/skills/do-code-review/languages/cpp.md +103 -0
- package/core/shared/skills/do-code-review/languages/csharp.md +97 -0
- package/core/shared/skills/do-code-review/languages/dart.md +108 -0
- package/core/shared/skills/do-code-review/languages/go.md +92 -0
- package/core/shared/skills/do-code-review/languages/java.md +103 -0
- package/core/shared/skills/do-code-review/languages/kotlin.md +88 -0
- package/core/shared/skills/do-code-review/languages/php.md +109 -0
- package/core/shared/skills/do-code-review/languages/python.md +94 -0
- package/core/shared/skills/do-code-review/languages/ruby.md +105 -0
- package/core/shared/skills/do-code-review/languages/rust.md +99 -0
- package/core/shared/skills/do-code-review/languages/shell.md +90 -0
- package/core/shared/skills/do-code-review/languages/swift.md +88 -0
- package/core/shared/skills/do-code-review/languages/typescript.md +97 -0
- package/core/shared/skills/do-code-review/review-policy.json +47 -0
- package/core/shared/skills/do-code-review/rules/universal.md +49 -0
- package/core/shared/skills/do-code-review/scripts/code_quality_checker.py +1696 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +532 -0
- package/core/shared/skills/do-code-review/scripts/pr_analyzer.py +620 -0
- package/core/shared/skills/do-code-review/scripts/review_report_generator.py +505 -0
- package/core/shared/skills/do-constitution/SKILL.md +72 -0
- package/core/shared/skills/do-design/SKILL.md +212 -0
- package/core/shared/skills/do-diagnose/SKILL.md +156 -0
- package/core/shared/skills/do-diagnose/references/code_audit.md +15 -0
- package/core/shared/skills/do-diagnose/references/refactoring.md +12 -0
- package/core/shared/skills/do-diagnose/references/root_cause.md +17 -0
- package/core/shared/skills/do-document/SKILL.md +81 -0
- package/core/shared/skills/do-document/references/api-reference.md +42 -0
- package/core/shared/skills/do-document/references/deep_research.md +18 -0
- package/core/shared/skills/do-document/references/embedded-research.md +11 -0
- package/core/shared/skills/do-document/references/implementation-flow.md +105 -0
- package/core/shared/skills/do-document/references/index_generator.md +9 -0
- package/core/shared/skills/do-document/references/user-guide.md +22 -0
- package/core/shared/skills/do-execute-plan/SKILL.md +261 -0
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +57 -0
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +92 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +155 -0
- package/core/shared/skills/do-execute-plan/references/run_position.md +63 -0
- package/core/shared/skills/do-execute-plan/references/scaffold.md +441 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +41 -0
- package/core/shared/skills/do-flow/SKILL.md +186 -0
- package/core/shared/skills/do-flow/references/task_classes.md +134 -0
- package/core/shared/skills/do-git/SKILL.md +115 -0
- package/core/shared/skills/do-git/references/hotfix-ritual.md +44 -0
- package/core/shared/skills/do-git/references/lifecycle-policy.md +54 -0
- package/core/shared/skills/do-git/references/release-ritual.md +64 -0
- package/core/shared/skills/do-implement/SKILL.md +222 -0
- package/core/shared/skills/do-plan/SKILL.md +219 -0
- package/core/shared/skills/do-test/SKILL.md +113 -0
- package/core/shared/templates/doflow/constitution-template.md +28 -0
- package/core/shared/templates/doflow/data-model-template.md +169 -0
- package/core/shared/templates/doflow/design-template.md +193 -0
- package/core/shared/templates/doflow/external-contract-template.md +35 -0
- package/core/shared/templates/doflow/intent-template.md +45 -0
- package/core/shared/templates/doflow/plan-template.md +151 -0
- package/core/shared/templates/doflow/question-log-template.md +51 -0
- package/core/shared/templates/doflow/requirement-template.md +126 -0
- package/core/shared/templates/doflow/specs-template.md +55 -0
- package/core/shared/templates/doflow/state-template.md +59 -0
- package/core/shared/workflows/do-flow-chain.md +17 -0
- package/package.json +46 -4
- package/src/adapters/antigravity/index.ts +631 -0
- package/src/adapters/claude/index.ts +548 -0
- package/src/adapters/claude/mcp.ts +94 -0
- package/src/adapters/codex/agents.ts +208 -0
- package/src/adapters/codex/config.ts +272 -0
- package/src/adapters/codex/hooks.ts +203 -0
- package/src/adapters/codex/index.ts +707 -0
- package/src/adapters/codex/mcp.ts +271 -0
- package/src/adapters/codex/native-projection.ts +34 -0
- package/src/adapters/copilot/index.ts +413 -0
- package/src/adapters/copy-tree.ts +620 -0
- package/src/adapters/gemini/hooks.ts +210 -0
- package/src/adapters/gemini/index.ts +390 -0
- package/src/adapters/hook-commands.ts +60 -0
- package/src/adapters/index.ts +106 -0
- package/src/adapters/kiro/index.ts +310 -0
- package/src/adapters/mcp-entries.ts +331 -0
- package/src/adapters/opencode/index.ts +466 -0
- package/src/adapters/pi/index.ts +279 -0
- package/src/adapters/pi/json-members.ts +185 -0
- package/src/adapters/pi/mcp.ts +373 -0
- package/src/adapters/resolve-source.ts +19 -0
- package/src/cli/commands/install.ts +218 -0
- package/src/cli/commands/list-backups.ts +21 -0
- package/src/cli/commands/reconcile.ts +120 -0
- package/src/cli/commands/remove.ts +82 -0
- package/src/cli/commands/rollback.ts +172 -0
- package/src/cli/commands/self-update.ts +33 -0
- package/src/cli/commands/status.ts +138 -0
- package/src/cli/commands/tools.ts +92 -0
- package/src/cli/commands/update.ts +162 -0
- package/src/cli/index.ts +568 -0
- package/src/cli/runtime-commands.ts +281 -0
- package/src/cli/shared.ts +218 -0
- package/src/helper/bash-helper.ts +34 -0
- package/src/helper/git.ts +24 -0
- package/src/helper/guardrails.ts +49 -0
- package/src/helper/harness-paths.ts +140 -0
- package/src/helper/marker-merge.ts +116 -0
- package/src/helper/prompt.ts +61 -0
- package/src/helper/repo-root.ts +29 -0
- package/src/helper/settings-merge.ts +151 -0
- package/src/helper/settings-scope.ts +33 -0
- package/src/helper/toml.ts +244 -0
- package/src/install/backup-set.ts +149 -0
- package/src/install/backup.ts +724 -0
- package/src/install/context.ts +58 -0
- package/src/install/manifest.ts +145 -0
- package/src/install/mcp.ts +254 -0
- package/src/install/paths.ts +84 -0
- package/src/install/targets.ts +90 -0
- package/src/install/tool-lifecycle.ts +151 -0
- package/src/lifecycle/bash-availability.ts +25 -0
- package/src/lifecycle/index.ts +812 -0
- package/src/lifecycle/mcp-index.ts +28 -0
- package/src/lifecycle/policies.ts +73 -0
- package/src/lifecycle/view.ts +175 -0
- package/src/registry/index.ts +611 -0
- package/src/release/sync-plugin-versions.ts +66 -0
- package/src/runtime/advice/cli.ts +283 -0
- package/src/runtime/advice/client.ts +324 -0
- package/src/runtime/advice/config.ts +189 -0
- package/src/runtime/advice/doctor.ts +169 -0
- package/src/runtime/advice/endpoint-guard.ts +134 -0
- package/src/runtime/advice/kinds.ts +154 -0
- package/src/runtime/advice/providers/ollama-systemone.ts +209 -0
- package/src/runtime/advice/record.ts +166 -0
- package/src/runtime/advice/registry.ts +170 -0
- package/src/runtime/advice/strength.ts +66 -0
- package/src/runtime/brief-tools.ts +342 -0
- package/src/runtime/c4-project.ts +371 -0
- package/src/runtime/capability-router.ts +484 -0
- package/src/runtime/checkouts.ts +170 -0
- package/src/runtime/claims.ts +468 -0
- package/src/runtime/cli-result.ts +55 -0
- package/src/runtime/cli.ts +678 -0
- package/src/runtime/command-detect.ts +516 -0
- package/src/runtime/context-pack.ts +492 -0
- package/src/runtime/decision-register.ts +493 -0
- package/src/runtime/evidence-ledger.ts +338 -0
- package/src/runtime/failure/capture.ts +272 -0
- package/src/runtime/failure/classifier.ts +68 -0
- package/src/runtime/failure/cli.ts +178 -0
- package/src/runtime/failure/home.ts +64 -0
- package/src/runtime/failure/store.ts +226 -0
- package/src/runtime/feature-resolve.ts +79 -0
- package/src/runtime/freshness.ts +177 -0
- package/src/runtime/health.ts +863 -0
- package/src/runtime/history-compactor.ts +237 -0
- package/src/runtime/implementation-gate.ts +321 -0
- package/src/runtime/inventory/coverage.ts +218 -0
- package/src/runtime/inventory/identity.ts +65 -0
- package/src/runtime/inventory/index.ts +705 -0
- package/src/runtime/inventory/precedence.ts +134 -0
- package/src/runtime/inventory/read-scopes.ts +131 -0
- package/src/runtime/inventory/siblings.ts +192 -0
- package/src/runtime/knowledge/chunker.ts +124 -0
- package/src/runtime/knowledge/hybrid.ts +149 -0
- package/src/runtime/knowledge/index-store.ts +145 -0
- package/src/runtime/knowledge/retrieval.ts +194 -0
- package/src/runtime/leak-scan.ts +185 -0
- package/src/runtime/lifecycle/cli.ts +276 -0
- package/src/runtime/lifecycle/event-store.ts +340 -0
- package/src/runtime/lifecycle/fold.ts +353 -0
- package/src/runtime/lifecycle/followup.ts +432 -0
- package/src/runtime/lifecycle/goal.ts +249 -0
- package/src/runtime/lifecycle/intent-writer.ts +86 -0
- package/src/runtime/lifecycle/overview.ts +292 -0
- package/src/runtime/lifecycle/release.ts +241 -0
- package/src/runtime/lifecycle/report-store.ts +221 -0
- package/src/runtime/lifecycle/retention.ts +212 -0
- package/src/runtime/lifecycle/root.ts +71 -0
- package/src/runtime/lifecycle/status.ts +353 -0
- package/src/runtime/lifecycle/store-upkeep.ts +393 -0
- package/src/runtime/locator-resolve.ts +158 -0
- package/src/runtime/mask.ts +210 -0
- package/src/runtime/mcp-consultations.ts +412 -0
- package/src/runtime/mcp-reach.ts +157 -0
- package/src/runtime/model-router.ts +153 -0
- package/src/runtime/outcome.ts +467 -0
- package/src/runtime/reach.ts +148 -0
- package/src/runtime/readiness-record.ts +150 -0
- package/src/runtime/readiness.ts +435 -0
- package/src/runtime/recovery.ts +282 -0
- package/src/runtime/research-request.ts +231 -0
- package/src/runtime/retrieval-plan.ts +578 -0
- package/src/runtime/scaffold/artifacts.ts +217 -0
- package/src/runtime/scaffold/fingerprint.ts +56 -0
- package/src/runtime/scaffold/generate.ts +911 -0
- package/src/runtime/scaffold/languages.ts +245 -0
- package/src/runtime/storage/cli.ts +64 -0
- package/src/runtime/storage/engine.ts +249 -0
- package/src/runtime/storage/registry.ts +130 -0
- package/src/runtime/storage/roots.ts +63 -0
- package/src/runtime/storage/rules.ts +282 -0
- package/src/runtime/storage/upkeep.ts +243 -0
- package/src/runtime/storage/work-status.ts +326 -0
- package/src/runtime/task-attempts.ts +415 -0
- package/src/runtime/task-classifier.ts +550 -0
- package/src/runtime/task-scope.ts +130 -0
- package/src/runtime/task-state.ts +176 -0
- package/src/runtime/trace/indicators.ts +235 -0
- package/src/runtime/trace/ledger.ts +626 -0
- package/src/runtime/trace/render.ts +76 -0
- package/src/runtime/trace/views.ts +459 -0
- package/src/runtime/verification/contract-runner.ts +341 -0
- package/src/runtime/verification/engine.ts +1461 -0
- package/src/runtime/verification/registry.ts +130 -0
- package/src/runtime/verification/scope-bound.ts +418 -0
- package/src/runtime/verification/test-selection.ts +172 -0
- package/src/runtime/workflow-engine.ts +588 -0
- package/src/runtime/workflow-orchestrator.ts +953 -0
- package/src/runtime/worktree.ts +340 -0
- package/src/state/index.ts +193 -0
- package/src/state/lockfile.ts +136 -0
- package/src/state/run-lock.ts +408 -0
- package/types/node-commonjs.d.ts +68 -0
- package/types/runtime.d.ts +91 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# pre-implementation-gate.sh — Canonical Policy Library: PreToolUse(Edit|Write|...)
|
|
3
|
+
# backstop for the doflow implement gate. The HARD half of the one enforced
|
|
4
|
+
# gate (the other half is the prompt-level do-prereqs.sh inside
|
|
5
|
+
# /do-execute-plan — defense in depth).
|
|
6
|
+
#
|
|
7
|
+
# Denies a SOURCE-file edit in two cases:
|
|
8
|
+
# 1. a feature has been STARTED (its feature_dir exists, in this checkout or,
|
|
9
|
+
# from a linked worktree, in the main checkout) but requirement.md,
|
|
10
|
+
# design.md, or plan.md is still missing: "don't write code before you've
|
|
11
|
+
# planned";
|
|
12
|
+
# 2. the task is held to a readiness record and has no READY one for the
|
|
13
|
+
# template of its workflow's first source-mutating stage. The task is held
|
|
14
|
+
# when its run (in this checkout or exactly one other) is open with that
|
|
15
|
+
# stage pending, or, with no run, on a feature branch whose folder has a
|
|
16
|
+
# decision register and all three artifacts.
|
|
17
|
+
# It is deliberately SCOPED so it never fires outside the doflow chain:
|
|
18
|
+
# - trunk -> allow
|
|
19
|
+
# - branch is a fix, bugfix, refactor, chore, release or hotfix branch
|
|
20
|
+
# (classes that cannot be a feature) -> no artifact check, run check only
|
|
21
|
+
# - edit target is under agent-docs/ -> allow (editing the artifacts themselves)
|
|
22
|
+
# - edit target outside the repo -> allow
|
|
23
|
+
# It reads files with jq and lists checkouts with one git call; it never runs
|
|
24
|
+
# the DoFlow runtime. Self-contained + fail-open: any uncertainty -> allow (exit 0).
|
|
25
|
+
#
|
|
26
|
+
# Canonical Policy Script Contract (design.md §4):
|
|
27
|
+
# env DOFLOW_PROJECT_DIR (repo root, if the front door sets it),
|
|
28
|
+
# DOFLOW_AGENT (harness id, informational only here),
|
|
29
|
+
# CLAUDE_CONFIG_DIR / CLAUDE_PROJECT_DIR / GEMINI_CONFIG_DIR / CODEX_HOME
|
|
30
|
+
# (each already read by the harness-specific dispatcher this policy
|
|
31
|
+
# replaces — kept so an unmodified front door still resolves its own
|
|
32
|
+
# harness's do-paths.sh resolver)
|
|
33
|
+
# stdin a PreToolUse-shaped JSON payload for an edit tool. Every harness's
|
|
34
|
+
# own field names differ (Claude/Gemini: tool_name/tool_input.file_path;
|
|
35
|
+
# Codex: tool_name=apply_patch with a unified diff in
|
|
36
|
+
# tool_input.command; Kiro: field names undocumented) — this script
|
|
37
|
+
# reads the union of known shapes rather than assuming one, since a
|
|
38
|
+
# native front door (Claude/Codex/Kiro) execs this script directly
|
|
39
|
+
# with no payload translation of its own.
|
|
40
|
+
# exit 0 = allow, non-zero = deny with the reason on stderr. A front door
|
|
41
|
+
# translates this into whatever JSON/exit-code shape its harness
|
|
42
|
+
# natively expects — this script never speaks a harness-specific
|
|
43
|
+
# decision shape.
|
|
44
|
+
#
|
|
45
|
+
# Reconciliation note (022-normalize-hooks, Phase A): claude/gemini/codex/kiro
|
|
46
|
+
# each shipped a separately-adapted copy of this gate; antigravity's copy
|
|
47
|
+
# additionally never depended on a do-paths.sh resolver at all — it computed
|
|
48
|
+
# feature_dir itself from the current branch name and checked the three
|
|
49
|
+
# artifact files directly on disk. That fallback path is folded in here as a
|
|
50
|
+
# resilience net for every harness (not only antigravity): if no do-paths.sh
|
|
51
|
+
# resolver is found, this script now computes feature_dir from the branch
|
|
52
|
+
# instead of silently allowing everything, which is a real behavior
|
|
53
|
+
# improvement for Kiro in particular — its own script's header already noted
|
|
54
|
+
# that its resolver is never installed on a real Kiro install today, so the
|
|
55
|
+
# gate has been a permanent no-op there until this fallback.
|
|
56
|
+
#
|
|
57
|
+
# Depends on nothing beyond bash, jq, and git being on PATH.
|
|
58
|
+
|
|
59
|
+
set -uo pipefail
|
|
60
|
+
|
|
61
|
+
command -v jq >/dev/null 2>&1 || exit 0 # no jq -> cannot evaluate -> allow
|
|
62
|
+
|
|
63
|
+
INPUT=$(cat)
|
|
64
|
+
|
|
65
|
+
# ── Normalized envelope (preferred; review R7) ────────────────────────────────
|
|
66
|
+
# A host adapter that has already decoded its native event sends
|
|
67
|
+
# {"doflow_event": {"operation": "edit", "paths": [..], "projectRoot": "..", "taskId": ".."}}
|
|
68
|
+
# and this policy consumes it verbatim. The union decoder below remains the
|
|
69
|
+
# fallback for front doors that still forward raw native payloads; per-harness
|
|
70
|
+
# knowledge belongs in those adapters, and each one that starts sending the
|
|
71
|
+
# envelope retires its share of the union.
|
|
72
|
+
ENVELOPE_ROOT=""
|
|
73
|
+
ENVELOPE_TASK=""
|
|
74
|
+
ENVELOPE_OP=$(printf '%s' "$INPUT" | jq -r '.doflow_event.operation // empty' 2>/dev/null)
|
|
75
|
+
if [ -n "$ENVELOPE_OP" ]; then
|
|
76
|
+
[ "$ENVELOPE_OP" = "edit" ] || exit 0 # only edits are gated
|
|
77
|
+
FILES=$(printf '%s' "$INPUT" | jq -r '.doflow_event.paths[]? // empty' 2>/dev/null | sed '/^$/d' | sort -u)
|
|
78
|
+
[ -n "$FILES" ] || exit 0
|
|
79
|
+
ENVELOPE_ROOT=$(printf '%s' "$INPUT" | jq -r '.doflow_event.projectRoot // empty' 2>/dev/null)
|
|
80
|
+
ENVELOPE_TASK=$(printf '%s' "$INPUT" | jq -r '.doflow_event.taskId // empty' 2>/dev/null)
|
|
81
|
+
else
|
|
82
|
+
|
|
83
|
+
# ── Tool name (union of every known field name across harnesses; LEGACY decoder) ──
|
|
84
|
+
tool=""
|
|
85
|
+
for field in '.tool_name' '.tool' '.toolName' '.name'; do
|
|
86
|
+
tool=$(printf '%s' "$INPUT" | jq -r "${field} // empty" 2>/dev/null)
|
|
87
|
+
[ -n "$tool" ] && break
|
|
88
|
+
done
|
|
89
|
+
|
|
90
|
+
is_patch_tool=false
|
|
91
|
+
case "$tool" in
|
|
92
|
+
Edit|Write|MultiEdit|edit_file|write_file|create_file|replace_file|replace) ;;
|
|
93
|
+
write_to_file|replace_file_content|multi_replace_file_content) ;;
|
|
94
|
+
apply_patch) is_patch_tool=true ;;
|
|
95
|
+
*) exit 0 ;;
|
|
96
|
+
esac
|
|
97
|
+
|
|
98
|
+
# ── Candidate files to gate ────────────────────────────────────────────────────
|
|
99
|
+
# Normal edit tools: one file, from the union of field names harnesses use.
|
|
100
|
+
# apply_patch (Codex): the tool has no file_path field at all — the paths are
|
|
101
|
+
# embedded in a unified diff under tool_input.command.
|
|
102
|
+
FILES=""
|
|
103
|
+
if [ "$is_patch_tool" = true ]; then
|
|
104
|
+
PATCH=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
105
|
+
[ -n "$PATCH" ] || exit 0
|
|
106
|
+
# "#" (not the original codex script's "|") is the sed delimiter here: BSD
|
|
107
|
+
# sed (macOS, no GNU coreutils) misparses "s|...(Add|Update|Delete)...|...|"
|
|
108
|
+
# because it splits on every unescaped "|", including the ones meant as ERE
|
|
109
|
+
# alternation inside the pattern — a portability bug in the source script
|
|
110
|
+
# this reconciliation fixes rather than ports, since it silently broke
|
|
111
|
+
# apply_patch parsing under this exact invocation on any non-GNU-sed host.
|
|
112
|
+
FILES=$(printf '%s\n' "$PATCH" | sed -nE \
|
|
113
|
+
-e 's#^\*\*\* (Add|Update|Delete) File: (.+)$#\2#p' \
|
|
114
|
+
-e 's#^\+\+\+ (a/|b/)?(.+)$#\2#p' \
|
|
115
|
+
| sed '/^\/dev\/null$/d' | sort -u)
|
|
116
|
+
[ -n "$FILES" ] || exit 0
|
|
117
|
+
else
|
|
118
|
+
file=""
|
|
119
|
+
for field in '.tool_input.file_path' '.tool_input.path' '.tool_input.target' '.file_path' '.path'; do
|
|
120
|
+
file=$(printf '%s' "$INPUT" | jq -r "${field} // empty" 2>/dev/null)
|
|
121
|
+
[ -n "$file" ] && break
|
|
122
|
+
done
|
|
123
|
+
[ -n "$file" ] || exit 0
|
|
124
|
+
FILES="$file"
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
fi # end legacy union decoder
|
|
128
|
+
|
|
129
|
+
# ── Resolve repo root ───────────────────────────────────────────────────────
|
|
130
|
+
# DOFLOW_PROJECT_DIR (explicit) beats the envelope's projectRoot beats git discovery.
|
|
131
|
+
ROOT="${DOFLOW_PROJECT_DIR:-$ENVELOPE_ROOT}"
|
|
132
|
+
if [ -z "$ROOT" ]; then
|
|
133
|
+
ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
# ── Resolve doflow state (feature_dir, repo_root, has_requirement/design/plan) ─
|
|
137
|
+
# Preferred path: exec whichever do-paths.sh resolver this install actually
|
|
138
|
+
# shipped, trying every known per-harness location (a front door invoking
|
|
139
|
+
# this script directly gives us no signal about which one, so this tries all
|
|
140
|
+
# of them rather than assuming DOFLOW_AGENT names the right one).
|
|
141
|
+
# Project-scoped candidates come before global ones; within each group the
|
|
142
|
+
# canonical, install-agnostic `.doflow/` location comes first — that is the
|
|
143
|
+
# two-step walk-up (project `.doflow`, then `$HOME/.doflow`) every SKILL.md
|
|
144
|
+
# and `doflow-run`'s own resolve_config_dir already uses. Without those two
|
|
145
|
+
# entries this list only ever found the per-harness MIRRORED copies, which a
|
|
146
|
+
# real install need never have projected — leaving the resolver unfound and
|
|
147
|
+
# the branch fallback below (flat paths only) as the whole gate.
|
|
148
|
+
RESOLVER_CANDIDATES=(
|
|
149
|
+
"$ROOT/.doflow/scripts/doflow/bash/do-paths.sh"
|
|
150
|
+
"$ROOT/.claude/scripts/doflow/bash/do-paths.sh"
|
|
151
|
+
"$ROOT/.codex/scripts/doflow/bash/do-paths.sh"
|
|
152
|
+
"$ROOT/.gemini/scripts/doflow/bash/do-paths.sh"
|
|
153
|
+
"$ROOT/.kiro/scripts/doflow/bash/do-paths.sh"
|
|
154
|
+
"$HOME/.doflow/scripts/doflow/bash/do-paths.sh"
|
|
155
|
+
"${CLAUDE_CONFIG_DIR:-$HOME/.claude}/scripts/doflow/bash/do-paths.sh"
|
|
156
|
+
"${CLAUDE_PROJECT_DIR:-}/.claude/scripts/doflow/bash/do-paths.sh"
|
|
157
|
+
"${CODEX_HOME:-$HOME/.codex}/scripts/doflow/bash/do-paths.sh"
|
|
158
|
+
"${GEMINI_CONFIG_DIR:-$HOME/.gemini}/scripts/doflow/bash/do-paths.sh"
|
|
159
|
+
"$HOME/.kiro/scripts/doflow/bash/do-paths.sh"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
RESOLVER=""
|
|
163
|
+
for candidate in "${RESOLVER_CANDIDATES[@]}"; do
|
|
164
|
+
[ -n "$candidate" ] && [ -x "$candidate" ] && { RESOLVER="$candidate"; break; }
|
|
165
|
+
done
|
|
166
|
+
|
|
167
|
+
feature_dir=""
|
|
168
|
+
repo_root=""
|
|
169
|
+
has_requirement=""
|
|
170
|
+
has_design=""
|
|
171
|
+
has_plan=""
|
|
172
|
+
|
|
173
|
+
if [ -n "$RESOLVER" ]; then
|
|
174
|
+
json=$("$RESOLVER" --json 2>/dev/null) || json=""
|
|
175
|
+
if [ -n "$json" ]; then
|
|
176
|
+
feature_dir=$(printf '%s' "$json" | jq -r '.feature_dir // empty' 2>/dev/null)
|
|
177
|
+
repo_root=$(printf '%s' "$json" | jq -r '.repo_root // empty' 2>/dev/null)
|
|
178
|
+
has_requirement=$(printf '%s' "$json" | jq -r '.has_requirement // false' 2>/dev/null)
|
|
179
|
+
has_design=$(printf '%s' "$json" | jq -r '.has_design // false' 2>/dev/null)
|
|
180
|
+
has_plan=$(printf '%s' "$json" | jq -r '.has_plan // false' 2>/dev/null)
|
|
181
|
+
fi
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# Classes that cannot be a feature are not held to requirement/design/plan, even when they share a
|
|
185
|
+
# slug with a started feature folder: fix, bugfix, refactor, chore, release, hotfix and trunk. Any
|
|
186
|
+
# other branch (feat/, feature/, 043-auth, kai/043-auth) runs the feature class and is gated. do-paths.sh
|
|
187
|
+
# classifies fix/bugfix/release/hotfix/trunk the same way (refactor/ and chore/ are its `other`); the
|
|
188
|
+
# chain test fails if the two disagree.
|
|
189
|
+
branch=$(git -C "${repo_root:-$ROOT}" branch --show-current 2>/dev/null || true)
|
|
190
|
+
exempt=false
|
|
191
|
+
case "$branch" in
|
|
192
|
+
""|master|main|develop|trunk|HEAD) exit 0 ;;
|
|
193
|
+
fix/*|bugfix/*|refactor/*|chore/*|release/*|hotfix/*) exempt=true ;;
|
|
194
|
+
esac
|
|
195
|
+
|
|
196
|
+
# Layout is decided ONCE from intention/requirement.md's presence, then every
|
|
197
|
+
# path below follows from it — the same single-probe rule do-paths.sh applies,
|
|
198
|
+
# so this probe can never report a self-contradictory mix of layouts. Without
|
|
199
|
+
# the structured arm a fully-planned feature (artifacts under intention/, design/,
|
|
200
|
+
# plan/) read as unplanned here and blocked every source edit.
|
|
201
|
+
probe_artifacts() {
|
|
202
|
+
local dir="$1/$feature_dir"
|
|
203
|
+
if [ -f "$dir/intention/requirement.md" ]; then
|
|
204
|
+
has_requirement=true
|
|
205
|
+
if [ -f "$dir/design/design.md" ]; then has_design=true; else has_design=false; fi
|
|
206
|
+
if [ -f "$dir/plan.md" ]; then has_plan=true; else has_plan=false; fi
|
|
207
|
+
elif [ -f "$dir/requirement.md" ]; then
|
|
208
|
+
has_requirement=true
|
|
209
|
+
if [ -f "$dir/design.md" ]; then has_design=true; else has_design=false; fi
|
|
210
|
+
if [ -f "$dir/plan.md" ]; then has_plan=true; else has_plan=false; fi
|
|
211
|
+
else
|
|
212
|
+
has_requirement=false; has_design=false; has_plan=false
|
|
213
|
+
fi
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Fallback: no resolver installed for this harness (or it produced nothing
|
|
217
|
+
# usable) -> compute state directly from the branch-coupled feature
|
|
218
|
+
# convention (feat/<slug> -> agent-docs/doflow/<slug>/), the same self
|
|
219
|
+
# contained approach antigravity's own copy of this gate already used.
|
|
220
|
+
# The task's slug is the resolver's, else the branch without its first
|
|
221
|
+
# prefix with any further / flattened to -, as the resolver derives it.
|
|
222
|
+
if [ -z "$feature_dir" ]; then
|
|
223
|
+
repo_root="$ROOT"
|
|
224
|
+
slug=${branch#*/}
|
|
225
|
+
feature_dir="agent-docs/doflow/$slug"
|
|
226
|
+
slug=${slug//\//-}
|
|
227
|
+
[ -d "$repo_root/$feature_dir" ] && probe_artifacts "$repo_root"
|
|
228
|
+
else
|
|
229
|
+
slug=${feature_dir##*/}
|
|
230
|
+
fi
|
|
231
|
+
[ -n "$feature_dir" ] && [ -n "$repo_root" ] || exit 0
|
|
232
|
+
|
|
233
|
+
# A task id stated by the envelope names the run and the record; one that is
|
|
234
|
+
# not a plain name cannot name either, so the slug stands in for it.
|
|
235
|
+
task_id="$ENVELOPE_TASK"
|
|
236
|
+
case "$task_id" in ""|.*|*..*|*[!A-Za-z0-9._-]*) task_id="$slug" ;; esac
|
|
237
|
+
[ -n "$task_id" ] || exit 0
|
|
238
|
+
|
|
239
|
+
# The real path of $1: its longest existing ancestor resolved with cd -P, the
|
|
240
|
+
# rest appended, so a path reached through a symlink (macOS /tmp and /var, a
|
|
241
|
+
# linked project folder) compares equal to the real repository root.
|
|
242
|
+
real_path() {
|
|
243
|
+
local p="$1" rest="" dir
|
|
244
|
+
while [ ! -e "$p" ] && [ "$p" != "/" ] && [ -n "$p" ]; do
|
|
245
|
+
rest="/${p##*/}$rest"
|
|
246
|
+
p=$(dirname "$p")
|
|
247
|
+
done
|
|
248
|
+
if [ -d "$p" ]; then
|
|
249
|
+
dir=$(cd -P "$p" 2>/dev/null && pwd) || return 1
|
|
250
|
+
p="$dir"
|
|
251
|
+
else
|
|
252
|
+
dir=$(cd -P "$(dirname "$p")" 2>/dev/null && pwd) || return 1
|
|
253
|
+
p="$dir/${p##*/}"
|
|
254
|
+
fi
|
|
255
|
+
printf '%s%s' "${p%/}" "$rest"
|
|
256
|
+
}
|
|
257
|
+
repo_real=$(cd -P "$repo_root" 2>/dev/null && pwd) || repo_real="$repo_root"
|
|
258
|
+
|
|
259
|
+
# Only source files inside this repo are gated: edits to doflow artifacts are
|
|
260
|
+
# always allowed, and an absolute path elsewhere is not this repo's.
|
|
261
|
+
GATED=false
|
|
262
|
+
while IFS= read -r file; do
|
|
263
|
+
[ -z "$file" ] && continue
|
|
264
|
+
case "$file" in *"/agent-docs/"*|agent-docs/*) continue ;; esac
|
|
265
|
+
case "$file" in
|
|
266
|
+
/*)
|
|
267
|
+
real=$(real_path "$file") || real="$file"
|
|
268
|
+
case "$real" in *"/agent-docs/"*) continue ;; esac
|
|
269
|
+
case "$real" in "$repo_real"/*) ;; *) continue ;; esac
|
|
270
|
+
;;
|
|
271
|
+
esac
|
|
272
|
+
GATED=true
|
|
273
|
+
break
|
|
274
|
+
done <<< "$FILES"
|
|
275
|
+
[ "$GATED" = true ] || exit 0
|
|
276
|
+
|
|
277
|
+
# ── Checkouts (one `git worktree list`, read at most once) ───────────────────
|
|
278
|
+
# MAIN is the main working tree when this checkout is a linked worktree of it;
|
|
279
|
+
# OTHERS are the repository's other checkouts. A bare main entry, a checkout
|
|
280
|
+
# whose directory is gone, and a DoFlow sandbox (.doflow-worktree-base at its
|
|
281
|
+
# root) are never read from; a sandbox sees no other checkout at all.
|
|
282
|
+
CHECKOUTS_READ=false
|
|
283
|
+
MAIN=""
|
|
284
|
+
OTHERS=()
|
|
285
|
+
checkouts() {
|
|
286
|
+
[ "$CHECKOUTS_READ" = true ] && return 0
|
|
287
|
+
CHECKOUTS_READ=true
|
|
288
|
+
local list here line real current="" main="" paths=() bare=() i
|
|
289
|
+
list=$(git -C "$repo_root" worktree list --porcelain 2>/dev/null) || return 0
|
|
290
|
+
here=$(cd -P "$repo_root" 2>/dev/null && pwd) || return 0
|
|
291
|
+
while IFS= read -r line; do
|
|
292
|
+
case "$line" in
|
|
293
|
+
"worktree "*) paths+=("${line#worktree }"); bare+=(false) ;;
|
|
294
|
+
bare) [ "${#paths[@]}" -gt 0 ] && bare[${#paths[@]}-1]=true ;;
|
|
295
|
+
esac
|
|
296
|
+
done <<< "$list"
|
|
297
|
+
[ "${#paths[@]}" -gt 0 ] || return 0
|
|
298
|
+
local reals=()
|
|
299
|
+
for i in "${!paths[@]}"; do
|
|
300
|
+
real=""
|
|
301
|
+
[ "${bare[$i]}" = false ] && real=$(cd -P "${paths[$i]}" 2>/dev/null && pwd)
|
|
302
|
+
reals+=("$real")
|
|
303
|
+
[ "$i" -eq 0 ] && main="$real"
|
|
304
|
+
if [ -n "$real" ] && { [ "$here" = "$real" ] || [ "${here#"$real"/}" != "$here" ]; }; then
|
|
305
|
+
[ "${#real}" -gt "${#current}" ] && current="$real"
|
|
306
|
+
fi
|
|
307
|
+
done
|
|
308
|
+
[ -n "$current" ] || return 0
|
|
309
|
+
[ -e "$current/.doflow-worktree-base" ] && return 0
|
|
310
|
+
[ "$main" != "$current" ] && MAIN="$main"
|
|
311
|
+
for real in "${reals[@]}"; do
|
|
312
|
+
[ -n "$real" ] && [ "$real" != "$current" ] && [ ! -e "$real/.doflow-worktree-base" ] && OTHERS+=("$real")
|
|
313
|
+
done
|
|
314
|
+
return 0
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# A state file under this checkout, else under exactly one other checkout, at
|
|
318
|
+
# the first of the given paths that exists in each; held in two or more others,
|
|
319
|
+
# it cannot be told which is meant -> allow.
|
|
320
|
+
FOUND=""
|
|
321
|
+
find_state() {
|
|
322
|
+
local rel root count=0 hit
|
|
323
|
+
FOUND=""
|
|
324
|
+
for rel in "$@"; do
|
|
325
|
+
if [ -f "$repo_root/$rel" ]; then FOUND="$repo_root/$rel"; return 0; fi
|
|
326
|
+
done
|
|
327
|
+
checkouts
|
|
328
|
+
for root in ${OTHERS[@]+"${OTHERS[@]}"}; do
|
|
329
|
+
hit=""
|
|
330
|
+
for rel in "$@"; do
|
|
331
|
+
[ -f "$root/$rel" ] && { hit="$root/$rel"; break; }
|
|
332
|
+
done
|
|
333
|
+
[ -n "$hit" ] || continue
|
|
334
|
+
count=$((count + 1))
|
|
335
|
+
FOUND="$hit"
|
|
336
|
+
done
|
|
337
|
+
[ "$count" -le 1 ] || exit 0
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
# The feature a run file belongs to: the slug it recorded when it started, else
|
|
341
|
+
# its task id when that names a feature folder in the checkout holding it.
|
|
342
|
+
run_feature() {
|
|
343
|
+
local recorded owner
|
|
344
|
+
recorded=$(jq -r '.featureSlug // empty' "$1" 2>/dev/null) || recorded=""
|
|
345
|
+
if [ -n "$recorded" ]; then printf '%s' "$recorded"; return 0; fi
|
|
346
|
+
owner=${1%/.doflow/state/orchestration/*}
|
|
347
|
+
[ -d "$owner/agent-docs/doflow/$task_id" ] && printf '%s' "$task_id"
|
|
348
|
+
return 0
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
# The task's run: this checkout's, else the one other checkout's run of the same
|
|
352
|
+
# feature. A run with the same id for another feature is a different task.
|
|
353
|
+
find_run() {
|
|
354
|
+
local rel=".doflow/state/orchestration/$task_id.json" root count=0
|
|
355
|
+
FOUND=""
|
|
356
|
+
if [ -f "$repo_root/$rel" ]; then FOUND="$repo_root/$rel"; return 0; fi
|
|
357
|
+
checkouts
|
|
358
|
+
for root in ${OTHERS[@]+"${OTHERS[@]}"}; do
|
|
359
|
+
[ -f "$root/$rel" ] || continue
|
|
360
|
+
[ "$(run_feature "$root/$rel")" = "$slug" ] || continue
|
|
361
|
+
count=$((count + 1))
|
|
362
|
+
FOUND="$root/$rel"
|
|
363
|
+
done
|
|
364
|
+
[ "$count" -le 1 ] || exit 0
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
# ── Feature folder: this checkout's, else the main checkout's ────────────────
|
|
368
|
+
FEATURE_ROOT=""
|
|
369
|
+
if [ -d "$repo_root/$feature_dir" ]; then
|
|
370
|
+
FEATURE_ROOT="$repo_root"
|
|
371
|
+
else
|
|
372
|
+
checkouts
|
|
373
|
+
if [ -n "$MAIN" ] && [ -d "$MAIN/$feature_dir" ]; then
|
|
374
|
+
FEATURE_ROOT="$MAIN"
|
|
375
|
+
probe_artifacts "$MAIN"
|
|
376
|
+
fi
|
|
377
|
+
fi
|
|
378
|
+
HAS_REGISTER=false
|
|
379
|
+
[ -n "$FEATURE_ROOT" ] && [ -f "$FEATURE_ROOT/$feature_dir/decisions/register.json" ] && HAS_REGISTER=true
|
|
380
|
+
PLANNED=false
|
|
381
|
+
[ "$has_requirement" = "true" ] && [ "$has_design" = "true" ] && [ "$has_plan" = "true" ] && PLANNED=true
|
|
382
|
+
|
|
383
|
+
# In the flow: block a source edit until requirement.md, design.md, AND
|
|
384
|
+
# plan.md all exist. Classes that cannot be a feature skip this check.
|
|
385
|
+
if [ "$exempt" = false ] && [ -n "$FEATURE_ROOT" ] && [ "$PLANNED" = false ]; then
|
|
386
|
+
echo "[pre-implementation-gate] doflow gate: feature $feature_dir is missing requirement.md, design.md, or plan.md — run /do-brainstorm, /do-design, then /do-plan before editing source. (Edits under agent-docs/ are always allowed; skip the flow by removing the feature dir.)" >&2
|
|
387
|
+
exit 2
|
|
388
|
+
fi
|
|
389
|
+
|
|
390
|
+
# ── Readiness ────────────────────────────────────────────────────────────────
|
|
391
|
+
# The workflow registry the installed runtime reads: beside this policy in an
|
|
392
|
+
# install, the source tree's own, the project's install, the global install.
|
|
393
|
+
POLICY_DIR=$(cd -P "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd) || POLICY_DIR=""
|
|
394
|
+
REGISTRY=""
|
|
395
|
+
for candidate in \
|
|
396
|
+
"${POLICY_DIR:+$POLICY_DIR/../../../runtime/core/registry/workflows.json}" \
|
|
397
|
+
"${POLICY_DIR:+$POLICY_DIR/../../../../registry/workflows.json}" \
|
|
398
|
+
"$repo_root/.doflow/runtime/core/registry/workflows.json" \
|
|
399
|
+
"$HOME/.doflow/runtime/core/registry/workflows.json"; do
|
|
400
|
+
[ -n "$candidate" ] && [ -f "$candidate" ] && { REGISTRY="$candidate"; break; }
|
|
401
|
+
done
|
|
402
|
+
|
|
403
|
+
# The class's gated stage: its first stage whose kind mutates source and that
|
|
404
|
+
# names a readiness template. Sets STAGE_ID, STAGE_TEMPLATE and STAGE_EDIT
|
|
405
|
+
# (false when the stage opts out of this edit-time check); all empty for none.
|
|
406
|
+
gated_stage() {
|
|
407
|
+
local out
|
|
408
|
+
STAGE_ID=""; STAGE_TEMPLATE=""; STAGE_EDIT=""
|
|
409
|
+
[ -n "$REGISTRY" ] || return 0
|
|
410
|
+
out=$(jq -r --arg c "$1" '. as $r
|
|
411
|
+
| [($r.classes[$c].stages // [])[]
|
|
412
|
+
| select(($r.stageKinds[.kind].mutatesSource // false) == true and (.readinessTemplate // "") != "")]
|
|
413
|
+
| first // empty
|
|
414
|
+
| .id, .readinessTemplate, (.editTimeGate != false)' "$REGISTRY" 2>/dev/null) || return 0
|
|
415
|
+
{ IFS= read -r STAGE_ID; IFS= read -r STAGE_TEMPLATE; IFS= read -r STAGE_EDIT; } <<< "$out"
|
|
416
|
+
return 0
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
# Whether the run file's program shows that stage completed or skipped.
|
|
420
|
+
stage_done() {
|
|
421
|
+
jq -e --arg id "$1" 'any(.program[]?; .type == "stage" and .id == $id
|
|
422
|
+
and (.status == "completed" or .status == "skipped"))' "$RUN" >/dev/null 2>&1
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
RUN=""
|
|
426
|
+
find_run
|
|
427
|
+
RUN="$FOUND"
|
|
428
|
+
run_slug=""
|
|
429
|
+
if [ -n "$RUN" ]; then
|
|
430
|
+
run_slug=$(run_feature "$RUN")
|
|
431
|
+
run_info=$(jq -r '(.state // ""), (.taskClass // "")' "$RUN" 2>/dev/null) || exit 0
|
|
432
|
+
{ IFS= read -r run_state; IFS= read -r run_class; } <<< "$run_info"
|
|
433
|
+
case "$run_state" in COMPLETED|REJECTED) exit 0 ;; esac
|
|
434
|
+
fi
|
|
435
|
+
|
|
436
|
+
if [ -n "$RUN" ]; then
|
|
437
|
+
class="$run_class"
|
|
438
|
+
elif [ "$exempt" = false ] && [ -n "$FEATURE_ROOT" ] && [ "$PLANNED" = true ] && [ "$HAS_REGISTER" = true ]; then
|
|
439
|
+
class=feature
|
|
440
|
+
else
|
|
441
|
+
exit 0
|
|
442
|
+
fi
|
|
443
|
+
|
|
444
|
+
gated_stage "$class"
|
|
445
|
+
[ -n "$STAGE_ID" ] && [ "$STAGE_EDIT" = true ] || exit 0
|
|
446
|
+
if [ -n "$RUN" ]; then
|
|
447
|
+
# A run whose program does not name that stage (missing, malformed, or compiled
|
|
448
|
+
# from a registry that named it differently) cannot be judged here -> allow, as
|
|
449
|
+
# the runtime reads such a run as having no gated stage.
|
|
450
|
+
jq -e --arg id "$STAGE_ID" 'any(.program[]?; .type == "stage" and .id == $id)' "$RUN" >/dev/null 2>&1 || exit 0
|
|
451
|
+
stage_done "$STAGE_ID" && exit 0
|
|
452
|
+
fi
|
|
453
|
+
|
|
454
|
+
# The record, where the runtime looks: under the feature's namespace (the run's
|
|
455
|
+
# own feature when it recorded one), then flat; this checkout first.
|
|
456
|
+
record_slug="${run_slug:-$slug}"
|
|
457
|
+
if [ -n "$record_slug" ] && [ "$record_slug" != "$task_id" ]; then
|
|
458
|
+
find_state ".doflow/state/readiness/$record_slug/$task_id.json" ".doflow/state/readiness/$task_id.json"
|
|
459
|
+
else
|
|
460
|
+
find_state ".doflow/state/readiness/$task_id.json"
|
|
461
|
+
fi
|
|
462
|
+
record_state=""; record_class=""; record_at=""
|
|
463
|
+
if [ -n "$FOUND" ]; then
|
|
464
|
+
record_info=$(jq -r '(.state // ""), (.taskClass // ""), (.evaluatedAt // "")' "$FOUND" 2>/dev/null) || exit 0
|
|
465
|
+
{ IFS= read -r record_state; IFS= read -r record_class; IFS= read -r record_at; } <<< "$record_info"
|
|
466
|
+
[ -n "$record_state" ] || exit 0
|
|
467
|
+
fi
|
|
468
|
+
|
|
469
|
+
# The runtime's refusal texts, byte for byte. The next command names the slug
|
|
470
|
+
# only when its feature folder exists here or in the main checkout, which is
|
|
471
|
+
# when the runtime names it too (and when `readiness --slug` resolves it).
|
|
472
|
+
next="doflow-run readiness --task-class $STAGE_TEMPLATE --task-id $task_id"
|
|
473
|
+
if [ -n "$record_slug" ] && [ "$record_slug" != "$task_id" ]; then
|
|
474
|
+
checkouts
|
|
475
|
+
if [ -d "$repo_root/agent-docs/doflow/$record_slug" ] || { [ -n "$MAIN" ] && [ -d "$MAIN/agent-docs/doflow/$record_slug" ]; }; then
|
|
476
|
+
next="$next --slug=$record_slug"
|
|
477
|
+
fi
|
|
478
|
+
fi
|
|
479
|
+
gate="doflow gate readiness-before-implementation: task '$task_id'"
|
|
480
|
+
rest="Next: $next, then gather what it lists until it reports READY. Nothing was changed."
|
|
481
|
+
if [ -z "$FOUND" ]; then
|
|
482
|
+
printf "%s has no readiness record for the '%s' template. %s\n" "$gate" "$STAGE_TEMPLATE" "$rest" >&2
|
|
483
|
+
exit 2
|
|
484
|
+
fi
|
|
485
|
+
if [ "$record_state" != READY ]; then
|
|
486
|
+
printf "%s was last evaluated %s at %s against the '%s' template, not READY. %s\n" \
|
|
487
|
+
"$gate" "$record_state" "$record_at" "$STAGE_TEMPLATE" "$rest" >&2
|
|
488
|
+
exit 2
|
|
489
|
+
fi
|
|
490
|
+
if [ "$record_class" != "$STAGE_TEMPLATE" ]; then
|
|
491
|
+
printf "%s has a READY record for the '%s' template, and this stage needs '%s'. %s\n" \
|
|
492
|
+
"$gate" "$record_class" "$STAGE_TEMPLATE" "$rest" >&2
|
|
493
|
+
exit 2
|
|
494
|
+
fi
|
|
495
|
+
|
|
496
|
+
exit 0
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# prompt-nudge.jq — decision program for the standalone-prompt nudge (IC-005)
|
|
2
|
+
#
|
|
3
|
+
# Invocation, identical in the UserPromptSubmit hook and in the corpus runner:
|
|
4
|
+
# printf '%s' "$INPUT" | jq -r --slurpfile R <registry> -f prompt-nudge.jq
|
|
5
|
+
# Input is the hook payload on stdin; $R[0] is core/registry/workflows.json and its
|
|
6
|
+
# `promptNudge` object holds every rule (IC-007). Output is one line: `nudge`, `suppress`,
|
|
7
|
+
# or empty. jq exits non-zero only on an error, and the caller treats that as silence.
|
|
8
|
+
#
|
|
9
|
+
# The rules are data; this file is the algorithm. It reads only `.` and $R[0].promptNudge,
|
|
10
|
+
# keeps no state, and gives the same output for the same input. A missing or mistyped
|
|
11
|
+
# rule, or a pathPattern that does not compile, is an error on every prompt, never a default.
|
|
12
|
+
#
|
|
13
|
+
# Portability: jq 1.5 built with Oniguruma. No builtin newer than 1.5 (no IN, trim, pick,
|
|
14
|
+
# abs, toarray, splits, $__loc__, halt_error), no if without else, no input or environment.
|
|
15
|
+
# Only the step 2 pattern and pathPattern are regexes; every list entry is matched literally.
|
|
16
|
+
# Whole-prompt anchors use \A and \z so a newline inside the prompt never ends a match.
|
|
17
|
+
#
|
|
18
|
+
# Cost: a prompt can be a pasted log of hundreds of KB, and every prompt of a session is
|
|
19
|
+
# decided until the session is nudged or suppressed. Work over the whole prompt is limited to
|
|
20
|
+
# single regex scans, slicing and splitting, which jq does natively; lowercasing and the line
|
|
21
|
+
# test, which jq does one character or one line at a time, touch only bounded pieces. Each
|
|
22
|
+
# such shortcut gives the answer IC-005's steps give on the whole prompt (see the differential
|
|
23
|
+
# case in test/hooks/prompt-nudge-decision.test.js).
|
|
24
|
+
|
|
25
|
+
# Whitespace removed from both ends. The trailing match may start only where a whitespace run
|
|
26
|
+
# starts: a plain \s+$ retries from every position of a long interior run, which is quadratic
|
|
27
|
+
# (a 20 KB run of spaces took 1.7 s). It runs on the last 1024 characters, and on the whole
|
|
28
|
+
# text only when those are all whitespace.
|
|
29
|
+
def strip_ws:
|
|
30
|
+
sub("^\\s+"; "")
|
|
31
|
+
| length as $n
|
|
32
|
+
| if $n <= 1024 then sub("(?<!\\s)\\s+$"; "")
|
|
33
|
+
else (.[$n - 1024:] | sub("(?<!\\s)\\s+$"; "")) as $tail
|
|
34
|
+
| if $tail != "" then .[0:$n - 1024] + $tail else sub("(?<!\\s)\\s+$"; "") end
|
|
35
|
+
end;
|
|
36
|
+
|
|
37
|
+
# A list entry with every regex metacharacter escaped. An empty entry is an error rather than
|
|
38
|
+
# a pattern that matches anything. Only an entry holding a metacharacter is rebuilt character by
|
|
39
|
+
# character; that loop is the costly part of building the patterns on every prompt.
|
|
40
|
+
def esc:
|
|
41
|
+
if . == "" then error("promptNudge holds an empty list entry")
|
|
42
|
+
elif test("[.*+?()\\[\\]{}|^$\\\\]") | not then .
|
|
43
|
+
else ("\\.*+?()[]{}|^$" | explode) as $meta
|
|
44
|
+
| explode
|
|
45
|
+
| map(. as $c | if any($meta[]; . == $c) then [92, $c] else [$c] end)
|
|
46
|
+
| add
|
|
47
|
+
| implode
|
|
48
|
+
end;
|
|
49
|
+
|
|
50
|
+
def string_list($v; $name):
|
|
51
|
+
if ($v | type) == "array" and ($v | length) > 0 and all($v[]; type == "string")
|
|
52
|
+
then $v
|
|
53
|
+
else error("promptNudge.\($name) is not a non-empty list of strings")
|
|
54
|
+
end;
|
|
55
|
+
|
|
56
|
+
def int($v; $name):
|
|
57
|
+
if ($v | type) == "number" and $v == ($v | floor)
|
|
58
|
+
then $v
|
|
59
|
+
else error("promptNudge.\($name) is not an integer")
|
|
60
|
+
end;
|
|
61
|
+
|
|
62
|
+
# One alternation of literal entries, for use inside (?: ... ).
|
|
63
|
+
def alt($v; $name): string_list($v; $name) | map(esc) | join("|");
|
|
64
|
+
|
|
65
|
+
# Each entry as a pattern that matches where the ASCII-lowercased text holds the entry: every
|
|
66
|
+
# lowercase ASCII letter becomes [xX], everything else is literal. An entry with an uppercase
|
|
67
|
+
# ASCII letter can never occur in lowercased text, so it is left out. One pattern per entry: an
|
|
68
|
+
# alternation of [xX]-led entries cannot skip ahead and scans about twice as slowly.
|
|
69
|
+
def ascii_ci_patterns($v; $name):
|
|
70
|
+
[string_list($v; $name)[] | (esc | explode) as $e | select(all($e[]; . < 65 or . > 90))
|
|
71
|
+
| $e | map(if 97 <= . and . <= 122 then [91, ., . - 32, 93] else [.] end) | add | implode];
|
|
72
|
+
|
|
73
|
+
# The line test: one line, already trimmed and non-empty, judged against the compiled rules.
|
|
74
|
+
def line_ok($re):
|
|
75
|
+
.[0:$re.maxScan] as $l
|
|
76
|
+
| ($l | ascii_downcase | sub($re.lead; "")) as $ll
|
|
77
|
+
| ($ll | sub($re.verb; "")) as $after
|
|
78
|
+
| ($ll | test("\\?\\s*$") | not)
|
|
79
|
+
and ($ll | test($re.startWord) | not)
|
|
80
|
+
and ($ll | test($re.verb))
|
|
81
|
+
and ($after | test($re.objectBlocker) | not)
|
|
82
|
+
and ($ll | test($re.contextBlocker) | not)
|
|
83
|
+
and (($after | test($re.noun)) or ($l | test($re.path)));
|
|
84
|
+
|
|
85
|
+
($R[0].promptNudge) as $n
|
|
86
|
+
| if ($n | type) == "object" then . else error("promptNudge is missing or not an object") end
|
|
87
|
+
| {
|
|
88
|
+
minChars: int($n.minChars; "minChars"),
|
|
89
|
+
maxScan: int($n.maxScanChars; "maxScanChars"),
|
|
90
|
+
prefixes: string_list($n.excludePrefixes; "excludePrefixes"),
|
|
91
|
+
phrases: ascii_ci_patterns($n.excludeContainsPhrases; "excludeContainsPhrases"),
|
|
92
|
+
stallWords: (alt($n.stallWords; "stallWords") | $n.stallWords),
|
|
93
|
+
stallWidth: ([$n.stallWords[] | length] | max),
|
|
94
|
+
lead: ("^(?:(?:" + alt($n.leadIns; "leadIns") + "),?\\s+)+"),
|
|
95
|
+
startWord: ("^(?:" + alt($n.excludeStartWords; "excludeStartWords") + ")\\b"),
|
|
96
|
+
verb: ("^(?:" + alt($n.verbs; "verbs") + ")\\b"),
|
|
97
|
+
objectBlocker: ("^\\s+(?:" + alt($n.objectBlockers; "objectBlockers") + ")\\b"),
|
|
98
|
+
contextBlocker: ("\\b(?:" + alt($n.contextBlockers; "contextBlockers") + ")\\b"),
|
|
99
|
+
noun: ("\\b(?:" + alt($n.nouns; "nouns") + ")s?\\b"),
|
|
100
|
+
path: $n.pathPattern
|
|
101
|
+
} as $re
|
|
102
|
+
# pathPattern is compiled here on every prompt, so a broken pattern never hides behind an
|
|
103
|
+
# early return.
|
|
104
|
+
| ("" | test($re.path)) as $path_compiles
|
|
105
|
+
# Step 1: the prompt as a string, carriage returns removed, trimmed. IC-005 states the steps
|
|
106
|
+
# on the ASCII-lowercased prompt `lt`; lowercasing costs a jq step per character (about 80 ms
|
|
107
|
+
# on 200 KB), so only bounded slices are lowercased and every whole-prompt check below runs on
|
|
108
|
+
# `t` in a form that gives the same answer as on `lt`.
|
|
109
|
+
| (if type == "object" then .prompt else null end) as $raw
|
|
110
|
+
| (if ($raw | type) == "string" then $raw else "" end
|
|
111
|
+
| if contains("\r") then split("\r") | join("") else . end | strip_ws) as $t
|
|
112
|
+
# Step 2 runs before the length floor so a short /do-plan also ends evaluation (suppress).
|
|
113
|
+
# IC-005's (^|[\s`'"(])/do(-[a-z]+)?\b on lt, as /[dD][oO]\b on t not preceded by a character
|
|
114
|
+
# outside that class: the optional -name never changes whether it matches, and a pattern that
|
|
115
|
+
# starts with `/` lets the regex engine skip to each slash.
|
|
116
|
+
| if ($t | test("(?<![^\\s`'\"(])/[dD][oO]\\b")) then "suppress"
|
|
117
|
+
elif ($t | length) < $re.minChars then ""
|
|
118
|
+
elif any($re.prefixes[]; . as $x | $t | startswith($x)) then ""
|
|
119
|
+
# Step 5: lt is a stall word followed only by punctuation and spaces. Lowercasing touches
|
|
120
|
+
# neither, so the head is lowercased and the rest checked on t.
|
|
121
|
+
elif (($t[0:$re.stallWidth] | ascii_downcase) as $h
|
|
122
|
+
| any($re.stallWords[]; . as $w
|
|
123
|
+
| ($h | startswith($w)) and ($t[($w | length):] | test("\\A[[:punct:]\\s]*\\z")))) then ""
|
|
124
|
+
elif ($t | endswith("?")) then ""
|
|
125
|
+
elif any($re.phrases[]; . as $x | $t | test($x)) then ""
|
|
126
|
+
# Step 8: only the first and the last non-empty lines are judged. t starts and ends with a
|
|
127
|
+
# non-space character, so its first and last lines are the first and last non-empty ones.
|
|
128
|
+
elif ($t | split("\n") | [.[0], .[-1]] | unique
|
|
129
|
+
| any(.[]; strip_ws | line_ok($re))) then "nudge"
|
|
130
|
+
else ""
|
|
131
|
+
end
|