ultimate-pi 0.20.0 → 0.22.1
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.
- package/.agents/skills/harness-context/SKILL.md +3 -3
- package/.agents/skills/harness-debate-plan/SKILL.md +2 -2
- package/.agents/skills/harness-decisions/SKILL.md +68 -2
- package/.agents/skills/harness-eval/SKILL.md +1 -1
- package/.agents/skills/harness-git-commit/SKILL.md +72 -0
- package/.agents/skills/harness-governor/SKILL.md +6 -6
- package/.agents/skills/harness-ls-lint-setup/SKILL.md +59 -0
- package/.agents/skills/harness-orchestration/SKILL.md +4 -4
- package/.agents/skills/harness-plan/SKILL.md +14 -12
- package/.agents/skills/harness-review/SKILL.md +3 -3
- package/.agents/skills/harness-sentrux-repair/SKILL.md +48 -0
- package/.agents/skills/harness-sentrux-setup/SKILL.md +2 -2
- package/.agents/skills/harness-spec/SKILL.md +1 -1
- package/.agents/skills/harness-steer/SKILL.md +2 -2
- package/.agents/skills/posthog-analyst/SKILL.md +1 -1
- package/.agents/skills/sentrux/SKILL.md +6 -4
- package/.agents/skills/web-retrieval/SKILL.md +1 -1
- package/.agents/skills/wiki-save/SKILL.md +1 -1
- package/.pi/PACKAGING.md +6 -0
- package/.pi/SYSTEM.md +21 -3
- package/.pi/agents/harness/ls-lint-steward.md +49 -0
- package/.pi/agents/harness/planning/decompose.md +5 -5
- package/.pi/agents/harness/planning/execution-plan-author.md +1 -1
- package/.pi/agents/harness/planning/hypothesis-validator.md +1 -1
- package/.pi/agents/harness/planning/hypothesis.md +1 -1
- package/.pi/agents/harness/planning/plan-adversary.md +1 -1
- package/.pi/agents/harness/planning/plan-evaluator.md +2 -2
- package/.pi/agents/harness/planning/plan-synthesizer.md +2 -2
- package/.pi/agents/harness/planning/review-integrator.md +1 -1
- package/.pi/agents/harness/planning/sprint-contract-auditor.md +5 -5
- package/.pi/agents/harness/reviewing/evaluator.md +1 -1
- package/.pi/agents/harness/running/executor.md +2 -2
- package/.pi/agents/harness/sentrux-repair-advisor.md +50 -0
- package/.pi/agents/harness/sentrux-steward.md +2 -2
- package/.pi/agents/pi-pi/prompt-expert.md +17 -2
- package/.pi/auto-commit.json +9 -2
- package/.pi/extensions/debate-orchestrator.ts +3 -0
- package/.pi/extensions/harness-anchored-edit.ts +7 -9
- package/.pi/extensions/harness-ask-user.ts +13 -34
- package/.pi/extensions/harness-debate-tools.ts +43 -4
- package/.pi/extensions/harness-live-widget.ts +28 -19
- package/.pi/extensions/harness-run-context.ts +278 -115
- package/.pi/extensions/harness-web-tools.ts +598 -471
- package/.pi/extensions/ls-lint-rules-sync.ts +103 -0
- package/.pi/extensions/observation-bus.ts +4 -0
- package/.pi/extensions/policy-gate.ts +270 -229
- package/.pi/extensions/sentrux-rules-sync.ts +2 -0
- package/.pi/extensions/soundboard.ts +48 -48
- package/.pi/harness/README.md +4 -0
- package/.pi/harness/agents.manifest.json +24 -16
- package/.pi/harness/agents.policy.yaml +49 -82
- package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
- package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
- package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
- package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
- package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
- package/.pi/harness/docs/adrs/README.md +5 -0
- package/.pi/harness/docs/practice-map.md +10 -5
- package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
- package/.pi/harness/evolution/self-healing-rules.json +16 -0
- package/.pi/harness/ls-lint/naming.manifest.json +128 -0
- package/.pi/harness/sentrux/architecture.manifest.json +1 -1
- package/.pi/harness/specs/auto-commit.schema.json +63 -0
- package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
- package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
- package/.pi/harness/specs/naming-manifest.schema.json +54 -0
- package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
- package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
- package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
- package/.pi/harness/specs/sentrux-report.schema.json +119 -0
- package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
- package/.pi/lib/agents-policy.d.mts +26 -51
- package/.pi/lib/agents-policy.mjs +41 -28
- package/.pi/lib/agt/build-evaluation-context.ts +136 -64
- package/.pi/lib/ask-user/constants.mjs +3 -0
- package/.pi/lib/ask-user/constants.ts +4 -0
- package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
- package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
- package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
- package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
- package/.pi/lib/ask-user/dialog.ts +2 -314
- package/.pi/lib/ask-user/fallback.ts +2 -78
- package/.pi/lib/ask-user/format.ts +85 -0
- package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
- package/.pi/lib/ask-user/index.ts +114 -0
- package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
- package/.pi/lib/ask-user/policy.mjs +43 -0
- package/.pi/lib/ask-user/policy.ts +104 -0
- package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
- package/.pi/lib/ask-user/presenters/headless.ts +131 -0
- package/.pi/lib/ask-user/presenters/select.ts +60 -0
- package/.pi/lib/ask-user/presenters/tui.ts +373 -0
- package/.pi/lib/ask-user/presenters/types.ts +13 -0
- package/.pi/lib/ask-user/render.ts +40 -9
- package/.pi/lib/ask-user/schema.ts +66 -13
- package/.pi/lib/ask-user/types.ts +60 -3
- package/.pi/lib/ask-user/validate-core.mjs +193 -7
- package/.pi/lib/ask-user/validate.ts +53 -34
- package/.pi/lib/harness-anchored-edit/package.json +3 -0
- package/.pi/lib/harness-artifact-gate.ts +75 -21
- package/.pi/lib/harness-auto-commit-config.mjs +321 -0
- package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
- package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
- package/.pi/lib/harness-lens/index.ts +241 -108
- package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
- package/.pi/lib/harness-repair-brief.ts +84 -25
- package/.pi/lib/harness-run-context.ts +42 -52
- package/.pi/lib/harness-sentrux-parse.mjs +272 -0
- package/.pi/lib/harness-sentrux-root.mjs +78 -0
- package/.pi/lib/harness-slash-completions.ts +116 -0
- package/.pi/lib/harness-spawn-topology.ts +121 -87
- package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
- package/.pi/lib/harness-subagents-bridge.ts +4 -1
- package/.pi/lib/harness-ui-state.ts +95 -48
- package/.pi/lib/plan-approval/dialog.ts +5 -0
- package/.pi/lib/plan-approval/validate.ts +1 -1
- package/.pi/lib/plan-approval-readiness.ts +32 -0
- package/.pi/lib/plan-debate-gate.ts +154 -114
- package/.pi/lib/plan-task-clarification.ts +158 -0
- package/.pi/prompts/harness-auto.md +2 -2
- package/.pi/prompts/harness-ls-lint-steward.md +43 -0
- package/.pi/prompts/harness-plan.md +63 -13
- package/.pi/prompts/harness-review.md +44 -10
- package/.pi/prompts/harness-run.md +35 -13
- package/.pi/prompts/harness-sentrux-steward.md +2 -2
- package/.pi/prompts/harness-setup.md +74 -5
- package/.pi/prompts/harness-steer.md +6 -5
- package/.pi/prompts/wiki-save.md +5 -4
- package/.pi/scripts/README.md +8 -0
- package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
- package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
- package/.pi/scripts/harness-cli-verify.sh +47 -0
- package/.pi/scripts/harness-git-churn.mjs +77 -0
- package/.pi/scripts/harness-git-commit.mjs +173 -0
- package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
- package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
- package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
- package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
- package/.pi/scripts/harness-sentrux-report.mjs +256 -0
- package/.pi/scripts/harness-verify.mjs +361 -125
- package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
- package/.pi/scripts/run-tests.mjs +1 -0
- package/.pi/settings.example.json +1 -0
- package/.sentrux/rules.toml +1 -1
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +32 -0
- package/README.md +13 -4
- package/package.json +13 -6
- package/vendor/pi-vcc/src/hooks/before-compact.ts +86 -60
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load and merge .pi/auto-commit.json (project overrides package).
|
|
3
|
+
* Format commit subjects and append Co-authored-by trailers.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFile, access } from "node:fs/promises";
|
|
7
|
+
import { constants } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
|
|
10
|
+
const TEMPLATE_PLACEHOLDERS = new Set(["type", "scope", "subject", "login", "email"]);
|
|
11
|
+
|
|
12
|
+
const DEFAULT_CO_AUTHOR = {
|
|
13
|
+
login: "pi-mono",
|
|
14
|
+
email: "261679550+pi-mono@users.noreply.github.com",
|
|
15
|
+
required: true,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const DEFAULT_MESSAGE = {
|
|
19
|
+
template: "{type}({scope}): {subject}",
|
|
20
|
+
templateNoScope: "{type}: {subject}",
|
|
21
|
+
typeDefault: "chore",
|
|
22
|
+
scopeDefault: "harness",
|
|
23
|
+
bodySeparator: "\n\n",
|
|
24
|
+
coAuthorTrailer: "Co-authored-by: {login} <{email}>",
|
|
25
|
+
maxSubjectLength: 72,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** @param {unknown} value */
|
|
29
|
+
function isPlainObject(value) {
|
|
30
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Deep-merge objects; arrays and scalars from override replace base.
|
|
35
|
+
* @param {Record<string, unknown>} base
|
|
36
|
+
* @param {Record<string, unknown>} override
|
|
37
|
+
*/
|
|
38
|
+
export function deepMerge(base, override) {
|
|
39
|
+
const out = { ...base };
|
|
40
|
+
for (const [key, val] of Object.entries(override)) {
|
|
41
|
+
if (
|
|
42
|
+
isPlainObject(val) &&
|
|
43
|
+
isPlainObject(out[key]) &&
|
|
44
|
+
!Array.isArray(val)
|
|
45
|
+
) {
|
|
46
|
+
out[key] = deepMerge(
|
|
47
|
+
/** @type {Record<string, unknown>} */ (out[key]),
|
|
48
|
+
/** @type {Record<string, unknown>} */ (val),
|
|
49
|
+
);
|
|
50
|
+
} else {
|
|
51
|
+
out[key] = val;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function readJsonIfExists(path) {
|
|
58
|
+
try {
|
|
59
|
+
await access(path, constants.R_OK);
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const raw = await readFile(path, "utf-8");
|
|
64
|
+
return JSON.parse(raw);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {string} template
|
|
69
|
+
*/
|
|
70
|
+
export function assertValidTemplate(template) {
|
|
71
|
+
const re = /\{([a-zA-Z_]+)\}/g;
|
|
72
|
+
let m;
|
|
73
|
+
while ((m = re.exec(template)) !== null) {
|
|
74
|
+
if (!TEMPLATE_PLACEHOLDERS.has(m[1])) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`auto-commit: unknown placeholder {${m[1]}} in template (allowed: ${[...TEMPLATE_PLACEHOLDERS].join(", ")})`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {Record<string, unknown>} config
|
|
84
|
+
*/
|
|
85
|
+
export function validateAutoCommitConfig(config) {
|
|
86
|
+
const coAuthor = /** @type {Record<string, unknown>} */ (
|
|
87
|
+
config.coAuthor ?? {}
|
|
88
|
+
);
|
|
89
|
+
const login = coAuthor.login;
|
|
90
|
+
const email = coAuthor.email;
|
|
91
|
+
if (typeof login !== "string" || !login.trim()) {
|
|
92
|
+
throw new Error("auto-commit: coAuthor.login is required");
|
|
93
|
+
}
|
|
94
|
+
if (typeof email !== "string" || !email.trim() || !email.includes("@")) {
|
|
95
|
+
throw new Error("auto-commit: coAuthor.email must be a valid email");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const message = /** @type {Record<string, unknown>} */ (
|
|
99
|
+
config.message ?? {}
|
|
100
|
+
);
|
|
101
|
+
const template = message.template;
|
|
102
|
+
if (typeof template !== "string" || !template.trim()) {
|
|
103
|
+
throw new Error("auto-commit: message.template is required");
|
|
104
|
+
}
|
|
105
|
+
assertValidTemplate(template);
|
|
106
|
+
const templateNoScope = message.templateNoScope;
|
|
107
|
+
if (templateNoScope != null) {
|
|
108
|
+
if (typeof templateNoScope !== "string" || !templateNoScope.trim()) {
|
|
109
|
+
throw new Error("auto-commit: message.templateNoScope must be non-empty");
|
|
110
|
+
}
|
|
111
|
+
assertValidTemplate(templateNoScope);
|
|
112
|
+
}
|
|
113
|
+
const trailer = message.coAuthorTrailer;
|
|
114
|
+
if (typeof trailer !== "string" || !trailer.trim()) {
|
|
115
|
+
throw new Error("auto-commit: message.coAuthorTrailer is required");
|
|
116
|
+
}
|
|
117
|
+
assertValidTemplate(trailer);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {string} projectRoot
|
|
122
|
+
* @param {string} upPkg
|
|
123
|
+
*/
|
|
124
|
+
export async function resolveAutoCommitConfig(projectRoot, upPkg) {
|
|
125
|
+
const pkgPath = join(upPkg, ".pi", "auto-commit.json");
|
|
126
|
+
const projectPath = join(projectRoot, ".pi", "auto-commit.json");
|
|
127
|
+
|
|
128
|
+
const pkgRaw = (await readJsonIfExists(pkgPath)) ?? {};
|
|
129
|
+
const projectRaw = (await readJsonIfExists(projectPath)) ?? {};
|
|
130
|
+
|
|
131
|
+
const base = {
|
|
132
|
+
dryRun: false,
|
|
133
|
+
coAuthor: { ...DEFAULT_CO_AUTHOR },
|
|
134
|
+
message: { ...DEFAULT_MESSAGE },
|
|
135
|
+
...(isPlainObject(pkgRaw) ? pkgRaw : {}),
|
|
136
|
+
};
|
|
137
|
+
const merged = deepMerge(
|
|
138
|
+
/** @type {Record<string, unknown>} */ (base),
|
|
139
|
+
/** @type {Record<string, unknown>} */ (
|
|
140
|
+
isPlainObject(projectRaw) ? projectRaw : {}
|
|
141
|
+
),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (!isPlainObject(merged.message)) {
|
|
145
|
+
merged.message = { ...DEFAULT_MESSAGE };
|
|
146
|
+
} else {
|
|
147
|
+
merged.message = { ...DEFAULT_MESSAGE, ...merged.message };
|
|
148
|
+
}
|
|
149
|
+
if (!isPlainObject(merged.coAuthor)) {
|
|
150
|
+
merged.coAuthor = { ...DEFAULT_CO_AUTHOR };
|
|
151
|
+
} else {
|
|
152
|
+
merged.coAuthor = { ...DEFAULT_CO_AUTHOR, ...merged.coAuthor };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
validateAutoCommitConfig(merged);
|
|
156
|
+
return merged;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @param {string} template
|
|
161
|
+
* @param {Record<string, string>} vars
|
|
162
|
+
*/
|
|
163
|
+
function applyTemplate(template, vars) {
|
|
164
|
+
return template.replace(/\{([a-zA-Z_]+)\}/g, (_, key) => vars[key] ?? "");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {Record<string, unknown>} config
|
|
169
|
+
* @param {{ type?: string, scope?: string, subject: string, body?: string }} input
|
|
170
|
+
*/
|
|
171
|
+
export function formatCommitMessage(config, input) {
|
|
172
|
+
const message = /** @type {Record<string, unknown>} */ (config.message);
|
|
173
|
+
const type =
|
|
174
|
+
(input.type ?? message.typeDefault ?? "chore").toString().trim() ||
|
|
175
|
+
"chore";
|
|
176
|
+
let scope = (input.scope ?? message.scopeDefault ?? "").toString().trim();
|
|
177
|
+
const subject = input.subject.trim();
|
|
178
|
+
if (!subject) {
|
|
179
|
+
throw new Error("auto-commit: subject is required");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const maxLen =
|
|
183
|
+
typeof message.maxSubjectLength === "number"
|
|
184
|
+
? message.maxSubjectLength
|
|
185
|
+
: 72;
|
|
186
|
+
let subjectLine = subject.split(/\r?\n/)[0] ?? subject;
|
|
187
|
+
if (subjectLine.length > maxLen) {
|
|
188
|
+
subjectLine = `${subjectLine.slice(0, maxLen - 3)}...`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const template =
|
|
192
|
+
scope.length > 0
|
|
193
|
+
? String(message.template)
|
|
194
|
+
: String(message.templateNoScope ?? message.template);
|
|
195
|
+
const subjectFormatted = applyTemplate(template, {
|
|
196
|
+
type,
|
|
197
|
+
scope,
|
|
198
|
+
subject: subjectLine,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const body = (input.body ?? "").trim();
|
|
202
|
+
const bodySep = String(message.bodySeparator ?? "\n\n");
|
|
203
|
+
if (!body) {
|
|
204
|
+
return subjectFormatted;
|
|
205
|
+
}
|
|
206
|
+
return `${subjectFormatted}${bodySep}${body}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Strip trailing co-authored-by lines from commit message body.
|
|
211
|
+
* @param {string} message
|
|
212
|
+
*/
|
|
213
|
+
export function stripCoAuthorTrailers(message) {
|
|
214
|
+
const lines = message.replace(/\r\n/g, "\n").split("\n");
|
|
215
|
+
while (lines.length > 0) {
|
|
216
|
+
const last = lines[lines.length - 1]?.trim() ?? "";
|
|
217
|
+
if (!last) {
|
|
218
|
+
lines.pop();
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (/^co-authored-by:/i.test(last)) {
|
|
222
|
+
lines.pop();
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
return lines.join("\n").trimEnd();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @param {Record<string, unknown>} coAuthor
|
|
232
|
+
* @param {string} trailerTemplate
|
|
233
|
+
*/
|
|
234
|
+
export function renderCoAuthorTrailer(coAuthor, trailerTemplate) {
|
|
235
|
+
return applyTemplate(trailerTemplate, {
|
|
236
|
+
login: String(coAuthor.login).trim(),
|
|
237
|
+
email: String(coAuthor.email).trim(),
|
|
238
|
+
type: "",
|
|
239
|
+
scope: "",
|
|
240
|
+
subject: "",
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* @param {string} message
|
|
246
|
+
* @param {Record<string, unknown>} coAuthor
|
|
247
|
+
* @param {string} trailerTemplate
|
|
248
|
+
*/
|
|
249
|
+
export function messageHasCoAuthorTrailer(message, coAuthor, trailerTemplate) {
|
|
250
|
+
const expected = renderCoAuthorTrailer(coAuthor, trailerTemplate)
|
|
251
|
+
.trim()
|
|
252
|
+
.toLowerCase();
|
|
253
|
+
const normalized = message.replace(/\r\n/g, "\n").toLowerCase();
|
|
254
|
+
return normalized.includes(expected);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* @param {string} message
|
|
259
|
+
* @param {Record<string, unknown>} config
|
|
260
|
+
*/
|
|
261
|
+
export function appendCoAuthorTrailer(message, config) {
|
|
262
|
+
const coAuthor = /** @type {Record<string, unknown>} */ (config.coAuthor);
|
|
263
|
+
const messageCfg = /** @type {Record<string, unknown>} */ (config.message);
|
|
264
|
+
const trailerTemplate = String(
|
|
265
|
+
messageCfg.coAuthorTrailer ?? DEFAULT_MESSAGE.coAuthorTrailer,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (coAuthor.required === false) {
|
|
269
|
+
return message;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const stripped = stripCoAuthorTrailers(message);
|
|
273
|
+
if (messageHasCoAuthorTrailer(stripped, coAuthor, trailerTemplate)) {
|
|
274
|
+
return stripped;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const trailer = renderCoAuthorTrailer(coAuthor, trailerTemplate);
|
|
278
|
+
if (!stripped) {
|
|
279
|
+
return trailer;
|
|
280
|
+
}
|
|
281
|
+
return `${stripped}\n\n${trailer}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Build final commit message (subject/body + trailer).
|
|
286
|
+
* @param {Record<string, unknown>} config
|
|
287
|
+
* @param {{ type?: string, scope?: string, subject?: string, body?: string, message?: string }} input
|
|
288
|
+
*/
|
|
289
|
+
export function buildFullCommitMessage(config, input) {
|
|
290
|
+
let core;
|
|
291
|
+
if (input.message != null && String(input.message).trim()) {
|
|
292
|
+
core = String(input.message).trim();
|
|
293
|
+
} else if (input.subject != null && String(input.subject).trim()) {
|
|
294
|
+
core = formatCommitMessage(config, {
|
|
295
|
+
type: input.type,
|
|
296
|
+
scope: input.scope,
|
|
297
|
+
subject: String(input.subject),
|
|
298
|
+
body: input.body,
|
|
299
|
+
});
|
|
300
|
+
} else {
|
|
301
|
+
throw new Error(
|
|
302
|
+
"auto-commit: provide --message or --subject for commit text",
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
return appendCoAuthorTrailer(core, config);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** @param {string} message */
|
|
309
|
+
export function splitSubjectAndBody(message) {
|
|
310
|
+
const normalized = message.replace(/\r\n/g, "\n");
|
|
311
|
+
const idx = normalized.indexOf("\n\n");
|
|
312
|
+
if (idx === -1) {
|
|
313
|
+
return { subject: normalized.trim(), body: "" };
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
subject: normalized.slice(0, idx).trim(),
|
|
317
|
+
body: normalized.slice(idx + 2).trim(),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export { DEFAULT_CO_AUTHOR, DEFAULT_MESSAGE, TEMPLATE_PLACEHOLDERS };
|
|
@@ -833,6 +833,60 @@ async function navRequest<T>(
|
|
|
833
833
|
}) as Promise<T | undefined>;
|
|
834
834
|
}
|
|
835
835
|
|
|
836
|
+
async function initializeLspOrThrow(input: {
|
|
837
|
+
connection: ReturnType<typeof createMessageConnection>;
|
|
838
|
+
root: string;
|
|
839
|
+
initialization?: Record<string, unknown>;
|
|
840
|
+
initializeTimeoutMs: number;
|
|
841
|
+
lspProcess: LSPProcess;
|
|
842
|
+
onStderr: (chunk: Buffer | string) => void;
|
|
843
|
+
}): Promise<Awaited<ReturnType<typeof safeSendRequest>>> {
|
|
844
|
+
const {
|
|
845
|
+
connection,
|
|
846
|
+
root,
|
|
847
|
+
initialization,
|
|
848
|
+
initializeTimeoutMs,
|
|
849
|
+
lspProcess,
|
|
850
|
+
onStderr,
|
|
851
|
+
} = input;
|
|
852
|
+
try {
|
|
853
|
+
return await withTimeout(
|
|
854
|
+
safeSendRequest(connection, "initialize", {
|
|
855
|
+
processId: process.pid,
|
|
856
|
+
rootUri: pathToFileURL(root).href,
|
|
857
|
+
workspaceFolders: [
|
|
858
|
+
{ name: "workspace", uri: pathToFileURL(root).href },
|
|
859
|
+
],
|
|
860
|
+
capabilities: {
|
|
861
|
+
window: { workDoneProgress: true },
|
|
862
|
+
workspace: {
|
|
863
|
+
workspaceFolders: true,
|
|
864
|
+
configuration: true,
|
|
865
|
+
didChangeWatchedFiles: { dynamicRegistration: true },
|
|
866
|
+
},
|
|
867
|
+
textDocument: {
|
|
868
|
+
synchronization: { didOpen: true, didChange: true },
|
|
869
|
+
publishDiagnostics: { versionSupport: true },
|
|
870
|
+
},
|
|
871
|
+
},
|
|
872
|
+
initializationOptions: initialization,
|
|
873
|
+
}),
|
|
874
|
+
initializeTimeoutMs,
|
|
875
|
+
);
|
|
876
|
+
} catch (err) {
|
|
877
|
+
const pid = lspProcess.pid;
|
|
878
|
+
void killProcessTree(lspProcess.process, pid);
|
|
879
|
+
setTimeout(() => {
|
|
880
|
+
if (!lspProcess.process.killed && process.platform !== "win32") {
|
|
881
|
+
lspProcess.process.kill("SIGKILL");
|
|
882
|
+
}
|
|
883
|
+
}, 2000);
|
|
884
|
+
throw err;
|
|
885
|
+
} finally {
|
|
886
|
+
(lspProcess.stderr as NodeJS.ReadableStream).off("data", onStderr);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
836
890
|
// --- Client Factory ---
|
|
837
891
|
|
|
838
892
|
export async function createLSPClient(options: {
|
|
@@ -987,45 +1041,14 @@ export async function createLSPClient(options: {
|
|
|
987
1041
|
connection.listen();
|
|
988
1042
|
setupConnectionLifecycle(state);
|
|
989
1043
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
],
|
|
999
|
-
capabilities: {
|
|
1000
|
-
window: { workDoneProgress: true },
|
|
1001
|
-
workspace: {
|
|
1002
|
-
workspaceFolders: true,
|
|
1003
|
-
configuration: true,
|
|
1004
|
-
didChangeWatchedFiles: { dynamicRegistration: true },
|
|
1005
|
-
},
|
|
1006
|
-
textDocument: {
|
|
1007
|
-
synchronization: { didOpen: true, didChange: true },
|
|
1008
|
-
publishDiagnostics: { versionSupport: true },
|
|
1009
|
-
},
|
|
1010
|
-
},
|
|
1011
|
-
initializationOptions: initialization,
|
|
1012
|
-
}),
|
|
1013
|
-
initializeTimeoutMs,
|
|
1014
|
-
);
|
|
1015
|
-
} catch (err) {
|
|
1016
|
-
// Hard-kill the hung process so it doesn't become a zombie.
|
|
1017
|
-
// SIGTERM alone is unreliable on Windows for cmd.exe/PowerShell trees.
|
|
1018
|
-
const pid = lspProcess.pid;
|
|
1019
|
-
void killProcessTree(lspProcess.process, pid);
|
|
1020
|
-
setTimeout(() => {
|
|
1021
|
-
if (!lspProcess.process.killed && process.platform !== "win32") {
|
|
1022
|
-
lspProcess.process.kill("SIGKILL");
|
|
1023
|
-
}
|
|
1024
|
-
}, 2000);
|
|
1025
|
-
throw err;
|
|
1026
|
-
} finally {
|
|
1027
|
-
(lspProcess.stderr as NodeJS.ReadableStream).off("data", onStderr);
|
|
1028
|
-
}
|
|
1044
|
+
const initResult = await initializeLspOrThrow({
|
|
1045
|
+
connection,
|
|
1046
|
+
root,
|
|
1047
|
+
initialization,
|
|
1048
|
+
initializeTimeoutMs,
|
|
1049
|
+
lspProcess,
|
|
1050
|
+
onStderr,
|
|
1051
|
+
});
|
|
1029
1052
|
|
|
1030
1053
|
if (initResult === undefined) {
|
|
1031
1054
|
const compactStderr = startupState.stderr
|