@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 +75 -0
- package/README.md +39 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +42 -6
- package/dist/cli.js.map +1 -1
- package/package.json +8 -2
- package/src/cli.ts +41 -6
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
|
-
|
|
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":";
|
|
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
|
|
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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":";AACA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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();
|