gentle-pi 2.2.0 → 2.4.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 (160) hide show
  1. package/README.md +76 -79
  2. package/assets/agents/review-readability.md +0 -2
  3. package/assets/agents/review-reliability.md +0 -2
  4. package/assets/agents/review-resilience.md +0 -2
  5. package/assets/agents/review-risk.md +0 -2
  6. package/assets/agents/sdd-apply.md +5 -3
  7. package/assets/agents/sdd-explore.md +1 -0
  8. package/assets/agents/sdd-proposal.md +2 -0
  9. package/assets/agents/sdd-research.md +54 -0
  10. package/assets/agents/sdd-status.md +4 -4
  11. package/assets/agents/sdd-tasks.md +2 -3
  12. package/assets/agents/sdd-verify.md +21 -1
  13. package/assets/chains/sdd-full.chain.md +1 -1
  14. package/assets/chains/sdd-verify.chain.md +1 -1
  15. package/assets/orchestrator-delegation.md +56 -249
  16. package/assets/orchestrator-memory.md +2 -0
  17. package/assets/orchestrator.md +20 -40
  18. package/assets/sdd-orchestrator-workflow.md +30 -37
  19. package/assets/support/sdd-status-contract.md +7 -7
  20. package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
  21. package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
  22. package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
  23. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
  24. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/README.md +10 -0
  25. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/manifest.json +74 -0
  26. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/orchestration/pi.md +53 -0
  27. package/contracts/review-provider-contract-mirror/v1.2.0/bundle/schemas/targeted-validator.schema.json +1 -0
  28. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-capabilities.baseline.json +9 -2
  29. package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-roles.baseline.json +2 -2
  30. package/docs/native-authority-architecture.md +9 -11
  31. package/docs/review-integration.md +27 -373
  32. package/extensions/ask-user-choice.ts +151 -0
  33. package/extensions/gentle-ai.ts +2349 -3785
  34. package/extensions/quiet-tools.ts +515 -32
  35. package/extensions/sdd-init.ts +4 -8
  36. package/lib/agent-home.ts +8 -0
  37. package/lib/gentle-ai-renderer.ts +70 -0
  38. package/lib/model-routing-authority.ts +133 -0
  39. package/lib/native-review-cli.ts +387 -940
  40. package/lib/opaque-pi-reviewer-adapter.ts +284 -0
  41. package/lib/provider-contract-bundle.ts +88 -6
  42. package/lib/review-candidate-view.ts +341 -132
  43. package/lib/review-host-relay.ts +324 -96
  44. package/lib/review-integration-v2.ts +868 -307
  45. package/lib/review-last-event-controller.ts +35 -0
  46. package/lib/sdd-preflight.ts +179 -68
  47. package/lib/sdd-status.ts +66 -111
  48. package/lib/terminal-theme.ts +1 -1
  49. package/package.json +83 -82
  50. package/runtime/gentle-ai-binary.mjs +1 -1
  51. package/runtime/native-review-cli.mjs +337 -890
  52. package/runtime/review-integration-v2.mjs +816 -255
  53. package/runtime/review-relay-contract.mjs +1 -1
  54. package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
  55. package/scripts/gentle-ai-installer.mjs +75 -22
  56. package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
  57. package/scripts/test-packed-runner.mjs +4 -7
  58. package/scripts/verify-package-files.mjs +23 -23
  59. package/skills/_shared/review-ledger-contract.md +14 -14
  60. package/skills/chained-pr/SKILL.md +3 -0
  61. package/skills/cognitive-doc-design/SKILL.md +1 -1
  62. package/skills/comment-writer/SKILL.md +1 -1
  63. package/skills/gentle-ai/SKILL.md +7 -74
  64. package/skills/issue-creation/SKILL.md +53 -93
  65. package/skills/judgment-day/SKILL.md +5 -7
  66. package/skills/rdd-defect-workflow/SKILL.md +3 -3
  67. package/skills/release/SKILL.md +3 -3
  68. package/skills/skill-registry/SKILL.md +1 -1
  69. package/skills/work-unit-commits/SKILL.md +3 -1
  70. package/tests/artifact-language.test.ts +24 -11
  71. package/tests/ask-user-choice.test.ts +264 -0
  72. package/tests/codegraph-tools.test.ts +3 -3
  73. package/tests/crosslane/cross-lane.mjs +15 -1168
  74. package/tests/delegated-key-learnings-contract.test.ts +8 -6
  75. package/tests/devbinary/native-review-parity.devtest.ts +167 -250
  76. package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
  77. package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
  78. package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
  79. package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
  80. package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
  81. package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
  82. package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
  83. package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
  84. package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
  85. package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
  86. package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
  87. package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
  88. package/tests/fixtures/provider-contract-bundle/v1.2.0/README.md +22 -0
  89. package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/manifest.json +11 -2
  90. package/tests/fixtures/provider-contract-bundle/v1.2.0/orchestration/pi.md +97 -0
  91. package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/lens.schema.json +16 -0
  92. package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/refuter.schema.json +1 -0
  93. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/lens.json +1 -0
  94. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/refuter.json +1 -0
  95. package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/targeted-validator.json +1 -0
  96. package/tests/gentle-ai-binary.test.ts +83 -13
  97. package/tests/gentle-ai-dev-binary.test.ts +51 -228
  98. package/tests/gentle-ai-installer.test.ts +137 -47
  99. package/tests/gentle-ai.test.ts +821 -131
  100. package/tests/gentle-theme.test.ts +133 -0
  101. package/tests/issue-creation-skill.test.ts +103 -0
  102. package/tests/maintainer/provider-relay.maintest.ts +340 -4
  103. package/tests/model-routing-authority.test.ts +257 -0
  104. package/tests/native-review-capability-contract.test.ts +45 -1
  105. package/tests/native-review-cli.test.ts +586 -962
  106. package/tests/native-review-consent.test.ts +150 -66
  107. package/tests/native-review-parity-runtime.test.ts +80 -359
  108. package/tests/native-review-parity.test.ts +674 -925
  109. package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
  110. package/tests/orchestrator-budget.test.ts +132 -88
  111. package/tests/orchestrator-rdd-ownership.test.ts +103 -0
  112. package/tests/package-manifest.test.ts +152 -36
  113. package/tests/provider-contract-bundle.test.ts +76 -0
  114. package/tests/provider-contract-mirror.test.ts +19 -0
  115. package/tests/provider-defect-handoff.test.ts +15 -118
  116. package/tests/quiet-tool-rendering.test.ts +1055 -28
  117. package/tests/review-agent-end-preflight.test.ts +408 -0
  118. package/tests/review-authority-recovery-docs.test.ts +1 -2
  119. package/tests/review-candidate-view.test.ts +409 -3
  120. package/tests/review-compact-contract.test.ts +4 -30
  121. package/tests/review-contract-prompt.test.ts +139 -0
  122. package/tests/review-controller-lock-status.test.ts +2 -2
  123. package/tests/review-controller-native-recovery.test.ts +363 -1450
  124. package/tests/review-controller-native-routing.test.ts +1251 -5642
  125. package/tests/review-controller-retired-ops.test.ts +1 -1
  126. package/tests/review-controller-workspace-root.test.ts +222 -71
  127. package/tests/review-controller.test.ts +26 -816
  128. package/tests/review-corrected-finalize-binding.test.ts +112 -153
  129. package/tests/review-dispatch-hydration-gap.test.ts +1 -53
  130. package/tests/review-gate.test.ts +0 -45
  131. package/tests/review-host-relay-restart-parity.test.ts +360 -0
  132. package/tests/review-host-relay-routing.test.ts +328 -91
  133. package/tests/review-host-relay.test.ts +436 -14
  134. package/tests/review-integration-v2-forward.test.ts +623 -553
  135. package/tests/review-integration-v2.test.ts +239 -165
  136. package/tests/review-last-event-closure.test.ts +408 -0
  137. package/tests/review-ledger-contract.test.ts +97 -35
  138. package/tests/review-recovered-lineage-routing.test.ts +0 -47
  139. package/tests/review-relay-transport-agent.test.ts +119 -46
  140. package/tests/review-snapshot.test.ts +3 -2
  141. package/tests/runtime-harness.mjs +362 -196
  142. package/tests/sdd-agent-tools.test.ts +36 -0
  143. package/tests/sdd-preflight.test.ts +81 -15
  144. package/tests/sdd-status.test.ts +109 -110
  145. package/tests/skill-collision-prefixes.test.ts +5 -8
  146. package/tests/writer-edit-surface-scope.test.ts +366 -0
  147. package/themes/Gentleman-Cute.json +94 -0
  148. package/themes/Gentleman-Sexy.json +92 -0
  149. package/lib/git-commit-transaction.ts +0 -861
  150. package/runtime/git-commit-transaction.mjs +0 -862
  151. package/scripts/run-git-commit-transaction.mjs +0 -35
  152. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
  153. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
  154. package/tests/git-commit-transaction.test.ts +0 -530
  155. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/lens.schema.json +0 -0
  156. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/refuter.schema.json +0 -0
  157. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/lens.json +0 -0
  158. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/refuter.json +0 -0
  159. /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/targeted-validator.json +0 -0
  160. /package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/schemas/targeted-validator.schema.json +0 -0
@@ -0,0 +1,8 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+
4
+ // Pi Subagents resolves its global directory as `PI_CODING_AGENT_DIR || ~/.pi/agent`,
5
+ // so an empty value must fall through here too or the two homes diverge again.
6
+ export function resolveGentlePiAgentHome(env: NodeJS.ProcessEnv = process.env): string {
7
+ return env.GENTLE_PI_AGENT_HOME || env.PI_CODING_AGENT_DIR || join(homedir(), ".pi", "agent");
8
+ }
@@ -0,0 +1,70 @@
1
+ import { keyHint, type AgentToolResult } from "@earendil-works/pi-coding-agent";
2
+ import { Text } from "@earendil-works/pi-tui";
3
+ import { sanitizeTerminalText } from "./terminal-theme.ts";
4
+
5
+ type GentleAiLifecycleColor = "warning" | "success" | "error" | "dim";
6
+
7
+ export interface GentleAiRenderTheme {
8
+ bold(value: string): string;
9
+ fg(color: GentleAiLifecycleColor, value: string): string;
10
+ }
11
+
12
+ export interface GentleAiRenderState {
13
+ lifecycleComponent?: boolean; genericLocked?: boolean;
14
+ }
15
+
16
+ export interface GentleAiRenderContext {
17
+ argsComplete?: boolean;
18
+ executionStarted?: boolean;
19
+ isPartial?: boolean;
20
+ isError?: boolean;
21
+ lastComponent?: unknown;
22
+ state?: unknown;
23
+ }
24
+
25
+ export function getGentleAiRenderState(state: unknown): GentleAiRenderState | undefined {
26
+ if (!state || typeof state !== "object" || Array.isArray(state)) return undefined;
27
+ const rowState = state as Record<string, unknown>, existing = rowState.gentleAiRender;
28
+ if (existing && typeof existing === "object" && !Array.isArray(existing)) return existing as GentleAiRenderState;
29
+ return (rowState.gentleAiRender = {} as GentleAiRenderState);
30
+ }
31
+
32
+ export interface GentleAiResultRenderOptions {
33
+ expanded?: boolean;
34
+ }
35
+
36
+ export function renderGentleAiResult(
37
+ result: AgentToolResult<unknown>,
38
+ options: GentleAiResultRenderOptions,
39
+ ): Text {
40
+ const textItems = result.content.flatMap((content) => (content.type === "text" ? [sanitizeTerminalText(content.text)] : []));
41
+ const text = textItems.join("\n");
42
+ const hasExpandableText = textItems.some((item) => item.length > 0);
43
+ const output = !hasExpandableText ? "" : options.expanded ? text : keyHint("app.tools.expand", "to expand");
44
+ return new Text(output, 0, 0);
45
+ }
46
+
47
+ export function renderGentleAiLifecycleCall(
48
+ operationPath: string,
49
+ theme: GentleAiRenderTheme,
50
+ context?: GentleAiRenderContext,
51
+ detail?: string,
52
+ ): Text {
53
+ const status = context?.isError
54
+ ? "failed"
55
+ : context?.argsComplete === false
56
+ ? "preparing"
57
+ : !context?.executionStarted || context.isPartial
58
+ ? "running"
59
+ : "completed";
60
+ const color = status === "preparing" || status === "running" ? "warning" : status === "completed" ? "success" : "error";
61
+ const lines = [theme.fg(color, theme.bold(`🌹︎ Gentle AI · ${status} · ${operationPath}`))];
62
+ if (detail) lines.push(theme.fg("dim", sanitizeTerminalText(detail)));
63
+ const state = getGentleAiRenderState(context?.state);
64
+ const component = context?.lastComponent instanceof Text && (!state || state.lifecycleComponent === true)
65
+ ? context.lastComponent
66
+ : new Text("", 0, 0);
67
+ if (state) state.lifecycleComponent = true;
68
+ component.setText(lines.join("\n"));
69
+ return component;
70
+ }
@@ -0,0 +1,133 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { access, readFile } from "node:fs/promises";
3
+
4
+ export const THINKING_LEVELS = [
5
+ "off",
6
+ "minimal",
7
+ "low",
8
+ "medium",
9
+ "high",
10
+ "xhigh",
11
+ "max",
12
+ ] as const;
13
+
14
+ export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
15
+
16
+ export interface AgentRoutingEntry {
17
+ model?: string;
18
+ thinking?: ThinkingLevel;
19
+ }
20
+
21
+ export type AgentModelConfig = Record<string, AgentRoutingEntry>;
22
+
23
+ export type ModelConfigFileResult =
24
+ | { status: "missing" }
25
+ | { status: "invalid"; path: string }
26
+ | { status: "valid"; config: AgentModelConfig };
27
+
28
+ const SAFE_MODEL_ID_PATTERN = /^[A-Za-z0-9._~:@/+%-]+$/;
29
+ const SAFE_AGENT_NAME_PATTERN = /^[A-Za-z0-9._:@/+%-]+$/;
30
+
31
+ function isRecord(value: unknown): value is Record<string, unknown> {
32
+ return typeof value === "object" && value !== null && !Array.isArray(value);
33
+ }
34
+
35
+ async function pathExists(path: string): Promise<boolean> {
36
+ try {
37
+ await access(path);
38
+ return true;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ export function isThinkingLevel(value: unknown): value is ThinkingLevel {
45
+ return (
46
+ typeof value === "string" &&
47
+ (THINKING_LEVELS as readonly string[]).includes(value)
48
+ );
49
+ }
50
+
51
+ export function normalizeModelId(value: unknown): string | undefined {
52
+ if (typeof value !== "string") return undefined;
53
+ const model = value.trim();
54
+ if (model.length === 0) return undefined;
55
+ if (!SAFE_MODEL_ID_PATTERN.test(model)) return undefined;
56
+ return model;
57
+ }
58
+
59
+ export function normalizeRoutingEntry(value: unknown): AgentRoutingEntry | undefined {
60
+ if (typeof value === "string") {
61
+ const model = normalizeModelId(value);
62
+ return model ? { model } : undefined;
63
+ }
64
+ if (!isRecord(value)) return undefined;
65
+ const model = normalizeModelId(value.model);
66
+ const thinking = isThinkingLevel(value.thinking) ? value.thinking : undefined;
67
+ if (!model && !thinking) {
68
+ return Object.keys(value).length === 0 ? {} : undefined;
69
+ }
70
+ return { model, thinking };
71
+ }
72
+
73
+ export function normalizeModelConfig(value: unknown): AgentModelConfig | undefined {
74
+ if (!isRecord(value)) return undefined;
75
+ const cleaned: AgentModelConfig = {};
76
+ for (const [name, entryValue] of Object.entries(value)) {
77
+ if (!SAFE_AGENT_NAME_PATTERN.test(name)) continue;
78
+ const entry = normalizeRoutingEntry(entryValue);
79
+ if (entry) cleaned[name] = entry;
80
+ }
81
+ return cleaned;
82
+ }
83
+
84
+ function parseModelConfigFileValue(value: Record<string, unknown>): AgentModelConfig {
85
+ const config: AgentModelConfig = {};
86
+ for (const [name, entryValue] of Object.entries(value)) {
87
+ const entry = normalizeRoutingEntry(entryValue);
88
+ if (entry) config[name] = entry;
89
+ }
90
+ return config;
91
+ }
92
+
93
+ export function readModelConfigFile(path: string): ModelConfigFileResult {
94
+ if (!existsSync(path)) return { status: "missing" };
95
+ try {
96
+ const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
97
+ if (!isRecord(parsed)) return { status: "invalid", path };
98
+ return { status: "valid", config: parseModelConfigFileValue(parsed) };
99
+ } catch {
100
+ return { status: "invalid", path };
101
+ }
102
+ }
103
+
104
+ export async function readModelConfigFileAsync(
105
+ path: string,
106
+ ): Promise<ModelConfigFileResult> {
107
+ if (!(await pathExists(path))) return { status: "missing" };
108
+ try {
109
+ const parsed: unknown = JSON.parse(await readFile(path, "utf8"));
110
+ if (!isRecord(parsed)) return { status: "invalid", path };
111
+ return { status: "valid", config: parseModelConfigFileValue(parsed) };
112
+ } catch {
113
+ return { status: "invalid", path };
114
+ }
115
+ }
116
+
117
+ export function readSavedModelConfig(
118
+ globalPath: string,
119
+ projectPath: string,
120
+ ): ModelConfigFileResult {
121
+ const globalResult = readModelConfigFile(globalPath);
122
+ if (globalResult.status !== "missing") return globalResult;
123
+ return readModelConfigFile(projectPath);
124
+ }
125
+
126
+ export async function readSavedModelConfigAsync(
127
+ globalPath: string,
128
+ projectPath: string,
129
+ ): Promise<ModelConfigFileResult> {
130
+ const globalResult = await readModelConfigFileAsync(globalPath);
131
+ if (globalResult.status !== "missing") return globalResult;
132
+ return readModelConfigFileAsync(projectPath);
133
+ }