@clossys/architect 0.1.6 → 0.1.9

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/CHANGELOG.md CHANGED
@@ -5,6 +5,81 @@ All notable changes to this package are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.9] - 2026-09-18
9
+
10
+ ### Added
11
+
12
+ - Documented installation against the public npm registry
13
+ (`https://registry.npmjs.org`) and that installing needs no authentication.
14
+ - Stated the charter close condition in the README: independent consumer
15
+ evidence of `architecture exception rate`, computed by
16
+ `assessArchitectureExceptions()`. No observed material changes is
17
+ indeterminate, never a zero rate. This package does not measure consumer
18
+ evidence and does not close the loop.
19
+ - Declared `foundry.assessment` against the mapped `architect-check` bin with
20
+ `invocation: "single-json-input"`, so first-day onboarding discovers this
21
+ role's assessment surface from the installed manifest instead of inferring
22
+ one. Advisor remains the only required first-day role.
23
+ - `architect-check assessment.json`: one JSON object with `topology`,
24
+ `observations`, and `maximumExceptionRate`. Prints the architecture
25
+ exception rate report and exits on the `0` / `1` / `2` ternary. The
26
+ existing `topology` and `exceptions` subcommands remain.
27
+
28
+ ### Notes
29
+
30
+ - This does not claim the position is closed. Qualification of `0.1.9` is
31
+ deferred under #833.
32
+
33
+ ## [0.1.8] - 2026-09-16
34
+
35
+ ### Note
36
+
37
+ - **0.1.7 was never published; this release supersedes it without repeating
38
+ its work.** 0.1.7 (below) carried the same `architect-check` entry-point
39
+ fix as this release, and its qualification record
40
+ (`governance/release-qualifications/clossys-architect-0.1.7.json`) was
41
+ generated and retained, binding candidate `packageTreeSha1
42
+ 45714600108521d00ca95f9d43a72092acbf8d42`. Before publication, #919
43
+ corrected the new `bin-entry.test.ts` added by #909/0.1.7 so it chmods a
44
+ temporary copy of `dist/cli.js` instead of the real packed file — a fix
45
+ needed because CI packs immediately after running the package's tests, so
46
+ the original test left CI publishing a tarball in the wrong file mode.
47
+ That correction touched a file inside `packages/architect/`, which moved
48
+ the package tree to `9233ea5efff49257bcebc7e6e5882753591f5aee` and left the
49
+ retained 0.1.7 record qualified against a package tree that no longer
50
+ exists. Qualification records are immutable — each file path is
51
+ introduced exactly once and is never corrected in place — so the 0.1.7
52
+ record cannot be updated to match, and 0.1.7 cannot be published. This
53
+ release reuses the already-fixed and already-corrected source unchanged
54
+ and exists solely to obtain a fresh, never-before-used record path. See
55
+ #909 (the original `architect-check` defect) and #919 (the test
56
+ correction that made 0.1.7 unpublishable).
57
+
58
+ ## [0.1.7] - 2026-09-16
59
+
60
+ ### Fixed
61
+
62
+ - **The published `architect-check` binary was completely dead.** The
63
+ entry-point guard in `src/cli.ts` compared `import.meta.url` (which Node
64
+ always resolves through symlinks) against `process.argv[1]` (which, for
65
+ an installed CLI, is the `node_modules/.bin` symlink itself, never the
66
+ real file). The two paths were never equal, so `run()` never fired.
67
+ Every documented invocation of the published package — `--help`,
68
+ `architect-check topology <file>` against both valid and invalid input,
69
+ even an unrecognized subcommand — returned exit `0` with zero bytes of
70
+ output, including invalid input the documented contract says must exit
71
+ `1` with a `"state":"violated"` report. A consumer who wired
72
+ `architect-check` into CI got a gate that could not fail on any input.
73
+ Fixed by resolving both sides with `realpathSync` before comparing, the
74
+ same pattern already used elsewhere in this repository (for example
75
+ `designer/src/cli.ts` and `writer/src/cli.ts`). See #909.
76
+ - Added a test that spawns the compiled `dist/cli.js` through a real
77
+ `node_modules/.bin`-shaped symlink built in a temp directory, the only
78
+ shape that actually exercises the entry-point guard — the existing
79
+ `cli.test.ts` suite calls the exported `main(argv)` directly and never
80
+ touched `process.argv[1]`, which is exactly why this defect shipped
81
+ undetected.
82
+
8
83
  ## [0.1.6] - 2026-09-14
9
84
 
10
85
  ### Changed
package/README.md CHANGED
@@ -14,6 +14,39 @@ It performs no network I/O and has no runtime dependencies. Install it with:
14
14
  npm install @clossys/architect
15
15
  ```
16
16
 
17
+ This package is published to the public npm registry, `https://registry.npmjs.org`.
18
+ Installing it needs no authentication: no npm token, no `.npmrc` registry
19
+ override, and no GitHub credential of any kind.
20
+
21
+ ## Close condition
22
+
23
+ Independent consumer evidence shows the position's owned metric meets its
24
+ setpoint over the declared review cadence. The owned metric is `architecture
25
+ exception rate`, computed by `assessArchitectureExceptions()`. No observed
26
+ material changes means the rate is `indeterminate`, never zero. This package
27
+ does not measure consumer evidence and does not close the loop; a consumer
28
+ binds the condition against their own declared topology and independent
29
+ observations. A green run of this package's tests is not a close.
30
+
31
+ ```bash
32
+ architect-check assessment.json
33
+ ```
34
+
35
+ The command prints JSON and exits `0` for satisfied, `1` for violated, and
36
+ `2` for indeterminate, unreadable, or invalid input. The assessment file is
37
+ one JSON object with `topology`, `observations`, and `maximumExceptionRate`.
38
+
39
+ This package declares that command as its first-day assessment surface in
40
+ its own manifest:
41
+
42
+ ```json
43
+ "foundry": { "assessment": { "bin": "architect-check", "invocation": "single-json-input" } }
44
+ ```
45
+
46
+ Onboarding discovers that declaration from the installed manifest and never
47
+ infers a surface. Architect is not a required first-day role; Advisor
48
+ remains the only required first-day assessment.
49
+
17
50
  ## Job charter
18
51
 
19
52
  Architect operates in **optimize** mode. Its durable objective is to reduce
@@ -87,11 +120,16 @@ responsibilities implemented somewhere in the topology.
87
120
  ## CLI
88
121
 
89
122
  ```bash
123
+ architect-check assessment.json
90
124
  architect-check topology topology.json
91
125
  architect-check exceptions topology.json observations.json --maximum-exception-rate 0.05
92
126
  ```
93
127
 
94
- Both commands emit JSON. Exit codes are `0` satisfied, `1` violated, and `2`
128
+ `architect-check assessment.json` is the first-day assessment surface: one
129
+ JSON file containing `topology`, `observations`, and `maximumExceptionRate`.
130
+ The `topology` and `exceptions` subcommands remain for the two-file form.
131
+
132
+ All commands emit JSON. Exit codes are `0` satisfied, `1` violated, and `2`
95
133
  indeterminate or unable to run. Invalid topology is a violation for the
96
134
  `topology` conformance command. Invalid inputs make the exception rate
97
135
  indeterminate because no valid measurement can be computed.
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAgBA,qBAAa,sBAAuB,SAAQ,KAAK;CAAG;AAwCpD,8FAA8F;AAC9F,wBAAgB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAMpD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAkBA,qBAAa,sBAAuB,SAAQ,KAAK;CAAG;AAqDpD,8FAA8F;AAC9F,wBAAgB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAOpD"}
package/dist/cli.js CHANGED
@@ -1,19 +1,24 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { assessArchitectureExceptions } from "./assessment.js";
6
6
  import { validateOperatingTopology } from "./topology.js";
7
- const USAGE = `Usage: architect-check topology <topology-file>
7
+ const USAGE = `Usage: architect-check <assessment.json>
8
+ architect-check topology <topology-file>
8
9
  architect-check exceptions <topology-file> <observations-file> --maximum-exception-rate <rate>
9
10
 
10
11
  Commands:
11
- topology Validate a provider-neutral operating topology.
12
- exceptions Assess architecture exception rate from observed changes.
12
+ <assessment.json> Assess architecture exception rate from one JSON file containing topology, observations, and maximumExceptionRate.
13
+ topology Validate a provider-neutral operating topology.
14
+ exceptions Assess architecture exception rate from observed changes.
13
15
 
14
16
  Exit codes: 0 = satisfied, 1 = violated, 2 = indeterminate or could not run.`;
15
17
  export class ArchitectCliInputError extends Error {
16
18
  }
19
+ function isRecord(value) {
20
+ return typeof value === "object" && value !== null && !Array.isArray(value);
21
+ }
17
22
  function readJson(path, label) {
18
23
  const resolved = resolve(path);
19
24
  if (!existsSync(resolved))
@@ -34,6 +39,15 @@ function readJson(path, label) {
34
39
  throw new ArchitectCliInputError(`${label} "${path}" is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
35
40
  }
36
41
  }
42
+ function assessmentCommand(path) {
43
+ const value = readJson(path, "assessment file");
44
+ if (!isRecord(value))
45
+ throw new ArchitectCliInputError("assessment file must be a JSON object");
46
+ const maximumExceptionRate = typeof value.maximumExceptionRate === "number" ? value.maximumExceptionRate : Number(value.maximumExceptionRate);
47
+ const report = assessArchitectureExceptions(value.topology, value.observations, { maximumExceptionRate });
48
+ console.log(JSON.stringify({ ...report, proposedPositions: [] }, null, 2));
49
+ return report.state === "satisfied" ? 0 : report.state === "violated" ? 1 : 2;
50
+ }
37
51
  function topologyCommand(argv) {
38
52
  if (argv.length !== 1)
39
53
  throw new ArchitectCliInputError("topology requires exactly one topology-file");
@@ -80,7 +94,9 @@ export function main(argv) {
80
94
  return topologyCommand(rest);
81
95
  if (command === "exceptions")
82
96
  return exceptionsCommand(rest);
83
- throw new ArchitectCliInputError(command === undefined ? "a command is required" : `unknown command "${command}"`);
97
+ if (command !== undefined && rest.length === 0)
98
+ return assessmentCommand(command);
99
+ throw new ArchitectCliInputError(command === undefined ? "a command or assessment.json file is required" : `unknown command "${command}"`);
84
100
  }
85
101
  function run() {
86
102
  try {
@@ -91,6 +107,26 @@ function run() {
91
107
  process.exitCode = 2;
92
108
  }
93
109
  }
94
- if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === resolve(process.argv[1]))
110
+ /**
111
+ * Same real-path guard `designer`'s `cli.ts` and `writer`'s `cli.ts` both
112
+ * use, for the same reason: `npm install` publishes `bin` entries as
113
+ * symlinks, so comparing `process.argv[1]` to `import.meta.url` without
114
+ * resolving symlinks on both sides fails the moment this file is actually
115
+ * invoked the only way it ships — as an installed CLI — and does so
116
+ * silently (`run()` never fires, nothing prints, exit code 0).
117
+ */
118
+ function detectMainModule() {
119
+ const argvPath = process.argv[1];
120
+ if (argvPath === undefined)
121
+ return false;
122
+ const modulePath = fileURLToPath(import.meta.url);
123
+ try {
124
+ return realpathSync(resolve(argvPath)) === realpathSync(modulePath);
125
+ }
126
+ catch {
127
+ return resolve(argvPath) === modulePath;
128
+ }
129
+ }
130
+ if (detectMainModule())
95
131
  run();
96
132
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clossys/architect",
3
- "version": "0.1.6",
3
+ "version": "0.1.9",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,6 +29,12 @@
29
29
  "LICENSE"
30
30
  ],
31
31
  "sideEffects": false,
32
+ "foundry": {
33
+ "assessment": {
34
+ "bin": "architect-check",
35
+ "invocation": "single-json-input"
36
+ }
37
+ },
32
38
  "description": "The architect role: provider-neutral operating topology, ontology, authority and interface contracts, plus evidence-based architecture exception assessment.",
33
39
  "keywords": [
34
40
  "architecture",
@@ -58,7 +64,7 @@
58
64
  "test": "vitest run"
59
65
  },
60
66
  "devDependencies": {
61
- "@types/node": "^26.4.1",
67
+ "@types/node": "^26.5.1",
62
68
  "typescript": "~6.0.0",
63
69
  "vitest": "^5.0.0"
64
70
  },
package/src/cli.ts CHANGED
@@ -1,21 +1,27 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { assessArchitectureExceptions } from "./assessment.js";
6
6
  import { validateOperatingTopology } from "./topology.js";
7
7
 
8
- const USAGE = `Usage: architect-check topology <topology-file>
8
+ const USAGE = `Usage: architect-check <assessment.json>
9
+ architect-check topology <topology-file>
9
10
  architect-check exceptions <topology-file> <observations-file> --maximum-exception-rate <rate>
10
11
 
11
12
  Commands:
12
- topology Validate a provider-neutral operating topology.
13
- exceptions Assess architecture exception rate from observed changes.
13
+ <assessment.json> Assess architecture exception rate from one JSON file containing topology, observations, and maximumExceptionRate.
14
+ topology Validate a provider-neutral operating topology.
15
+ exceptions Assess architecture exception rate from observed changes.
14
16
 
15
17
  Exit codes: 0 = satisfied, 1 = violated, 2 = indeterminate or could not run.`;
16
18
 
17
19
  export class ArchitectCliInputError extends Error {}
18
20
 
21
+ function isRecord(value: unknown): value is Record<string, unknown> {
22
+ return typeof value === "object" && value !== null && !Array.isArray(value);
23
+ }
24
+
19
25
  function readJson(path: string, label: string): unknown {
20
26
  const resolved = resolve(path);
21
27
  if (!existsSync(resolved)) throw new ArchitectCliInputError(`${label} "${path}" does not exist`);
@@ -25,6 +31,15 @@ function readJson(path: string, label: string): unknown {
25
31
  catch (error) { throw new ArchitectCliInputError(`${label} "${path}" is not valid JSON: ${error instanceof Error ? error.message : String(error)}`); }
26
32
  }
27
33
 
34
+ function assessmentCommand(path: string): number {
35
+ const value = readJson(path, "assessment file");
36
+ if (!isRecord(value)) throw new ArchitectCliInputError("assessment file must be a JSON object");
37
+ const maximumExceptionRate = typeof value.maximumExceptionRate === "number" ? value.maximumExceptionRate : Number(value.maximumExceptionRate);
38
+ const report = assessArchitectureExceptions(value.topology, value.observations, { maximumExceptionRate });
39
+ console.log(JSON.stringify({ ...report, proposedPositions: [] }, null, 2));
40
+ return report.state === "satisfied" ? 0 : report.state === "violated" ? 1 : 2;
41
+ }
42
+
28
43
  function topologyCommand(argv: readonly string[]): number {
29
44
  if (argv.length !== 1) throw new ArchitectCliInputError("topology requires exactly one topology-file");
30
45
  const findings = validateOperatingTopology(readJson(argv[0] as string, "topology-file"));
@@ -60,7 +75,8 @@ export function main(argv: readonly string[]): number {
60
75
  const [command, ...rest] = argv;
61
76
  if (command === "topology") return topologyCommand(rest);
62
77
  if (command === "exceptions") return exceptionsCommand(rest);
63
- throw new ArchitectCliInputError(command === undefined ? "a command is required" : `unknown command "${command}"`);
78
+ if (command !== undefined && rest.length === 0) return assessmentCommand(command);
79
+ throw new ArchitectCliInputError(command === undefined ? "a command or assessment.json file is required" : `unknown command "${command}"`);
64
80
  }
65
81
 
66
82
  function run(): void {
@@ -71,4 +87,23 @@ function run(): void {
71
87
  }
72
88
  }
73
89
 
74
- if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === resolve(process.argv[1])) run();
90
+ /**
91
+ * Same real-path guard `designer`'s `cli.ts` and `writer`'s `cli.ts` both
92
+ * use, for the same reason: `npm install` publishes `bin` entries as
93
+ * symlinks, so comparing `process.argv[1]` to `import.meta.url` without
94
+ * resolving symlinks on both sides fails the moment this file is actually
95
+ * invoked the only way it ships — as an installed CLI — and does so
96
+ * silently (`run()` never fires, nothing prints, exit code 0).
97
+ */
98
+ function detectMainModule(): boolean {
99
+ const argvPath = process.argv[1];
100
+ if (argvPath === undefined) return false;
101
+ const modulePath = fileURLToPath(import.meta.url);
102
+ try {
103
+ return realpathSync(resolve(argvPath)) === realpathSync(modulePath);
104
+ } catch {
105
+ return resolve(argvPath) === modulePath;
106
+ }
107
+ }
108
+
109
+ if (detectMainModule()) run();