@michaelxwb/code-flow 0.8.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -152
- package/bin/code-flow.js +12 -0
- package/docs/README.md +19 -0
- package/docs/USAGE.md +156 -1417
- package/docs/v1-design.md +1858 -0
- package/docs/v1-implementation.md +186 -0
- package/lib/adapters.js +126 -0
- package/lib/cli.js +258 -0
- package/lib/config.js +85 -0
- package/lib/doctor.js +283 -0
- package/lib/frontmatter.js +42 -0
- package/lib/fs.js +141 -0
- package/lib/glob.js +23 -0
- package/lib/hook.js +99 -0
- package/lib/init.js +132 -0
- package/lib/learn.js +170 -0
- package/lib/locks.js +52 -0
- package/lib/markdown.js +13 -0
- package/lib/process.js +116 -0
- package/lib/render.js +115 -0
- package/lib/repo.js +48 -0
- package/lib/runs.js +155 -0
- package/lib/session.js +40 -0
- package/lib/spec.js +80 -0
- package/lib/task.js +258 -0
- package/lib/taskdoc.js +308 -0
- package/lib/toml.js +140 -0
- package/lib/verify.js +168 -0
- package/lib/worktree.js +930 -0
- package/package.json +18 -21
- package/templates/adapters/opencode.mjs +65 -0
- package/templates/config.json +39 -0
- package/templates/instructions.md +13 -0
- package/templates/launcher/bridge.cjs +295 -0
- package/templates/launcher/hook.cmd +15 -0
- package/templates/launcher/hook.ps1 +10 -0
- package/templates/launcher/hook.sh +20 -0
- package/templates/skills/cf-check.md +19 -0
- package/templates/skills/cf-init.md +16 -0
- package/templates/skills/cf-spec.md +20 -0
- package/templates/skills/cf-task-align.md +19 -0
- package/templates/skills/cf-task-archive.md +20 -0
- package/templates/skills/cf-task-plan.md +31 -0
- package/templates/skills/cf-task-prd.md +14 -0
- package/templates/skills/cf-task-start.md +41 -0
- package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-frontend.md +12 -12
- package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-full.md +17 -19
- package/{src/core/code-flow/specs/shared/design → templates/workflow}/design-lite.md +12 -12
- package/{src/core/code-flow/specs/shared/prd-template.md → templates/workflow/prd.md} +1 -1
- package/templates/workflow/project-guidelines.md +48 -0
- package/templates/workflow/scenarios.md +67 -0
- package/templates/workflow/task.md +56 -0
- package/docs/acceptance-contract.md +0 -217
- package/docs/ci/acceptance-evidence.md +0 -77
- package/docs/migrations/runtime-0.7.0.md +0 -41
- package/docs/migrations/runtime-0.8.0.md +0 -154
- package/docs/migrations/runtime-0.9.0.md +0 -40
- package/docs/migrations/spec-workflow-0.6.0.md +0 -42
- package/docs/spec-injection-hardening.md +0 -54
- package/docs/spec-injection-performance.json +0 -73
- package/docs/spec-optimization.md +0 -12
- package/src/adapters/claude/CLAUDE.md +0 -45
- package/src/adapters/claude/commands/cf-init.md +0 -262
- package/src/adapters/claude/commands/cf-learn.md +0 -291
- package/src/adapters/claude/commands/cf-spec.md +0 -87
- package/src/adapters/claude/commands/cf-stats.md +0 -96
- package/src/adapters/claude/commands/cf-sync.md +0 -39
- package/src/adapters/claude/commands/cf-task/align.md +0 -331
- package/src/adapters/claude/commands/cf-task/archive.md +0 -155
- package/src/adapters/claude/commands/cf-task/block.md +0 -49
- package/src/adapters/claude/commands/cf-task/graph.md +0 -92
- package/src/adapters/claude/commands/cf-task/note.md +0 -248
- package/src/adapters/claude/commands/cf-task/plan.md +0 -477
- package/src/adapters/claude/commands/cf-task/prd.md +0 -173
- package/src/adapters/claude/commands/cf-task/start.md +0 -317
- package/src/adapters/claude/commands/cf-task/status.md +0 -71
- package/src/adapters/claude/commands/cf-task/verify-e2e.md +0 -148
- package/src/adapters/claude/commands/cf-validate.md +0 -100
- package/src/adapters/claude/settings.local.json +0 -50
- package/src/adapters/codex/AGENTS.md +0 -45
- package/src/adapters/codex/config.toml +0 -3
- package/src/adapters/codex/hooks.json +0 -54
- package/src/adapters/codex/skills/cf-init/SKILL.md +0 -273
- package/src/adapters/codex/skills/cf-learn/SKILL.md +0 -296
- package/src/adapters/codex/skills/cf-spec/SKILL.md +0 -88
- package/src/adapters/codex/skills/cf-stats/SKILL.md +0 -97
- package/src/adapters/codex/skills/cf-sync/SKILL.md +0 -45
- package/src/adapters/codex/skills/cf-task-align/SKILL.md +0 -332
- package/src/adapters/codex/skills/cf-task-archive/SKILL.md +0 -156
- package/src/adapters/codex/skills/cf-task-block/SKILL.md +0 -50
- package/src/adapters/codex/skills/cf-task-graph/SKILL.md +0 -93
- package/src/adapters/codex/skills/cf-task-note/SKILL.md +0 -245
- package/src/adapters/codex/skills/cf-task-plan/SKILL.md +0 -478
- package/src/adapters/codex/skills/cf-task-prd/SKILL.md +0 -174
- package/src/adapters/codex/skills/cf-task-start/SKILL.md +0 -318
- package/src/adapters/codex/skills/cf-task-status/SKILL.md +0 -72
- package/src/adapters/codex/skills/cf-task-verify-e2e/SKILL.md +0 -145
- package/src/adapters/codex/skills/cf-validate/SKILL.md +0 -102
- package/src/adapters/commands.json +0 -115
- package/src/adapters/costrict/CLAUDE.md +0 -45
- package/src/adapters/costrict/commands/cf-init.md +0 -262
- package/src/adapters/costrict/commands/cf-learn.md +0 -291
- package/src/adapters/costrict/commands/cf-spec.md +0 -87
- package/src/adapters/costrict/commands/cf-stats.md +0 -96
- package/src/adapters/costrict/commands/cf-sync.md +0 -39
- package/src/adapters/costrict/commands/cf-task/align.md +0 -331
- package/src/adapters/costrict/commands/cf-task/archive.md +0 -155
- package/src/adapters/costrict/commands/cf-task/block.md +0 -49
- package/src/adapters/costrict/commands/cf-task/graph.md +0 -92
- package/src/adapters/costrict/commands/cf-task/note.md +0 -248
- package/src/adapters/costrict/commands/cf-task/plan.md +0 -477
- package/src/adapters/costrict/commands/cf-task/prd.md +0 -173
- package/src/adapters/costrict/commands/cf-task/start.md +0 -317
- package/src/adapters/costrict/commands/cf-task/status.md +0 -71
- package/src/adapters/costrict/commands/cf-task/verify-e2e.md +0 -148
- package/src/adapters/costrict/commands/cf-validate.md +0 -100
- package/src/adapters/costrict/settings.local.json +0 -50
- package/src/adapters/install-plan.json +0 -100
- package/src/adapters/opencode/AGENTS.md +0 -45
- package/src/adapters/opencode/commands/cf-init.md +0 -281
- package/src/adapters/opencode/commands/cf-learn.md +0 -295
- package/src/adapters/opencode/commands/cf-spec.md +0 -91
- package/src/adapters/opencode/commands/cf-stats.md +0 -100
- package/src/adapters/opencode/commands/cf-sync.md +0 -44
- package/src/adapters/opencode/commands/cf-task/align.md +0 -331
- package/src/adapters/opencode/commands/cf-task/archive.md +0 -160
- package/src/adapters/opencode/commands/cf-task/block.md +0 -54
- package/src/adapters/opencode/commands/cf-task/graph.md +0 -97
- package/src/adapters/opencode/commands/cf-task/note.md +0 -253
- package/src/adapters/opencode/commands/cf-task/plan.md +0 -477
- package/src/adapters/opencode/commands/cf-task/prd.md +0 -178
- package/src/adapters/opencode/commands/cf-task/start.md +0 -317
- package/src/adapters/opencode/commands/cf-task/status.md +0 -76
- package/src/adapters/opencode/commands/cf-task/verify-e2e.md +0 -148
- package/src/adapters/opencode/commands/cf-validate.md +0 -105
- package/src/adapters/opencode/opencode.json +0 -3
- package/src/adapters/opencode/plugins/code-flow/index.js +0 -162
- package/src/adapters/opencode/plugins/code-flow/package.json +0 -6
- package/src/adapters/platform-bindings.json +0 -370
- package/src/cli.js +0 -1076
- package/src/core/code-flow/config.yml +0 -161
- package/src/core/code-flow/runtime-commands.json +0 -259
- package/src/core/code-flow/scripts/cf_acceptance_manifest.py +0 -10
- package/src/core/code-flow/scripts/cf_acceptance_runner.py +0 -10
- package/src/core/code-flow/scripts/cf_audit.py +0 -10
- package/src/core/code-flow/scripts/cf_codex_hook.py +0 -10
- package/src/core/code-flow/scripts/cf_design_lint.py +0 -10
- package/src/core/code-flow/scripts/cf_evidence_import.py +0 -10
- package/src/core/code-flow/scripts/cf_feedback.py +0 -10
- package/src/core/code-flow/scripts/cf_opencode_event.py +0 -10
- package/src/core/code-flow/scripts/cf_post_hook.py +0 -11
- package/src/core/code-flow/scripts/cf_pre_tool_hook.py +0 -11
- package/src/core/code-flow/scripts/cf_scenarios.py +0 -10
- package/src/core/code-flow/scripts/cf_spec_context.py +0 -10
- package/src/core/code-flow/scripts/cf_spec_gate.py +0 -10
- package/src/core/code-flow/scripts/cf_spec_session.py +0 -10
- package/src/core/code-flow/scripts/cf_stats.py +0 -10
- package/src/core/code-flow/scripts/cf_stop_hook.py +0 -11
- package/src/core/code-flow/scripts/cf_sync.py +0 -10
- package/src/core/code-flow/scripts/cf_task_index.py +0 -10
- package/src/core/code-flow/scripts/cf_task_parallel.py +0 -10
- package/src/core/code-flow/scripts/cf_task_workflow.py +0 -10
- package/src/core/code-flow/scripts/cf_user_prompt_hook.py +0 -11
- package/src/core/code-flow/scripts/cf_validation.py +0 -10
- package/src/core/code-flow/scripts/codeflow/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_evidence.py +0 -347
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_manifest_core.py +0 -660
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_plan.py +0 -202
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_registration.py +0 -60
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_risk.py +0 -73
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_schema.py +0 -282
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_acceptance_selection.py +0 -266
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_inputs.py +0 -172
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_check_registration.py +0 -326
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_checks.py +0 -333
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_plan_quality.py +0 -197
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_quality.py +0 -174
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_scenario_source.py +0 -300
- package/src/core/code-flow/scripts/codeflow/acceptance/cf_traceability.py +0 -502
- package/src/core/code-flow/scripts/codeflow/cli/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_manifest.py +0 -347
- package/src/core/code-flow/scripts/codeflow/cli/cf_acceptance_runner.py +0 -1490
- package/src/core/code-flow/scripts/codeflow/cli/cf_audit.py +0 -533
- package/src/core/code-flow/scripts/codeflow/cli/cf_design_lint.py +0 -172
- package/src/core/code-flow/scripts/codeflow/cli/cf_evidence_import.py +0 -607
- package/src/core/code-flow/scripts/codeflow/cli/cf_feedback.py +0 -53
- package/src/core/code-flow/scripts/codeflow/cli/cf_scenarios.py +0 -58
- package/src/core/code-flow/scripts/codeflow/cli/cf_spec_context.py +0 -1175
- package/src/core/code-flow/scripts/codeflow/cli/cf_spec_gate.py +0 -60
- package/src/core/code-flow/scripts/codeflow/cli/cf_spec_session.py +0 -46
- package/src/core/code-flow/scripts/codeflow/cli/cf_stats.py +0 -540
- package/src/core/code-flow/scripts/codeflow/cli/cf_sync.py +0 -144
- package/src/core/code-flow/scripts/codeflow/cli/cf_task_index.py +0 -177
- package/src/core/code-flow/scripts/codeflow/cli/cf_task_parallel.py +0 -1142
- package/src/core/code-flow/scripts/codeflow/cli/cf_task_workflow.py +0 -742
- package/src/core/code-flow/scripts/codeflow/cli/cf_validation.py +0 -31
- package/src/core/code-flow/scripts/codeflow/core/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/core/cf_context_cache.py +0 -67
- package/src/core/code-flow/scripts/codeflow/core/cf_core.py +0 -609
- package/src/core/code-flow/scripts/codeflow/core/cf_exec_base.py +0 -325
- package/src/core/code-flow/scripts/codeflow/core/cf_fs.py +0 -57
- package/src/core/code-flow/scripts/codeflow/core/cf_git.py +0 -92
- package/src/core/code-flow/scripts/codeflow/core/cf_log.py +0 -160
- package/src/core/code-flow/scripts/codeflow/core/cf_markdown.py +0 -77
- package/src/core/code-flow/scripts/codeflow/core/cf_native_paths.py +0 -26
- package/src/core/code-flow/scripts/codeflow/core/cf_patch.py +0 -33
- package/src/core/code-flow/scripts/codeflow/core/cf_runtime_install.py +0 -32
- package/src/core/code-flow/scripts/codeflow/core/cf_session_state.py +0 -37
- package/src/core/code-flow/scripts/codeflow/core/cf_stop_guard.py +0 -53
- package/src/core/code-flow/scripts/codeflow/core/cf_task_state.py +0 -9
- package/src/core/code-flow/scripts/codeflow/core/cf_validators.py +0 -408
- package/src/core/code-flow/scripts/codeflow/core/cf_worktree_env.py +0 -253
- package/src/core/code-flow/scripts/codeflow/evidence/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/evidence/cf_evidence_store.py +0 -527
- package/src/core/code-flow/scripts/codeflow/hooks/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/hooks/cf_codex_hook.py +0 -95
- package/src/core/code-flow/scripts/codeflow/hooks/cf_opencode_event.py +0 -98
- package/src/core/code-flow/scripts/codeflow/hooks/cf_post_hook.py +0 -30
- package/src/core/code-flow/scripts/codeflow/hooks/cf_pre_tool_hook.py +0 -41
- package/src/core/code-flow/scripts/codeflow/hooks/cf_stop_hook.py +0 -292
- package/src/core/code-flow/scripts/codeflow/hooks/cf_user_prompt_hook.py +0 -161
- package/src/core/code-flow/scripts/codeflow/spec/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/spec/cf_active_task.py +0 -412
- package/src/core/code-flow/scripts/codeflow/spec/cf_context_refresh.py +0 -301
- package/src/core/code-flow/scripts/codeflow/spec/cf_edit_service.py +0 -149
- package/src/core/code-flow/scripts/codeflow/spec/cf_injection.py +0 -111
- package/src/core/code-flow/scripts/codeflow/spec/cf_rule_readiness.py +0 -114
- package/src/core/code-flow/scripts/codeflow/spec/cf_scan.py +0 -367
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_budget.py +0 -55
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_context_model.py +0 -477
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_impact.py +0 -119
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_metadata.py +0 -387
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_migrate.py +0 -529
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize.py +0 -193
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_optimize_apply.py +0 -187
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_resolver.py +0 -369
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_router.py +0 -304
- package/src/core/code-flow/scripts/codeflow/spec/cf_spec_verify.py +0 -687
- package/src/core/code-flow/scripts/codeflow/spec/cf_stage_gate.py +0 -330
- package/src/core/code-flow/scripts/codeflow/spec/cf_task_projection.py +0 -83
- package/src/core/code-flow/scripts/codeflow/spec/cf_workflow_service.py +0 -755
- package/src/core/code-flow/scripts/codeflow/spec/cf_workflow_transaction.py +0 -110
- package/src/core/code-flow/scripts/codeflow/task/__init__.py +0 -0
- package/src/core/code-flow/scripts/codeflow/task/cf_requirement_verification.py +0 -867
- package/src/core/code-flow/scripts/codeflow/task/cf_task_doc.py +0 -178
- package/src/core/code-flow/scripts/codeflow/task/cf_task_merge.py +0 -635
- package/src/core/code-flow/scripts/codeflow/task/cf_task_plan.py +0 -251
- package/src/core/code-flow/scripts/codeflow/task/cf_task_runtime.py +0 -570
- package/src/core/code-flow/scripts/codeflow/task/cf_validation_service.py +0 -73
- package/src/core/code-flow/scripts/runtime-resources.json +0 -29
- package/src/core/code-flow/specs/backend/_map.md +0 -56
- package/src/core/code-flow/specs/backend/code-quality-performance.md +0 -59
- package/src/core/code-flow/specs/backend/database.md +0 -51
- package/src/core/code-flow/specs/backend/directory-structure.md +0 -54
- package/src/core/code-flow/specs/backend/logging.md +0 -50
- package/src/core/code-flow/specs/backend/platform-rules.md +0 -55
- package/src/core/code-flow/specs/frontend/_map.md +0 -52
- package/src/core/code-flow/specs/frontend/component-specs.md +0 -68
- package/src/core/code-flow/specs/frontend/directory-structure.md +0 -53
- package/src/core/code-flow/specs/frontend/quality-standards.md +0 -58
- package/src/core/code-flow/specs/shared/_map.md +0 -57
- package/src/core/code-flow/specs/shared/scenarios-template.md +0 -55
- package/src/core/code-flow/tasks/.gitkeep +0 -0
- package/src/core/code-flow/validation.yml +0 -59
- package/src/migrate/runtime.js +0 -213
- package/src/migrate/spec-workflow.js +0 -253
- package/src/runtime.js +0 -219
package/package.json
CHANGED
|
@@ -1,33 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michaelxwb/code-flow",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Global Node-only spec injection and task-to-spec workflow for AI coding agents",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"bin": {
|
|
7
|
-
"code-flow": "
|
|
7
|
+
"code-flow": "bin/code-flow.js"
|
|
8
8
|
},
|
|
9
9
|
"engines": {
|
|
10
10
|
"node": ">=18"
|
|
11
11
|
},
|
|
12
|
+
"codeFlow": {
|
|
13
|
+
"hookProtocol": 1
|
|
14
|
+
},
|
|
12
15
|
"files": [
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"src/core/code-flow/scripts/runtime-resources.json",
|
|
18
|
-
"src/adapters/**",
|
|
19
|
-
"docs/migrations/spec-workflow-0.6.0.md",
|
|
20
|
-
"docs/migrations/runtime-0.7.0.md",
|
|
21
|
-
"docs/migrations/runtime-0.8.0.md",
|
|
22
|
-
"docs/migrations/runtime-0.9.0.md",
|
|
23
|
-
"docs/acceptance-contract.md",
|
|
24
|
-
"docs/spec-injection-hardening.md",
|
|
25
|
-
"docs/spec-optimization.md",
|
|
26
|
-
"docs/spec-injection-performance.json",
|
|
16
|
+
"bin/",
|
|
17
|
+
"lib/",
|
|
18
|
+
"templates/",
|
|
19
|
+
"README.md",
|
|
27
20
|
"docs/USAGE.md",
|
|
28
|
-
"docs/
|
|
21
|
+
"docs/v1-design.md",
|
|
22
|
+
"docs/v1-implementation.md",
|
|
23
|
+
"docs/README.md"
|
|
29
24
|
],
|
|
30
|
-
"dependencies": {
|
|
31
|
-
|
|
25
|
+
"dependencies": {},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "node test/run.cjs",
|
|
28
|
+
"test:performance": "node --test test/performance.test.js"
|
|
32
29
|
}
|
|
33
|
-
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// code-flow:v1
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { isAbsolute, join } from "node:path";
|
|
5
|
+
const NODE = __CODE_FLOW_NODE__;
|
|
6
|
+
const ENTRY = __CODE_FLOW_ENTRY__;
|
|
7
|
+
function invoke(directory, event, signal) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const bridge = join(directory, ".code-flow/launcher/bridge.cjs");
|
|
10
|
+
const useBridge = existsSync(bridge);
|
|
11
|
+
const command = existsSync(NODE) ? NODE : process.execPath;
|
|
12
|
+
const args = useBridge
|
|
13
|
+
? [bridge, "--format", "opencode", "--node", NODE, "--entry", ENTRY]
|
|
14
|
+
: [ENTRY, "hook", "prompt", "--format=opencode"];
|
|
15
|
+
const child = execFile(command, args,
|
|
16
|
+
{ cwd: directory, timeout: 3000, maxBuffer: 131072, windowsHide: true, signal },
|
|
17
|
+
(error, stdout, stderr) => {
|
|
18
|
+
if (stderr.trim()) console.error(stderr.trim());
|
|
19
|
+
if (error) return reject(error);
|
|
20
|
+
try {
|
|
21
|
+
const result = stdout.trim() ? JSON.parse(stdout) : { context: "" };
|
|
22
|
+
if (typeof result.context !== "string") throw new Error("Invalid code-flow context");
|
|
23
|
+
resolve(result.context);
|
|
24
|
+
} catch (failure) { reject(failure); }
|
|
25
|
+
});
|
|
26
|
+
child.stdin.on("error", reject);
|
|
27
|
+
child.stdin.end(JSON.stringify({ directory, event }));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export default {
|
|
31
|
+
id: "code-flow",
|
|
32
|
+
async setup(ctx) {
|
|
33
|
+
const pending = new Map(), jobs = new Map(), controller = new AbortController();
|
|
34
|
+
const diagnose = error => { if (!controller.signal.aborted) console.error("code-flow: " + error.message); };
|
|
35
|
+
try {
|
|
36
|
+
await ctx.session.hook("prompt", event => {
|
|
37
|
+
if (controller.signal.aborted) return;
|
|
38
|
+
if (typeof event?.prompt?.text !== "string" || !event.prompt.text.trim()) return;
|
|
39
|
+
const sid = event.sessionID;
|
|
40
|
+
const previous = jobs.get(sid) || Promise.resolve();
|
|
41
|
+
const job = previous.then(async () => {
|
|
42
|
+
if (typeof sid !== "string" || !sid) throw new Error("Missing native sessionID");
|
|
43
|
+
const info = await ctx.session.get({ sessionID: sid });
|
|
44
|
+
if (!isAbsolute(info?.location?.directory || "")) throw new Error("Missing session directory");
|
|
45
|
+
const text = await invoke(info.location.directory, event, controller.signal);
|
|
46
|
+
if (!controller.signal.aborted) pending.set(sid, text);
|
|
47
|
+
}).catch(error => {
|
|
48
|
+
pending.delete(sid);
|
|
49
|
+
diagnose(error);
|
|
50
|
+
});
|
|
51
|
+
jobs.set(sid, job);
|
|
52
|
+
void job.finally(() => { if (jobs.get(sid) === job) jobs.delete(sid); });
|
|
53
|
+
return job;
|
|
54
|
+
});
|
|
55
|
+
await ctx.session.hook("context", async event => {
|
|
56
|
+
try {
|
|
57
|
+
await jobs.get(event.sessionID);
|
|
58
|
+
const text = pending.get(event.sessionID);
|
|
59
|
+
if (text && !controller.signal.aborted) event.system.push({ type: "text", text });
|
|
60
|
+
} catch (error) { diagnose(error); }
|
|
61
|
+
});
|
|
62
|
+
} catch (error) { diagnose(error); }
|
|
63
|
+
return () => { controller.abort(); pending.clear(); jobs.clear(); };
|
|
64
|
+
}
|
|
65
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": {
|
|
3
|
+
"note": "本字段仅用于说明,code-flow 会忽略。JSON 不支持注释,因此用 _comment 记录参数含义。",
|
|
4
|
+
"version": "配置格式版本,当前固定为 1。",
|
|
5
|
+
"workflow": "工作流级别:hook(默认,Spec 注入 + 一份带声明测试的任务文档)或 full(额外 PRD/design/scenarios)。",
|
|
6
|
+
"always": "每次 prompt 都注入的 Spec 路径数组;路径相对 .code-flow/specs,不带 .md,可带 #RULE-...。",
|
|
7
|
+
"budget": {
|
|
8
|
+
"maxTokens": "一次注入的最大估算 token 数(正整数);超出时按优先级省略并输出 omitted。",
|
|
9
|
+
"maxBytes": "一次注入的最大 UTF-8 字节数(正整数);与 maxTokens 同时生效。"
|
|
10
|
+
},
|
|
11
|
+
"tests": {
|
|
12
|
+
"timeoutSeconds": "任务声明测试的默认超时秒数(正整数);单个测试可用 timeout 覆盖。",
|
|
13
|
+
"jobs": "task finish 并行执行声明测试的默认并发数(正整数)。",
|
|
14
|
+
"regression": "合并后追加的回归命令:null 表示关闭;否则为对象,提供 argv 或 run 之一,可带 cwd/timeout/parallel/group/env。"
|
|
15
|
+
},
|
|
16
|
+
"capture": {
|
|
17
|
+
"enabled": "是否自动记录过程事件;只有 literal true 开启。",
|
|
18
|
+
"path": "过程事件目录,必须是 .code-flow 下的相对路径。"
|
|
19
|
+
},
|
|
20
|
+
"paths": "文件 glob 到 Spec 引用数组的映射;prompt 中出现匹配路径时注入对应 Spec。glob 支持 *、**、?,不支持 {}、[]、() 或 !。"
|
|
21
|
+
},
|
|
22
|
+
"version": 1,
|
|
23
|
+
"workflow": "hook",
|
|
24
|
+
"always": [],
|
|
25
|
+
"budget": {
|
|
26
|
+
"maxTokens": 2500,
|
|
27
|
+
"maxBytes": 16384
|
|
28
|
+
},
|
|
29
|
+
"tests": {
|
|
30
|
+
"timeoutSeconds": 120,
|
|
31
|
+
"jobs": 2,
|
|
32
|
+
"regression": null
|
|
33
|
+
},
|
|
34
|
+
"capture": {
|
|
35
|
+
"enabled": false,
|
|
36
|
+
"path": ".code-flow/process"
|
|
37
|
+
},
|
|
38
|
+
"paths": {}
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
使用全局 code-flow CLI,以及 `.code-flow/config.json`、`.code-flow/specs/`、`.code-flow/tasks/`、`.code-flow/templates/` 中的当前项目数据。
|
|
2
|
+
|
|
3
|
+
一个需求一份任务文档 `<requirement>.md`,包含多个 `## TASK-NNN` 段。通过任务文档路径 + TASK ID,或 prompt Hook 提供的 session token 选择 Task;不要根据状态推断。
|
|
4
|
+
|
|
5
|
+
实现前读取当前 Task 的 Spec 引用。Spec 引用是相对于 `.code-flow/specs` 的路径,不带 `.md`。
|
|
6
|
+
|
|
7
|
+
工作流级别来自 `.code-flow/config.json` 的 `workflow`:`hook`(默认)使用 Spec 注入加一份带声明测试的任务文档,不要求 PRD/design/scenarios;`full` 增加 PRD/design/scenarios 流程。设计文档是项目资产,不作为 prompt 输入;只有 Spec 会被注入。
|
|
8
|
+
|
|
9
|
+
标记完成前,用 `task finish <task-document>.md --task TASK-NNN` 执行声明的测试。测试应限定在有意义的 unit/integration 回归;优先一次框架调用,使用框架自己的 workers。
|
|
10
|
+
|
|
11
|
+
archive 应用声明的 Spec Delta 并移动需求;CI 独立执行完整回归和 E2E。使用 `cf-task:prd`、`cf-task:align` 和 `cf-task:plan` 处理文档流程;不要为每个 Task 建一个文件。
|
|
12
|
+
|
|
13
|
+
Hook 诊断永不阻止工作。缺少绑定时要显式提供任务文档 + TASK ID,或重新执行 task start。
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const { spawn, spawnSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
const MAX_INPUT = 1024 * 1024;
|
|
9
|
+
const MAX_OUTPUT = 1024 * 1024;
|
|
10
|
+
const MAX_DIAGNOSTIC = 4096;
|
|
11
|
+
const PROBE_MS = 800;
|
|
12
|
+
const RUN_RESERVE_MS = 1200;
|
|
13
|
+
const CACHE_TTL_MS = 600000;
|
|
14
|
+
const TOTAL_MS = 2000;
|
|
15
|
+
|
|
16
|
+
function emptyPayload(format) {
|
|
17
|
+
return format === 'opencode'
|
|
18
|
+
? { context: '' }
|
|
19
|
+
: { hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: '' } };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function validPayload(format, payload) {
|
|
23
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return false;
|
|
24
|
+
if (format === 'opencode') return typeof payload.context === 'string';
|
|
25
|
+
const hook = payload.hookSpecificOutput;
|
|
26
|
+
return hook !== null && typeof hook === 'object' &&
|
|
27
|
+
hook.hookEventName === 'UserPromptSubmit' && typeof hook.additionalContext === 'string';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizePayload(format, payload) {
|
|
31
|
+
if (!validPayload(format, payload)) return null;
|
|
32
|
+
return format === 'opencode' ? { context: payload.context } : {
|
|
33
|
+
hookSpecificOutput: { hookEventName: 'UserPromptSubmit',
|
|
34
|
+
additionalContext: payload.hookSpecificOutput.additionalContext }
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function emit(format, payload) {
|
|
39
|
+
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readStdin(deadline) {
|
|
43
|
+
return new Promise(resolve => {
|
|
44
|
+
const chunks = [];
|
|
45
|
+
let size = 0, timer;
|
|
46
|
+
const finish = value => {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
process.stdin.removeAllListeners();
|
|
49
|
+
process.stdin.destroy();
|
|
50
|
+
resolve(value);
|
|
51
|
+
};
|
|
52
|
+
timer = setTimeout(() => finish(''), Math.max(0, Math.min(TOTAL_MS, deadline - Date.now())));
|
|
53
|
+
process.stdin.on('data', chunk => {
|
|
54
|
+
size += chunk.length;
|
|
55
|
+
if (size > MAX_INPUT) finish('');
|
|
56
|
+
else chunks.push(chunk);
|
|
57
|
+
});
|
|
58
|
+
process.stdin.on('end', () => finish(Buffer.concat(chunks).toString('utf8')));
|
|
59
|
+
process.stdin.on('error', () => finish(''));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function inside(root, candidate) {
|
|
64
|
+
const relative = path.relative(root, candidate);
|
|
65
|
+
return relative === '' || (!relative.startsWith('..' + path.sep) && relative !== '..');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function safeCandidate(root, candidate) {
|
|
69
|
+
try {
|
|
70
|
+
const real = fs.realpathSync(candidate);
|
|
71
|
+
return fs.statSync(real).isFile() && !inside(root, real);
|
|
72
|
+
} catch (_) { return false; }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function realFile(candidate) {
|
|
76
|
+
try { return fs.realpathSync(candidate); } catch (_) { return null; }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function findOnPath(root) {
|
|
80
|
+
const names = process.platform === 'win32'
|
|
81
|
+
? ['code-flow.cmd', 'code-flow.exe', 'code-flow']
|
|
82
|
+
: ['code-flow'];
|
|
83
|
+
const found = [];
|
|
84
|
+
for (const directory of (process.env.PATH || '').split(path.delimiter)) {
|
|
85
|
+
if (!directory) continue;
|
|
86
|
+
for (const name of names) {
|
|
87
|
+
const candidate = path.join(directory, name);
|
|
88
|
+
if (safeCandidate(root, candidate)) found.push(candidate);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return [...new Set(found)];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function packageIdentity(entry) {
|
|
95
|
+
let directory = path.dirname(entry);
|
|
96
|
+
while (directory !== path.dirname(directory)) {
|
|
97
|
+
const file = path.join(directory, 'package.json');
|
|
98
|
+
if (fs.existsSync(file)) {
|
|
99
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch (_) { return null; }
|
|
100
|
+
}
|
|
101
|
+
directory = path.dirname(directory);
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function validCapability(data, candidate) {
|
|
107
|
+
if (!data || data.package !== '@michaelxwb/code-flow' || data.hook_protocol !== 1) return false;
|
|
108
|
+
if (typeof data.node !== 'string' || !path.isAbsolute(data.node)) return false;
|
|
109
|
+
if (typeof data.entry !== 'string' || !path.isAbsolute(data.entry)) return false;
|
|
110
|
+
const dataEntry = realFile(data.entry), candidateEntry = realFile(candidate);
|
|
111
|
+
if (!dataEntry || !candidateEntry) return false;
|
|
112
|
+
const shim = process.platform === 'win32' || !/\.js$/i.test(candidateEntry);
|
|
113
|
+
if (candidateEntry !== dataEntry && !shim) return false;
|
|
114
|
+
const pkg = packageIdentity(dataEntry);
|
|
115
|
+
if (!pkg || pkg.name !== data.package || pkg.version !== data.version) return false;
|
|
116
|
+
const bin = pkg.bin && typeof pkg.bin === 'object' ? pkg.bin['code-flow'] : pkg.bin;
|
|
117
|
+
if (typeof bin !== 'string' ||
|
|
118
|
+
path.resolve(path.dirname(dataEntry), '..', bin) !== path.resolve(dataEntry)) return false;
|
|
119
|
+
return pkg.codeFlow && pkg.codeFlow.hookProtocol === 1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function probeCommand(candidate, args) {
|
|
123
|
+
if (process.platform === 'win32' && /\.(?:cmd|bat)$/i.test(candidate)) {
|
|
124
|
+
const quote = value => '"' + value.replace(/"/g, '""') + '"';
|
|
125
|
+
return [process.env.ComSpec || 'cmd.exe',
|
|
126
|
+
['/d', '/s', '/c', [candidate, ...args].map(quote).join(' ')]];
|
|
127
|
+
}
|
|
128
|
+
return [candidate, args];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function probe(root, candidate, budget) {
|
|
132
|
+
const [file, args] = probeCommand(candidate, ['hook', 'capabilities', '--json']);
|
|
133
|
+
return new Promise(resolve => {
|
|
134
|
+
const chunks = [];
|
|
135
|
+
let size = 0, settled = false, timer;
|
|
136
|
+
const child = spawn(file, args, {
|
|
137
|
+
cwd: root, stdio: ['ignore', 'pipe', 'pipe'], detached: process.platform !== 'win32',
|
|
138
|
+
windowsHide: true
|
|
139
|
+
});
|
|
140
|
+
const finish = ok => {
|
|
141
|
+
if (settled) return;
|
|
142
|
+
settled = true; clearTimeout(timer);
|
|
143
|
+
try { child.stdout.destroy(); child.stderr.destroy(); } catch (_) { /* best effort */ }
|
|
144
|
+
if (!ok) { resolve(null); return; }
|
|
145
|
+
try {
|
|
146
|
+
const data = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
147
|
+
resolve(validCapability(data, candidate) ? data : null);
|
|
148
|
+
} catch (_) { resolve(null); }
|
|
149
|
+
};
|
|
150
|
+
timer = setTimeout(() => { killTree(child); finish(false); }, Math.max(1, Math.min(PROBE_MS, budget)));
|
|
151
|
+
child.on('error', () => finish(false));
|
|
152
|
+
child.stdout.on('data', chunk => {
|
|
153
|
+
size += chunk.length;
|
|
154
|
+
if (size > MAX_OUTPUT) { killTree(child); finish(false); }
|
|
155
|
+
else chunks.push(chunk);
|
|
156
|
+
});
|
|
157
|
+
child.stderr.on('data', () => {});
|
|
158
|
+
child.on('close', code => finish(code === 0));
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function cacheFile(root) {
|
|
163
|
+
const digest = crypto.createHash('sha256').update(fs.realpathSync(root)).digest('hex');
|
|
164
|
+
return path.join(os.tmpdir(), 'code-flow', digest, 'bridge.json');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function selectionKey(fixed) {
|
|
168
|
+
return crypto.createHash('sha256').update(JSON.stringify({
|
|
169
|
+
path: process.env.PATH || '', node: fixed.node || '', entry: fixed.entry || ''
|
|
170
|
+
})).digest('hex');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function readCache(root, key) {
|
|
174
|
+
let data;
|
|
175
|
+
try { data = JSON.parse(fs.readFileSync(cacheFile(root), 'utf8')); } catch (_) { return null; }
|
|
176
|
+
if (!data || data.key !== key || typeof data.checked_at !== 'number' ||
|
|
177
|
+
Date.now() - data.checked_at > CACHE_TTL_MS ||
|
|
178
|
+
typeof data.entry_mtime !== 'number' || typeof data.candidate !== 'string' ||
|
|
179
|
+
!data.capability || typeof data.capability !== 'object') return null;
|
|
180
|
+
try { if (fs.statSync(data.capability.entry).mtimeMs !== data.entry_mtime) return null; }
|
|
181
|
+
catch (_) { return null; }
|
|
182
|
+
return validCapability(data.capability, data.candidate) ? data.capability : null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function writeCache(root, key, selected) {
|
|
186
|
+
try {
|
|
187
|
+
const { candidate, ...capability } = selected;
|
|
188
|
+
const entry_mtime = fs.statSync(capability.entry).mtimeMs;
|
|
189
|
+
const file = cacheFile(root), temporary = file + '.' + process.pid + '.' + Date.now() + '.tmp';
|
|
190
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
191
|
+
fs.writeFileSync(temporary, JSON.stringify({ key, checked_at: Date.now(), entry_mtime,
|
|
192
|
+
candidate, capability }), { mode: 0o600 });
|
|
193
|
+
fs.renameSync(temporary, file);
|
|
194
|
+
} catch (_) { /* best effort */ }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function probePath(root, fixed, deadline) {
|
|
198
|
+
for (const candidate of findOnPath(root)) {
|
|
199
|
+
const remaining = deadline - Date.now();
|
|
200
|
+
if (remaining <= 0) break;
|
|
201
|
+
const capability = await probe(root, candidate, remaining);
|
|
202
|
+
if (capability) return { ...capability, candidate };
|
|
203
|
+
}
|
|
204
|
+
if (fixed.node && fixed.entry && fs.existsSync(fixed.node) && fs.existsSync(fixed.entry)) {
|
|
205
|
+
const capability = { node: fixed.node, entry: fixed.entry, package: '@michaelxwb/code-flow',
|
|
206
|
+
version: packageIdentity(fixed.entry)?.version, hook_protocol: 1 };
|
|
207
|
+
if (validCapability(capability, fixed.entry)) return { ...capability, candidate: fixed.entry };
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function select(root, fixed, deadline) {
|
|
213
|
+
const key = selectionKey(fixed), cached = readCache(root, key);
|
|
214
|
+
if (cached) return cached;
|
|
215
|
+
const selected = await probePath(root, fixed, deadline);
|
|
216
|
+
if (selected) writeCache(root, key, selected);
|
|
217
|
+
return selected;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function killTree(child) {
|
|
221
|
+
if (!child.pid) return;
|
|
222
|
+
if (process.platform === 'win32') {
|
|
223
|
+
spawnSync('taskkill', ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
try { process.kill(-child.pid, 'SIGKILL'); } catch (_) { try { child.kill('SIGKILL'); } catch (_) {} }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function run(root, selected, format, input, deadline) {
|
|
230
|
+
return new Promise(resolve => {
|
|
231
|
+
const chunks = [];
|
|
232
|
+
let stderr = '', size = 0, settled = false, timer;
|
|
233
|
+
const child = spawn(selected.node, [selected.entry, 'hook', 'prompt', '--format=' + format], {
|
|
234
|
+
cwd: root, stdio: ['pipe', 'pipe', 'pipe'], detached: process.platform !== 'win32', windowsHide: true
|
|
235
|
+
});
|
|
236
|
+
const finish = ok => {
|
|
237
|
+
if (settled) return;
|
|
238
|
+
settled = true; clearTimeout(timer);
|
|
239
|
+
if (!ok) {
|
|
240
|
+
if (stderr.trim()) process.stderr.write(stderr.trim() + '\n');
|
|
241
|
+
resolve(null);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
246
|
+
resolve(normalizePayload(format, parsed));
|
|
247
|
+
} catch (_) { resolve(null); }
|
|
248
|
+
};
|
|
249
|
+
timer = setTimeout(() => { killTree(child); finish(false); },
|
|
250
|
+
Math.max(0, Math.min(TOTAL_MS, deadline - Date.now())));
|
|
251
|
+
child.on('error', error => {
|
|
252
|
+
stderr = (stderr + error.message).slice(-MAX_DIAGNOSTIC); finish(false);
|
|
253
|
+
});
|
|
254
|
+
child.stdout.on('data', chunk => {
|
|
255
|
+
size += chunk.length;
|
|
256
|
+
if (size > MAX_OUTPUT) { killTree(child); finish(false); }
|
|
257
|
+
else chunks.push(chunk);
|
|
258
|
+
});
|
|
259
|
+
child.stderr.setEncoding('utf8');
|
|
260
|
+
child.stderr.on('data', chunk => { stderr = (stderr + chunk).slice(-MAX_DIAGNOSTIC); });
|
|
261
|
+
child.on('close', code => finish(code === 0));
|
|
262
|
+
child.stdin.on('error', () => {});
|
|
263
|
+
child.stdin.end(input);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function parseArgs(argv) {
|
|
268
|
+
const fixed = { node: '', entry: '' }, options = {};
|
|
269
|
+
for (let index = 0; index < argv.length; index++) {
|
|
270
|
+
if (argv[index] === '--format') options.format = argv[++index];
|
|
271
|
+
else if (argv[index] === '--node') fixed.node = argv[++index];
|
|
272
|
+
else if (argv[index] === '--entry') fixed.entry = argv[++index];
|
|
273
|
+
}
|
|
274
|
+
return { fixed, format: options.format || 'claude' };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function main() {
|
|
278
|
+
const { fixed, format } = parseArgs(process.argv.slice(2));
|
|
279
|
+
const root = process.cwd(), deadline = Date.now() + TOTAL_MS;
|
|
280
|
+
const input = await readStdin(deadline);
|
|
281
|
+
if (!input.trim()) return;
|
|
282
|
+
const selected = await select(root, fixed, deadline - RUN_RESERVE_MS);
|
|
283
|
+
if (!selected) {
|
|
284
|
+
process.stderr.write('code-flow bridge: no valid global entry; run doctor\n');
|
|
285
|
+
emit(format, emptyPayload(format));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const output = await run(root, selected, format, input, deadline);
|
|
289
|
+
emit(format, output ?? emptyPayload(format));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
main().catch(error => {
|
|
293
|
+
process.stderr.write('code-flow bridge: ' + error.message + '\n');
|
|
294
|
+
emit('claude', emptyPayload('claude'));
|
|
295
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
rem code-flow:v1
|
|
3
|
+
setlocal
|
|
4
|
+
set "ROOT=%~dp0..\.."
|
|
5
|
+
set "FORMAT=%~1"
|
|
6
|
+
if "%FORMAT%"=="" set "FORMAT=claude"
|
|
7
|
+
set "NODE="
|
|
8
|
+
where node >nul 2>nul && set "NODE=node"
|
|
9
|
+
if not defined NODE set "NODE=__FIXED_NODE__"
|
|
10
|
+
if not exist "%ROOT%\.code-flow\launcher\bridge.cjs" (
|
|
11
|
+
echo code-flow launcher missing; run init again 1>&2
|
|
12
|
+
exit /b 0
|
|
13
|
+
)
|
|
14
|
+
"%NODE%" "%ROOT%\.code-flow\launcher\bridge.cjs" --format "%FORMAT%" --node "__FIXED_NODE__" --entry "__FIXED_ENTRY__"
|
|
15
|
+
exit /b 0
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# code-flow:v1
|
|
2
|
+
param([string]$Format = 'claude')
|
|
3
|
+
$cmd = Join-Path $PSScriptRoot 'hook.cmd'
|
|
4
|
+
if (-not (Test-Path -LiteralPath $cmd)) {
|
|
5
|
+
[Console]::Error.WriteLine('code-flow launcher missing; run init again')
|
|
6
|
+
exit 0
|
|
7
|
+
}
|
|
8
|
+
$shell = if ($env:ComSpec) { $env:ComSpec } else { 'cmd.exe' }
|
|
9
|
+
& $shell /d /c call $cmd $Format
|
|
10
|
+
exit 0
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# code-flow:v1
|
|
3
|
+
root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) || exit 0
|
|
4
|
+
cd "$root" || exit 0
|
|
5
|
+
format=${1:-claude}
|
|
6
|
+
node=$(command -v node 2>/dev/null || true)
|
|
7
|
+
[ -n "$node" ] || node='__FIXED_NODE__'
|
|
8
|
+
bridge="$root/.code-flow/launcher/bridge.cjs"
|
|
9
|
+
if [ ! -f "$bridge" ]; then
|
|
10
|
+
printf 'code-flow launcher missing; run init again\n' >&2
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
if code_flow_output=$("$node" "$bridge" --format "$format" --node '__FIXED_NODE__' --entry '__FIXED_ENTRY__'); then
|
|
14
|
+
if [ -n "$code_flow_output" ]; then
|
|
15
|
+
printf '%s\n' "$code_flow_output"
|
|
16
|
+
fi
|
|
17
|
+
else
|
|
18
|
+
printf 'code-flow launcher failed; run doctor\n' >&2
|
|
19
|
+
fi
|
|
20
|
+
exit 0
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cf-check
|
|
3
|
+
description: Run read-only code-flow health checks.
|
|
4
|
+
---
|
|
5
|
+
<!-- code-flow:v1 -->
|
|
6
|
+
Run:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
code-flow doctor --json
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Report:
|
|
13
|
+
- hook/launcher entry and template status;
|
|
14
|
+
- config and Spec parse errors;
|
|
15
|
+
- Task document errors and dependency problems;
|
|
16
|
+
- worktree run ledger state;
|
|
17
|
+
- the next safe action.
|
|
18
|
+
|
|
19
|
+
`doctor` is read-only. Do not repair or mutate project files from this skill unless the user explicitly asks.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cf-init
|
|
3
|
+
description: Initialize code-flow v1 and organize current project standards.
|
|
4
|
+
---
|
|
5
|
+
<!-- code-flow:v1 -->
|
|
6
|
+
Run the globally installed `code-flow init --platform=<claude|codex|costrict|opencode|all>`.
|
|
7
|
+
|
|
8
|
+
`init` overwrites tool-owned templates under `.code-flow/templates/` and platform skills/hooks. It preserves `.code-flow/specs/`, `.code-flow/tasks/` and an existing `config.json`. If it reports `conflicts`, resolve those paths and re-run; other files are already updated.
|
|
9
|
+
|
|
10
|
+
For an existing nonempty `.code-flow` without a valid v1 config, preserve it outside the project and manually clear earlier managed platform entries first. Do not write conversion or cleanup scripts.
|
|
11
|
+
|
|
12
|
+
Keep `workflow` at `hook` unless the requirement is complex enough to justify the full PRD/design/scenarios flow. `capture.enabled` can record process data under `.code-flow/process/`; it is off by default.
|
|
13
|
+
|
|
14
|
+
After init, read `.code-flow/templates/project-guidelines.md` and write the project-specific guidelines into `CLAUDE.md` / `AGENTS.md` outside the code-flow managed block. Keep the project's identity, architecture, commands and Spec conventions there; do not modify the managed block or overwrite user content. Specs under `.code-flow/specs` are the project standards: read them before writing rules, and update `config.json` `always` / `paths` when adding a Spec.
|
|
15
|
+
|
|
16
|
+
Inspect current code and project instructions. Create a small number of applicable Specs with JSON frontmatter `{"description":"...","next_rule":1}`. Use path identity, a `## Rules` section, stable RULE IDs and indented examples. Set `next_rule` above all allocated numeric IDs. Configure a few shared rules in `always` and concrete file globs in `paths`. Finish with `code-flow doctor --json`.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cf-spec
|
|
3
|
+
description: Inspect current code-flow v1 Specs and apply reviewed Task Spec Deltas.
|
|
4
|
+
---
|
|
5
|
+
<!-- code-flow:v1 -->
|
|
6
|
+
Use `code-flow spec list --json`, `code-flow spec inject --task-file <task-document>.md --task TASK-001 --json`, or `code-flow spec inject --path <file> --full`.
|
|
7
|
+
|
|
8
|
+
Spec identity is its path relative to `.code-flow/specs` without `.md`. Frontmatter is JSON with `description` and a positive `next_rule`. Rules use stable RULE IDs and complete indented explanations/examples.
|
|
9
|
+
|
|
10
|
+
Review applicability and semantic conflicts against current code before writing a Spec Delta. Prefer stable shared constraints over task-specific narration.
|
|
11
|
+
|
|
12
|
+
Delta operations are add/update/remove. Every item needs a stable ID, target Spec path and evidence; update/remove require the existing Rule ref and exact before text. New numeric IDs come from `next_rule`, never from the largest remaining Rule.
|
|
13
|
+
|
|
14
|
+
Use:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
code-flow spec learn --from-task <task-document>.md --task TASK-001 --dry-run --json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Review the exact diff, then run without `--dry-run`. Repeating an already applied payload is safe; changed content requires a new Delta ID. A missing target or before mismatch is an error. Preserve Applied Deltas records so interrupted multi-file learning can resume.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cf-task-align
|
|
3
|
+
description: Produce frontend/backend design and manual acceptance scenarios.
|
|
4
|
+
---
|
|
5
|
+
<!-- code-flow:v1 -->
|
|
6
|
+
Read the requirement PRD or user brief. Read `.code-flow/templates/design-lite.md`, `design-full.md`, or `design-frontend.md` as appropriate. Write:
|
|
7
|
+
|
|
8
|
+
- `.code-flow/tasks/<date>/<requirement>/<requirement>.frontend.design.md`
|
|
9
|
+
- `.code-flow/tasks/<date>/<requirement>/<requirement>.backend.design.md`
|
|
10
|
+
- or one `<requirement>.design.md` for a single domain
|
|
11
|
+
- `.code-flow/tasks/<date>/<requirement>/<requirement>.scenarios.md`
|
|
12
|
+
|
|
13
|
+
Design rules:
|
|
14
|
+
- full-stack requirements keep frontend and backend design separate;
|
|
15
|
+
- scenarios use S-/E-/B- IDs, level, boundary and expected result;
|
|
16
|
+
- scenarios are a manual tester reference, not a manifest or blocking gate;
|
|
17
|
+
- do not run or copy old manifest/design-lint/Stage Gate/E2E commands from templates.
|
|
18
|
+
|
|
19
|
+
When design and scenarios are ready, continue with `cf-task:plan`.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cf-task-archive
|
|
3
|
+
description: Archive a completed requirement after applying Spec Deltas.
|
|
4
|
+
---
|
|
5
|
+
<!-- code-flow:v1 -->
|
|
6
|
+
Read `code-flow task status <requirement-dir> --json`. Every TASK in the one requirement task document must be `done`.
|
|
7
|
+
|
|
8
|
+
Preview and apply:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
code-flow task archive <requirement-dir> --dry-run --json
|
|
12
|
+
code-flow task archive <requirement-dir> --json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Archive:
|
|
16
|
+
- applies each TASK's Spec Delta in the coordinator worktree;
|
|
17
|
+
- moves the whole requirement directory;
|
|
18
|
+
- does not run tests, E2E, or scenarios.
|
|
19
|
+
|
|
20
|
+
Do not run archive inside a worktree. Use the returned `archived_to` path. If Delta application fails, fix the Delta or explicitly use `--no-learn`; do not hand-edit the archive destination.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cf-task-plan
|
|
3
|
+
description: Split a requirement into one multi-TASK document.
|
|
4
|
+
---
|
|
5
|
+
<!-- code-flow:v1 -->
|
|
6
|
+
Read `.code-flow/config.json` `workflow` first:
|
|
7
|
+
|
|
8
|
+
- `hook` (default): write one task document with TASK sections and declared tests; do not require or read PRD/design/scenarios; leave Source/Scenarios empty.
|
|
9
|
+
- `full`: read the requirement's design files and scenarios first; fill Source/Scenarios.
|
|
10
|
+
|
|
11
|
+
Read `.code-flow/templates/task.md`. Write exactly one task document:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
.code-flow/tasks/<date>/<requirement>/<requirement>.md
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Start with JSON frontmatter naming the requirement:
|
|
18
|
+
|
|
19
|
+
```markdown
|
|
20
|
+
---
|
|
21
|
+
{"version":1,"requirement":"<requirement>"}
|
|
22
|
+
---
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Inside it create `## TASK-NNN` sections. Keep each Task small enough to demonstrate a capability. For every Task declare:
|
|
26
|
+
|
|
27
|
+
- Status, Depends, Source, Scenarios, Spec-Refs, Paths;
|
|
28
|
+
- a `### Tests` JSON array of fast unit/integration commands;
|
|
29
|
+
- a `### Spec Delta` JSON array (empty is valid).
|
|
30
|
+
|
|
31
|
+
Do not create one file per Task. Do not create an acceptance manifest. Do not run old scenario/quality/Stage Gate commands. Verify the result with `code-flow task status --json`, then use `cf-task:start`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cf-task-prd
|
|
3
|
+
description: Produce a concise PRD for one requirement.
|
|
4
|
+
---
|
|
5
|
+
<!-- code-flow:v1 -->
|
|
6
|
+
Read `.code-flow/templates/prd.md`. Ask only questions that block scope, users, or acceptance. Write one requirement PRD to `.code-flow/tasks/<date>/<requirement>/<requirement>.prd.md`.
|
|
7
|
+
|
|
8
|
+
Keep it short:
|
|
9
|
+
- problem, goal, users, user stories;
|
|
10
|
+
- functional requirements and boundaries;
|
|
11
|
+
- non-functional constraints only when real;
|
|
12
|
+
- risks and open questions.
|
|
13
|
+
|
|
14
|
+
Do not create Task files here. Do not run old manifest, scenario, design-lint, Stage Gate, or E2E gates. When the PRD is accepted, continue with `cf-task:align`.
|