ralph-flow-pi 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.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +428 -0
  3. package/dist/cli.d.ts +9 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +56 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/prompts.d.ts +25 -0
  8. package/dist/commands/prompts.d.ts.map +1 -0
  9. package/dist/commands/prompts.js +249 -0
  10. package/dist/commands/prompts.js.map +1 -0
  11. package/dist/commands/tools.d.ts +47 -0
  12. package/dist/commands/tools.d.ts.map +1 -0
  13. package/dist/commands/tools.js +633 -0
  14. package/dist/commands/tools.js.map +1 -0
  15. package/dist/engine/check-bash.d.ts +121 -0
  16. package/dist/engine/check-bash.d.ts.map +1 -0
  17. package/dist/engine/check-bash.js +373 -0
  18. package/dist/engine/check-bash.js.map +1 -0
  19. package/dist/engine/check.d.ts +47 -0
  20. package/dist/engine/check.d.ts.map +1 -0
  21. package/dist/engine/check.js +298 -0
  22. package/dist/engine/check.js.map +1 -0
  23. package/dist/engine/core.d.ts +153 -0
  24. package/dist/engine/core.d.ts.map +1 -0
  25. package/dist/engine/core.js +1984 -0
  26. package/dist/engine/core.js.map +1 -0
  27. package/dist/engine/lock.d.ts +27 -0
  28. package/dist/engine/lock.d.ts.map +1 -0
  29. package/dist/engine/lock.js +121 -0
  30. package/dist/engine/lock.js.map +1 -0
  31. package/dist/engine/runner.d.ts +108 -0
  32. package/dist/engine/runner.d.ts.map +1 -0
  33. package/dist/engine/runner.js +510 -0
  34. package/dist/engine/runner.js.map +1 -0
  35. package/dist/engine/skills.d.ts +53 -0
  36. package/dist/engine/skills.d.ts.map +1 -0
  37. package/dist/engine/skills.js +109 -0
  38. package/dist/engine/skills.js.map +1 -0
  39. package/dist/engine/step-tools.d.ts +22 -0
  40. package/dist/engine/step-tools.d.ts.map +1 -0
  41. package/dist/engine/step-tools.js +45 -0
  42. package/dist/engine/step-tools.js.map +1 -0
  43. package/dist/engine/types.d.ts +136 -0
  44. package/dist/engine/types.d.ts.map +1 -0
  45. package/dist/engine/types.js +20 -0
  46. package/dist/engine/types.js.map +1 -0
  47. package/dist/headless.d.ts +57 -0
  48. package/dist/headless.d.ts.map +1 -0
  49. package/dist/headless.js +318 -0
  50. package/dist/headless.js.map +1 -0
  51. package/dist/pi/adapter.d.ts +135 -0
  52. package/dist/pi/adapter.d.ts.map +1 -0
  53. package/dist/pi/adapter.js +231 -0
  54. package/dist/pi/adapter.js.map +1 -0
  55. package/dist/pi/interactive.d.ts +28 -0
  56. package/dist/pi/interactive.d.ts.map +1 -0
  57. package/dist/pi/interactive.js +58 -0
  58. package/dist/pi/interactive.js.map +1 -0
  59. package/dist/pi/tui.d.ts +12 -0
  60. package/dist/pi/tui.d.ts.map +1 -0
  61. package/dist/pi/tui.js +12 -0
  62. package/dist/pi/tui.js.map +1 -0
  63. package/dist/tui/app.d.ts +25 -0
  64. package/dist/tui/app.d.ts.map +1 -0
  65. package/dist/tui/app.js +47 -0
  66. package/dist/tui/app.js.map +1 -0
  67. package/dist/tui/embed.d.ts +42 -0
  68. package/dist/tui/embed.d.ts.map +1 -0
  69. package/dist/tui/embed.js +38 -0
  70. package/dist/tui/embed.js.map +1 -0
  71. package/dist/tui/extension.d.ts +88 -0
  72. package/dist/tui/extension.d.ts.map +1 -0
  73. package/dist/tui/extension.js +114 -0
  74. package/dist/tui/extension.js.map +1 -0
  75. package/dist/tui/history-editor.d.ts +38 -0
  76. package/dist/tui/history-editor.d.ts.map +1 -0
  77. package/dist/tui/history-editor.js +55 -0
  78. package/dist/tui/history-editor.js.map +1 -0
  79. package/dist/tui/launcher.d.ts +24 -0
  80. package/dist/tui/launcher.d.ts.map +1 -0
  81. package/dist/tui/launcher.js +97 -0
  82. package/dist/tui/launcher.js.map +1 -0
  83. package/dist/tui/render.d.ts +87 -0
  84. package/dist/tui/render.d.ts.map +1 -0
  85. package/dist/tui/render.js +266 -0
  86. package/dist/tui/render.js.map +1 -0
  87. package/dist/tui/run-app.d.ts +49 -0
  88. package/dist/tui/run-app.d.ts.map +1 -0
  89. package/dist/tui/run-app.js +317 -0
  90. package/dist/tui/run-app.js.map +1 -0
  91. package/dist/tui/run-model.d.ts +162 -0
  92. package/dist/tui/run-model.d.ts.map +1 -0
  93. package/dist/tui/run-model.js +280 -0
  94. package/dist/tui/run-model.js.map +1 -0
  95. package/dist/tui/run-view.d.ts +71 -0
  96. package/dist/tui/run-view.d.ts.map +1 -0
  97. package/dist/tui/run-view.js +167 -0
  98. package/dist/tui/run-view.js.map +1 -0
  99. package/dist/tui/welcome-header.d.ts +40 -0
  100. package/dist/tui/welcome-header.d.ts.map +1 -0
  101. package/dist/tui/welcome-header.js +90 -0
  102. package/dist/tui/welcome-header.js.map +1 -0
  103. package/package.json +55 -0
  104. package/skills/c-to-rust-audit/SKILL.md +67 -0
  105. package/skills/c-to-rust-implement/SKILL.md +151 -0
  106. package/skills/c-to-rust-implement/references/c-to-rust-patterns.md +86 -0
  107. package/skills/c-to-rust-implement/references/conditional-compilation.md +47 -0
  108. package/skills/c-to-rust-implement/references/crate-reference.md +15 -0
  109. package/skills/c-to-rust-implement/references/error-strategies.md +80 -0
  110. package/skills/c-to-rust-implement/references/inline-asm.md +37 -0
  111. package/skills/c-to-rust-plan/SKILL.md +166 -0
  112. package/skills/c-to-rust-plan/references/detection-commands.md +66 -0
  113. package/skills/c-to-rust-test-gen/SKILL.md +130 -0
  114. package/skills/c-to-rust-test-gen/references/proptest-patterns.md +81 -0
  115. package/skills/c-to-rust-test-gen/references/test-porting.md +56 -0
  116. package/skills/c-to-rust-validate/SKILL.md +121 -0
  117. package/skills/everything2rust-audit/SKILL.md +69 -0
  118. package/skills/everything2rust-design/SKILL.md +121 -0
  119. package/skills/everything2rust-design/references/domain-playbooks.md +68 -0
  120. package/skills/everything2rust-design/references/paradigm-map.md +99 -0
  121. package/skills/everything2rust-implement/SKILL.md +101 -0
  122. package/skills/everything2rust-spec/SKILL.md +86 -0
  123. package/skills/everything2rust-spec/references/oracle-strategies.md +96 -0
  124. package/skills/everything2rust-survey/SKILL.md +99 -0
  125. package/skills/everything2rust-test-gen/SKILL.md +68 -0
  126. package/skills/everything2rust-test-gen/references/harness-patterns.md +186 -0
  127. package/skills/everything2rust-validate/SKILL.md +85 -0
  128. package/workflows/c-to-rust.yaml +202 -0
  129. package/workflows/everything2rust.yaml +259 -0
  130. package/workflows/loop.yaml +68 -0
  131. package/workflows/spec.yaml +183 -0
@@ -0,0 +1,167 @@
1
+ /**
2
+ * The run view — a full-screen pi-tui app that shows one workflow running.
3
+ *
4
+ * This is the payoff of Direction A. No supervisor LLM sits between the user and
5
+ * the engine: the runner drives the workflow autonomously, its structured events
6
+ * feed the pure run-model, and this file renders that model and turns keystrokes
7
+ * into engine actions. The only AI sessions are the ones actually doing the work
8
+ * (each DO step) and judging it (each CHECK) — and you watch them live.
9
+ *
10
+ * The interaction model is one persistent input, always available except during
11
+ * CHECK (read-only by design) and once the run has ended. There is no separate
12
+ * modal hotkey layer for gates/pauses — that would be a second interaction
13
+ * language bolted onto the first, and the whole point of this view is to feel
14
+ * like talking to the work the way you would in Claude Code or opencode: plain
15
+ * text always talks to the work (steer the live DO session / revise a gate /
16
+ * leave a note for the next retry), `/`-commands always control the state
17
+ * machine (`/ralphflow-continue`, `/ralphflow-cancel` — the same names the chat
18
+ * surface already uses for the same actions, see commands/prompts.ts).
19
+ *
20
+ * All the visual logic is in render.ts (pure, fully tested). This file is the
21
+ * thin pi-tui shell: a Component that emits render.ts's lines, plus the input
22
+ * box and the dispatcher that turns submitted text into engine actions.
23
+ */
24
+ import { isTerminal } from "./run-model.js";
25
+ import { renderScreen } from "./render.js";
26
+ import { Input, matchesKey, Key, getSelectListTheme } from "../pi/tui.js";
27
+ /** A Component that renders the run model; `scrollBack` is owned by the view. */
28
+ class ScreenComponent {
29
+ model;
30
+ scrollBack = 0;
31
+ constructor(model) {
32
+ this.model = model;
33
+ }
34
+ render(width) {
35
+ return renderScreen(this.model(), width, this.scrollBack);
36
+ }
37
+ invalidate() { }
38
+ }
39
+ /** Whether the persistent input should be mounted for the current model state. */
40
+ function inputActive(m) {
41
+ return m.activePhase !== "check" && !isTerminal(m);
42
+ }
43
+ function isCommand(text, names) {
44
+ const head = text.split(/\s+/, 1)[0]?.toLowerCase() ?? "";
45
+ return names.includes(head);
46
+ }
47
+ /**
48
+ * The single dispatch rule for whatever the user submits. Pure function of
49
+ * (text, model) → an action call, so it's unit-testable without a terminal.
50
+ */
51
+ export function dispatchInput(rawText, m, actions) {
52
+ const text = rawText.trim();
53
+ if (!text)
54
+ return;
55
+ if (isCommand(text, ["/ralphflow-continue", "/continue"])) {
56
+ if (m.status === "gate")
57
+ actions.approveGate();
58
+ else if (m.status === "paused" || m.status === "stalled")
59
+ actions.resume();
60
+ return;
61
+ }
62
+ if (isCommand(text, ["/ralphflow-cancel", "/cancel"])) {
63
+ if (m.status !== "completed" && m.status !== "cancelled")
64
+ actions.cancel();
65
+ return;
66
+ }
67
+ if (text.startsWith("/"))
68
+ return; // unrecognized command — ignore rather than misfire
69
+ if (m.status === "running" && m.activePhase === "do") {
70
+ actions.sendMessage(text);
71
+ return;
72
+ }
73
+ if (m.status === "gate") {
74
+ actions.reviseGate(text);
75
+ return;
76
+ }
77
+ if (m.status === "paused" || m.status === "stalled") {
78
+ actions.attachNote(text);
79
+ return;
80
+ }
81
+ // running+check, completed, cancelled: no input is mounted for these states
82
+ // in practice (see inputActive), but stay a no-op if reached anyway.
83
+ }
84
+ /**
85
+ * Run the full-screen view for one instance until it finishes or the user quits.
86
+ * Resolves when the TUI is torn down. `getModel` returns the live model that the
87
+ * caller mutates from runner events; call `requestRender()` after each mutation.
88
+ */
89
+ export function createRunView(opts) {
90
+ const { tui, getModel, actions, onQuit } = opts;
91
+ const screen = new ScreenComponent(getModel);
92
+ const stepInput = new Input();
93
+ let inputMounted = false;
94
+ stepInput.onSubmit = (value) => {
95
+ const text = value.trim();
96
+ stepInput.setValue("");
97
+ if (text)
98
+ dispatchInput(text, getModel(), actions);
99
+ requestRender();
100
+ };
101
+ /** Mount/focus the input exactly when the current state accepts it. */
102
+ function syncInput() {
103
+ const active = inputActive(getModel());
104
+ if (active && !inputMounted) {
105
+ tui.addChild(stepInput);
106
+ tui.setFocus(stepInput);
107
+ inputMounted = true;
108
+ }
109
+ else if (!active && inputMounted) {
110
+ tui.removeChild(stepInput);
111
+ tui.setFocus(null);
112
+ inputMounted = false;
113
+ }
114
+ }
115
+ function requestRender() {
116
+ syncInput();
117
+ tui.requestRender();
118
+ }
119
+ /**
120
+ * Global keys — scrolling and quit only. Everything else (typed text, Enter,
121
+ * cursor movement within the box) is NOT handled here: when this returns
122
+ * false, pi-tui forwards the raw input to the focused component itself
123
+ * (stepInput, via tui.setFocus in syncInput), which is where actual typing
124
+ * belongs. Returns true if handled here.
125
+ */
126
+ function handleInput(data) {
127
+ if (tui.hasOverlay())
128
+ return false;
129
+ if (matchesKey(data, Key.up)) {
130
+ screen.scrollBack += 1;
131
+ requestRender();
132
+ return true;
133
+ }
134
+ if (matchesKey(data, Key.down)) {
135
+ screen.scrollBack = Math.max(0, screen.scrollBack - 1);
136
+ requestRender();
137
+ return true;
138
+ }
139
+ if (matchesKey(data, Key.pageUp)) {
140
+ screen.scrollBack += 10;
141
+ requestRender();
142
+ return true;
143
+ }
144
+ if (matchesKey(data, Key.pageDown)) {
145
+ screen.scrollBack = Math.max(0, screen.scrollBack - 10);
146
+ requestRender();
147
+ return true;
148
+ }
149
+ if (matchesKey(data, Key.escape)) {
150
+ // First Esc clears a draft in progress (cheap to hit by accident while
151
+ // typing); only an Esc on an empty box actually quits.
152
+ if (stepInput.getValue().trim().length > 0) {
153
+ stepInput.setValue("");
154
+ requestRender();
155
+ return true;
156
+ }
157
+ onQuit();
158
+ return true;
159
+ }
160
+ return false;
161
+ }
162
+ return { screen, stepInput, requestRender, handleInput };
163
+ }
164
+ // getSelectListTheme is imported so the launcher (which shares this module's pi
165
+ // surface) can theme its picker without importing pi-tui directly.
166
+ export { getSelectListTheme };
167
+ //# sourceMappingURL=run-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-view.js","sourceRoot":"","sources":["../../src/tui/run-view.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Custom startup header — replaces Pi's generic "Pi can explain its own
3
+ * features and look up its docs..." welcome text with a small bordered
4
+ * banner that actually says what ralph-flow-pi is and how to start it, via
5
+ * `ctx.ui.setHeader` (same "session_start" seam `setEditorComponent` uses —
6
+ * see extension.ts and history-editor.ts). `Component` (pi-tui) is a
7
+ * structural interface (`render(width): string[]`), not a base class to
8
+ * extend, so this is a plain object literal — no subclassing, and the
9
+ * content-building part is a pure function, unit-testable without a TUI.
10
+ *
11
+ * The box is hand-rolled rather than reused from pi-tui/pi-coding-agent:
12
+ * `Box` (pi-tui) is padding+background only, no border; `DynamicBorder`
13
+ * (pi-coding-agent) is a single horizontal rule, not a full frame. Every
14
+ * width computation goes through `visibleWidth`/`wrapTextWithAnsi` (both
15
+ * confirmed ANSI-aware AND CJK/emoji-width-aware by a quick node -e check
16
+ * against the real package, not assumed) — the exact pitfall
17
+ * [[pi-sdk-gotchas]]-equivalent lesson from render.ts's own header comment:
18
+ * get this wrong and pi-tui throws "line exceeds terminal width" outright,
19
+ * it doesn't just look a little off.
20
+ */
21
+ import { type Component, type Theme } from "../pi/tui.js";
22
+ export interface WelcomeWorkflowSummary {
23
+ name: string;
24
+ desc: string;
25
+ }
26
+ /** The actual content, separated from the pi-tui plumbing so it's testable headlessly. */
27
+ export declare function buildWelcomeLines(theme: Theme, workflows: WelcomeWorkflowSummary[], width: number): string[];
28
+ /**
29
+ * `listWorkflows` is a thunk, not a snapshot — `setHeader`'s factory runs once
30
+ * at session start, but the header component's own `render()` runs on every
31
+ * redraw, so re-listing each time keeps it honest if workflows change (a
32
+ * project file edited mid-session, `ralphflow doctor` fixing something) —
33
+ * cheap enough (`engine.listWorkflows()` is a directory scan, not a build).
34
+ */
35
+ export declare function createWelcomeHeaderFactory(listWorkflows: () => Array<{
36
+ name: string;
37
+ desc: string;
38
+ invalid?: boolean;
39
+ }>): (_tui: unknown, theme: Theme) => Component;
40
+ //# sourceMappingURL=welcome-header.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"welcome-header.d.ts","sourceRoot":"","sources":["../../src/tui/welcome-header.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAG1D,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAsDD,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,sBAAsB,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAE5G;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,MAAM,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,IAErE,MAAM,OAAO,EAAE,OAAO,KAAK,KAAG,SAAS,CAShD"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Custom startup header — replaces Pi's generic "Pi can explain its own
3
+ * features and look up its docs..." welcome text with a small bordered
4
+ * banner that actually says what ralph-flow-pi is and how to start it, via
5
+ * `ctx.ui.setHeader` (same "session_start" seam `setEditorComponent` uses —
6
+ * see extension.ts and history-editor.ts). `Component` (pi-tui) is a
7
+ * structural interface (`render(width): string[]`), not a base class to
8
+ * extend, so this is a plain object literal — no subclassing, and the
9
+ * content-building part is a pure function, unit-testable without a TUI.
10
+ *
11
+ * The box is hand-rolled rather than reused from pi-tui/pi-coding-agent:
12
+ * `Box` (pi-tui) is padding+background only, no border; `DynamicBorder`
13
+ * (pi-coding-agent) is a single horizontal rule, not a full frame. Every
14
+ * width computation goes through `visibleWidth`/`wrapTextWithAnsi` (both
15
+ * confirmed ANSI-aware AND CJK/emoji-width-aware by a quick node -e check
16
+ * against the real package, not assumed) — the exact pitfall
17
+ * [[pi-sdk-gotchas]]-equivalent lesson from render.ts's own header comment:
18
+ * get this wrong and pi-tui throws "line exceeds terminal width" outright,
19
+ * it doesn't just look a little off.
20
+ */
21
+ import { visibleWidth, wrapTextWithAnsi } from "../pi/tui.js";
22
+ /** More than this and the header starts competing with the chat for space. */
23
+ const MAX_LISTED = 8;
24
+ /** Caps the box width on wide terminals — an 800px-wide slab of text reads worse than a compact card. */
25
+ const MAX_BOX_WIDTH = 68;
26
+ /** The unboxed content lines — colored, not yet wrapped or framed. */
27
+ function contentLines(theme, workflows) {
28
+ const lines = [];
29
+ lines.push(theme.bold(theme.fg("accent", "ralph-flow")) + theme.fg("muted", " · DO → CHECK 工作流引擎"));
30
+ if (workflows.length > 0) {
31
+ lines.push("");
32
+ const shown = workflows.slice(0, MAX_LISTED);
33
+ const overflow = workflows.length - shown.length;
34
+ const names = shown.map((w) => theme.fg("text", w.name)).join(theme.fg("muted", " · "));
35
+ const suffix = overflow > 0 ? theme.fg("muted", ` (+${overflow})`) : "";
36
+ lines.push(theme.fg("muted", "可用工作流:") + names + suffix);
37
+ }
38
+ lines.push("");
39
+ lines.push(theme.fg("muted", '试试说 "用 spec 工作流帮我实现一个登录接口" 之类的话,或输入 ') + theme.fg("accent", "/ralphflow-start"));
40
+ return lines;
41
+ }
42
+ /**
43
+ * Wraps colored lines in a rounded box, sized to fit `outerWidth` (capped at
44
+ * MAX_BOX_WIDTH). Every body line is padded to the exact same visible width
45
+ * so the right border lines up — the one detail that's easy to get subtly
46
+ * wrong with ANSI-colored, CJK-mixed content, which is why it goes through
47
+ * `visibleWidth` per line rather than `.length`.
48
+ */
49
+ function boxIt(theme, lines, outerWidth) {
50
+ const border = (s) => theme.fg("borderAccent", s);
51
+ const innerWidth = Math.max(20, Math.min(MAX_BOX_WIDTH, outerWidth) - 4); // "│ " + content + " │"
52
+ const wrapped = [];
53
+ for (const line of lines) {
54
+ if (line === "") {
55
+ wrapped.push("");
56
+ continue;
57
+ }
58
+ wrapped.push(...wrapTextWithAnsi(line, innerWidth));
59
+ }
60
+ const top = border(`╭${"─".repeat(innerWidth + 2)}╮`);
61
+ const bottom = border(`╰${"─".repeat(innerWidth + 2)}╯`);
62
+ const body = wrapped.map((line) => {
63
+ const pad = " ".repeat(Math.max(0, innerWidth - visibleWidth(line)));
64
+ return border("│ ") + line + pad + border(" │");
65
+ });
66
+ return [top, ...body, bottom];
67
+ }
68
+ /** The actual content, separated from the pi-tui plumbing so it's testable headlessly. */
69
+ export function buildWelcomeLines(theme, workflows, width) {
70
+ return boxIt(theme, contentLines(theme, workflows), width);
71
+ }
72
+ /**
73
+ * `listWorkflows` is a thunk, not a snapshot — `setHeader`'s factory runs once
74
+ * at session start, but the header component's own `render()` runs on every
75
+ * redraw, so re-listing each time keeps it honest if workflows change (a
76
+ * project file edited mid-session, `ralphflow doctor` fixing something) —
77
+ * cheap enough (`engine.listWorkflows()` is a directory scan, not a build).
78
+ */
79
+ export function createWelcomeHeaderFactory(listWorkflows) {
80
+ return (_tui, theme) => ({
81
+ render(width) {
82
+ const workflows = listWorkflows().filter((w) => !w.invalid);
83
+ return buildWelcomeLines(theme, workflows, width);
84
+ },
85
+ // Nothing cached — every render() call already re-lists workflows and
86
+ // re-reads theme colors from scratch, so there's no state to drop.
87
+ invalidate() { },
88
+ });
89
+ }
90
+ //# sourceMappingURL=welcome-header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"welcome-header.js","sourceRoot":"","sources":["../../src/tui/welcome-header.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "ralph-flow-pi",
3
+ "version": "0.1.0",
4
+ "description": "DO → CHECK workflow engine on the Pi SDK — every step runs in a fresh, isolated AI session",
5
+ "type": "module",
6
+ "bin": {
7
+ "ralphflow": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist/",
11
+ "workflows/",
12
+ "skills/"
13
+ ],
14
+ "keywords": [
15
+ "cli",
16
+ "ai-agent",
17
+ "workflow",
18
+ "automation",
19
+ "llm",
20
+ "coding-agent",
21
+ "pi-sdk",
22
+ "spec-driven-development"
23
+ ],
24
+ "homepage": "https://github.com/534529531/ralph-flow-pi#readme",
25
+ "bugs": {
26
+ "url": "https://github.com/534529531/ralph-flow-pi/issues"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/534529531/ralph-flow-pi.git"
31
+ },
32
+ "author": "534529531",
33
+ "scripts": {
34
+ "build": "tsc -p tsconfig.json",
35
+ "dev": "tsc -p tsconfig.json --watch",
36
+ "typecheck": "tsc -p tsconfig.json --noEmit",
37
+ "test": "vitest run",
38
+ "prepublishOnly": "npm run build && npm test"
39
+ },
40
+ "dependencies": {
41
+ "@earendil-works/pi-coding-agent": "^0.80.10",
42
+ "js-yaml": "^4.1.0",
43
+ "typebox": "^1.1.38"
44
+ },
45
+ "devDependencies": {
46
+ "@types/js-yaml": "^4.0.9",
47
+ "@types/node": "^20.11.0",
48
+ "typescript": "^5.3.0",
49
+ "vitest": "^4.1.7"
50
+ },
51
+ "engines": {
52
+ "node": ">=20.0.0"
53
+ },
54
+ "license": "MIT"
55
+ }
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: c-to-rust-audit
3
+ description: 独立审计 C→Rust 翻译质量。逐模块逐函数对照 plan.json 和 C 源码,验证完整性与语义等价。发现遗漏或偏差立即修复。在 c-to-rust 工作流的 audit-core 和 audit-full 步骤触发。
4
+ ---
5
+
6
+ 你是审计师,不是实现者。你的任务是审查已有翻译的质量,而不是从头实现。对照 plan.json 和 C 原实现,逐模块逐函数验证 Rust 实现的完整性和正确性。发现遗漏或偏差,立即修复。
7
+
8
+ ## 输入 / 输出
9
+
10
+ - 输入:plan.json、Rust 项目、C 源码、function-contracts.md(如有)
11
+ - 输出:所有审计范围内的函数确认完整且语义等价;发现的问题已修复
12
+
13
+ ## 范围
14
+
15
+ - **audit-core**:`difficulty` ∈ {trivial, owned} 的模块
16
+ - **audit-full**:全部模块(重点在 stateful/callback/global)
17
+
18
+ ## 审查方法
19
+
20
+ 对范围内的每个模块,逐函数审查:
21
+
22
+ ### 第一步:完整性检查
23
+
24
+ 对照 plan.json 中该模块的 `public_functions` 和 `internal_functions`,在 Rust 源码中找到每个函数的实现。
25
+
26
+ - 函数存在且非桩?→ 继续
27
+ - 函数缺失?→ 实现它
28
+ - 函数是空函数体或仅返回默认值?→ 读 C 原实现,补全它
29
+
30
+ ### 第二步:语义等价检查
31
+
32
+ 对每个已存在的函数,对照 C 原实现验证语义等价性:
33
+
34
+ - 读 C 源码,理解该函数做什么
35
+ - 读 Rust 实现,判断是否产生相同行为
36
+ - 关注:
37
+ - 错误路径:C 的每个错误分支在 Rust 中是否有对应处理
38
+ - 边界条件:C 的 NULL/0/MAX 检查是否被保留
39
+ - 状态转换:C 中的状态机是否完整
40
+ - 副作用:C 中对全局状态、文件、内存的修改是否等效
41
+ - 返回值:C 的返回码/出参语义是否被正确映射
42
+
43
+ ### 第三步:专项审查(audit-full 重点)
44
+
45
+ 对 stateful/callback/global 模块,额外关注:
46
+
47
+ - **资源管理**:C 中有 init/alloc/open,对应的 deinit/free/close 在 Rust 中是否正确(Drop 还是显式方法)
48
+ - **多阶段操作**:C 中通过中间状态标记实现的操作序列,Rust 是否保留了全部阶段
49
+ - **异常恢复**:C 中有从异常状态恢复的逻辑,Rust 是否保留
50
+ - **控制反转**:C 中通过函数指针实现的回调,Rust 是否正确建模
51
+ - **二进制布局**:C 结构体的 repr 在 Rust 中是否一致(尤其注意 struct padding 和 enum 大小)
52
+ - **活代码**:Rust 中定义但从未被调用的函数/结构体/缓存——可能是 C 中使用了但 Rust 遗漏了调用方
53
+
54
+ ### 第四步:修复
55
+
56
+ 发现遗漏或偏差,立即修复——不要只标注问题留给后续步骤。
57
+
58
+ - 缺失函数:读 C 原实现,补全 Rust 版本
59
+ - 语义偏差:重写 Rust 实现,匹配 C 语义
60
+ - 死代码:补全调用方,或删除无用的定义
61
+
62
+ ## 完成标准
63
+
64
+ - 审计范围内的每个模块:所有 public_functions + internal_functions 均已实现且语义等价
65
+ - cargo build 通过
66
+ - cargo test 通过(修复可能引入的测试失败)
67
+ - 任何确实无法修复的问题,在 plan.json 对应模块的 notes 中记录原因
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: c-to-rust-implement
3
+ description: 填充 Rust API 桩,逐模块把 C 翻译成惯用安全 Rust。工程师式自治:编译器/测试报错即自愈信号,用 systematic-debugging 定位根因后修复,迭代到绿。在 c-to-rust 工作流的 impl-core 和 impl-full 步骤触发。
4
+ ---
5
+
6
+ 桩和测试已就位(来自 baseline)。你是工程师:读原始 C 实现 → 理解语义 → 写出通过全部测试的惯用安全 Rust。没有"试 N 次就放弃"——持续迭代直到全部测试通过。不要主动放弃任何模块。
7
+
8
+ ## 输入 / 输出
9
+
10
+ > `<产出目录>` = DO 提示词「产出目录」一节给出的路径(形如 `.ralph-flow/artifacts/<任务摘要>-<后缀>/`)。
11
+
12
+ - 输入:当前范围的 C 源文件、Rust 桩、测试(baseline)、`plan.json`
13
+ - 输出:测试全绿,clippy 干净,unsafe 比例 < 10%(cargo geiger Expressions 口径)且每处有 SAFETY 注释,每模块一个 commit
14
+
15
+ ## 范围
16
+
17
+ - **impl-core**:`difficulty` ∈ {trivial, owned}(通常 layer 1-2)
18
+ - **impl-full**:剩余 {stateful, callback, global}
19
+
20
+ ## 核心规则
21
+
22
+ - **重写所有权,不是重写语法** — 用 Rust 所有权类型消除裸指针,而不是给 C 语法套 `unsafe`
23
+ - **测试即规格** — 全部测试(oracle + prop)通过 = 实现正确
24
+ - **签名可演进** — API 签名尽量稳定;确需调整时直接改,并同步更新调用方测试与 plan.json(这是正常工程,不是失败)
25
+ - **git 是检查点** — 每模块通过后 commit(`impl-core: <module>` / `impl-full: <module>`),让后续步骤可验证进度
26
+
27
+ ## 实现顺序
28
+
29
+ ### 模块顺序
30
+
31
+ 1. 按 plan.json `layers` 自底向上:layer 1 最先。
32
+ 2. 同层内被依赖最多的先实现。
33
+ 3. **默认串行**(更可控)。仅当两个模块的 `depends_on` 与 `transitive_depends_on` **完全不相交**时,才可选 worktree 并行加速。
34
+
35
+ ### 模块内顺序
36
+
37
+ 错误类型 → 数据类型(derive 常用 trait) → 构造函数(C init) → 叶子函数(含 internal_functions) → 核心算法 → Drop(C deinit/free 的 RAII 替代)。每写完一个函数就 `cargo build`,早发现早修。
38
+
39
+ ### 逐函数对照(强制)
40
+
41
+ 每完成一个模块,对照 plan.json 验证:
42
+
43
+ ```
44
+ 模块 <name> 进度:
45
+ - [ ] 读 C 原实现,理解语义与所有权
46
+ - [ ] 写惯用安全 Rust(错误→类型→构造→叶子函数→核心→Drop)
47
+ - [ ] 对照 plan.json:public_functions 和 internal_functions 每个都有对应 Rust 实现
48
+ - [ ] 确认实现非桩(不是空函数体、不是仅返回默认值/Ok(()))
49
+ - [ ] cargo build 通过(编译器报错→定位根因→修,迭代到 Finished)
50
+ - [ ] 本模块 oracle + prop 测试全绿
51
+ - [ ] cargo clippy 本模块无 warning;unsafe<10% 且每处有 SAFETY
52
+ - [ ] git commit(impl-core:/impl-full:)+ plan.json completed_modules 更新
53
+ ```
54
+
55
+ 没有任何函数可以跳过——要么实现,要么在 plan.json 中显式记录原因。
56
+
57
+ ### 大模块分批(impl-full 时重要)
58
+
59
+ 若某模块函数数超过 20 个,分 2-3 批实现。每批:
60
+ 1. 实现该批函数 → cargo build → cargo test → 修复
61
+ 2. git commit(标注批次,如 `impl-full: <module> batch 1/3`)
62
+ 3. 全部批次完成后做一次集成验证
63
+
64
+ 分批原则:先基础设施(读写原语、状态编解码)、再核心算法、最后资源管理与异常恢复。
65
+
66
+ ### 崩溃恢复(步骤开始时)
67
+
68
+ ```bash
69
+ jq -r '.completed_modules[]' <产出目录>/plan.json # 已完成的跳过
70
+ ```
71
+
72
+ 以 `cargo test` 实际结果为准:若某模块标记完成但测试失败,清掉它的 completed 标记重新实现。
73
+
74
+ ### Worktree 并行(可选)
75
+
76
+ ```
77
+ 对每个可并行模块 M:EnterWorktree("impl-<M>") → 实现+测试+commit → ExitWorktree(keep)
78
+ 全部完成后:git merge 各分支 → 解决 lib.rs/types.rs 冲突 → cargo build 验证集成 → 下一层
79
+ ```
80
+
81
+ 并行只是加速手段;拿不准就串行。
82
+
83
+ ## 自愈循环 = systematic-debugging(不是机械计数)
84
+
85
+ Rust 编译器和测试是你最好的自愈工具。报错不是要数着次数放弃,而是**定位根因**:
86
+
87
+ ```
88
+ cargo build 2>&1
89
+ → 编译器精确告诉你哪错、怎么改 → 修 → 重来,直到 Finished
90
+ cargo test 2>&1
91
+ → 失败 → 用 systematic-debugging:复现 → 对照 C 源码定位语义偏差 → 改根因(不是改断言迁就)→ 重来,直到 ok
92
+ cargo clippy -- -D warnings 2>&1
93
+ → 修 warning,直到干净
94
+ git commit
95
+ ```
96
+
97
+ **当同一个错误反复出现**(你试了几次都没动),停止盲改,切换到根因分析:
98
+ 1. **重读 C 原实现** — 你对业务语义的理解很可能有偏差(测试失败时尤其如此)
99
+ 2. **检查 C 是否本身有 UB/旧 bug** — Rust 更严格,会暴露 C 里隐藏的问题;记入模块 notes,Rust 沿用等价的防御性行为
100
+ 3. **检查所有权/生命周期模型** — C 的隐式所有权约定在 Rust 可能需要 `Rc`/`Arc` 或重构数据结构,而不是硬塞 `unsafe`
101
+ 4. **检查 plan.json 依赖图** — 类型冲突往往是漏标了隐式依赖(补 `transitive_depends_on`,按正确顺序重做该模块)
102
+
103
+ **库 API 变更导致的语法不兼容**(requirement:精准自愈):当某 crate 版本的 API 与预期不符(编译器报"no method/field"、签名不匹配),读编译器建议 + `cargo doc`/源码确认新 API,按报错精准打补丁;必要时在 Cargo.toml 固定到兼容版本。这类修复走同一个"读报错→改→重编"循环。
104
+
105
+ 常见 C→Rust 模式(详见 `references/c-to-rust-patterns.md`):
106
+ - `malloc/free` → `Vec`/`Box` + Drop 自动释放
107
+ - `return -1`/`ERR_CODE` → `Err(AppError::...)`;出参 `int* err` → `Result<T, AppError>`
108
+ - `goto cleanup` → RAII
109
+ - 函数指针表 → `trait` + `Box<dyn Trait>`
110
+ - `static mut STATE` → `OnceLock<Mutex<T>>`
111
+ - `#ifdef` → `#[cfg(...)]` / feature flag
112
+ - 内联汇编 → 优先 `std::arch` 内联函数,否则 `asm!()` + unsafe(见 `references/inline-asm.md`)
113
+
114
+ ## Unsafe 使用规则
115
+
116
+ ### 度量标准(全工作流统一:cargo geiger 是唯一权威)
117
+
118
+ 不要手写行计数脚本。unsafe 比例统一用 **cargo geiger** 度量(已由 setup-env 步骤安装好)。
119
+ 口径 = geiger 输出中本项目 crate 行的 **Expressions** 列 `used/total`,要求 `used/total < 10%`:
120
+
121
+ ```bash
122
+ cd <output_dir>
123
+ RATIO=$(cargo geiger 2>/dev/null | grep -m1 -F "$(basename "$PWD") " | grep -oE '[0-9]+/[0-9]+' | sed -n '2p')
124
+ U=${RATIO%/*}; T=${RATIO#*/}
125
+ awk -v u="$U" -v t="$T" 'BEGIN{ if(t==""||t+0==0){print "geiger 解析失败(确认 setup-env 已装 geiger)"; exit 2} r=u*100/t; printf "unsafe expr %d/%d = %.1f%% (target <10%%)\n",u,t,r; exit (r<10?0:1) }'
126
+ ```
127
+
128
+ geiger 报告里 `unsafe fn` / `unsafe impl` / `unsafe trait` / `extern "C"` 声明计入 Functions/Impls/Traits 列,**不计入** Expressions 列——这正是我们想要的:度量真正的不安全代码量,而非声明标记。
129
+
130
+ ### 规则
131
+
132
+ - **默认零 unsafe** — 设计所有权类型消除裸指针;多数 C 模式都有安全 Rust 等价物
133
+ - unsafe 仅用于:FFI 调用(plan.json 声明)、平台原语(mmap/ioctl/SIMD)、自引用结构(优先 `Pin`)
134
+ - 每个 `unsafe {` 前一行必须有 `// SAFETY:` 注释,说明维持了什么不变性、外围如何保证
135
+ - unsafe 块尽量小(1-3 行),禁止把整个函数体塞进大 `unsafe {}`
136
+ - **禁止用 `unsafe fn` 包装安全代码来刷低行数百分比**(unsafe fn 不计入行数但污染 API)
137
+
138
+ ## 完成标准(每模块)
139
+
140
+ - `cargo build` 通过;本模块 oracle + prop 测试通过
141
+ - 对照 plan.json:本模块所有 public_functions + internal_functions 均已实现(非桩)
142
+ - `cargo clippy` 本模块无 warning
143
+ - unsafe 比例 < 10%(cargo geiger Expressions used/total),每处有 SAFETY 注释
144
+ - `git commit`(impl-core:/impl-full: 前缀);plan.json completed_modules 已更新
145
+
146
+ ## 完成标准(全项目,impl-full 末)
147
+
148
+ - `cargo build --release` 通过;`cargo test --all` 全绿(无 FAILED、无 `#[ignore]`)
149
+ - `cargo clippy -- -D warnings` 无 error
150
+ - 全局 unsafe < 10%(cargo geiger Expressions used/total),每处有 SAFETY;无 `todo!()`/`unimplemented!()`/`dbg!()` 残留
151
+ - 无 libc 依赖(FFI 边界模块除外);每模块有 impl-core:/impl-full: commit