paqad-ai 1.63.2 → 1.63.3
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 +22 -0
- package/dist/cli/index.js +140 -72
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +22 -0
- package/dist/index.js +34 -6
- package/dist/index.js.map +1 -1
- package/dist/kernel/gate.js +33 -7
- package/dist/kernel/gate.js.map +1 -1
- package/dist/rule-scripts/index.js.map +1 -1
- package/dist/stage-evidence/live-writer.js.map +1 -1
- package/dist/stage-evidence/marker-parse.js +12 -6
- package/dist/stage-evidence/marker-parse.js.map +1 -1
- package/dist/stage-evidence/narration.js.map +1 -1
- package/package.json +4 -4
- package/runtime/base/agents/requirement-analyst.md +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# paqad-ai
|
|
2
2
|
|
|
3
|
+
## 1.63.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- eb1fa9a: fix(#401): spec freeze now runs the spec-quality review it always claimed to require
|
|
8
|
+
|
|
9
|
+
`paqad-ai spec freeze` told you, in both the rule pack and the workflow contract, that
|
|
10
|
+
freezing enforces "no critical spec-review defects". It never did: the CLI evaluated the
|
|
11
|
+
freeze with no review attached, so that clause was enforced nowhere. Agents closed the gap by
|
|
12
|
+
hand-running `compliance review`, which left a stray `.paqad/compliance/<slug>/spec-review.json`
|
|
13
|
+
behind that was neither part of the change's evidence bundle nor git-ignored.
|
|
14
|
+
|
|
15
|
+
Freeze now runs the review itself and blocks on a critical defect like any other blocker, so
|
|
16
|
+
there is no second command to run and no stray artifact. On a clean freeze the defect summary
|
|
17
|
+
is folded into the bundle's `specification.json`, so the review evidence travels with the spec
|
|
18
|
+
of record. Non-critical findings never block.
|
|
19
|
+
|
|
20
|
+
Two related fixes: `spec freeze` and `compliance review` now reject a spec file that resolves
|
|
21
|
+
outside the project root and record `spec_file` as a project-relative posix path, and the
|
|
22
|
+
managed `.paqad/.gitignore` covers `compliance/` so a compliance artifact can never surface as
|
|
23
|
+
an untracked, committable file.
|
|
24
|
+
|
|
3
25
|
## 1.63.2
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/dist/cli/index.js
CHANGED
|
@@ -2903,9 +2903,29 @@ var init_feature_spec_schema = __esm({
|
|
|
2903
2903
|
},
|
|
2904
2904
|
frozen: {
|
|
2905
2905
|
oneOf: [{ type: "null" }, { $ref: "#/$defs/frozenMetadata" }]
|
|
2906
|
-
}
|
|
2906
|
+
},
|
|
2907
|
+
spec_review: { $ref: "#/$defs/specReviewSummary" }
|
|
2907
2908
|
},
|
|
2908
2909
|
$defs: {
|
|
2910
|
+
specReviewSummary: {
|
|
2911
|
+
type: "object",
|
|
2912
|
+
additionalProperties: false,
|
|
2913
|
+
required: ["reviewed_at", "defect_count", "by_severity"],
|
|
2914
|
+
properties: {
|
|
2915
|
+
reviewed_at: { type: "string", format: "date-time" },
|
|
2916
|
+
defect_count: { type: "integer", minimum: 0 },
|
|
2917
|
+
by_severity: {
|
|
2918
|
+
type: "object",
|
|
2919
|
+
additionalProperties: false,
|
|
2920
|
+
required: ["critical", "major", "minor"],
|
|
2921
|
+
properties: {
|
|
2922
|
+
critical: { type: "integer", minimum: 0 },
|
|
2923
|
+
major: { type: "integer", minimum: 0 },
|
|
2924
|
+
minor: { type: "integer", minimum: 0 }
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
},
|
|
2909
2929
|
verificationCase: {
|
|
2910
2930
|
type: "object",
|
|
2911
2931
|
additionalProperties: false,
|
|
@@ -8161,6 +8181,7 @@ function defaultFeatureDevelopmentPolicy() {
|
|
|
8161
8181
|
instructions: [
|
|
8162
8182
|
"Write or refine the feature specification before implementation when the lane includes specification.",
|
|
8163
8183
|
"Spec sign-off (issue #102): on graduated/full lanes the spec must carry behaviour, acceptance criteria (AC-n, given/when/then, proof_type), and human-confirmed invariants (INV-n), and must be frozen before development. Freeze requires no open questions, no critical spec-review defects, and a confirmed invariant set. A mid-build goal change or a work-vs-spec contradiction surfaces via the Decision Pause Contract (spec.change / spec.contradiction) and is never resolved silently.",
|
|
8184
|
+
SPEC_REVIEW_ENFORCEMENT_INSTRUCTION,
|
|
8164
8185
|
"Freeze the spec before writing code on graduated/full lanes: run `npx paqad-ai spec freeze <spec-file> --signed-off-by <name> --confirm-invariants` and resolve every printed blocker (missing ACs/invariants, open questions) before development. It writes the frozen spec into the active feature bundle (`specification.json`) that development builds against and the spec-change guard checks for drift."
|
|
8165
8186
|
],
|
|
8166
8187
|
required_inputs: ["approved spec boundary"],
|
|
@@ -8543,6 +8564,7 @@ stages:
|
|
|
8543
8564
|
instructions:
|
|
8544
8565
|
- Write or refine the feature specification before implementation when the lane includes specification.
|
|
8545
8566
|
- "Spec sign-off (issue #102): on graduated/full lanes the spec must carry behaviour, acceptance criteria (AC-n, given/when/then, proof_type), and human-confirmed invariants (INV-n), and must be frozen before development. Freeze requires no open questions, no critical spec-review defects, and a confirmed invariant set. A mid-build goal change or a work-vs-spec contradiction surfaces via the Decision Pause Contract (spec.change / spec.contradiction) and is never resolved silently."
|
|
8567
|
+
- "${SPEC_REVIEW_ENFORCEMENT_INSTRUCTION}"
|
|
8546
8568
|
- "Freeze the spec before writing code on graduated/full lanes: run \`npx paqad-ai spec freeze <spec-file> --signed-off-by <name> --confirm-invariants\` and resolve every printed blocker (missing ACs/invariants, open questions) before development. It writes the frozen spec into the active feature bundle (\`specification.json\`) that development builds against and the spec-change guard checks for drift."
|
|
8547
8569
|
required_inputs:
|
|
8548
8570
|
- approved spec boundary
|
|
@@ -8649,7 +8671,7 @@ stages:
|
|
|
8649
8671
|
- CI gate result
|
|
8650
8672
|
`;
|
|
8651
8673
|
}
|
|
8652
|
-
var STAGE_ORDER, REQUIRED_TRUE_STRICTNESS;
|
|
8674
|
+
var STAGE_ORDER, REQUIRED_TRUE_STRICTNESS, SPEC_REVIEW_ENFORCEMENT_INSTRUCTION;
|
|
8653
8675
|
var init_feature_development_policy = __esm({
|
|
8654
8676
|
"src/pipeline/feature-development-policy.ts"() {
|
|
8655
8677
|
"use strict";
|
|
@@ -8674,6 +8696,7 @@ var init_feature_development_policy = __esm({
|
|
|
8674
8696
|
checks: { block_on_failure: true },
|
|
8675
8697
|
documentation_sync: { require_canonical_sync: true }
|
|
8676
8698
|
};
|
|
8699
|
+
SPEC_REVIEW_ENFORCEMENT_INSTRUCTION = "Spec-review enforcement (issue #401): SCRIPT-ENFORCED. `paqad-ai spec freeze` runs the spec-quality review itself and refuses to freeze while a critical defect is open, so never run `compliance review` by hand to satisfy the clause above. Major and minor findings never block. The defect summary is folded into the bundle's specification.json and no .paqad/compliance/<slug>/spec-review.json is written by this workflow. Author the spec markdown inside the project: a path resolving outside the project root is rejected.";
|
|
8677
8700
|
}
|
|
8678
8701
|
});
|
|
8679
8702
|
|
|
@@ -25660,7 +25683,13 @@ var MANAGED_GITIGNORE_ENTRIES = [
|
|
|
25660
25683
|
"delivery-detection.json",
|
|
25661
25684
|
// regenerated from git history per machine
|
|
25662
25685
|
"# compliance ledger (share via dashboard/SIEM, not git)",
|
|
25663
|
-
"ledger/"
|
|
25686
|
+
"ledger/",
|
|
25687
|
+
// Issue #401 — everything under `compliance/` (the obligation index, the compliance
|
|
25688
|
+
// report, a spec-review report) is derived from the spec plus the test tree and is
|
|
25689
|
+
// regenerable, exactly the `ledger/` rationale. It was NOT ignored, so a report written
|
|
25690
|
+
// there surfaced as an untracked, committable artifact in a consumer project and fed the
|
|
25691
|
+
// "untracked .paqad/ artifact leaks into the changed-file set" class tracked by #205.
|
|
25692
|
+
"compliance/"
|
|
25664
25693
|
];
|
|
25665
25694
|
var MANAGED_GITATTRIBUTES_ENTRIES = ["decisions/index.json merge=union"];
|
|
25666
25695
|
var DEPRECATED_ARTIFACTS = [
|
|
@@ -30883,7 +30912,7 @@ init_cancelled_error();
|
|
|
30883
30912
|
init_events();
|
|
30884
30913
|
|
|
30885
30914
|
// src/index.ts
|
|
30886
|
-
var VERSION = "1.63.
|
|
30915
|
+
var VERSION = "1.63.3";
|
|
30887
30916
|
|
|
30888
30917
|
// src/cli/commands/audit.ts
|
|
30889
30918
|
init_esm_shims();
|
|
@@ -32018,6 +32047,42 @@ init_esm_shims();
|
|
|
32018
32047
|
import { readFile as readFile68, stat as stat7 } from "fs/promises";
|
|
32019
32048
|
import path10 from "path";
|
|
32020
32049
|
import { Command as Command4 } from "commander";
|
|
32050
|
+
|
|
32051
|
+
// src/stage-evidence/artifact-path.ts
|
|
32052
|
+
init_esm_shims();
|
|
32053
|
+
import { realpathSync } from "fs";
|
|
32054
|
+
import { isAbsolute as isAbsolute6, relative as relative24, resolve as resolve7 } from "pathe";
|
|
32055
|
+
var ArtifactOutOfTreeError = class extends Error {
|
|
32056
|
+
constructor(input3) {
|
|
32057
|
+
super(`artifact must be a path inside the project; got ${input3}`);
|
|
32058
|
+
this.input = input3;
|
|
32059
|
+
this.name = "ArtifactOutOfTreeError";
|
|
32060
|
+
}
|
|
32061
|
+
};
|
|
32062
|
+
function realOrLexical(p) {
|
|
32063
|
+
try {
|
|
32064
|
+
return realpathSync.native(p);
|
|
32065
|
+
} catch {
|
|
32066
|
+
return p;
|
|
32067
|
+
}
|
|
32068
|
+
}
|
|
32069
|
+
function isEscape(rel) {
|
|
32070
|
+
return rel === "" || rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || isAbsolute6(rel);
|
|
32071
|
+
}
|
|
32072
|
+
function normalizeArtifactPath(projectRoot, input3) {
|
|
32073
|
+
const rootResolved = resolve7(projectRoot);
|
|
32074
|
+
const rootReal = realOrLexical(rootResolved);
|
|
32075
|
+
const absInput = isAbsolute6(input3) ? realOrLexical(input3) : resolve7(rootReal, input3);
|
|
32076
|
+
for (const root of /* @__PURE__ */ new Set([rootReal, rootResolved])) {
|
|
32077
|
+
const rel = relative24(root, absInput);
|
|
32078
|
+
if (!isEscape(rel)) {
|
|
32079
|
+
return rel.replace(/\\/g, "/");
|
|
32080
|
+
}
|
|
32081
|
+
}
|
|
32082
|
+
throw new ArtifactOutOfTreeError(input3);
|
|
32083
|
+
}
|
|
32084
|
+
|
|
32085
|
+
// src/cli/commands/compliance.ts
|
|
32021
32086
|
function resolveIndexPath2(explicitPath, spec) {
|
|
32022
32087
|
if (explicitPath) return explicitPath;
|
|
32023
32088
|
if (spec) return specIndexPath(spec);
|
|
@@ -32076,8 +32141,17 @@ function createComplianceCommand() {
|
|
|
32076
32141
|
process.exitCode = 0;
|
|
32077
32142
|
});
|
|
32078
32143
|
command.command("review <specFile>").description("Run deterministic spec-quality review and persist the review report").option("--project-root <path>", "Project root", process.cwd()).option("--json", "Print JSON instead of a human-readable summary").action(async (specFile, options) => {
|
|
32079
|
-
|
|
32080
|
-
|
|
32144
|
+
let relativeSpec;
|
|
32145
|
+
try {
|
|
32146
|
+
relativeSpec = normalizeArtifactPath(options.projectRoot, specFile);
|
|
32147
|
+
} catch {
|
|
32148
|
+
console.error(
|
|
32149
|
+
`**\u25B8 paqad** \xB7 a spec has to live inside the project for its review to be portable, and ${specFile} resolves outside it.`
|
|
32150
|
+
);
|
|
32151
|
+
process.exitCode = 1;
|
|
32152
|
+
return;
|
|
32153
|
+
}
|
|
32154
|
+
const specPath = path10.resolve(options.projectRoot, relativeSpec);
|
|
32081
32155
|
const specMarkdown = await readFile68(specPath, "utf8");
|
|
32082
32156
|
const previousReport = await loadSpecReviewReport({
|
|
32083
32157
|
project_root: options.projectRoot,
|
|
@@ -32407,7 +32481,7 @@ function resolveRelativeSpec(projectRoot, spec) {
|
|
|
32407
32481
|
init_esm_shims();
|
|
32408
32482
|
init_paths();
|
|
32409
32483
|
import { existsSync as existsSync105 } from "fs";
|
|
32410
|
-
import { dirname as dirname79, isAbsolute as
|
|
32484
|
+
import { dirname as dirname79, isAbsolute as isAbsolute7, join as join194, resolve as resolve11 } from "path";
|
|
32411
32485
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
32412
32486
|
import { Command as Command5 } from "commander";
|
|
32413
32487
|
|
|
@@ -32415,7 +32489,7 @@ import { Command as Command5 } from "commander";
|
|
|
32415
32489
|
init_esm_shims();
|
|
32416
32490
|
import { createReadStream, existsSync as existsSync104, statSync as statSync13 } from "fs";
|
|
32417
32491
|
import { createServer } from "http";
|
|
32418
|
-
import { extname as extname9, join as join193, normalize as normalize2, resolve as
|
|
32492
|
+
import { extname as extname9, join as join193, normalize as normalize2, resolve as resolve10 } from "path";
|
|
32419
32493
|
import { createGzip, gzipSync as gzipSync2 } from "zlib";
|
|
32420
32494
|
init_paths();
|
|
32421
32495
|
init_logger_registry();
|
|
@@ -32505,7 +32579,7 @@ function buildNodeDetail(graph, nodeId, options) {
|
|
|
32505
32579
|
init_esm_shims();
|
|
32506
32580
|
import { existsSync as existsSync75 } from "fs";
|
|
32507
32581
|
import { readFile as readFile69, readdir as readdir15, stat as stat8 } from "fs/promises";
|
|
32508
|
-
import { extname as extname8, join as join161, relative as
|
|
32582
|
+
import { extname as extname8, join as join161, relative as relative25, sep as sep6 } from "path";
|
|
32509
32583
|
init_paths();
|
|
32510
32584
|
var KNOWN_TIERS = /* @__PURE__ */ new Set(["green", "amber", "red", "unknown"]);
|
|
32511
32585
|
function normaliseTier(value) {
|
|
@@ -32543,7 +32617,7 @@ function languageFromExtension(filePath) {
|
|
|
32543
32617
|
return map[ext] ?? null;
|
|
32544
32618
|
}
|
|
32545
32619
|
function toProjectRelative2(projectRoot, absolutePath) {
|
|
32546
|
-
const rel =
|
|
32620
|
+
const rel = relative25(projectRoot, absolutePath);
|
|
32547
32621
|
return rel === "" ? "." : rel.split(sep6).join("/");
|
|
32548
32622
|
}
|
|
32549
32623
|
function moduleNameForFile(relativePath, knownModules) {
|
|
@@ -33437,11 +33511,11 @@ import {
|
|
|
33437
33511
|
lstatSync as lstatSync2,
|
|
33438
33512
|
mkdirSync as mkdirSync47,
|
|
33439
33513
|
readFileSync as readFileSync76,
|
|
33440
|
-
realpathSync,
|
|
33514
|
+
realpathSync as realpathSync2,
|
|
33441
33515
|
renameSync as renameSync13,
|
|
33442
33516
|
writeFileSync as writeFileSync39
|
|
33443
33517
|
} from "fs";
|
|
33444
|
-
import { dirname as dirname77, join as join166, resolve as
|
|
33518
|
+
import { dirname as dirname77, join as join166, resolve as resolve8, sep as sep7 } from "path";
|
|
33445
33519
|
var WriteConflictError = class extends Error {
|
|
33446
33520
|
currentContent;
|
|
33447
33521
|
currentHash;
|
|
@@ -33490,8 +33564,8 @@ function resolveManagedPath(projectRoot, relativePath) {
|
|
|
33490
33564
|
if (!ALLOWED_EXTENSIONS.some((extension) => posix4.endsWith(extension))) {
|
|
33491
33565
|
throw new PathNotAllowedError(relativePath, "extension is not editable");
|
|
33492
33566
|
}
|
|
33493
|
-
const realRoot = existsSync79(projectRoot) ?
|
|
33494
|
-
const absolute =
|
|
33567
|
+
const realRoot = existsSync79(projectRoot) ? realpathSync2(resolve8(projectRoot)) : resolve8(projectRoot);
|
|
33568
|
+
const absolute = resolve8(realRoot, posix4);
|
|
33495
33569
|
const rootPrefix = `${realRoot}${sep7}`;
|
|
33496
33570
|
if (!absolute.startsWith(rootPrefix)) {
|
|
33497
33571
|
throw new PathNotAllowedError(relativePath, "outside the project root");
|
|
@@ -33502,7 +33576,7 @@ function resolveManagedPath(projectRoot, relativePath) {
|
|
|
33502
33576
|
if (lstatSync2(probe).isSymbolicLink()) {
|
|
33503
33577
|
throw new PathNotAllowedError(relativePath, "symlinks are not editable");
|
|
33504
33578
|
}
|
|
33505
|
-
const real =
|
|
33579
|
+
const real = realpathSync2(probe);
|
|
33506
33580
|
if (real !== probe && !real.startsWith(rootPrefix)) {
|
|
33507
33581
|
throw new PathNotAllowedError(relativePath, "resolves outside the project root");
|
|
33508
33582
|
}
|
|
@@ -35413,7 +35487,7 @@ function removePack2(projectRoot, input3) {
|
|
|
35413
35487
|
init_esm_shims();
|
|
35414
35488
|
init_paths();
|
|
35415
35489
|
import { existsSync as existsSync101 } from "fs";
|
|
35416
|
-
import { join as join190, resolve as
|
|
35490
|
+
import { join as join190, resolve as resolve9 } from "path";
|
|
35417
35491
|
|
|
35418
35492
|
// src/dashboard/collectors/architecture.ts
|
|
35419
35493
|
init_esm_shims();
|
|
@@ -37188,7 +37262,7 @@ function computeOverall(sections) {
|
|
|
37188
37262
|
}
|
|
37189
37263
|
function buildReport(projectRoot, options = {}) {
|
|
37190
37264
|
const now = options.now ?? Date.now();
|
|
37191
|
-
const root =
|
|
37265
|
+
const root = resolve9(projectRoot);
|
|
37192
37266
|
const generatedAt = new Date(now).toISOString();
|
|
37193
37267
|
const paqadDir = join190(root, PATHS.AGENCY_DIR);
|
|
37194
37268
|
const manifestPath = join190(root, PATHS.ONBOARDING_MANIFEST);
|
|
@@ -37491,8 +37565,8 @@ function writeText(res, body, status = 200, contentType = "text/plain; charset=u
|
|
|
37491
37565
|
function serveStatic(req, res, staticDir, urlPath) {
|
|
37492
37566
|
const safePath = normalize2(urlPath).replace(/^[/\\]+/, "");
|
|
37493
37567
|
let filePath = join193(staticDir, safePath);
|
|
37494
|
-
const resolved =
|
|
37495
|
-
if (!resolved.startsWith(
|
|
37568
|
+
const resolved = resolve10(filePath);
|
|
37569
|
+
if (!resolved.startsWith(resolve10(staticDir))) {
|
|
37496
37570
|
writeText(res, "Forbidden", 403);
|
|
37497
37571
|
return;
|
|
37498
37572
|
}
|
|
@@ -38226,12 +38300,12 @@ function openBrowser(options) {
|
|
|
38226
38300
|
var DEFAULT_PORT = 5372;
|
|
38227
38301
|
function resolveStaticDir(override) {
|
|
38228
38302
|
if (override) {
|
|
38229
|
-
return
|
|
38303
|
+
return isAbsolute7(override) ? override : resolve11(process.cwd(), override);
|
|
38230
38304
|
}
|
|
38231
38305
|
const here = dirname79(fileURLToPath3(import.meta.url));
|
|
38232
38306
|
const candidates = [
|
|
38233
|
-
|
|
38234
|
-
|
|
38307
|
+
resolve11(here, "../../runtime/graph-ui"),
|
|
38308
|
+
resolve11(here, "../../../runtime/graph-ui")
|
|
38235
38309
|
];
|
|
38236
38310
|
for (const c of candidates) {
|
|
38237
38311
|
if (existsSync105(join194(c, "index.html"))) return c;
|
|
@@ -38240,7 +38314,7 @@ function resolveStaticDir(override) {
|
|
|
38240
38314
|
}
|
|
38241
38315
|
function createDashboardCommand() {
|
|
38242
38316
|
return new Command5("dashboard").description("Open the paqad-ai project dashboard in a local web view").option("--port <n>", "Server port (auto-increments if occupied)", String(DEFAULT_PORT)).option("--host <host>", "Bind address", "127.0.0.1").option("--no-open", "Do not open the browser automatically").option("--no-watch", "Disable live reload on .paqad/ changes").option("--quiet", "Suppress non-essential stdout", false).option("--read-only", "Disable every mutation endpoint (for shared or CI usage)", false).option("--project-root <path>", "Project root", process.cwd()).option("--static-dir <path>", "Override the bundled frontend directory").action(async (options) => {
|
|
38243
|
-
const projectRoot =
|
|
38317
|
+
const projectRoot = resolve11(options.projectRoot);
|
|
38244
38318
|
const paqadDir = join194(projectRoot, PATHS.AGENCY_DIR);
|
|
38245
38319
|
if (!existsSync105(paqadDir)) {
|
|
38246
38320
|
process.stderr.write(
|
|
@@ -38320,7 +38394,7 @@ Received ${signal}, shutting down\u2026
|
|
|
38320
38394
|
init_esm_shims();
|
|
38321
38395
|
init_paths();
|
|
38322
38396
|
import { existsSync as existsSync106, readFileSync as readFileSync92, readdirSync as readdirSync29 } from "fs";
|
|
38323
|
-
import { join as join195, resolve as
|
|
38397
|
+
import { join as join195, resolve as resolve12 } from "path";
|
|
38324
38398
|
import { Command as Command6 } from "commander";
|
|
38325
38399
|
|
|
38326
38400
|
// src/delivery/ci-gate.ts
|
|
@@ -38770,7 +38844,7 @@ function loadEvidenceBody(projectRoot) {
|
|
|
38770
38844
|
}
|
|
38771
38845
|
function createDeliveryCommand() {
|
|
38772
38846
|
return new Command6("deliver").description("Run the delivery chain: PR (after asking) \u2192 wait for CI \u2192 evidence on green").option("--project-root <path>", "Project root", process.cwd()).option("--dry-run", "Render branch/commit/PR text without pushing", false).option("--base <branch>", "Base branch for the PR", "main").option("--draft", "Open the PR as a draft", false).option("--title <title>", "Change title (branch slug + PR title)", "").option("--summary <summary>", "One-line summary for the commit/PR body", "").option("--ticket <ref>", "Linked ticket ref (e.g. #45 or PQD-123)").option("--ticket-type <type>", "Ticket type (Story/Bug/Task) \u2192 conventional commit type").option("--scope <scope>", "Conventional-commit scope (usually the module)").option("--open-pr <mode>", "Recommended open_pr choice for the pause (yes|draft|no)", "yes").option("--reviewer <handle>", "PR reviewer (repeatable)", collect, []).option("--label <label>", "PR label (repeatable)", collect, []).action(async (options) => {
|
|
38773
|
-
const projectRoot =
|
|
38847
|
+
const projectRoot = resolve12(options.projectRoot);
|
|
38774
38848
|
const shell = createDeliveryShell(projectRoot);
|
|
38775
38849
|
const host = new GithubHostProvider(shell);
|
|
38776
38850
|
const result = await runDeliver({
|
|
@@ -38967,11 +39041,11 @@ function createEnableCommand() {
|
|
|
38967
39041
|
// src/cli/commands/evidence.ts
|
|
38968
39042
|
init_esm_shims();
|
|
38969
39043
|
import { mkdirSync as mkdirSync49, writeFileSync as writeFileSync41 } from "fs";
|
|
38970
|
-
import { dirname as dirname80, resolve as
|
|
39044
|
+
import { dirname as dirname80, resolve as resolve13 } from "path";
|
|
38971
39045
|
import { Command as Command11 } from "commander";
|
|
38972
39046
|
function createEvidenceCommand() {
|
|
38973
39047
|
return new Command11("evidence").description("Render verification evidence as a scannable PR comment (Markdown or JSON)").argument("[sha]", "Commit SHA or label shown in the headline").option("--format <fmt>", "Output format: markdown | json", "markdown").option("--project-root <path>", "Project root", process.cwd()).option("--output <path>", "Write to a file instead of stdout (for gh pr comment --body-file)").option("--fail-on-red", "Exit non-zero when the overall status is fail", false).action((sha, options) => {
|
|
38974
|
-
const projectRoot =
|
|
39048
|
+
const projectRoot = resolve13(options.projectRoot);
|
|
38975
39049
|
const fmt = options.format.toLowerCase();
|
|
38976
39050
|
if (fmt !== "markdown" && fmt !== "json") {
|
|
38977
39051
|
process.stderr.write(
|
|
@@ -38992,7 +39066,7 @@ function createEvidenceCommand() {
|
|
|
38992
39066
|
}
|
|
38993
39067
|
const output = fmt === "json" ? JSON.stringify(evidence, null, 2) : renderEvidenceMarkdown(evidence, sha ? { sha } : {});
|
|
38994
39068
|
if (options.output) {
|
|
38995
|
-
const target =
|
|
39069
|
+
const target = resolve13(options.output);
|
|
38996
39070
|
mkdirSync49(dirname80(target), { recursive: true });
|
|
38997
39071
|
writeFileSync41(target, `${output}
|
|
38998
39072
|
`, "utf8");
|
|
@@ -39104,19 +39178,19 @@ function createFeatureCommand() {
|
|
|
39104
39178
|
init_esm_shims();
|
|
39105
39179
|
init_paths();
|
|
39106
39180
|
import { existsSync as existsSync107 } from "fs";
|
|
39107
|
-
import { dirname as dirname81, isAbsolute as
|
|
39181
|
+
import { dirname as dirname81, isAbsolute as isAbsolute8, join as join196, resolve as resolve14 } from "path";
|
|
39108
39182
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
39109
39183
|
import { Command as Command13 } from "commander";
|
|
39110
39184
|
var DEFAULT_PORT2 = 5372;
|
|
39111
39185
|
var DEPRECATION_LINE = "paqad-ai graph has moved into the dashboard. Opening the dashboard on the Graph view. Run paqad-ai dashboard next time.";
|
|
39112
39186
|
function resolveStaticDir2(override) {
|
|
39113
39187
|
if (override) {
|
|
39114
|
-
return
|
|
39188
|
+
return isAbsolute8(override) ? override : resolve14(process.cwd(), override);
|
|
39115
39189
|
}
|
|
39116
39190
|
const here = dirname81(fileURLToPath4(import.meta.url));
|
|
39117
39191
|
const candidates = [
|
|
39118
|
-
|
|
39119
|
-
|
|
39192
|
+
resolve14(here, "../../runtime/graph-ui"),
|
|
39193
|
+
resolve14(here, "../../../runtime/graph-ui")
|
|
39120
39194
|
];
|
|
39121
39195
|
for (const c of candidates) {
|
|
39122
39196
|
if (existsSync107(join196(c, "index.html"))) return c;
|
|
@@ -39125,7 +39199,7 @@ function resolveStaticDir2(override) {
|
|
|
39125
39199
|
}
|
|
39126
39200
|
function createGraphCommand() {
|
|
39127
39201
|
return new Command13("graph").description("Deprecated: opens the dashboard on the Graph view").option("--port <n>", "Server port (auto-increments if occupied)", String(DEFAULT_PORT2)).option("--host <host>", "Bind address", "127.0.0.1").option("--no-open", "Do not open the browser automatically").option("--no-watch", "Disable live reload on .paqad/ changes").option("--quiet", "Suppress non-essential stdout", false).option("--project-root <path>", "Project root", process.cwd()).option("--static-dir <path>", "Override the bundled frontend directory").action(async (options) => {
|
|
39128
|
-
const projectRoot =
|
|
39202
|
+
const projectRoot = resolve14(options.projectRoot);
|
|
39129
39203
|
const paqadDir = join196(projectRoot, PATHS.AGENCY_DIR);
|
|
39130
39204
|
if (!existsSync107(paqadDir)) {
|
|
39131
39205
|
process.stderr.write(
|
|
@@ -41338,7 +41412,7 @@ init_session();
|
|
|
41338
41412
|
init_esm_shims();
|
|
41339
41413
|
init_stage_ledger();
|
|
41340
41414
|
init_session();
|
|
41341
|
-
import { isAbsolute as
|
|
41415
|
+
import { isAbsolute as isAbsolute9, relative as relative26 } from "pathe";
|
|
41342
41416
|
init_stages();
|
|
41343
41417
|
function stagesWithKind(rows, kind) {
|
|
41344
41418
|
const set = /* @__PURE__ */ new Set();
|
|
@@ -41495,7 +41569,7 @@ init_service();
|
|
|
41495
41569
|
init_esm_shims();
|
|
41496
41570
|
init_project_intelligence();
|
|
41497
41571
|
import { readFileSync as readFileSync102 } from "fs";
|
|
41498
|
-
import { isAbsolute as
|
|
41572
|
+
import { isAbsolute as isAbsolute10, join as join213 } from "path";
|
|
41499
41573
|
import { confirm, input as input2, select as select3 } from "@inquirer/prompts";
|
|
41500
41574
|
import { Command as Command18 } from "commander";
|
|
41501
41575
|
|
|
@@ -42975,7 +43049,7 @@ function createRagCommand() {
|
|
|
42975
43049
|
readFile: (path11) => {
|
|
42976
43050
|
try {
|
|
42977
43051
|
return readFileSync102(
|
|
42978
|
-
|
|
43052
|
+
isAbsolute10(path11) ? path11 : join213(options.projectRoot, path11),
|
|
42979
43053
|
"utf8"
|
|
42980
43054
|
);
|
|
42981
43055
|
} catch {
|
|
@@ -44145,7 +44219,7 @@ init_framework_config();
|
|
|
44145
44219
|
init_esm_shims();
|
|
44146
44220
|
init_path_utils();
|
|
44147
44221
|
init_reconciler();
|
|
44148
|
-
import { isAbsolute as
|
|
44222
|
+
import { isAbsolute as isAbsolute11, relative as relative27 } from "path";
|
|
44149
44223
|
function normalise(path11) {
|
|
44150
44224
|
return toPosixPath(path11).replace(/^\.\//, "").replace(/\/+$/, "");
|
|
44151
44225
|
}
|
|
@@ -44160,7 +44234,7 @@ function resolvePathSensitivity(projectRoot, relPath) {
|
|
|
44160
44234
|
if (!map) {
|
|
44161
44235
|
return "normal";
|
|
44162
44236
|
}
|
|
44163
|
-
const rel =
|
|
44237
|
+
const rel = isAbsolute11(relPath) ? relative27(projectRoot, relPath) : relPath;
|
|
44164
44238
|
const target = normalise(rel);
|
|
44165
44239
|
if (target.length === 0) {
|
|
44166
44240
|
return "normal";
|
|
@@ -44564,7 +44638,20 @@ function freezeSpec(spec, input3) {
|
|
|
44564
44638
|
spec_hash: spec.spec_hash,
|
|
44565
44639
|
signed_off_by: input3.signed_off_by
|
|
44566
44640
|
};
|
|
44567
|
-
|
|
44641
|
+
const specReview = input3.spec_review ? summarizeSpecReview2(input3.spec_review) : void 0;
|
|
44642
|
+
return { ...spec, frozen, ...specReview === void 0 ? {} : { spec_review: specReview } };
|
|
44643
|
+
}
|
|
44644
|
+
function summarizeSpecReview2(report) {
|
|
44645
|
+
const open = report.defects.filter((defect) => defect.status !== "resolved");
|
|
44646
|
+
return {
|
|
44647
|
+
reviewed_at: report.metadata.reviewed_at,
|
|
44648
|
+
defect_count: open.length,
|
|
44649
|
+
by_severity: {
|
|
44650
|
+
critical: open.filter((defect) => defect.severity === "critical").length,
|
|
44651
|
+
major: open.filter((defect) => defect.severity === "major").length,
|
|
44652
|
+
minor: open.filter((defect) => defect.severity === "minor").length
|
|
44653
|
+
}
|
|
44654
|
+
};
|
|
44568
44655
|
}
|
|
44569
44656
|
function isFrozenSpecStale(spec, currentSpecHash) {
|
|
44570
44657
|
return spec.frozen !== null && spec.frozen.spec_hash !== currentSpecHash;
|
|
@@ -44675,34 +44762,6 @@ init_esm_shims();
|
|
|
44675
44762
|
init_stage_ledger();
|
|
44676
44763
|
init_session();
|
|
44677
44764
|
|
|
44678
|
-
// src/stage-evidence/artifact-path.ts
|
|
44679
|
-
init_esm_shims();
|
|
44680
|
-
import { realpathSync as realpathSync2 } from "fs";
|
|
44681
|
-
import { isAbsolute as isAbsolute11, relative as relative27, resolve as resolve14 } from "pathe";
|
|
44682
|
-
var ArtifactOutOfTreeError = class extends Error {
|
|
44683
|
-
constructor(input3) {
|
|
44684
|
-
super(`artifact must be a path inside the project; got ${input3}`);
|
|
44685
|
-
this.input = input3;
|
|
44686
|
-
this.name = "ArtifactOutOfTreeError";
|
|
44687
|
-
}
|
|
44688
|
-
};
|
|
44689
|
-
function realOrLexical(p) {
|
|
44690
|
-
try {
|
|
44691
|
-
return realpathSync2.native(p);
|
|
44692
|
-
} catch {
|
|
44693
|
-
return p;
|
|
44694
|
-
}
|
|
44695
|
-
}
|
|
44696
|
-
function normalizeArtifactPath(projectRoot, input3) {
|
|
44697
|
-
const rootAbs = realOrLexical(resolve14(projectRoot));
|
|
44698
|
-
const absInput = isAbsolute11(input3) ? realOrLexical(input3) : resolve14(rootAbs, input3);
|
|
44699
|
-
const rel = relative27(rootAbs, absInput);
|
|
44700
|
-
if (rel === "" || rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || isAbsolute11(rel)) {
|
|
44701
|
-
throw new ArtifactOutOfTreeError(input3);
|
|
44702
|
-
}
|
|
44703
|
-
return rel.replace(/\\/g, "/");
|
|
44704
|
-
}
|
|
44705
|
-
|
|
44706
44765
|
// src/stage-evidence/bundle-artifact.ts
|
|
44707
44766
|
init_esm_shims();
|
|
44708
44767
|
init_paths2();
|
|
@@ -45827,9 +45886,13 @@ function createSpecCommand() {
|
|
|
45827
45886
|
try {
|
|
45828
45887
|
relSpec = normalizeArtifactPath(options.projectRoot, specFile);
|
|
45829
45888
|
} catch {
|
|
45830
|
-
|
|
45889
|
+
console.error(
|
|
45890
|
+
`**\u25B8 paqad** \xB7 a spec has to live inside the project so its record stays portable, and ${specFile} resolves outside it. Author it in the repo and freeze it from there.`
|
|
45891
|
+
);
|
|
45892
|
+
process.exitCode = 1;
|
|
45893
|
+
return;
|
|
45831
45894
|
}
|
|
45832
|
-
if (
|
|
45895
|
+
if (classifyBundlePath(relSpec) !== null) {
|
|
45833
45896
|
console.error(
|
|
45834
45897
|
`**\u25B8 paqad** \xB7 a feature bundle holds only its rigid artifacts, so a spec can't live at ${relSpec}. Author it outside \`.paqad/ledger/feature-evidence/\` \u2014 the freeze writes specification.json into the bundle for you.`
|
|
45835
45898
|
);
|
|
@@ -45847,11 +45910,15 @@ function createSpecCommand() {
|
|
|
45847
45910
|
const specId = options.specId ?? basename16(specFile).replace(/\.[^.]+$/, "");
|
|
45848
45911
|
const built = buildFeatureSpec({
|
|
45849
45912
|
spec_id: specId,
|
|
45850
|
-
spec_file:
|
|
45913
|
+
spec_file: relSpec,
|
|
45851
45914
|
spec_markdown: markdown
|
|
45852
45915
|
});
|
|
45853
45916
|
const spec = options.confirmInvariants ? { ...built, invariants: built.invariants.map((inv) => ({ ...inv, confirmed: true })) } : built;
|
|
45854
|
-
const
|
|
45917
|
+
const specReview = reviewSpecification({
|
|
45918
|
+
spec_file: relSpec,
|
|
45919
|
+
spec_markdown: markdown
|
|
45920
|
+
});
|
|
45921
|
+
const evaluation = evaluateSpecFreeze(spec, specReview);
|
|
45855
45922
|
if (!evaluation.can_freeze) {
|
|
45856
45923
|
console.error(
|
|
45857
45924
|
`**\u25B8 paqad** \xB7 can't freeze this spec yet \u2014 ${evaluation.blockers.length} blocker${evaluation.blockers.length === 1 ? "" : "s"} to resolve:`
|
|
@@ -45864,7 +45931,8 @@ function createSpecCommand() {
|
|
|
45864
45931
|
}
|
|
45865
45932
|
const frozen = freezeSpec(spec, {
|
|
45866
45933
|
signed_off_by: options.signedOffBy,
|
|
45867
|
-
frozen_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
45934
|
+
frozen_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
45935
|
+
spec_review: specReview
|
|
45868
45936
|
});
|
|
45869
45937
|
let bundlePath = null;
|
|
45870
45938
|
try {
|