architectonic 0.0.5 → 0.0.6

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 CHANGED
@@ -16,6 +16,8 @@ The initial placeholder releases were intentionally minimal. Starting in
16
16
  local manifest, repair light manifest drift, update safely, and remove layers
17
17
  without trampling local forks.
18
18
 
19
+ Starting in `0.0.6`, it can also inspect drift with `status` and `diff`.
20
+
19
21
  ## Command shape
20
22
 
21
23
  The primary interaction model is:
@@ -28,6 +30,8 @@ architectonic add skills
28
30
  architectonic add teleology identity skills
29
31
  architectonic init
30
32
  architectonic doctor
33
+ architectonic status
34
+ architectonic diff
31
35
  architectonic list
32
36
  architectonic update
33
37
  architectonic remove
@@ -53,6 +57,8 @@ npx architectonic init --preset company
53
57
  npx architectonic list
54
58
  npx architectonic doctor
55
59
  npx architectonic doctor --fix
60
+ npx architectonic status
61
+ npx architectonic diff teleology
56
62
  npx architectonic update
57
63
  npx architectonic update --dry-run
58
64
  npx architectonic remove skills
@@ -99,6 +105,20 @@ ARCHITECTONIC_ADD_SOURCE # change the default source mode
99
105
  package metadata matches the expected layer. `doctor --fix` repairs light
100
106
  manifest drift such as stale package names or recoverable default paths.
101
107
 
108
+ `status` gives a read-only summary of each layer:
109
+
110
+ ```text
111
+ git layers: branch, dirty/clean, ahead/behind upstream
112
+ npm layers: installed version vs published version
113
+ ```
114
+
115
+ `diff <layer>` drills into one layer:
116
+
117
+ ```text
118
+ git layers: local status lines plus ahead/behind numbers
119
+ npm layers: installed version vs published version
120
+ ```
121
+
102
122
  `init` creates a workspace root, installs a preset, and seeds a top-level
103
123
  `README.md` and `AGENTS.md`.
104
124
 
@@ -4,7 +4,7 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { spawnSync } from "node:child_process";
6
6
 
7
- const VERSION = "0.0.5";
7
+ const VERSION = "0.0.6";
8
8
  const args = process.argv.slice(2);
9
9
  const [command, ...rest] = args;
10
10
  const supported = ["teleology", "identity", "project", "skills"];
@@ -34,6 +34,8 @@ Usage:
34
34
  npx architectonic list
35
35
  npx architectonic doctor
36
36
  npx architectonic doctor --fix
37
+ npx architectonic status
38
+ npx architectonic diff <layer>
37
39
  npx architectonic update
38
40
  npx architectonic remove <layer>
39
41
 
@@ -50,7 +52,10 @@ Run vs install:
50
52
 
51
53
  What add does:
52
54
  Installs the selected layer repositories into the target directory
53
- from git or npm sources and records them in architectonic.json.`);
55
+ from git or npm sources and records them in architectonic.json.
56
+
57
+ What status and diff do:
58
+ Inspect local drift from recorded sources without mutating anything.`);
54
59
  }
55
60
 
56
61
  function printUsageError(message) {
@@ -209,6 +214,56 @@ function parseUpdateArgs(tokens) {
209
214
  return { installDir, dryRun };
210
215
  }
211
216
 
217
+ function parseStatusArgs(tokens) {
218
+ let installDir = process.cwd();
219
+
220
+ for (let index = 0; index < tokens.length; index += 1) {
221
+ const token = tokens[index];
222
+ if (token.startsWith("-")) {
223
+ throw new Error(`Unknown option: ${token}`);
224
+ }
225
+ installDir = path.resolve(token);
226
+ }
227
+
228
+ return { installDir };
229
+ }
230
+
231
+ function parseDiffArgs(tokens) {
232
+ let installDir = process.cwd();
233
+ let target = "";
234
+
235
+ for (let index = 0; index < tokens.length; index += 1) {
236
+ const token = tokens[index];
237
+ if (token === "--dir" || token === "--out") {
238
+ const next = tokens[index + 1];
239
+ if (!next) {
240
+ throw new Error(`Missing value for ${token}`);
241
+ }
242
+ installDir = path.resolve(next);
243
+ index += 1;
244
+ continue;
245
+ }
246
+ if (token.startsWith("--dir=")) {
247
+ installDir = path.resolve(token.slice("--dir=".length));
248
+ continue;
249
+ }
250
+ if (token.startsWith("--out=")) {
251
+ installDir = path.resolve(token.slice("--out=".length));
252
+ continue;
253
+ }
254
+ if (token.startsWith("-")) {
255
+ throw new Error(`Unknown option: ${token}`);
256
+ }
257
+ if (!target) {
258
+ target = token;
259
+ continue;
260
+ }
261
+ throw new Error(`Unexpected argument: ${token}`);
262
+ }
263
+
264
+ return { installDir, target };
265
+ }
266
+
212
267
  function parseRemoveArgs(tokens) {
213
268
  let installDir = process.cwd();
214
269
  let force = false;
@@ -497,6 +552,95 @@ function updateManifestLayerRecord(manifest, installDir, layerName, absoluteLaye
497
552
  };
498
553
  }
499
554
 
555
+ function getGitHead(layerPath) {
556
+ const result = gitResult(["rev-parse", "HEAD"], layerPath);
557
+ if (result.status !== 0) {
558
+ return null;
559
+ }
560
+ return (result.stdout || "").trim() || null;
561
+ }
562
+
563
+ function getGitBranch(layerPath) {
564
+ const result = gitResult(["rev-parse", "--abbrev-ref", "HEAD"], layerPath);
565
+ if (result.status !== 0) {
566
+ return null;
567
+ }
568
+ return (result.stdout || "").trim() || null;
569
+ }
570
+
571
+ function getGitUpstream(layerPath) {
572
+ const result = gitResult(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], layerPath);
573
+ if (result.status !== 0) {
574
+ return null;
575
+ }
576
+ return (result.stdout || "").trim() || null;
577
+ }
578
+
579
+ function getGitAheadBehind(layerPath) {
580
+ const upstream = getGitUpstream(layerPath);
581
+ if (!upstream) {
582
+ return null;
583
+ }
584
+ const result = gitResult(["rev-list", "--left-right", "--count", `${upstream}...HEAD`], layerPath);
585
+ if (result.status !== 0) {
586
+ return null;
587
+ }
588
+ const [behindRaw, aheadRaw] = (result.stdout || "").trim().split(/\s+/);
589
+ return {
590
+ upstream,
591
+ behind: Number.parseInt(behindRaw || "0", 10),
592
+ ahead: Number.parseInt(aheadRaw || "0", 10),
593
+ };
594
+ }
595
+
596
+ function getGitDiffSummary(layerPath) {
597
+ const result = gitResult(["status", "--short"], layerPath);
598
+ if (result.status !== 0) {
599
+ return null;
600
+ }
601
+ const lines = (result.stdout || "").split(/\r?\n/).filter(Boolean);
602
+ return {
603
+ dirty: lines.length > 0,
604
+ lines,
605
+ };
606
+ }
607
+
608
+ function getNpmPublishedVersion(packageSpec) {
609
+ const result = spawnSync("npm", ["view", packageSpec, "version"], {
610
+ encoding: "utf8",
611
+ stdio: "pipe",
612
+ shell: true,
613
+ });
614
+ if (result.status !== 0) {
615
+ return null;
616
+ }
617
+ return (result.stdout || "").trim() || null;
618
+ }
619
+
620
+ function describeGitLayerState(layerPath) {
621
+ const branch = getGitBranch(layerPath);
622
+ const head = getGitHead(layerPath);
623
+ const diffSummary = getGitDiffSummary(layerPath);
624
+ const aheadBehind = getGitAheadBehind(layerPath);
625
+ return {
626
+ branch,
627
+ head,
628
+ dirty: diffSummary?.dirty ?? false,
629
+ diffLines: diffSummary?.lines ?? [],
630
+ aheadBehind,
631
+ };
632
+ }
633
+
634
+ function describeNpmLayerState(layerName, layerPath) {
635
+ const installedVersion = readInstalledVersion(layerPath);
636
+ const publishedVersion = getNpmPublishedVersion(packageSpecFor(layerName));
637
+ return {
638
+ installedVersion,
639
+ publishedVersion,
640
+ outdated: Boolean(installedVersion && publishedVersion && installedVersion !== publishedVersion),
641
+ };
642
+ }
643
+
500
644
  function installTargets(targets, installDir, source) {
501
645
  ensureInstallPrereqs(source);
502
646
  fs.mkdirSync(installDir, { recursive: true });
@@ -626,6 +770,54 @@ function listCommand(tokens) {
626
770
  }
627
771
  }
628
772
 
773
+ function statusCommand(tokens) {
774
+ const { installDir } = parseStatusArgs(tokens);
775
+ const { manifest } = loadManifestFromDir(installDir);
776
+ const entries = Object.entries(manifest.layers || {});
777
+
778
+ if (!entries.length) {
779
+ console.log("No layers installed.");
780
+ return;
781
+ }
782
+
783
+ ensureGitAvailable();
784
+ ensureNpmAvailable();
785
+
786
+ console.log(`architectonic status`);
787
+ console.log(` root: ${installDir}`);
788
+
789
+ for (const [name, layer] of entries) {
790
+ const layerPath = path.resolve(installDir, String(layer.path || ""));
791
+ if (!pathExists(layerPath)) {
792
+ console.log(` [missing] ${name}: ${layer.path || "unknown path"}`);
793
+ continue;
794
+ }
795
+
796
+ if (layer.source === "git") {
797
+ const state = describeGitLayerState(layerPath);
798
+ const dirtyLabel = state.dirty ? "dirty" : "clean";
799
+ const branchLabel = state.branch || "detached";
800
+ let relation = "no-upstream";
801
+ if (state.aheadBehind) {
802
+ relation = `ahead ${state.aheadBehind.ahead}, behind ${state.aheadBehind.behind}`;
803
+ }
804
+ console.log(` [git] ${name}: ${branchLabel}, ${dirtyLabel}, ${relation}`);
805
+ continue;
806
+ }
807
+
808
+ if (layer.source === "npm") {
809
+ const state = describeNpmLayerState(name, layerPath);
810
+ const installed = state.installedVersion || "unknown";
811
+ const published = state.publishedVersion || "unknown";
812
+ const relation = state.outdated ? "outdated" : "current-or-unknown";
813
+ console.log(` [npm] ${name}: installed ${installed}, published ${published}, ${relation}`);
814
+ continue;
815
+ }
816
+
817
+ console.log(` [unknown] ${name}: unsupported source ${layer.source || "unknown"}`);
818
+ }
819
+ }
820
+
629
821
  function inferLayerPath(installDir, layerName) {
630
822
  const candidate = targetPathFor(installDir, layerName);
631
823
  return pathExists(candidate) ? candidate : null;
@@ -837,6 +1029,66 @@ function removeCommand(tokens) {
837
1029
  console.log(`Updated ${manifestPath}`);
838
1030
  }
839
1031
 
1032
+ function diffCommand(tokens) {
1033
+ const { installDir, target } = parseDiffArgs(tokens);
1034
+ if (!target) {
1035
+ throw new Error("Specify a layer to diff.");
1036
+ }
1037
+ if (!supportedSet.has(target)) {
1038
+ throw new Error(`Unknown layer: ${target}`);
1039
+ }
1040
+ const { manifest } = loadManifestFromDir(installDir);
1041
+ const layer = manifest.layers[target];
1042
+ if (!layer) {
1043
+ throw new Error(`Layer not recorded in manifest: ${target}`);
1044
+ }
1045
+ const layerPath = path.resolve(installDir, String(layer.path || ""));
1046
+ if (!pathExists(layerPath)) {
1047
+ throw new Error(`Layer path does not exist: ${layerPath}`);
1048
+ }
1049
+
1050
+ if (layer.source === "git") {
1051
+ ensureGitAvailable();
1052
+ const summary = getGitDiffSummary(layerPath);
1053
+ const aheadBehind = getGitAheadBehind(layerPath);
1054
+ console.log(`architectonic diff ${target}`);
1055
+ console.log(` source: git`);
1056
+ console.log(` path: ${layerPath}`);
1057
+ if (aheadBehind) {
1058
+ console.log(` upstream: ${aheadBehind.upstream}`);
1059
+ console.log(` ahead: ${aheadBehind.ahead}`);
1060
+ console.log(` behind: ${aheadBehind.behind}`);
1061
+ }
1062
+ if (!summary || !summary.lines.length) {
1063
+ console.log(` local changes: none`);
1064
+ return;
1065
+ }
1066
+ console.log(` local changes:`);
1067
+ for (const line of summary.lines) {
1068
+ console.log(` ${line}`);
1069
+ }
1070
+ return;
1071
+ }
1072
+
1073
+ if (layer.source === "npm") {
1074
+ ensureNpmAvailable();
1075
+ const state = describeNpmLayerState(target, layerPath);
1076
+ console.log(`architectonic diff ${target}`);
1077
+ console.log(` source: npm`);
1078
+ console.log(` path: ${layerPath}`);
1079
+ console.log(` installed version: ${state.installedVersion || "unknown"}`);
1080
+ console.log(` published version: ${state.publishedVersion || "unknown"}`);
1081
+ if (state.outdated) {
1082
+ console.log(` drift: newer package available`);
1083
+ } else {
1084
+ console.log(` drift: none detected from package version`);
1085
+ }
1086
+ return;
1087
+ }
1088
+
1089
+ throw new Error(`Unsupported source for diff: ${layer.source || "unknown"}`);
1090
+ }
1091
+
840
1092
  try {
841
1093
  if (!command || command === "help" || command === "--help" || command === "-h") {
842
1094
  printHelp();
@@ -863,6 +1115,16 @@ try {
863
1115
  process.exit(0);
864
1116
  }
865
1117
 
1118
+ if (command === "status") {
1119
+ statusCommand(rest);
1120
+ process.exit(0);
1121
+ }
1122
+
1123
+ if (command === "diff") {
1124
+ diffCommand(rest);
1125
+ process.exit(0);
1126
+ }
1127
+
866
1128
  if (command === "update") {
867
1129
  updateCommand(rest);
868
1130
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "architectonic",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "CLI for composing agentic system layers such as teleology, identity, project, and skills.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",