@clossys/architect 0.1.8 → 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 +25 -0
- package/README.md +39 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +20 -4
- package/dist/cli.js.map +1 -1
- package/package.json +7 -1
- package/src/cli.ts +20 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ 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
|
+
|
|
8
33
|
## [0.1.8] - 2026-09-16
|
|
9
34
|
|
|
10
35
|
### Note
|
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
|
@@ -4,16 +4,21 @@ 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 {
|
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,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAE1D,MAAM,KAAK,GAAG
|
|
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",
|
package/src/cli.ts
CHANGED
|
@@ -5,17 +5,23 @@ 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 {
|