paseo-req-kit 0.0.0-stage → 1.7.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/CHANGELOG.md +217 -0
  2. package/LICENSE +21 -0
  3. package/README.md +141 -2
  4. package/SCOPE.md +11 -0
  5. package/bin/locate.mjs +46 -0
  6. package/bin/req-doctor.mjs +21 -0
  7. package/bin/req.mjs +175 -0
  8. package/bin/start-pipeline +21 -0
  9. package/bin/start-pipeline.mjs +22 -0
  10. package/config.example.json +101 -0
  11. package/contracts/README.md +18 -0
  12. package/contracts/feature-files.md +87 -0
  13. package/contracts/notifier-event.md +71 -0
  14. package/contracts/result-file.md +17 -0
  15. package/contracts/spec.md +34 -0
  16. package/contracts/state.md +44 -0
  17. package/contracts/verify.md +39 -0
  18. package/docs/adr/0001-step-agents-under-origin-chat.md +15 -0
  19. package/docs/adr/0002-no-directory-switch-in-chat.md +15 -0
  20. package/docs/adr/0003-script-engine-not-hub.md +15 -0
  21. package/docs/adr/0004-result-files.md +15 -0
  22. package/docs/adr/0005-no-default-branch-local-commits-only.md +15 -0
  23. package/docs/adr/0006-skill-install-targets.md +15 -0
  24. package/docs/adr/0007-own-interview-skill.md +15 -0
  25. package/docs/adr/0008-entries-do-not-call-entries.md +15 -0
  26. package/docs/adr/0009-versioned-install-layout.md +15 -0
  27. package/docs/adr/0010-generated-feature-overview.md +15 -0
  28. package/docs/adr/0011-resume-after-completion.md +15 -0
  29. package/docs/adr/0012-review-panel.md +18 -0
  30. package/docs/adr/0013-no-release-tooling.md +15 -0
  31. package/docs/adr/0014-notifier-slot.md +19 -0
  32. package/docs/adr/0015-incremental-continue.md +17 -0
  33. package/docs/adr/0016-upgrade-from-tags.md +19 -0
  34. package/docs/adr/0017-use-paseo-capabilities.md +18 -0
  35. package/docs/adr/0018-own-worktree-option.md +21 -0
  36. package/docs/adr/0019-parallel-orchestration.md +21 -0
  37. package/docs/adr/0020-scheduling-through-the-chat.md +21 -0
  38. package/docs/adr/0021-npm-version-manager.md +19 -0
  39. package/docs/adr/0022-develop-and-verify-phases.md +32 -0
  40. package/docs/adr/README.md +26 -0
  41. package/docs/guide//344/275/277/347/224/250/350/200/205/346/214/207/345/215/227.md +78 -0
  42. package/docs/guide//345/256/211/350/243/205/344/270/216/345/215/207/347/272/247.md +348 -0
  43. package/docs/guide//347/273/264/346/212/244/350/200/205/346/214/207/345/215/227.md +85 -0
  44. package/docs/guide//351/241/271/347/233/256/345/256/232/345/210/266/344/270/216/350/277/220/350/241/214/350/247/204/345/210/231.md +251 -0
  45. package/engine/prompts/00-shared-context.md +100 -0
  46. package/engine/prompts/02-spec-review.md +46 -0
  47. package/engine/prompts/03-design.md +50 -0
  48. package/engine/prompts/03a-design-proposal.md +30 -0
  49. package/engine/prompts/04-design-review.md +72 -0
  50. package/engine/prompts/04b-design-revise.md +34 -0
  51. package/engine/prompts/05-implement.md +66 -0
  52. package/engine/prompts/06-code-review.md +66 -0
  53. package/engine/prompts/07-review-fix.md +41 -0
  54. package/engine/prompts/07b-handoff.md +45 -0
  55. package/engine/prompts/08-service-start.md +59 -0
  56. package/engine/prompts/09-verify.md +90 -0
  57. package/engine/prompts/10-defect-fix.md +41 -0
  58. package/engine/prompts/10b-reverify.md +43 -0
  59. package/engine/prompts/11-report.md +93 -0
  60. package/engine/prompts/browser-verify.md +13 -0
  61. package/engine/prompts/incremental.md +21 -0
  62. package/engine/prompts/panel-member.md +38 -0
  63. package/engine/prompts/paseo-services.md +9 -0
  64. package/engine/prompts/verify-deployed.md +14 -0
  65. package/engine/src/activity.ts +87 -0
  66. package/engine/src/cli-args.ts +204 -0
  67. package/engine/src/config.ts +479 -0
  68. package/engine/src/configure.ts +237 -0
  69. package/engine/src/control.ts +230 -0
  70. package/engine/src/daemon-check.ts +104 -0
  71. package/engine/src/daemon-version.ts +66 -0
  72. package/engine/src/digest.ts +133 -0
  73. package/engine/src/doctor.ts +435 -0
  74. package/engine/src/eval.ts +324 -0
  75. package/engine/src/features.ts +268 -0
  76. package/engine/src/git.ts +30 -0
  77. package/engine/src/launch.ts +405 -0
  78. package/engine/src/locations.ts +55 -0
  79. package/engine/src/main.ts +313 -0
  80. package/engine/src/messages.ts +308 -0
  81. package/engine/src/notifiers.ts +76 -0
  82. package/engine/src/notify-test.ts +97 -0
  83. package/engine/src/paseo-json.ts +73 -0
  84. package/engine/src/paseo-profiles.ts +133 -0
  85. package/engine/src/paseo-runtime.ts +437 -0
  86. package/engine/src/paths.ts +10 -0
  87. package/engine/src/pipeline.ts +1126 -0
  88. package/engine/src/preflight.ts +298 -0
  89. package/engine/src/prompts.ts +72 -0
  90. package/engine/src/queue.ts +61 -0
  91. package/engine/src/runtime-loader.ts +14 -0
  92. package/engine/src/runtime.ts +155 -0
  93. package/engine/src/schedule-time.ts +51 -0
  94. package/engine/src/smoke-test.ts +202 -0
  95. package/engine/src/spec-review.ts +100 -0
  96. package/engine/src/state.ts +278 -0
  97. package/engine/src/steps.ts +242 -0
  98. package/engine/src/usage.ts +67 -0
  99. package/engine/src/worktree.ts +72 -0
  100. package/install.mjs +928 -0
  101. package/notifiers/im-template.mjs +58 -0
  102. package/package.json +67 -4
  103. package/principles/INDEX.md +11 -0
  104. package/skills/acceptance-criteria/SKILL.md +52 -0
  105. package/skills/principle-delete-before-add/SKILL.md +33 -0
  106. package/skills/principle-dont-block-on-humans/SKILL.md +35 -0
  107. package/skills/principle-fix-root-cause/SKILL.md +33 -0
  108. package/skills/principle-prove-it-works/SKILL.md +33 -0
  109. package/skills/principle-report-honestly/SKILL.md +33 -0
  110. package/skills/principle-speak-to-reader/SKILL.md +33 -0
  111. package/skills/principle-this-requirement-only/SKILL.md +33 -0
  112. package/skills/req/SKILL.md +99 -0
  113. package/skills/req/playbooks/accept.md +65 -0
  114. package/skills/req/playbooks/change.md +51 -0
  115. package/skills/req/playbooks/incident.md +55 -0
  116. package/skills/req/playbooks/new.md +83 -0
  117. package/skills/req/playbooks/small.md +48 -0
  118. package/skills/req/playbooks/status.md +39 -0
  119. package/skills/req/playbooks/takeover.md +51 -0
  120. package/skills/req/playbooks/verify.md +57 -0
  121. package/skills/req-doctor/SKILL.md +36 -0
  122. package/skills/req-retro/SKILL.md +79 -0
  123. package/skills/req-setup/SKILL.md +101 -0
  124. package/skills/req-setup/templates/paseo/copy-local-files.mjs +32 -0
  125. package/skills/req-setup/templates/paseo/with-port.mjs +36 -0
  126. package/skills/requirement-interview/SKILL.md +94 -0
  127. package/skills/requirement-kickoff/SKILL.md +17 -0
  128. package/skills/result-report/SKILL.md +34 -0
  129. package/skills/spec-writing/SKILL.md +41 -0
  130. package/skills/spec-writing/spec-template.md +106 -0
@@ -0,0 +1,242 @@
1
+ import type { AgentRole } from "./runtime.js";
2
+
3
+ export type StepId =
4
+ | "design"
5
+ | "design_review"
6
+ | "design_revise"
7
+ | "implement"
8
+ | "code_review"
9
+ | "review_fix"
10
+ | "service_start"
11
+ | "verify"
12
+ | "defect_fix"
13
+ | "reverify"
14
+ | "report"
15
+ /** Handoff note for testing (提测说明), the last step of the develop phase. */
16
+ | "handoff"
17
+ /** One member of the optional review panel; runs before design_review / code_review consolidates. */
18
+ | "panel_review"
19
+ /** Independent review of a draft spec before the requester confirms it (run from the chat, not the pipeline). */
20
+ | "spec_review"
21
+ /** One of the parallel design proposals that the design step compares (optional). */
22
+ | "design_proposal";
23
+
24
+ type FieldSpec =
25
+ | { kind: "boolean" }
26
+ | { kind: "enum"; values: readonly string[] }
27
+ | { kind: "string" }
28
+ | { kind: "string[]" };
29
+
30
+ export interface ResultSpec {
31
+ required: Record<string, FieldSpec>;
32
+ optional?: Record<string, FieldSpec>;
33
+ /** Extra cross-field rule; returns an error message or null. */
34
+ check?: (value: Record<string, unknown>) => string | null;
35
+ }
36
+
37
+ export interface StepDef {
38
+ id: StepId;
39
+ /** Step number in the 11-step list the user defined. */
40
+ number: number;
41
+ title: string;
42
+ role: AgentRole;
43
+ promptFile: string;
44
+ result: ResultSpec;
45
+ }
46
+
47
+ const done: ResultSpec = { required: { status: { kind: "enum", values: ["done", "partial"] } } };
48
+
49
+ /** Review steps that can run with a review panel. */
50
+ export const PANEL_STEPS = ["design_review", "code_review"] as const;
51
+ export type PanelStep = (typeof PANEL_STEPS)[number];
52
+
53
+ export const STEPS: Record<StepId, StepDef> = {
54
+ spec_review: {
55
+ id: "spec_review",
56
+ number: 2,
57
+ title: "需求说明审阅",
58
+ role: "reviewer",
59
+ promptFile: "02-spec-review.md",
60
+ result: {
61
+ required: { verdict: { kind: "enum", values: ["ready", "issues"] } },
62
+ optional: { issues: { kind: "string[]" } },
63
+ check: (value) =>
64
+ value.verdict === "issues" && (!Array.isArray(value.issues) || value.issues.length === 0)
65
+ ? "verdict 为 issues 时,issues 必须是非空的字符串数组(每条一句话)"
66
+ : null,
67
+ },
68
+ },
69
+ design_proposal: {
70
+ id: "design_proposal",
71
+ number: 3,
72
+ title: "方案提案",
73
+ role: "builder",
74
+ promptFile: "03a-design-proposal.md",
75
+ result: done,
76
+ },
77
+ panel_review: {
78
+ id: "panel_review",
79
+ number: 0,
80
+ title: "评审小组成员",
81
+ role: "reviewer",
82
+ promptFile: "panel-member.md",
83
+ result: done,
84
+ },
85
+ design: {
86
+ id: "design",
87
+ number: 3,
88
+ title: "方案设计",
89
+ role: "builder",
90
+ promptFile: "03-design.md",
91
+ result: { required: { has_runnable_service: { kind: "boolean" } } },
92
+ },
93
+ design_review: {
94
+ id: "design_review",
95
+ number: 4,
96
+ title: "方案审核",
97
+ role: "reviewer",
98
+ promptFile: "04-design-review.md",
99
+ result: {
100
+ required: { verdict: { kind: "enum", values: ["approved", "changes_required", "blocked"] } },
101
+ optional: { questions: { kind: "string[]" } },
102
+ check: (value) =>
103
+ value.verdict === "blocked" && (!Array.isArray(value.questions) || value.questions.length === 0)
104
+ ? "verdict 为 blocked 时,questions 必须是非空的字符串数组"
105
+ : null,
106
+ },
107
+ },
108
+ design_revise: {
109
+ id: "design_revise",
110
+ number: 4,
111
+ title: "方案修订",
112
+ role: "builder",
113
+ promptFile: "04b-design-revise.md",
114
+ result: done,
115
+ },
116
+ implement: {
117
+ id: "implement",
118
+ number: 5,
119
+ title: "代码实现",
120
+ role: "builder",
121
+ promptFile: "05-implement.md",
122
+ result: done,
123
+ },
124
+ code_review: {
125
+ id: "code_review",
126
+ number: 6,
127
+ title: "代码评审",
128
+ role: "reviewer",
129
+ promptFile: "06-code-review.md",
130
+ result: { required: { verdict: { kind: "enum", values: ["pass", "changes_required"] } } },
131
+ },
132
+ review_fix: {
133
+ id: "review_fix",
134
+ number: 7,
135
+ title: "评审问题修复",
136
+ role: "builder",
137
+ promptFile: "07-review-fix.md",
138
+ result: done,
139
+ },
140
+ handoff: {
141
+ id: "handoff",
142
+ number: 7,
143
+ title: "提测说明",
144
+ role: "builder",
145
+ promptFile: "07b-handoff.md",
146
+ result: done,
147
+ },
148
+ service_start: {
149
+ id: "service_start",
150
+ number: 8,
151
+ title: "服务启动验证",
152
+ role: "builder",
153
+ promptFile: "08-service-start.md",
154
+ result: { required: { started: { kind: "boolean" } } },
155
+ },
156
+ verify: {
157
+ id: "verify",
158
+ number: 9,
159
+ title: "功能验证",
160
+ role: "reviewer",
161
+ promptFile: "09-verify.md",
162
+ result: { required: { result: { kind: "enum", values: ["pass", "defects_found", "blocked"] } } },
163
+ },
164
+ defect_fix: {
165
+ id: "defect_fix",
166
+ number: 10,
167
+ title: "缺陷修复",
168
+ role: "builder",
169
+ promptFile: "10-defect-fix.md",
170
+ result: done,
171
+ },
172
+ reverify: {
173
+ id: "reverify",
174
+ number: 10,
175
+ title: "复验",
176
+ role: "reviewer",
177
+ promptFile: "10b-reverify.md",
178
+ result: { required: { result: { kind: "enum", values: ["pass", "defects_remaining"] } } },
179
+ },
180
+ report: {
181
+ id: "report",
182
+ number: 11,
183
+ title: "完成报告",
184
+ role: "builder",
185
+ promptFile: "11-report.md",
186
+ result: {
187
+ required: {
188
+ outcome: { kind: "enum", values: ["completed", "completed_with_issues", "not_completed"] },
189
+ summary: { kind: "string" },
190
+ },
191
+ },
192
+ },
193
+ };
194
+
195
+ function checkField(name: string, spec: FieldSpec, value: unknown): string | null {
196
+ switch (spec.kind) {
197
+ case "boolean":
198
+ return typeof value === "boolean" ? null : `${name} 必须是 true 或 false`;
199
+ case "string":
200
+ return typeof value === "string" && value.trim() !== "" ? null : `${name} 必须是非空字符串`;
201
+ case "string[]":
202
+ return Array.isArray(value) && value.every((item) => typeof item === "string")
203
+ ? null
204
+ : `${name} 必须是字符串数组`;
205
+ case "enum":
206
+ return typeof value === "string" && spec.values.includes(value)
207
+ ? null
208
+ : `${name} 必须是 ${spec.values.map((v) => `"${v}"`).join(" / ")} 之一`;
209
+ }
210
+ }
211
+
212
+ export type ValidationResult =
213
+ | { ok: true; value: Record<string, unknown> }
214
+ | { ok: false; error: string };
215
+
216
+ export function validateResult(spec: ResultSpec, raw: unknown): ValidationResult {
217
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
218
+ return { ok: false, error: "内容必须是一个 JSON 对象" };
219
+ }
220
+ const value = raw as Record<string, unknown>;
221
+ for (const [name, field] of Object.entries(spec.required)) {
222
+ if (!(name in value)) return { ok: false, error: `缺少字段 ${name}` };
223
+ const error = checkField(name, field, value[name]);
224
+ if (error) return { ok: false, error };
225
+ }
226
+ for (const [name, field] of Object.entries(spec.optional ?? {})) {
227
+ if (name in value && value[name] !== undefined) {
228
+ const error = checkField(name, field, value[name]);
229
+ if (error) return { ok: false, error };
230
+ }
231
+ }
232
+ const crossError = spec.check?.(value) ?? null;
233
+ if (crossError) return { ok: false, error: crossError };
234
+ return { ok: true, value };
235
+ }
236
+
237
+ /** Steps of the verify phase; they may use phases.verify.agents. */
238
+ export const VERIFY_PHASE_STEPS: readonly StepId[] = ["service_start", "verify", "defect_fix", "reverify"];
239
+
240
+ export function stepKey(id: StepId, round: number | null): string {
241
+ return round === null ? id : `${id}-r${round}`;
242
+ }
@@ -0,0 +1,67 @@
1
+ // Time and token usage per step, as reported by the agents, and plain-language summaries.
2
+
3
+ export interface AgentUsage {
4
+ inputTokens?: number;
5
+ cachedInputTokens?: number;
6
+ outputTokens?: number;
7
+ totalCostUsd?: number;
8
+ }
9
+
10
+ interface UsageRecord {
11
+ attempt: number;
12
+ startedAt: string;
13
+ endedAt: string | null;
14
+ usage?: AgentUsage | null;
15
+ }
16
+
17
+ export interface UsageTotals {
18
+ steps: number;
19
+ /** Sum of step durations (steps of a review panel overlap, so this can exceed wall time). */
20
+ agentMinutes: number;
21
+ inputTokens: number | null;
22
+ outputTokens: number | null;
23
+ costUsd: number | null;
24
+ }
25
+
26
+ function add(total: number | null, value: number | undefined): number | null {
27
+ return typeof value === "number" ? (total ?? 0) + value : total;
28
+ }
29
+
30
+ export function totals(records: UsageRecord[], now = new Date()): UsageTotals {
31
+ let agentMinutes = 0;
32
+ let inputTokens: number | null = null;
33
+ let outputTokens: number | null = null;
34
+ let costUsd: number | null = null;
35
+ for (const r of records) {
36
+ const end = r.endedAt ? new Date(r.endedAt) : now;
37
+ agentMinutes += Math.max(0, (end.getTime() - new Date(r.startedAt).getTime()) / 60_000);
38
+ inputTokens = add(inputTokens, r.usage?.inputTokens);
39
+ outputTokens = add(outputTokens, r.usage?.outputTokens);
40
+ costUsd = add(costUsd, r.usage?.totalCostUsd);
41
+ }
42
+ return { steps: records.length, agentMinutes, inputTokens, outputTokens, costUsd };
43
+ }
44
+
45
+ function tokens(n: number): string {
46
+ return n >= 10_000 ? `${(n / 10_000).toFixed(1).replace(/\.0$/, "")} 万` : String(n);
47
+ }
48
+
49
+ function minutes(m: number): string {
50
+ if (m < 1) return "不到 1 分钟";
51
+ if (m < 60) return `${Math.round(m)} 分钟`;
52
+ const h = Math.floor(m / 60);
53
+ const rest = Math.round(m % 60);
54
+ return rest === 0 ? `${h} 小时` : `${h} 小时 ${rest} 分`;
55
+ }
56
+
57
+ /** e.g. "6 个步骤,agent 合计用时 2 小时 5 分;输入 120 万 tokens,输出 8.3 万 tokens,约 $4.20" */
58
+ export function describeTotals(t: UsageTotals): string {
59
+ const parts = [`${t.steps} 个步骤,agent 合计用时 ${minutes(t.agentMinutes)}`];
60
+ const usage: string[] = [];
61
+ if (t.inputTokens !== null) usage.push(`输入 ${tokens(t.inputTokens)} tokens`);
62
+ if (t.outputTokens !== null) usage.push(`输出 ${tokens(t.outputTokens)} tokens`);
63
+ if (t.costUsd !== null) usage.push(`约 $${t.costUsd.toFixed(2)}`);
64
+ if (usage.length > 0) parts.push(usage.join(","));
65
+ else parts.push("agent 没有报告 token 用量");
66
+ return parts.join(";");
67
+ }
@@ -0,0 +1,72 @@
1
+ // --worktree: develop a requirement in a Paseo worktree workspace created for it.
2
+ // The chat stays where it is (its working directory never changes); the spec moves
3
+ // to the new worktree, is committed there on its own branch, and the step agents are
4
+ // placed in that workspace while remaining subagents of the chat. See ADR 0018.
5
+
6
+ import { execFileSync } from "node:child_process";
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import type { AgentRuntime } from "./runtime.js";
10
+
11
+ function git(cwd: string, ...args: string[]): string {
12
+ return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
13
+ }
14
+
15
+ export interface OwnWorktree {
16
+ repo: string;
17
+ workspaceId: string;
18
+ branch: string;
19
+ /** Whether the uncommitted copy in the chat's checkout was removed after moving. */
20
+ removedSource: boolean;
21
+ }
22
+
23
+ export async function moveToOwnWorktree(input: {
24
+ runtime: Pick<AgentRuntime, "createWorktree">;
25
+ sourceRepo: string;
26
+ featureDir: string;
27
+ originAgentId: string | null;
28
+ title: string;
29
+ }): Promise<OwnWorktree> {
30
+ const { sourceRepo, featureDir } = input;
31
+ const branch = `feature/${path.basename(featureDir)}`;
32
+ try {
33
+ git(sourceRepo, "show-ref", "--verify", "--quiet", `refs/heads/${branch}`);
34
+ throw new Error(`分支 ${branch} 已经存在。这个需求可能已经有自己的工作区了:用 --list 查看,或在那个工作区的对话中继续。`);
35
+ } catch (error) {
36
+ if ((error as Error).message.startsWith("分支")) throw error;
37
+ // show-ref failed: the branch does not exist yet, as expected.
38
+ }
39
+ const head = git(sourceRepo, "rev-parse", "--abbrev-ref", "HEAD");
40
+ const baseRef = head === "HEAD" ? git(sourceRepo, "rev-parse", "HEAD") : head;
41
+
42
+ const created = await input.runtime.createWorktree({
43
+ sourceRepo,
44
+ baseRef,
45
+ branchName: branch,
46
+ title: input.title,
47
+ callerAgentId: input.originAgentId,
48
+ });
49
+ const repo = fs.realpathSync(created.directory);
50
+
51
+ // The spec and interview notes were written in the chat's checkout; move them over.
52
+ const from = path.join(sourceRepo, featureDir);
53
+ const to = path.join(repo, featureDir);
54
+ fs.mkdirSync(to, { recursive: true });
55
+ fs.cpSync(from, to, { recursive: true, filter: (src) => path.basename(src) !== ".pipeline" });
56
+ git(repo, "add", "--", featureDir);
57
+ const staged = git(repo, "diff", "--cached", "--name-only", "--", featureDir);
58
+ if (staged) {
59
+ try {
60
+ git(repo, "commit", "-q", "-m", `docs(req): ${input.title} 需求说明`, "--", featureDir);
61
+ } catch (error) {
62
+ const stderr = (error as { stderr?: Buffer | string }).stderr?.toString().trim();
63
+ throw new Error(`在新工作区 ${repo} 中提交需求说明失败:${stderr || (error as Error).message}`);
64
+ }
65
+ }
66
+
67
+ // Leave nothing behind in the chat's checkout unless it was already committed there.
68
+ const tracked = git(sourceRepo, "ls-files", "--", featureDir);
69
+ const removedSource = tracked === "";
70
+ if (removedSource) fs.rmSync(from, { recursive: true, force: true });
71
+ return { repo, workspaceId: created.workspaceId, branch, removedSource };
72
+ }