@pknx/waterfall-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/bin/waterfall.mjs +14 -0
- package/lib/cli/agent/agent-message.ts +71 -0
- package/lib/cli/agent/agent-translators.ts +145 -0
- package/lib/cli/agent/backend-invoke.ts +133 -0
- package/lib/cli/agent/backends.ts +100 -0
- package/lib/cli/agent/global-prompts.ts +55 -0
- package/lib/cli/commands/bug-start.ts +115 -0
- package/lib/cli/commands/comment-add.ts +47 -0
- package/lib/cli/commands/cr-all.ts +18 -0
- package/lib/cli/commands/cr-finish.ts +176 -0
- package/lib/cli/commands/cr-start.ts +105 -0
- package/lib/cli/commands/cr-to-rq.ts +18 -0
- package/lib/cli/commands/export-pdf.ts +193 -0
- package/lib/cli/commands/horizontal/horizontal.ts +232 -0
- package/lib/cli/commands/horizontal-create.ts +34 -0
- package/lib/cli/commands/horizontal-update.ts +32 -0
- package/lib/cli/commands/join-hint.ts +4 -0
- package/lib/cli/commands/registry.ts +59 -0
- package/lib/cli/commands/resolve-operator-hint.ts +120 -0
- package/lib/cli/commands/rq-all.ts +18 -0
- package/lib/cli/commands/rq-to-uc.ts +18 -0
- package/lib/cli/commands/story-close.ts +124 -0
- package/lib/cli/commands/sync-work-items.ts +59 -0
- package/lib/cli/commands/sys-start.ts +96 -0
- package/lib/cli/commands/test-all.ts +18 -0
- package/lib/cli/commands/test-to-story.ts +18 -0
- package/lib/cli/commands/types.ts +33 -0
- package/lib/cli/commands/uc-all.ts +18 -0
- package/lib/cli/commands/uc-to-story.ts +18 -0
- package/lib/cli/commands/uc-to-test.ts +18 -0
- package/lib/cli/comments/item-comments.ts +285 -0
- package/lib/cli/config/dot-waterfall.ts +404 -0
- package/lib/cli/config/global-cli.ts +21 -0
- package/lib/cli/config/sync-work-item-config.ts +34 -0
- package/lib/cli/core/cli-help-spec.ts +833 -0
- package/lib/cli/core/cli-log.ts +124 -0
- package/lib/cli/core/exec-file.ts +8 -0
- package/lib/cli/core/prompt-map.ts +64 -0
- package/lib/cli/core/slug.ts +44 -0
- package/lib/cli/entry.ts +4 -0
- package/lib/cli/export/collect-md.ts +41 -0
- package/lib/cli/export/export-items.ts +104 -0
- package/lib/cli/export/export-pdf-path.ts +88 -0
- package/lib/cli/export/merge-md.ts +37 -0
- package/lib/cli/export/mermaid-run.ts +104 -0
- package/lib/cli/export/pandoc-pdf.ts +90 -0
- package/lib/cli/export/pdf-bundled-worker.mjs +73 -0
- package/lib/cli/export/pdf-bundled.ts +36 -0
- package/lib/cli/git/cr-agent-context.ts +62 -0
- package/lib/cli/git/git-branch-guards.ts +60 -0
- package/lib/cli/git/git-cli-mock.ts +191 -0
- package/lib/cli/git/git-cli.ts +24 -0
- package/lib/cli/main.ts +434 -0
- package/lib/cli/paths.ts +9 -0
- package/lib/cli/project/pom-json.ts +55 -0
- package/lib/cli/spec/spec-init.ts +216 -0
- package/lib/cli/spec/spec-root.ts +93 -0
- package/lib/cli/sync/apply-remote-comments.ts +87 -0
- package/lib/cli/sync/attachment-category.ts +43 -0
- package/lib/cli/sync/diff-work-items.ts +113 -0
- package/lib/cli/sync/materialize-remote-bugs.ts +66 -0
- package/lib/cli/sync/provider-types.ts +43 -0
- package/lib/cli/sync/providers/direct-provider.ts +27 -0
- package/lib/cli/sync/providers/jira-provider.ts +34 -0
- package/lib/cli/sync/providers/registry.ts +26 -0
- package/lib/cli/sync/run-sync-work-items.ts +202 -0
- package/lib/cli/sync/spec-work-items.ts +226 -0
- package/lib/cli/sync/sync-hint-json.ts +163 -0
- package/lib/cli/sync/work-item-meta.ts +117 -0
- package/lib/cli/work-items/infer-bug-sys.ts +147 -0
- package/lib/cli/work-items/remote-bug-import-scaffold.ts +32 -0
- package/lib/cli/work-items/write-bug-to-spec.ts +158 -0
- package/package.json +54 -0
- package/prompts/commands/bug-start.md +46 -0
- package/prompts/commands/cr-finish.md +44 -0
- package/prompts/commands/cr-start.md +65 -0
- package/prompts/commands/cr-to-rq.md +62 -0
- package/prompts/commands/horizontal-create.md +27 -0
- package/prompts/commands/horizontal-update.md +39 -0
- package/prompts/commands/rq-to-uc.md +62 -0
- package/prompts/commands/story-close-all.md +34 -0
- package/prompts/commands/story-close.md +44 -0
- package/prompts/commands/sync-bugs-refine-imports.md +33 -0
- package/prompts/commands/sys-start.md +63 -0
- package/prompts/commands/test-to-story.md +64 -0
- package/prompts/commands/uc-to-story.md +85 -0
- package/prompts/commands/uc-to-test.md +58 -0
- package/prompts/global/before-changing-spec.md +62 -0
- package/prompts/global/content-requirements-vs-use-cases.md +116 -0
- package/prompts/global/cursor-overview.md +31 -0
- package/prompts/global/git-usage.md +46 -0
- package/prompts/global/horizontal-structure.md +75 -0
- package/prompts/global/workflows-index.md +59 -0
- package/prompts/items/bug-document-structure.md +23 -0
- package/prompts/items/cr-document-structure.md +45 -0
- package/prompts/items/rq-theme-document-structure.md +36 -0
- package/prompts/items/story-document-structure.md +49 -0
- package/prompts/items/sys-document-structure.md +36 -0
- package/prompts/items/tst-document-structure.md +55 -0
- package/prompts/items/uc-document-structure.md +38 -0
- package/spec-template/README.md +11 -0
- package/spec-template/full/doc/spec-structure.md +16 -0
- package/spec-template/full/prompts/before-changing-spec.md +7 -0
- package/spec-template/full/prompts/workflows.md +25 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Waterfall CLI contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# @pknx/waterfall-cli
|
|
2
|
+
|
|
3
|
+
Command-line tool for **waterfall-spec** workflows: change requests, stories, agent prompts (Cursor / Cline / Claude Code), sync with work trackers, and PDF export.
|
|
4
|
+
|
|
5
|
+
The executable name on your PATH is **`waterfall`** (not the last segment of the package name).
|
|
6
|
+
|
|
7
|
+
> **Unscoped name:** [`waterfall-cli`](https://www.npmjs.com/package/waterfall-cli) on npm is a different package. This project publishes as **`@pknx/waterfall-cli`**.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- **Node.js** 20 or newer (22 recommended)
|
|
12
|
+
- Git (for branch workflows and `spec init`)
|
|
13
|
+
|
|
14
|
+
Heavy features (diagrams, PDF) pull in optional tooling such as Puppeteer and Pandoc-related packages; first install may take a moment.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @pknx/waterfall-cli
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
waterfall --help
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
One-off run without a global install:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx @pknx/waterfall-cli --help
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
Inside or above your spec repo:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
waterfall help
|
|
40
|
+
waterfall spec init --title "My product" ./my-spec
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Configure defaults with a `.waterfall` file in the project (see `waterfall help` and built-in comments when the file is created).
|
|
44
|
+
|
|
45
|
+
## Publish checklist (maintainers)
|
|
46
|
+
|
|
47
|
+
1. Bump **`version`** in `package.json` (semver). Current line: **0.1.0**.
|
|
48
|
+
2. Update this README if install or behavior changed.
|
|
49
|
+
3. Run **`npm test`** and **`npm run pack:check`**.
|
|
50
|
+
4. **`npm login`** (one-time per machine) then from this directory:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm publish
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Scoped package **`@pknx/waterfall-cli`** uses `"publishConfig": { "access": "public" }` so the first publish does not require passing `--access public` manually.
|
|
57
|
+
|
|
58
|
+
5. Optional: enable **GitHub Actions** publish by adding an npm automation token as repository secret **`NPM_TOKEN`** and using the workflow in `.github/workflows/publish-npm.yml` (on release publish).
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
const entry = path.join(root, "lib/cli/entry.ts");
|
|
8
|
+
// Preserve invoking cwd so ./.waterfall and spec walk-up resolve like other CLIs (not forced to package root).
|
|
9
|
+
const r = spawnSync(
|
|
10
|
+
process.execPath,
|
|
11
|
+
["--import", "tsx", entry, ...process.argv.slice(2)],
|
|
12
|
+
{ stdio: "inherit", cwd: process.cwd(), env: process.env },
|
|
13
|
+
);
|
|
14
|
+
process.exit(r.status === null ? 1 : r.status);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intermediate agent message: paths + metadata only (no backend-specific wire format).
|
|
3
|
+
* Translators in `agent-translators.ts` turn this into strings or argv for each CLI.
|
|
4
|
+
*/
|
|
5
|
+
import { formatCrBranchChangedFilesBlock } from "../git/cr-agent-context";
|
|
6
|
+
import type { WaterfallGlobalPrompt } from "./global-prompts";
|
|
7
|
+
|
|
8
|
+
/** How workflow markdown is supplied to the agent (translator reads file when embed). */
|
|
9
|
+
export type PromptDelivery = "embed" | "reference";
|
|
10
|
+
|
|
11
|
+
export function parsePromptDelivery(
|
|
12
|
+
raw: string | undefined,
|
|
13
|
+
): PromptDelivery | undefined {
|
|
14
|
+
if (!raw?.trim()) return undefined;
|
|
15
|
+
const v = raw.trim().toLowerCase().replace(/_/g, "-");
|
|
16
|
+
if (v === "embed" || v === "inline") return "embed";
|
|
17
|
+
if (v === "reference" || v === "ref" || v === "path") return "reference";
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type WaterfallAgentMessage = {
|
|
22
|
+
/** Spec repository root (agent workspace). */
|
|
23
|
+
specRoot: string;
|
|
24
|
+
/** Primary workflow markdown (absolute path under waterfall-cli `prompts/`). */
|
|
25
|
+
workflowAbsolutePath: string;
|
|
26
|
+
/** `<waterfall-cli>/prompts` — sibling trees: global/, commands/, items/. */
|
|
27
|
+
waterfallPromptsRoot: string;
|
|
28
|
+
promptDelivery: PromptDelivery;
|
|
29
|
+
/** Raw operator hint (may be empty). */
|
|
30
|
+
operatorHint: string;
|
|
31
|
+
/**
|
|
32
|
+
* When on a CR feature branch, git diff path list for scope (separate `user` segment from `operatorHint`).
|
|
33
|
+
*/
|
|
34
|
+
crBranchChangedPathsBlock?: string;
|
|
35
|
+
/** `prompts/global/*.md` to cite in `system` (most important first). */
|
|
36
|
+
globalPrompts: WaterfallGlobalPrompt[];
|
|
37
|
+
orchestrationContext?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type BuildAgentMessageOpts = {
|
|
41
|
+
orchestrationContext?: string;
|
|
42
|
+
promptDelivery?: PromptDelivery;
|
|
43
|
+
waterfallPromptsRoot: string;
|
|
44
|
+
globalPrompts: WaterfallGlobalPrompt[];
|
|
45
|
+
/**
|
|
46
|
+
* When false, omit the CR-branch `develop...HEAD` path list — use when the operator hint
|
|
47
|
+
* already embeds that scope (e.g. `horizontal update`).
|
|
48
|
+
* @default true
|
|
49
|
+
*/
|
|
50
|
+
attachCrBranchPaths?: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function buildWaterfallAgentMessage(
|
|
54
|
+
specRoot: string,
|
|
55
|
+
absPromptPath: string,
|
|
56
|
+
hint: string,
|
|
57
|
+
opts: BuildAgentMessageOpts,
|
|
58
|
+
): WaterfallAgentMessage {
|
|
59
|
+
const attachCr = opts.attachCrBranchPaths !== false;
|
|
60
|
+
const crBlock = attachCr ? formatCrBranchChangedFilesBlock(specRoot) : "";
|
|
61
|
+
return {
|
|
62
|
+
specRoot,
|
|
63
|
+
workflowAbsolutePath: absPromptPath,
|
|
64
|
+
waterfallPromptsRoot: opts.waterfallPromptsRoot,
|
|
65
|
+
promptDelivery: opts.promptDelivery ?? "embed",
|
|
66
|
+
operatorHint: hint,
|
|
67
|
+
crBranchChangedPathsBlock: crBlock.trim() ? crBlock : undefined,
|
|
68
|
+
globalPrompts: [...opts.globalPrompts],
|
|
69
|
+
orchestrationContext: opts.orchestrationContext,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translators: WaterfallAgentMessage → wire format for a specific agent CLI.
|
|
3
|
+
* Each backend has its own entry point (`translateCursor`, `translateClaudeCode`, …)
|
|
4
|
+
* so you can change one tool’s payload without touching the others.
|
|
5
|
+
*/
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import type { BackendId } from "./backends";
|
|
9
|
+
import type { PromptDelivery, WaterfallAgentMessage } from "./agent-message";
|
|
10
|
+
import {
|
|
11
|
+
GLOBAL_PROMPT_DESCRIPTION,
|
|
12
|
+
type WaterfallGlobalPrompt,
|
|
13
|
+
} from "./global-prompts";
|
|
14
|
+
|
|
15
|
+
/** One piece of system or user content: inline text and/or a file to read (absolute path). */
|
|
16
|
+
export type AgentPromptSegment =
|
|
17
|
+
| { kind: "text"; text: string }
|
|
18
|
+
| { kind: "file"; path: string; description: string };
|
|
19
|
+
|
|
20
|
+
/** Structured payload before a backend flattens or forwards it (e.g. JSON on the CLI argv). */
|
|
21
|
+
export type WaterfallAgentPayload = {
|
|
22
|
+
system: AgentPromptSegment[];
|
|
23
|
+
user: AgentPromptSegment[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type AgentTranslator = (msg: WaterfallAgentMessage) => string;
|
|
27
|
+
|
|
28
|
+
function globalFile(
|
|
29
|
+
promptsRoot: string,
|
|
30
|
+
name: string,
|
|
31
|
+
description: string,
|
|
32
|
+
): AgentPromptSegment {
|
|
33
|
+
return {
|
|
34
|
+
kind: "file",
|
|
35
|
+
path: path.join(promptsRoot, "global", name),
|
|
36
|
+
description,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** `prompts/global/*` from `msg.globalPrompts` order, then the spec repo’s CURSOR.md. */
|
|
41
|
+
function globalPromptSegments(msg: WaterfallAgentMessage): AgentPromptSegment[] {
|
|
42
|
+
const fromCommands: AgentPromptSegment[] = msg.globalPrompts.map(
|
|
43
|
+
(gp: WaterfallGlobalPrompt) =>
|
|
44
|
+
globalFile(
|
|
45
|
+
msg.waterfallPromptsRoot,
|
|
46
|
+
gp,
|
|
47
|
+
GLOBAL_PROMPT_DESCRIPTION[gp],
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
const specLaw: AgentPromptSegment = {
|
|
51
|
+
kind: "file",
|
|
52
|
+
path: path.join(msg.specRoot, "CURSOR.md"),
|
|
53
|
+
description:
|
|
54
|
+
"This repository: layout, branching, artifact graph — normative spec law",
|
|
55
|
+
};
|
|
56
|
+
return [...fromCommands, specLaw];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildWaterfallAgentPayload(msg: WaterfallAgentMessage): WaterfallAgentPayload {
|
|
60
|
+
const delivery: PromptDelivery = msg.promptDelivery;
|
|
61
|
+
const hint = msg.operatorHint;
|
|
62
|
+
const hintBlock = hint.trim() ? hint : "(none)";
|
|
63
|
+
|
|
64
|
+
const system: AgentPromptSegment[] = [
|
|
65
|
+
{
|
|
66
|
+
kind: "text",
|
|
67
|
+
text: `Repository root (spec workspace): ${msg.specRoot}`,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
kind: "file",
|
|
71
|
+
path: msg.workflowAbsolutePath,
|
|
72
|
+
description:
|
|
73
|
+
"Primary workflow for this CLI command — execute its steps and scope",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
kind: "text",
|
|
77
|
+
text: `Waterfall CLI prompts root: ${msg.waterfallPromptsRoot}`,
|
|
78
|
+
},
|
|
79
|
+
...globalPromptSegments(msg),
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
if (msg.orchestrationContext) {
|
|
83
|
+
system.push({
|
|
84
|
+
kind: "text",
|
|
85
|
+
text: msg.orchestrationContext,
|
|
86
|
+
});
|
|
87
|
+
system.push({
|
|
88
|
+
kind: "text",
|
|
89
|
+
text: 'Follow the "CLI orchestration" section in the primary workflow markdown.',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const user: AgentPromptSegment[] = [{ kind: "text", text: hintBlock }];
|
|
94
|
+
if (msg.crBranchChangedPathsBlock?.trim()) {
|
|
95
|
+
user.push({
|
|
96
|
+
kind: "text",
|
|
97
|
+
text: msg.crBranchChangedPathsBlock.trimEnd(),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (delivery !== "reference") {
|
|
102
|
+
const body = fs.readFileSync(msg.workflowAbsolutePath, "utf8");
|
|
103
|
+
user.push({ kind: "text", text: body });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { system, user };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* JSON wire string with structured `system` / `user` segment arrays.
|
|
111
|
+
* Wire-format framing (if any) belongs in backend translators; this payload stays data-oriented.
|
|
112
|
+
*/
|
|
113
|
+
function jsonStructuredWire(msg: WaterfallAgentMessage): string {
|
|
114
|
+
const payload = buildWaterfallAgentPayload(msg);
|
|
115
|
+
return `${JSON.stringify(payload, null, 2)}\n`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Cursor CLI (`agent` / `cursor -p`): one positional prompt after flags. */
|
|
119
|
+
export function translateCursor(msg: WaterfallAgentMessage): string {
|
|
120
|
+
return jsonStructuredWire(msg);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Claude Code CLI (`claude -p`): one positional prompt; swap implementation for `--input-format` etc. */
|
|
124
|
+
export function translateClaudeCode(msg: WaterfallAgentMessage): string {
|
|
125
|
+
return jsonStructuredWire(msg);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Cline CLI (`cline -y`): one positional prompt after optional `-m`. */
|
|
129
|
+
export function translateClineOllama(msg: WaterfallAgentMessage): string {
|
|
130
|
+
return jsonStructuredWire(msg);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const agentTranslators: Record<BackendId, AgentTranslator> = {
|
|
134
|
+
cursor: translateCursor,
|
|
135
|
+
"claude-code": translateClaudeCode,
|
|
136
|
+
"cline-ollama": translateClineOllama,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export function translateAgentPrompt(
|
|
140
|
+
backend: BackendId,
|
|
141
|
+
msg: WaterfallAgentMessage,
|
|
142
|
+
): string {
|
|
143
|
+
const t = agentTranslators[backend];
|
|
144
|
+
return t(msg);
|
|
145
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CR-005: spawn the selected agent backend after building an intermediate message
|
|
3
|
+
* and running it through a backend-specific translator.
|
|
4
|
+
*/
|
|
5
|
+
import * as exec from "../core/exec-file";
|
|
6
|
+
import type { BackendId } from "./backends";
|
|
7
|
+
import * as backends from "./backends";
|
|
8
|
+
import {
|
|
9
|
+
buildWaterfallAgentMessage,
|
|
10
|
+
type PromptDelivery,
|
|
11
|
+
} from "./agent-message";
|
|
12
|
+
import type { WaterfallGlobalPrompt } from "./global-prompts";
|
|
13
|
+
import type { LogLevel } from "../core/cli-log";
|
|
14
|
+
import { traceTranslatedPayloadForBackend } from "../core/cli-log";
|
|
15
|
+
import { translateAgentPrompt } from "./agent-translators";
|
|
16
|
+
|
|
17
|
+
export type { PromptDelivery, WaterfallAgentMessage } from "./agent-message";
|
|
18
|
+
export { parsePromptDelivery } from "./agent-message";
|
|
19
|
+
|
|
20
|
+
export type {
|
|
21
|
+
AgentPromptSegment,
|
|
22
|
+
WaterfallAgentPayload,
|
|
23
|
+
} from "./agent-translators";
|
|
24
|
+
|
|
25
|
+
export type InvokeAgentBackendOpts = {
|
|
26
|
+
orchestrationContext?: string;
|
|
27
|
+
promptDelivery?: PromptDelivery;
|
|
28
|
+
waterfallPromptsRoot: string;
|
|
29
|
+
globalPrompts: WaterfallGlobalPrompt[];
|
|
30
|
+
attachCrBranchPaths?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Tracing: defaults to `normal`. Cursor also echoes the full JSON wire to stdout when not `silent`.
|
|
33
|
+
* Other backends echo only when `verbose`.
|
|
34
|
+
*/
|
|
35
|
+
logLevel?: LogLevel;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Build the same JSON string passed to the backend as the positional prompt. */
|
|
39
|
+
export function buildAgentWireMessage(
|
|
40
|
+
backend: BackendId,
|
|
41
|
+
specRoot: string,
|
|
42
|
+
absPromptPath: string,
|
|
43
|
+
hint: string,
|
|
44
|
+
opts: InvokeAgentBackendOpts,
|
|
45
|
+
): string {
|
|
46
|
+
const messageModel = buildWaterfallAgentMessage(specRoot, absPromptPath, hint, {
|
|
47
|
+
orchestrationContext: opts.orchestrationContext,
|
|
48
|
+
promptDelivery: opts.promptDelivery,
|
|
49
|
+
waterfallPromptsRoot: opts.waterfallPromptsRoot,
|
|
50
|
+
globalPrompts: opts.globalPrompts,
|
|
51
|
+
attachCrBranchPaths: opts.attachCrBranchPaths,
|
|
52
|
+
});
|
|
53
|
+
return translateAgentPrompt(backend, messageModel);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Run the backend process with stdio inherited (operator sees streaming output).
|
|
58
|
+
* Caller must ensure the prompt file exists and backend is on PATH.
|
|
59
|
+
*/
|
|
60
|
+
export function invokeAgentBackend(
|
|
61
|
+
backend: BackendId,
|
|
62
|
+
specRoot: string,
|
|
63
|
+
absPromptPath: string,
|
|
64
|
+
hint: string,
|
|
65
|
+
opts: InvokeAgentBackendOpts,
|
|
66
|
+
): void {
|
|
67
|
+
const message = buildAgentWireMessage(
|
|
68
|
+
backend,
|
|
69
|
+
specRoot,
|
|
70
|
+
absPromptPath,
|
|
71
|
+
hint,
|
|
72
|
+
opts,
|
|
73
|
+
);
|
|
74
|
+
traceTranslatedPayloadForBackend(
|
|
75
|
+
backend,
|
|
76
|
+
opts.logLevel ?? "normal",
|
|
77
|
+
message,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
switch (backend) {
|
|
81
|
+
case "cursor": {
|
|
82
|
+
const bin = backends.resolveCursorCliBinary();
|
|
83
|
+
// Flags must come before the positional prompt (otherwise --output-format can be misparsed or swallowed).
|
|
84
|
+
// --approve-mcps: headless MCP consent; --sandbox disabled: avoid sandbox waits on git/file tools.
|
|
85
|
+
exec.execFileSync(
|
|
86
|
+
bin,
|
|
87
|
+
[
|
|
88
|
+
"-p",
|
|
89
|
+
"--force",
|
|
90
|
+
"--approve-mcps",
|
|
91
|
+
"--sandbox",
|
|
92
|
+
"disabled",
|
|
93
|
+
"--output-format",
|
|
94
|
+
"text",
|
|
95
|
+
"--workspace",
|
|
96
|
+
specRoot,
|
|
97
|
+
message,
|
|
98
|
+
],
|
|
99
|
+
{
|
|
100
|
+
cwd: specRoot,
|
|
101
|
+
stdio: "inherit",
|
|
102
|
+
env: process.env,
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "claude-code": {
|
|
108
|
+
exec.execFileSync("claude", ["-p", message, "--output-format", "text"], {
|
|
109
|
+
cwd: specRoot,
|
|
110
|
+
stdio: "inherit",
|
|
111
|
+
env: process.env,
|
|
112
|
+
});
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case "cline-ollama": {
|
|
116
|
+
// Cline is the agent; Ollama is the in-Cline provider (CR-005). -y: headless auto-approve.
|
|
117
|
+
// Large argv payloads must still pass -m: Cline may not apply saved model id to every task.
|
|
118
|
+
const model = process.env.WATERFALL_CLINE_MODEL?.trim();
|
|
119
|
+
const argv = model ? ["-y", "-m", model, message] : ["-y", message];
|
|
120
|
+
exec.execFileSync("cline", argv, {
|
|
121
|
+
cwd: specRoot,
|
|
122
|
+
stdio: "inherit",
|
|
123
|
+
env: process.env,
|
|
124
|
+
});
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
default: {
|
|
128
|
+
const _exhaustive: never = backend;
|
|
129
|
+
void _exhaustive;
|
|
130
|
+
throw new Error("Unknown backend");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import type { PromptDelivery } from "./agent-message";
|
|
3
|
+
|
|
4
|
+
export type BackendId = "cline-ollama" | "cursor" | "claude-code";
|
|
5
|
+
|
|
6
|
+
/** Cursor ships the `agent` CLI; some installs expose `cursor` instead (CR-005). */
|
|
7
|
+
export function resolveCursorCliBinary(): string {
|
|
8
|
+
try {
|
|
9
|
+
execSync("command -v agent", { stdio: "ignore" });
|
|
10
|
+
return "agent";
|
|
11
|
+
} catch {
|
|
12
|
+
try {
|
|
13
|
+
execSync("command -v cursor", { stdio: "ignore" });
|
|
14
|
+
return "cursor";
|
|
15
|
+
} catch {
|
|
16
|
+
throw new Error(
|
|
17
|
+
"Cursor backend: neither `agent` nor `cursor` found on PATH. Install Cursor CLI: https://cursor.com/docs/cli/overview",
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type BackendCheckOptions = {
|
|
24
|
+
/** When true, skip PATH probes (unit tests). */
|
|
25
|
+
skipPathCheck?: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function parseBackendId(s: string | undefined): BackendId {
|
|
29
|
+
const v = (s ?? "cline-ollama").toLowerCase().replace(/_/g, "-");
|
|
30
|
+
if (v === "cline" || v === "cline-ollama") return "cline-ollama";
|
|
31
|
+
if (v === "ollama") {
|
|
32
|
+
throw new Error(
|
|
33
|
+
'Backend id "ollama" is invalid. Use --backend cline-ollama (Cline CLI). Configure Ollama as a provider inside Cline (`cline auth`), not as a separate waterfall backend.',
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
if (v === "cursor" || v === "cursor-cli") return "cursor";
|
|
37
|
+
if (v === "claude" || v === "claude-code" || v === "anthropic")
|
|
38
|
+
return "claude-code";
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Unknown backend "${s}". Use: cline-ollama | cursor | claude-code`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Fail fast when the underlying tool is not on PATH (unless skipPathCheck). */
|
|
45
|
+
export function checkBackendAvailable(
|
|
46
|
+
id: BackendId,
|
|
47
|
+
opts: BackendCheckOptions = {},
|
|
48
|
+
): void {
|
|
49
|
+
if (opts.skipPathCheck) return;
|
|
50
|
+
switch (id) {
|
|
51
|
+
case "cline-ollama":
|
|
52
|
+
try {
|
|
53
|
+
execSync("command -v cline", { stdio: "ignore" });
|
|
54
|
+
} catch {
|
|
55
|
+
throw new Error(
|
|
56
|
+
"Backend cline-ollama: `cline` CLI not on PATH. Install: npm install -g cline — https://docs.cline.bot/cline-cli/getting-started (configure Ollama or other models via `cline auth`).",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
case "cursor":
|
|
61
|
+
try {
|
|
62
|
+
resolveCursorCliBinary();
|
|
63
|
+
} catch (e) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
(e as Error).message ||
|
|
66
|
+
"Backend cursor: install Cursor CLI (`agent` or `cursor` on PATH).",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
case "claude-code":
|
|
71
|
+
try {
|
|
72
|
+
execSync("command -v claude", { stdio: "ignore" });
|
|
73
|
+
} catch {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"Backend claude-code: `claude` CLI not found on PATH. Install Claude Code or use another --backend.",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function describeBackendInvocation(
|
|
83
|
+
id: BackendId,
|
|
84
|
+
specRoot: string,
|
|
85
|
+
promptRelativePath: string,
|
|
86
|
+
hint: string,
|
|
87
|
+
promptDelivery: PromptDelivery = "embed",
|
|
88
|
+
): string {
|
|
89
|
+
const promptPath = promptRelativePath;
|
|
90
|
+
const delivery =
|
|
91
|
+
promptDelivery === "reference" ? " prompt_delivery=reference" : "";
|
|
92
|
+
switch (id) {
|
|
93
|
+
case "cline-ollama":
|
|
94
|
+
return `[cline-ollama] specRoot=${specRoot} prompt=${promptPath}${delivery} hint=${JSON.stringify(hint)} (invoke: cline -y … headless; Ollama = provider in Cline, not raw ollama run)`;
|
|
95
|
+
case "cursor":
|
|
96
|
+
return `[cursor] specRoot=${specRoot} prompt=${promptPath}${delivery} hint=${JSON.stringify(hint)} (invoke: agent -p --force --approve-mcps --sandbox disabled --output-format text --workspace <specRoot> <payload>)`;
|
|
97
|
+
case "claude-code":
|
|
98
|
+
return `[claude-code] specRoot=${specRoot} prompt=${promptPath}${delivery} hint=${JSON.stringify(hint)} (invoke: claude -p … --output-format text)`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Files under `waterfall-cli/prompts/global/`. Each agent run lists an ordered subset
|
|
3
|
+
* (most important first) for `system` file segments.
|
|
4
|
+
*/
|
|
5
|
+
export enum WaterfallGlobalPrompt {
|
|
6
|
+
BeforeChangingSpec = "before-changing-spec.md",
|
|
7
|
+
WorkflowsIndex = "workflows-index.md",
|
|
8
|
+
CursorOverview = "cursor-overview.md",
|
|
9
|
+
GitUsage = "git-usage.md",
|
|
10
|
+
ContentRequirementsVsUseCases = "content-requirements-vs-use-cases.md",
|
|
11
|
+
HorizontalStructure = "horizontal-structure.md",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Short labels for `kind: "file"` segments (agent reads path on disk). */
|
|
15
|
+
export const GLOBAL_PROMPT_DESCRIPTION: Record<WaterfallGlobalPrompt, string> = {
|
|
16
|
+
[WaterfallGlobalPrompt.BeforeChangingSpec]:
|
|
17
|
+
"Cross-artifact rules, operator hint policy (interpret for judgment; never paste verbatim into Summary/Purpose/titles), prompts are not spec filler",
|
|
18
|
+
[WaterfallGlobalPrompt.WorkflowsIndex]:
|
|
19
|
+
"Lifecycle edges, CLI wire embed vs reference delivery, operator-hint behavior, command → prompt map",
|
|
20
|
+
[WaterfallGlobalPrompt.CursorOverview]:
|
|
21
|
+
"How CURSOR.md relates to prompts and the spec tree",
|
|
22
|
+
[WaterfallGlobalPrompt.GitUsage]:
|
|
23
|
+
"Git baseline develop, diffs, and review habits for spec work",
|
|
24
|
+
[WaterfallGlobalPrompt.ContentRequirementsVsUseCases]:
|
|
25
|
+
"Requirements vs use cases — content shape and layering",
|
|
26
|
+
[WaterfallGlobalPrompt.HorizontalStructure]:
|
|
27
|
+
"Horizontal (HOR) kinds, sections, and aggregation rules",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Default for lifecycle edges and `* all` orchestration: edit rules, RQ/UC content
|
|
32
|
+
* shape, workflow map, then CURSOR’s role.
|
|
33
|
+
*/
|
|
34
|
+
export const AGENT_GLOBAL_PROMPTS_LIFECYCLE: WaterfallGlobalPrompt[] = [
|
|
35
|
+
WaterfallGlobalPrompt.BeforeChangingSpec,
|
|
36
|
+
WaterfallGlobalPrompt.ContentRequirementsVsUseCases,
|
|
37
|
+
WaterfallGlobalPrompt.WorkflowsIndex,
|
|
38
|
+
WaterfallGlobalPrompt.CursorOverview,
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/** `horizontal update`: add HOR shape after workflow index. */
|
|
42
|
+
export const AGENT_GLOBAL_PROMPTS_HORIZONTAL_UPDATE: WaterfallGlobalPrompt[] = [
|
|
43
|
+
WaterfallGlobalPrompt.BeforeChangingSpec,
|
|
44
|
+
WaterfallGlobalPrompt.WorkflowsIndex,
|
|
45
|
+
WaterfallGlobalPrompt.HorizontalStructure,
|
|
46
|
+
WaterfallGlobalPrompt.CursorOverview,
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/** `horizontal create`: refine scaffold Purpose (Aggregated definitions follow `horizontal update`). */
|
|
50
|
+
export const AGENT_GLOBAL_PROMPTS_HORIZONTAL_CREATE: WaterfallGlobalPrompt[] = [
|
|
51
|
+
WaterfallGlobalPrompt.BeforeChangingSpec,
|
|
52
|
+
WaterfallGlobalPrompt.WorkflowsIndex,
|
|
53
|
+
WaterfallGlobalPrompt.HorizontalStructure,
|
|
54
|
+
WaterfallGlobalPrompt.CursorOverview,
|
|
55
|
+
];
|