@basou/core 0.12.0 → 0.13.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/dist/index.d.ts +1104 -10
- package/dist/index.js +965 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/event.schema.json +7 -0
- package/schemas/manifest.schema.json +92 -9
- package/schemas/session-import.schema.json +7 -0
package/dist/index.js
CHANGED
|
@@ -102,6 +102,14 @@ function sumDurations(intervals) {
|
|
|
102
102
|
return total;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
// src/adapters/session-label.ts
|
|
106
|
+
function sessionLabelDateSpan(startIso, endIso) {
|
|
107
|
+
const a = startIso.slice(0, 10);
|
|
108
|
+
const b = endIso.slice(0, 10);
|
|
109
|
+
if (a === b) return a;
|
|
110
|
+
return a < b ? `${a}..${b}` : `${b}..${a}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
105
113
|
// src/adapters/claude-code/transcript-importer.ts
|
|
106
114
|
var CLAUDE_IMPORT_SOURCE = "claude-code-import";
|
|
107
115
|
function claudeTranscriptToImportPayload(records, options) {
|
|
@@ -201,8 +209,7 @@ function claudeTranscriptToImportPayload(records, options) {
|
|
|
201
209
|
const externalId = options.externalId ?? claudeSessionId;
|
|
202
210
|
const commandCount = derived.reduce((n, e) => e.type === "command_executed" ? n + 1 : n, 0);
|
|
203
211
|
const fileCount = relatedFiles.size;
|
|
204
|
-
const
|
|
205
|
-
const label = `claude-code ${date}: ${commandCount} ${commandCount === 1 ? "command" : "commands"}, ${fileCount} ${fileCount === 1 ? "file" : "files"}`;
|
|
212
|
+
const label = `claude-code ${sessionLabelDateSpan(minTs, maxTs)}: ${commandCount} ${commandCount === 1 ? "command" : "commands"}, ${fileCount} ${fileCount === 1 ? "file" : "files"}`;
|
|
206
213
|
const active = engagementTsMs.length >= 2 ? activeTimeFromTimestamps(engagementTsMs, ACTIVE_GAP_CAP_MS) : void 0;
|
|
207
214
|
const metricsFields = {
|
|
208
215
|
...outputTokens > 0 ? { output_tokens: outputTokens } : {},
|
|
@@ -414,8 +421,7 @@ function codexRolloutToImportPayload(records, options) {
|
|
|
414
421
|
];
|
|
415
422
|
const externalId = options.externalId ?? codexSessionId;
|
|
416
423
|
const commandCount = derived.length;
|
|
417
|
-
const
|
|
418
|
-
const label = `codex ${date}: ${commandCount} ${commandCount === 1 ? "command" : "commands"}`;
|
|
424
|
+
const label = `codex ${sessionLabelDateSpan(minTs, maxTs)}: ${commandCount} ${commandCount === 1 ? "command" : "commands"}`;
|
|
419
425
|
const minTsMs = Date.parse(minTs);
|
|
420
426
|
const turnIntervals = [];
|
|
421
427
|
let machineActiveMs = 0;
|
|
@@ -905,7 +911,12 @@ var TaskArchivedEventSchema = BaseEventSchema.extend({
|
|
|
905
911
|
}).strict();
|
|
906
912
|
var NoteAddedEventSchema = BaseEventSchema.extend({
|
|
907
913
|
type: z3.literal("note_added"),
|
|
908
|
-
body: z3.string()
|
|
914
|
+
body: z3.string(),
|
|
915
|
+
// `next_step` marks a note authored by `basou note` as the operator's resume
|
|
916
|
+
// hint, which orientation surfaces as the next starting point. Absent (the
|
|
917
|
+
// `basou session note` default) is a plain annotation orientation does not
|
|
918
|
+
// surface. Optional so pre-existing note_added events remain valid.
|
|
919
|
+
kind: z3.enum(["note", "next_step"]).optional()
|
|
909
920
|
});
|
|
910
921
|
var AdapterOutputEventSchema = BaseEventSchema.extend({
|
|
911
922
|
type: z3.literal("adapter_output"),
|
|
@@ -4154,42 +4165,64 @@ import { lstat as lstat3 } from "fs/promises";
|
|
|
4154
4165
|
|
|
4155
4166
|
// src/schemas/manifest.schema.ts
|
|
4156
4167
|
import { z as z9 } from "zod";
|
|
4157
|
-
var ProjectSchema = z9.
|
|
4168
|
+
var ProjectSchema = z9.looseObject({
|
|
4158
4169
|
name: z9.string().optional(),
|
|
4159
4170
|
description: z9.string().optional(),
|
|
4160
4171
|
repository_url: z9.string().nullable().optional()
|
|
4161
4172
|
});
|
|
4162
|
-
var CapabilitiesSchema = z9.
|
|
4173
|
+
var CapabilitiesSchema = z9.looseObject({
|
|
4163
4174
|
enabled: z9.array(z9.string())
|
|
4164
4175
|
});
|
|
4165
|
-
var ApprovalConfigSchema = z9.
|
|
4176
|
+
var ApprovalConfigSchema = z9.looseObject({
|
|
4166
4177
|
required_for: z9.array(z9.string()).optional(),
|
|
4167
4178
|
default_risk_level: z9.enum(["low", "medium", "high", "critical"])
|
|
4168
4179
|
});
|
|
4169
|
-
var ClaudeCodeAdapterConfigSchema = z9.
|
|
4180
|
+
var ClaudeCodeAdapterConfigSchema = z9.looseObject({
|
|
4170
4181
|
enabled: z9.boolean(),
|
|
4171
4182
|
config_path: z9.string().optional()
|
|
4172
4183
|
});
|
|
4173
|
-
var AdaptersSchema = z9.
|
|
4184
|
+
var AdaptersSchema = z9.looseObject({
|
|
4174
4185
|
"claude-code": ClaudeCodeAdapterConfigSchema
|
|
4175
4186
|
});
|
|
4176
|
-
var GitConfigSchema = z9.
|
|
4187
|
+
var GitConfigSchema = z9.looseObject({
|
|
4177
4188
|
events_log: z9.enum(["ignore", "commit"]).default("ignore")
|
|
4178
4189
|
});
|
|
4179
|
-
var SOURCE_ROOT_PATTERN = /^(?![~/\\])(?![A-Za-z]:)[^\0\\]
|
|
4190
|
+
var SOURCE_ROOT_PATTERN = /^(?![~/\\])(?![A-Za-z]:)(?!\s)[^\0\\]*[^\0\\\s]$/;
|
|
4180
4191
|
var SourceRootSchema = z9.string().min(1).regex(SOURCE_ROOT_PATTERN, {
|
|
4181
4192
|
message: "source_roots entries must be relative paths (no absolute path, '~', '\\', or null byte)"
|
|
4182
4193
|
});
|
|
4183
|
-
var ImportConfigSchema = z9.
|
|
4194
|
+
var ImportConfigSchema = z9.looseObject({
|
|
4184
4195
|
source_roots: z9.array(SourceRootSchema).min(1).optional()
|
|
4185
4196
|
});
|
|
4186
|
-
var
|
|
4197
|
+
var RepoVisibilitySchema = z9.enum(["public", "private", "future-public"]);
|
|
4198
|
+
var RepoLanguageSchema = z9.enum(["en", "ja", "en+ja"]);
|
|
4199
|
+
var PublishKindSchema = z9.enum(["web", "npm"]);
|
|
4200
|
+
var PublishTargetSchema = z9.looseObject({
|
|
4201
|
+
kind: PublishKindSchema,
|
|
4202
|
+
visibility: RepoVisibilitySchema.optional(),
|
|
4203
|
+
language: RepoLanguageSchema.optional()
|
|
4204
|
+
});
|
|
4205
|
+
var RepoEntrySchema = z9.looseObject({
|
|
4206
|
+
path: SourceRootSchema,
|
|
4207
|
+
visibility: RepoVisibilitySchema.optional(),
|
|
4208
|
+
language: RepoLanguageSchema.optional(),
|
|
4209
|
+
publishes: z9.array(PublishTargetSchema).optional()
|
|
4210
|
+
});
|
|
4211
|
+
var WorkspaceMetaSchema = z9.looseObject({
|
|
4187
4212
|
id: WorkspaceIdSchema,
|
|
4188
4213
|
name: z9.string().min(1),
|
|
4189
4214
|
created_at: IsoTimestampSchema,
|
|
4190
|
-
updated_at: IsoTimestampSchema
|
|
4215
|
+
updated_at: IsoTimestampSchema,
|
|
4216
|
+
/**
|
|
4217
|
+
* The generated workspace view: a throwaway directory that aggregates the
|
|
4218
|
+
* roster repos via symlinks (one `<repo-basename>` symlink per repo). A path
|
|
4219
|
+
* relative to the manifest root, reusing the machine-portable source-root
|
|
4220
|
+
* constraint. Absent for a solo project (no view needed); `basou project
|
|
4221
|
+
* workspace` reconciles the view's symlinks to the declared roster.
|
|
4222
|
+
*/
|
|
4223
|
+
view: SourceRootSchema.optional()
|
|
4191
4224
|
});
|
|
4192
|
-
var ManifestSchema = z9.
|
|
4225
|
+
var ManifestSchema = z9.looseObject({
|
|
4193
4226
|
schema_version: SchemaVersionSchema,
|
|
4194
4227
|
basou_version: z9.literal("0.1.0"),
|
|
4195
4228
|
workspace: WorkspaceMetaSchema,
|
|
@@ -4198,8 +4231,13 @@ var ManifestSchema = z9.object({
|
|
|
4198
4231
|
approval: ApprovalConfigSchema,
|
|
4199
4232
|
adapters: AdaptersSchema,
|
|
4200
4233
|
git: GitConfigSchema,
|
|
4201
|
-
import: ImportConfigSchema.optional()
|
|
4234
|
+
import: ImportConfigSchema.optional(),
|
|
4235
|
+
repos: z9.array(RepoEntrySchema).min(1).optional()
|
|
4202
4236
|
});
|
|
4237
|
+
var KNOWN_TOP_LEVEL_KEYS = new Set(Object.keys(ManifestSchema.shape));
|
|
4238
|
+
function unknownManifestKeys(manifest) {
|
|
4239
|
+
return Object.keys(manifest).filter((k) => !KNOWN_TOP_LEVEL_KEYS.has(k)).sort();
|
|
4240
|
+
}
|
|
4203
4241
|
|
|
4204
4242
|
// src/storage/manifest.ts
|
|
4205
4243
|
function createManifest(input) {
|
|
@@ -4267,6 +4305,7 @@ function hasErrorCode3(error) {
|
|
|
4267
4305
|
}
|
|
4268
4306
|
|
|
4269
4307
|
// src/orientation/orientation-renderer.ts
|
|
4308
|
+
var DECISION_TRAILING_ACTIVITY_GAP_MS = 60 * 60 * 1e3;
|
|
4270
4309
|
async function summarizeOrientation(input) {
|
|
4271
4310
|
const limit = input.relatedFilesLimit ?? 10;
|
|
4272
4311
|
const now = new Date(input.nowIso);
|
|
@@ -4275,8 +4314,17 @@ async function summarizeOrientation(input) {
|
|
|
4275
4314
|
if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
|
|
4276
4315
|
const entries = await loadSessionEntries(input.paths, loadOpts);
|
|
4277
4316
|
const decisions = [];
|
|
4317
|
+
let latestActivityAt = null;
|
|
4318
|
+
let latestNote = null;
|
|
4319
|
+
const noteActivity = (iso) => {
|
|
4320
|
+
if (latestActivityAt === null || Date.parse(iso) > Date.parse(latestActivityAt)) {
|
|
4321
|
+
latestActivityAt = iso;
|
|
4322
|
+
}
|
|
4323
|
+
};
|
|
4278
4324
|
for (const entry of entries) {
|
|
4279
4325
|
const sessionDir = join14(input.paths.sessions, entry.sessionId);
|
|
4326
|
+
const counted = entry.session.session.status !== "archived";
|
|
4327
|
+
if (counted) noteActivity(entry.session.session.ended_at ?? entry.session.session.started_at);
|
|
4280
4328
|
try {
|
|
4281
4329
|
for await (const ev of replayEvents(sessionDir, {
|
|
4282
4330
|
onWarning: (w) => input.onWarning?.(w, entry.sessionId)
|
|
@@ -4288,6 +4336,12 @@ async function summarizeOrientation(input) {
|
|
|
4288
4336
|
occurredAt: ev.occurred_at
|
|
4289
4337
|
});
|
|
4290
4338
|
}
|
|
4339
|
+
if (counted && ev.type === "note_added" && ev.kind === "next_step") {
|
|
4340
|
+
if (latestNote === null || Date.parse(ev.occurred_at) > Date.parse(latestNote.occurredAt)) {
|
|
4341
|
+
latestNote = { body: ev.body, sessionId: entry.sessionId, occurredAt: ev.occurred_at };
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
if (counted) noteActivity(ev.occurred_at);
|
|
4291
4345
|
}
|
|
4292
4346
|
} catch {
|
|
4293
4347
|
input.onSessionSkip?.(entry.sessionId, "events_jsonl_unreadable");
|
|
@@ -4367,6 +4421,7 @@ async function summarizeOrientation(input) {
|
|
|
4367
4421
|
latestSession,
|
|
4368
4422
|
latestDecision: latestDecision ?? null,
|
|
4369
4423
|
decisionCount: decisions.length,
|
|
4424
|
+
latestNote,
|
|
4370
4425
|
relatedFiles: { displayed, overflow },
|
|
4371
4426
|
inFlightTasks,
|
|
4372
4427
|
plannedTasks,
|
|
@@ -4375,6 +4430,7 @@ async function summarizeOrientation(input) {
|
|
|
4375
4430
|
freshness: {
|
|
4376
4431
|
newestStartedAt: newest?.session.session.started_at ?? null,
|
|
4377
4432
|
newestSource: newest?.session.session.source.kind ?? null,
|
|
4433
|
+
latestActivityAt,
|
|
4378
4434
|
bySource,
|
|
4379
4435
|
sourceRoots
|
|
4380
4436
|
}
|
|
@@ -4418,9 +4474,16 @@ function formatOrientationBody(summary, opts) {
|
|
|
4418
4474
|
lines.push("- \u6700\u7D42 session: (no live sessions)");
|
|
4419
4475
|
}
|
|
4420
4476
|
if (summary.latestDecision !== null) {
|
|
4477
|
+
const decAge = relativeAgeJa(summary.latestDecision.occurredAt, now);
|
|
4421
4478
|
lines.push(
|
|
4422
|
-
`- \u76F4\u8FD1\u306E\u5224\u65AD: ${summary.latestDecision.title} [${shortId(summary.latestDecision.decisionId)}]`
|
|
4479
|
+
`- \u76F4\u8FD1\u306E\u5224\u65AD: ${summary.latestDecision.title} [${shortId(summary.latestDecision.decisionId)}] (${decAge})`
|
|
4423
4480
|
);
|
|
4481
|
+
const activityAt = summary.freshness.latestActivityAt;
|
|
4482
|
+
if (activityAt !== null && Date.parse(activityAt) - Date.parse(summary.latestDecision.occurredAt) > DECISION_TRAILING_ACTIVITY_GAP_MS) {
|
|
4483
|
+
lines.push(
|
|
4484
|
+
` - \u6CE8: \u3053\u308C\u306F\u6700\u5F8C\u306B\u300C\u8A18\u9332\u3055\u308C\u305F\u300D\u5224\u65AD\u3067\u3059\u3002\u6700\u7D42\u6D3B\u52D5 (${relativeAgeJa(activityAt, now)}) \u306F\u3053\u308C\u3088\u308A\u5F8C\u306E\u305F\u3081\u3001\u73FE\u5728\u306E\u65B9\u91DD\u304C\u53CD\u6620\u3055\u308C\u3066\u3044\u306A\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059(\u4F1A\u8A71\u3067\u306E\u610F\u601D\u6C7A\u5B9A\u306F\u81EA\u52D5\u8A18\u9332\u3055\u308C\u307E\u305B\u3093)\u3002`
|
|
4485
|
+
);
|
|
4486
|
+
}
|
|
4424
4487
|
if (summary.decisionCount > 1) {
|
|
4425
4488
|
lines.push(` - ${summary.decisionCount} decisions total \u2014 see decisions.md`);
|
|
4426
4489
|
}
|
|
@@ -4470,15 +4533,26 @@ function formatOrientationBody(summary, opts) {
|
|
|
4470
4533
|
lines.push("");
|
|
4471
4534
|
lines.push("## \u3069\u3053\u3078\u5411\u304B\u3046");
|
|
4472
4535
|
lines.push("");
|
|
4473
|
-
if (summary.
|
|
4536
|
+
if (summary.latestNote !== null) {
|
|
4537
|
+
const noteAge = relativeAgeJa(summary.latestNote.occurredAt, now);
|
|
4538
|
+
lines.push(
|
|
4539
|
+
`- \u6B21\u306E\u8D77\u70B9 (\u8A18\u9332\u6E08\u307F, ${noteAge}): ${noteSummary(summary.latestNote.body)} [session ${shortId(summary.latestNote.sessionId)}]`
|
|
4540
|
+
);
|
|
4541
|
+
const activityAt = summary.freshness.latestActivityAt;
|
|
4542
|
+
if (activityAt !== null && Date.parse(activityAt) - Date.parse(summary.latestNote.occurredAt) > DECISION_TRAILING_ACTIVITY_GAP_MS) {
|
|
4543
|
+
lines.push(
|
|
4544
|
+
` - \u6CE8: \u3053\u306E\u8D77\u70B9\u306E\u8A18\u9332\u5F8C (\u6700\u7D42\u6D3B\u52D5 ${relativeAgeJa(activityAt, now)}) \u3082\u4F5C\u696D\u304C\u7D9A\u3044\u3066\u3044\u307E\u3059\u3002\u518D\u958B\u70B9\u304C\u53E4\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002`
|
|
4545
|
+
);
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
for (const t of summary.plannedTasks) {
|
|
4549
|
+
lines.push(`- ${t.title} [${shortId(t.id)}]`);
|
|
4550
|
+
}
|
|
4551
|
+
if (summary.latestNote === null && summary.plannedTasks.length === 0) {
|
|
4474
4552
|
lines.push("- (no planned tasks \u2014 direction is inferred from recent decisions)");
|
|
4475
4553
|
if (summary.latestDecision !== null) {
|
|
4476
4554
|
lines.push(` - \u76F4\u8FD1\u306E\u5224\u65AD: ${summary.latestDecision.title}`);
|
|
4477
4555
|
}
|
|
4478
|
-
} else {
|
|
4479
|
-
for (const t of summary.plannedTasks) {
|
|
4480
|
-
lines.push(`- ${t.title} [${shortId(t.id)}]`);
|
|
4481
|
-
}
|
|
4482
4556
|
}
|
|
4483
4557
|
lines.push("");
|
|
4484
4558
|
lines.push("## \u3053\u308C\u306F\u6700\u65B0\u304B");
|
|
@@ -4492,6 +4566,11 @@ function formatOrientationBody(summary, opts) {
|
|
|
4492
4566
|
} else {
|
|
4493
4567
|
lines.push("- newest captured session: (no sessions captured yet)");
|
|
4494
4568
|
}
|
|
4569
|
+
if (summary.freshness.latestActivityAt !== null) {
|
|
4570
|
+
lines.push(
|
|
4571
|
+
`- latest activity: ${summary.freshness.latestActivityAt} (${relativeAge(summary.freshness.latestActivityAt, now)})`
|
|
4572
|
+
);
|
|
4573
|
+
}
|
|
4495
4574
|
const sourceBreakdown = summary.freshness.bySource.map(({ kind, count }) => `${kind} ${count}`).join(", ");
|
|
4496
4575
|
lines.push(
|
|
4497
4576
|
`- sessions: ${summary.sessionCount}${sourceBreakdown !== "" ? ` (${sourceBreakdown})` : ""}`
|
|
@@ -4549,14 +4628,22 @@ function freshnessVerdict(summary, staleness, now) {
|
|
|
4549
4628
|
const rel = relativeAgeJa(summary.freshness.newestStartedAt, now);
|
|
4550
4629
|
const tool = toolDisplayName(summary.freshness.newestSource);
|
|
4551
4630
|
const suspectCount = summary.suspects.length;
|
|
4552
|
-
const suspectClause = suspectCount > 0 ? `\u8981\u6CE8\u610F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${suspectCount} \u4EF6\u3042\u308A\u307E\u3059\u3002` : "\u53D6\u308A\u3053\u307C\u3057\u30FB\u8981\u6CE8\u610F\u306A\u3057\u3002";
|
|
4553
4631
|
if (staleness === null) {
|
|
4554
4632
|
return [
|
|
4555
4633
|
`\u2139\uFE0F \u53D6\u308A\u8FBC\u307F\u6E08\u307F\u306E\u72B6\u614B\u3092\u8868\u793A\u3057\u3066\u3044\u307E\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002`,
|
|
4556
4634
|
"\u6700\u65B0\u304B\u78BA\u8A8D\u3059\u308B\u306B\u306F `basou refresh` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
|
|
4557
4635
|
];
|
|
4558
4636
|
}
|
|
4559
|
-
|
|
4637
|
+
const lines = [
|
|
4638
|
+
`\u2705 \u53D6\u308A\u8FBC\u307F\u306F\u6700\u65B0\u3067\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002\u672A\u53D6\u308A\u8FBC\u307F\u306E native \u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u3042\u308A\u307E\u305B\u3093\u3002`
|
|
4639
|
+
];
|
|
4640
|
+
if (suspectCount > 0) {
|
|
4641
|
+
lines.push(`\u305F\u3060\u3057\u8981\u6CE8\u610F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${suspectCount} \u4EF6\u3042\u308A\u307E\u3059(\u4E0A\u8A18\u300C\u8981\u6CE8\u610F session\u300D\u53C2\u7167)\u3002`);
|
|
4642
|
+
}
|
|
4643
|
+
lines.push(
|
|
4644
|
+
"\u6CE8: \u3053\u306E\u5224\u5B9A\u306F\u53D6\u308A\u8FBC\u307F\u6E08\u307F native \u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u9BAE\u5EA6\u3068 suspect \u306E\u6709\u7121\u3060\u3051\u3092\u898B\u307E\u3059\u3002\u8A08\u753B\u2194\u5B9F\u88C5\u306E\u30C9\u30EA\u30D5\u30C8\u3084\u672A\u8A18\u9332\u306E\u610F\u601D\u6C7A\u5B9A\u307E\u3067\u306F\u691C\u77E5\u3057\u307E\u305B\u3093\u3002"
|
|
4645
|
+
);
|
|
4646
|
+
return lines;
|
|
4560
4647
|
}
|
|
4561
4648
|
function relativeAgeJa(startedAt, now) {
|
|
4562
4649
|
if (startedAt === null) return "(\u4E0D\u660E)";
|
|
@@ -4579,6 +4666,11 @@ function relativeAge(startedAt, now) {
|
|
|
4579
4666
|
if (ms < 1e3) return "just now";
|
|
4580
4667
|
return `${formatDurationMs(ms)} ago`;
|
|
4581
4668
|
}
|
|
4669
|
+
var NOTE_SUMMARY_MAX = 200;
|
|
4670
|
+
function noteSummary(body) {
|
|
4671
|
+
const oneLine = body.replace(/\s+/g, " ").trim();
|
|
4672
|
+
return oneLine.length > NOTE_SUMMARY_MAX ? `${oneLine.slice(0, NOTE_SUMMARY_MAX - 1)}\u2026` : oneLine;
|
|
4673
|
+
}
|
|
4582
4674
|
function suspectText(reason) {
|
|
4583
4675
|
if (reason === "events_say_ended_but_yaml_running") return "ended (yaml stale)";
|
|
4584
4676
|
if (reason === "running_no_end_event") return "no end event";
|
|
@@ -4590,6 +4682,581 @@ function shortId(id) {
|
|
|
4590
4682
|
return id.slice(0, sep + 1) + id.slice(sep + 1, sep + 1 + 10);
|
|
4591
4683
|
}
|
|
4592
4684
|
|
|
4685
|
+
// src/project/relative-path.ts
|
|
4686
|
+
function normalizeRelativePath(p) {
|
|
4687
|
+
const trimmed = p.trim();
|
|
4688
|
+
const absolute = trimmed.startsWith("/");
|
|
4689
|
+
const out = [];
|
|
4690
|
+
for (const seg of trimmed.split("/")) {
|
|
4691
|
+
if (seg === "" || seg === ".") continue;
|
|
4692
|
+
if (seg === "..") {
|
|
4693
|
+
const top = out[out.length - 1];
|
|
4694
|
+
if (top !== void 0 && top !== "..") {
|
|
4695
|
+
out.pop();
|
|
4696
|
+
} else if (!absolute) {
|
|
4697
|
+
out.push("..");
|
|
4698
|
+
}
|
|
4699
|
+
continue;
|
|
4700
|
+
}
|
|
4701
|
+
out.push(seg);
|
|
4702
|
+
}
|
|
4703
|
+
const joined = out.join("/");
|
|
4704
|
+
if (absolute) return `/${joined}`;
|
|
4705
|
+
return joined.length === 0 ? "." : joined;
|
|
4706
|
+
}
|
|
4707
|
+
|
|
4708
|
+
// src/project/archive.ts
|
|
4709
|
+
function planArchive(input) {
|
|
4710
|
+
const target = normalizeRelativePath(input.target);
|
|
4711
|
+
const repos = input.repos ?? [];
|
|
4712
|
+
const isAnchor = input.targetIsAnchor === true || target === ".";
|
|
4713
|
+
const matched = repos.filter((r) => normalizeRelativePath(r.path) === target);
|
|
4714
|
+
const found = matched.length > 0;
|
|
4715
|
+
if (isAnchor || !found) {
|
|
4716
|
+
return {
|
|
4717
|
+
target,
|
|
4718
|
+
found,
|
|
4719
|
+
isAnchor,
|
|
4720
|
+
nextRepos: repos,
|
|
4721
|
+
reposEmptied: false,
|
|
4722
|
+
remainingCount: repos.length,
|
|
4723
|
+
becomesSolo: false
|
|
4724
|
+
};
|
|
4725
|
+
}
|
|
4726
|
+
const nextRepos = repos.filter((r) => normalizeRelativePath(r.path) !== target);
|
|
4727
|
+
const remainingCount = nextRepos.length;
|
|
4728
|
+
let sourceRootRemoval;
|
|
4729
|
+
let nextSourceRoots;
|
|
4730
|
+
if (input.sourceRoots !== void 0) {
|
|
4731
|
+
const pruned = input.sourceRoots.filter((s) => normalizeRelativePath(s) !== target);
|
|
4732
|
+
if (pruned.length !== input.sourceRoots.length) {
|
|
4733
|
+
sourceRootRemoval = target;
|
|
4734
|
+
nextSourceRoots = pruned;
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
return {
|
|
4738
|
+
target,
|
|
4739
|
+
found: true,
|
|
4740
|
+
isAnchor: false,
|
|
4741
|
+
rosterEntry: matched[matched.length - 1],
|
|
4742
|
+
nextRepos,
|
|
4743
|
+
reposEmptied: remainingCount === 0,
|
|
4744
|
+
...sourceRootRemoval !== void 0 ? { sourceRootRemoval } : {},
|
|
4745
|
+
...nextSourceRoots !== void 0 ? { nextSourceRoots } : {},
|
|
4746
|
+
remainingCount,
|
|
4747
|
+
becomesSolo: remainingCount === 1
|
|
4748
|
+
};
|
|
4749
|
+
}
|
|
4750
|
+
|
|
4751
|
+
// src/project/gitignore-plan.ts
|
|
4752
|
+
function isPublicFacing(v) {
|
|
4753
|
+
return v === "public" || v === "future-public";
|
|
4754
|
+
}
|
|
4755
|
+
function planGitignore(input) {
|
|
4756
|
+
const plans = [];
|
|
4757
|
+
const unknown = [];
|
|
4758
|
+
const unreachable = [];
|
|
4759
|
+
for (const repo of input.repos) {
|
|
4760
|
+
if (!repo.reachable) {
|
|
4761
|
+
unreachable.push(repo.path);
|
|
4762
|
+
continue;
|
|
4763
|
+
}
|
|
4764
|
+
if (repo.visibility === void 0) {
|
|
4765
|
+
unknown.push(repo.path);
|
|
4766
|
+
continue;
|
|
4767
|
+
}
|
|
4768
|
+
if (!isPublicFacing(repo.visibility)) continue;
|
|
4769
|
+
const present = /* @__PURE__ */ new Set();
|
|
4770
|
+
for (const line of repo.currentLines) {
|
|
4771
|
+
const trimmed = line.trim();
|
|
4772
|
+
present.add(trimmed);
|
|
4773
|
+
if (trimmed.startsWith("/")) present.add(trimmed.slice(1));
|
|
4774
|
+
}
|
|
4775
|
+
const toAdd = input.required.filter((p) => !present.has(p));
|
|
4776
|
+
if (toAdd.length > 0) plans.push({ path: repo.path, toAdd });
|
|
4777
|
+
}
|
|
4778
|
+
return {
|
|
4779
|
+
plans,
|
|
4780
|
+
unknown,
|
|
4781
|
+
unreachable,
|
|
4782
|
+
ok: plans.length === 0 && unknown.length === 0 && unreachable.length === 0
|
|
4783
|
+
};
|
|
4784
|
+
}
|
|
4785
|
+
|
|
4786
|
+
// src/project/preset.ts
|
|
4787
|
+
function visibilityLabel(v) {
|
|
4788
|
+
switch (v) {
|
|
4789
|
+
case "public":
|
|
4790
|
+
return "public(git \u5C65\u6B74\u306F\u516C\u958B)";
|
|
4791
|
+
case "private":
|
|
4792
|
+
return "private(git \u5C65\u6B74\u306F\u975E\u516C\u958B)";
|
|
4793
|
+
case "future-public":
|
|
4794
|
+
return "future-public(\u73FE\u5728\u306F\u975E\u516C\u958B\u30FB\u5C06\u6765\u516C\u958B\u4E88\u5B9A)";
|
|
4795
|
+
default:
|
|
4796
|
+
return "\u672A\u8A2D\u5B9A";
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
function sourceLanguageLabel(l) {
|
|
4800
|
+
switch (l) {
|
|
4801
|
+
case "en":
|
|
4802
|
+
return "en(commit\u30FB\u30B3\u30E1\u30F3\u30C8\u30FB\u30B3\u30FC\u30C9\u306F\u82F1\u8A9E)";
|
|
4803
|
+
case "ja":
|
|
4804
|
+
return "ja(commit\u30FB\u30B3\u30E1\u30F3\u30C8\u30FB\u30B3\u30FC\u30C9\u306F\u65E5\u672C\u8A9E)";
|
|
4805
|
+
case "en+ja":
|
|
4806
|
+
return "en+ja(commit\u30FB\u30B3\u30E1\u30F3\u30C8\u30FB\u30B3\u30FC\u30C9\u306F\u65E5\u82F1)";
|
|
4807
|
+
default:
|
|
4808
|
+
return "\u672A\u8A2D\u5B9A";
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
function publishKindLabel(k) {
|
|
4812
|
+
return k === "web" ? "web(\u30C7\u30D7\u30ED\u30A4)" : "npm(\u30D1\u30C3\u30B1\u30FC\u30B8)";
|
|
4813
|
+
}
|
|
4814
|
+
function publishVisibilityLabel(v) {
|
|
4815
|
+
switch (v) {
|
|
4816
|
+
case "public":
|
|
4817
|
+
return "\u516C\u958B";
|
|
4818
|
+
case "private":
|
|
4819
|
+
return "\u975E\u516C\u958B";
|
|
4820
|
+
case "future-public":
|
|
4821
|
+
return "\u5C06\u6765\u516C\u958B";
|
|
4822
|
+
default:
|
|
4823
|
+
return "\u53EF\u8996\u6027\u672A\u8A2D\u5B9A";
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
function contentLanguageLabel(l) {
|
|
4827
|
+
return l ?? "\u8A00\u8A9E\u672A\u8A2D\u5B9A";
|
|
4828
|
+
}
|
|
4829
|
+
function isRenderable(repo) {
|
|
4830
|
+
return repo.visibility !== void 0 || repo.language !== void 0 || repo.publishes !== void 0 && repo.publishes.length > 0;
|
|
4831
|
+
}
|
|
4832
|
+
function renderPresetBlock(repo) {
|
|
4833
|
+
const lines = [];
|
|
4834
|
+
lines.push("## \u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u69CB\u6210(basou \u304C\u751F\u6210 \u2014 manifest \u304C\u6B63\u672C)");
|
|
4835
|
+
lines.push("");
|
|
4836
|
+
lines.push(
|
|
4837
|
+
"\u3053\u306E\u30BB\u30AF\u30B7\u30E7\u30F3\u306F `.basou/manifest.yaml` \u306E\u5BA3\u8A00\u304B\u3089 `basou project preset` \u304C\u751F\u6210\u3057\u307E\u3059\u3002\u7DE8\u96C6\u306F manifest \u5074\u3067\u884C\u3063\u3066\u304F\u3060\u3055\u3044(\u30DE\u30FC\u30AB\u30FC\u5916\u306E\u8A18\u8FF0\u306F\u4FDD\u6301\u3055\u308C\u307E\u3059)\u3002"
|
|
4838
|
+
);
|
|
4839
|
+
lines.push("");
|
|
4840
|
+
lines.push(`- \u30BD\u30FC\u30B9\u53EF\u8996\u6027: ${visibilityLabel(repo.visibility)}`);
|
|
4841
|
+
lines.push(`- \u30BD\u30FC\u30B9\u8A00\u8A9E: ${sourceLanguageLabel(repo.language)}`);
|
|
4842
|
+
const publishes = repo.publishes ?? [];
|
|
4843
|
+
if (publishes.length === 0) {
|
|
4844
|
+
lines.push("- \u914D\u4FE1\u7269: \u306A\u3057");
|
|
4845
|
+
} else {
|
|
4846
|
+
lines.push("- \u914D\u4FE1\u7269:");
|
|
4847
|
+
for (const p of publishes) {
|
|
4848
|
+
lines.push(
|
|
4849
|
+
` - ${publishKindLabel(p.kind)} \u2014 ${publishVisibilityLabel(p.visibility)} / ${contentLanguageLabel(p.language)}`
|
|
4850
|
+
);
|
|
4851
|
+
}
|
|
4852
|
+
}
|
|
4853
|
+
return lines.join("\n");
|
|
4854
|
+
}
|
|
4855
|
+
function normalizeBlock(s) {
|
|
4856
|
+
return s.replace(/\r\n/g, "\n").replace(/\n+$/, "");
|
|
4857
|
+
}
|
|
4858
|
+
function summarizePresetPlan(facts) {
|
|
4859
|
+
const deduped = [];
|
|
4860
|
+
const seenPath = /* @__PURE__ */ new Set();
|
|
4861
|
+
for (const f of facts) {
|
|
4862
|
+
const key = normalizeRelativePath(f.path);
|
|
4863
|
+
if (seenPath.has(key)) continue;
|
|
4864
|
+
seenPath.add(key);
|
|
4865
|
+
deduped.push(f);
|
|
4866
|
+
}
|
|
4867
|
+
const byCanonical = /* @__PURE__ */ new Map();
|
|
4868
|
+
for (const f of deduped) {
|
|
4869
|
+
if (f.isAnchor || !f.reachable || f.canonicalName === void 0 || !isRenderable(f)) continue;
|
|
4870
|
+
const repos = byCanonical.get(f.canonicalName) ?? [];
|
|
4871
|
+
repos.push(f.path);
|
|
4872
|
+
byCanonical.set(f.canonicalName, repos);
|
|
4873
|
+
}
|
|
4874
|
+
const collisions = [];
|
|
4875
|
+
const collidingPaths = /* @__PURE__ */ new Set();
|
|
4876
|
+
for (const [canonicalName, repos] of byCanonical) {
|
|
4877
|
+
if (repos.length > 1) {
|
|
4878
|
+
collisions.push({ canonicalName, repos });
|
|
4879
|
+
for (const r of repos) collidingPaths.add(r);
|
|
4880
|
+
}
|
|
4881
|
+
}
|
|
4882
|
+
const plans = [];
|
|
4883
|
+
const inSync = [];
|
|
4884
|
+
const undeclared = [];
|
|
4885
|
+
const markerConflicts = [];
|
|
4886
|
+
const unreadable = [];
|
|
4887
|
+
const anchors = [];
|
|
4888
|
+
const unreachable = [];
|
|
4889
|
+
for (const f of deduped) {
|
|
4890
|
+
if (f.isAnchor) {
|
|
4891
|
+
anchors.push(f.path);
|
|
4892
|
+
continue;
|
|
4893
|
+
}
|
|
4894
|
+
if (!f.reachable) {
|
|
4895
|
+
unreachable.push(f.path);
|
|
4896
|
+
continue;
|
|
4897
|
+
}
|
|
4898
|
+
if (!isRenderable(f)) {
|
|
4899
|
+
undeclared.push(f.path);
|
|
4900
|
+
continue;
|
|
4901
|
+
}
|
|
4902
|
+
if (collidingPaths.has(f.path)) continue;
|
|
4903
|
+
if (f.canonicalName === void 0) {
|
|
4904
|
+
unreachable.push(f.path);
|
|
4905
|
+
continue;
|
|
4906
|
+
}
|
|
4907
|
+
const desiredBlock = renderPresetBlock(f);
|
|
4908
|
+
if (!f.canonicalPresent) {
|
|
4909
|
+
plans.push({ path: f.path, canonicalName: f.canonicalName, action: "create", desiredBlock });
|
|
4910
|
+
continue;
|
|
4911
|
+
}
|
|
4912
|
+
if (f.canonicalReadable === false) {
|
|
4913
|
+
unreadable.push(f.path);
|
|
4914
|
+
continue;
|
|
4915
|
+
}
|
|
4916
|
+
if (f.markerKind === "ok") {
|
|
4917
|
+
if (normalizeBlock(f.currentBlock ?? "") === normalizeBlock(desiredBlock)) {
|
|
4918
|
+
inSync.push(f.path);
|
|
4919
|
+
} else {
|
|
4920
|
+
plans.push({
|
|
4921
|
+
path: f.path,
|
|
4922
|
+
canonicalName: f.canonicalName,
|
|
4923
|
+
action: "update",
|
|
4924
|
+
desiredBlock
|
|
4925
|
+
});
|
|
4926
|
+
}
|
|
4927
|
+
continue;
|
|
4928
|
+
}
|
|
4929
|
+
markerConflicts.push({ repo: f.path, reason: f.markerKind ?? "no_markers" });
|
|
4930
|
+
}
|
|
4931
|
+
return {
|
|
4932
|
+
plans,
|
|
4933
|
+
inSync,
|
|
4934
|
+
undeclared,
|
|
4935
|
+
markerConflicts,
|
|
4936
|
+
unreadable,
|
|
4937
|
+
collisions,
|
|
4938
|
+
anchors,
|
|
4939
|
+
unreachable,
|
|
4940
|
+
ok: plans.length === 0 && markerConflicts.length === 0 && unreadable.length === 0 && collisions.length === 0 && unreachable.length === 0 && undeclared.length === 0
|
|
4941
|
+
};
|
|
4942
|
+
}
|
|
4943
|
+
|
|
4944
|
+
// src/project/rename.ts
|
|
4945
|
+
function pathBasename(p) {
|
|
4946
|
+
const parts = normalizeRelativePath(p).split("/");
|
|
4947
|
+
return parts[parts.length - 1];
|
|
4948
|
+
}
|
|
4949
|
+
function dedupRepos(entries) {
|
|
4950
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4951
|
+
const out = [];
|
|
4952
|
+
for (const e of entries) {
|
|
4953
|
+
const k = normalizeRelativePath(e.path);
|
|
4954
|
+
if (seen.has(k)) continue;
|
|
4955
|
+
seen.add(k);
|
|
4956
|
+
out.push(e);
|
|
4957
|
+
}
|
|
4958
|
+
return out;
|
|
4959
|
+
}
|
|
4960
|
+
function dedupNorm(items) {
|
|
4961
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4962
|
+
const out = [];
|
|
4963
|
+
for (const s of items) {
|
|
4964
|
+
const k = normalizeRelativePath(s);
|
|
4965
|
+
if (seen.has(k)) continue;
|
|
4966
|
+
seen.add(k);
|
|
4967
|
+
out.push(s);
|
|
4968
|
+
}
|
|
4969
|
+
return out;
|
|
4970
|
+
}
|
|
4971
|
+
function planRename(input) {
|
|
4972
|
+
const oldTarget = normalizeRelativePath(input.oldPath);
|
|
4973
|
+
const newTarget = normalizeRelativePath(input.newPath);
|
|
4974
|
+
const repos = input.repos ?? [];
|
|
4975
|
+
const basenameChanged = pathBasename(oldTarget) !== pathBasename(newTarget);
|
|
4976
|
+
const noop = oldTarget === newTarget;
|
|
4977
|
+
const isAnchor = input.oldIsAnchor === true || oldTarget === ".";
|
|
4978
|
+
const found = repos.some((r) => normalizeRelativePath(r.path) === oldTarget);
|
|
4979
|
+
const collision = !noop && repos.some((r) => normalizeRelativePath(r.path) === newTarget);
|
|
4980
|
+
if (noop || isAnchor || !found || collision) {
|
|
4981
|
+
return {
|
|
4982
|
+
oldTarget,
|
|
4983
|
+
newTarget,
|
|
4984
|
+
noop,
|
|
4985
|
+
isAnchor,
|
|
4986
|
+
found,
|
|
4987
|
+
collision,
|
|
4988
|
+
nextRepos: repos,
|
|
4989
|
+
reposChanged: false,
|
|
4990
|
+
basenameChanged
|
|
4991
|
+
};
|
|
4992
|
+
}
|
|
4993
|
+
const rosterEntry = repos.find((r) => normalizeRelativePath(r.path) === oldTarget);
|
|
4994
|
+
const nextRepos = dedupRepos(
|
|
4995
|
+
repos.map((r) => normalizeRelativePath(r.path) === oldTarget ? { ...r, path: newTarget } : r)
|
|
4996
|
+
);
|
|
4997
|
+
let sourceRootRenamed;
|
|
4998
|
+
let nextSourceRoots;
|
|
4999
|
+
if (input.sourceRoots !== void 0 && input.sourceRoots.some((s) => normalizeRelativePath(s) === oldTarget)) {
|
|
5000
|
+
nextSourceRoots = dedupNorm(
|
|
5001
|
+
input.sourceRoots.map((s) => normalizeRelativePath(s) === oldTarget ? newTarget : s)
|
|
5002
|
+
);
|
|
5003
|
+
sourceRootRenamed = oldTarget;
|
|
5004
|
+
}
|
|
5005
|
+
return {
|
|
5006
|
+
oldTarget,
|
|
5007
|
+
newTarget,
|
|
5008
|
+
noop: false,
|
|
5009
|
+
isAnchor: false,
|
|
5010
|
+
found: true,
|
|
5011
|
+
collision: false,
|
|
5012
|
+
rosterEntry,
|
|
5013
|
+
nextRepos,
|
|
5014
|
+
reposChanged: true,
|
|
5015
|
+
...sourceRootRenamed !== void 0 ? { sourceRootRenamed } : {},
|
|
5016
|
+
...nextSourceRoots !== void 0 ? { nextSourceRoots } : {},
|
|
5017
|
+
basenameChanged
|
|
5018
|
+
};
|
|
5019
|
+
}
|
|
5020
|
+
|
|
5021
|
+
// src/project/roster.ts
|
|
5022
|
+
function summarizeRosterDrift(input) {
|
|
5023
|
+
const captured = new Set((input.sourceRoots ?? []).map(normalizeRelativePath));
|
|
5024
|
+
const declared = /* @__PURE__ */ new Map();
|
|
5025
|
+
for (const r of input.repos ?? []) declared.set(normalizeRelativePath(r.path), r);
|
|
5026
|
+
const gaps = [];
|
|
5027
|
+
const matched = [];
|
|
5028
|
+
for (const [norm, entry] of declared) {
|
|
5029
|
+
if (captured.has(norm)) matched.push(norm);
|
|
5030
|
+
else gaps.push(entry);
|
|
5031
|
+
}
|
|
5032
|
+
const extra = [...captured].filter((c) => !declared.has(c)).sort();
|
|
5033
|
+
return {
|
|
5034
|
+
declaredCount: declared.size,
|
|
5035
|
+
capturedCount: captured.size,
|
|
5036
|
+
gaps,
|
|
5037
|
+
extra,
|
|
5038
|
+
matched: matched.sort(),
|
|
5039
|
+
ok: gaps.length === 0
|
|
5040
|
+
};
|
|
5041
|
+
}
|
|
5042
|
+
function reconcileSourceRoots(input) {
|
|
5043
|
+
const current = input.sourceRoots ?? [];
|
|
5044
|
+
const seen = new Set(current.map(normalizeRelativePath));
|
|
5045
|
+
const added = [];
|
|
5046
|
+
for (const r of input.repos ?? []) {
|
|
5047
|
+
const norm = normalizeRelativePath(r.path);
|
|
5048
|
+
if (seen.has(norm)) continue;
|
|
5049
|
+
seen.add(norm);
|
|
5050
|
+
added.push(norm);
|
|
5051
|
+
}
|
|
5052
|
+
return {
|
|
5053
|
+
next: [...current, ...added],
|
|
5054
|
+
added,
|
|
5055
|
+
unchanged: added.length === 0
|
|
5056
|
+
};
|
|
5057
|
+
}
|
|
5058
|
+
function planRosterAdoption(candidates) {
|
|
5059
|
+
const repos = [];
|
|
5060
|
+
const excluded = [];
|
|
5061
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5062
|
+
for (const c of candidates) {
|
|
5063
|
+
const norm = normalizeRelativePath(c.path);
|
|
5064
|
+
if (seen.has(norm)) continue;
|
|
5065
|
+
seen.add(norm);
|
|
5066
|
+
if (c.kind === "repo") repos.push({ path: c.path });
|
|
5067
|
+
else excluded.push({ path: c.path, kind: c.kind });
|
|
5068
|
+
}
|
|
5069
|
+
return { repos, excluded };
|
|
5070
|
+
}
|
|
5071
|
+
|
|
5072
|
+
// src/project/symlinks.ts
|
|
5073
|
+
function summarizeSymlinkPlan(facts) {
|
|
5074
|
+
const deduped = [];
|
|
5075
|
+
const seenPath = /* @__PURE__ */ new Set();
|
|
5076
|
+
for (const f of facts) {
|
|
5077
|
+
const key = normalizeRelativePath(f.path);
|
|
5078
|
+
if (seenPath.has(key)) continue;
|
|
5079
|
+
seenPath.add(key);
|
|
5080
|
+
deduped.push(f);
|
|
5081
|
+
}
|
|
5082
|
+
const byCanonical = /* @__PURE__ */ new Map();
|
|
5083
|
+
for (const f of deduped) {
|
|
5084
|
+
if (f.isAnchor || !f.reachable || !f.canonicalPresent || f.canonicalName === void 0) {
|
|
5085
|
+
continue;
|
|
5086
|
+
}
|
|
5087
|
+
const repos = byCanonical.get(f.canonicalName) ?? [];
|
|
5088
|
+
repos.push(f.path);
|
|
5089
|
+
byCanonical.set(f.canonicalName, repos);
|
|
5090
|
+
}
|
|
5091
|
+
const collisions = [];
|
|
5092
|
+
const collidingPaths = /* @__PURE__ */ new Set();
|
|
5093
|
+
for (const [canonicalName, repos] of byCanonical) {
|
|
5094
|
+
if (repos.length > 1) {
|
|
5095
|
+
collisions.push({ canonicalName, repos });
|
|
5096
|
+
for (const r of repos) collidingPaths.add(r);
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
5099
|
+
const plans = [];
|
|
5100
|
+
const conflicts = [];
|
|
5101
|
+
const missingCanonical = [];
|
|
5102
|
+
const unreachable = [];
|
|
5103
|
+
for (const f of deduped) {
|
|
5104
|
+
if (f.isAnchor) continue;
|
|
5105
|
+
if (!f.reachable) {
|
|
5106
|
+
unreachable.push(f.path);
|
|
5107
|
+
continue;
|
|
5108
|
+
}
|
|
5109
|
+
if (!f.canonicalPresent) {
|
|
5110
|
+
missingCanonical.push(f.path);
|
|
5111
|
+
continue;
|
|
5112
|
+
}
|
|
5113
|
+
if (collidingPaths.has(f.path)) continue;
|
|
5114
|
+
const toCreate = [];
|
|
5115
|
+
for (const file of f.files) {
|
|
5116
|
+
if (file.state === "missing") {
|
|
5117
|
+
toCreate.push({ name: file.name, target: file.expectedTarget });
|
|
5118
|
+
} else if (file.state === "mismatch") {
|
|
5119
|
+
conflicts.push({
|
|
5120
|
+
repo: f.path,
|
|
5121
|
+
file: file.name,
|
|
5122
|
+
reason: "mismatch",
|
|
5123
|
+
...file.actualTarget !== void 0 ? { actualTarget: file.actualTarget } : {}
|
|
5124
|
+
});
|
|
5125
|
+
} else if (file.state === "occupied") {
|
|
5126
|
+
conflicts.push({ repo: f.path, file: file.name, reason: "occupied" });
|
|
5127
|
+
} else if (file.state === "blocked") {
|
|
5128
|
+
conflicts.push({ repo: f.path, file: file.name, reason: "blocked" });
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
if (toCreate.length > 0) plans.push({ path: f.path, toCreate });
|
|
5132
|
+
}
|
|
5133
|
+
return {
|
|
5134
|
+
plans,
|
|
5135
|
+
conflicts,
|
|
5136
|
+
missingCanonical,
|
|
5137
|
+
unreachable,
|
|
5138
|
+
collisions,
|
|
5139
|
+
ok: plans.length === 0 && conflicts.length === 0 && missingCanonical.length === 0 && unreachable.length === 0 && collisions.length === 0
|
|
5140
|
+
};
|
|
5141
|
+
}
|
|
5142
|
+
|
|
5143
|
+
// src/project/wiring.ts
|
|
5144
|
+
function isPublicFacing2(v) {
|
|
5145
|
+
return v === "public" || v === "future-public";
|
|
5146
|
+
}
|
|
5147
|
+
function summarizeWiring(facts) {
|
|
5148
|
+
const risks = [];
|
|
5149
|
+
const unknown = [];
|
|
5150
|
+
const incomplete = [];
|
|
5151
|
+
const unreachable = [];
|
|
5152
|
+
for (const f of facts) {
|
|
5153
|
+
if (!f.reachable) {
|
|
5154
|
+
unreachable.push(f.path);
|
|
5155
|
+
continue;
|
|
5156
|
+
}
|
|
5157
|
+
if (isPublicFacing2(f.visibility)) {
|
|
5158
|
+
for (const file of f.instructionFiles) {
|
|
5159
|
+
if (file.tracked) risks.push({ repo: f.path, visibility: f.visibility, file: file.name });
|
|
5160
|
+
}
|
|
5161
|
+
} else if (f.visibility === void 0) {
|
|
5162
|
+
unknown.push(f.path);
|
|
5163
|
+
}
|
|
5164
|
+
const missing = f.instructionFiles.filter((file) => !file.present).map((file) => file.name);
|
|
5165
|
+
if (missing.length > 0) incomplete.push({ repo: f.path, missing });
|
|
5166
|
+
}
|
|
5167
|
+
return {
|
|
5168
|
+
repos: facts,
|
|
5169
|
+
risks,
|
|
5170
|
+
unknown,
|
|
5171
|
+
incomplete,
|
|
5172
|
+
unreachable,
|
|
5173
|
+
ok: risks.length === 0 && unknown.length === 0 && unreachable.length === 0
|
|
5174
|
+
};
|
|
5175
|
+
}
|
|
5176
|
+
|
|
5177
|
+
// src/project/workspace-view.ts
|
|
5178
|
+
function planWorkspaceView(facts, existing = [], rosterNames = []) {
|
|
5179
|
+
const deduped = [];
|
|
5180
|
+
const seenPath = /* @__PURE__ */ new Set();
|
|
5181
|
+
for (const f of facts) {
|
|
5182
|
+
const key = normalizeRelativePath(f.path);
|
|
5183
|
+
if (seenPath.has(key)) continue;
|
|
5184
|
+
seenPath.add(key);
|
|
5185
|
+
deduped.push(f);
|
|
5186
|
+
}
|
|
5187
|
+
const byLinkName = /* @__PURE__ */ new Map();
|
|
5188
|
+
for (const f of deduped) {
|
|
5189
|
+
if (!f.reachable || f.linkName === void 0) continue;
|
|
5190
|
+
const repos = byLinkName.get(f.linkName) ?? [];
|
|
5191
|
+
repos.push(f.path);
|
|
5192
|
+
byLinkName.set(f.linkName, repos);
|
|
5193
|
+
}
|
|
5194
|
+
const collisions = [];
|
|
5195
|
+
const collidingPaths = /* @__PURE__ */ new Set();
|
|
5196
|
+
for (const [linkName, repos] of byLinkName) {
|
|
5197
|
+
if (repos.length > 1) {
|
|
5198
|
+
collisions.push({ linkName, repos });
|
|
5199
|
+
for (const r of repos) collidingPaths.add(r);
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
const toCreate = [];
|
|
5203
|
+
const conflicts = [];
|
|
5204
|
+
const unreachable = [];
|
|
5205
|
+
let correctCount = 0;
|
|
5206
|
+
for (const f of deduped) {
|
|
5207
|
+
if (!f.reachable) {
|
|
5208
|
+
unreachable.push(f.path);
|
|
5209
|
+
continue;
|
|
5210
|
+
}
|
|
5211
|
+
if (collidingPaths.has(f.path)) continue;
|
|
5212
|
+
if (f.linkName === void 0 || f.expectedTarget === void 0 || f.state === void 0) {
|
|
5213
|
+
continue;
|
|
5214
|
+
}
|
|
5215
|
+
if (f.state === "missing") {
|
|
5216
|
+
toCreate.push({ name: f.linkName, target: f.expectedTarget });
|
|
5217
|
+
} else if (f.state === "mismatch") {
|
|
5218
|
+
conflicts.push({
|
|
5219
|
+
name: f.linkName,
|
|
5220
|
+
reason: "mismatch",
|
|
5221
|
+
...f.actualTarget !== void 0 ? { actualTarget: f.actualTarget } : {}
|
|
5222
|
+
});
|
|
5223
|
+
} else if (f.state === "occupied") {
|
|
5224
|
+
conflicts.push({ name: f.linkName, reason: "occupied" });
|
|
5225
|
+
} else if (f.state === "blocked") {
|
|
5226
|
+
conflicts.push({ name: f.linkName, reason: "blocked" });
|
|
5227
|
+
} else {
|
|
5228
|
+
correctCount += 1;
|
|
5229
|
+
}
|
|
5230
|
+
}
|
|
5231
|
+
const ownedNames = new Set(rosterNames);
|
|
5232
|
+
for (const f of deduped) {
|
|
5233
|
+
if (f.reachable && f.linkName !== void 0) ownedNames.add(f.linkName);
|
|
5234
|
+
}
|
|
5235
|
+
const toPrune = [];
|
|
5236
|
+
const strayUnknown = [];
|
|
5237
|
+
const seenExisting = /* @__PURE__ */ new Set();
|
|
5238
|
+
for (const e of existing) {
|
|
5239
|
+
if (ownedNames.has(e.name)) continue;
|
|
5240
|
+
if (seenExisting.has(e.name)) continue;
|
|
5241
|
+
seenExisting.add(e.name);
|
|
5242
|
+
if (e.kind === "repo") {
|
|
5243
|
+
toPrune.push({ name: e.name, target: e.target });
|
|
5244
|
+
} else {
|
|
5245
|
+
strayUnknown.push({ name: e.name, target: e.target, reason: e.kind });
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
return {
|
|
5249
|
+
toCreate,
|
|
5250
|
+
conflicts,
|
|
5251
|
+
collisions,
|
|
5252
|
+
unreachable,
|
|
5253
|
+
toPrune,
|
|
5254
|
+
strayUnknown,
|
|
5255
|
+
correctCount,
|
|
5256
|
+
ok: toCreate.length === 0 && conflicts.length === 0 && collisions.length === 0 && unreachable.length === 0 && toPrune.length === 0 && strayUnknown.length === 0
|
|
5257
|
+
};
|
|
5258
|
+
}
|
|
5259
|
+
|
|
4593
5260
|
// src/report/report-renderer.ts
|
|
4594
5261
|
import { join as join16 } from "path";
|
|
4595
5262
|
|
|
@@ -5190,6 +5857,231 @@ function formatInt(n) {
|
|
|
5190
5857
|
return n.toLocaleString("en-US");
|
|
5191
5858
|
}
|
|
5192
5859
|
|
|
5860
|
+
// src/review/review-gaps.ts
|
|
5861
|
+
import { existsSync, realpathSync } from "fs";
|
|
5862
|
+
import { homedir as homedir2 } from "os";
|
|
5863
|
+
import { basename as basename2, isAbsolute, join as join17 } from "path";
|
|
5864
|
+
function stripQuotes(s) {
|
|
5865
|
+
if (s.length >= 2 && (s[0] === '"' && s.at(-1) === '"' || s[0] === "'" && s.at(-1) === "'")) {
|
|
5866
|
+
return s.slice(1, -1);
|
|
5867
|
+
}
|
|
5868
|
+
return s;
|
|
5869
|
+
}
|
|
5870
|
+
var realpathCache = /* @__PURE__ */ new Map();
|
|
5871
|
+
function resolveRealpath(absPath) {
|
|
5872
|
+
const cached = realpathCache.get(absPath);
|
|
5873
|
+
if (cached !== void 0) return cached;
|
|
5874
|
+
let resolved;
|
|
5875
|
+
try {
|
|
5876
|
+
resolved = realpathSync(absPath);
|
|
5877
|
+
} catch {
|
|
5878
|
+
resolved = null;
|
|
5879
|
+
}
|
|
5880
|
+
realpathCache.set(absPath, resolved);
|
|
5881
|
+
return resolved;
|
|
5882
|
+
}
|
|
5883
|
+
var repoRootCache = /* @__PURE__ */ new Map();
|
|
5884
|
+
function isRepoRoot(realPath) {
|
|
5885
|
+
const cached = repoRootCache.get(realPath);
|
|
5886
|
+
if (cached !== void 0) return cached;
|
|
5887
|
+
const result = existsSync(join17(realPath, ".git"));
|
|
5888
|
+
repoRootCache.set(realPath, result);
|
|
5889
|
+
return result;
|
|
5890
|
+
}
|
|
5891
|
+
function normalizeRepoPath(p) {
|
|
5892
|
+
if (!p) return null;
|
|
5893
|
+
let s = stripQuotes(p.trim()).replace(/\/+$/, "");
|
|
5894
|
+
if (s.length === 0 || s === "~") return null;
|
|
5895
|
+
if (s.startsWith("~/")) s = homedir2() + s.slice(1);
|
|
5896
|
+
if (isAbsolute(s)) {
|
|
5897
|
+
const real = resolveRealpath(s);
|
|
5898
|
+
if (real !== null) {
|
|
5899
|
+
return isRepoRoot(real) ? real : null;
|
|
5900
|
+
}
|
|
5901
|
+
}
|
|
5902
|
+
s = s.replace(/\/[^/]*-workspace\/([^/]+)/, "/$1");
|
|
5903
|
+
const seg = s.split("/").filter((x) => x.length > 0).pop();
|
|
5904
|
+
if (seg === void 0) return null;
|
|
5905
|
+
if (/-workspace$/.test(seg) || seg.includes("$")) return null;
|
|
5906
|
+
return s;
|
|
5907
|
+
}
|
|
5908
|
+
function normalizeRepoKey(p) {
|
|
5909
|
+
const full = normalizeRepoPath(p);
|
|
5910
|
+
return full === null ? null : basename2(full);
|
|
5911
|
+
}
|
|
5912
|
+
function inspectCommand(args) {
|
|
5913
|
+
const a = args.join(" ");
|
|
5914
|
+
const files = /* @__PURE__ */ new Set();
|
|
5915
|
+
const examinedDiff = /\bgit\s+(?:diff|show|log\s+-p|add\s+-p)\b/.test(a);
|
|
5916
|
+
for (const re of [
|
|
5917
|
+
/\b(?:cat|less|bat|head|tail)\s+([^\s|&;<>]+)/g,
|
|
5918
|
+
/\bsed\s+-n\s+'[^']*'\s+([^\s|&;<>]+)/g,
|
|
5919
|
+
/\b(?:rg|grep)\b[^|&;]*?\s([^\s|&;<>]+\.[A-Za-z0-9]+)(?:\s|$)/g
|
|
5920
|
+
]) {
|
|
5921
|
+
let m;
|
|
5922
|
+
while ((m = re.exec(a)) !== null) {
|
|
5923
|
+
const f = m[1];
|
|
5924
|
+
if (f !== void 0) files.add(basename2(f));
|
|
5925
|
+
}
|
|
5926
|
+
}
|
|
5927
|
+
return { files: [...files], examinedDiff };
|
|
5928
|
+
}
|
|
5929
|
+
function commandRepo(args, cwd) {
|
|
5930
|
+
const cd = args.join(" ").match(/\bcd\s+("[^"]+"|'[^']+'|[^\s&]+)\s*&&/);
|
|
5931
|
+
if (cd) return normalizeRepoPath(cd[1]);
|
|
5932
|
+
return normalizeRepoPath(cwd);
|
|
5933
|
+
}
|
|
5934
|
+
function commandFailed(exitCode) {
|
|
5935
|
+
return exitCode !== null && exitCode !== 0;
|
|
5936
|
+
}
|
|
5937
|
+
function commitFiles(args) {
|
|
5938
|
+
const a = args.join(" ");
|
|
5939
|
+
const add = a.match(/git add\s+([^&|;]+)/);
|
|
5940
|
+
if (!add?.[1]) return [];
|
|
5941
|
+
return add[1].split(/\s+/).filter((t) => /\.[A-Za-z]/.test(t) && !t.startsWith("-")).map((t) => basename2(t));
|
|
5942
|
+
}
|
|
5943
|
+
var REVIEW_SOURCE = "codex-import";
|
|
5944
|
+
var DEFAULT_WINDOW_HOURS = 24;
|
|
5945
|
+
async function findReviewGaps(input) {
|
|
5946
|
+
const now = new Date(input.nowIso);
|
|
5947
|
+
const windowHours = input.windowHours ?? DEFAULT_WINDOW_HOURS;
|
|
5948
|
+
const scope = input.scope && input.scope.length > 0 ? input.scope : null;
|
|
5949
|
+
const loadOpts = { now };
|
|
5950
|
+
if (input.onSessionSkip !== void 0) loadOpts.onSkip = input.onSessionSkip;
|
|
5951
|
+
if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
|
|
5952
|
+
const entries = await loadSessionEntries(input.paths, loadOpts);
|
|
5953
|
+
const reviews = [];
|
|
5954
|
+
const workUnits = /* @__PURE__ */ new Map();
|
|
5955
|
+
const unknownCommits = /* @__PURE__ */ new Map();
|
|
5956
|
+
for (const entry of entries) {
|
|
5957
|
+
const sessionDir = join17(input.paths.sessions, entry.sessionId);
|
|
5958
|
+
const isReview = entry.session.session.source.kind === REVIEW_SOURCE;
|
|
5959
|
+
const reviewRepos = /* @__PURE__ */ new Map();
|
|
5960
|
+
let reviewEnd = null;
|
|
5961
|
+
try {
|
|
5962
|
+
for await (const ev of replayEvents(sessionDir, {
|
|
5963
|
+
onWarning: (w) => input.onWarning?.(w, entry.sessionId)
|
|
5964
|
+
})) {
|
|
5965
|
+
if (ev.type !== "command_executed") continue;
|
|
5966
|
+
if (commandFailed(ev.exit_code)) continue;
|
|
5967
|
+
const at = Date.parse(ev.occurred_at);
|
|
5968
|
+
if (isReview) {
|
|
5969
|
+
const repo2 = commandRepo(ev.args, ev.cwd);
|
|
5970
|
+
if (repo2 === null) continue;
|
|
5971
|
+
const ins = inspectCommand(ev.args);
|
|
5972
|
+
const slot = reviewRepos.get(repo2) ?? { examinedDiff: false, files: /* @__PURE__ */ new Set() };
|
|
5973
|
+
if (ins.examinedDiff) slot.examinedDiff = true;
|
|
5974
|
+
for (const f of ins.files) slot.files.add(f);
|
|
5975
|
+
reviewRepos.set(repo2, slot);
|
|
5976
|
+
if (!Number.isNaN(at)) reviewEnd = reviewEnd === null ? at : Math.max(reviewEnd, at);
|
|
5977
|
+
continue;
|
|
5978
|
+
}
|
|
5979
|
+
if (!ev.args.join(" ").includes("git commit")) continue;
|
|
5980
|
+
const repo = commandRepo(ev.args, ev.cwd);
|
|
5981
|
+
if (repo === null || Number.isNaN(at)) {
|
|
5982
|
+
const list2 = unknownCommits.get(entry.sessionId) ?? [];
|
|
5983
|
+
list2.push(Number.isNaN(at) ? null : at);
|
|
5984
|
+
unknownCommits.set(entry.sessionId, list2);
|
|
5985
|
+
continue;
|
|
5986
|
+
}
|
|
5987
|
+
const byRepo = workUnits.get(entry.sessionId) ?? /* @__PURE__ */ new Map();
|
|
5988
|
+
const list = byRepo.get(repo) ?? [];
|
|
5989
|
+
list.push({ repo, at, files: commitFiles(ev.args) });
|
|
5990
|
+
byRepo.set(repo, list);
|
|
5991
|
+
workUnits.set(entry.sessionId, byRepo);
|
|
5992
|
+
}
|
|
5993
|
+
} catch {
|
|
5994
|
+
input.onSessionSkip?.(entry.sessionId, "events_jsonl_unreadable");
|
|
5995
|
+
continue;
|
|
5996
|
+
}
|
|
5997
|
+
if (isReview && reviewRepos.size > 0) {
|
|
5998
|
+
reviews.push({ sessionId: entry.sessionId, endedAt: reviewEnd, repos: reviewRepos });
|
|
5999
|
+
}
|
|
6000
|
+
}
|
|
6001
|
+
const windowMs = windowHours * 3600 * 1e3;
|
|
6002
|
+
const units = [];
|
|
6003
|
+
let newestCommit = null;
|
|
6004
|
+
for (const [sessionId, byRepo] of workUnits) {
|
|
6005
|
+
for (const [repoPath, commits] of byRepo) {
|
|
6006
|
+
const label = basename2(repoPath);
|
|
6007
|
+
if (scope !== null && !scope.includes(label)) continue;
|
|
6008
|
+
const times = commits.map((c) => c.at).sort((a, b) => a - b);
|
|
6009
|
+
const first = times[0] ?? null;
|
|
6010
|
+
const last = times[times.length - 1] ?? null;
|
|
6011
|
+
if (last !== null) newestCommit = newestCommit === null ? last : Math.max(newestCommit, last);
|
|
6012
|
+
const changedFiles = new Set(commits.flatMap((c) => c.files));
|
|
6013
|
+
const before = first ?? last ?? 0;
|
|
6014
|
+
const nearby = reviews.filter((r) => {
|
|
6015
|
+
if (!r.repos.has(repoPath) || r.endedAt === null) return false;
|
|
6016
|
+
return r.endedAt <= before && r.endedAt >= before - windowMs;
|
|
6017
|
+
});
|
|
6018
|
+
const bound = nearby.filter((r) => {
|
|
6019
|
+
const touched = r.repos.get(repoPath);
|
|
6020
|
+
if (touched === void 0) return false;
|
|
6021
|
+
if (touched.examinedDiff) return true;
|
|
6022
|
+
for (const f of changedFiles) if (touched.files.has(f)) return true;
|
|
6023
|
+
return false;
|
|
6024
|
+
});
|
|
6025
|
+
const verdict = bound.length > 0 ? "candidate" : nearby.length > 0 ? "near_unbound" : "omission";
|
|
6026
|
+
const cited = verdict === "candidate" ? bound : verdict === "near_unbound" ? nearby : [];
|
|
6027
|
+
units.push({
|
|
6028
|
+
repo: label,
|
|
6029
|
+
sessionId,
|
|
6030
|
+
commitCount: commits.length,
|
|
6031
|
+
firstCommitAt: first === null ? null : new Date(first).toISOString(),
|
|
6032
|
+
lastCommitAt: last === null ? null : new Date(last).toISOString(),
|
|
6033
|
+
verdict,
|
|
6034
|
+
reviews: cited.map((r) => ({
|
|
6035
|
+
sessionId: r.sessionId,
|
|
6036
|
+
examinedDiff: r.repos.get(repoPath)?.examinedDiff ?? false,
|
|
6037
|
+
files: [...r.repos.get(repoPath)?.files ?? []].slice(0, 8),
|
|
6038
|
+
endedAt: r.endedAt === null ? null : new Date(r.endedAt).toISOString()
|
|
6039
|
+
}))
|
|
6040
|
+
});
|
|
6041
|
+
}
|
|
6042
|
+
}
|
|
6043
|
+
if (scope === null) {
|
|
6044
|
+
for (const [sessionId, times] of unknownCommits) {
|
|
6045
|
+
const valid = times.filter((t) => t !== null).sort((a, b) => a - b);
|
|
6046
|
+
const first = valid[0] ?? null;
|
|
6047
|
+
const last = valid[valid.length - 1] ?? null;
|
|
6048
|
+
if (last !== null) newestCommit = newestCommit === null ? last : Math.max(newestCommit, last);
|
|
6049
|
+
units.push({
|
|
6050
|
+
repo: "(unknown)",
|
|
6051
|
+
sessionId,
|
|
6052
|
+
commitCount: times.length,
|
|
6053
|
+
firstCommitAt: first === null ? null : new Date(first).toISOString(),
|
|
6054
|
+
lastCommitAt: last === null ? null : new Date(last).toISOString(),
|
|
6055
|
+
verdict: "unknown",
|
|
6056
|
+
reviews: []
|
|
6057
|
+
});
|
|
6058
|
+
}
|
|
6059
|
+
}
|
|
6060
|
+
const recentFirst = (a, b) => (Date.parse(b.lastCommitAt ?? "") || 0) - (Date.parse(a.lastCommitAt ?? "") || 0);
|
|
6061
|
+
const repoKeys = [...new Set(units.map((u) => u.repo))].sort();
|
|
6062
|
+
const repos = repoKeys.map((repo) => {
|
|
6063
|
+
const us = units.filter((u) => u.repo === repo);
|
|
6064
|
+
return {
|
|
6065
|
+
repo,
|
|
6066
|
+
units: us.length,
|
|
6067
|
+
omissionUnits: us.filter((u) => u.verdict === "omission").length,
|
|
6068
|
+
nearUnboundUnits: us.filter((u) => u.verdict === "near_unbound").length,
|
|
6069
|
+
candidateUnits: us.filter((u) => u.verdict === "candidate").length,
|
|
6070
|
+
unknownUnits: us.filter((u) => u.verdict === "unknown").length
|
|
6071
|
+
};
|
|
6072
|
+
});
|
|
6073
|
+
return {
|
|
6074
|
+
generatedAt: input.nowIso,
|
|
6075
|
+
windowHours,
|
|
6076
|
+
scope,
|
|
6077
|
+
repos,
|
|
6078
|
+
gaps: units.filter((u) => u.verdict === "omission" || u.verdict === "near_unbound").sort(recentFirst),
|
|
6079
|
+
candidates: units.filter((u) => u.verdict === "candidate").sort(recentFirst),
|
|
6080
|
+
unknowns: units.filter((u) => u.verdict === "unknown").sort(recentFirst),
|
|
6081
|
+
newestCommitAt: newestCommit === null ? null : new Date(newestCommit).toISOString()
|
|
6082
|
+
};
|
|
6083
|
+
}
|
|
6084
|
+
|
|
5193
6085
|
// src/runtime/child-process-runner.ts
|
|
5194
6086
|
import { spawn as spawn2 } from "child_process";
|
|
5195
6087
|
var DEFAULT_KILL_GRACE_MS = 5e3;
|
|
@@ -5437,28 +6329,28 @@ function serializeJsonSchema(schema) {
|
|
|
5437
6329
|
|
|
5438
6330
|
// src/storage/basou-dir.ts
|
|
5439
6331
|
import { lstat as lstat4, mkdir as mkdir4 } from "fs/promises";
|
|
5440
|
-
import { join as
|
|
6332
|
+
import { join as join18 } from "path";
|
|
5441
6333
|
function basouPaths(repositoryRoot) {
|
|
5442
|
-
const root =
|
|
5443
|
-
const approvalsBase =
|
|
6334
|
+
const root = join18(repositoryRoot, ".basou");
|
|
6335
|
+
const approvalsBase = join18(root, "approvals");
|
|
5444
6336
|
return {
|
|
5445
6337
|
root,
|
|
5446
|
-
sessions:
|
|
5447
|
-
tasks:
|
|
6338
|
+
sessions: join18(root, "sessions"),
|
|
6339
|
+
tasks: join18(root, "tasks"),
|
|
5448
6340
|
approvals: {
|
|
5449
|
-
pending:
|
|
5450
|
-
resolved:
|
|
6341
|
+
pending: join18(approvalsBase, "pending"),
|
|
6342
|
+
resolved: join18(approvalsBase, "resolved")
|
|
5451
6343
|
},
|
|
5452
|
-
locks:
|
|
5453
|
-
logs:
|
|
5454
|
-
raw:
|
|
5455
|
-
tmp:
|
|
6344
|
+
locks: join18(root, "locks"),
|
|
6345
|
+
logs: join18(root, "logs"),
|
|
6346
|
+
raw: join18(root, "raw"),
|
|
6347
|
+
tmp: join18(root, "tmp"),
|
|
5456
6348
|
files: {
|
|
5457
|
-
manifest:
|
|
5458
|
-
status:
|
|
5459
|
-
handoff:
|
|
5460
|
-
decisions:
|
|
5461
|
-
orientation:
|
|
6349
|
+
manifest: join18(root, "manifest.yaml"),
|
|
6350
|
+
status: join18(root, "status.json"),
|
|
6351
|
+
handoff: join18(root, "handoff.md"),
|
|
6352
|
+
decisions: join18(root, "decisions.md"),
|
|
6353
|
+
orientation: join18(root, "orientation.md")
|
|
5462
6354
|
}
|
|
5463
6355
|
};
|
|
5464
6356
|
}
|
|
@@ -5515,12 +6407,12 @@ function hasErrorCode4(error) {
|
|
|
5515
6407
|
|
|
5516
6408
|
// src/storage/gitignore.ts
|
|
5517
6409
|
import { readFile as readFile8, writeFile as writeFile2 } from "fs/promises";
|
|
5518
|
-
import { join as
|
|
6410
|
+
import { join as join19 } from "path";
|
|
5519
6411
|
var MARKER = "# Basou - default ignore";
|
|
5520
6412
|
var BASOU_GITIGNORE_BLOCK = "# Basou - default ignore\n.basou/logs/\n.basou/raw/\n.basou/tmp/\n.basou/locks/\n.basou/status.json\n.basou/orientation.md\n.basou/sessions/*/events.jsonl\n.basou/sessions/*/artifacts/\n.basou/approvals/pending/\n.basou/approvals/resolved/\n\n# Basou - default commit\n# .basou/manifest.yaml\n# .basou/handoff.md\n# .basou/decisions.md\n# .basou/tasks/\n# .basou/sessions/*/session.yaml\n# .basou/sessions/*/transcript.md\n# .basou/sessions/*/changed-files.json\n";
|
|
5521
6413
|
var BASOU_GITIGNORE_BLOCK_LOCAL_ONLY = "# Basou - default ignore\n# Local-only: basou's trail is never committed (personal/local state,\n# regenerable by re-importing from the agents' own logs). Recommended for\n# monitored repos and any workspace kept out of version control.\n.basou/\n";
|
|
5522
6414
|
async function appendBasouGitignore(repositoryRoot, options = {}) {
|
|
5523
|
-
const gitignorePath =
|
|
6415
|
+
const gitignorePath = join19(repositoryRoot, ".gitignore");
|
|
5524
6416
|
let body;
|
|
5525
6417
|
let existed;
|
|
5526
6418
|
try {
|
|
@@ -5671,8 +6563,8 @@ function hasErrorCode6(error) {
|
|
|
5671
6563
|
|
|
5672
6564
|
// src/storage/session-import.ts
|
|
5673
6565
|
import { mkdir as mkdir5, readFile as readFile10, rm as rm2 } from "fs/promises";
|
|
5674
|
-
import { homedir as
|
|
5675
|
-
import { join as
|
|
6566
|
+
import { homedir as homedir3 } from "os";
|
|
6567
|
+
import { join as join20 } from "path";
|
|
5676
6568
|
async function importSessionFromJson(paths, manifest, payload, options) {
|
|
5677
6569
|
if (options.taskIdOverride !== void 0 && !TaskIdSchema.safeParse(options.taskIdOverride).success) {
|
|
5678
6570
|
throw new Error(`Invalid task_id: ${options.taskIdOverride}`);
|
|
@@ -5697,7 +6589,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
|
|
|
5697
6589
|
pathSanitizeReport
|
|
5698
6590
|
};
|
|
5699
6591
|
}
|
|
5700
|
-
const sessionDir =
|
|
6592
|
+
const sessionDir = join20(paths.sessions, newSessionId);
|
|
5701
6593
|
try {
|
|
5702
6594
|
await mkdir5(sessionDir, { recursive: true });
|
|
5703
6595
|
} catch (error) {
|
|
@@ -5711,7 +6603,7 @@ async function importSessionFromJson(paths, manifest, payload, options) {
|
|
|
5711
6603
|
throw error;
|
|
5712
6604
|
}
|
|
5713
6605
|
try {
|
|
5714
|
-
const sessionYamlPath =
|
|
6606
|
+
const sessionYamlPath = join20(sessionDir, "session.yaml");
|
|
5715
6607
|
await linkYamlFile(sessionYamlPath, withIntegrity(sessionRecord, chainResult));
|
|
5716
6608
|
} catch (error) {
|
|
5717
6609
|
await rm2(sessionDir, { recursive: true, force: true }).catch(() => void 0);
|
|
@@ -5780,7 +6672,7 @@ function withIntegrity(record, chainResult) {
|
|
|
5780
6672
|
};
|
|
5781
6673
|
}
|
|
5782
6674
|
function buildSessionRecord(input, manifest, newSessionId, options) {
|
|
5783
|
-
const home =
|
|
6675
|
+
const home = homedir3();
|
|
5784
6676
|
const workingDirectoryRaw = input.working_directory;
|
|
5785
6677
|
const workingDirectorySanitized = sanitizeWorkingDirectory(workingDirectoryRaw, {
|
|
5786
6678
|
homedir: home
|
|
@@ -5879,7 +6771,7 @@ function reuseDerivedIds(priorDerived, freshDerived, sessionId) {
|
|
|
5879
6771
|
async function reimportPreservingId(paths, manifest, priorSessionId, freshPayload, options = {}) {
|
|
5880
6772
|
const sessionId = priorSessionId;
|
|
5881
6773
|
const importSource = freshPayload.session.source.kind;
|
|
5882
|
-
const sessionDir =
|
|
6774
|
+
const sessionDir = join20(paths.sessions, priorSessionId);
|
|
5883
6775
|
const lock = options.dryRun === true ? null : await acquireLock(paths, "session", priorSessionId);
|
|
5884
6776
|
try {
|
|
5885
6777
|
const priorVerdict = await verifyEventsChain(paths, priorSessionId);
|
|
@@ -5921,7 +6813,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
|
|
|
5921
6813
|
};
|
|
5922
6814
|
const updatedRecord = { schema_version: "0.1.0", session: preservedInner };
|
|
5923
6815
|
if (options.dryRun !== true) {
|
|
5924
|
-
const eventsPath =
|
|
6816
|
+
const eventsPath = join20(sessionDir, "events.jsonl");
|
|
5925
6817
|
let priorEventsRaw = null;
|
|
5926
6818
|
try {
|
|
5927
6819
|
priorEventsRaw = await readFile10(eventsPath);
|
|
@@ -5933,7 +6825,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
|
|
|
5933
6825
|
const chainResult = await writeEventsBulk(sessionDir, mergedEvents, { chain: true });
|
|
5934
6826
|
try {
|
|
5935
6827
|
await overwriteYamlFile(
|
|
5936
|
-
|
|
6828
|
+
join20(sessionDir, "session.yaml"),
|
|
5937
6829
|
withIntegrity(updatedRecord, chainResult)
|
|
5938
6830
|
);
|
|
5939
6831
|
} catch (error) {
|
|
@@ -5957,7 +6849,7 @@ async function reimportPreservingId(paths, manifest, priorSessionId, freshPayloa
|
|
|
5957
6849
|
}
|
|
5958
6850
|
}
|
|
5959
6851
|
async function rechainSessionInPlace(paths, sessionId, options = {}) {
|
|
5960
|
-
const sessionDir =
|
|
6852
|
+
const sessionDir = join20(paths.sessions, sessionId);
|
|
5961
6853
|
let lock;
|
|
5962
6854
|
try {
|
|
5963
6855
|
lock = await acquireLock(paths, "session", sessionId);
|
|
@@ -5990,7 +6882,7 @@ async function rechainSessionInPlace(paths, sessionId, options = {}) {
|
|
|
5990
6882
|
if (verdict.status !== "unchained") {
|
|
5991
6883
|
return { status: "skipped", reason: "tampered" };
|
|
5992
6884
|
}
|
|
5993
|
-
const eventsPath =
|
|
6885
|
+
const eventsPath = join20(sessionDir, "events.jsonl");
|
|
5994
6886
|
let priorRaw;
|
|
5995
6887
|
try {
|
|
5996
6888
|
priorRaw = await readFile10(eventsPath);
|
|
@@ -6038,7 +6930,7 @@ async function rechainSessionInPlace(paths, sessionId, options = {}) {
|
|
|
6038
6930
|
}
|
|
6039
6931
|
try {
|
|
6040
6932
|
await overwriteYamlFile(
|
|
6041
|
-
|
|
6933
|
+
join20(sessionDir, "session.yaml"),
|
|
6042
6934
|
withIntegrity(record, { headHash: chainResult.headHash, count: chainResult.count })
|
|
6043
6935
|
);
|
|
6044
6936
|
} catch (error) {
|
|
@@ -6120,23 +7012,34 @@ export {
|
|
|
6120
7012
|
enumerateTaskIds,
|
|
6121
7013
|
finalizeSessionYaml,
|
|
6122
7014
|
findErrorCode,
|
|
7015
|
+
findReviewGaps,
|
|
6123
7016
|
formatDurationMs,
|
|
6124
7017
|
genesisHash,
|
|
6125
7018
|
getDiff,
|
|
6126
7019
|
getSnapshot,
|
|
6127
7020
|
importSessionFromJson,
|
|
6128
7021
|
inspectChainTail,
|
|
7022
|
+
isGitNotFound,
|
|
6129
7023
|
isImportDerivedSource,
|
|
6130
7024
|
isLazyExpired,
|
|
7025
|
+
isRenderable,
|
|
6131
7026
|
isValidPrefixedId,
|
|
6132
7027
|
lineHash,
|
|
6133
7028
|
linkYamlFile,
|
|
6134
7029
|
loadApproval,
|
|
6135
7030
|
loadSessionEntries,
|
|
6136
7031
|
loadTaskEntries,
|
|
7032
|
+
normalizeRepoKey,
|
|
7033
|
+
normalizeRepoPath,
|
|
6137
7034
|
overwriteYamlFile,
|
|
6138
7035
|
parseDuration,
|
|
6139
7036
|
parseMarkers,
|
|
7037
|
+
pathBasename,
|
|
7038
|
+
planArchive,
|
|
7039
|
+
planGitignore,
|
|
7040
|
+
planRename,
|
|
7041
|
+
planRosterAdoption,
|
|
7042
|
+
planWorkspaceView,
|
|
6140
7043
|
prefixedUlid,
|
|
6141
7044
|
readAllEvents,
|
|
6142
7045
|
readManifest,
|
|
@@ -6148,12 +7051,14 @@ export {
|
|
|
6148
7051
|
readYamlFile,
|
|
6149
7052
|
rechainSessionInPlace,
|
|
6150
7053
|
reconcileAllTasks,
|
|
7054
|
+
reconcileSourceRoots,
|
|
6151
7055
|
reconcileTask,
|
|
6152
7056
|
refreshTaskLinkedSessions,
|
|
6153
7057
|
reimportPreservingId,
|
|
6154
7058
|
renderDecisions,
|
|
6155
7059
|
renderHandoff,
|
|
6156
7060
|
renderOrientation,
|
|
7061
|
+
renderPresetBlock,
|
|
6157
7062
|
renderReport,
|
|
6158
7063
|
renderWithMarkers,
|
|
6159
7064
|
replayEvents,
|
|
@@ -6162,6 +7067,7 @@ export {
|
|
|
6162
7067
|
resolveRepositoryRoot,
|
|
6163
7068
|
resolveSessionId,
|
|
6164
7069
|
resolveTaskId,
|
|
7070
|
+
safeSimpleGit,
|
|
6165
7071
|
sanitizePath,
|
|
6166
7072
|
sanitizeRelatedFiles,
|
|
6167
7073
|
sanitizeWorkingDirectory,
|
|
@@ -6170,8 +7076,13 @@ export {
|
|
|
6170
7076
|
sessionWorkStatsFromEvents,
|
|
6171
7077
|
summarizeAdapterOutput,
|
|
6172
7078
|
summarizeOrientation,
|
|
7079
|
+
summarizePresetPlan,
|
|
7080
|
+
summarizeRosterDrift,
|
|
7081
|
+
summarizeSymlinkPlan,
|
|
7082
|
+
summarizeWiring,
|
|
6173
7083
|
tryRemoteUrl,
|
|
6174
7084
|
ulid,
|
|
7085
|
+
unknownManifestKeys,
|
|
6175
7086
|
updateTaskStatusWithEvent,
|
|
6176
7087
|
verifyEventsChain,
|
|
6177
7088
|
writeEventsBulk,
|