@pushpalsdev/cli 1.0.31 → 1.0.33

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 (46) hide show
  1. package/dist/pushpals-cli.js +33 -4
  2. package/monitor-ui/+not-found.html +1 -1
  3. package/monitor-ui/_expo/static/js/web/{entry-275c5f7972e2d2f4f0422fe2213a7f89.js → entry-5e6db7139bc13703a24f952bd64faf4c.js} +2 -2
  4. package/monitor-ui/_sitemap.html +1 -1
  5. package/monitor-ui/index.html +1 -1
  6. package/monitor-ui/modal.html +1 -1
  7. package/package.json +1 -1
  8. package/runtime/prompts/localbuddy/localbuddy_planner_git_diff_section.md +0 -1
  9. package/runtime/prompts/localbuddy/localbuddy_planner_git_status_section.md +0 -1
  10. package/runtime/prompts/localbuddy/localbuddy_planner_output_contract.md +1 -0
  11. package/runtime/prompts/remotebuddy/autonomy_ideation_system_prompt.md +31 -30
  12. package/runtime/prompts/remotebuddy/autonomy_scoring_system_prompt.md +2 -2
  13. package/runtime/prompts/remotebuddy/context_packer_user_prompt.md +1 -0
  14. package/runtime/prompts/remotebuddy/remotebuddy_system_prompt.md +1 -0
  15. package/runtime/prompts/review_agent/review_prompt_template.md +1 -0
  16. package/runtime/prompts/review_agent/reviewer.md +6 -4
  17. package/runtime/prompts/workerpals/commit_message_prompt.md +3 -0
  18. package/runtime/prompts/workerpals/miniswe_broker_system_prompt.md +10 -9
  19. package/runtime/prompts/workerpals/miniswe_strict_tool_use_guidance.md +1 -0
  20. package/runtime/prompts/workerpals/openai_codex_runtime_policy_appendix.md +1 -0
  21. package/runtime/prompts/workerpals/openai_codex_task_execute_system_prompt.md +2 -0
  22. package/runtime/prompts/workerpals/task_quality_critic_system_prompt.md +3 -2
  23. package/runtime/sandbox/apps/workerpals/src/backends/openai_codex_backend.ts +4 -4
  24. package/runtime/sandbox/apps/workerpals/src/backends/openhands_task_execute.ts +1 -4
  25. package/runtime/sandbox/apps/workerpals/src/common/execution_utils.ts +5 -3
  26. package/runtime/sandbox/apps/workerpals/src/common/generic_python_executor.ts +1 -4
  27. package/runtime/sandbox/apps/workerpals/src/common/worktree_cleanup.ts +3 -2
  28. package/runtime/sandbox/apps/workerpals/src/docker_executor.ts +42 -27
  29. package/runtime/sandbox/apps/workerpals/src/execute_job.ts +7 -7
  30. package/runtime/sandbox/apps/workerpals/src/job_runner.ts +7 -4
  31. package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +2 -1
  32. package/runtime/sandbox/packages/shared/src/autonomy_policy.ts +8 -3
  33. package/runtime/sandbox/packages/shared/src/communication.ts +19 -8
  34. package/runtime/sandbox/packages/shared/src/config.ts +9 -24
  35. package/runtime/sandbox/packages/shared/src/config_template_parity.ts +5 -6
  36. package/runtime/sandbox/packages/shared/src/git_backend.ts +5 -9
  37. package/runtime/sandbox/packages/shared/src/local_network.ts +3 -1
  38. package/runtime/sandbox/packages/shared/src/localbuddy_runtime.ts +4 -5
  39. package/runtime/sandbox/packages/shared/src/vision.ts +6 -2
  40. package/runtime/sandbox/prompts/workerpals/commit_message_prompt.md +3 -0
  41. package/runtime/sandbox/prompts/workerpals/miniswe_broker_system_prompt.md +10 -9
  42. package/runtime/sandbox/prompts/workerpals/miniswe_strict_tool_use_guidance.md +1 -0
  43. package/runtime/sandbox/prompts/workerpals/openai_codex_runtime_policy_appendix.md +1 -0
  44. package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +2 -0
  45. package/runtime/sandbox/prompts/workerpals/task_quality_critic_system_prompt.md +3 -2
  46. package/runtime/vision.example.md +24 -5
@@ -457,11 +457,7 @@ function normalizeWorkerImageRebuildMode(value: string): "auto" | "always" | "ne
457
457
 
458
458
  function normalizeStartupPortConflictPolicy(value: string): "fail" | "terminate_pushpals" {
459
459
  const text = value.trim().toLowerCase().replace(/-/g, "_");
460
- if (
461
- text === "terminate_pushpals" ||
462
- text === "kill_pushpals" ||
463
- text === "auto_kill_pushpals"
464
- ) {
460
+ if (text === "terminate_pushpals" || text === "kill_pushpals" || text === "auto_kill_pushpals") {
465
461
  return "terminate_pushpals";
466
462
  }
467
463
  return "fail";
@@ -535,10 +531,7 @@ function resolveLlmConfig(
535
531
  );
536
532
  const codexTimeoutMs = Math.max(
537
533
  10_000,
538
- asInt(
539
- parseIntEnv(`${envPrefix}_LLM_CODEX_TIMEOUT_MS`) ?? llmNode.codex_timeout_ms,
540
- 120_000,
541
- ),
534
+ asInt(parseIntEnv(`${envPrefix}_LLM_CODEX_TIMEOUT_MS`) ?? llmNode.codex_timeout_ms, 120_000),
542
535
  );
543
536
  return {
544
537
  backend,
@@ -916,7 +909,8 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
916
909
  Math.min(
917
910
  10,
918
911
  asInt(
919
- parseIntEnv("WORKERPALS_QUALITY_MAX_AUTO_REVISIONS") ?? workerNode.quality_max_auto_revisions,
912
+ parseIntEnv("WORKERPALS_QUALITY_MAX_AUTO_REVISIONS") ??
913
+ workerNode.quality_max_auto_revisions,
920
914
  DEFAULT_WORKERPALS_QUALITY_MAX_AUTO_REVISIONS,
921
915
  ),
922
916
  ),
@@ -973,8 +967,7 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
973
967
  const workerQualityCriticTimeoutMs = Math.max(
974
968
  1_000,
975
969
  asInt(
976
- parseIntEnv("WORKERPALS_QUALITY_CRITIC_TIMEOUT_MS") ??
977
- workerNode.quality_critic_timeout_ms,
970
+ parseIntEnv("WORKERPALS_QUALITY_CRITIC_TIMEOUT_MS") ?? workerNode.quality_critic_timeout_ms,
978
971
  DEFAULT_WORKERPALS_QUALITY_CRITIC_TIMEOUT_MS,
979
972
  ),
980
973
  );
@@ -1431,8 +1424,7 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
1431
1424
  parseBoolEnv("PUSHPALS_ALLOW_EXTERNAL_CLEAN") ??
1432
1425
  asBoolean(startupNode.allow_external_clean, false);
1433
1426
  const startupPortPreflight =
1434
- parseBoolEnv("PUSHPALS_STARTUP_PORT_PREFLIGHT") ??
1435
- asBoolean(startupNode.port_preflight, true);
1427
+ parseBoolEnv("PUSHPALS_STARTUP_PORT_PREFLIGHT") ?? asBoolean(startupNode.port_preflight, true);
1436
1428
  const startupPortConflictPolicy = normalizeStartupPortConflictPolicy(
1437
1429
  firstNonEmpty(
1438
1430
  process.env.PUSHPALS_STARTUP_PORT_CONFLICT_POLICY,
@@ -1508,10 +1500,7 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
1508
1500
  asBoolean(remoteNode.auto_spawn_workerpals, true),
1509
1501
  maxWorkerpals: Math.max(
1510
1502
  1,
1511
- asInt(
1512
- parseIntEnv("REMOTEBUDDY_MAX_WORKERPALS") ?? remoteNode.max_workerpals,
1513
- 20,
1514
- ),
1503
+ asInt(parseIntEnv("REMOTEBUDDY_MAX_WORKERPALS") ?? remoteNode.max_workerpals, 20),
1515
1504
  ),
1516
1505
  workerpalStartupTimeoutMs: Math.max(
1517
1506
  1_000,
@@ -1647,8 +1636,7 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
1647
1636
  llmTimeoutMs: Math.max(
1648
1637
  1_000,
1649
1638
  asInt(
1650
- parseIntEnv("REMOTEBUDDY_AUTONOMY_LLM_TIMEOUT_MS") ??
1651
- remoteAutonomyNode.llm_timeout_ms,
1639
+ parseIntEnv("REMOTEBUDDY_AUTONOMY_LLM_TIMEOUT_MS") ?? remoteAutonomyNode.llm_timeout_ms,
1652
1640
  12_000,
1653
1641
  ),
1654
1642
  ),
@@ -2156,10 +2144,7 @@ function sanitizeConfigString(value: string): string {
2156
2144
  return out;
2157
2145
  }
2158
2146
 
2159
- function sanitizeConfigValueForLogging(
2160
- value: unknown,
2161
- parentKey = "",
2162
- ): unknown {
2147
+ function sanitizeConfigValueForLogging(value: unknown, parentKey = ""): unknown {
2163
2148
  if (
2164
2149
  typeof value === "string" ||
2165
2150
  typeof value === "number" ||
@@ -18,11 +18,7 @@ export function collectDotEnvKeys(raw: string): Set<string> {
18
18
  return keys;
19
19
  }
20
20
 
21
- function collectTomlLeafKeysFromNode(
22
- node: unknown,
23
- prefix: string,
24
- out: Set<string>,
25
- ): void {
21
+ function collectTomlLeafKeysFromNode(node: unknown, prefix: string, out: Set<string>): void {
26
22
  if (!isObject(node)) return;
27
23
  for (const [rawKey, value] of Object.entries(node)) {
28
24
  const key = rawKey.trim();
@@ -43,7 +39,10 @@ export function collectTomlLeafKeys(raw: string): Set<string> {
43
39
  return keys;
44
40
  }
45
41
 
46
- export function missingTemplateKeys(templateKeys: Iterable<string>, localKeys: Set<string>): string[] {
42
+ export function missingTemplateKeys(
43
+ templateKeys: Iterable<string>,
44
+ localKeys: Set<string>,
45
+ ): string[] {
47
46
  const templateSet = new Set(templateKeys);
48
47
  const missing: string[] = [];
49
48
  for (const key of templateSet) {
@@ -42,10 +42,7 @@ export function sanitizeGitRemoteUrl(remoteUrl: string): string {
42
42
  return raw.replace(/^(https?:\/\/)[^@/]+@/i, "$1");
43
43
  }
44
44
 
45
- function firstNonEmpty(
46
- env: Record<string, string | undefined>,
47
- keys: readonly string[],
48
- ): string {
45
+ function firstNonEmpty(env: Record<string, string | undefined>, keys: readonly string[]): string {
49
46
  for (const key of keys) {
50
47
  const value = trimToken(env[key]);
51
48
  if (value) return value;
@@ -110,10 +107,7 @@ export function toGitHubRepoWebUrl(remoteUrl: string): string | null {
110
107
  return `https://github.com/${repo.owner}/${repo.repo}`;
111
108
  }
112
109
 
113
- async function defaultRunCommand(
114
- command: string[],
115
- cwd?: string,
116
- ): Promise<CommandCaptureResult> {
110
+ async function defaultRunCommand(command: string[], cwd?: string): Promise<CommandCaptureResult> {
117
111
  try {
118
112
  const proc = Bun.spawn(command, {
119
113
  cwd,
@@ -147,7 +141,9 @@ async function resolveGitHubCliToken(
147
141
  cwd?: string,
148
142
  ): Promise<string> {
149
143
  const useHostname = host && host !== "github.com";
150
- const command = useHostname ? ["gh", "auth", "token", "--hostname", host] : ["gh", "auth", "token"];
144
+ const command = useHostname
145
+ ? ["gh", "auth", "token", "--hostname", host]
146
+ : ["gh", "auth", "token"];
151
147
  const result = await runCommand(command, cwd);
152
148
  return result.ok ? trimToken(result.stdout) : "";
153
149
  }
@@ -89,7 +89,9 @@ export function resolveLocalServerConnection(options: {
89
89
  serverWasNormalized: boolean;
90
90
  authTokenWasIgnored: boolean;
91
91
  } {
92
- const rawServer = String(options.serverUrl ?? "").trim().replace(/\/+$/, "");
92
+ const rawServer = String(options.serverUrl ?? "")
93
+ .trim()
94
+ .replace(/\/+$/, "");
93
95
  const normalizedServer = normalizeLoopbackHttpUrl(rawServer, options.fallbackPort);
94
96
  const authToken = String(options.authToken ?? "").trim();
95
97
  return {
@@ -50,10 +50,7 @@ export function parseLocalBuddyRuntimeSnapshot(raw: string): LocalBuddyRuntimeSn
50
50
  return {
51
51
  localbuddy: {
52
52
  enabled,
53
- port:
54
- Number.isFinite(port) && port >= 1 && port <= 65_535
55
- ? port
56
- : DEFAULT_LOCALBUDDY_PORT,
53
+ port: Number.isFinite(port) && port >= 1 && port <= 65_535 ? port : DEFAULT_LOCALBUDDY_PORT,
57
54
  },
58
55
  };
59
56
  }
@@ -155,7 +152,9 @@ function resolveRuntimeConfigDir(workspaceRoot: string, configuredDir?: string):
155
152
  }
156
153
 
157
154
  function parseBoolEnv(value: string | undefined): boolean | undefined {
158
- const text = String(value ?? "").trim().toLowerCase();
155
+ const text = String(value ?? "")
156
+ .trim()
157
+ .toLowerCase();
159
158
  if (!text) return undefined;
160
159
  if (TRUTHY.has(text)) return true;
161
160
  if (FALSY.has(text)) return false;
@@ -40,7 +40,9 @@ export type VisionDocValidation = {
40
40
  const MAX_KEY_ITEMS_PER_BUCKET = 8;
41
41
 
42
42
  function toLines(markdown: string): string[] {
43
- return String(markdown ?? "").replace(/\r\n/g, "\n").split("\n");
43
+ return String(markdown ?? "")
44
+ .replace(/\r\n/g, "\n")
45
+ .split("\n");
44
46
  }
45
47
 
46
48
  function extractOneSentence(lines: string[]): string {
@@ -75,7 +77,9 @@ function extractOneSentence(lines: string[]): string {
75
77
  }
76
78
 
77
79
  function normalizeItem(value: string): string {
78
- return String(value ?? "").replace(/\s+/g, " ").trim();
80
+ return String(value ?? "")
81
+ .replace(/\s+/g, " ")
82
+ .trim();
79
83
  }
80
84
 
81
85
  function dedupeAndClamp(values: string[]): string[] {
@@ -10,6 +10,7 @@ Output only the raw commit message text — no markdown fences, no explanation,
10
10
  - <specific implementation detail>
11
11
 
12
12
  Tests:
13
+
13
14
  - <test runner command>
14
15
 
15
16
  ## Writing rules
@@ -25,12 +26,14 @@ Background context: "can you add one more unit test for localbuddy"
25
26
 
26
27
  Bad (copies instruction / uses planning language):
27
28
  {{type}}({{area}}): lets add one more unit test for localbuddy
29
+
28
30
  - At least one new unit test is added validating a meaningful LocalBuddy behavior.
29
31
  - All existing and new tests pass.
30
32
  - No unrelated files are modified.
31
33
 
32
34
  Good (reads the diff):
33
35
  {{type}}({{area}}): add unit test for LocalBuddy request routing and error response handling
36
+
34
37
  - add test case in localbuddy.test.ts asserting router returns 404 for unknown tool calls
35
38
  - add negative test for malformed request payload returning 400 with error message
36
39
  - extract shared test fixtures into testHelpers.ts to reduce duplication
@@ -5,18 +5,19 @@ Repository root: {{repo}}
5
5
 
6
6
  Output format (STRICT JSON, no markdown, no extra keys unless specified):
7
7
  {
8
- "actions": [
9
- {"type":"read_file","path":"README.md"},
10
- {"type":"append_line","path":"README.md","line":"..."},
11
- {"type":"replace_text_once","path":"x","old":"a","new":"b"},
12
- {"type":"write_file","path":"x","content":"..."},
13
- {"type":"run_shell","command":"git status --porcelain"}
14
- ],
15
- "done": false,
16
- "note": "short explanation"
8
+ "actions": [
9
+ {"type":"read_file","path":"README.md"},
10
+ {"type":"append_line","path":"README.md","line":"..."},
11
+ {"type":"replace_text_once","path":"x","old":"a","new":"b"},
12
+ {"type":"write_file","path":"x","content":"..."},
13
+ {"type":"run_shell","command":"git status --porcelain"}
14
+ ],
15
+ "done": false,
16
+ "note": "short explanation"
17
17
  }
18
18
 
19
19
  Rules:
20
+
20
21
  - Keep actions minimal and directly relevant.
21
22
  - JSON syntax must be exact: use ":" between keys and values, never ",".
22
23
  - Use double quotes for all keys and string values.
@@ -1,4 +1,5 @@
1
1
  CRITICAL: You must use tools to make progress.
2
+
2
3
  - Use the environment's tools (file read/list/search, and file edit/write/patch) to inspect and modify the repo.
3
4
  - Do NOT only describe what you would do; actually do it.
4
5
  - Avoid broad scans; choose one target file quickly.
@@ -1,4 +1,5 @@
1
1
  Runtime policy guardrails (mandatory):
2
+
2
3
  - Codex CLI is required infrastructure in this environment.
3
4
  - Never bypass Codex usage by changing tests/code expectations.
4
5
  - If Codex CLI auth/execution is unavailable, hard-fail and stop.
@@ -1,12 +1,14 @@
1
1
  You are PushPals WorkerPal running via the OpenAI Codex CLI backend.
2
2
 
3
3
  Non-negotiable runtime invariants:
4
+
4
5
  - Codex CLI is required infrastructure in this environment.
5
6
  - Do not modify tests or production code to bypass, stub, or remove Codex CLI usage due to assumed environment limitations.
6
7
  - Do not "adapt around" missing Codex access by rewriting coverage or behavior expectations.
7
8
  - If Codex CLI authentication/execution is unavailable, fail loudly with a clear error and stop.
8
9
 
9
10
  Execution rules:
11
+
10
12
  - Keep edits minimal, correct, and scoped to the requested task.
11
13
  - Read relevant files before editing, then run focused validation.
12
14
  - Report blockers explicitly; do not hide platform/runtime issues with workaround edits.
@@ -2,8 +2,9 @@ You are a strict code-review critic for worker-generated patches.
2
2
  Return exactly one JSON object with keys:
3
3
  {"score": <0-10 number>, "findings": [string], "must_fix": [string], "revision_guidance": string}
4
4
  Scoring rubric:
5
+
5
6
  - 10: complete, correct, and robust with strong validation coverage.
6
7
  - 8-9: good quality with minor non-blocking issues.
7
8
  - <=7: requires revision before commit.
8
- must_fix must list blocking issues only.
9
- Do not include markdown or prose outside JSON.
9
+ must_fix must list blocking issues only.
10
+ Do not include markdown or prose outside JSON.
@@ -9,18 +9,21 @@
9
9
  ## 1) Who this is for
10
10
 
11
11
  ### Primary users
12
+
12
13
  - **User type A:** (e.g., app developers, SREs, analysts, end-users)
13
14
  - Jobs-to-be-done: …
14
15
  - Pain today: …
15
16
  - Success looks like: …
16
17
 
17
18
  ### Secondary users
19
+
18
20
  - **User type B:** …
19
21
  - Jobs-to-be-done: …
20
22
  - Pain today: …
21
23
  - Success looks like: …
22
24
 
23
- ### Non-users (explicitly *not* optimizing for)
25
+ ### Non-users (explicitly _not_ optimizing for)
26
+
24
27
  - **Not for:** …
25
28
  - Why: …
26
29
 
@@ -31,13 +34,15 @@
31
34
  ## 2) The problem we solve
32
35
 
33
36
  ### Today’s reality
37
+
34
38
  - What is hard / slow / risky today?
35
39
  - What failures happen repeatedly? (bugs, incidents, misconfig, confusion)
36
40
  - What is expensive? (time, money, cognitive load, coordination)
37
41
 
38
42
  ### The change we want
39
- - In 6–12 months, what should feel *meaningfully easier*?
40
- - In 23 years, what should be *obviously different*?
43
+
44
+ - In 612 months, what should feel _meaningfully easier_?
45
+ - In 2–3 years, what should be _obviously different_?
41
46
 
42
47
  > **Optional:** Add a 3–5 line “story” of a user before vs after.
43
48
 
@@ -66,11 +71,13 @@ These are **tie-breakers** when tradeoffs happen. Put them in priority order.
66
71
  Pick a small set of metrics you can actually track.
67
72
 
68
73
  ### User-facing outcomes
74
+
69
75
  - **Time-to-success:** e.g., median time from install → first successful use
70
76
  - **Quality:** e.g., bug rate / support tickets per active user
71
77
  - **Trust:** e.g., SLO compliance, error rate, crash-free sessions
72
78
 
73
79
  ### Developer / maintainer outcomes
80
+
74
81
  - **Change velocity:** PR cycle time, lead time to release
75
82
  - **Operational burden:** pages/alerts per week, toil hours
76
83
  - **Maintainability:** test coverage for critical paths, build time, flake rate
@@ -81,17 +88,20 @@ Pick a small set of metrics you can actually track.
81
88
 
82
89
  ## 5) Scope and boundaries
83
90
 
84
- ### In scope (what we *are*)
91
+ ### In scope (what we _are_)
92
+
85
93
  - Core capability A: …
86
94
  - Core capability B: …
87
95
  - Core capability C: …
88
96
 
89
- ### Out of scope / non-goals (what we are *not*)
97
+ ### Out of scope / non-goals (what we are _not_)
98
+
90
99
  - Not a replacement for: …
91
100
  - Not trying to support: …
92
101
  - Not optimizing for: …
93
102
 
94
103
  ### Compatibility & support policy (optional)
104
+
95
105
  - Supported platforms / versions: …
96
106
  - Breaking changes policy: …
97
107
  - Deprecation timeline: …
@@ -120,6 +130,7 @@ Pick 3–5 items max. Each should be **outcome-oriented**.
120
130
  These are “bets” with explicit results.
121
131
 
122
132
  ### Objective A: <name>
133
+
123
134
  - **Problem:** …
124
135
  - **Approach:** …
125
136
  - **Deliverables:** …
@@ -127,6 +138,7 @@ These are “bets” with explicit results.
127
138
  - **Exit criteria:** How we’ll know it worked (measurable)
128
139
 
129
140
  ### Objective B: <name>
141
+
130
142
  - …
131
143
 
132
144
  ---
@@ -136,6 +148,7 @@ These are “bets” with explicit results.
136
148
  Describe where this repo is going, without over-promising.
137
149
 
138
150
  ### Strategic bets
151
+
139
152
  - **Bet 1:** …
140
153
  - Why it matters: …
141
154
  - What we’ll likely build: …
@@ -143,6 +156,7 @@ Describe where this repo is going, without over-promising.
143
156
  - **Bet 2:** …
144
157
 
145
158
  ### “If we’re right, then…”
159
+
146
160
  - Users will be able to: …
147
161
  - Maintainers will spend less time on: …
148
162
  - The ecosystem will have: …
@@ -152,6 +166,7 @@ Describe where this repo is going, without over-promising.
152
166
  ## 9) Guardrails and constraints
153
167
 
154
168
  ### Guardrails (how we avoid harm / churn)
169
+
155
170
  - Prefer changes that are **reversible** or behind flags.
156
171
  - Default to **secure / safe** settings.
157
172
  - Optimize for the **common path**; support escape hatches for experts.
@@ -159,6 +174,7 @@ Describe where this repo is going, without over-promising.
159
174
  - Pay down operational toil before adding big surface area.
160
175
 
161
176
  ### Constraints (reality checks)
177
+
162
178
  - Staffing level / maintainer bandwidth: …
163
179
  - Hard requirements (privacy, compliance, perf, cost): …
164
180
  - External dependencies: …
@@ -181,11 +197,14 @@ Describe where this repo is going, without over-promising.
181
197
  ## Appendix (optional but powerful)
182
198
 
183
199
  ### A) Glossary
200
+
184
201
  - Term: definition…
185
202
 
186
203
  ### B) Personas (one-page each)
204
+
187
205
  - Persona, environment, constraints, success criteria…
188
206
 
189
207
  ### C) Example “no” responses (template)
208
+
190
209
  - “Thanks — this is valuable, but it conflicts with our non-goal X…”
191
210
  - “We’d reconsider if metric Y becomes a problem…”