@archora/forge-cli 1.2.2 → 1.3.0
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/LICENSE +121 -0
- package/dist/index.js +243 -5
- package/package.json +51 -51
package/LICENSE
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Archora Forge Proprietary Source-Available License (Paid-Use-Only)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aleksandr Kotov (the "Licensor"). All rights reserved.
|
|
4
|
+
|
|
5
|
+
The source code and any other materials made available in this repository
|
|
6
|
+
(collectively, the "Software") are proprietary and confidential to the
|
|
7
|
+
Licensor. They are published in source-available form for the sole purpose
|
|
8
|
+
of allowing prospective licensees to inspect and evaluate the Software
|
|
9
|
+
before purchasing a commercial license.
|
|
10
|
+
|
|
11
|
+
This Software is NOT open source. The publication of this repository on a
|
|
12
|
+
public hosting platform does NOT constitute a grant of any open-source or
|
|
13
|
+
free-software license, and does NOT grant any rights beyond those expressly
|
|
14
|
+
stated below.
|
|
15
|
+
|
|
16
|
+
1. Limited Permission Granted Without a Paid License
|
|
17
|
+
|
|
18
|
+
Without entering into a separate written commercial license with the
|
|
19
|
+
Licensor, you are permitted to do ONLY the following with the Software:
|
|
20
|
+
|
|
21
|
+
(a) view and read the source code via the hosting platform on which
|
|
22
|
+
it is published;
|
|
23
|
+
(b) clone or download a copy strictly for the purpose of personally
|
|
24
|
+
evaluating whether to obtain a paid commercial license, for a
|
|
25
|
+
reasonable evaluation period not exceeding thirty (30) days; and
|
|
26
|
+
(c) discuss the Software publicly (e.g. cite, link, or quote short
|
|
27
|
+
excerpts for commentary, criticism, news reporting, teaching,
|
|
28
|
+
scholarship, or research) consistent with applicable fair use
|
|
29
|
+
or fair dealing law.
|
|
30
|
+
|
|
31
|
+
No other rights are granted. In particular, and without limitation,
|
|
32
|
+
the following are NOT permitted without a separate paid commercial
|
|
33
|
+
license signed by the Licensor:
|
|
34
|
+
|
|
35
|
+
- any use of the Software, in whole or in part, in any project,
|
|
36
|
+
product, service, library, internal tool, script, pipeline,
|
|
37
|
+
build, CI job, or deliverable, whether commercial or
|
|
38
|
+
non-commercial, paid or unpaid, personal or organizational,
|
|
39
|
+
for-profit or not-for-profit, including hobby and educational
|
|
40
|
+
projects;
|
|
41
|
+
- execution of the Software for any purpose other than short-term
|
|
42
|
+
evaluation by you personally as described in clause 1(b);
|
|
43
|
+
- copying, modifying, merging, publishing, sublicensing,
|
|
44
|
+
distributing, selling, hosting, or offering the Software (or any
|
|
45
|
+
derivative work) as a service to any third party;
|
|
46
|
+
- using code generated by the Software in any project for which
|
|
47
|
+
you do not hold a valid commercial license to the Software;
|
|
48
|
+
- removing or altering any copyright, trademark, license, or
|
|
49
|
+
attribution notices contained in the Software or in code it
|
|
50
|
+
generates;
|
|
51
|
+
- using the Software, its source code, or its outputs to train,
|
|
52
|
+
fine-tune, evaluate, benchmark, or otherwise develop
|
|
53
|
+
machine-learning or artificial-intelligence models, datasets, or
|
|
54
|
+
systems;
|
|
55
|
+
- reverse-engineering the Software for any purpose other than
|
|
56
|
+
evaluation as described in clause 1(b), to the extent such
|
|
57
|
+
restriction is permitted by applicable law.
|
|
58
|
+
|
|
59
|
+
2. No Implied License
|
|
60
|
+
|
|
61
|
+
No license is granted by implication, estoppel, exhaustion, course of
|
|
62
|
+
dealing, or otherwise. Public availability of this repository does
|
|
63
|
+
not change this.
|
|
64
|
+
|
|
65
|
+
3. Commercial License Required
|
|
66
|
+
|
|
67
|
+
To use the Software for any purpose beyond the limited evaluation
|
|
68
|
+
described in Section 1, you must first obtain a paid commercial
|
|
69
|
+
license from the Licensor. Pricing and terms are available on
|
|
70
|
+
request — see COMMERCIAL-LICENSE.md.
|
|
71
|
+
|
|
72
|
+
4. Trademarks
|
|
73
|
+
|
|
74
|
+
"Archora" and "Archora Forge" and any associated names, logos, and
|
|
75
|
+
brands are trademarks of the Licensor. No trademark rights are
|
|
76
|
+
granted by this license. Forks, modifications, or derivative works
|
|
77
|
+
(where permitted under a separate commercial license) may not use
|
|
78
|
+
these marks without the Licensor's prior written consent.
|
|
79
|
+
|
|
80
|
+
5. Term and Termination
|
|
81
|
+
|
|
82
|
+
Your permission under Section 1 terminates automatically and
|
|
83
|
+
immediately upon any breach of this license, or upon written notice
|
|
84
|
+
from the Licensor. Upon termination you must delete all copies of
|
|
85
|
+
the Software in your possession or control, except for archival
|
|
86
|
+
copies you are required by law to retain.
|
|
87
|
+
|
|
88
|
+
6. No Warranty
|
|
89
|
+
|
|
90
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
91
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
92
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND
|
|
93
|
+
NON-INFRINGEMENT.
|
|
94
|
+
|
|
95
|
+
7. Limitation of Liability
|
|
96
|
+
|
|
97
|
+
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL
|
|
98
|
+
THE LICENSOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
99
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION
|
|
100
|
+
WITH THE SOFTWARE OR THIS LICENSE, EVEN IF ADVISED OF THE POSSIBILITY
|
|
101
|
+
OF SUCH DAMAGES.
|
|
102
|
+
|
|
103
|
+
8. Governing Law
|
|
104
|
+
|
|
105
|
+
This license shall be governed by and construed in accordance with
|
|
106
|
+
the laws applicable at the Licensor's place of residence, without
|
|
107
|
+
regard to its conflict-of-laws principles.
|
|
108
|
+
|
|
109
|
+
9. Prior Versions
|
|
110
|
+
|
|
111
|
+
Versions of the Software previously distributed under a different
|
|
112
|
+
license (including, without limitation, the MIT License) remain
|
|
113
|
+
governed by the license under which they were originally
|
|
114
|
+
distributed, with respect to the specific commits to which that
|
|
115
|
+
prior license applied. From the effective date of this license,
|
|
116
|
+
subsequent commits and releases are governed exclusively by these
|
|
117
|
+
terms.
|
|
118
|
+
|
|
119
|
+
For commercial licensing inquiries, contact:
|
|
120
|
+
Email: akotov@archora.dev
|
|
121
|
+
Telegram: @akotofff
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ var ForgeError = class extends Error {
|
|
|
30
30
|
this.details = details;
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
|
-
var forgeCoreVersion = "1.
|
|
33
|
+
var forgeCoreVersion = "1.3.0";
|
|
34
34
|
var forgeGeneratedMarker = "// @archora-forge-generated";
|
|
35
35
|
var forgeGeneratedMetadataMarker = "// @archora-forge-meta";
|
|
36
36
|
async function writeGeneratedFiles(files, options) {
|
|
@@ -3350,6 +3350,9 @@ function renderReadiness(readiness) {
|
|
|
3350
3350
|
<div class="card">
|
|
3351
3351
|
<h2>Pilot Readiness</h2>
|
|
3352
3352
|
<p>Status: <strong>${escapeHtml(readiness.status ?? "n/a")}</strong></p>
|
|
3353
|
+
${readiness.gate ? `<p>Gate: <strong>${escapeHtml(readiness.gate.result ?? "n/a")}</strong></p>
|
|
3354
|
+
<p>Recommended CI mode: <strong>${escapeHtml(readiness.gate.recommendedCiMode ?? "n/a")}</strong></p>
|
|
3355
|
+
<p>${escapeHtml(readiness.gate.reason ?? "")}</p>` : ""}
|
|
3353
3356
|
<p>${escapeHtml(readiness.decision ?? "")}</p>
|
|
3354
3357
|
</div>
|
|
3355
3358
|
<div class="card">
|
|
@@ -3364,6 +3367,10 @@ function renderReadiness(readiness) {
|
|
|
3364
3367
|
<h2>Warnings</h2>
|
|
3365
3368
|
${renderSimpleList(readiness.warnings ?? [], "No warnings.")}
|
|
3366
3369
|
</div>
|
|
3370
|
+
${readiness.reviewerChecklist ? `<div class="card">
|
|
3371
|
+
<h2>Reviewer Checklist</h2>
|
|
3372
|
+
${renderSimpleList(readiness.reviewerChecklist, "No reviewer checklist.")}
|
|
3373
|
+
</div>` : ""}
|
|
3367
3374
|
</section>`;
|
|
3368
3375
|
}
|
|
3369
3376
|
function renderSimpleList(items, empty) {
|
|
@@ -4056,12 +4063,33 @@ function createAuditReadiness(input) {
|
|
|
4056
4063
|
...input.diagnostics.length > 0 ? ["Use fix suggestions to decide which OpenAPI changes are required before purchase."] : [],
|
|
4057
4064
|
...input.ok ? ["Use this audit package as the paid pilot evidence bundle."] : []
|
|
4058
4065
|
];
|
|
4066
|
+
const status = blockers.length > 0 ? "blocked" : warnings.length > 0 ? "needs-attention" : "ready";
|
|
4067
|
+
const gate = status === "ready" ? {
|
|
4068
|
+
result: "pass",
|
|
4069
|
+
recommendedCiMode: "block",
|
|
4070
|
+
reason: "Generated resource layer is ready for a blocking CI gate under the current policy."
|
|
4071
|
+
} : status === "needs-attention" ? {
|
|
4072
|
+
result: "warn",
|
|
4073
|
+
recommendedCiMode: "comment",
|
|
4074
|
+
reason: "Continue evaluation, but require explicit owner review before blocking merges."
|
|
4075
|
+
} : {
|
|
4076
|
+
result: "fail",
|
|
4077
|
+
recommendedCiMode: "block",
|
|
4078
|
+
reason: "Do not widen rollout until blockers are fixed or explicitly accepted."
|
|
4079
|
+
};
|
|
4059
4080
|
return {
|
|
4060
|
-
status
|
|
4081
|
+
status,
|
|
4082
|
+
gate,
|
|
4061
4083
|
decision: input.ok ? "Generated resource layer is ready for local adoption review." : "Generated resource layer needs fixes or explicit risk acceptance before purchase.",
|
|
4062
4084
|
blockers,
|
|
4063
4085
|
warnings,
|
|
4064
4086
|
nextActions: nextActions.length > 0 ? nextActions : ["No action required."],
|
|
4087
|
+
reviewerChecklist: [
|
|
4088
|
+
"Open `index.html` and confirm detected resources match the frontend mental model.",
|
|
4089
|
+
"Open `report.md` and review blockers, warnings, scorecard and fix suggestions.",
|
|
4090
|
+
"Open `typecheck.md` and confirm generated TypeScript passed or every failure is triaged.",
|
|
4091
|
+
"Compare generated files with the existing API layer before committing rollout scope."
|
|
4092
|
+
],
|
|
4065
4093
|
summary: {
|
|
4066
4094
|
healthScore: input.healthScore,
|
|
4067
4095
|
resources: input.resources,
|
|
@@ -4093,6 +4121,16 @@ function createAuditMarkdown(payload) {
|
|
|
4093
4121
|
|
|
4094
4122
|
Status: ${payload.ok ? "passed" : "failed"}
|
|
4095
4123
|
|
|
4124
|
+
## Executive Review
|
|
4125
|
+
|
|
4126
|
+
Readiness: ${payload.readiness.status}
|
|
4127
|
+
|
|
4128
|
+
Gate: ${payload.readiness.gate.result}
|
|
4129
|
+
|
|
4130
|
+
Recommended CI mode: ${payload.readiness.gate.recommendedCiMode}
|
|
4131
|
+
|
|
4132
|
+
Gate reason: ${payload.readiness.gate.reason}
|
|
4133
|
+
|
|
4096
4134
|
Decision: ${payload.readiness.decision}
|
|
4097
4135
|
|
|
4098
4136
|
Health score: ${payload.healthScore}
|
|
@@ -4111,6 +4149,10 @@ ${Object.entries(payload.scorecard).map(([key, value]) => `- ${key}: ${value}`).
|
|
|
4111
4149
|
|
|
4112
4150
|
${payload.fixSuggestions.length > 0 ? payload.fixSuggestions.map((item) => `- ${item.code} (${item.count}): ${item.suggestion}`).join("\n") : "- No fix suggestions."}
|
|
4113
4151
|
|
|
4152
|
+
## Reviewer Checklist
|
|
4153
|
+
|
|
4154
|
+
${payload.readiness.reviewerChecklist.map((item) => `- ${item}`).join("\n")}
|
|
4155
|
+
|
|
4114
4156
|
## Artifacts
|
|
4115
4157
|
|
|
4116
4158
|
${payload.audit.artifacts.map((artifact) => `- ${artifact}`).join("\n")}
|
|
@@ -4345,6 +4387,13 @@ function createMarkdownReport(payload) {
|
|
|
4345
4387
|
|
|
4346
4388
|
Readiness: ${payload.readiness.status}
|
|
4347
4389
|
|
|
4390
|
+
${payload.readiness.gate ? `Gate: ${payload.readiness.gate.result}
|
|
4391
|
+
|
|
4392
|
+
Recommended CI mode: ${payload.readiness.gate.recommendedCiMode}
|
|
4393
|
+
|
|
4394
|
+
Gate reason: ${payload.readiness.gate.reason}
|
|
4395
|
+
` : ""}
|
|
4396
|
+
|
|
4348
4397
|
Decision: ${payload.readiness.decision}
|
|
4349
4398
|
|
|
4350
4399
|
Blockers:
|
|
@@ -4430,6 +4479,19 @@ function createReadinessSummary(input) {
|
|
|
4430
4479
|
...input.summary.healthScore < 90 ? [`Health score is ${input.summary.healthScore}, below the recommended pilot threshold of 90.`] : []
|
|
4431
4480
|
];
|
|
4432
4481
|
const status = blockers.length > 0 ? "blocked" : warnings.length > 0 ? "needs-attention" : "ready";
|
|
4482
|
+
const gate = status === "ready" ? {
|
|
4483
|
+
result: "pass",
|
|
4484
|
+
recommendedCiMode: "block",
|
|
4485
|
+
reason: "Keep the blocking check enabled; no report findings require human acceptance."
|
|
4486
|
+
} : status === "needs-attention" ? {
|
|
4487
|
+
result: "warn",
|
|
4488
|
+
recommendedCiMode: "comment",
|
|
4489
|
+
reason: "Use comment-only mode until warnings are triaged or accepted."
|
|
4490
|
+
} : {
|
|
4491
|
+
result: "fail",
|
|
4492
|
+
recommendedCiMode: "block",
|
|
4493
|
+
reason: "Block merge or require explicit acceptance before pilot handoff."
|
|
4494
|
+
};
|
|
4433
4495
|
const nextActions = status === "ready" ? ["Use the report as the pilot readiness artifact and keep `archora-forge check` in CI."] : [
|
|
4434
4496
|
...input.drift.length > 0 ? ["Run `archora-forge generate` and commit the generated output, or review intentional drift before the pilot handoff."] : [],
|
|
4435
4497
|
...input.diagnostics.length > 0 ? ["Review diagnostics and decide which schema fixes are required for the pilot scope."] : [],
|
|
@@ -4437,6 +4499,7 @@ function createReadinessSummary(input) {
|
|
|
4437
4499
|
];
|
|
4438
4500
|
return {
|
|
4439
4501
|
status,
|
|
4502
|
+
gate,
|
|
4440
4503
|
decision: status === "ready" ? "Schema is ready for a pilot readiness handoff under the current check policy." : status === "needs-attention" ? "Schema can continue through pilot review, but findings should be triaged first." : "Schema is not ready for pilot handoff until blockers are resolved or explicitly accepted.",
|
|
4441
4504
|
blockers,
|
|
4442
4505
|
warnings,
|
|
@@ -4462,17 +4525,18 @@ function evaluateFailedChecks(input) {
|
|
|
4462
4525
|
// src/commands/ci.command.ts
|
|
4463
4526
|
import { access as access3 } from "fs/promises";
|
|
4464
4527
|
function registerCiCommand(cli) {
|
|
4465
|
-
cli.command("ci <action> <provider>", "Create CI workflow templates for Forge impact review").option("--force", "Overwrite an existing workflow file").option("--output <path>", "Write workflow to a custom path").option("--mode <mode>", "Workflow mode: impact or pilot").option("--schema <path>", "OpenAPI schema path inside the repository").option("--base <ref>", "Git base ref for previous schema").action(async (action, provider, options) => {
|
|
4528
|
+
cli.command("ci <action> <provider>", "Create CI workflow templates for Forge impact review").option("--force", "Overwrite an existing workflow file").option("--output <path>", "Write workflow to a custom path").option("--mode <mode>", "Workflow mode: impact or pilot").option("--gate <mode>", "Merge gate mode: block or comment").option("--schema <path>", "OpenAPI schema path inside the repository").option("--base <ref>", "Git base ref for previous schema").action(async (action, provider, options) => {
|
|
4466
4529
|
if (action !== "init") throw new Error(`Unknown CI action "${action}". Use init.`);
|
|
4467
4530
|
if (provider !== "github") throw new Error(`Unsupported CI provider "${provider}". Use github.`);
|
|
4468
4531
|
const mode = normalizeMode(options.mode);
|
|
4532
|
+
const gate = normalizeGate(options.gate);
|
|
4469
4533
|
const path = options.output ?? ".github/workflows/archora-forge-impact.yml";
|
|
4470
4534
|
if (await fileExists2(path) && !options.force) {
|
|
4471
4535
|
logger.warn(`${path} already exists`);
|
|
4472
4536
|
logger.line("Use --force to overwrite it.");
|
|
4473
4537
|
return;
|
|
4474
4538
|
}
|
|
4475
|
-
await writeReportFile(path, createGithubImpactWorkflow({ mode, schema: options.schema ?? "openapi.yaml", base: options.base ?? "origin/main" }));
|
|
4539
|
+
await writeReportFile(path, createGithubImpactWorkflow({ mode, gate, schema: options.schema ?? "openapi.yaml", base: options.base ?? "origin/main" }));
|
|
4476
4540
|
logger.success(`Created ${path}`);
|
|
4477
4541
|
logger.line("Next: review OPENAPI_SCHEMA and OPENAPI_BASE_REF for your repository.");
|
|
4478
4542
|
});
|
|
@@ -4485,6 +4549,11 @@ function normalizeMode(value) {
|
|
|
4485
4549
|
if (value === "impact" || value === "pilot") return value;
|
|
4486
4550
|
throw new Error("Invalid CI mode. Use impact or pilot.");
|
|
4487
4551
|
}
|
|
4552
|
+
function normalizeGate(value) {
|
|
4553
|
+
if (!value) return "block";
|
|
4554
|
+
if (value === "block" || value === "comment") return value;
|
|
4555
|
+
throw new Error("Invalid CI gate mode. Use block or comment.");
|
|
4556
|
+
}
|
|
4488
4557
|
function createGithubImpactWorkflow(options) {
|
|
4489
4558
|
const runStep = options.mode === "pilot" ? ` pnpm exec archora-forge pilot "$OPENAPI_SCHEMA" --base "$OPENAPI_BASE_REF" \\
|
|
4490
4559
|
--repo . \\
|
|
@@ -4496,6 +4565,12 @@ function createGithubImpactWorkflow(options) {
|
|
|
4496
4565
|
--pr-comment-file forge-impact-pr.md`;
|
|
4497
4566
|
const artifactPath = options.mode === "pilot" ? ` forge-pilot/**` : ` forge-impact.md
|
|
4498
4567
|
forge-impact-pr.md`;
|
|
4568
|
+
const blockStep = options.gate === "block" ? `
|
|
4569
|
+
- name: Block merge on blocked API impact
|
|
4570
|
+
if: steps.forge.outcome == 'failure'
|
|
4571
|
+
run: |
|
|
4572
|
+
echo "Archora Forge reported blocked API impact. Review the PR comment and uploaded artifacts."
|
|
4573
|
+
exit 1` : "";
|
|
4499
4574
|
return `name: archora-forge-impact
|
|
4500
4575
|
|
|
4501
4576
|
on:
|
|
@@ -4512,6 +4587,7 @@ permissions:
|
|
|
4512
4587
|
env:
|
|
4513
4588
|
OPENAPI_SCHEMA: ${options.schema}
|
|
4514
4589
|
OPENAPI_BASE_REF: ${options.base}
|
|
4590
|
+
FORGE_GATE_MODE: ${options.gate}
|
|
4515
4591
|
|
|
4516
4592
|
jobs:
|
|
4517
4593
|
${options.mode}:
|
|
@@ -4533,6 +4609,8 @@ jobs:
|
|
|
4533
4609
|
- run: pnpm install --frozen-lockfile
|
|
4534
4610
|
|
|
4535
4611
|
- name: Run Forge impact
|
|
4612
|
+
id: forge
|
|
4613
|
+
continue-on-error: true
|
|
4536
4614
|
run: |
|
|
4537
4615
|
${runStep}
|
|
4538
4616
|
|
|
@@ -4548,6 +4626,7 @@ ${artifactPath}
|
|
|
4548
4626
|
with:
|
|
4549
4627
|
file-path: forge-impact-pr.md
|
|
4550
4628
|
comment-tag: archora-forge-impact
|
|
4629
|
+
${blockStep}
|
|
4551
4630
|
`;
|
|
4552
4631
|
}
|
|
4553
4632
|
|
|
@@ -4640,14 +4719,48 @@ function formatPullRequestComment(payload) {
|
|
|
4640
4719
|
return [
|
|
4641
4720
|
"## Frontend API Impact",
|
|
4642
4721
|
"",
|
|
4722
|
+
`Merge decision: ${formatMergeDecision(payload.decision.status)}`,
|
|
4723
|
+
`Merge risk: ${payload.decision.mergeRisk}`,
|
|
4724
|
+
`Reason: ${payload.decision.reason}`,
|
|
4725
|
+
"",
|
|
4643
4726
|
payload.prSummary,
|
|
4644
4727
|
"",
|
|
4728
|
+
"## Next Actions",
|
|
4729
|
+
"",
|
|
4730
|
+
...formatNextActionLines(payload),
|
|
4731
|
+
"",
|
|
4645
4732
|
"## Source Usage",
|
|
4646
4733
|
"",
|
|
4647
4734
|
...formatSourceUsageLines(payload.sourceUsages ?? []),
|
|
4648
4735
|
""
|
|
4649
4736
|
].join("\n");
|
|
4650
4737
|
}
|
|
4738
|
+
function formatMergeDecision(status) {
|
|
4739
|
+
if (status === "blocked") return "block";
|
|
4740
|
+
if (status === "review") return "review";
|
|
4741
|
+
return "pass";
|
|
4742
|
+
}
|
|
4743
|
+
function formatNextActionLines(payload) {
|
|
4744
|
+
if (payload.decision.status === "blocked") {
|
|
4745
|
+
return [
|
|
4746
|
+
"- Do not merge until the breaking frontend contract changes are handled.",
|
|
4747
|
+
"- Update impacted source usages before regenerating committed Forge output.",
|
|
4748
|
+
"- Re-run `archora-forge impact` after the OpenAPI or frontend changes are updated."
|
|
4749
|
+
];
|
|
4750
|
+
}
|
|
4751
|
+
if (payload.decision.status === "review") {
|
|
4752
|
+
return [
|
|
4753
|
+
"- Review warnings with the frontend owner before merge.",
|
|
4754
|
+
"- Regenerate Forge output in the branch if the contract change is accepted.",
|
|
4755
|
+
"- Keep the PR comment attached to the API change for reviewer context."
|
|
4756
|
+
];
|
|
4757
|
+
}
|
|
4758
|
+
return [
|
|
4759
|
+
"- Merge can continue from the API impact perspective.",
|
|
4760
|
+
"- Regenerate Forge output when the schema change is accepted.",
|
|
4761
|
+
"- Keep `archora-forge check` in CI to catch drift after regeneration."
|
|
4762
|
+
];
|
|
4763
|
+
}
|
|
4651
4764
|
function formatSourceUsageLines(usages) {
|
|
4652
4765
|
if (usages.length === 0) return ["No impacted source usages found."];
|
|
4653
4766
|
return usages.slice(0, 50).map((usage) => `- \`${usage.path}:${usage.lines.join(",")}\`: ${usage.matches.join(", ")}`);
|
|
@@ -4832,6 +4945,10 @@ function registerDemoCommand(cli) {
|
|
|
4832
4945
|
skipTypecheck: true
|
|
4833
4946
|
});
|
|
4834
4947
|
await writeReportFile(join8(reportDir, "go-no-go.md"), createDemoGoNoGo(impact, audit.payload.ok));
|
|
4948
|
+
await Promise.all([
|
|
4949
|
+
writeReportFile(join8(reportDir, "check.html"), createDemoCheckHtml(impact, audit.payload.ok)),
|
|
4950
|
+
writeReportFile(join8(reportDir, "README.md"), createDemoHandoff())
|
|
4951
|
+
]);
|
|
4835
4952
|
logger.title();
|
|
4836
4953
|
logger.line(`Demo package: ${outDir}`);
|
|
4837
4954
|
logger.line(`Decision: ${impact.decision.status === "blocked" ? "no-go" : "review"}`);
|
|
@@ -4867,6 +4984,69 @@ function createDemoGoNoGo(impact, auditOk) {
|
|
|
4867
4984
|
""
|
|
4868
4985
|
].join("\n");
|
|
4869
4986
|
}
|
|
4987
|
+
function createDemoCheckHtml(impact, auditOk) {
|
|
4988
|
+
const failedChecks = [
|
|
4989
|
+
...impact.decision.status === "blocked" ? ["api-impact-blocked"] : [],
|
|
4990
|
+
...!auditOk ? ["audit-readiness-failed"] : []
|
|
4991
|
+
];
|
|
4992
|
+
const status = failedChecks.length === 0 ? "ready" : "blocked";
|
|
4993
|
+
return createHtmlReport("Archora Forge Check", {
|
|
4994
|
+
ok: failedChecks.length === 0,
|
|
4995
|
+
schema: impact.newSchema,
|
|
4996
|
+
healthScore: auditOk ? 100 : 80,
|
|
4997
|
+
generatedFiles: impact.affectedFiles.length,
|
|
4998
|
+
failedChecks,
|
|
4999
|
+
readiness: {
|
|
5000
|
+
status,
|
|
5001
|
+
gate: status === "blocked" ? {
|
|
5002
|
+
result: "fail",
|
|
5003
|
+
recommendedCiMode: "block",
|
|
5004
|
+
reason: "Demo impact contains blocking frontend API changes."
|
|
5005
|
+
} : {
|
|
5006
|
+
result: "pass",
|
|
5007
|
+
recommendedCiMode: "block",
|
|
5008
|
+
reason: "Demo package has no blocking checks under the current policy."
|
|
5009
|
+
},
|
|
5010
|
+
decision: status === "blocked" ? "Do not merge until breaking frontend contract changes are handled." : "The demo package can continue through review.",
|
|
5011
|
+
blockers: failedChecks.length > 0 ? failedChecks.map((check) => `Failed check: ${check}.`) : [],
|
|
5012
|
+
warnings: impact.summary.warnings > 0 ? [`Impact report contains ${impact.summary.warnings} warnings.`] : [],
|
|
5013
|
+
nextActions: status === "blocked" ? ["Open `impact-pr.md` first.", "Review impacted source usages.", "Open `audit/index.html` for generated output readiness."] : ["Attach this report to the PR or pilot handoff."],
|
|
5014
|
+
reviewerChecklist: [
|
|
5015
|
+
"Confirm `impact-pr.md` gives a clear merge decision.",
|
|
5016
|
+
"Confirm affected generated files map to real frontend ownership.",
|
|
5017
|
+
"Confirm `audit/index.html` matches the expected resource model."
|
|
5018
|
+
]
|
|
5019
|
+
},
|
|
5020
|
+
summary: impact.summary,
|
|
5021
|
+
decision: impact.decision,
|
|
5022
|
+
affectedResources: impact.affectedResources,
|
|
5023
|
+
affectedFiles: impact.affectedFiles,
|
|
5024
|
+
sourceUsages: impact.sourceUsages,
|
|
5025
|
+
migrationHints: impact.migrationHints
|
|
5026
|
+
});
|
|
5027
|
+
}
|
|
5028
|
+
function createDemoHandoff() {
|
|
5029
|
+
return [
|
|
5030
|
+
"# Forge Demo Package",
|
|
5031
|
+
"",
|
|
5032
|
+
"Open `impact-pr.md` first. It is the pull-request comment a reviewer should see before merge.",
|
|
5033
|
+
"",
|
|
5034
|
+
"Then review:",
|
|
5035
|
+
"",
|
|
5036
|
+
"- Open `impact.md` for the full frontend API impact report.",
|
|
5037
|
+
"- Open `check.html` for the reviewer-friendly readiness summary.",
|
|
5038
|
+
"- Open `audit/index.html` for generated output readiness and audit evidence.",
|
|
5039
|
+
"- Open `go-no-go.md` for the short adoption decision.",
|
|
5040
|
+
"",
|
|
5041
|
+
"To run the same package on a real repo:",
|
|
5042
|
+
"",
|
|
5043
|
+
"```sh",
|
|
5044
|
+
"archora-forge pilot ./openapi.yaml --base origin/main --repo . --out forge-pilot",
|
|
5045
|
+
"archora-forge ci init github --schema ./openapi.yaml --base origin/main --gate comment",
|
|
5046
|
+
"```",
|
|
5047
|
+
""
|
|
5048
|
+
].join("\n");
|
|
5049
|
+
}
|
|
4870
5050
|
var demoOldSchema = `openapi: 3.0.3
|
|
4871
5051
|
info: { title: Forge Demo API, version: 1.0.0 }
|
|
4872
5052
|
paths:
|
|
@@ -5573,7 +5753,10 @@ function registerPilotCommand(cli) {
|
|
|
5573
5753
|
schemaHeader: options.schemaHeader
|
|
5574
5754
|
});
|
|
5575
5755
|
const decision = createGoNoGo({ impact, auditOk: audit.payload.ok, auditDecision: audit.payload.readiness.decision });
|
|
5576
|
-
await
|
|
5756
|
+
await Promise.all([
|
|
5757
|
+
writeReportFile(join9(outDir, "go-no-go.md"), decision.markdown),
|
|
5758
|
+
writeReportFile(join9(outDir, "pilot-report.md"), createPilotReport({ impact, status: decision.status, auditOk: audit.payload.ok, auditDecision: audit.payload.readiness.decision }))
|
|
5759
|
+
]);
|
|
5577
5760
|
logger.title();
|
|
5578
5761
|
logger.line(`Pilot package: ${outDir}`);
|
|
5579
5762
|
logger.line(`Decision: ${decision.status}`);
|
|
@@ -5649,6 +5832,61 @@ function createGoNoGo(input) {
|
|
|
5649
5832
|
];
|
|
5650
5833
|
return { status, markdown: lines.join("\n") };
|
|
5651
5834
|
}
|
|
5835
|
+
function createPilotReport(input) {
|
|
5836
|
+
return [
|
|
5837
|
+
"# Archora Forge Pilot Report",
|
|
5838
|
+
"",
|
|
5839
|
+
"## Decision",
|
|
5840
|
+
"",
|
|
5841
|
+
`Decision: ${input.status}`,
|
|
5842
|
+
`Impact: ${input.impact.decision.status}`,
|
|
5843
|
+
`Merge risk: ${input.impact.decision.mergeRisk}`,
|
|
5844
|
+
`Audit: ${input.auditOk ? "passed" : "failed"}`,
|
|
5845
|
+
"",
|
|
5846
|
+
"## Artifact Links",
|
|
5847
|
+
"",
|
|
5848
|
+
"- `impact-pr.md` - PR comment for reviewers.",
|
|
5849
|
+
"- `impact.md` - full frontend API impact report.",
|
|
5850
|
+
"- `impact.json` - machine-readable impact payload.",
|
|
5851
|
+
"- `audit/index.html` - generated output audit and readiness report.",
|
|
5852
|
+
"- `audit/report.md` - Markdown audit handoff.",
|
|
5853
|
+
"- `go-no-go.md` - short adoption decision.",
|
|
5854
|
+
"",
|
|
5855
|
+
"## Impact Summary",
|
|
5856
|
+
"",
|
|
5857
|
+
`- Old schema: ${input.impact.oldSchema}`,
|
|
5858
|
+
`- New schema: ${input.impact.newSchema}`,
|
|
5859
|
+
...input.impact.base ? [`- Base ref: ${input.impact.base}`] : [],
|
|
5860
|
+
`- Breaking changes: ${input.impact.summary.breaking}`,
|
|
5861
|
+
`- Warnings: ${input.impact.summary.warnings}`,
|
|
5862
|
+
`- Non-breaking changes: ${input.impact.summary.nonBreaking}`,
|
|
5863
|
+
`- Source usage matches: ${input.impact.sourceUsages?.length ?? 0}`,
|
|
5864
|
+
"",
|
|
5865
|
+
"## Affected Surface",
|
|
5866
|
+
"",
|
|
5867
|
+
`- Resources: ${formatInlineList(input.impact.affectedResources)}`,
|
|
5868
|
+
`- Generated files: ${formatInlineList(input.impact.affectedFiles)}`,
|
|
5869
|
+
`- Operation IDs: ${formatInlineList(input.impact.impactedSurface.operationIds)}`,
|
|
5870
|
+
`- Client methods: ${formatInlineList(input.impact.impactedSurface.clientMethods)}`,
|
|
5871
|
+
`- Query hooks: ${formatInlineList(input.impact.impactedSurface.queryHooks)}`,
|
|
5872
|
+
"",
|
|
5873
|
+
"## Reviewer Checklist",
|
|
5874
|
+
"",
|
|
5875
|
+
"- Confirm `impact-pr.md` gives a clear merge decision.",
|
|
5876
|
+
"- Confirm `impact.md` names the breaking changes and affected generated files.",
|
|
5877
|
+
"- Confirm `audit/index.html` matches the frontend resource model.",
|
|
5878
|
+
"- Confirm `audit/typecheck.md` passed or every failure is triaged.",
|
|
5879
|
+
"- Confirm `go-no-go.md` matches the team decision before purchase or rollout.",
|
|
5880
|
+
"",
|
|
5881
|
+
"## Next Review Step",
|
|
5882
|
+
"",
|
|
5883
|
+
input.status === "go" ? "Add the generated GitHub workflow, run the first PR with Forge comments enabled, then decide whether to commit generated output." : input.status === "conditional-go" ? "Review warnings with the frontend owner before committing generated output or widening scope." : "Do not roll out Forge on this schema until blockers are fixed or explicitly accepted by the frontend owner.",
|
|
5884
|
+
""
|
|
5885
|
+
].join("\n");
|
|
5886
|
+
}
|
|
5887
|
+
function formatInlineList(values) {
|
|
5888
|
+
return values && values.length > 0 ? values.map((value) => `\`${value}\``).join(", ") : "none";
|
|
5889
|
+
}
|
|
5652
5890
|
|
|
5653
5891
|
// src/commands/inspect.command.ts
|
|
5654
5892
|
function registerInspectCommand(cli) {
|
package/package.json
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@archora/forge-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "CLI for generating typed frontend resource contracts from OpenAPI contracts.",
|
|
5
|
-
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/archora-dev/archora-forge.git"
|
|
9
|
-
},
|
|
10
|
-
"bugs": {
|
|
11
|
-
"url": "https://github.com/archora-dev/archora-forge/issues"
|
|
12
|
-
},
|
|
13
|
-
"homepage": "https://github.com/archora-dev/archora-forge#readme",
|
|
14
|
-
"type": "module",
|
|
15
|
-
"main": "./dist/index.js",
|
|
16
|
-
"types": "./dist/index.d.ts",
|
|
17
|
-
"bin": {
|
|
18
|
-
"archora-forge": "dist/index.js"
|
|
19
|
-
},
|
|
20
|
-
"exports": {
|
|
21
|
-
".": {
|
|
22
|
-
"types": "./dist/index.d.ts",
|
|
23
|
-
"import": "./dist/index.js"
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
"files": [
|
|
27
|
-
"dist"
|
|
28
|
-
],
|
|
29
|
-
"publishConfig": {
|
|
30
|
-
"access": "public"
|
|
31
|
-
},
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@archora/forge-cli",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "CLI for generating typed frontend resource contracts from OpenAPI contracts.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/archora-dev/archora-forge.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/archora-dev/archora-forge/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/archora-dev/archora-forge#readme",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"bin": {
|
|
18
|
+
"archora-forge": "dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"cac": "^6.7.14",
|
|
34
|
+
"jiti": "^2.7.0",
|
|
35
|
+
"picocolors": "^1.1.1",
|
|
36
|
+
"prettier": "^3.5.3",
|
|
37
|
+
"yaml": "^2.8.4"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.15.17",
|
|
41
|
+
"tsup": "^8.4.0",
|
|
42
|
+
"typescript": "^5.8.3",
|
|
43
|
+
"@archora/forge-config": "1.3.0",
|
|
44
|
+
"@archora/forge-core": "1.3.0"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"dev": "tsup --watch",
|
|
49
|
+
"typecheck": "tsc --noEmit"
|
|
50
|
+
}
|
|
51
|
+
}
|