@coresource/hz 0.1.8 → 0.2.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.
- package/dist/hz.mjs +197 -68
- package/package.json +1 -1
package/dist/hz.mjs
CHANGED
|
@@ -552,9 +552,11 @@ function normalizeMissionSummary(value) {
|
|
|
552
552
|
return {
|
|
553
553
|
completedFeatures: asFiniteNumber(value.completedFeatures),
|
|
554
554
|
createdAt: asNonEmptyString(value.createdAt),
|
|
555
|
+
description: asNonEmptyString(value.description) ?? asNonEmptyString(value.taskDescription),
|
|
555
556
|
missionId,
|
|
556
557
|
passedAssertions: asFiniteNumber(value.passedAssertions),
|
|
557
558
|
state: asNonEmptyString(value.state) ?? "unknown",
|
|
559
|
+
taskDescription: asNonEmptyString(value.taskDescription) ?? asNonEmptyString(value.description),
|
|
558
560
|
totalAssertions: asFiniteNumber(value.totalAssertions),
|
|
559
561
|
totalFeatures: asFiniteNumber(value.totalFeatures)
|
|
560
562
|
};
|
|
@@ -579,6 +581,7 @@ function normalizeMissionState(value) {
|
|
|
579
581
|
sandboxMinutesUsed: asNullableNumber(value.sandboxMinutesUsed),
|
|
580
582
|
sealedMilestones: asFiniteNumber(value.sealedMilestones),
|
|
581
583
|
state: asNonEmptyString(value.state) ?? "unknown",
|
|
584
|
+
taskDescription: asNonEmptyString(value.taskDescription),
|
|
582
585
|
totalAssertions: asFiniteNumber(value.totalAssertions),
|
|
583
586
|
totalFeatures: asFiniteNumber(value.totalFeatures),
|
|
584
587
|
totalMilestones: asFiniteNumber(value.totalMilestones),
|
|
@@ -685,6 +688,27 @@ function formatPercent(part, total) {
|
|
|
685
688
|
}
|
|
686
689
|
return `${(part / total * 100).toFixed(1)}%`;
|
|
687
690
|
}
|
|
691
|
+
function truncateText(value, limit) {
|
|
692
|
+
if (value.length <= limit) {
|
|
693
|
+
return value;
|
|
694
|
+
}
|
|
695
|
+
return `${value.slice(0, Math.max(0, limit - 1)).trimEnd()}\u2026`;
|
|
696
|
+
}
|
|
697
|
+
function summarizeMissionDescription(value, limit = 72) {
|
|
698
|
+
if (!value) {
|
|
699
|
+
return "\u2014";
|
|
700
|
+
}
|
|
701
|
+
const taskSectionMatch = value.match(
|
|
702
|
+
/(?:^|\n)##\s*Task\s*\n+([\s\S]*?)(?:\n\s*\n|\n##\s|$)/i
|
|
703
|
+
);
|
|
704
|
+
const source = taskSectionMatch?.[1] ?? value;
|
|
705
|
+
const lines = source.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
706
|
+
const candidate = lines.find((line) => !line.startsWith("#") && !/^[-*]\s/.test(line)) ?? lines[0];
|
|
707
|
+
if (!candidate) {
|
|
708
|
+
return "\u2014";
|
|
709
|
+
}
|
|
710
|
+
return truncateText(candidate.replace(/\s+/g, " "), limit);
|
|
711
|
+
}
|
|
688
712
|
function plainCell(value) {
|
|
689
713
|
return {
|
|
690
714
|
display: value,
|
|
@@ -787,21 +811,42 @@ function registerMissionInfoCommand(mission, context, dependencies = {}) {
|
|
|
787
811
|
}
|
|
788
812
|
|
|
789
813
|
// src/commands/mission-list.ts
|
|
814
|
+
async function resolveMissionDescription(apiClient, missionSummary) {
|
|
815
|
+
if (missionSummary.taskDescription || missionSummary.description) {
|
|
816
|
+
return missionSummary.taskDescription ?? missionSummary.description;
|
|
817
|
+
}
|
|
818
|
+
try {
|
|
819
|
+
const missionState = normalizeMissionState(
|
|
820
|
+
await apiClient.request({
|
|
821
|
+
path: `/missions/${missionSummary.missionId}/mission/state`
|
|
822
|
+
})
|
|
823
|
+
);
|
|
824
|
+
return missionState.taskDescription;
|
|
825
|
+
} catch {
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
790
829
|
function registerMissionListCommand(mission, context, dependencies = {}) {
|
|
791
830
|
mission.command("list").description("List recent missions").action(async (_options, command) => {
|
|
792
831
|
const apiClient = await createMissionApiClient(context, command, dependencies);
|
|
793
|
-
const
|
|
832
|
+
const missionSummaries = normalizeMissionSummaries(
|
|
794
833
|
await apiClient.request({
|
|
795
834
|
path: "/missions"
|
|
796
835
|
})
|
|
797
836
|
);
|
|
798
|
-
if (
|
|
837
|
+
if (missionSummaries.length === 0) {
|
|
799
838
|
writeLine(context.stdout, "No missions found");
|
|
800
839
|
return;
|
|
801
840
|
}
|
|
841
|
+
const missions = await Promise.all(
|
|
842
|
+
missionSummaries.map(async (missionSummary) => ({
|
|
843
|
+
...missionSummary,
|
|
844
|
+
description: await resolveMissionDescription(apiClient, missionSummary)
|
|
845
|
+
}))
|
|
846
|
+
);
|
|
802
847
|
renderTable(
|
|
803
848
|
context.stdout,
|
|
804
|
-
["Mission ID", "State", "Created", "Features", "Assertions"],
|
|
849
|
+
["Mission ID", "State", "Created", "Features", "Assertions", "Description"],
|
|
805
850
|
missions.map((missionSummary) => [
|
|
806
851
|
plainCell(missionSummary.missionId),
|
|
807
852
|
coloredCell(
|
|
@@ -814,7 +859,8 @@ function registerMissionListCommand(mission, context, dependencies = {}) {
|
|
|
814
859
|
),
|
|
815
860
|
plainCell(
|
|
816
861
|
`${missionSummary.passedAssertions}/${missionSummary.totalAssertions}`
|
|
817
|
-
)
|
|
862
|
+
),
|
|
863
|
+
plainCell(summarizeMissionDescription(missionSummary.description))
|
|
818
864
|
])
|
|
819
865
|
);
|
|
820
866
|
});
|
|
@@ -2149,17 +2195,9 @@ function defaultCreateWebSocket(url, options) {
|
|
|
2149
2195
|
return new WebSocket(url, { headers: options.headers });
|
|
2150
2196
|
}
|
|
2151
2197
|
function defaultRegisterSignalHandler(signal, handler) {
|
|
2152
|
-
|
|
2153
|
-
const wrapper = () => {
|
|
2154
|
-
if (fired) {
|
|
2155
|
-
process.exit(130);
|
|
2156
|
-
}
|
|
2157
|
-
fired = true;
|
|
2158
|
-
handler();
|
|
2159
|
-
};
|
|
2160
|
-
process.on(signal, wrapper);
|
|
2198
|
+
process.once(signal, handler);
|
|
2161
2199
|
return () => {
|
|
2162
|
-
process.off(signal,
|
|
2200
|
+
process.off(signal, handler);
|
|
2163
2201
|
};
|
|
2164
2202
|
}
|
|
2165
2203
|
function createMissionWebSocketUrl(endpoint, missionId) {
|
|
@@ -2898,6 +2936,17 @@ function resolveResumeExitCode(state) {
|
|
|
2898
2936
|
return null;
|
|
2899
2937
|
}
|
|
2900
2938
|
}
|
|
2939
|
+
function resolveWatchExitCode(state) {
|
|
2940
|
+
switch (state) {
|
|
2941
|
+
case "cancelled":
|
|
2942
|
+
case "completed":
|
|
2943
|
+
return 0;
|
|
2944
|
+
case "failed":
|
|
2945
|
+
return 1;
|
|
2946
|
+
default:
|
|
2947
|
+
return null;
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2901
2950
|
async function runMissionResume(missionIdValue, command, context, dependencies) {
|
|
2902
2951
|
const homeDir = resolveLifecycleHomeDir(context);
|
|
2903
2952
|
const missionId = await resolveMissionId(missionIdValue, context, command, homeDir);
|
|
@@ -2969,6 +3018,48 @@ async function runMissionResume(missionIdValue, command, context, dependencies)
|
|
|
2969
3018
|
throw new CommanderError(monitorResult.exitCode, "mission-monitor", "");
|
|
2970
3019
|
}
|
|
2971
3020
|
}
|
|
3021
|
+
async function runMissionWatch(missionIdValue, command, context, dependencies) {
|
|
3022
|
+
const homeDir = resolveLifecycleHomeDir(context);
|
|
3023
|
+
const missionId = await resolveMissionId(missionIdValue, context, command, homeDir);
|
|
3024
|
+
const { apiClient, authConfig } = await createMissionClientContext(
|
|
3025
|
+
context,
|
|
3026
|
+
command,
|
|
3027
|
+
dependencies,
|
|
3028
|
+
homeDir
|
|
3029
|
+
);
|
|
3030
|
+
const snapshot = await fetchMissionSnapshot(apiClient, missionId);
|
|
3031
|
+
writeLine(
|
|
3032
|
+
context.stdout,
|
|
3033
|
+
`Watching mission ${pc5.cyan(missionId)} from state ${pc5.bold(snapshot.state.state)}.`
|
|
3034
|
+
);
|
|
3035
|
+
renderMissionSnapshot(context.stdout, snapshot);
|
|
3036
|
+
const watchExitCode = resolveWatchExitCode(snapshot.state.state);
|
|
3037
|
+
if (watchExitCode === 0) {
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
if (watchExitCode === 1) {
|
|
3041
|
+
throw new CliError(
|
|
3042
|
+
`Mission ${missionId} is currently ${snapshot.state.state}.`,
|
|
3043
|
+
1
|
|
3044
|
+
);
|
|
3045
|
+
}
|
|
3046
|
+
const monitorResult = await monitorMission({
|
|
3047
|
+
apiClient,
|
|
3048
|
+
apiKey: authConfig.apiKey,
|
|
3049
|
+
createSpinner: dependencies.createSpinner,
|
|
3050
|
+
createWebSocket: dependencies.createWebSocket,
|
|
3051
|
+
endpoint: authConfig.endpoint,
|
|
3052
|
+
missionId,
|
|
3053
|
+
promptInput: dependencies.promptInput,
|
|
3054
|
+
promptSelect: dependencies.promptSelect,
|
|
3055
|
+
registerSignalHandler: dependencies.registerSignalHandler,
|
|
3056
|
+
sleep: dependencies.sleep,
|
|
3057
|
+
stdout: context.stdout
|
|
3058
|
+
});
|
|
3059
|
+
if (monitorResult.exitCode !== 0) {
|
|
3060
|
+
throw new CommanderError(monitorResult.exitCode, "mission-monitor", "");
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
2972
3063
|
function registerMissionLifecycleCommands(mission, context, dependencies = {}) {
|
|
2973
3064
|
mission.command("pause").description("Pause a mission").argument("[missionId]").action(async (missionId, _options, command) => {
|
|
2974
3065
|
await runLifecycleMutation(
|
|
@@ -2982,6 +3073,9 @@ function registerMissionLifecycleCommands(mission, context, dependencies = {}) {
|
|
|
2982
3073
|
mission.command("resume").description("Resume a mission").argument("[missionId]").action(async (missionId, _options, command) => {
|
|
2983
3074
|
await runMissionResume(missionId, command, context, dependencies);
|
|
2984
3075
|
});
|
|
3076
|
+
mission.command("watch").description("Watch a mission's live progress").argument("[missionId]").action(async (missionId, _options, command) => {
|
|
3077
|
+
await runMissionWatch(missionId, command, context, dependencies);
|
|
3078
|
+
});
|
|
2985
3079
|
mission.command("cancel").description("Cancel a mission").argument("[missionId]").action(async (missionId, _options, command) => {
|
|
2986
3080
|
await runLifecycleMutation(
|
|
2987
3081
|
"cancel",
|
|
@@ -2991,40 +3085,15 @@ function registerMissionLifecycleCommands(mission, context, dependencies = {}) {
|
|
|
2991
3085
|
dependencies
|
|
2992
3086
|
);
|
|
2993
3087
|
});
|
|
2994
|
-
mission.command("watch").description("Watch a mission's live progress").argument("[missionId]").action(async (missionId, _options, command) => {
|
|
2995
|
-
const homeDir = resolveLifecycleHomeDir(context);
|
|
2996
|
-
const resolved = await resolveMissionId(missionId, context, command, homeDir);
|
|
2997
|
-
const { apiClient, authConfig } = await createMissionClientContext(
|
|
2998
|
-
context,
|
|
2999
|
-
command,
|
|
3000
|
-
dependencies,
|
|
3001
|
-
homeDir
|
|
3002
|
-
);
|
|
3003
|
-
const monitorResult = await monitorMission({
|
|
3004
|
-
apiClient,
|
|
3005
|
-
apiKey: authConfig.apiKey,
|
|
3006
|
-
createSpinner: dependencies.createSpinner,
|
|
3007
|
-
createWebSocket: dependencies.createWebSocket,
|
|
3008
|
-
endpoint: authConfig.endpoint,
|
|
3009
|
-
missionId: resolved,
|
|
3010
|
-
promptInput: dependencies.promptInput,
|
|
3011
|
-
promptSelect: dependencies.promptSelect,
|
|
3012
|
-
registerSignalHandler: dependencies.registerSignalHandler,
|
|
3013
|
-
sleep: dependencies.sleep,
|
|
3014
|
-
stdout: context.stdout
|
|
3015
|
-
});
|
|
3016
|
-
if (monitorResult.exitCode !== 0) {
|
|
3017
|
-
throw new CommanderError(monitorResult.exitCode, "mission-monitor", "");
|
|
3018
|
-
}
|
|
3019
|
-
});
|
|
3020
3088
|
}
|
|
3021
3089
|
|
|
3022
3090
|
// src/commands/mission-run.ts
|
|
3023
3091
|
import { stat as stat3 } from "fs/promises";
|
|
3024
|
-
import
|
|
3092
|
+
import path7 from "path";
|
|
3025
3093
|
import { CommanderError as CommanderError2 } from "commander";
|
|
3026
3094
|
|
|
3027
3095
|
// src/planning.ts
|
|
3096
|
+
import path4 from "path";
|
|
3028
3097
|
import {
|
|
3029
3098
|
confirm as defaultConfirmPrompt,
|
|
3030
3099
|
input as defaultInputPrompt2,
|
|
@@ -3232,25 +3301,85 @@ function renderFailures(stdout, failures) {
|
|
|
3232
3301
|
writeLine(stdout, `- ${failure}`);
|
|
3233
3302
|
});
|
|
3234
3303
|
}
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3304
|
+
function tokenizeText(value) {
|
|
3305
|
+
return value.toLowerCase().split(/[^a-z0-9]+/i).filter((token) => token.length > 1);
|
|
3306
|
+
}
|
|
3307
|
+
function summarizeRepoNames(repoPaths) {
|
|
3308
|
+
return repoPaths.map((repoPath) => {
|
|
3309
|
+
const repoName = path4.basename(repoPath);
|
|
3310
|
+
return repoName.length > 0 ? repoName : repoPath;
|
|
3311
|
+
});
|
|
3312
|
+
}
|
|
3313
|
+
function pickAutoApprovedOption(question, taskDescription, repoPaths) {
|
|
3314
|
+
if (!question.options || question.options.length === 0) {
|
|
3315
|
+
return null;
|
|
3241
3316
|
}
|
|
3317
|
+
const repoNames = summarizeRepoNames(repoPaths);
|
|
3318
|
+
const references = (question.references ?? []).map((reference) => path4.basename(reference));
|
|
3319
|
+
const contextTokens = /* @__PURE__ */ new Set([
|
|
3320
|
+
...tokenizeText(question.text),
|
|
3321
|
+
...tokenizeText(taskDescription),
|
|
3322
|
+
...repoNames.flatMap((repoName) => tokenizeText(repoName)),
|
|
3323
|
+
...references.flatMap((reference) => tokenizeText(reference))
|
|
3324
|
+
]);
|
|
3325
|
+
let bestOption = question.options[0] ?? null;
|
|
3326
|
+
let bestScore = Number.NEGATIVE_INFINITY;
|
|
3327
|
+
for (const option of question.options) {
|
|
3328
|
+
const label = `${option.label} ${option.description ?? ""} ${option.id}`.trim();
|
|
3329
|
+
const optionTokens = tokenizeText(label);
|
|
3330
|
+
let score = 0;
|
|
3331
|
+
for (const token of optionTokens) {
|
|
3332
|
+
if (contextTokens.has(token)) {
|
|
3333
|
+
score += 3;
|
|
3334
|
+
}
|
|
3335
|
+
if (/continue|approve|yes|confirm|accept|keep|use|primary/.test(token)) {
|
|
3336
|
+
score += 1;
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
if (score > bestScore) {
|
|
3340
|
+
bestScore = score;
|
|
3341
|
+
bestOption = option;
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
return bestOption;
|
|
3345
|
+
}
|
|
3346
|
+
function buildAutoApprovedDetail(question, taskDescription, repoPaths) {
|
|
3347
|
+
if (question.inputDefault && question.inputDefault.trim().length > 0) {
|
|
3348
|
+
return question.inputDefault.trim();
|
|
3349
|
+
}
|
|
3350
|
+
const repoNames = summarizeRepoNames(repoPaths);
|
|
3351
|
+
const repoPhrase = repoNames.length === 1 ? `the \`${repoNames[0]}\` repository` : `the repositories ${repoNames.map((repoName) => `\`${repoName}\``).join(", ")}`;
|
|
3352
|
+
const references = question.references && question.references.length > 0 ? ` Ground the answer in ${question.references.join(", ")}.` : "";
|
|
3353
|
+
const questionText = question.text.toLowerCase();
|
|
3354
|
+
const task = taskDescription.trim().replace(/\s+/g, " ");
|
|
3355
|
+
if (/validation|test|package\.json/.test(questionText)) {
|
|
3356
|
+
return `Validate the change in ${repoPhrase} with the referenced tests and package scripts, then run the relevant tests, typecheck, and build checks.${references}`;
|
|
3357
|
+
}
|
|
3358
|
+
if (/repo surface|repo path|scope|primary repo/.test(questionText)) {
|
|
3359
|
+
return `Focus on ${repoPhrase} as the primary implementation surface for: ${task}.${references}`;
|
|
3360
|
+
}
|
|
3361
|
+
if (/debug info|api connection|endpoint|response status|http method|verbose/.test(questionText)) {
|
|
3362
|
+
return `Implement ${task} so verbose mode shows safe API-connection diagnostics such as the configured endpoint, HTTP method, and response status without exposing secrets.${references}`;
|
|
3363
|
+
}
|
|
3364
|
+
if (/req-func|flag|command/.test(questionText) || /--[a-z0-9-]+/.test(task)) {
|
|
3365
|
+
return `Implement ${task} in ${repoPhrase}, preserving the default output unless the requested flag or option is present.${references}`;
|
|
3366
|
+
}
|
|
3367
|
+
return `${task}. Use ${repoPhrase} as the source of truth.${references}`;
|
|
3368
|
+
}
|
|
3369
|
+
async function promptForAnswers(options, questions, round) {
|
|
3242
3370
|
const promptSelect = options.promptSelect ?? defaultPromptSelect2;
|
|
3243
3371
|
const promptInput = options.promptInput ?? defaultPromptInput2;
|
|
3244
3372
|
const answers = [];
|
|
3245
3373
|
const transcript = [];
|
|
3246
3374
|
for (const question of questions) {
|
|
3247
3375
|
if (question.options && question.options.length > 0) {
|
|
3376
|
+
const autoSelectedOption = options.autoApprove ? pickAutoApprovedOption(question, options.taskDescription, options.repoPaths) : null;
|
|
3248
3377
|
const choices = question.options.map((option) => ({
|
|
3249
3378
|
description: option.description,
|
|
3250
3379
|
name: option.label,
|
|
3251
3380
|
value: option.id
|
|
3252
3381
|
}));
|
|
3253
|
-
const optionId2 = await promptSelect({
|
|
3382
|
+
const optionId2 = autoSelectedOption?.id ?? await promptSelect({
|
|
3254
3383
|
choices,
|
|
3255
3384
|
message: question.text
|
|
3256
3385
|
});
|
|
@@ -3269,7 +3398,7 @@ async function promptForAnswers(options, questions, round) {
|
|
|
3269
3398
|
});
|
|
3270
3399
|
continue;
|
|
3271
3400
|
}
|
|
3272
|
-
const detail = await promptInput({
|
|
3401
|
+
const detail = options.autoApprove ? buildAutoApprovedDetail(question, options.taskDescription, options.repoPaths) : await promptInput({
|
|
3273
3402
|
default: question.inputDefault,
|
|
3274
3403
|
message: question.detailPrompt ?? question.text
|
|
3275
3404
|
});
|
|
@@ -3518,7 +3647,7 @@ async function runPlanningFlow(options) {
|
|
|
3518
3647
|
// src/upload.ts
|
|
3519
3648
|
import { confirm } from "@inquirer/prompts";
|
|
3520
3649
|
import { createReadStream as createReadStream2 } from "fs";
|
|
3521
|
-
import
|
|
3650
|
+
import path6 from "path";
|
|
3522
3651
|
import { Transform as Transform2 } from "stream";
|
|
3523
3652
|
import ora3 from "ora";
|
|
3524
3653
|
import pc7 from "picocolors";
|
|
@@ -3536,7 +3665,7 @@ import {
|
|
|
3536
3665
|
rm as rm3,
|
|
3537
3666
|
stat as stat2
|
|
3538
3667
|
} from "fs/promises";
|
|
3539
|
-
import
|
|
3668
|
+
import path5 from "path";
|
|
3540
3669
|
import { createInterface } from "readline";
|
|
3541
3670
|
import { Transform } from "stream";
|
|
3542
3671
|
import { pipeline } from "stream/promises";
|
|
@@ -3560,7 +3689,7 @@ var SECRET_PATTERNS = [
|
|
|
3560
3689
|
{
|
|
3561
3690
|
kind: "stripe_live_key",
|
|
3562
3691
|
message: "Found a Stripe live secret key.",
|
|
3563
|
-
regex: /sk_live_[A-Za-z0-9]
|
|
3692
|
+
regex: /sk_live_[A-Za-z0-9]{16,}/g
|
|
3564
3693
|
},
|
|
3565
3694
|
{
|
|
3566
3695
|
kind: "aws_access_key",
|
|
@@ -3570,7 +3699,7 @@ var SECRET_PATTERNS = [
|
|
|
3570
3699
|
{
|
|
3571
3700
|
kind: "github_personal_access_token",
|
|
3572
3701
|
message: "Found a GitHub personal access token.",
|
|
3573
|
-
regex: /ghp_[A-Za-z0-9]
|
|
3702
|
+
regex: /ghp_[A-Za-z0-9]{36,}/g
|
|
3574
3703
|
},
|
|
3575
3704
|
{
|
|
3576
3705
|
kind: "private_key",
|
|
@@ -3607,7 +3736,7 @@ function compareStrings(left, right) {
|
|
|
3607
3736
|
return left < right ? -1 : 1;
|
|
3608
3737
|
}
|
|
3609
3738
|
function toPosixPath(filePath) {
|
|
3610
|
-
return filePath.split(
|
|
3739
|
+
return filePath.split(path5.sep).join(path5.posix.sep);
|
|
3611
3740
|
}
|
|
3612
3741
|
function splitNullTerminatedBuffer(buffer) {
|
|
3613
3742
|
return buffer.toString("utf8").split("\0").filter((entry) => entry.length > 0);
|
|
@@ -3639,7 +3768,7 @@ async function ensureDirectory2(dirPath) {
|
|
|
3639
3768
|
}
|
|
3640
3769
|
async function validateDirectoryPath(repoPath, options) {
|
|
3641
3770
|
const cwd = options.cwd ?? process.cwd();
|
|
3642
|
-
const absolutePath =
|
|
3771
|
+
const absolutePath = path5.resolve(cwd, repoPath);
|
|
3643
3772
|
let directoryStats;
|
|
3644
3773
|
try {
|
|
3645
3774
|
directoryStats = await stat2(absolutePath);
|
|
@@ -3670,7 +3799,7 @@ async function resolveRepositoryInputs(repoPaths, options) {
|
|
|
3670
3799
|
);
|
|
3671
3800
|
const counts = /* @__PURE__ */ new Map();
|
|
3672
3801
|
const baseNames = absolutePaths.map(
|
|
3673
|
-
(absolutePath) => sanitizeIdentifier(
|
|
3802
|
+
(absolutePath) => sanitizeIdentifier(path5.basename(absolutePath))
|
|
3674
3803
|
);
|
|
3675
3804
|
for (const baseName of baseNames) {
|
|
3676
3805
|
counts.set(baseName, (counts.get(baseName) ?? 0) + 1);
|
|
@@ -3718,7 +3847,7 @@ function matchSecretPatterns(line, relativePath, lineNumber) {
|
|
|
3718
3847
|
}
|
|
3719
3848
|
async function scanRegularFileForSecrets(filePath, relativePath) {
|
|
3720
3849
|
const findings = [];
|
|
3721
|
-
const basename =
|
|
3850
|
+
const basename = path5.posix.basename(relativePath);
|
|
3722
3851
|
if (basename.startsWith(".env")) {
|
|
3723
3852
|
findings.push({
|
|
3724
3853
|
kind: "env_file",
|
|
@@ -3740,7 +3869,7 @@ async function scanRegularFileForSecrets(filePath, relativePath) {
|
|
|
3740
3869
|
return findings;
|
|
3741
3870
|
}
|
|
3742
3871
|
async function classifyRepositoryEntry(repoPath, relativePath) {
|
|
3743
|
-
const absolutePath =
|
|
3872
|
+
const absolutePath = path5.join(repoPath, relativePath);
|
|
3744
3873
|
const entryStats = await lstat(absolutePath);
|
|
3745
3874
|
if (entryStats.isDirectory()) {
|
|
3746
3875
|
return null;
|
|
@@ -3769,8 +3898,8 @@ async function classifyRepositoryEntry(repoPath, relativePath) {
|
|
|
3769
3898
|
}
|
|
3770
3899
|
function shouldIgnoreNonGitEntry(relativePath, isDirectory) {
|
|
3771
3900
|
const normalizedPath = toPosixPath(relativePath);
|
|
3772
|
-
const basename =
|
|
3773
|
-
const segments = normalizedPath.split(
|
|
3901
|
+
const basename = path5.posix.basename(normalizedPath);
|
|
3902
|
+
const segments = normalizedPath.split(path5.posix.sep);
|
|
3774
3903
|
if (segments.includes(GIT_DIR_NAME)) {
|
|
3775
3904
|
return true;
|
|
3776
3905
|
}
|
|
@@ -3780,13 +3909,13 @@ function shouldIgnoreNonGitEntry(relativePath, isDirectory) {
|
|
|
3780
3909
|
return basename === ".DS_Store" || basename === ".env";
|
|
3781
3910
|
}
|
|
3782
3911
|
async function walkNonGitEntries(repoPath, currentRelativePath = "") {
|
|
3783
|
-
const currentAbsolutePath =
|
|
3912
|
+
const currentAbsolutePath = path5.join(repoPath, currentRelativePath);
|
|
3784
3913
|
const dirEntries = await readdir3(currentAbsolutePath, { withFileTypes: true });
|
|
3785
3914
|
const results = [];
|
|
3786
3915
|
for (const dirEntry of dirEntries.sort(
|
|
3787
3916
|
(left, right) => compareStrings(left.name, right.name)
|
|
3788
3917
|
)) {
|
|
3789
|
-
const relativePath = currentRelativePath ?
|
|
3918
|
+
const relativePath = currentRelativePath ? path5.posix.join(currentRelativePath, dirEntry.name) : dirEntry.name;
|
|
3790
3919
|
if (shouldIgnoreNonGitEntry(relativePath, dirEntry.isDirectory())) {
|
|
3791
3920
|
continue;
|
|
3792
3921
|
}
|
|
@@ -3810,7 +3939,7 @@ async function pathExists(filePath) {
|
|
|
3810
3939
|
}
|
|
3811
3940
|
}
|
|
3812
3941
|
async function isGitBackedRepository(repoPath) {
|
|
3813
|
-
return pathExists(
|
|
3942
|
+
return pathExists(path5.join(repoPath, GIT_DIR_NAME));
|
|
3814
3943
|
}
|
|
3815
3944
|
async function runGitCommand(repoPath, args, options = {}) {
|
|
3816
3945
|
const encoding = options.encoding ?? "utf8";
|
|
@@ -3875,7 +4004,7 @@ async function discoverGitEntries(repoPath) {
|
|
|
3875
4004
|
const candidateEntries = [.../* @__PURE__ */ new Set([...trackedEntries, ...untrackedEntries])].map((entry) => toPosixPath(entry)).filter((entry) => !entry.startsWith(".git/") && entry !== ".git");
|
|
3876
4005
|
const relativePaths = [];
|
|
3877
4006
|
for (const entry of candidateEntries.sort(compareStrings)) {
|
|
3878
|
-
if (await pathExists(
|
|
4007
|
+
if (await pathExists(path5.join(repoPath, entry))) {
|
|
3879
4008
|
relativePaths.push(entry);
|
|
3880
4009
|
}
|
|
3881
4010
|
}
|
|
@@ -3955,7 +4084,7 @@ async function analyzeRepository(input) {
|
|
|
3955
4084
|
}
|
|
3956
4085
|
async function writeDeterministicArchive(analysis, outputDir) {
|
|
3957
4086
|
await ensureDirectory2(outputDir);
|
|
3958
|
-
const finalArchivePath =
|
|
4087
|
+
const finalArchivePath = path5.join(outputDir, `${analysis.input.repoId}.tar.zst`);
|
|
3959
4088
|
const tempArchivePath = `${finalArchivePath}.${process.pid}.${Date.now()}.tmp`;
|
|
3960
4089
|
const archiveHash = createHash2("sha256");
|
|
3961
4090
|
let archiveBytes = 0;
|
|
@@ -4322,7 +4451,7 @@ async function runUploadPipeline(options) {
|
|
|
4322
4451
|
repos = await snapshotRepositories({
|
|
4323
4452
|
allowSecrets: options.allowSecrets === true,
|
|
4324
4453
|
cwd: options.cwd,
|
|
4325
|
-
outputDir:
|
|
4454
|
+
outputDir: path6.join(launchSnapshot.paths.launchDir, "archives"),
|
|
4326
4455
|
repoPaths: options.repoPaths
|
|
4327
4456
|
});
|
|
4328
4457
|
} catch (error) {
|
|
@@ -4332,7 +4461,7 @@ async function runUploadPipeline(options) {
|
|
|
4332
4461
|
repos = await snapshotRepositories({
|
|
4333
4462
|
allowSecrets: true,
|
|
4334
4463
|
cwd: options.cwd,
|
|
4335
|
-
outputDir:
|
|
4464
|
+
outputDir: path6.join(launchSnapshot.paths.launchDir, "archives"),
|
|
4336
4465
|
repoPaths: options.repoPaths
|
|
4337
4466
|
});
|
|
4338
4467
|
repos = repos.map((repo) => ({
|
|
@@ -4464,7 +4593,7 @@ async function resolveRepoPaths(repoValues, cwd = process.cwd()) {
|
|
|
4464
4593
|
if (repoValues.length === 0) {
|
|
4465
4594
|
throw new CliError("At least one `--repo` path is required.");
|
|
4466
4595
|
}
|
|
4467
|
-
const resolved = [...new Set(repoValues.map((repoPath) =>
|
|
4596
|
+
const resolved = [...new Set(repoValues.map((repoPath) => path7.resolve(cwd, repoPath)))];
|
|
4468
4597
|
for (const repoPath of resolved) {
|
|
4469
4598
|
let repoStats;
|
|
4470
4599
|
try {
|