@adaptic/maestro 1.4.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/maestro.mjs +346 -53
- package/package.json +1 -1
package/bin/maestro.mjs
CHANGED
|
@@ -8,16 +8,21 @@
|
|
|
8
8
|
* npx @adaptic/maestro doctor # Verify installation and configuration
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { resolve, join, dirname } from "node:path";
|
|
11
|
+
import { resolve, join, dirname, relative, sep } from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import {
|
|
14
14
|
mkdirSync,
|
|
15
15
|
cpSync,
|
|
16
|
+
copyFileSync,
|
|
16
17
|
existsSync,
|
|
17
18
|
readFileSync,
|
|
18
19
|
writeFileSync,
|
|
20
|
+
readdirSync,
|
|
21
|
+
statSync,
|
|
22
|
+
lstatSync,
|
|
19
23
|
} from "node:fs";
|
|
20
24
|
import { execFileSync } from "node:child_process";
|
|
25
|
+
import { createHash } from "node:crypto";
|
|
21
26
|
|
|
22
27
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
28
|
const MAESTRO_ROOT = resolve(__dirname, "..");
|
|
@@ -664,68 +669,332 @@ rubrics: []
|
|
|
664
669
|
}
|
|
665
670
|
|
|
666
671
|
// ---------------------------------------------------------------------------
|
|
667
|
-
// UPGRADE — update framework files in current agent repo
|
|
672
|
+
// UPGRADE — update framework files in current agent repo (smart merge)
|
|
668
673
|
// ---------------------------------------------------------------------------
|
|
674
|
+
//
|
|
675
|
+
// Upgrade philosophy: by default, never silently overwrite a file the user has
|
|
676
|
+
// modified locally. Detection uses git as the source of truth:
|
|
677
|
+
//
|
|
678
|
+
// • untracked or has-uncommitted-changes in agent repo ⇒ "locally modified"
|
|
679
|
+
// • tracked and clean against HEAD ⇒ "vendored, safe to overwrite"
|
|
680
|
+
//
|
|
681
|
+
// For every framework file we'd otherwise overwrite, we classify into:
|
|
682
|
+
//
|
|
683
|
+
// added — file doesn't exist in agent repo → copy
|
|
684
|
+
// updated — file exists, agent's copy matches HEAD (no local edits) → overwrite
|
|
685
|
+
// same — file exists, content already byte-identical to upstream → skip
|
|
686
|
+
// preserved — file exists with local edits → keep agent's copy, write the
|
|
687
|
+
// upstream version to .maestro/incoming/<path> for manual review
|
|
688
|
+
// (unless --force-overwrite is passed)
|
|
689
|
+
//
|
|
690
|
+
// Flags:
|
|
691
|
+
// --dry-run preview only; no files written
|
|
692
|
+
// --force-overwrite overwrite even locally-modified files (with backup)
|
|
693
|
+
// --no-incoming skip writing .maestro/incoming/ shadows
|
|
694
|
+
// --verbose list every file's classification
|
|
695
|
+
|
|
696
|
+
// Paths that get upgraded. Each entry is { path, mode } where mode is:
|
|
697
|
+
// "smart" — per-file classification described above
|
|
698
|
+
// "merge" — add new files only; never overwrite existing (used for agents/)
|
|
699
|
+
const UPGRADE_PATHS = [
|
|
700
|
+
{ path: "scripts", mode: "smart" },
|
|
701
|
+
{ path: "policies", mode: "smart" },
|
|
702
|
+
{ path: "docs", mode: "smart" },
|
|
703
|
+
{ path: "public/assets", mode: "smart" },
|
|
704
|
+
{ path: "workflows", mode: "smart" },
|
|
705
|
+
{ path: "schedules", mode: "smart" },
|
|
706
|
+
{ path: "desktop-control", mode: "smart" },
|
|
707
|
+
{ path: "ingest", mode: "smart" },
|
|
708
|
+
{ path: "mcp", mode: "smart" },
|
|
709
|
+
{ path: ".claude/commands", mode: "smart" },
|
|
710
|
+
{ path: "plugins/maestro-skills", mode: "smart" },
|
|
711
|
+
{ path: "teams", mode: "smart" },
|
|
712
|
+
{ path: "agents", mode: "merge" },
|
|
713
|
+
];
|
|
714
|
+
|
|
715
|
+
function sha256File(p) {
|
|
716
|
+
return createHash("sha256").update(readFileSync(p)).digest("hex");
|
|
717
|
+
}
|
|
669
718
|
|
|
670
|
-
function
|
|
671
|
-
const
|
|
719
|
+
function walkFiles(root) {
|
|
720
|
+
const out = [];
|
|
721
|
+
if (!existsSync(root)) return out;
|
|
722
|
+
const stack = [root];
|
|
723
|
+
while (stack.length) {
|
|
724
|
+
const dir = stack.pop();
|
|
725
|
+
for (const name of readdirSync(dir)) {
|
|
726
|
+
const full = join(dir, name);
|
|
727
|
+
// Use lstat first so we can detect (and skip) symlinks — particularly
|
|
728
|
+
// broken ones, which are common in shared scaffolds.
|
|
729
|
+
let lst;
|
|
730
|
+
try { lst = lstatSync(full); }
|
|
731
|
+
catch { continue; }
|
|
732
|
+
if (lst.isSymbolicLink()) {
|
|
733
|
+
// Skip symlinks entirely — agents shouldn't inherit links into paths
|
|
734
|
+
// that may not exist on the target machine.
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (lst.isDirectory()) stack.push(full);
|
|
738
|
+
else if (lst.isFile()) out.push(full);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return out;
|
|
742
|
+
}
|
|
672
743
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
744
|
+
// .maestroignore — gitignore-style allowlist of paths the upgrade must NOT
|
|
745
|
+
// touch. Supports comments (#), directory prefixes (trailing /), exact paths,
|
|
746
|
+
// simple globs (* matches a single path segment, ** matches recursively), and
|
|
747
|
+
// negation (! prefix to un-ignore). Match order is top-to-bottom; last match
|
|
748
|
+
// wins, like .gitignore.
|
|
749
|
+
function loadMaestroignore(cwd) {
|
|
750
|
+
const file = join(cwd, ".maestroignore");
|
|
751
|
+
if (!existsSync(file)) return null;
|
|
752
|
+
const patterns = [];
|
|
753
|
+
for (const raw of readFileSync(file, "utf-8").split(/\r?\n/)) {
|
|
754
|
+
const stripped = raw.replace(/^\s+|\s+$/g, "");
|
|
755
|
+
if (!stripped || stripped.startsWith("#")) continue;
|
|
756
|
+
const negate = stripped.startsWith("!");
|
|
757
|
+
const pat = negate ? stripped.slice(1).trim() : stripped;
|
|
758
|
+
if (!pat) continue;
|
|
759
|
+
patterns.push({ pat, negate });
|
|
676
760
|
}
|
|
761
|
+
return patterns.length ? patterns : null;
|
|
762
|
+
}
|
|
677
763
|
|
|
678
|
-
|
|
764
|
+
function patternToRegex(pat) {
|
|
765
|
+
// Directory prefix: `scripts/daemon/` matches scripts/daemon/* (recursive)
|
|
766
|
+
if (pat.endsWith("/")) {
|
|
767
|
+
const prefix = pat.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
768
|
+
return new RegExp("^" + prefix);
|
|
769
|
+
}
|
|
770
|
+
// Glob with * and **.
|
|
771
|
+
// Step order matters: ** must be captured before single * to preserve
|
|
772
|
+
// recursive semantics. * is intentionally NOT in the regex-escape char
|
|
773
|
+
// class so we can rewrite it after escaping the other specials.
|
|
774
|
+
if (pat.includes("*")) {
|
|
775
|
+
const escaped = pat
|
|
776
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
777
|
+
.replace(/\*\*/g, "<DBL>")
|
|
778
|
+
.replace(/\*/g, "[^/]*")
|
|
779
|
+
.replace(/<DBL>/g, ".*");
|
|
780
|
+
return new RegExp("^" + escaped + "$");
|
|
781
|
+
}
|
|
782
|
+
// Exact: also match anything underneath (a path like `scripts/daemon`
|
|
783
|
+
// without trailing slash should still protect the whole directory if
|
|
784
|
+
// it resolves to one — gitignore semantics).
|
|
785
|
+
const escaped = pat.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
786
|
+
return new RegExp("^" + escaped + "(/|$)");
|
|
787
|
+
}
|
|
679
788
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
789
|
+
function matchesIgnore(repoRel, patterns) {
|
|
790
|
+
if (!patterns) return false;
|
|
791
|
+
let ignored = false;
|
|
792
|
+
for (const { pat, negate } of patterns) {
|
|
793
|
+
if (patternToRegex(pat).test(repoRel)) {
|
|
794
|
+
ignored = !negate;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return ignored;
|
|
798
|
+
}
|
|
685
799
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
800
|
+
function isGitRepo(cwd) {
|
|
801
|
+
try {
|
|
802
|
+
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd, stdio: "pipe" });
|
|
803
|
+
return true;
|
|
804
|
+
} catch { return false; }
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Build a Set of repo-relative paths that are dirty (modified, added, untracked).
|
|
808
|
+
// Single git invocation is much faster than per-file checks.
|
|
809
|
+
function dirtyPathSet(cwd) {
|
|
810
|
+
const set = new Set();
|
|
811
|
+
try {
|
|
812
|
+
const out = execFileSync(
|
|
813
|
+
"git",
|
|
814
|
+
["status", "--porcelain=v1", "-z", "--untracked-files=all"],
|
|
815
|
+
{ cwd, encoding: "utf-8" }
|
|
816
|
+
);
|
|
817
|
+
// -z separates records with NUL; each record is "XY path" (no quoting).
|
|
818
|
+
// For renames (R/C) the format is "R newpath\0oldpath\0" — we only care
|
|
819
|
+
// about destination, but for safety we treat both as dirty.
|
|
820
|
+
for (const rec of out.split("\0")) {
|
|
821
|
+
if (!rec) continue;
|
|
822
|
+
const status = rec.slice(0, 2);
|
|
823
|
+
const path = rec.slice(3);
|
|
824
|
+
// Anything in porcelain output is non-clean by definition.
|
|
825
|
+
if (status !== " ") set.add(path);
|
|
693
826
|
}
|
|
827
|
+
} catch {
|
|
828
|
+
// Not a git repo or git failed — caller will fall back to caution mode.
|
|
829
|
+
}
|
|
830
|
+
return set;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function parseUpgradeFlags(args) {
|
|
834
|
+
const flags = {
|
|
835
|
+
dryRun: false,
|
|
836
|
+
forceOverwrite: false,
|
|
837
|
+
noIncoming: false,
|
|
838
|
+
verbose: false,
|
|
839
|
+
};
|
|
840
|
+
for (const a of args) {
|
|
841
|
+
if (a === "--dry-run" || a === "-n") flags.dryRun = true;
|
|
842
|
+
else if (a === "--force-overwrite" || a === "--force") flags.forceOverwrite = true;
|
|
843
|
+
else if (a === "--no-incoming") flags.noIncoming = true;
|
|
844
|
+
else if (a === "--verbose" || a === "-v") flags.verbose = true;
|
|
845
|
+
else if (a === "--help" || a === "-h") return null;
|
|
846
|
+
else { fail(`Unknown flag: ${a}`); process.exit(1); }
|
|
847
|
+
}
|
|
848
|
+
return flags;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function upgrade(args = []) {
|
|
852
|
+
const flags = parseUpgradeFlags(args);
|
|
853
|
+
if (flags === null) {
|
|
854
|
+
console.log(`
|
|
855
|
+
Usage: maestro upgrade [flags]
|
|
856
|
+
|
|
857
|
+
Flags:
|
|
858
|
+
--dry-run, -n Preview changes without writing
|
|
859
|
+
--force-overwrite Overwrite even locally-modified files (backs them up)
|
|
860
|
+
--no-incoming Don't write .maestro/incoming/ shadows for preserved files
|
|
861
|
+
--verbose, -v Print classification for every file
|
|
862
|
+
--help, -h Show this help
|
|
863
|
+
|
|
864
|
+
Per-file behaviour:
|
|
865
|
+
added — new upstream file → copy
|
|
866
|
+
updated — existed, byte-equal to your committed copy → safe overwrite
|
|
867
|
+
same — existed and already byte-identical to upstream → skip
|
|
868
|
+
ignored — matches a pattern in .maestroignore → never touched (any state)
|
|
869
|
+
preserved — has uncommitted local edits → keep yours, upstream lands at
|
|
870
|
+
.maestro/incoming/<path> for manual diff
|
|
871
|
+
mergeKept — under agents/ → never overwrite (custom agents preserved)
|
|
872
|
+
forced — overwritten by --force-overwrite; backup at .maestro/backup/<path>
|
|
873
|
+
|
|
874
|
+
.maestroignore format (gitignore-style, top-down, last match wins):
|
|
875
|
+
scripts/slack-send.sh exact file
|
|
876
|
+
scripts/daemon/ directory and everything beneath
|
|
877
|
+
scripts/send-email-*.py single-segment glob
|
|
878
|
+
workflows/** recursive glob
|
|
879
|
+
!workflows/quarterly/ un-ignore (force overwrite)
|
|
880
|
+
`);
|
|
881
|
+
return;
|
|
694
882
|
}
|
|
695
883
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
if (existsSync(
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
updated++;
|
|
884
|
+
const cwd = process.cwd();
|
|
885
|
+
|
|
886
|
+
if (!existsSync(join(cwd, "config/agent.ts")) && !existsSync(join(cwd, "CLAUDE.md"))) {
|
|
887
|
+
fail("Not a Maestro agent directory (config/agent.ts not found)");
|
|
888
|
+
process.exit(1);
|
|
702
889
|
}
|
|
703
890
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
updated++;
|
|
891
|
+
const inGit = isGitRepo(cwd);
|
|
892
|
+
if (!inGit) {
|
|
893
|
+
warn("Not in a git repo — cannot detect local modifications.");
|
|
894
|
+
warn("All existing files will be treated as locally modified and preserved.");
|
|
895
|
+
warn("Use --force-overwrite to override.");
|
|
710
896
|
}
|
|
711
897
|
|
|
712
|
-
|
|
713
|
-
const
|
|
714
|
-
if (
|
|
715
|
-
|
|
716
|
-
ok("agents (merged, custom agents preserved)");
|
|
717
|
-
updated++;
|
|
898
|
+
const dirty = inGit ? dirtyPathSet(cwd) : null;
|
|
899
|
+
const ignorePatterns = loadMaestroignore(cwd);
|
|
900
|
+
if (ignorePatterns) {
|
|
901
|
+
log(`.maestroignore loaded (${ignorePatterns.length} pattern${ignorePatterns.length === 1 ? "" : "s"})`);
|
|
718
902
|
}
|
|
719
903
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
904
|
+
const banner = flags.dryRun ? "DRY RUN — " : "";
|
|
905
|
+
log(`${banner}Upgrading framework files from @adaptic/maestro...`);
|
|
906
|
+
|
|
907
|
+
const counts = { added: 0, updated: 0, same: 0, ignored: 0, preserved: 0, mergeKept: 0, forced: 0 };
|
|
908
|
+
const preservedFiles = [];
|
|
909
|
+
const ignoredFiles = [];
|
|
910
|
+
|
|
911
|
+
for (const { path: relRoot, mode } of UPGRADE_PATHS) {
|
|
912
|
+
const srcRoot = join(MAESTRO_ROOT, relRoot);
|
|
913
|
+
const dstRoot = join(cwd, relRoot);
|
|
914
|
+
if (!existsSync(srcRoot)) continue;
|
|
915
|
+
|
|
916
|
+
const srcFiles = walkFiles(srcRoot);
|
|
917
|
+
for (const srcFile of srcFiles) {
|
|
918
|
+
const relFromRoot = relative(srcRoot, srcFile);
|
|
919
|
+
const dstFile = join(dstRoot, relFromRoot);
|
|
920
|
+
const repoRel = relative(cwd, dstFile).split(sep).join("/");
|
|
921
|
+
|
|
922
|
+
const isIgnored = matchesIgnore(repoRel, ignorePatterns);
|
|
923
|
+
|
|
924
|
+
// Case 0: .maestroignore match — never touch, regardless of state.
|
|
925
|
+
// Checked BEFORE existsSync so that protected new-files-from-upstream
|
|
926
|
+
// are never silently introduced into the agent repo either.
|
|
927
|
+
if (isIgnored) {
|
|
928
|
+
counts.ignored++;
|
|
929
|
+
ignoredFiles.push(repoRel);
|
|
930
|
+
if (flags.verbose) console.log(` · ${repoRel} (ignored via .maestroignore)`);
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Case 1: new file (doesn't exist in agent) — always copy.
|
|
935
|
+
if (!existsSync(dstFile)) {
|
|
936
|
+
if (!flags.dryRun) {
|
|
937
|
+
mkdirSync(dirname(dstFile), { recursive: true });
|
|
938
|
+
copyFileSync(srcFile, dstFile);
|
|
939
|
+
}
|
|
940
|
+
counts.added++;
|
|
941
|
+
if (flags.verbose) ok(`+ ${repoRel}`);
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Case 2: byte-identical — nothing to do.
|
|
946
|
+
const srcHash = sha256File(srcFile);
|
|
947
|
+
const dstHash = sha256File(dstFile);
|
|
948
|
+
if (srcHash === dstHash) {
|
|
949
|
+
counts.same++;
|
|
950
|
+
if (flags.verbose) console.log(` = ${repoRel}`);
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Case 3: merge-mode never overwrites existing files.
|
|
955
|
+
if (mode === "merge") {
|
|
956
|
+
counts.mergeKept++;
|
|
957
|
+
if (flags.verbose) console.log(` ~ ${repoRel} (merge-mode, kept)`);
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Case 4: locally modified? Determine via git dirty set.
|
|
962
|
+
const locallyModified = inGit ? dirty.has(repoRel) : true;
|
|
963
|
+
|
|
964
|
+
if (locallyModified && !flags.forceOverwrite) {
|
|
965
|
+
counts.preserved++;
|
|
966
|
+
preservedFiles.push(repoRel);
|
|
967
|
+
if (!flags.dryRun && !flags.noIncoming) {
|
|
968
|
+
const shadow = join(cwd, ".maestro", "incoming", repoRel);
|
|
969
|
+
mkdirSync(dirname(shadow), { recursive: true });
|
|
970
|
+
copyFileSync(srcFile, shadow);
|
|
971
|
+
}
|
|
972
|
+
if (flags.verbose) console.log(` ~ ${repoRel} (local edits, preserved)`);
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// Case 5: overwrite (clean tracked file, or --force).
|
|
977
|
+
if (locallyModified && flags.forceOverwrite) {
|
|
978
|
+
// Back up the about-to-be-clobbered file.
|
|
979
|
+
if (!flags.dryRun) {
|
|
980
|
+
const backup = join(cwd, ".maestro", "backup", repoRel);
|
|
981
|
+
mkdirSync(dirname(backup), { recursive: true });
|
|
982
|
+
copyFileSync(dstFile, backup);
|
|
983
|
+
}
|
|
984
|
+
counts.forced++;
|
|
985
|
+
if (flags.verbose) warn(`! ${repoRel} (forced; backup written)`);
|
|
986
|
+
} else {
|
|
987
|
+
counts.updated++;
|
|
988
|
+
if (flags.verbose) console.log(` > ${repoRel}`);
|
|
989
|
+
}
|
|
990
|
+
if (!flags.dryRun) {
|
|
991
|
+
mkdirSync(dirname(dstFile), { recursive: true });
|
|
992
|
+
copyFileSync(srcFile, dstFile);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
726
995
|
}
|
|
727
996
|
|
|
728
|
-
//
|
|
997
|
+
// Ensure standard runtime directories exist.
|
|
729
998
|
const ensureDirs = [
|
|
730
999
|
"state/handoffs", "state/huddle", "state/indexes", "state/rag",
|
|
731
1000
|
"state/sessions", "state/slack-responded", "state/slack-thread-tracker",
|
|
@@ -739,15 +1008,33 @@ function upgrade() {
|
|
|
739
1008
|
for (const dir of ensureDirs) {
|
|
740
1009
|
const dirPath = join(cwd, dir);
|
|
741
1010
|
if (!existsSync(dirPath)) {
|
|
742
|
-
mkdirSync(dirPath, { recursive: true });
|
|
1011
|
+
if (!flags.dryRun) mkdirSync(dirPath, { recursive: true });
|
|
743
1012
|
newDirs++;
|
|
744
1013
|
}
|
|
745
1014
|
}
|
|
746
|
-
|
|
1015
|
+
|
|
1016
|
+
// Summary
|
|
1017
|
+
console.log();
|
|
1018
|
+
console.log(`${C.bold}Upgrade summary${C.reset}${flags.dryRun ? " (dry run, nothing written)" : ""}`);
|
|
1019
|
+
if (counts.added) ok(`${counts.added} added (new files)`);
|
|
1020
|
+
if (counts.updated) ok(`${counts.updated} updated (vendored, no local edits)`);
|
|
1021
|
+
if (counts.same) console.log(` = ${counts.same} already up-to-date`);
|
|
1022
|
+
if (counts.ignored) console.log(` · ${counts.ignored} ignored (.maestroignore protected)`);
|
|
1023
|
+
if (counts.mergeKept) console.log(` ~ ${counts.mergeKept} merge-mode kept (agents/ custom files preserved)`);
|
|
1024
|
+
if (counts.preserved) warn(`${counts.preserved} preserved (local edits — kept your version)`);
|
|
1025
|
+
if (counts.forced) warn(`${counts.forced} force-overwritten (backups in .maestro/backup/)`);
|
|
1026
|
+
if (newDirs) ok(`${newDirs} new directories created`);
|
|
1027
|
+
|
|
1028
|
+
if (preservedFiles.length && !flags.noIncoming && !flags.dryRun) {
|
|
1029
|
+
console.log();
|
|
1030
|
+
log("Upstream versions of your locally-modified files saved to .maestro/incoming/");
|
|
1031
|
+
log("Review with: diff <path> .maestro/incoming/<path>");
|
|
1032
|
+
for (const p of preservedFiles.slice(0, 5)) console.log(` ${p}`);
|
|
1033
|
+
if (preservedFiles.length > 5) console.log(` … and ${preservedFiles.length - 5} more`);
|
|
1034
|
+
}
|
|
747
1035
|
|
|
748
1036
|
console.log();
|
|
749
|
-
|
|
750
|
-
log("Agent-specific files (config/, CLAUDE.md, knowledge/, memory/) were NOT modified.");
|
|
1037
|
+
log("Agent-specific paths (config/, CLAUDE.md, knowledge/, memory/, state/, outputs/, logs/, .env) were NOT touched.");
|
|
751
1038
|
}
|
|
752
1039
|
|
|
753
1040
|
// ---------------------------------------------------------------------------
|
|
@@ -820,16 +1107,22 @@ const [, , command, ...args] = process.argv;
|
|
|
820
1107
|
|
|
821
1108
|
switch (command) {
|
|
822
1109
|
case "create": create(args[0]); break;
|
|
823
|
-
case "upgrade": upgrade(); break;
|
|
1110
|
+
case "upgrade": upgrade(args); break;
|
|
824
1111
|
case "doctor": doctor(); break;
|
|
825
1112
|
case "--help": case "-h": case undefined:
|
|
826
1113
|
console.log(`
|
|
827
1114
|
Maestro — Autonomous AI Agent Operating System
|
|
828
1115
|
|
|
829
1116
|
Usage:
|
|
830
|
-
npx @adaptic/maestro create <dirname>
|
|
831
|
-
npx @adaptic/maestro upgrade
|
|
832
|
-
npx @adaptic/maestro doctor
|
|
1117
|
+
npx @adaptic/maestro create <dirname> Create a new agent repo
|
|
1118
|
+
npx @adaptic/maestro upgrade [--dry-run] Update framework files
|
|
1119
|
+
npx @adaptic/maestro doctor Verify installation
|
|
1120
|
+
|
|
1121
|
+
Upgrade flags:
|
|
1122
|
+
--dry-run, -n Preview changes without writing
|
|
1123
|
+
--force-overwrite Overwrite even locally-modified files (backs them up)
|
|
1124
|
+
--no-incoming Don't write .maestro/incoming/ shadows
|
|
1125
|
+
--verbose, -v List classification for every file
|
|
833
1126
|
|
|
834
1127
|
Workflow:
|
|
835
1128
|
1. npx @adaptic/maestro create my-agent
|