truecourse 0.6.0-next.7 → 0.6.0-next.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/README.md +5 -1
- package/cli.mjs +61 -14
- package/package.json +1 -1
- package/public/assets/{index-CkKIAyL2.js → index-BABCLoif.js} +3 -3
- package/public/index.html +1 -1
- package/server.mjs +15 -2
package/README.md
CHANGED
|
@@ -193,6 +193,8 @@ truecourse verify # Check code against the contracts → d
|
|
|
193
193
|
|
|
194
194
|
Resolve conflicts and review drifts visually in the [dashboard](#dashboard-web-ui)'s BL Drift section, or drive every step from the CLI.
|
|
195
195
|
|
|
196
|
+
> Like `analyze`, the spec → contracts → verify track requires a **git repository** — TrueCourse's baselines are commit-anchored (committable `LATEST.json`, diff vs HEAD, stashing the committed state). On a non-git folder these commands stop with a clear message and the dashboard hides their actions.
|
|
197
|
+
|
|
196
198
|
## How it works
|
|
197
199
|
|
|
198
200
|
Three stages run in order, each producing artifacts the next consumes:
|
|
@@ -254,7 +256,8 @@ truecourse spec docs uninclude <path>
|
|
|
254
256
|
|
|
255
257
|
# Contract extraction (canonical spec → .tc artifacts)
|
|
256
258
|
truecourse contracts generate # Extract / re-extract TC contract files
|
|
257
|
-
truecourse contracts list # List
|
|
259
|
+
truecourse contracts list # List artifacts (kind · identity · location)
|
|
260
|
+
truecourse contracts list --inferred / --authored # Only reverse-engineered (_inferred/) / only authored
|
|
258
261
|
truecourse contracts validate # Parse + resolve TC files; report unresolved refs
|
|
259
262
|
|
|
260
263
|
# Verification (code against contracts)
|
|
@@ -268,6 +271,7 @@ truecourse drifts list --offset 20 / --severity critical,high # Page through /
|
|
|
268
271
|
# Inference (code → inferred contracts) — reverse-engineer undocumented decisions
|
|
269
272
|
truecourse infer # Write inferred .tc files to contracts/_inferred/
|
|
270
273
|
truecourse infer --dry-run # Report what would be written, touch nothing
|
|
274
|
+
truecourse contracts list --inferred # Review what infer produced (kind · confidence · code location)
|
|
271
275
|
```
|
|
272
276
|
|
|
273
277
|
---
|
package/cli.mjs
CHANGED
|
@@ -10077,15 +10077,17 @@ async function getGit(repoPath) {
|
|
|
10077
10077
|
const git = simpleGit(repoPath);
|
|
10078
10078
|
const isRepo = await isGitRepo(repoPath);
|
|
10079
10079
|
if (!isRepo) {
|
|
10080
|
-
throw createAppError(
|
|
10080
|
+
throw createAppError(NOT_A_GIT_REPO_MESSAGE, 400);
|
|
10081
10081
|
}
|
|
10082
10082
|
return git;
|
|
10083
10083
|
}
|
|
10084
|
+
var NOT_A_GIT_REPO_MESSAGE;
|
|
10084
10085
|
var init_git = __esm({
|
|
10085
10086
|
"packages/core/dist/lib/git.js"() {
|
|
10086
10087
|
"use strict";
|
|
10087
10088
|
init_esm();
|
|
10088
10089
|
init_errors();
|
|
10090
|
+
NOT_A_GIT_REPO_MESSAGE = "The selected folder is not a git repository. Please select a folder that has been initialized with git.";
|
|
10089
10091
|
}
|
|
10090
10092
|
});
|
|
10091
10093
|
|
|
@@ -128242,7 +128244,7 @@ function readToolVersion() {
|
|
|
128242
128244
|
if (cachedVersion)
|
|
128243
128245
|
return cachedVersion;
|
|
128244
128246
|
if (true) {
|
|
128245
|
-
cachedVersion = "0.6.0-next.
|
|
128247
|
+
cachedVersion = "0.6.0-next.9";
|
|
128246
128248
|
return cachedVersion;
|
|
128247
128249
|
}
|
|
128248
128250
|
try {
|
|
@@ -149108,9 +149110,21 @@ function removeManualInclude(repoRoot6, docPath) {
|
|
|
149108
149110
|
|
|
149109
149111
|
// tools/cli/src/commands/contracts.ts
|
|
149110
149112
|
init_helpers();
|
|
149113
|
+
|
|
149114
|
+
// tools/cli/src/commands/git-guard.ts
|
|
149115
|
+
init_dist4();
|
|
149116
|
+
init_git();
|
|
149117
|
+
async function requireGitRepo(root) {
|
|
149118
|
+
if (await isGitRepo(root)) return;
|
|
149119
|
+
pt(NOT_A_GIT_REPO_MESSAGE);
|
|
149120
|
+
process.exit(1);
|
|
149121
|
+
}
|
|
149122
|
+
|
|
149123
|
+
// tools/cli/src/commands/contracts.ts
|
|
149111
149124
|
async function runContractsGenerate(options = {}) {
|
|
149112
149125
|
const repoRoot6 = options.cwd ?? process.cwd();
|
|
149113
149126
|
mt(options.diff ? "Contracts (dry run)" : "Contracts");
|
|
149127
|
+
await requireGitRepo(repoRoot6);
|
|
149114
149128
|
if (!hasCanonicalSpec(repoRoot6)) {
|
|
149115
149129
|
O2.error(
|
|
149116
149130
|
"No .truecourse/specs/claims.json found. Run `truecourse spec scan` first to build the canonical claim set."
|
|
@@ -149216,23 +149230,51 @@ async function runContractsList(options = {}) {
|
|
|
149216
149230
|
O2.info("No contracts found. Run `truecourse contracts generate` first.");
|
|
149217
149231
|
return;
|
|
149218
149232
|
}
|
|
149219
|
-
const
|
|
149233
|
+
const { parser: parser4, resolver } = await Promise.resolve().then(() => (init_dist8(), dist_exports3));
|
|
149234
|
+
const fileNodes = [];
|
|
149235
|
+
let parseErrors = 0;
|
|
149220
149236
|
const visit = (dir) => {
|
|
149221
149237
|
for (const entry of fs47.readdirSync(dir, { withFileTypes: true })) {
|
|
149222
149238
|
const full = path54.join(dir, entry.name);
|
|
149223
149239
|
if (entry.isDirectory()) visit(full);
|
|
149224
|
-
else if (entry.isFile() && entry.name.endsWith(".tc"))
|
|
149240
|
+
else if (entry.isFile() && entry.name.endsWith(".tc")) {
|
|
149241
|
+
try {
|
|
149242
|
+
fileNodes.push(parser4.parseFile(full, fs47.readFileSync(full, "utf-8")));
|
|
149243
|
+
} catch {
|
|
149244
|
+
parseErrors += 1;
|
|
149245
|
+
}
|
|
149246
|
+
}
|
|
149225
149247
|
}
|
|
149226
149248
|
};
|
|
149227
149249
|
visit(contractsDir);
|
|
149228
|
-
|
|
149229
|
-
|
|
149230
|
-
|
|
149250
|
+
const filter2 = options.inferred && !options.authored ? "inferred" : options.authored && !options.inferred ? "authored" : null;
|
|
149251
|
+
const resolution = resolver.resolve(fileNodes);
|
|
149252
|
+
const artifacts = [...resolution.index.values()].filter((a) => filter2 ? a.provenance === filter2 : true).sort(
|
|
149253
|
+
(a, b) => a.ref.type === b.ref.type ? a.ref.identity.localeCompare(b.ref.identity) : a.ref.type.localeCompare(b.ref.type)
|
|
149254
|
+
);
|
|
149255
|
+
if (artifacts.length === 0) {
|
|
149256
|
+
if (filter2 === "inferred") {
|
|
149257
|
+
O2.info("No inferred contracts. Run `truecourse infer` to reverse-engineer undocumented decisions.");
|
|
149258
|
+
} else if (filter2 === "authored") {
|
|
149259
|
+
O2.info("No authored contracts. Run `truecourse contracts generate` first.");
|
|
149260
|
+
} else {
|
|
149261
|
+
O2.info("No .tc files in .truecourse/contracts/.");
|
|
149262
|
+
}
|
|
149231
149263
|
return;
|
|
149232
149264
|
}
|
|
149233
|
-
|
|
149234
|
-
|
|
149235
|
-
|
|
149265
|
+
const scope = filter2 ? `${filter2} ` : "";
|
|
149266
|
+
mt(`Contracts \u2014 ${artifacts.length} ${scope}artifact${artifacts.length === 1 ? "" : "s"}`);
|
|
149267
|
+
for (const a of artifacts) {
|
|
149268
|
+
const conf = a.confidence ? `[${a.confidence}] ` : "";
|
|
149269
|
+
const loc = a.origin ? a.origin.lines[0] >= 0 ? `${a.origin.source}:${a.origin.lines[0]}` : a.origin.source : `${path54.relative(repoRoot6, a.declarationLoc.filePath)}:${a.declarationLoc.lineStart}`;
|
|
149270
|
+
console.log(` ${conf}${a.ref.type}:${a.ref.identity} ${loc}`);
|
|
149271
|
+
}
|
|
149272
|
+
if (parseErrors > 0) {
|
|
149273
|
+
O2.warn(`${parseErrors} file${parseErrors === 1 ? "" : "s"} could not be parsed \u2014 run \`truecourse contracts validate\`.`);
|
|
149274
|
+
}
|
|
149275
|
+
gt(
|
|
149276
|
+
filter2 ? "`truecourse verify`." : "Filter with `--inferred` / `--authored`. `truecourse contracts validate` then `truecourse verify`."
|
|
149277
|
+
);
|
|
149236
149278
|
}
|
|
149237
149279
|
async function runContractsValidate(options = {}) {
|
|
149238
149280
|
const repoRoot6 = options.cwd ?? process.cwd();
|
|
@@ -149361,6 +149403,7 @@ function withTracker(stepDefs) {
|
|
|
149361
149403
|
async function runSpecScan(opts = {}) {
|
|
149362
149404
|
const root = repoRoot(opts);
|
|
149363
149405
|
mt("Spec scan");
|
|
149406
|
+
await requireGitRepo(root);
|
|
149364
149407
|
const { renderer, tracker } = withTracker(SCAN_STEPS);
|
|
149365
149408
|
try {
|
|
149366
149409
|
const { consolidate: consolidate2 } = await scanInProcess(root, { tracker });
|
|
@@ -149408,6 +149451,7 @@ async function runSpecResolve(opts = {}) {
|
|
|
149408
149451
|
return;
|
|
149409
149452
|
}
|
|
149410
149453
|
mt("Spec resolve \u2014 accepting all defaults");
|
|
149454
|
+
await requireGitRepo(root);
|
|
149411
149455
|
const { renderer, tracker } = withTracker(RESOLVE_STEPS);
|
|
149412
149456
|
try {
|
|
149413
149457
|
const { additions } = await resolveAllDefaultsInProcess(root, { tracker });
|
|
@@ -149456,6 +149500,7 @@ async function runVerify(opts = {}) {
|
|
|
149456
149500
|
const root = repoRoot(opts);
|
|
149457
149501
|
const { skipStash } = await resolveStashDecision({ stash: opts.stash }, root);
|
|
149458
149502
|
mt("Verify");
|
|
149503
|
+
await requireGitRepo(root);
|
|
149459
149504
|
const { renderer, tracker } = withTracker(VERIFY_STEPS);
|
|
149460
149505
|
try {
|
|
149461
149506
|
const { verify: verify2 } = await verifyInProcess(root, { tracker, codeDir: opts.codeDir, skipStash });
|
|
@@ -149494,6 +149539,7 @@ async function runVerify(opts = {}) {
|
|
|
149494
149539
|
async function runVerifyDiff(opts) {
|
|
149495
149540
|
const root = repoRoot(opts);
|
|
149496
149541
|
mt("Verify diff");
|
|
149542
|
+
await requireGitRepo(root);
|
|
149497
149543
|
const { renderer, tracker } = withTracker(VERIFY_STEPS);
|
|
149498
149544
|
try {
|
|
149499
149545
|
const { diff } = await verifyDiffInProcess(root, { tracker, codeDir: opts.codeDir });
|
|
@@ -149524,6 +149570,7 @@ async function runVerifyDiff(opts) {
|
|
|
149524
149570
|
async function runInfer(opts = {}) {
|
|
149525
149571
|
const root = repoRoot(opts);
|
|
149526
149572
|
mt("Infer");
|
|
149573
|
+
await requireGitRepo(root);
|
|
149527
149574
|
const { renderer, tracker } = withTracker(INFER_STEPS);
|
|
149528
149575
|
try {
|
|
149529
149576
|
const { infer: infer2, written, proposed } = await inferInProcess(root, {
|
|
@@ -149549,7 +149596,7 @@ async function runInfer(opts = {}) {
|
|
|
149549
149596
|
}
|
|
149550
149597
|
const wrote = opts.dryRun ? proposed.length : written.length;
|
|
149551
149598
|
gt(
|
|
149552
|
-
infer2.decisions.length === 0 ? "No undocumented decisions found." : opts.dryRun ? `${wrote} inferred contract${wrote === 1 ? "" : "s"} would be written to _inferred/ (dry run).` : `${wrote} inferred contract${wrote === 1 ? "" : "s"} written to _inferred
|
|
149599
|
+
infer2.decisions.length === 0 ? "No undocumented decisions found." : opts.dryRun ? `${wrote} inferred contract${wrote === 1 ? "" : "s"} would be written to _inferred/ (dry run).` : `${wrote} inferred contract${wrote === 1 ? "" : "s"} written to _inferred/ \u2014 review with \`truecourse contracts list --inferred\`.`
|
|
149553
149600
|
);
|
|
149554
149601
|
} catch (e) {
|
|
149555
149602
|
renderer.dispose();
|
|
@@ -152888,7 +152935,7 @@ async function runHooksRun() {
|
|
|
152888
152935
|
|
|
152889
152936
|
// tools/cli/src/index.ts
|
|
152890
152937
|
var program2 = new Command();
|
|
152891
|
-
program2.name("truecourse").version("0.6.0-next.
|
|
152938
|
+
program2.name("truecourse").version("0.6.0-next.9").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
|
|
152892
152939
|
var dashboardCmd = program2.command("dashboard").description("Start the TrueCourse dashboard and open it in your browser").option("--reconfigure", "Re-prompt for console vs background service mode").option("--service", "Run as a background service (skips mode prompt)").option("--console", "Run in this terminal (skips mode prompt)").action(async (options) => {
|
|
152893
152940
|
if (options.service && options.console) {
|
|
152894
152941
|
console.error("error: --service and --console are mutually exclusive");
|
|
@@ -152945,8 +152992,8 @@ var contractsCmd = program2.command("contracts").description("Manage spec-driven
|
|
|
152945
152992
|
contractsCmd.command("generate").description("Extract .tc artifacts from prose specs (LLM, cached)").option("--diff", "Dry run \u2014 show what would change without writing").action(async (options) => {
|
|
152946
152993
|
await runContractsGenerate({ diff: !!options.diff });
|
|
152947
152994
|
});
|
|
152948
|
-
contractsCmd.command("list").description("List the .tc artifacts in this repo").action(async () => {
|
|
152949
|
-
await runContractsList();
|
|
152995
|
+
contractsCmd.command("list").description("List the .tc artifacts in this repo (kind \xB7 identity \xB7 location)").option("--inferred", "Only inferred artifacts (reverse-engineered, in _inferred/)").option("--authored", "Only authored artifacts (exclude _inferred/)").action(async (options) => {
|
|
152996
|
+
await runContractsList({ inferred: !!options.inferred, authored: !!options.authored });
|
|
152950
152997
|
});
|
|
152951
152998
|
contractsCmd.command("validate").description("Parse and resolve all .tc files, report any issues").action(async () => {
|
|
152952
152999
|
await runContractsValidate();
|