@coresource/hz 0.1.9 → 0.20.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 -69
- 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,41 +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]").option("--yes", "Automatically approve all triage prompts").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
|
-
autoApprove: options.yes === true,
|
|
3007
|
-
createSpinner: dependencies.createSpinner,
|
|
3008
|
-
createWebSocket: dependencies.createWebSocket,
|
|
3009
|
-
endpoint: authConfig.endpoint,
|
|
3010
|
-
missionId: resolved,
|
|
3011
|
-
promptInput: dependencies.promptInput,
|
|
3012
|
-
promptSelect: dependencies.promptSelect,
|
|
3013
|
-
registerSignalHandler: dependencies.registerSignalHandler,
|
|
3014
|
-
sleep: dependencies.sleep,
|
|
3015
|
-
stdout: context.stdout
|
|
3016
|
-
});
|
|
3017
|
-
if (monitorResult.exitCode !== 0) {
|
|
3018
|
-
throw new CommanderError(monitorResult.exitCode, "mission-monitor", "");
|
|
3019
|
-
}
|
|
3020
|
-
});
|
|
3021
3088
|
}
|
|
3022
3089
|
|
|
3023
3090
|
// src/commands/mission-run.ts
|
|
3024
3091
|
import { stat as stat3 } from "fs/promises";
|
|
3025
|
-
import
|
|
3092
|
+
import path7 from "path";
|
|
3026
3093
|
import { CommanderError as CommanderError2 } from "commander";
|
|
3027
3094
|
|
|
3028
3095
|
// src/planning.ts
|
|
3096
|
+
import path4 from "path";
|
|
3029
3097
|
import {
|
|
3030
3098
|
confirm as defaultConfirmPrompt,
|
|
3031
3099
|
input as defaultInputPrompt2,
|
|
@@ -3233,25 +3301,85 @@ function renderFailures(stdout, failures) {
|
|
|
3233
3301
|
writeLine(stdout, `- ${failure}`);
|
|
3234
3302
|
});
|
|
3235
3303
|
}
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
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;
|
|
3242
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) {
|
|
3243
3370
|
const promptSelect = options.promptSelect ?? defaultPromptSelect2;
|
|
3244
3371
|
const promptInput = options.promptInput ?? defaultPromptInput2;
|
|
3245
3372
|
const answers = [];
|
|
3246
3373
|
const transcript = [];
|
|
3247
3374
|
for (const question of questions) {
|
|
3248
3375
|
if (question.options && question.options.length > 0) {
|
|
3376
|
+
const autoSelectedOption = options.autoApprove ? pickAutoApprovedOption(question, options.taskDescription, options.repoPaths) : null;
|
|
3249
3377
|
const choices = question.options.map((option) => ({
|
|
3250
3378
|
description: option.description,
|
|
3251
3379
|
name: option.label,
|
|
3252
3380
|
value: option.id
|
|
3253
3381
|
}));
|
|
3254
|
-
const optionId2 = await promptSelect({
|
|
3382
|
+
const optionId2 = autoSelectedOption?.id ?? await promptSelect({
|
|
3255
3383
|
choices,
|
|
3256
3384
|
message: question.text
|
|
3257
3385
|
});
|
|
@@ -3270,7 +3398,7 @@ async function promptForAnswers(options, questions, round) {
|
|
|
3270
3398
|
});
|
|
3271
3399
|
continue;
|
|
3272
3400
|
}
|
|
3273
|
-
const detail = await promptInput({
|
|
3401
|
+
const detail = options.autoApprove ? buildAutoApprovedDetail(question, options.taskDescription, options.repoPaths) : await promptInput({
|
|
3274
3402
|
default: question.inputDefault,
|
|
3275
3403
|
message: question.detailPrompt ?? question.text
|
|
3276
3404
|
});
|
|
@@ -3519,7 +3647,7 @@ async function runPlanningFlow(options) {
|
|
|
3519
3647
|
// src/upload.ts
|
|
3520
3648
|
import { confirm } from "@inquirer/prompts";
|
|
3521
3649
|
import { createReadStream as createReadStream2 } from "fs";
|
|
3522
|
-
import
|
|
3650
|
+
import path6 from "path";
|
|
3523
3651
|
import { Transform as Transform2 } from "stream";
|
|
3524
3652
|
import ora3 from "ora";
|
|
3525
3653
|
import pc7 from "picocolors";
|
|
@@ -3537,7 +3665,7 @@ import {
|
|
|
3537
3665
|
rm as rm3,
|
|
3538
3666
|
stat as stat2
|
|
3539
3667
|
} from "fs/promises";
|
|
3540
|
-
import
|
|
3668
|
+
import path5 from "path";
|
|
3541
3669
|
import { createInterface } from "readline";
|
|
3542
3670
|
import { Transform } from "stream";
|
|
3543
3671
|
import { pipeline } from "stream/promises";
|
|
@@ -3561,7 +3689,7 @@ var SECRET_PATTERNS = [
|
|
|
3561
3689
|
{
|
|
3562
3690
|
kind: "stripe_live_key",
|
|
3563
3691
|
message: "Found a Stripe live secret key.",
|
|
3564
|
-
regex: /sk_live_[A-Za-z0-9]
|
|
3692
|
+
regex: /sk_live_[A-Za-z0-9]{16,}/g
|
|
3565
3693
|
},
|
|
3566
3694
|
{
|
|
3567
3695
|
kind: "aws_access_key",
|
|
@@ -3571,7 +3699,7 @@ var SECRET_PATTERNS = [
|
|
|
3571
3699
|
{
|
|
3572
3700
|
kind: "github_personal_access_token",
|
|
3573
3701
|
message: "Found a GitHub personal access token.",
|
|
3574
|
-
regex: /ghp_[A-Za-z0-9]
|
|
3702
|
+
regex: /ghp_[A-Za-z0-9]{36,}/g
|
|
3575
3703
|
},
|
|
3576
3704
|
{
|
|
3577
3705
|
kind: "private_key",
|
|
@@ -3608,7 +3736,7 @@ function compareStrings(left, right) {
|
|
|
3608
3736
|
return left < right ? -1 : 1;
|
|
3609
3737
|
}
|
|
3610
3738
|
function toPosixPath(filePath) {
|
|
3611
|
-
return filePath.split(
|
|
3739
|
+
return filePath.split(path5.sep).join(path5.posix.sep);
|
|
3612
3740
|
}
|
|
3613
3741
|
function splitNullTerminatedBuffer(buffer) {
|
|
3614
3742
|
return buffer.toString("utf8").split("\0").filter((entry) => entry.length > 0);
|
|
@@ -3640,7 +3768,7 @@ async function ensureDirectory2(dirPath) {
|
|
|
3640
3768
|
}
|
|
3641
3769
|
async function validateDirectoryPath(repoPath, options) {
|
|
3642
3770
|
const cwd = options.cwd ?? process.cwd();
|
|
3643
|
-
const absolutePath =
|
|
3771
|
+
const absolutePath = path5.resolve(cwd, repoPath);
|
|
3644
3772
|
let directoryStats;
|
|
3645
3773
|
try {
|
|
3646
3774
|
directoryStats = await stat2(absolutePath);
|
|
@@ -3671,7 +3799,7 @@ async function resolveRepositoryInputs(repoPaths, options) {
|
|
|
3671
3799
|
);
|
|
3672
3800
|
const counts = /* @__PURE__ */ new Map();
|
|
3673
3801
|
const baseNames = absolutePaths.map(
|
|
3674
|
-
(absolutePath) => sanitizeIdentifier(
|
|
3802
|
+
(absolutePath) => sanitizeIdentifier(path5.basename(absolutePath))
|
|
3675
3803
|
);
|
|
3676
3804
|
for (const baseName of baseNames) {
|
|
3677
3805
|
counts.set(baseName, (counts.get(baseName) ?? 0) + 1);
|
|
@@ -3719,7 +3847,7 @@ function matchSecretPatterns(line, relativePath, lineNumber) {
|
|
|
3719
3847
|
}
|
|
3720
3848
|
async function scanRegularFileForSecrets(filePath, relativePath) {
|
|
3721
3849
|
const findings = [];
|
|
3722
|
-
const basename =
|
|
3850
|
+
const basename = path5.posix.basename(relativePath);
|
|
3723
3851
|
if (basename.startsWith(".env")) {
|
|
3724
3852
|
findings.push({
|
|
3725
3853
|
kind: "env_file",
|
|
@@ -3741,7 +3869,7 @@ async function scanRegularFileForSecrets(filePath, relativePath) {
|
|
|
3741
3869
|
return findings;
|
|
3742
3870
|
}
|
|
3743
3871
|
async function classifyRepositoryEntry(repoPath, relativePath) {
|
|
3744
|
-
const absolutePath =
|
|
3872
|
+
const absolutePath = path5.join(repoPath, relativePath);
|
|
3745
3873
|
const entryStats = await lstat(absolutePath);
|
|
3746
3874
|
if (entryStats.isDirectory()) {
|
|
3747
3875
|
return null;
|
|
@@ -3770,8 +3898,8 @@ async function classifyRepositoryEntry(repoPath, relativePath) {
|
|
|
3770
3898
|
}
|
|
3771
3899
|
function shouldIgnoreNonGitEntry(relativePath, isDirectory) {
|
|
3772
3900
|
const normalizedPath = toPosixPath(relativePath);
|
|
3773
|
-
const basename =
|
|
3774
|
-
const segments = normalizedPath.split(
|
|
3901
|
+
const basename = path5.posix.basename(normalizedPath);
|
|
3902
|
+
const segments = normalizedPath.split(path5.posix.sep);
|
|
3775
3903
|
if (segments.includes(GIT_DIR_NAME)) {
|
|
3776
3904
|
return true;
|
|
3777
3905
|
}
|
|
@@ -3781,13 +3909,13 @@ function shouldIgnoreNonGitEntry(relativePath, isDirectory) {
|
|
|
3781
3909
|
return basename === ".DS_Store" || basename === ".env";
|
|
3782
3910
|
}
|
|
3783
3911
|
async function walkNonGitEntries(repoPath, currentRelativePath = "") {
|
|
3784
|
-
const currentAbsolutePath =
|
|
3912
|
+
const currentAbsolutePath = path5.join(repoPath, currentRelativePath);
|
|
3785
3913
|
const dirEntries = await readdir3(currentAbsolutePath, { withFileTypes: true });
|
|
3786
3914
|
const results = [];
|
|
3787
3915
|
for (const dirEntry of dirEntries.sort(
|
|
3788
3916
|
(left, right) => compareStrings(left.name, right.name)
|
|
3789
3917
|
)) {
|
|
3790
|
-
const relativePath = currentRelativePath ?
|
|
3918
|
+
const relativePath = currentRelativePath ? path5.posix.join(currentRelativePath, dirEntry.name) : dirEntry.name;
|
|
3791
3919
|
if (shouldIgnoreNonGitEntry(relativePath, dirEntry.isDirectory())) {
|
|
3792
3920
|
continue;
|
|
3793
3921
|
}
|
|
@@ -3811,7 +3939,7 @@ async function pathExists(filePath) {
|
|
|
3811
3939
|
}
|
|
3812
3940
|
}
|
|
3813
3941
|
async function isGitBackedRepository(repoPath) {
|
|
3814
|
-
return pathExists(
|
|
3942
|
+
return pathExists(path5.join(repoPath, GIT_DIR_NAME));
|
|
3815
3943
|
}
|
|
3816
3944
|
async function runGitCommand(repoPath, args, options = {}) {
|
|
3817
3945
|
const encoding = options.encoding ?? "utf8";
|
|
@@ -3876,7 +4004,7 @@ async function discoverGitEntries(repoPath) {
|
|
|
3876
4004
|
const candidateEntries = [.../* @__PURE__ */ new Set([...trackedEntries, ...untrackedEntries])].map((entry) => toPosixPath(entry)).filter((entry) => !entry.startsWith(".git/") && entry !== ".git");
|
|
3877
4005
|
const relativePaths = [];
|
|
3878
4006
|
for (const entry of candidateEntries.sort(compareStrings)) {
|
|
3879
|
-
if (await pathExists(
|
|
4007
|
+
if (await pathExists(path5.join(repoPath, entry))) {
|
|
3880
4008
|
relativePaths.push(entry);
|
|
3881
4009
|
}
|
|
3882
4010
|
}
|
|
@@ -3956,7 +4084,7 @@ async function analyzeRepository(input) {
|
|
|
3956
4084
|
}
|
|
3957
4085
|
async function writeDeterministicArchive(analysis, outputDir) {
|
|
3958
4086
|
await ensureDirectory2(outputDir);
|
|
3959
|
-
const finalArchivePath =
|
|
4087
|
+
const finalArchivePath = path5.join(outputDir, `${analysis.input.repoId}.tar.zst`);
|
|
3960
4088
|
const tempArchivePath = `${finalArchivePath}.${process.pid}.${Date.now()}.tmp`;
|
|
3961
4089
|
const archiveHash = createHash2("sha256");
|
|
3962
4090
|
let archiveBytes = 0;
|
|
@@ -4323,7 +4451,7 @@ async function runUploadPipeline(options) {
|
|
|
4323
4451
|
repos = await snapshotRepositories({
|
|
4324
4452
|
allowSecrets: options.allowSecrets === true,
|
|
4325
4453
|
cwd: options.cwd,
|
|
4326
|
-
outputDir:
|
|
4454
|
+
outputDir: path6.join(launchSnapshot.paths.launchDir, "archives"),
|
|
4327
4455
|
repoPaths: options.repoPaths
|
|
4328
4456
|
});
|
|
4329
4457
|
} catch (error) {
|
|
@@ -4333,7 +4461,7 @@ async function runUploadPipeline(options) {
|
|
|
4333
4461
|
repos = await snapshotRepositories({
|
|
4334
4462
|
allowSecrets: true,
|
|
4335
4463
|
cwd: options.cwd,
|
|
4336
|
-
outputDir:
|
|
4464
|
+
outputDir: path6.join(launchSnapshot.paths.launchDir, "archives"),
|
|
4337
4465
|
repoPaths: options.repoPaths
|
|
4338
4466
|
});
|
|
4339
4467
|
repos = repos.map((repo) => ({
|
|
@@ -4465,7 +4593,7 @@ async function resolveRepoPaths(repoValues, cwd = process.cwd()) {
|
|
|
4465
4593
|
if (repoValues.length === 0) {
|
|
4466
4594
|
throw new CliError("At least one `--repo` path is required.");
|
|
4467
4595
|
}
|
|
4468
|
-
const resolved = [...new Set(repoValues.map((repoPath) =>
|
|
4596
|
+
const resolved = [...new Set(repoValues.map((repoPath) => path7.resolve(cwd, repoPath)))];
|
|
4469
4597
|
for (const repoPath of resolved) {
|
|
4470
4598
|
let repoStats;
|
|
4471
4599
|
try {
|