@outcomeeng/spx 0.1.6 → 0.1.8
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/README.md +1 -1
- package/dist/{chunk-5L7CHFBC.js → chunk-BQLLI7GS.js} +1 -1
- package/dist/chunk-BQLLI7GS.js.map +1 -0
- package/dist/cli.js +340 -278
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-5L7CHFBC.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LEAF_KIND,
|
|
3
3
|
parseWorkItemName
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-BQLLI7GS.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -17,19 +17,19 @@ async function initCommand(options = {}) {
|
|
|
17
17
|
["plugin", "marketplace", "list"],
|
|
18
18
|
{ cwd }
|
|
19
19
|
);
|
|
20
|
-
const exists = listOutput.includes("
|
|
20
|
+
const exists = listOutput.includes("outcomeeng");
|
|
21
21
|
if (!exists) {
|
|
22
22
|
await execa(
|
|
23
23
|
"claude",
|
|
24
|
-
["plugin", "marketplace", "add", "
|
|
24
|
+
["plugin", "marketplace", "add", "outcomeeng/claude"],
|
|
25
25
|
{ cwd }
|
|
26
26
|
);
|
|
27
|
-
return "\u2713
|
|
27
|
+
return "\u2713 outcomeeng marketplace installed successfully\n\nRun 'claude plugin marketplace list' to view all marketplaces.";
|
|
28
28
|
} else {
|
|
29
|
-
await execa("claude", ["plugin", "marketplace", "update", "
|
|
29
|
+
await execa("claude", ["plugin", "marketplace", "update", "outcomeeng"], {
|
|
30
30
|
cwd
|
|
31
31
|
});
|
|
32
|
-
return "\u2713
|
|
32
|
+
return "\u2713 outcomeeng marketplace updated successfully\n\nThe marketplace is now up to date.";
|
|
33
33
|
}
|
|
34
34
|
} catch (error) {
|
|
35
35
|
if (error instanceof Error) {
|
|
@@ -643,7 +643,7 @@ Searched for: **/.claude/settings.local.json`;
|
|
|
643
643
|
|
|
644
644
|
// src/domains/claude/index.ts
|
|
645
645
|
function registerClaudeCommands(claudeCmd) {
|
|
646
|
-
claudeCmd.command("init").description("Initialize or update
|
|
646
|
+
claudeCmd.command("init").description("Initialize or update outcomeeng marketplace plugin").action(async () => {
|
|
647
647
|
try {
|
|
648
648
|
const output = await initCommand({ cwd: process.cwd() });
|
|
649
649
|
console.log(output);
|
|
@@ -704,7 +704,154 @@ var claudeDomain = {
|
|
|
704
704
|
|
|
705
705
|
// src/commands/session/archive.ts
|
|
706
706
|
import { mkdir, rename, stat } from "fs/promises";
|
|
707
|
-
import { dirname, join as join2 } from "path";
|
|
707
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
708
|
+
|
|
709
|
+
// src/git/root.ts
|
|
710
|
+
import { execa as execa2 } from "execa";
|
|
711
|
+
import { dirname, isAbsolute, join, resolve } from "path";
|
|
712
|
+
|
|
713
|
+
// src/config/defaults.ts
|
|
714
|
+
var DEFAULT_CONFIG = {
|
|
715
|
+
specs: {
|
|
716
|
+
root: "specs",
|
|
717
|
+
work: {
|
|
718
|
+
dir: "work",
|
|
719
|
+
statusDirs: {
|
|
720
|
+
doing: "doing",
|
|
721
|
+
backlog: "backlog",
|
|
722
|
+
done: "archive"
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
decisions: "decisions",
|
|
726
|
+
templates: "templates"
|
|
727
|
+
},
|
|
728
|
+
sessions: {
|
|
729
|
+
dir: ".spx/sessions",
|
|
730
|
+
statusDirs: {
|
|
731
|
+
todo: "todo",
|
|
732
|
+
doing: "doing",
|
|
733
|
+
archive: "archive"
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
// src/git/root.ts
|
|
739
|
+
var defaultDeps = {
|
|
740
|
+
execa: async (command, args, options) => {
|
|
741
|
+
const result = await execa2(command, args, options);
|
|
742
|
+
return {
|
|
743
|
+
exitCode: result.exitCode ?? 0,
|
|
744
|
+
stdout: typeof result.stdout === "string" ? result.stdout : String(result.stdout),
|
|
745
|
+
stderr: typeof result.stderr === "string" ? result.stderr : String(result.stderr)
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
var NOT_GIT_REPO_WARNING = "Warning: Not in a git repository. Sessions will be created relative to current directory.";
|
|
750
|
+
function extractStdout(stdout) {
|
|
751
|
+
if (!stdout) return "";
|
|
752
|
+
const str = typeof stdout === "string" ? stdout : String(stdout);
|
|
753
|
+
return str.trim().replace(/\/+$/, "");
|
|
754
|
+
}
|
|
755
|
+
async function detectMainRepoRoot(cwd = process.cwd(), deps = defaultDeps) {
|
|
756
|
+
try {
|
|
757
|
+
const toplevelResult = await deps.execa(
|
|
758
|
+
"git",
|
|
759
|
+
["rev-parse", "--show-toplevel"],
|
|
760
|
+
{ cwd, reject: false }
|
|
761
|
+
);
|
|
762
|
+
if (toplevelResult.exitCode !== 0 || !toplevelResult.stdout) {
|
|
763
|
+
return {
|
|
764
|
+
root: cwd,
|
|
765
|
+
isGitRepo: false,
|
|
766
|
+
warning: NOT_GIT_REPO_WARNING
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
const toplevel = extractStdout(toplevelResult.stdout);
|
|
770
|
+
const commonDirResult = await deps.execa(
|
|
771
|
+
"git",
|
|
772
|
+
["rev-parse", "--git-common-dir"],
|
|
773
|
+
{ cwd, reject: false }
|
|
774
|
+
);
|
|
775
|
+
if (commonDirResult.exitCode !== 0 || !commonDirResult.stdout) {
|
|
776
|
+
return {
|
|
777
|
+
root: toplevel,
|
|
778
|
+
isGitRepo: true
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
const commonDir = extractStdout(commonDirResult.stdout);
|
|
782
|
+
const absoluteCommonDir = isAbsolute(commonDir) ? commonDir : resolve(toplevel, commonDir);
|
|
783
|
+
const mainRepoRoot = dirname(absoluteCommonDir);
|
|
784
|
+
return {
|
|
785
|
+
root: mainRepoRoot,
|
|
786
|
+
isGitRepo: true
|
|
787
|
+
};
|
|
788
|
+
} catch {
|
|
789
|
+
return {
|
|
790
|
+
root: cwd,
|
|
791
|
+
isGitRepo: false,
|
|
792
|
+
warning: NOT_GIT_REPO_WARNING
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
async function resolveSessionConfig(options = {}) {
|
|
797
|
+
const { sessionsDir, cwd, deps } = options;
|
|
798
|
+
const { statusDirs: statusDirs2 } = DEFAULT_CONFIG.sessions;
|
|
799
|
+
if (sessionsDir) {
|
|
800
|
+
return {
|
|
801
|
+
config: {
|
|
802
|
+
todoDir: join(sessionsDir, statusDirs2.todo),
|
|
803
|
+
doingDir: join(sessionsDir, statusDirs2.doing),
|
|
804
|
+
archiveDir: join(sessionsDir, statusDirs2.archive)
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
const gitResult = await detectMainRepoRoot(cwd, deps);
|
|
809
|
+
const baseDir = join(gitResult.root, DEFAULT_CONFIG.sessions.dir);
|
|
810
|
+
return {
|
|
811
|
+
config: {
|
|
812
|
+
todoDir: join(baseDir, statusDirs2.todo),
|
|
813
|
+
doingDir: join(baseDir, statusDirs2.doing),
|
|
814
|
+
archiveDir: join(baseDir, statusDirs2.archive)
|
|
815
|
+
},
|
|
816
|
+
warning: gitResult.warning
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/session/batch.ts
|
|
821
|
+
var BatchError = class extends Error {
|
|
822
|
+
results;
|
|
823
|
+
constructor(results) {
|
|
824
|
+
const failures = results.filter((r) => !r.ok);
|
|
825
|
+
const successes = results.filter((r) => r.ok);
|
|
826
|
+
super(
|
|
827
|
+
`${failures.length} of ${results.length} operations failed. ${successes.length} succeeded.`
|
|
828
|
+
);
|
|
829
|
+
this.name = "BatchError";
|
|
830
|
+
this.results = results;
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
async function processBatch(ids, handler) {
|
|
834
|
+
const results = [];
|
|
835
|
+
for (const id of ids) {
|
|
836
|
+
try {
|
|
837
|
+
const output2 = await handler(id);
|
|
838
|
+
results.push({ id, ok: true, message: output2 });
|
|
839
|
+
} catch (error) {
|
|
840
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
841
|
+
results.push({ id, ok: false, message });
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
const output = results.map((r) => r.ok ? r.message : `Error (${r.id}): ${r.message}`).join("\n\n");
|
|
845
|
+
const hasFailures = results.some((r) => !r.ok);
|
|
846
|
+
if (hasFailures) {
|
|
847
|
+
const err = new BatchError(results);
|
|
848
|
+
err.message = `${err.message}
|
|
849
|
+
|
|
850
|
+
${output}`;
|
|
851
|
+
throw err;
|
|
852
|
+
}
|
|
853
|
+
return output;
|
|
854
|
+
}
|
|
708
855
|
|
|
709
856
|
// src/session/errors.ts
|
|
710
857
|
var SessionError = class extends Error {
|
|
@@ -753,33 +900,76 @@ var NoSessionsAvailableError = class extends SessionError {
|
|
|
753
900
|
}
|
|
754
901
|
};
|
|
755
902
|
|
|
756
|
-
// src/session/
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
dir: "work",
|
|
765
|
-
statusDirs: {
|
|
766
|
-
doing: "doing",
|
|
767
|
-
backlog: "backlog",
|
|
768
|
-
done: "archive"
|
|
769
|
-
}
|
|
770
|
-
},
|
|
771
|
-
decisions: "decisions",
|
|
772
|
-
templates: "templates"
|
|
773
|
-
},
|
|
774
|
-
sessions: {
|
|
775
|
-
dir: ".spx/sessions",
|
|
776
|
-
statusDirs: {
|
|
777
|
-
todo: "todo",
|
|
778
|
-
doing: "doing",
|
|
779
|
-
archive: "archive"
|
|
780
|
-
}
|
|
903
|
+
// src/commands/session/archive.ts
|
|
904
|
+
var SessionAlreadyArchivedError = class extends Error {
|
|
905
|
+
/** The session ID that is already archived */
|
|
906
|
+
sessionId;
|
|
907
|
+
constructor(sessionId) {
|
|
908
|
+
super(`Session already archived: ${sessionId}.`);
|
|
909
|
+
this.name = "SessionAlreadyArchivedError";
|
|
910
|
+
this.sessionId = sessionId;
|
|
781
911
|
}
|
|
782
912
|
};
|
|
913
|
+
async function resolveArchivePaths(sessionId, config) {
|
|
914
|
+
const filename = `${sessionId}.md`;
|
|
915
|
+
const todoPath = join2(config.todoDir, filename);
|
|
916
|
+
const doingPath = join2(config.doingDir, filename);
|
|
917
|
+
const archivePath = join2(config.archiveDir, filename);
|
|
918
|
+
try {
|
|
919
|
+
const archiveStats = await stat(archivePath);
|
|
920
|
+
if (archiveStats.isFile()) {
|
|
921
|
+
throw new SessionAlreadyArchivedError(sessionId);
|
|
922
|
+
}
|
|
923
|
+
} catch (error) {
|
|
924
|
+
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
925
|
+
throw error;
|
|
926
|
+
}
|
|
927
|
+
if (error instanceof SessionAlreadyArchivedError) {
|
|
928
|
+
throw error;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
try {
|
|
932
|
+
const todoStats = await stat(todoPath);
|
|
933
|
+
if (todoStats.isFile()) {
|
|
934
|
+
return { source: todoPath, target: archivePath };
|
|
935
|
+
}
|
|
936
|
+
} catch {
|
|
937
|
+
}
|
|
938
|
+
try {
|
|
939
|
+
const doingStats = await stat(doingPath);
|
|
940
|
+
if (doingStats.isFile()) {
|
|
941
|
+
return { source: doingPath, target: archivePath };
|
|
942
|
+
}
|
|
943
|
+
} catch {
|
|
944
|
+
}
|
|
945
|
+
throw new SessionNotFoundError(sessionId);
|
|
946
|
+
}
|
|
947
|
+
async function archiveSingle(sessionId, config) {
|
|
948
|
+
const { source, target } = await resolveArchivePaths(sessionId, config);
|
|
949
|
+
await mkdir(dirname2(target), { recursive: true });
|
|
950
|
+
await rename(source, target);
|
|
951
|
+
return `Archived session: ${sessionId}
|
|
952
|
+
Archive location: ${target}`;
|
|
953
|
+
}
|
|
954
|
+
async function archiveCommand(options) {
|
|
955
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
956
|
+
return processBatch(options.sessionIds, (id) => archiveSingle(id, config));
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/commands/session/delete.ts
|
|
960
|
+
import { stat as stat2, unlink } from "fs/promises";
|
|
961
|
+
|
|
962
|
+
// src/session/delete.ts
|
|
963
|
+
function resolveDeletePath(sessionId, existingPaths) {
|
|
964
|
+
const matchingPath = existingPaths.find((path8) => path8.includes(sessionId));
|
|
965
|
+
if (!matchingPath) {
|
|
966
|
+
throw new SessionNotFoundError(sessionId);
|
|
967
|
+
}
|
|
968
|
+
return matchingPath;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// src/session/show.ts
|
|
972
|
+
import { join as join3 } from "path";
|
|
783
973
|
|
|
784
974
|
// src/session/list.ts
|
|
785
975
|
import { parse as parseYaml } from "yaml";
|
|
@@ -819,6 +1009,8 @@ function parseSessionId(id) {
|
|
|
819
1009
|
}
|
|
820
1010
|
|
|
821
1011
|
// src/session/types.ts
|
|
1012
|
+
var SESSION_STATUSES = ["todo", "doing", "archive"];
|
|
1013
|
+
var DEFAULT_LIST_STATUSES = ["doing", "todo"];
|
|
822
1014
|
var PRIORITY_ORDER = {
|
|
823
1015
|
high: 0,
|
|
824
1016
|
medium: 1,
|
|
@@ -905,11 +1097,11 @@ function sortSessions(sessions) {
|
|
|
905
1097
|
// src/session/show.ts
|
|
906
1098
|
var { dir: sessionsBaseDir, statusDirs } = DEFAULT_CONFIG.sessions;
|
|
907
1099
|
var DEFAULT_SESSION_CONFIG = {
|
|
908
|
-
todoDir:
|
|
909
|
-
doingDir:
|
|
910
|
-
archiveDir:
|
|
1100
|
+
todoDir: join3(sessionsBaseDir, statusDirs.todo),
|
|
1101
|
+
doingDir: join3(sessionsBaseDir, statusDirs.doing),
|
|
1102
|
+
archiveDir: join3(sessionsBaseDir, statusDirs.archive)
|
|
911
1103
|
};
|
|
912
|
-
var SEARCH_ORDER = [
|
|
1104
|
+
var SEARCH_ORDER = [...SESSION_STATUSES];
|
|
913
1105
|
function resolveSessionPaths(id, config = DEFAULT_SESSION_CONFIG) {
|
|
914
1106
|
const filename = `${id}.md`;
|
|
915
1107
|
return [
|
|
@@ -941,76 +1133,6 @@ function formatShowOutput(content, options) {
|
|
|
941
1133
|
return header + separator + content;
|
|
942
1134
|
}
|
|
943
1135
|
|
|
944
|
-
// src/commands/session/archive.ts
|
|
945
|
-
var SessionAlreadyArchivedError = class extends Error {
|
|
946
|
-
/** The session ID that is already archived */
|
|
947
|
-
sessionId;
|
|
948
|
-
constructor(sessionId) {
|
|
949
|
-
super(`Session already archived: ${sessionId}.`);
|
|
950
|
-
this.name = "SessionAlreadyArchivedError";
|
|
951
|
-
this.sessionId = sessionId;
|
|
952
|
-
}
|
|
953
|
-
};
|
|
954
|
-
async function resolveArchivePaths(sessionId, config) {
|
|
955
|
-
const filename = `${sessionId}.md`;
|
|
956
|
-
const todoPath = join2(config.todoDir, filename);
|
|
957
|
-
const doingPath = join2(config.doingDir, filename);
|
|
958
|
-
const archivePath = join2(config.archiveDir, filename);
|
|
959
|
-
try {
|
|
960
|
-
const archiveStats = await stat(archivePath);
|
|
961
|
-
if (archiveStats.isFile()) {
|
|
962
|
-
throw new SessionAlreadyArchivedError(sessionId);
|
|
963
|
-
}
|
|
964
|
-
} catch (error) {
|
|
965
|
-
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
966
|
-
throw error;
|
|
967
|
-
}
|
|
968
|
-
if (error instanceof SessionAlreadyArchivedError) {
|
|
969
|
-
throw error;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
try {
|
|
973
|
-
const todoStats = await stat(todoPath);
|
|
974
|
-
if (todoStats.isFile()) {
|
|
975
|
-
return { source: todoPath, target: archivePath };
|
|
976
|
-
}
|
|
977
|
-
} catch {
|
|
978
|
-
}
|
|
979
|
-
try {
|
|
980
|
-
const doingStats = await stat(doingPath);
|
|
981
|
-
if (doingStats.isFile()) {
|
|
982
|
-
return { source: doingPath, target: archivePath };
|
|
983
|
-
}
|
|
984
|
-
} catch {
|
|
985
|
-
}
|
|
986
|
-
throw new SessionNotFoundError(sessionId);
|
|
987
|
-
}
|
|
988
|
-
async function archiveCommand(options) {
|
|
989
|
-
const config = options.sessionsDir ? {
|
|
990
|
-
todoDir: join2(options.sessionsDir, "todo"),
|
|
991
|
-
doingDir: join2(options.sessionsDir, "doing"),
|
|
992
|
-
archiveDir: join2(options.sessionsDir, "archive")
|
|
993
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
994
|
-
const { source, target } = await resolveArchivePaths(options.sessionId, config);
|
|
995
|
-
await mkdir(dirname(target), { recursive: true });
|
|
996
|
-
await rename(source, target);
|
|
997
|
-
return `Archived session: ${options.sessionId}
|
|
998
|
-
Archive location: ${target}`;
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
// src/commands/session/delete.ts
|
|
1002
|
-
import { stat as stat2, unlink } from "fs/promises";
|
|
1003
|
-
import { join as join3 } from "path";
|
|
1004
|
-
|
|
1005
|
-
// src/session/delete.ts
|
|
1006
|
-
function resolveDeletePath(sessionId, existingPaths) {
|
|
1007
|
-
const matchingPath = existingPaths.find((path8) => path8.includes(sessionId));
|
|
1008
|
-
if (!matchingPath) {
|
|
1009
|
-
throw new SessionNotFoundError(sessionId);
|
|
1010
|
-
}
|
|
1011
|
-
return matchingPath;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
1136
|
// src/commands/session/delete.ts
|
|
1015
1137
|
async function findExistingPaths(paths) {
|
|
1016
1138
|
const existing = [];
|
|
@@ -1025,62 +1147,21 @@ async function findExistingPaths(paths) {
|
|
|
1025
1147
|
}
|
|
1026
1148
|
return existing;
|
|
1027
1149
|
}
|
|
1028
|
-
async function
|
|
1029
|
-
const
|
|
1030
|
-
todoDir: join3(options.sessionsDir, "todo"),
|
|
1031
|
-
doingDir: join3(options.sessionsDir, "doing"),
|
|
1032
|
-
archiveDir: join3(options.sessionsDir, "archive")
|
|
1033
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1034
|
-
const paths = resolveSessionPaths(options.sessionId, config);
|
|
1150
|
+
async function deleteSingle(sessionId, config) {
|
|
1151
|
+
const paths = resolveSessionPaths(sessionId, config);
|
|
1035
1152
|
const existingPaths = await findExistingPaths(paths);
|
|
1036
|
-
const pathToDelete = resolveDeletePath(
|
|
1153
|
+
const pathToDelete = resolveDeletePath(sessionId, existingPaths);
|
|
1037
1154
|
await unlink(pathToDelete);
|
|
1038
|
-
return `Deleted session: ${
|
|
1155
|
+
return `Deleted session: ${sessionId}`;
|
|
1156
|
+
}
|
|
1157
|
+
async function deleteCommand(options) {
|
|
1158
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1159
|
+
return processBatch(options.sessionIds, (id) => deleteSingle(id, config));
|
|
1039
1160
|
}
|
|
1040
1161
|
|
|
1041
1162
|
// src/commands/session/handoff.ts
|
|
1042
1163
|
import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
1043
|
-
import { join as
|
|
1044
|
-
|
|
1045
|
-
// src/git/root.ts
|
|
1046
|
-
import { execa as execa2 } from "execa";
|
|
1047
|
-
import { join as join4 } from "path";
|
|
1048
|
-
var defaultDeps = {
|
|
1049
|
-
execa: (command, args, options) => execa2(command, args, options)
|
|
1050
|
-
};
|
|
1051
|
-
var NOT_GIT_REPO_WARNING = "Warning: Not in a git repository. Sessions will be created relative to current directory.";
|
|
1052
|
-
async function detectGitRoot(cwd = process.cwd(), deps = defaultDeps) {
|
|
1053
|
-
try {
|
|
1054
|
-
const result = await deps.execa(
|
|
1055
|
-
"git",
|
|
1056
|
-
["rev-parse", "--show-toplevel"],
|
|
1057
|
-
{ cwd, reject: false }
|
|
1058
|
-
);
|
|
1059
|
-
if (result.exitCode === 0 && result.stdout) {
|
|
1060
|
-
const stdout = typeof result.stdout === "string" ? result.stdout : result.stdout.toString();
|
|
1061
|
-
const gitRoot = stdout.trim().replace(/\/+$/, "");
|
|
1062
|
-
return {
|
|
1063
|
-
root: gitRoot,
|
|
1064
|
-
isGitRepo: true
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
return {
|
|
1068
|
-
root: cwd,
|
|
1069
|
-
isGitRepo: false,
|
|
1070
|
-
warning: NOT_GIT_REPO_WARNING
|
|
1071
|
-
};
|
|
1072
|
-
} catch {
|
|
1073
|
-
return {
|
|
1074
|
-
root: cwd,
|
|
1075
|
-
isGitRepo: false,
|
|
1076
|
-
warning: NOT_GIT_REPO_WARNING
|
|
1077
|
-
};
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
function buildSessionPathFromRoot(gitRoot, sessionId, config) {
|
|
1081
|
-
const filename = `${sessionId}.md`;
|
|
1082
|
-
return join4(gitRoot, config.todoDir, filename);
|
|
1083
|
-
}
|
|
1164
|
+
import { join as join4, resolve as resolve2 } from "path";
|
|
1084
1165
|
|
|
1085
1166
|
// src/session/create.ts
|
|
1086
1167
|
var MIN_CONTENT_LENGTH = 1;
|
|
@@ -1101,7 +1182,6 @@ function validateSessionContent(content) {
|
|
|
1101
1182
|
}
|
|
1102
1183
|
|
|
1103
1184
|
// src/commands/session/handoff.ts
|
|
1104
|
-
var { statusDirs: statusDirs2 } = DEFAULT_CONFIG.sessions;
|
|
1105
1185
|
var FRONT_MATTER_START = /^---\r?\n/;
|
|
1106
1186
|
function hasFrontmatter(content) {
|
|
1107
1187
|
return FRONT_MATTER_START.test(content);
|
|
@@ -1126,20 +1206,7 @@ priority: medium
|
|
|
1126
1206
|
${content}`;
|
|
1127
1207
|
}
|
|
1128
1208
|
async function handoffCommand(options) {
|
|
1129
|
-
const config = options.sessionsDir
|
|
1130
|
-
todoDir: join5(options.sessionsDir, statusDirs2.todo),
|
|
1131
|
-
doingDir: join5(options.sessionsDir, statusDirs2.doing),
|
|
1132
|
-
archiveDir: join5(options.sessionsDir, statusDirs2.archive)
|
|
1133
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1134
|
-
let baseDir;
|
|
1135
|
-
let warningMessage;
|
|
1136
|
-
if (options.sessionsDir) {
|
|
1137
|
-
baseDir = options.sessionsDir;
|
|
1138
|
-
} else {
|
|
1139
|
-
const gitResult = await detectGitRoot();
|
|
1140
|
-
baseDir = gitResult.root;
|
|
1141
|
-
warningMessage = gitResult.warning;
|
|
1142
|
-
}
|
|
1209
|
+
const { config, warning } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1143
1210
|
const sessionId = generateSessionId();
|
|
1144
1211
|
const fullContent = buildSessionContent(options.content);
|
|
1145
1212
|
const validation = validateSessionContent(fullContent);
|
|
@@ -1147,23 +1214,21 @@ async function handoffCommand(options) {
|
|
|
1147
1214
|
throw new SessionInvalidContentError(validation.error ?? "Unknown validation error");
|
|
1148
1215
|
}
|
|
1149
1216
|
const filename = `${sessionId}.md`;
|
|
1150
|
-
const sessionPath =
|
|
1151
|
-
const absolutePath =
|
|
1152
|
-
|
|
1153
|
-
await mkdir2(todoDir, { recursive: true });
|
|
1217
|
+
const sessionPath = join4(config.todoDir, filename);
|
|
1218
|
+
const absolutePath = resolve2(sessionPath);
|
|
1219
|
+
await mkdir2(config.todoDir, { recursive: true });
|
|
1154
1220
|
await writeFile(sessionPath, fullContent, "utf-8");
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
if (warningMessage) {
|
|
1158
|
-
process.stderr.write(`${warningMessage}
|
|
1221
|
+
if (warning) {
|
|
1222
|
+
process.stderr.write(`${warning}
|
|
1159
1223
|
`);
|
|
1160
1224
|
}
|
|
1161
|
-
return
|
|
1225
|
+
return `Created handoff session <HANDOFF_ID>${sessionId}</HANDOFF_ID>
|
|
1226
|
+
<SESSION_FILE>${absolutePath}</SESSION_FILE>`;
|
|
1162
1227
|
}
|
|
1163
1228
|
|
|
1164
1229
|
// src/commands/session/list.ts
|
|
1165
1230
|
import { readdir, readFile } from "fs/promises";
|
|
1166
|
-
import { join as
|
|
1231
|
+
import { join as join5 } from "path";
|
|
1167
1232
|
async function loadSessionsFromDir(dir, status) {
|
|
1168
1233
|
try {
|
|
1169
1234
|
const files = await readdir(dir);
|
|
@@ -1171,7 +1236,7 @@ async function loadSessionsFromDir(dir, status) {
|
|
|
1171
1236
|
for (const file of files) {
|
|
1172
1237
|
if (!file.endsWith(".md")) continue;
|
|
1173
1238
|
const id = file.replace(".md", "");
|
|
1174
|
-
const filePath =
|
|
1239
|
+
const filePath = join5(dir, file);
|
|
1175
1240
|
const content = await readFile(filePath, "utf-8");
|
|
1176
1241
|
const metadata = parseSessionMetadata(content);
|
|
1177
1242
|
sessions.push({
|
|
@@ -1189,7 +1254,12 @@ async function loadSessionsFromDir(dir, status) {
|
|
|
1189
1254
|
throw error;
|
|
1190
1255
|
}
|
|
1191
1256
|
}
|
|
1192
|
-
|
|
1257
|
+
var STATUS_DIR_KEY = {
|
|
1258
|
+
todo: "todoDir",
|
|
1259
|
+
doing: "doingDir",
|
|
1260
|
+
archive: "archiveDir"
|
|
1261
|
+
};
|
|
1262
|
+
function formatTextOutput(sessions) {
|
|
1193
1263
|
if (sessions.length === 0) {
|
|
1194
1264
|
return ` (no sessions)`;
|
|
1195
1265
|
}
|
|
@@ -1199,47 +1269,38 @@ function formatTextOutput(sessions, _status) {
|
|
|
1199
1269
|
return ` ${s.id}${priority}${tags}`;
|
|
1200
1270
|
}).join("\n");
|
|
1201
1271
|
}
|
|
1272
|
+
function validateStatus(input) {
|
|
1273
|
+
if (SESSION_STATUSES.includes(input)) {
|
|
1274
|
+
return input;
|
|
1275
|
+
}
|
|
1276
|
+
throw new Error(
|
|
1277
|
+
`Invalid status: "${input}". Valid values: ${SESSION_STATUSES.join(", ")}`
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1202
1280
|
async function listCommand(options) {
|
|
1203
|
-
const config = options.sessionsDir
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
archiveDir: join6(options.sessionsDir, "archive")
|
|
1207
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1208
|
-
const statuses = options.status ? [options.status] : ["todo", "doing", "archive"];
|
|
1209
|
-
const allSessions = {
|
|
1210
|
-
todo: [],
|
|
1211
|
-
doing: [],
|
|
1212
|
-
archive: []
|
|
1213
|
-
};
|
|
1281
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1282
|
+
const statuses = options.status !== void 0 ? [validateStatus(options.status)] : DEFAULT_LIST_STATUSES;
|
|
1283
|
+
const sessionsByStatus = {};
|
|
1214
1284
|
for (const status of statuses) {
|
|
1215
|
-
const
|
|
1216
|
-
const sessions = await loadSessionsFromDir(
|
|
1217
|
-
|
|
1285
|
+
const dirKey = STATUS_DIR_KEY[status];
|
|
1286
|
+
const sessions = await loadSessionsFromDir(config[dirKey], status);
|
|
1287
|
+
sessionsByStatus[status] = sortSessions(sessions);
|
|
1218
1288
|
}
|
|
1219
1289
|
if (options.format === "json") {
|
|
1220
|
-
return JSON.stringify(
|
|
1290
|
+
return JSON.stringify(sessionsByStatus, null, 2);
|
|
1221
1291
|
}
|
|
1222
1292
|
const lines = [];
|
|
1223
|
-
|
|
1224
|
-
lines.push(
|
|
1225
|
-
lines.push(formatTextOutput(
|
|
1226
|
-
lines.push("");
|
|
1227
|
-
}
|
|
1228
|
-
if (statuses.includes("todo")) {
|
|
1229
|
-
lines.push("TODO:");
|
|
1230
|
-
lines.push(formatTextOutput(allSessions.todo, "todo"));
|
|
1293
|
+
for (const status of statuses) {
|
|
1294
|
+
lines.push(`${status.toUpperCase()}:`);
|
|
1295
|
+
lines.push(formatTextOutput(sessionsByStatus[status] ?? []));
|
|
1231
1296
|
lines.push("");
|
|
1232
1297
|
}
|
|
1233
|
-
if (statuses.includes("archive")) {
|
|
1234
|
-
lines.push("ARCHIVE:");
|
|
1235
|
-
lines.push(formatTextOutput(allSessions.archive, "archive"));
|
|
1236
|
-
}
|
|
1237
1298
|
return lines.join("\n").trim();
|
|
1238
1299
|
}
|
|
1239
1300
|
|
|
1240
1301
|
// src/commands/session/pickup.ts
|
|
1241
1302
|
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile2, rename as rename2 } from "fs/promises";
|
|
1242
|
-
import { join as
|
|
1303
|
+
import { join as join6 } from "path";
|
|
1243
1304
|
|
|
1244
1305
|
// src/session/pickup.ts
|
|
1245
1306
|
function buildClaimPaths(sessionId, config) {
|
|
@@ -1275,6 +1336,8 @@ function selectBestSession(sessions) {
|
|
|
1275
1336
|
}
|
|
1276
1337
|
|
|
1277
1338
|
// src/commands/session/pickup.ts
|
|
1339
|
+
var PICKUP_SOURCE_STATUS = SESSION_STATUSES[0];
|
|
1340
|
+
var PICKUP_TARGET_STATUS = SESSION_STATUSES[1];
|
|
1278
1341
|
async function loadTodoSessions(config) {
|
|
1279
1342
|
try {
|
|
1280
1343
|
const files = await readdir2(config.todoDir);
|
|
@@ -1282,12 +1345,12 @@ async function loadTodoSessions(config) {
|
|
|
1282
1345
|
for (const file of files) {
|
|
1283
1346
|
if (!file.endsWith(".md")) continue;
|
|
1284
1347
|
const id = file.replace(".md", "");
|
|
1285
|
-
const filePath =
|
|
1348
|
+
const filePath = join6(config.todoDir, file);
|
|
1286
1349
|
const content = await readFile2(filePath, "utf-8");
|
|
1287
1350
|
const metadata = parseSessionMetadata(content);
|
|
1288
1351
|
sessions.push({
|
|
1289
1352
|
id,
|
|
1290
|
-
status:
|
|
1353
|
+
status: PICKUP_SOURCE_STATUS,
|
|
1291
1354
|
path: filePath,
|
|
1292
1355
|
metadata
|
|
1293
1356
|
});
|
|
@@ -1301,11 +1364,7 @@ async function loadTodoSessions(config) {
|
|
|
1301
1364
|
}
|
|
1302
1365
|
}
|
|
1303
1366
|
async function pickupCommand(options) {
|
|
1304
|
-
const config = options.sessionsDir
|
|
1305
|
-
todoDir: join7(options.sessionsDir, "todo"),
|
|
1306
|
-
doingDir: join7(options.sessionsDir, "doing"),
|
|
1307
|
-
archiveDir: join7(options.sessionsDir, "archive")
|
|
1308
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1367
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1309
1368
|
let sessionId;
|
|
1310
1369
|
if (options.auto) {
|
|
1311
1370
|
const sessions = await loadTodoSessions(config);
|
|
@@ -1327,7 +1386,7 @@ async function pickupCommand(options) {
|
|
|
1327
1386
|
throw classifyClaimError(error, sessionId);
|
|
1328
1387
|
}
|
|
1329
1388
|
const content = await readFile2(paths.target, "utf-8");
|
|
1330
|
-
const output = formatShowOutput(content, { status:
|
|
1389
|
+
const output = formatShowOutput(content, { status: PICKUP_TARGET_STATUS });
|
|
1331
1390
|
return `Claimed session <PICKUP_ID>${sessionId}</PICKUP_ID>
|
|
1332
1391
|
|
|
1333
1392
|
${output}`;
|
|
@@ -1335,7 +1394,8 @@ ${output}`;
|
|
|
1335
1394
|
|
|
1336
1395
|
// src/commands/session/prune.ts
|
|
1337
1396
|
import { readdir as readdir3, readFile as readFile3, unlink as unlink2 } from "fs/promises";
|
|
1338
|
-
import { join as
|
|
1397
|
+
import { join as join7 } from "path";
|
|
1398
|
+
var PRUNE_STATUS = SESSION_STATUSES[2];
|
|
1339
1399
|
var DEFAULT_KEEP_COUNT = 5;
|
|
1340
1400
|
var PruneValidationError = class extends Error {
|
|
1341
1401
|
constructor(message) {
|
|
@@ -1359,12 +1419,12 @@ async function loadArchiveSessions(config) {
|
|
|
1359
1419
|
for (const file of files) {
|
|
1360
1420
|
if (!file.endsWith(".md")) continue;
|
|
1361
1421
|
const id = file.replace(".md", "");
|
|
1362
|
-
const filePath =
|
|
1422
|
+
const filePath = join7(config.archiveDir, file);
|
|
1363
1423
|
const content = await readFile3(filePath, "utf-8");
|
|
1364
1424
|
const metadata = parseSessionMetadata(content);
|
|
1365
1425
|
sessions.push({
|
|
1366
1426
|
id,
|
|
1367
|
-
status:
|
|
1427
|
+
status: PRUNE_STATUS,
|
|
1368
1428
|
path: filePath,
|
|
1369
1429
|
metadata
|
|
1370
1430
|
});
|
|
@@ -1388,11 +1448,7 @@ async function pruneCommand(options) {
|
|
|
1388
1448
|
validatePruneOptions(options);
|
|
1389
1449
|
const keep = options.keep ?? DEFAULT_KEEP_COUNT;
|
|
1390
1450
|
const dryRun = options.dryRun ?? false;
|
|
1391
|
-
const config = options.sessionsDir
|
|
1392
|
-
todoDir: join8(options.sessionsDir, "todo"),
|
|
1393
|
-
doingDir: join8(options.sessionsDir, "doing"),
|
|
1394
|
-
archiveDir: join8(options.sessionsDir, "archive")
|
|
1395
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1451
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1396
1452
|
const sessions = await loadArchiveSessions(config);
|
|
1397
1453
|
const toPrune = selectSessionsToPrune(sessions, keep);
|
|
1398
1454
|
if (toPrune.length === 0) {
|
|
@@ -1421,7 +1477,6 @@ async function pruneCommand(options) {
|
|
|
1421
1477
|
|
|
1422
1478
|
// src/commands/session/release.ts
|
|
1423
1479
|
import { readdir as readdir4, rename as rename3 } from "fs/promises";
|
|
1424
|
-
import { join as join9 } from "path";
|
|
1425
1480
|
|
|
1426
1481
|
// src/session/release.ts
|
|
1427
1482
|
function buildReleasePaths(sessionId, config) {
|
|
@@ -1458,11 +1513,7 @@ async function loadDoingSessions(config) {
|
|
|
1458
1513
|
}
|
|
1459
1514
|
}
|
|
1460
1515
|
async function releaseCommand(options) {
|
|
1461
|
-
const config = options.sessionsDir
|
|
1462
|
-
todoDir: join9(options.sessionsDir, "todo"),
|
|
1463
|
-
doingDir: join9(options.sessionsDir, "doing"),
|
|
1464
|
-
archiveDir: join9(options.sessionsDir, "archive")
|
|
1465
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1516
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1466
1517
|
let sessionId;
|
|
1467
1518
|
if (options.sessionId) {
|
|
1468
1519
|
sessionId = options.sessionId;
|
|
@@ -1489,7 +1540,6 @@ Session returned to todo directory.`;
|
|
|
1489
1540
|
|
|
1490
1541
|
// src/commands/session/show.ts
|
|
1491
1542
|
import { readFile as readFile4, stat as stat3 } from "fs/promises";
|
|
1492
|
-
import { join as join10 } from "path";
|
|
1493
1543
|
async function findExistingPath(paths, _config) {
|
|
1494
1544
|
for (let i = 0; i < paths.length; i++) {
|
|
1495
1545
|
const filePath = paths[i];
|
|
@@ -1503,20 +1553,19 @@ async function findExistingPath(paths, _config) {
|
|
|
1503
1553
|
}
|
|
1504
1554
|
return null;
|
|
1505
1555
|
}
|
|
1506
|
-
async function
|
|
1507
|
-
const
|
|
1508
|
-
todoDir: join10(options.sessionsDir, "todo"),
|
|
1509
|
-
doingDir: join10(options.sessionsDir, "doing"),
|
|
1510
|
-
archiveDir: join10(options.sessionsDir, "archive")
|
|
1511
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1512
|
-
const paths = resolveSessionPaths(options.sessionId, config);
|
|
1556
|
+
async function showSingle(sessionId, config) {
|
|
1557
|
+
const paths = resolveSessionPaths(sessionId, config);
|
|
1513
1558
|
const found = await findExistingPath(paths, config);
|
|
1514
1559
|
if (!found) {
|
|
1515
|
-
throw new SessionNotFoundError(
|
|
1560
|
+
throw new SessionNotFoundError(sessionId);
|
|
1516
1561
|
}
|
|
1517
1562
|
const content = await readFile4(found.path, "utf-8");
|
|
1518
1563
|
return formatShowOutput(content, { status: found.status });
|
|
1519
1564
|
}
|
|
1565
|
+
async function showCommand(options) {
|
|
1566
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1567
|
+
return processBatch(options.sessionIds, (id) => showSingle(id, config));
|
|
1568
|
+
}
|
|
1520
1569
|
|
|
1521
1570
|
// src/domains/session/help.ts
|
|
1522
1571
|
var SESSION_FORMAT_HELP = `
|
|
@@ -1535,7 +1584,8 @@ Workflow:
|
|
|
1535
1584
|
1. handoff - Create session (todo)
|
|
1536
1585
|
2. pickup - Claim session (todo -> doing)
|
|
1537
1586
|
3. release - Return session (doing -> todo)
|
|
1538
|
-
4.
|
|
1587
|
+
4. archive - Move session to archive
|
|
1588
|
+
5. delete - Remove session permanently
|
|
1539
1589
|
`;
|
|
1540
1590
|
var HANDOFF_FRONTMATTER_HELP = `
|
|
1541
1591
|
Usage:
|
|
@@ -1580,17 +1630,17 @@ async function readStdin() {
|
|
|
1580
1630
|
if (process.stdin.isTTY) {
|
|
1581
1631
|
return void 0;
|
|
1582
1632
|
}
|
|
1583
|
-
return new Promise((
|
|
1633
|
+
return new Promise((resolve3) => {
|
|
1584
1634
|
let data = "";
|
|
1585
1635
|
process.stdin.setEncoding("utf-8");
|
|
1586
1636
|
process.stdin.on("data", (chunk) => {
|
|
1587
1637
|
data += chunk;
|
|
1588
1638
|
});
|
|
1589
1639
|
process.stdin.on("end", () => {
|
|
1590
|
-
|
|
1640
|
+
resolve3(data.trim() || void 0);
|
|
1591
1641
|
});
|
|
1592
1642
|
process.stdin.on("error", () => {
|
|
1593
|
-
|
|
1643
|
+
resolve3(void 0);
|
|
1594
1644
|
});
|
|
1595
1645
|
});
|
|
1596
1646
|
}
|
|
@@ -1599,7 +1649,7 @@ function handleError(error) {
|
|
|
1599
1649
|
process.exit(1);
|
|
1600
1650
|
}
|
|
1601
1651
|
function registerSessionCommands(sessionCmd) {
|
|
1602
|
-
sessionCmd.command("list").description("List
|
|
1652
|
+
sessionCmd.command("list").description("List active sessions (doing + todo by default)").option("--status <status>", "Filter by status (todo|doing|archive); defaults to doing + todo").option("--json", "Output as JSON").option("--sessions-dir <path>", "Custom sessions directory").action(async (options) => {
|
|
1603
1653
|
try {
|
|
1604
1654
|
const output = await listCommand({
|
|
1605
1655
|
status: options.status,
|
|
@@ -1611,10 +1661,22 @@ function registerSessionCommands(sessionCmd) {
|
|
|
1611
1661
|
handleError(error);
|
|
1612
1662
|
}
|
|
1613
1663
|
});
|
|
1614
|
-
sessionCmd.command("
|
|
1664
|
+
sessionCmd.command("todo").description("List todo sessions").option("--json", "Output as JSON").option("--sessions-dir <path>", "Custom sessions directory").action(async (options) => {
|
|
1665
|
+
try {
|
|
1666
|
+
const output = await listCommand({
|
|
1667
|
+
status: SESSION_STATUSES[0],
|
|
1668
|
+
format: options.json ? "json" : "text",
|
|
1669
|
+
sessionsDir: options.sessionsDir
|
|
1670
|
+
});
|
|
1671
|
+
console.log(output);
|
|
1672
|
+
} catch (error) {
|
|
1673
|
+
handleError(error);
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
sessionCmd.command("show <id...>").description("Show session content").option("--sessions-dir <path>", "Custom sessions directory").action(async (ids, options) => {
|
|
1615
1677
|
try {
|
|
1616
1678
|
const output = await showCommand({
|
|
1617
|
-
|
|
1679
|
+
sessionIds: ids,
|
|
1618
1680
|
sessionsDir: options.sessionsDir
|
|
1619
1681
|
});
|
|
1620
1682
|
console.log(output);
|
|
@@ -1661,10 +1723,10 @@ function registerSessionCommands(sessionCmd) {
|
|
|
1661
1723
|
handleError(error);
|
|
1662
1724
|
}
|
|
1663
1725
|
});
|
|
1664
|
-
sessionCmd.command("delete <id
|
|
1726
|
+
sessionCmd.command("delete <id...>").description("Delete one or more sessions").option("--sessions-dir <path>", "Custom sessions directory").action(async (ids, options) => {
|
|
1665
1727
|
try {
|
|
1666
1728
|
const output = await deleteCommand({
|
|
1667
|
-
|
|
1729
|
+
sessionIds: ids,
|
|
1668
1730
|
sessionsDir: options.sessionsDir
|
|
1669
1731
|
});
|
|
1670
1732
|
console.log(output);
|
|
@@ -1689,10 +1751,10 @@ function registerSessionCommands(sessionCmd) {
|
|
|
1689
1751
|
handleError(error);
|
|
1690
1752
|
}
|
|
1691
1753
|
});
|
|
1692
|
-
sessionCmd.command("archive <id
|
|
1754
|
+
sessionCmd.command("archive <id...>").description("Move one or more sessions to the archive directory").option("--sessions-dir <path>", "Custom sessions directory").action(async (ids, options) => {
|
|
1693
1755
|
try {
|
|
1694
1756
|
const output = await archiveCommand({
|
|
1695
|
-
|
|
1757
|
+
sessionIds: ids,
|
|
1696
1758
|
sessionsDir: options.sessionsDir
|
|
1697
1759
|
});
|
|
1698
1760
|
console.log(output);
|
|
@@ -3182,7 +3244,7 @@ var ParseErrorCode;
|
|
|
3182
3244
|
|
|
3183
3245
|
// src/validation/config/scope.ts
|
|
3184
3246
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
3185
|
-
import { join as
|
|
3247
|
+
import { join as join8 } from "path";
|
|
3186
3248
|
var TSCONFIG_FILES = {
|
|
3187
3249
|
full: "tsconfig.json",
|
|
3188
3250
|
production: "tsconfig.production.json"
|
|
@@ -3229,7 +3291,7 @@ function hasTypeScriptFilesRecursive(dirPath, maxDepth = 2, deps = defaultScopeD
|
|
|
3229
3291
|
if (hasDirectTsFiles) return true;
|
|
3230
3292
|
const subdirs = items.filter((item) => item.isDirectory() && !item.name.startsWith("."));
|
|
3231
3293
|
for (const subdir of subdirs.slice(0, 5)) {
|
|
3232
|
-
if (hasTypeScriptFilesRecursive(
|
|
3294
|
+
if (hasTypeScriptFilesRecursive(join8(dirPath, subdir.name), maxDepth - 1, deps)) {
|
|
3233
3295
|
return true;
|
|
3234
3296
|
}
|
|
3235
3297
|
}
|
|
@@ -3572,7 +3634,7 @@ function buildEslintArgs(context) {
|
|
|
3572
3634
|
}
|
|
3573
3635
|
async function validateESLint(context, runner = defaultEslintProcessRunner) {
|
|
3574
3636
|
const { scope, validatedFiles, mode } = context;
|
|
3575
|
-
return new Promise((
|
|
3637
|
+
return new Promise((resolve3) => {
|
|
3576
3638
|
if (!validatedFiles || validatedFiles.length === 0) {
|
|
3577
3639
|
if (scope === VALIDATION_SCOPES.PRODUCTION) {
|
|
3578
3640
|
process.env.ESLINT_PRODUCTION_ONLY = "1";
|
|
@@ -3591,13 +3653,13 @@ async function validateESLint(context, runner = defaultEslintProcessRunner) {
|
|
|
3591
3653
|
});
|
|
3592
3654
|
eslintProcess.on("close", (code) => {
|
|
3593
3655
|
if (code === 0) {
|
|
3594
|
-
|
|
3656
|
+
resolve3({ success: true });
|
|
3595
3657
|
} else {
|
|
3596
|
-
|
|
3658
|
+
resolve3({ success: false, error: `ESLint exited with code ${code}` });
|
|
3597
3659
|
}
|
|
3598
3660
|
});
|
|
3599
3661
|
eslintProcess.on("error", (error) => {
|
|
3600
|
-
|
|
3662
|
+
resolve3({ success: false, error: error.message });
|
|
3601
3663
|
});
|
|
3602
3664
|
});
|
|
3603
3665
|
}
|
|
@@ -3644,7 +3706,7 @@ async function validateKnip(typescriptScope, runner = defaultKnipProcessRunner)
|
|
|
3644
3706
|
if (analyzeDirectories.length === 0) {
|
|
3645
3707
|
return { success: true };
|
|
3646
3708
|
}
|
|
3647
|
-
return new Promise((
|
|
3709
|
+
return new Promise((resolve3) => {
|
|
3648
3710
|
const knipProcess = runner.spawn("npx", ["knip"], {
|
|
3649
3711
|
cwd: process.cwd(),
|
|
3650
3712
|
stdio: "pipe"
|
|
@@ -3659,17 +3721,17 @@ async function validateKnip(typescriptScope, runner = defaultKnipProcessRunner)
|
|
|
3659
3721
|
});
|
|
3660
3722
|
knipProcess.on("close", (code) => {
|
|
3661
3723
|
if (code === 0) {
|
|
3662
|
-
|
|
3724
|
+
resolve3({ success: true });
|
|
3663
3725
|
} else {
|
|
3664
3726
|
const errorOutput = knipOutput || knipError || "Unused code detected";
|
|
3665
|
-
|
|
3727
|
+
resolve3({
|
|
3666
3728
|
success: false,
|
|
3667
3729
|
error: errorOutput
|
|
3668
3730
|
});
|
|
3669
3731
|
}
|
|
3670
3732
|
});
|
|
3671
3733
|
knipProcess.on("error", (error) => {
|
|
3672
|
-
|
|
3734
|
+
resolve3({ success: false, error: error.message });
|
|
3673
3735
|
});
|
|
3674
3736
|
});
|
|
3675
3737
|
} catch (error) {
|
|
@@ -3761,7 +3823,7 @@ import { spawn as spawn3 } from "child_process";
|
|
|
3761
3823
|
import { existsSync as existsSync2, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
3762
3824
|
import { mkdtemp } from "fs/promises";
|
|
3763
3825
|
import { tmpdir } from "os";
|
|
3764
|
-
import { isAbsolute, join as
|
|
3826
|
+
import { isAbsolute as isAbsolute2, join as join9 } from "path";
|
|
3765
3827
|
var defaultTypeScriptProcessRunner = { spawn: spawn3 };
|
|
3766
3828
|
var defaultTypeScriptDeps = {
|
|
3767
3829
|
mkdtemp,
|
|
@@ -3775,19 +3837,19 @@ function buildTypeScriptArgs(context) {
|
|
|
3775
3837
|
return scope === VALIDATION_SCOPES.FULL ? ["tsc", "--noEmit"] : ["tsc", "--project", configFile];
|
|
3776
3838
|
}
|
|
3777
3839
|
async function createFileSpecificTsconfig(scope, files, deps = defaultTypeScriptDeps) {
|
|
3778
|
-
const tempDir = await deps.mkdtemp(
|
|
3779
|
-
const configPath =
|
|
3840
|
+
const tempDir = await deps.mkdtemp(join9(tmpdir(), "validate-ts-"));
|
|
3841
|
+
const configPath = join9(tempDir, "tsconfig.json");
|
|
3780
3842
|
const baseConfigFile = TSCONFIG_FILES[scope];
|
|
3781
3843
|
const projectRoot = process.cwd();
|
|
3782
|
-
const absoluteFiles = files.map((file) =>
|
|
3844
|
+
const absoluteFiles = files.map((file) => isAbsolute2(file) ? file : join9(projectRoot, file));
|
|
3783
3845
|
const tempConfig = {
|
|
3784
|
-
extends:
|
|
3846
|
+
extends: join9(projectRoot, baseConfigFile),
|
|
3785
3847
|
files: absoluteFiles,
|
|
3786
3848
|
include: [],
|
|
3787
3849
|
exclude: [],
|
|
3788
3850
|
compilerOptions: {
|
|
3789
3851
|
noEmit: true,
|
|
3790
|
-
typeRoots: [
|
|
3852
|
+
typeRoots: [join9(projectRoot, "node_modules", "@types")],
|
|
3791
3853
|
types: ["node"]
|
|
3792
3854
|
}
|
|
3793
3855
|
};
|
|
@@ -3807,7 +3869,7 @@ async function validateTypeScript(scope, typescriptScope, files, runner = defaul
|
|
|
3807
3869
|
if (files && files.length > 0) {
|
|
3808
3870
|
const { configPath, cleanup } = await createFileSpecificTsconfig(scope, files, deps);
|
|
3809
3871
|
try {
|
|
3810
|
-
return await new Promise((
|
|
3872
|
+
return await new Promise((resolve3) => {
|
|
3811
3873
|
const tscProcess = runner.spawn("npx", ["tsc", "--project", configPath], {
|
|
3812
3874
|
cwd: process.cwd(),
|
|
3813
3875
|
stdio: "inherit"
|
|
@@ -3815,14 +3877,14 @@ async function validateTypeScript(scope, typescriptScope, files, runner = defaul
|
|
|
3815
3877
|
tscProcess.on("close", (code) => {
|
|
3816
3878
|
cleanup();
|
|
3817
3879
|
if (code === 0) {
|
|
3818
|
-
|
|
3880
|
+
resolve3({ success: true, skipped: false });
|
|
3819
3881
|
} else {
|
|
3820
|
-
|
|
3882
|
+
resolve3({ success: false, error: `TypeScript exited with code ${code}` });
|
|
3821
3883
|
}
|
|
3822
3884
|
});
|
|
3823
3885
|
tscProcess.on("error", (error) => {
|
|
3824
3886
|
cleanup();
|
|
3825
|
-
|
|
3887
|
+
resolve3({ success: false, error: error.message });
|
|
3826
3888
|
});
|
|
3827
3889
|
});
|
|
3828
3890
|
} catch (error) {
|
|
@@ -3834,20 +3896,20 @@ async function validateTypeScript(scope, typescriptScope, files, runner = defaul
|
|
|
3834
3896
|
tool = "npx";
|
|
3835
3897
|
tscArgs = buildTypeScriptArgs({ scope, configFile });
|
|
3836
3898
|
}
|
|
3837
|
-
return new Promise((
|
|
3899
|
+
return new Promise((resolve3) => {
|
|
3838
3900
|
const tscProcess = runner.spawn(tool, tscArgs, {
|
|
3839
3901
|
cwd: process.cwd(),
|
|
3840
3902
|
stdio: "inherit"
|
|
3841
3903
|
});
|
|
3842
3904
|
tscProcess.on("close", (code) => {
|
|
3843
3905
|
if (code === 0) {
|
|
3844
|
-
|
|
3906
|
+
resolve3({ success: true, skipped: false });
|
|
3845
3907
|
} else {
|
|
3846
|
-
|
|
3908
|
+
resolve3({ success: false, error: `TypeScript exited with code ${code}` });
|
|
3847
3909
|
}
|
|
3848
3910
|
});
|
|
3849
3911
|
tscProcess.on("error", (error) => {
|
|
3850
|
-
|
|
3912
|
+
resolve3({ success: false, error: error.message });
|
|
3851
3913
|
});
|
|
3852
3914
|
});
|
|
3853
3915
|
}
|