gentle-pi 2.2.0 → 2.3.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 (130) hide show
  1. package/README.md +51 -80
  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-proposal.md +2 -0
  8. package/assets/agents/sdd-research.md +54 -0
  9. package/assets/agents/sdd-status.md +4 -4
  10. package/assets/agents/sdd-tasks.md +2 -3
  11. package/assets/agents/sdd-verify.md +21 -1
  12. package/assets/chains/sdd-full.chain.md +1 -1
  13. package/assets/chains/sdd-verify.chain.md +1 -1
  14. package/assets/orchestrator-delegation.md +54 -248
  15. package/assets/orchestrator-memory.md +2 -0
  16. package/assets/orchestrator.md +22 -38
  17. package/assets/sdd-orchestrator-workflow.md +30 -37
  18. package/assets/support/sdd-status-contract.md +7 -7
  19. package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
  20. package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
  21. package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
  22. package/docs/native-authority-architecture.md +9 -11
  23. package/docs/review-integration.md +27 -373
  24. package/extensions/ask-user-choice.ts +151 -0
  25. package/extensions/gentle-ai.ts +1778 -3673
  26. package/extensions/quiet-tools.ts +515 -32
  27. package/extensions/sdd-init.ts +4 -8
  28. package/lib/gentle-ai-renderer.ts +70 -0
  29. package/lib/model-routing-authority.ts +133 -0
  30. package/lib/native-review-cli.ts +372 -940
  31. package/lib/opaque-pi-reviewer-adapter.ts +284 -0
  32. package/lib/review-candidate-view.ts +341 -132
  33. package/lib/review-host-relay.ts +210 -68
  34. package/lib/review-integration-v2.ts +839 -307
  35. package/lib/review-last-event-controller.ts +35 -0
  36. package/lib/sdd-preflight.ts +177 -66
  37. package/lib/sdd-status.ts +66 -111
  38. package/lib/terminal-theme.ts +1 -1
  39. package/package.json +83 -82
  40. package/runtime/gentle-ai-binary.mjs +1 -1
  41. package/runtime/native-review-cli.mjs +322 -890
  42. package/runtime/review-integration-v2.mjs +789 -257
  43. package/runtime/review-relay-contract.mjs +1 -1
  44. package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
  45. package/scripts/gentle-ai-installer.mjs +75 -22
  46. package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
  47. package/scripts/test-packed-runner.mjs +4 -7
  48. package/scripts/verify-package-files.mjs +12 -13
  49. package/skills/_shared/review-ledger-contract.md +8 -14
  50. package/skills/chained-pr/SKILL.md +3 -0
  51. package/skills/cognitive-doc-design/SKILL.md +1 -1
  52. package/skills/comment-writer/SKILL.md +1 -1
  53. package/skills/gentle-ai/SKILL.md +7 -74
  54. package/skills/judgment-day/SKILL.md +5 -7
  55. package/skills/rdd-defect-workflow/SKILL.md +3 -3
  56. package/skills/release/SKILL.md +3 -3
  57. package/skills/skill-registry/SKILL.md +1 -1
  58. package/skills/work-unit-commits/SKILL.md +3 -1
  59. package/tests/artifact-language.test.ts +24 -11
  60. package/tests/ask-user-choice.test.ts +264 -0
  61. package/tests/codegraph-tools.test.ts +3 -3
  62. package/tests/crosslane/cross-lane.mjs +15 -1168
  63. package/tests/delegated-key-learnings-contract.test.ts +8 -6
  64. package/tests/devbinary/native-review-parity.devtest.ts +167 -250
  65. package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
  66. package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
  67. package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
  68. package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
  69. package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
  70. package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
  71. package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
  72. package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
  73. package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
  74. package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
  75. package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
  76. package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
  77. package/tests/gentle-ai-binary.test.ts +83 -13
  78. package/tests/gentle-ai-dev-binary.test.ts +51 -228
  79. package/tests/gentle-ai-installer.test.ts +137 -47
  80. package/tests/gentle-ai.test.ts +820 -131
  81. package/tests/gentle-theme.test.ts +133 -0
  82. package/tests/maintainer/provider-relay.maintest.ts +340 -4
  83. package/tests/model-routing-authority.test.ts +257 -0
  84. package/tests/native-review-capability-contract.test.ts +28 -1
  85. package/tests/native-review-cli.test.ts +586 -962
  86. package/tests/native-review-consent.test.ts +150 -66
  87. package/tests/native-review-parity-runtime.test.ts +80 -359
  88. package/tests/native-review-parity.test.ts +674 -925
  89. package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
  90. package/tests/orchestrator-budget.test.ts +132 -88
  91. package/tests/orchestrator-rdd-ownership.test.ts +103 -0
  92. package/tests/package-manifest.test.ts +37 -35
  93. package/tests/provider-defect-handoff.test.ts +15 -118
  94. package/tests/quiet-tool-rendering.test.ts +1055 -28
  95. package/tests/review-authority-recovery-docs.test.ts +1 -2
  96. package/tests/review-candidate-view.test.ts +409 -3
  97. package/tests/review-compact-contract.test.ts +4 -30
  98. package/tests/review-controller-lock-status.test.ts +2 -2
  99. package/tests/review-controller-native-recovery.test.ts +363 -1450
  100. package/tests/review-controller-native-routing.test.ts +1251 -5642
  101. package/tests/review-controller-retired-ops.test.ts +1 -1
  102. package/tests/review-controller-workspace-root.test.ts +222 -71
  103. package/tests/review-controller.test.ts +26 -816
  104. package/tests/review-corrected-finalize-binding.test.ts +112 -153
  105. package/tests/review-dispatch-hydration-gap.test.ts +1 -53
  106. package/tests/review-gate.test.ts +0 -45
  107. package/tests/review-host-relay-restart-parity.test.ts +360 -0
  108. package/tests/review-host-relay-routing.test.ts +117 -82
  109. package/tests/review-host-relay.test.ts +242 -8
  110. package/tests/review-integration-v2-forward.test.ts +576 -553
  111. package/tests/review-integration-v2.test.ts +239 -165
  112. package/tests/review-last-event-closure.test.ts +408 -0
  113. package/tests/review-ledger-contract.test.ts +97 -35
  114. package/tests/review-recovered-lineage-routing.test.ts +0 -47
  115. package/tests/review-relay-transport-agent.test.ts +119 -46
  116. package/tests/review-snapshot.test.ts +3 -2
  117. package/tests/runtime-harness.mjs +361 -196
  118. package/tests/sdd-agent-tools.test.ts +36 -0
  119. package/tests/sdd-preflight.test.ts +81 -15
  120. package/tests/sdd-status.test.ts +109 -110
  121. package/tests/skill-collision-prefixes.test.ts +5 -8
  122. package/tests/writer-edit-surface-scope.test.ts +230 -0
  123. package/themes/Gentleman-Cute.json +94 -0
  124. package/themes/Gentleman-Sexy.json +92 -0
  125. package/lib/git-commit-transaction.ts +0 -861
  126. package/runtime/git-commit-transaction.mjs +0 -862
  127. package/scripts/run-git-commit-transaction.mjs +0 -35
  128. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
  129. package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
  130. package/tests/git-commit-transaction.test.ts +0 -530
@@ -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
+ }