paqad-ai 0.1.2 → 0.1.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.
Files changed (39) hide show
  1. package/dist/cli/index.js +471 -122
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/index.d.ts +12 -2
  4. package/dist/index.js +486 -151
  5. package/dist/index.js.map +1 -1
  6. package/package.json +1 -1
  7. package/runtime/capabilities/coding/stacks/flutter/pack.yaml +5 -1
  8. package/runtime/capabilities/coding/stacks/laravel/pack.yaml +15 -1
  9. package/runtime/capabilities/coding/stacks/react/pack.yaml +6 -2
  10. package/runtime/capabilities/coding/stacks/vue/pack.yaml +6 -2
  11. package/runtime/capabilities/security/rules/pentest.md +109 -9
  12. package/runtime/capabilities/security/skills/auth-mechanism-review/SKILL.md +88 -0
  13. package/runtime/capabilities/security/skills/auth-mechanism-review/agents/openai.yaml +3 -0
  14. package/runtime/capabilities/security/skills/auth-mechanism-review/references/auth-attack-checklist.md +84 -0
  15. package/runtime/capabilities/security/skills/business-logic-abuse-review/references/abuse-cases.md +33 -5
  16. package/runtime/capabilities/security/skills/cryptographic-review/SKILL.md +72 -0
  17. package/runtime/capabilities/security/skills/cryptographic-review/agents/openai.yaml +3 -0
  18. package/runtime/capabilities/security/skills/cryptographic-review/references/crypto-weakness-patterns.md +156 -0
  19. package/runtime/capabilities/security/skills/dependency-advisory-triage/references/advisory-normalization.md +33 -1
  20. package/runtime/capabilities/security/skills/input-validation-review/SKILL.md +75 -0
  21. package/runtime/capabilities/security/skills/input-validation-review/agents/openai.yaml +3 -0
  22. package/runtime/capabilities/security/skills/input-validation-review/references/input-attack-patterns.md +74 -0
  23. package/runtime/capabilities/security/skills/logging-monitoring-review/SKILL.md +72 -0
  24. package/runtime/capabilities/security/skills/logging-monitoring-review/agents/openai.yaml +3 -0
  25. package/runtime/capabilities/security/skills/logging-monitoring-review/references/logging-gaps-checklist.md +77 -0
  26. package/runtime/capabilities/security/skills/permission-boundary-review/references/boundary-checklist.md +31 -1
  27. package/runtime/capabilities/security/skills/rate-limiting-review/SKILL.md +67 -0
  28. package/runtime/capabilities/security/skills/rate-limiting-review/agents/openai.yaml +3 -0
  29. package/runtime/capabilities/security/skills/rate-limiting-review/references/rate-limit-signals.md +153 -0
  30. package/runtime/capabilities/security/skills/runtime-surface-probing/references/runtime-surface-checks.md +57 -3
  31. package/runtime/capabilities/security/skills/stride-threat-model/SKILL.md +60 -0
  32. package/runtime/capabilities/security/skills/stride-threat-model/agents/openai.yaml +3 -0
  33. package/runtime/capabilities/security/skills/stride-threat-model/references/stride-checklist.md +69 -0
  34. package/runtime/hooks/silent-update.sh +115 -0
  35. package/runtime/templates/agent-configs/agents.md.hbs +8 -0
  36. package/runtime/templates/agent-configs/claude.md.hbs +8 -0
  37. package/runtime/templates/agent-configs/gemini.md.hbs +8 -0
  38. package/runtime/templates/agent-configs/junie.md.hbs +8 -0
  39. package/scripts/deprecate-old-versions.sh +55 -0
package/dist/cli/index.js CHANGED
@@ -96,7 +96,12 @@ var PATHS = {
96
96
  CLAUDE_MD: "CLAUDE.md",
97
97
  AGENTS_MD: "AGENTS.md",
98
98
  GEMINI_MD: "GEMINI.md",
99
- SCRIPTS_DIR: "scripts"
99
+ SCRIPTS_DIR: "scripts",
100
+ HOOKS_DIR: ".paqad/hooks",
101
+ LOGS_DIR: ".paqad/logs",
102
+ LOCKS_DIR: ".paqad/locks",
103
+ AUTO_UPDATE_LOG: ".paqad/logs/auto-update.log",
104
+ HOOKS_SILENT_UPDATE: ".paqad/hooks/silent-update.sh"
100
105
  };
101
106
  var REGISTRIES = [
102
107
  "module-registry.md",
@@ -4137,8 +4142,10 @@ function writeFrameworkMetadata(projectRoot, version) {
4137
4142
  mkdirSync2(dirname11(join21(projectRoot, PATHS.FRAMEWORK_VERSION)), {
4138
4143
  recursive: true
4139
4144
  });
4140
- writeFileSync2(join21(projectRoot, PATHS.FRAMEWORK_VERSION), `${version}
4141
- `);
4145
+ const content = `version=${version}
4146
+ updated_at=${(/* @__PURE__ */ new Date()).toISOString()}
4147
+ `;
4148
+ writeFileSync2(join21(projectRoot, PATHS.FRAMEWORK_VERSION), content);
4142
4149
  writeFileSync2(join21(projectRoot, PATHS.FRAMEWORK_PATH), `${resolveFrameworkInstallPath()}
4143
4150
  `);
4144
4151
  }
@@ -5518,6 +5525,10 @@ function writeGeneratedFiles(projectRoot, files) {
5518
5525
  return { written, skipped };
5519
5526
  }
5520
5527
 
5528
+ // src/onboarding/orchestrator.ts
5529
+ import { readFileSync as readFileSync12 } from "fs";
5530
+ import { join as join33 } from "path";
5531
+
5521
5532
  // src/resolver/resolver.ts
5522
5533
  import fg2 from "fast-glob";
5523
5534
  import { basename as basename3, extname, relative as relative3 } from "pathe";
@@ -6393,6 +6404,17 @@ var OnboardingOrchestrator = class {
6393
6404
  stack_profile: selections.stack_profile
6394
6405
  })
6395
6406
  );
6407
+ const silentUpdateSrc = join33(runtimeRoot, "..", "hooks", "silent-update.sh");
6408
+ try {
6409
+ const hookContent = readFileSync12(silentUpdateSrc, "utf8");
6410
+ generatedFiles.push({
6411
+ path: PATHS.HOOKS_SILENT_UPDATE,
6412
+ content: hookContent,
6413
+ autoUpdate: true,
6414
+ executable: true
6415
+ });
6416
+ } catch {
6417
+ }
6396
6418
  const writeResult = writeGeneratedFiles(options.projectRoot, generatedFiles);
6397
6419
  const drift = await writeStackArtifacts(
6398
6420
  options.projectRoot,
@@ -6403,6 +6425,7 @@ var OnboardingOrchestrator = class {
6403
6425
  writeProjectProfile2(options.projectRoot, profile);
6404
6426
  writeGitignore(options.projectRoot);
6405
6427
  writeDetectionReport(options.projectRoot, detection);
6428
+ writeFrameworkMetadata(options.projectRoot, VERSION);
6406
6429
  bootstrapFramework(options.projectRoot);
6407
6430
  const manifestPath = writeOnboardingManifest(options.projectRoot, {
6408
6431
  framework_version: VERSION,
@@ -6676,7 +6699,7 @@ function buildRustCommands(usingCompose) {
6676
6699
  }
6677
6700
 
6678
6701
  // src/onboarding/scaffold-generator.ts
6679
- import { join as join33 } from "path";
6702
+ import { join as join34 } from "path";
6680
6703
 
6681
6704
  // src/templates/registry.ts
6682
6705
  import fg4 from "fast-glob";
@@ -6684,8 +6707,8 @@ import { basename as basename4, relative as relative5 } from "pathe";
6684
6707
 
6685
6708
  // src/onboarding/scaffold-generator.ts
6686
6709
  var FEATURE_TEMPLATE_TARGETS = [
6687
- ["business.md.hbs", join33(PATHS.MODULE_FEATURES_DIR, "core", "business.md")],
6688
- ["technical.md.hbs", join33(PATHS.MODULE_FEATURES_DIR, "core", "technical.md")]
6710
+ ["business.md.hbs", join34(PATHS.MODULE_FEATURES_DIR, "core", "business.md")],
6711
+ ["technical.md.hbs", join34(PATHS.MODULE_FEATURES_DIR, "core", "technical.md")]
6689
6712
  ];
6690
6713
 
6691
6714
  // src/packs/manager.ts
@@ -6699,14 +6722,14 @@ import {
6699
6722
  writeFileSync as writeFileSync5
6700
6723
  } from "fs";
6701
6724
  import { homedir as homedir2 } from "os";
6702
- import { join as join34, resolve as resolve2 } from "path";
6725
+ import { join as join35, resolve as resolve2 } from "path";
6703
6726
  import { execa } from "execa";
6704
6727
  var SOURCE_ORDER2 = ["built-in", "global", "project"];
6705
6728
  function resolvePackManagerRoots(projectRoot = process.cwd(), overrides = {}) {
6706
6729
  return {
6707
6730
  runtimeRoot: overrides.runtimeRoot ?? getRuntimeRoot(),
6708
- globalPacksRoot: overrides.globalPacksRoot ?? process.env.PAQAD_GLOBAL_PACKS_ROOT ?? join34(homedir2(), ".paqad", "packs"),
6709
- projectPacksRoot: overrides.projectPacksRoot ?? join34(projectRoot, ".paqad", "packs"),
6731
+ globalPacksRoot: overrides.globalPacksRoot ?? process.env.PAQAD_GLOBAL_PACKS_ROOT ?? join35(homedir2(), ".paqad", "packs"),
6732
+ projectPacksRoot: overrides.projectPacksRoot ?? join35(projectRoot, ".paqad", "packs"),
6710
6733
  registryUrl: overrides.registryUrl ?? process.env.PAQAD_PACK_REGISTRY_URL
6711
6734
  };
6712
6735
  }
@@ -6754,7 +6777,7 @@ async function installPack(source, options = {}) {
6754
6777
  if (!pack.validation.valid) {
6755
6778
  throw new Error(formatValidationIssues(pack.validation.issues));
6756
6779
  }
6757
- const destination = join34(installRoot, pack.manifest.name);
6780
+ const destination = join35(installRoot, pack.manifest.name);
6758
6781
  rmSync2(destination, { recursive: true, force: true });
6759
6782
  cpSync(candidateRoot, destination, { recursive: true });
6760
6783
  const installed = loader.validatePack(destination, scope === "project" ? "project" : "global");
@@ -6766,12 +6789,12 @@ async function installPack(source, options = {}) {
6766
6789
  function removePack(name, projectRoot = process.cwd(), scope = "global", overrides = {}) {
6767
6790
  const roots = resolvePackManagerRoots(projectRoot, overrides);
6768
6791
  const targetRoot = scope === "project" ? roots.projectPacksRoot : roots.globalPacksRoot;
6769
- const target = join34(targetRoot, name);
6792
+ const target = join35(targetRoot, name);
6770
6793
  if (existsSync15(target)) {
6771
6794
  rmSync2(target, { recursive: true, force: true });
6772
6795
  return;
6773
6796
  }
6774
- const builtInRoot = join34(roots.runtimeRoot, "capabilities", "coding", "stacks", name);
6797
+ const builtInRoot = join35(roots.runtimeRoot, "capabilities", "coding", "stacks", name);
6775
6798
  if (existsSync15(builtInRoot)) {
6776
6799
  throw new Error(
6777
6800
  `Cannot remove built-in pack "${name}"; remove a global or project override instead`
@@ -6789,14 +6812,14 @@ function validatePackAt(path) {
6789
6812
  function createPack(name, options = {}) {
6790
6813
  const destinationRoot = options.destinationRoot ?? process.cwd();
6791
6814
  const ecosystem = options.ecosystem ?? "node";
6792
- const packRoot = join34(destinationRoot, name);
6815
+ const packRoot = join35(destinationRoot, name);
6793
6816
  if (existsSync15(packRoot)) {
6794
6817
  throw new Error(`Pack scaffold already exists at ${packRoot}`);
6795
6818
  }
6796
- mkdirSync5(join34(packRoot, "rules"), { recursive: true });
6797
- writeFileSync5(join34(packRoot, "pack.yaml"), renderPackTemplate(name, ecosystem));
6819
+ mkdirSync5(join35(packRoot, "rules"), { recursive: true });
6820
+ writeFileSync5(join35(packRoot, "pack.yaml"), renderPackTemplate(name, ecosystem));
6798
6821
  writeFileSync5(
6799
- join34(packRoot, "rules", "conventions.md"),
6822
+ join35(packRoot, "rules", "conventions.md"),
6800
6823
  `# ${name}
6801
6824
 
6802
6825
  Document project-specific conventions for the ${name} stack here.
@@ -6813,7 +6836,7 @@ function listPackNamesForSource(source, roots) {
6813
6836
  }
6814
6837
  function resolveSourceRoot(source, roots) {
6815
6838
  if (source === "built-in") {
6816
- return join34(roots.runtimeRoot, "capabilities", "coding", "stacks");
6839
+ return join35(roots.runtimeRoot, "capabilities", "coding", "stacks");
6817
6840
  }
6818
6841
  return source === "global" ? roots.globalPacksRoot : roots.projectPacksRoot;
6819
6842
  }
@@ -6839,7 +6862,7 @@ function looksLikeGitUrl(source) {
6839
6862
  return source.startsWith("http://") || source.startsWith("https://") || source.startsWith("ssh://") || source.startsWith("git@") || source.startsWith("file://") || source.endsWith(".git");
6840
6863
  }
6841
6864
  async function clonePackSource(source) {
6842
- const tempRoot = mkdtempSync(join34(homedir2(), ".paqad-pack-clone-"));
6865
+ const tempRoot = mkdtempSync(join35(homedir2(), ".paqad-pack-clone-"));
6843
6866
  try {
6844
6867
  await execa("git", ["clone", "--depth", "1", source, tempRoot]);
6845
6868
  } catch (error) {
@@ -6849,11 +6872,11 @@ async function clonePackSource(source) {
6849
6872
  return findPackRoot(tempRoot);
6850
6873
  }
6851
6874
  function findPackRoot(root) {
6852
- const rootManifest = join34(root, "pack.yaml");
6875
+ const rootManifest = join35(root, "pack.yaml");
6853
6876
  if (existsSync15(rootManifest)) {
6854
6877
  return root;
6855
6878
  }
6856
- const candidates = readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join34(root, entry.name)).filter((candidate) => existsSync15(join34(candidate, "pack.yaml")));
6879
+ const candidates = readdirSync3(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join35(root, entry.name)).filter((candidate) => existsSync15(join35(candidate, "pack.yaml")));
6857
6880
  if (candidates.length === 1) {
6858
6881
  return candidates[0];
6859
6882
  }
@@ -6954,13 +6977,13 @@ async function queryOsv(packages) {
6954
6977
  // src/pentest/progress-tracker.ts
6955
6978
  import { existsSync as existsSync17 } from "fs";
6956
6979
  import { mkdir as mkdir12, readdir as readdir4, readFile as readFile16, writeFile as writeFile12 } from "fs/promises";
6957
- import { dirname as dirname17, join as join36 } from "path";
6980
+ import { dirname as dirname17, join as join37 } from "path";
6958
6981
 
6959
6982
  // src/pentest/shared.ts
6960
6983
  import { createHash as createHash7 } from "crypto";
6961
6984
  import { existsSync as existsSync16 } from "fs";
6962
6985
  import { mkdir as mkdir11, readFile as readFile15, readdir as readdir3, writeFile as writeFile11 } from "fs/promises";
6963
- import { basename as basename5, dirname as dirname16, join as join35, relative as relative6 } from "path";
6986
+ import { basename as basename5, dirname as dirname16, join as join36, relative as relative6 } from "path";
6964
6987
  import { execa as execa2 } from "execa";
6965
6988
  import fg5 from "fast-glob";
6966
6989
  function toLocalTimestamp(date) {
@@ -7024,7 +7047,7 @@ async function discoverTargetUrl(projectRoot, stack, explicit) {
7024
7047
  }
7025
7048
  const envFiles = [".env", ".env.local", ".env.example"];
7026
7049
  for (const envFile of envFiles) {
7027
- const path = join35(projectRoot, envFile);
7050
+ const path = join36(projectRoot, envFile);
7028
7051
  if (!existsSync16(path)) {
7029
7052
  continue;
7030
7053
  }
@@ -7046,9 +7069,9 @@ async function discoverTargetUrl(projectRoot, stack, explicit) {
7046
7069
  return null;
7047
7070
  }
7048
7071
  async function runProjectScript(projectRoot, scriptName, env, logDir) {
7049
- const scriptPath = join35(projectRoot, PATHS.SCRIPTS_DIR, scriptName);
7050
- const stdoutPath = join35(logDir, `${scriptName}.stdout.log`);
7051
- const stderrPath = join35(logDir, `${scriptName}.stderr.log`);
7072
+ const scriptPath = join36(projectRoot, PATHS.SCRIPTS_DIR, scriptName);
7073
+ const stdoutPath = join36(logDir, `${scriptName}.stdout.log`);
7074
+ const stderrPath = join36(logDir, `${scriptName}.stderr.log`);
7052
7075
  await mkdir11(logDir, { recursive: true });
7053
7076
  if (!existsSync16(scriptPath)) {
7054
7077
  await writeFile11(stdoutPath, "");
@@ -7078,7 +7101,7 @@ async function runProjectScript(projectRoot, scriptName, env, logDir) {
7078
7101
  };
7079
7102
  }
7080
7103
  async function loadModuleDocs(projectRoot, focusModules = []) {
7081
- const moduleRoot = join35(projectRoot, PATHS.MODULES_DIR);
7104
+ const moduleRoot = join36(projectRoot, PATHS.MODULES_DIR);
7082
7105
  if (!existsSync16(moduleRoot)) {
7083
7106
  return [];
7084
7107
  }
@@ -7147,7 +7170,7 @@ function inferSourceArtifacts(baseDir, scriptResults) {
7147
7170
  return [
7148
7171
  ...new Set(
7149
7172
  artifacts.map(
7150
- (artifact) => artifact.startsWith(".") ? artifact : relative6(dirname16(baseDir), join35(dirname16(baseDir), artifact))
7173
+ (artifact) => artifact.startsWith(".") ? artifact : relative6(dirname16(baseDir), join36(dirname16(baseDir), artifact))
7151
7174
  )
7152
7175
  )
7153
7176
  ];
@@ -7221,7 +7244,7 @@ var PentestProgressTracker = class {
7221
7244
  };
7222
7245
  }
7223
7246
  async load(projectRoot, runId) {
7224
- const target = join36(projectRoot, PATHS.PENTEST_RUNS_DIR, runId, "progress.json");
7247
+ const target = join37(projectRoot, PATHS.PENTEST_RUNS_DIR, runId, "progress.json");
7225
7248
  if (!existsSync17(target)) {
7226
7249
  return null;
7227
7250
  }
@@ -7233,7 +7256,7 @@ var PentestProgressTracker = class {
7233
7256
  return parsed;
7234
7257
  }
7235
7258
  async save(projectRoot, progress) {
7236
- const target = join36(projectRoot, PATHS.PENTEST_RUNS_DIR, progress.run_id, "progress.json");
7259
+ const target = join37(projectRoot, PATHS.PENTEST_RUNS_DIR, progress.run_id, "progress.json");
7237
7260
  progress.updated_at = (/* @__PURE__ */ new Date()).toISOString();
7238
7261
  await mkdir12(dirname17(target), { recursive: true });
7239
7262
  await writeFile12(target, `${JSON.stringify(progress, null, 2)}
@@ -7292,7 +7315,7 @@ var PentestProgressTracker = class {
7292
7315
  return step.status === "completed" && step.input_hash === inputHash;
7293
7316
  }
7294
7317
  async findIncompleteRun(projectRoot, workflow, sourceReportPath) {
7295
- const runsDir = join36(projectRoot, PATHS.PENTEST_RUNS_DIR);
7318
+ const runsDir = join37(projectRoot, PATHS.PENTEST_RUNS_DIR);
7296
7319
  if (!existsSync17(runsDir)) {
7297
7320
  return null;
7298
7321
  }
@@ -7335,15 +7358,15 @@ var PentestProgressTracker = class {
7335
7358
  }
7336
7359
  };
7337
7360
  function runArtifactsDir(projectRoot, runId) {
7338
- return join36(projectRoot, PATHS.PENTEST_RUNS_DIR, runId, "artifacts");
7361
+ return join37(projectRoot, PATHS.PENTEST_RUNS_DIR, runId, "artifacts");
7339
7362
  }
7340
7363
  function runLogsDir(projectRoot, runId) {
7341
- return join36(projectRoot, PATHS.PENTEST_RUNS_DIR, runId, "logs");
7364
+ return join37(projectRoot, PATHS.PENTEST_RUNS_DIR, runId, "logs");
7342
7365
  }
7343
7366
 
7344
7367
  // src/pipeline/lane-runner.ts
7345
7368
  import { mkdir as mkdir16, writeFile as writeFile16 } from "fs/promises";
7346
- import { dirname as dirname20, join as join42 } from "path";
7369
+ import { dirname as dirname20, join as join43 } from "path";
7347
7370
 
7348
7371
  // src/pipeline/phases/shared.ts
7349
7372
  function createPassResult(phase, summary, context, artifacts = [`handoff:${context.phases.length + 1}`]) {
@@ -7463,12 +7486,12 @@ var LoadDocsPhase = class {
7463
7486
 
7464
7487
  // src/workflows/pentest.ts
7465
7488
  import { mkdir as mkdir13, writeFile as writeFile13 } from "fs/promises";
7466
- import { join as join39, relative as relative7 } from "path";
7489
+ import { join as join40, relative as relative7 } from "path";
7467
7490
 
7468
7491
  // src/pentest/findings.ts
7469
7492
  import { existsSync as existsSync18 } from "fs";
7470
7493
  import { readFile as readFile17 } from "fs/promises";
7471
- import { join as join37 } from "path";
7494
+ import { join as join38 } from "path";
7472
7495
  function createFinding(input) {
7473
7496
  return {
7474
7497
  id: "",
@@ -7638,11 +7661,264 @@ function buildModuleFindings(docs, tests) {
7638
7661
  );
7639
7662
  }
7640
7663
  }
7664
+ const authMechanismTests = extractRelevantTestPaths(tests, moduleDoc.module, [
7665
+ "algorithm",
7666
+ "expir",
7667
+ "signature",
7668
+ "refresh",
7669
+ "revok",
7670
+ "logout",
7671
+ "session.invalidat",
7672
+ "alg:none",
7673
+ "jwt"
7674
+ ]);
7675
+ if (hasAtLeast(lowered, 2, [
7676
+ "jwt",
7677
+ "token",
7678
+ "bearer",
7679
+ "session",
7680
+ "cookie",
7681
+ "oauth",
7682
+ "saml",
7683
+ "oidc",
7684
+ "sanctum"
7685
+ ])) {
7686
+ if (authMechanismTests.length === 0) {
7687
+ findings.push(
7688
+ createFinding({
7689
+ title: `Auth token lifecycle checks are thin for ${moduleDoc.module}`,
7690
+ description: "Module docs describe authentication tokens, sessions, or OAuth flows, but the current tests do not show evidence of token expiry, signature validation, session invalidation, or logout handling.",
7691
+ impact: "high",
7692
+ effort: "medium",
7693
+ possible_solution_direction: "Add tests for token expiry enforcement, alg:none rejection, session invalidation on logout, and refresh token rotation for the documented auth flows.",
7694
+ how_to_reproduce: [
7695
+ `Review ${moduleDoc.paths.join(", ")} for token or session handling.`,
7696
+ "Search tests for algorithm, expiry, signature, revocation, and logout coverage for that module."
7697
+ ],
7698
+ impact_area: [...moduleDoc.paths, `module:${moduleDoc.module}`],
7699
+ evidence: [
7700
+ ...moduleDoc.paths.map((path) => `Doc evidence: ${path}`),
7701
+ "No matching auth token lifecycle test evidence found for this module."
7702
+ ],
7703
+ category: "auth-mechanism",
7704
+ status: "open",
7705
+ confidence: 0.71,
7706
+ source_types: ["docs", "tests"],
7707
+ affected_modules: [moduleDoc.module],
7708
+ affected_packages: [],
7709
+ runtime_required: false,
7710
+ manual_follow_up: true
7711
+ })
7712
+ );
7713
+ }
7714
+ }
7715
+ const ssrfTests = extractRelevantTestPaths(tests, moduleDoc.module, [
7716
+ "ssrf",
7717
+ "redirect",
7718
+ "allowlist",
7719
+ "block",
7720
+ "internal",
7721
+ "private",
7722
+ "localhost",
7723
+ "metadata"
7724
+ ]);
7725
+ if (hasAtLeast(lowered, 2, [
7726
+ "url",
7727
+ "redirect",
7728
+ "callback",
7729
+ "webhook",
7730
+ "fetch",
7731
+ "http",
7732
+ "proxy",
7733
+ "curl",
7734
+ "axios"
7735
+ ])) {
7736
+ if (ssrfTests.length === 0) {
7737
+ findings.push(
7738
+ createFinding({
7739
+ title: `Outbound request / redirect surface lacks abuse validation for ${moduleDoc.module}`,
7740
+ description: "Module docs describe outbound HTTP calls, URL parameters, webhooks, or redirect flows, but the current tests do not show evidence of SSRF prevention, redirect allowlist validation, or private-range blocking.",
7741
+ impact: "high",
7742
+ effort: "medium",
7743
+ possible_solution_direction: "Add tests that verify SSRF prevention (blocked private ranges, redirect chain validation) and open-redirect protection for the documented outbound request surfaces.",
7744
+ how_to_reproduce: [
7745
+ `Review ${moduleDoc.paths.join(", ")} for URL, redirect, or webhook parameter handling.`,
7746
+ "Test with SSRF payloads: 127.0.0.1, 169.254.169.254, [::1], redirect chain to internal IP."
7747
+ ],
7748
+ impact_area: [...moduleDoc.paths, `module:${moduleDoc.module}`],
7749
+ evidence: [
7750
+ ...moduleDoc.paths.map((path) => `Doc evidence: ${path}`),
7751
+ "No matching SSRF or redirect validation test evidence found for this module."
7752
+ ],
7753
+ category: "input-validation",
7754
+ status: "open",
7755
+ confidence: 0.7,
7756
+ source_types: ["docs", "tests"],
7757
+ affected_modules: [moduleDoc.module],
7758
+ affected_packages: [],
7759
+ runtime_required: false,
7760
+ manual_follow_up: true
7761
+ })
7762
+ );
7763
+ }
7764
+ }
7765
+ const massAssignTests = extractRelevantTestPaths(tests, moduleDoc.module, [
7766
+ "mass",
7767
+ "assign",
7768
+ "fillable",
7769
+ "guarded",
7770
+ "allowlist",
7771
+ "forbidden",
7772
+ "attr_protected",
7773
+ "protected"
7774
+ ]);
7775
+ if (hasAtLeast(lowered, 2, [
7776
+ "fill",
7777
+ "create",
7778
+ "update",
7779
+ "patch",
7780
+ "assign",
7781
+ "body",
7782
+ "request",
7783
+ "fillable",
7784
+ "guarded"
7785
+ ])) {
7786
+ if (massAssignTests.length === 0) {
7787
+ findings.push(
7788
+ createFinding({
7789
+ title: `Mass assignment protection evidence is absent for ${moduleDoc.module}`,
7790
+ description: "Module docs describe model creation or update flows accepting request body fields, but the current tests do not show evidence of mass assignment protection (fillable allowlist, guarded fields, or forbidden-field rejection).",
7791
+ impact: "medium",
7792
+ effort: "low",
7793
+ possible_solution_direction: "Verify the model uses an explicit $fillable allowlist or $guarded blocklist. Add tests that confirm unintended fields (is_admin, role, price, balance) are rejected when submitted in the request body.",
7794
+ how_to_reproduce: [
7795
+ `Review ${moduleDoc.paths.join(", ")} for model create/update operations.`,
7796
+ "Submit a PATCH/POST request with extra privileged fields: is_admin=true, role=admin, price=0."
7797
+ ],
7798
+ impact_area: [...moduleDoc.paths, `module:${moduleDoc.module}`],
7799
+ evidence: [
7800
+ ...moduleDoc.paths.map((path) => `Doc evidence: ${path}`),
7801
+ "No matching mass assignment protection test evidence found for this module."
7802
+ ],
7803
+ category: "input-validation",
7804
+ status: "open",
7805
+ confidence: 0.68,
7806
+ source_types: ["docs", "tests"],
7807
+ affected_modules: [moduleDoc.module],
7808
+ affected_packages: [],
7809
+ runtime_required: false,
7810
+ manual_follow_up: false
7811
+ })
7812
+ );
7813
+ }
7814
+ }
7815
+ const cryptoTests = extractRelevantTestPaths(tests, moduleDoc.module, [
7816
+ "crypto",
7817
+ "encrypt",
7818
+ "hash.strength",
7819
+ "key.rotation",
7820
+ "salt",
7821
+ "entropy",
7822
+ "randomBytes",
7823
+ "bcrypt",
7824
+ "argon"
7825
+ ]);
7826
+ if (hasAtLeast(lowered, 2, [
7827
+ "encrypt",
7828
+ "decrypt",
7829
+ "hash",
7830
+ "md5",
7831
+ "sha1",
7832
+ "secret",
7833
+ "key",
7834
+ "cipher",
7835
+ "aes",
7836
+ "rsa",
7837
+ "hmac"
7838
+ ])) {
7839
+ if (cryptoTests.length === 0) {
7840
+ findings.push(
7841
+ createFinding({
7842
+ title: `Cryptographic implementation lacks verification coverage for ${moduleDoc.module}`,
7843
+ description: "Module docs describe encryption, hashing, or key management, but the current tests do not show evidence of cryptographic strength validation (algorithm checks, key rotation, salt uniqueness, or secure PRNG usage).",
7844
+ impact: "high",
7845
+ effort: "medium",
7846
+ possible_solution_direction: "Add tests that verify password hashing uses bcrypt/argon2 with a sufficient cost factor, encryption uses a secure algorithm with a randomly generated IV, and tokens are generated with a cryptographically secure PRNG.",
7847
+ how_to_reproduce: [
7848
+ `Review ${moduleDoc.paths.join(", ")} for cryptographic operations.`,
7849
+ "Check for MD5/SHA1 password hashing, hardcoded IVs, or Math.random() for token generation."
7850
+ ],
7851
+ impact_area: [...moduleDoc.paths, `module:${moduleDoc.module}`],
7852
+ evidence: [
7853
+ ...moduleDoc.paths.map((path) => `Doc evidence: ${path}`),
7854
+ "No matching cryptographic strength or key management test evidence found for this module."
7855
+ ],
7856
+ category: "cryptographic",
7857
+ status: "open",
7858
+ confidence: 0.67,
7859
+ source_types: ["docs", "tests"],
7860
+ affected_modules: [moduleDoc.module],
7861
+ affected_packages: [],
7862
+ runtime_required: false,
7863
+ manual_follow_up: true
7864
+ })
7865
+ );
7866
+ }
7867
+ }
7868
+ const loggingTests = extractRelevantTestPaths(tests, moduleDoc.module, [
7869
+ "audit",
7870
+ "log.assert",
7871
+ "event.dispatch",
7872
+ "trail",
7873
+ "activity",
7874
+ "log.entry",
7875
+ "logged",
7876
+ "event"
7877
+ ]);
7878
+ if (hasAtLeast(lowered, 2, [
7879
+ "log",
7880
+ "audit",
7881
+ "event",
7882
+ "track",
7883
+ "monitor",
7884
+ "alert",
7885
+ "sentry",
7886
+ "activity"
7887
+ ])) {
7888
+ if (loggingTests.length === 0) {
7889
+ findings.push(
7890
+ createFinding({
7891
+ title: `Security-relevant actions lack audit logging evidence for ${moduleDoc.module}`,
7892
+ description: "Module docs describe operations that should be audited (authentication, financial transactions, admin actions, or data exports), but the current tests do not show evidence of audit log assertions or event dispatch verification.",
7893
+ impact: "medium",
7894
+ effort: "medium",
7895
+ possible_solution_direction: "Add tests that assert audit log entries are written for high-value actions with actor identity, IP, and timestamp. Verify sensitive data (passwords, tokens) is not included in log output.",
7896
+ how_to_reproduce: [
7897
+ `Review ${moduleDoc.paths.join(", ")} for operations that must be auditable.`,
7898
+ "Check logging configuration and assert that log entries are created with sufficient context for forensic analysis."
7899
+ ],
7900
+ impact_area: [...moduleDoc.paths, `module:${moduleDoc.module}`],
7901
+ evidence: [
7902
+ ...moduleDoc.paths.map((path) => `Doc evidence: ${path}`),
7903
+ "No matching audit logging or event dispatch test evidence found for this module."
7904
+ ],
7905
+ category: "logging-monitoring",
7906
+ status: "open",
7907
+ confidence: 0.65,
7908
+ source_types: ["docs", "tests"],
7909
+ affected_modules: [moduleDoc.module],
7910
+ affected_packages: [],
7911
+ runtime_required: false,
7912
+ manual_follow_up: false
7913
+ })
7914
+ );
7915
+ }
7916
+ }
7641
7917
  }
7642
7918
  return findings;
7643
7919
  }
7644
7920
  async function buildSecretFindings(artifactDir) {
7645
- const path = join37(artifactDir, "secrets", "matches.txt");
7921
+ const path = join38(artifactDir, "secrets", "matches.txt");
7646
7922
  if (!existsSync18(path)) {
7647
7923
  return [];
7648
7924
  }
@@ -7743,6 +8019,9 @@ function evaluateRetestStatus(sourceFinding, currentFindings, runtimeStatus, blo
7743
8019
  function hasAny(content, tokens) {
7744
8020
  return tokens.some((token) => content.includes(token));
7745
8021
  }
8022
+ function hasAtLeast(content, count, tokens) {
8023
+ return tokens.filter((token) => content.includes(token)).length >= count;
8024
+ }
7746
8025
  function normalizeImpact(content) {
7747
8026
  const lowered = content.toLowerCase();
7748
8027
  if (lowered.includes("critical") || lowered.includes("remote code execution") || lowered.includes("credential") || lowered.includes("auth bypass")) {
@@ -7755,8 +8034,8 @@ function normalizeImpact(content) {
7755
8034
  }
7756
8035
  async function readNativeAuditFindings(artifactDir) {
7757
8036
  const findings = [];
7758
- const dependencyDir = join37(artifactDir, "dependencies");
7759
- const npmAudit = await readJsonMaybe(join37(dependencyDir, "npm-audit.json"));
8037
+ const dependencyDir = join38(artifactDir, "dependencies");
8038
+ const npmAudit = await readJsonMaybe(join38(dependencyDir, "npm-audit.json"));
7760
8039
  for (const [name, vulnerability] of Object.entries(npmAudit?.vulnerabilities ?? {})) {
7761
8040
  const via = (vulnerability.via ?? []).find((entry) => typeof entry === "object");
7762
8041
  findings.push({
@@ -7768,7 +8047,7 @@ async function readNativeAuditFindings(artifactDir) {
7768
8047
  details: via?.url ?? ""
7769
8048
  });
7770
8049
  }
7771
- const pnpmAudit = await readJsonMaybe(join37(dependencyDir, "pnpm-audit.json"));
8050
+ const pnpmAudit = await readJsonMaybe(join38(dependencyDir, "pnpm-audit.json"));
7772
8051
  for (const advisory of Object.values(pnpmAudit?.advisories ?? {})) {
7773
8052
  findings.push({
7774
8053
  package_name: advisory.module_name ?? "unknown",
@@ -7779,7 +8058,7 @@ async function readNativeAuditFindings(artifactDir) {
7779
8058
  details: advisory.overview ?? ""
7780
8059
  });
7781
8060
  }
7782
- const composerAudit = await readJsonMaybe(join37(dependencyDir, "composer-audit.json"));
8061
+ const composerAudit = await readJsonMaybe(join38(dependencyDir, "composer-audit.json"));
7783
8062
  if (Array.isArray(composerAudit?.advisories)) {
7784
8063
  for (const advisory of composerAudit.advisories) {
7785
8064
  findings.push({
@@ -7828,10 +8107,33 @@ async function readJsonMaybe(path) {
7828
8107
 
7829
8108
  // src/pentest/file-check-mapper.ts
7830
8109
  var GENERIC_SECURITY_MAP = [
7831
- { glob: "**/auth*", checks: ["permission-boundary-review", "runtime-surface-probing"] },
8110
+ {
8111
+ glob: "**/auth*",
8112
+ checks: ["permission-boundary-review", "runtime-surface-probing", "auth-mechanism-review"]
8113
+ },
7832
8114
  { glob: "**/guard*", checks: ["permission-boundary-review"] },
7833
- { glob: "**/.env*", checks: ["runtime-surface-probing"] },
7834
- { glob: "**/config*", checks: ["runtime-surface-probing"] }
8115
+ { glob: "**/.env*", checks: ["runtime-surface-probing", "cryptographic-review"] },
8116
+ { glob: "**/config*", checks: ["runtime-surface-probing"] },
8117
+ // Auth / session files
8118
+ { glob: "**/jwt*", checks: ["auth-mechanism-review"] },
8119
+ { glob: "**/token*", checks: ["auth-mechanism-review"] },
8120
+ { glob: "**/passport*", checks: ["auth-mechanism-review"] },
8121
+ { glob: "**/session*", checks: ["auth-mechanism-review"] },
8122
+ // GraphQL schema and resolvers
8123
+ { glob: "**/*.graphql", checks: ["input-validation-review", "permission-boundary-review"] },
8124
+ { glob: "**/schema.graphql", checks: ["input-validation-review", "permission-boundary-review"] },
8125
+ { glob: "**/resolvers/**", checks: ["input-validation-review", "permission-boundary-review"] },
8126
+ // Logging and audit
8127
+ { glob: "**/log*", checks: ["logging-monitoring-review"] },
8128
+ { glob: "**/audit*", checks: ["logging-monitoring-review"] },
8129
+ { glob: "**/monitor*", checks: ["logging-monitoring-review"] },
8130
+ // Cryptography
8131
+ { glob: "**/*crypt*", checks: ["cryptographic-review"] },
8132
+ { glob: "**/*cipher*", checks: ["cryptographic-review"] },
8133
+ { glob: "**/*hash*", checks: ["cryptographic-review"] },
8134
+ // File uploads and input
8135
+ { glob: "**/upload*", checks: ["input-validation-review"] },
8136
+ { glob: "**/file*", checks: ["input-validation-review"] }
7835
8137
  ];
7836
8138
  var FileCheckMapper = class {
7837
8139
  constructor(frameworks, projectRoot) {
@@ -7892,7 +8194,7 @@ var FileCheckMapper = class {
7892
8194
 
7893
8195
  // src/pentest/incremental-scanner.ts
7894
8196
  import { readFile as readFile18 } from "fs/promises";
7895
- import { join as join38 } from "path";
8197
+ import { join as join39 } from "path";
7896
8198
  import { createHash as createHash8 } from "crypto";
7897
8199
  import { execa as execa3 } from "execa";
7898
8200
  var IncrementalScanner = class {
@@ -7926,7 +8228,7 @@ var IncrementalScanner = class {
7926
8228
  }
7927
8229
  }
7928
8230
  async gitDiff(projectRoot, lastRunId) {
7929
- const progressPath = join38(projectRoot, ".paqad", "pentest", "runs", lastRunId, "progress.json");
8231
+ const progressPath = join39(projectRoot, ".paqad", "pentest", "runs", lastRunId, "progress.json");
7930
8232
  let baseCommit;
7931
8233
  try {
7932
8234
  const raw = await readFile18(progressPath, "utf8");
@@ -7943,7 +8245,7 @@ var IncrementalScanner = class {
7943
8245
  return result.stdout.split("\n").map((f) => f.trim()).filter(Boolean);
7944
8246
  }
7945
8247
  async hashDiff(projectRoot, lastRunId) {
7946
- const manifestPath = join38(projectRoot, ".paqad", "pentest", "runs", lastRunId, "progress.json");
8248
+ const manifestPath = join39(projectRoot, ".paqad", "pentest", "runs", lastRunId, "progress.json");
7947
8249
  let fileManifest = {};
7948
8250
  try {
7949
8251
  const raw = await readFile18(manifestPath, "utf8");
@@ -7955,7 +8257,7 @@ var IncrementalScanner = class {
7955
8257
  const changed = [];
7956
8258
  for (const [filePath, storedHash] of Object.entries(fileManifest)) {
7957
8259
  try {
7958
- const content = await readFile18(join38(projectRoot, filePath), "utf8");
8260
+ const content = await readFile18(join39(projectRoot, filePath), "utf8");
7959
8261
  const currentHash = createHash8("sha256").update(content).digest("hex");
7960
8262
  if (currentHash !== storedHash) {
7961
8263
  changed.push(filePath);
@@ -7968,11 +8270,11 @@ var IncrementalScanner = class {
7968
8270
  }
7969
8271
  async warnIfFullScanStale(projectRoot, thresholdDays) {
7970
8272
  try {
7971
- const runsDir = join38(projectRoot, ".paqad", "pentest", "runs");
8273
+ const runsDir = join39(projectRoot, ".paqad", "pentest", "runs");
7972
8274
  const { readdir: readdir8 } = await import("fs/promises");
7973
8275
  const runs = await readdir8(runsDir).catch(() => []);
7974
8276
  for (const run of runs.sort().reverse()) {
7975
- const progressPath = join38(runsDir, run, "progress.json");
8277
+ const progressPath = join39(runsDir, run, "progress.json");
7976
8278
  try {
7977
8279
  const raw = await readFile18(progressPath, "utf8");
7978
8280
  const progress = JSON.parse(raw);
@@ -8170,8 +8472,8 @@ var PentestWorkflow = class {
8170
8472
  await this.tracker.save(options.projectRoot, progress);
8171
8473
  const docs = await loadModuleDocs(options.projectRoot, focusModules);
8172
8474
  const tests = await loadTests(options.projectRoot, focusModules);
8173
- const docsPath = join39(artifactsDir, "docs-summary.json");
8174
- const testsPath = join39(artifactsDir, "tests-summary.json");
8475
+ const docsPath = join40(artifactsDir, "docs-summary.json");
8476
+ const testsPath = join40(artifactsDir, "tests-summary.json");
8175
8477
  await writeJson(docsPath, docs);
8176
8478
  await writeJson(
8177
8479
  testsPath,
@@ -8272,7 +8574,7 @@ var PentestWorkflow = class {
8272
8574
  progressRunId: progress.run_id,
8273
8575
  reportTimestamp: new Date(progress.started_at)
8274
8576
  });
8275
- const findingIndexPath = join39(
8577
+ const findingIndexPath = join40(
8276
8578
  options.projectRoot,
8277
8579
  PATHS.PENTEST_RUNS_DIR,
8278
8580
  progress.run_id,
@@ -8289,7 +8591,7 @@ var PentestWorkflow = class {
8289
8591
  }
8290
8592
  if (report === null) {
8291
8593
  const existingSidecar = progress.sidecar_path ? await readJsonIfExists(
8292
- join39(options.projectRoot, progress.sidecar_path)
8594
+ join40(options.projectRoot, progress.sidecar_path)
8293
8595
  ) : null;
8294
8596
  report = existingSidecar;
8295
8597
  }
@@ -8304,18 +8606,18 @@ var PentestWorkflow = class {
8304
8606
  this.tracker.markStepRunning(progress, "write-report", writeHash);
8305
8607
  await this.tracker.save(options.projectRoot, progress);
8306
8608
  await writeJson(
8307
- join39(options.projectRoot, PATHS.PENTEST_RUNS_DIR, progress.run_id, "report-preview.json"),
8609
+ join40(options.projectRoot, PATHS.PENTEST_RUNS_DIR, progress.run_id, "report-preview.json"),
8308
8610
  { report_id: report.report_id, findings: report.findings.length }
8309
8611
  );
8310
- await writeJson(join39(options.projectRoot, report.sidecar_path), report);
8612
+ await writeJson(join40(options.projectRoot, report.sidecar_path), report);
8311
8613
  await writeJson(
8312
- join39(options.projectRoot, PATHS.PENTEST_RUNS_DIR, progress.run_id, "blocked-checks.json"),
8614
+ join40(options.projectRoot, PATHS.PENTEST_RUNS_DIR, progress.run_id, "blocked-checks.json"),
8313
8615
  report.blocked_checks
8314
8616
  );
8315
- await mkdir13(join39(options.projectRoot, PATHS.PENTEST_DIR), { recursive: true });
8617
+ await mkdir13(join40(options.projectRoot, PATHS.PENTEST_DIR), { recursive: true });
8316
8618
  const markdown = buildPentestMarkdown(report);
8317
- await writeJson(join39(options.projectRoot, report.sidecar_path), report);
8318
- await writeFile13(join39(options.projectRoot, report.report_path), markdown);
8619
+ await writeJson(join40(options.projectRoot, report.sidecar_path), report);
8620
+ await writeFile13(join40(options.projectRoot, report.report_path), markdown);
8319
8621
  this.tracker.markStepCompleted(
8320
8622
  progress,
8321
8623
  "write-report",
@@ -8342,7 +8644,7 @@ async function buildCurrentPentestReport(input) {
8342
8644
  const docs = await loadModuleDocs(input.projectRoot, input.focusModules);
8343
8645
  const tests = await loadTests(input.projectRoot, input.focusModules);
8344
8646
  const osvFindings = await queryOsv(input.snapshot.packages);
8345
- const osvPath = join39(input.artifactsDir, "dependencies", "osv-results.json");
8647
+ const osvPath = join40(input.artifactsDir, "dependencies", "osv-results.json");
8346
8648
  await writeJson(osvPath, osvFindings);
8347
8649
  const dependencyFindings = await buildDependencyFindings(
8348
8650
  input.snapshot,
@@ -8351,7 +8653,7 @@ async function buildCurrentPentestReport(input) {
8351
8653
  );
8352
8654
  const moduleFindings = buildModuleFindings(docs, tests);
8353
8655
  const secretFindings = await buildSecretFindings(input.artifactsDir);
8354
- const runtimePayload = await readJsonIfExists(join39(input.artifactsDir, "runtime", "runtime-checks.json"));
8656
+ const runtimePayload = await readJsonIfExists(join40(input.artifactsDir, "runtime", "runtime-checks.json"));
8355
8657
  const runtimeStatus = input.targetUrl ? runtimePayload?.reachable ? {
8356
8658
  target_url: input.targetUrl,
8357
8659
  status: "reachable",
@@ -8377,8 +8679,8 @@ async function buildCurrentPentestReport(input) {
8377
8679
  ...runtimeFindings
8378
8680
  ]);
8379
8681
  const timestamp = toLocalTimestamp(input.reportTimestamp);
8380
- const reportPath = join39(PATHS.PENTEST_DIR, `${timestamp}.md`);
8381
- const sidecarPath = join39(PATHS.PENTEST_DIR, `${timestamp}.json`);
8682
+ const reportPath = join40(PATHS.PENTEST_DIR, `${timestamp}.md`);
8683
+ const sidecarPath = join40(PATHS.PENTEST_DIR, `${timestamp}.json`);
8382
8684
  const stack = getPrimaryStack({
8383
8685
  routing: { domain: "coding" },
8384
8686
  stack_profile: input.snapshot.profile
@@ -8431,9 +8733,9 @@ async function buildCurrentPentestReport(input) {
8431
8733
  raw_evidence_paths: [
8432
8734
  relative7(input.projectRoot, osvPath),
8433
8735
  ...[
8434
- join39(PATHS.PENTEST_RUNS_DIR, input.progressRunId, "finding-index.json"),
8435
- join39(PATHS.PENTEST_RUNS_DIR, input.progressRunId, "artifacts", "docs-summary.json"),
8436
- join39(PATHS.PENTEST_RUNS_DIR, input.progressRunId, "artifacts", "tests-summary.json")
8736
+ join40(PATHS.PENTEST_RUNS_DIR, input.progressRunId, "finding-index.json"),
8737
+ join40(PATHS.PENTEST_RUNS_DIR, input.progressRunId, "artifacts", "docs-summary.json"),
8738
+ join40(PATHS.PENTEST_RUNS_DIR, input.progressRunId, "artifacts", "tests-summary.json")
8437
8739
  ]
8438
8740
  ]
8439
8741
  };
@@ -8473,7 +8775,7 @@ var PentestPhase = class {
8473
8775
 
8474
8776
  // src/workflows/pentest-retest.ts
8475
8777
  import { mkdir as mkdir14, writeFile as writeFile14 } from "fs/promises";
8476
- import { dirname as dirname18, join as join40 } from "path";
8778
+ import { dirname as dirname18, join as join41 } from "path";
8477
8779
  var RETEST_STEPS = [
8478
8780
  { id: "load-source-report", title: "Load source pentest report" },
8479
8781
  { id: "rerun-evidence", title: "Collect fresh evidence for source findings" },
@@ -8494,7 +8796,7 @@ var PentestRetestWorkflow = class {
8494
8796
  }
8495
8797
  const normalizedSourcePath = normalizeSidecarPath(sourceReportPath);
8496
8798
  const sourceSidecar = await readJsonIfExists(
8497
- join40(options.projectRoot, normalizedSourcePath)
8799
+ join41(options.projectRoot, normalizedSourcePath)
8498
8800
  );
8499
8801
  if (sourceSidecar === null) {
8500
8802
  throw new Error(`Source pentest sidecar not found at ${normalizedSourcePath}`);
@@ -8519,12 +8821,12 @@ var PentestRetestWorkflow = class {
8519
8821
  if (!this.tracker.shouldSkipStep(progress, "load-source-report", sourceHash)) {
8520
8822
  this.tracker.markStepRunning(progress, "load-source-report", sourceHash);
8521
8823
  await this.tracker.save(options.projectRoot, progress);
8522
- const sourceCopy = join40(artifactsDir, "source-report.json");
8824
+ const sourceCopy = join41(artifactsDir, "source-report.json");
8523
8825
  await writeJson(sourceCopy, sourceSidecar);
8524
8826
  this.tracker.markStepCompleted(
8525
8827
  progress,
8526
8828
  "load-source-report",
8527
- [join40(PATHS.PENTEST_RUNS_DIR, progress.run_id, "artifacts", "source-report.json")],
8829
+ [join41(PATHS.PENTEST_RUNS_DIR, progress.run_id, "artifacts", "source-report.json")],
8528
8830
  [...RETEST_SKILLS.source].map(skillPath)
8529
8831
  );
8530
8832
  await this.tracker.save(options.projectRoot, progress);
@@ -8541,7 +8843,7 @@ var PentestRetestWorkflow = class {
8541
8843
  PENTEST_RUN_ID: progress.run_id,
8542
8844
  PENTEST_ARTIFACT_DIR: artifactsDir,
8543
8845
  PENTEST_TARGET_URL: targetUrl ?? "",
8544
- PENTEST_SOURCE_REPORT: join40(options.projectRoot, normalizedSourcePath),
8846
+ PENTEST_SOURCE_REPORT: join41(options.projectRoot, normalizedSourcePath),
8545
8847
  PENTEST_DB_CONNECTION_NAME: options.dbConnectionName ?? ""
8546
8848
  };
8547
8849
  const scriptResults = await Promise.all([
@@ -8590,8 +8892,8 @@ var PentestRetestWorkflow = class {
8590
8892
  ...currentReport,
8591
8893
  report_id: toReportId("RETEST", new Date(progress.started_at)),
8592
8894
  workflow: "pentest-retest",
8593
- report_path: join40(PATHS.PENTEST_RETEST_DIR, `${timestamp}-${sourceSlug}.md`),
8594
- sidecar_path: join40(PATHS.PENTEST_RETEST_DIR, `${timestamp}-${sourceSlug}.json`),
8895
+ report_path: join41(PATHS.PENTEST_RETEST_DIR, `${timestamp}-${sourceSlug}.md`),
8896
+ sidecar_path: join41(PATHS.PENTEST_RETEST_DIR, `${timestamp}-${sourceSlug}.json`),
8595
8897
  source_report_path: normalizedSourcePath,
8596
8898
  source_report_id: sourceSidecar.report_id,
8597
8899
  findings: retestFindings,
@@ -8604,7 +8906,7 @@ var PentestRetestWorkflow = class {
8604
8906
  ...[...RETEST_SKILLS.source, ...RETEST_SKILLS.evaluate].map(skillPath)
8605
8907
  ]
8606
8908
  };
8607
- const findingIndexPath = join40(
8909
+ const findingIndexPath = join41(
8608
8910
  options.projectRoot,
8609
8911
  PATHS.PENTEST_RUNS_DIR,
8610
8912
  progress.run_id,
@@ -8614,14 +8916,14 @@ var PentestRetestWorkflow = class {
8614
8916
  this.tracker.markStepCompleted(
8615
8917
  progress,
8616
8918
  "evaluate-source-findings",
8617
- [join40(PATHS.PENTEST_RUNS_DIR, progress.run_id, "finding-index.json")],
8919
+ [join41(PATHS.PENTEST_RUNS_DIR, progress.run_id, "finding-index.json")],
8618
8920
  [...RETEST_SKILLS.evaluate].map(skillPath)
8619
8921
  );
8620
8922
  await this.tracker.save(options.projectRoot, progress);
8621
8923
  }
8622
8924
  if (retestReport === null) {
8623
8925
  const existing = progress.sidecar_path ? await readJsonIfExists(
8624
- join40(options.projectRoot, progress.sidecar_path)
8926
+ join41(options.projectRoot, progress.sidecar_path)
8625
8927
  ) : null;
8626
8928
  retestReport = existing;
8627
8929
  }
@@ -8635,12 +8937,12 @@ var PentestRetestWorkflow = class {
8635
8937
  if (!this.tracker.shouldSkipStep(progress, "write-report", writeHash)) {
8636
8938
  this.tracker.markStepRunning(progress, "write-report", writeHash);
8637
8939
  await this.tracker.save(options.projectRoot, progress);
8638
- await writeJson(join40(options.projectRoot, retestReport.sidecar_path), retestReport);
8639
- await mkdir14(dirname18(join40(options.projectRoot, retestReport.report_path)), {
8940
+ await writeJson(join41(options.projectRoot, retestReport.sidecar_path), retestReport);
8941
+ await mkdir14(dirname18(join41(options.projectRoot, retestReport.report_path)), {
8640
8942
  recursive: true
8641
8943
  });
8642
8944
  await writeFile14(
8643
- join40(options.projectRoot, retestReport.report_path),
8945
+ join41(options.projectRoot, retestReport.report_path),
8644
8946
  buildPentestMarkdown(retestReport)
8645
8947
  );
8646
8948
  this.tracker.markStepCompleted(
@@ -8718,7 +9020,7 @@ var ProjectQuestionPhase = class {
8718
9020
 
8719
9021
  // src/workflows/root-cause-analysis.ts
8720
9022
  import { mkdir as mkdir15, writeFile as writeFile15 } from "fs/promises";
8721
- import { dirname as dirname19, join as join41 } from "path";
9023
+ import { dirname as dirname19, join as join42 } from "path";
8722
9024
  var DEFAULT_TITLE_SLUG = "root-cause-analysis";
8723
9025
  var RCA_SECTIONS = [
8724
9026
  "Summary",
@@ -8737,8 +9039,8 @@ var RootCauseAnalysisWorkflow = class {
8737
9039
  async run(options) {
8738
9040
  const title = deriveTitle(options.classification.request_text);
8739
9041
  const filename = `${toLocalTimestamp2(/* @__PURE__ */ new Date())}-${slugify2(title) || DEFAULT_TITLE_SLUG}.md`;
8740
- const relativePath = join41(PATHS.RCA_DIR, filename);
8741
- const outputPath = join41(options.projectRoot, relativePath);
9042
+ const relativePath = join42(PATHS.RCA_DIR, filename);
9043
+ const outputPath = join42(options.projectRoot, relativePath);
8742
9044
  await mkdir15(dirname19(outputPath), { recursive: true });
8743
9045
  await writeFile15(outputPath, buildRcaDocument(title, options.classification));
8744
9046
  return {
@@ -8887,22 +9189,22 @@ var DEFAULT_PHASES = {
8887
9189
 
8888
9190
  // src/pipeline/stream-truncator.ts
8889
9191
  import { appendFile, mkdir as mkdir17 } from "fs/promises";
8890
- import { join as join43, dirname as dirname21 } from "path";
9192
+ import { join as join44, dirname as dirname21 } from "path";
8891
9193
 
8892
9194
  // src/resolver/deduplicator.ts
8893
9195
  import { createHash as createHash9 } from "crypto";
8894
9196
  import { readFile as readFile19, writeFile as writeFile17, mkdir as mkdir18 } from "fs/promises";
8895
- import { join as join44, dirname as dirname22 } from "path";
9197
+ import { join as join45, dirname as dirname22 } from "path";
8896
9198
 
8897
9199
  // src/scripts/generator.ts
8898
9200
  import { chmodSync as chmodSync2 } from "fs";
8899
9201
  import { mkdir as mkdir19, writeFile as writeFile18 } from "fs/promises";
8900
- import { dirname as dirname23, join as join45 } from "path";
9202
+ import { dirname as dirname23, join as join46 } from "path";
8901
9203
 
8902
9204
  // src/skills/cache-manager.ts
8903
9205
  import { createHash as createHash10 } from "crypto";
8904
9206
  import { mkdir as mkdir20, readFile as readFile20, readdir as readdir5, rm as rm2, stat as stat3, writeFile as writeFile19 } from "fs/promises";
8905
- import { join as join46 } from "path";
9207
+ import { join as join47 } from "path";
8906
9208
  import fg6 from "fast-glob";
8907
9209
 
8908
9210
  // src/skills/frontmatter-parser.ts
@@ -8927,24 +9229,49 @@ var conditionalProcessor = new ConditionalSectionProcessor();
8927
9229
 
8928
9230
  // src/skills/index-generator.ts
8929
9231
  import { mkdir as mkdir21, readFile as readFile21, writeFile as writeFile20 } from "fs/promises";
8930
- import { dirname as dirname24, join as join47, relative as relative8 } from "path";
9232
+ import { dirname as dirname24, join as join48, relative as relative8 } from "path";
8931
9233
  import fg7 from "fast-glob";
8932
9234
 
8933
9235
  // src/skills/loader.ts
8934
9236
  import { readFile as readFile22 } from "fs/promises";
8935
9237
  import { basename as basename6 } from "pathe";
8936
9238
 
9239
+ // src/update/audit.ts
9240
+ import { appendFileSync, mkdirSync as mkdirSync6 } from "fs";
9241
+ import { dirname as dirname25, join as join49 } from "path";
9242
+ function auditPath(projectRoot) {
9243
+ return join49(projectRoot, PATHS.AUDIT_LOG);
9244
+ }
9245
+ function ts() {
9246
+ return (/* @__PURE__ */ new Date()).toISOString();
9247
+ }
9248
+ function appendAuditLog2(projectRoot, previous, updated) {
9249
+ const path = auditPath(projectRoot);
9250
+ mkdirSync6(dirname25(path), { recursive: true });
9251
+ const line = `[${ts()}] INFO silent-update previous=${previous ?? "unknown"} updated=${updated}
9252
+ `;
9253
+ appendFileSync(path, line);
9254
+ }
9255
+ function appendAuditLogFailure(projectRoot, previous, target, error) {
9256
+ const path = auditPath(projectRoot);
9257
+ mkdirSync6(dirname25(path), { recursive: true });
9258
+ const sanitized = error.replace(/"/g, "'");
9259
+ const line = `[${ts()}] WARN silent-update-failed previous=${previous ?? "unknown"} target=${target} error="${sanitized}"
9260
+ `;
9261
+ appendFileSync(path, line);
9262
+ }
9263
+
8937
9264
  // src/update/updater.ts
8938
- import { chmodSync as chmodSync3, existsSync as existsSync19, mkdirSync as mkdirSync6, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
9265
+ import { chmodSync as chmodSync3, existsSync as existsSync19, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
8939
9266
  import { mkdtemp, readdir as readdir6, readFile as readFile23, rm as rm3 } from "fs/promises";
8940
9267
  import { tmpdir } from "os";
8941
- import { dirname as dirname25, join as join48, relative as relative9 } from "path";
9268
+ import { dirname as dirname26, join as join50, relative as relative9 } from "path";
8942
9269
  var FrameworkUpdater = class {
8943
9270
  constructor(options = {}) {
8944
9271
  this.options = options;
8945
9272
  }
8946
9273
  async run(projectRoot) {
8947
- const previousVersion = readText(join48(projectRoot, PATHS.FRAMEWORK_VERSION));
9274
+ const previousVersion = readText(join50(projectRoot, PATHS.FRAMEWORK_VERSION));
8948
9275
  const manifest = readManifest(projectRoot);
8949
9276
  const artifactPolicy = new Map(
8950
9277
  manifest?.generated_artifacts.map((artifact) => [artifact.path, artifact.auto_update]) ?? []
@@ -8954,18 +9281,18 @@ var FrameworkUpdater = class {
8954
9281
  const skipped = [];
8955
9282
  const newScripts = [];
8956
9283
  for (const candidate of candidates) {
8957
- const target = join48(projectRoot, candidate.path);
9284
+ const target = join50(projectRoot, candidate.path);
8958
9285
  const existed = existsSync19(target);
8959
9286
  const autoUpdate = artifactPolicy.get(candidate.path) ?? candidate.autoUpdate;
8960
9287
  if (existed && autoUpdate === false) {
8961
9288
  skipped.push({
8962
9289
  path: candidate.path,
8963
- before: readFileSync12(target, "utf8"),
9290
+ before: readFileSync13(target, "utf8"),
8964
9291
  after: candidate.content
8965
9292
  });
8966
9293
  continue;
8967
9294
  }
8968
- mkdirSync6(dirname25(target), { recursive: true });
9295
+ mkdirSync7(dirname26(target), { recursive: true });
8969
9296
  writeFileSync6(target, candidate.content);
8970
9297
  if (candidate.executable === true) {
8971
9298
  chmodSync3(target, 493);
@@ -8975,9 +9302,13 @@ var FrameworkUpdater = class {
8975
9302
  newScripts.push(candidate.path);
8976
9303
  }
8977
9304
  }
8978
- mkdirSync6(dirname25(join48(projectRoot, PATHS.FRAMEWORK_VERSION)), { recursive: true });
8979
- writeFileSync6(join48(projectRoot, PATHS.FRAMEWORK_VERSION), `${VERSION}
8980
- `);
9305
+ mkdirSync7(dirname26(join50(projectRoot, PATHS.FRAMEWORK_VERSION)), { recursive: true });
9306
+ writeFileSync6(
9307
+ join50(projectRoot, PATHS.FRAMEWORK_VERSION),
9308
+ `version=${VERSION}
9309
+ updated_at=${(/* @__PURE__ */ new Date()).toISOString()}
9310
+ `
9311
+ );
8981
9312
  return {
8982
9313
  previous_version: previousVersion,
8983
9314
  target_version: VERSION,
@@ -8996,7 +9327,7 @@ var FrameworkUpdater = class {
8996
9327
  if (profile === null) {
8997
9328
  throw new Error("Cannot update framework-managed artifacts without a project profile");
8998
9329
  }
8999
- const tempRoot = await mkdtemp(join48(tmpdir(), "paqad-ai-update-"));
9330
+ const tempRoot = await mkdtemp(join50(tmpdir(), "paqad-ai-update-"));
9000
9331
  try {
9001
9332
  const result = await new OnboardingOrchestrator().run({
9002
9333
  projectRoot: tempRoot,
@@ -9016,11 +9347,11 @@ var FrameworkUpdater = class {
9016
9347
  }
9017
9348
  };
9018
9349
  function readManifest(projectRoot) {
9019
- const path = join48(projectRoot, PATHS.ONBOARDING_MANIFEST);
9350
+ const path = join50(projectRoot, PATHS.ONBOARDING_MANIFEST);
9020
9351
  if (!existsSync19(path)) {
9021
9352
  return null;
9022
9353
  }
9023
- return JSON.parse(readFileSync12(path, "utf8"));
9354
+ return JSON.parse(readFileSync13(path, "utf8"));
9024
9355
  }
9025
9356
  function readProfile(projectRoot) {
9026
9357
  return readProjectProfile(projectRoot);
@@ -9029,14 +9360,16 @@ function readText(path) {
9029
9360
  if (!existsSync19(path)) {
9030
9361
  return null;
9031
9362
  }
9032
- return readFileSync12(path, "utf8").trim();
9363
+ const raw = readFileSync13(path, "utf8");
9364
+ const match = raw.match(/^version=(.+)$/m);
9365
+ return match ? match[1].trim() : raw.trim();
9033
9366
  }
9034
9367
  async function collectFiles(root, generated) {
9035
9368
  const paths = generated.length > 0 ? generated : await walk3(root);
9036
9369
  return Promise.all(
9037
9370
  paths.map(async (file) => ({
9038
9371
  path: file,
9039
- content: await readFile23(join48(root, file), "utf8"),
9372
+ content: await readFile23(join50(root, file), "utf8"),
9040
9373
  autoUpdate: true,
9041
9374
  executable: file.startsWith("scripts/")
9042
9375
  }))
@@ -9046,7 +9379,7 @@ async function walk3(root, current = root) {
9046
9379
  const entries = await readdir6(current, { withFileTypes: true });
9047
9380
  const files = [];
9048
9381
  for (const entry of entries) {
9049
- const absolute = join48(current, entry.name);
9382
+ const absolute = join50(current, entry.name);
9050
9383
  if (entry.isDirectory()) {
9051
9384
  files.push(...await walk3(root, absolute));
9052
9385
  continue;
@@ -9058,30 +9391,30 @@ async function walk3(root, current = root) {
9058
9391
 
9059
9392
  // src/verification/gates/documentation-freshness.ts
9060
9393
  import { existsSync as existsSync20 } from "fs";
9061
- import { join as join49 } from "path";
9394
+ import { join as join51 } from "path";
9062
9395
  var STALENESS_WINDOW_MS2 = 1e3 * 60 * 60 * 24 * 7;
9063
9396
 
9064
9397
  // src/patterns/pattern-store.ts
9065
9398
  import { readFile as readFile24, writeFile as writeFile21, mkdir as mkdir22, unlink } from "fs/promises";
9066
- import { join as join50, dirname as dirname26 } from "path";
9399
+ import { join as join52, dirname as dirname27 } from "path";
9067
9400
  import { homedir as homedir3 } from "os";
9068
- var GLOBAL_PATTERNS_DIR = join50(homedir3(), ".paqad", "patterns");
9401
+ var GLOBAL_PATTERNS_DIR = join52(homedir3(), ".paqad", "patterns");
9069
9402
  var PatternStore = class {
9070
9403
  get indexPath() {
9071
- return join50(GLOBAL_PATTERNS_DIR, "index.json");
9404
+ return join52(GLOBAL_PATTERNS_DIR, "index.json");
9072
9405
  }
9073
9406
  get entriesDir() {
9074
- return join50(GLOBAL_PATTERNS_DIR, "entries");
9407
+ return join52(GLOBAL_PATTERNS_DIR, "entries");
9075
9408
  }
9076
9409
  async save(pattern) {
9077
9410
  await mkdir22(this.entriesDir, { recursive: true });
9078
- const entryPath = join50(this.entriesDir, `${pattern.id}.json`);
9411
+ const entryPath = join52(this.entriesDir, `${pattern.id}.json`);
9079
9412
  await writeFile21(entryPath, JSON.stringify(pattern, null, 2), "utf8");
9080
9413
  await this.updateIndex(pattern);
9081
9414
  }
9082
9415
  async load(id) {
9083
9416
  try {
9084
- const raw = await readFile24(join50(this.entriesDir, `${id}.json`), "utf8");
9417
+ const raw = await readFile24(join52(this.entriesDir, `${id}.json`), "utf8");
9085
9418
  return JSON.parse(raw);
9086
9419
  } catch {
9087
9420
  return null;
@@ -9111,12 +9444,12 @@ var PatternStore = class {
9111
9444
  } else {
9112
9445
  index.entries.push(entry);
9113
9446
  }
9114
- await mkdir22(dirname26(this.indexPath), { recursive: true });
9447
+ await mkdir22(dirname27(this.indexPath), { recursive: true });
9115
9448
  await writeFile21(this.indexPath, JSON.stringify(index, null, 2), "utf8");
9116
9449
  }
9117
9450
  async delete(id) {
9118
9451
  try {
9119
- await unlink(join50(this.entriesDir, `${id}.json`));
9452
+ await unlink(join52(this.entriesDir, `${id}.json`));
9120
9453
  } catch {
9121
9454
  }
9122
9455
  const index = await this.loadIndex();
@@ -9216,16 +9549,16 @@ ${p.solution}
9216
9549
 
9217
9550
  // src/workflows/template-loader.ts
9218
9551
  import { readFile as readFile25, readdir as readdir7 } from "fs/promises";
9219
- import { join as join51 } from "path";
9552
+ import { join as join53 } from "path";
9220
9553
  import YAML6 from "yaml";
9221
9554
 
9222
9555
  // src/workflows/engine.ts
9223
9556
  import { readFile as readFile26, writeFile as writeFile23, mkdir as mkdir23 } from "fs/promises";
9224
- import { join as join52, dirname as dirname27 } from "path";
9557
+ import { join as join54, dirname as dirname28 } from "path";
9225
9558
  import { randomUUID as randomUUID3 } from "crypto";
9226
9559
 
9227
9560
  // src/index.ts
9228
- var VERSION = "0.1.0";
9561
+ var VERSION = "0.1.3";
9229
9562
 
9230
9563
  // src/cli/commands/capabilities.ts
9231
9564
  import { Command } from "commander";
@@ -9401,8 +9734,8 @@ function createPacksCommand() {
9401
9734
 
9402
9735
  // src/cli/commands/refresh.ts
9403
9736
  import { Command as Command7 } from "commander";
9404
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
9405
- import { join as join53 } from "path";
9737
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
9738
+ import { join as join55 } from "path";
9406
9739
  function createRefreshCommand() {
9407
9740
  return new Command7("refresh").description("Refresh derived framework artifacts").option("--project-root <path>", "Project root", process.cwd()).option("--design-system", "Refresh design-system markdown from design tokens").option("--stack", "Refresh the cached stack snapshot").action(async (options) => {
9408
9741
  const shouldRefreshDesignSystem = options.designSystem ?? true;
@@ -9438,14 +9771,14 @@ function createRefreshCommand() {
9438
9771
  });
9439
9772
  }
9440
9773
  function writeRefreshDrift(projectRoot, refreshDrift) {
9441
- const path = join53(projectRoot, PATHS.STACK_DRIFT);
9774
+ const path = join55(projectRoot, PATHS.STACK_DRIFT);
9442
9775
  const current = readExistingJson(path);
9443
9776
  writeFileSync7(path, `${JSON.stringify({ ...current ?? {}, ...refreshDrift }, null, 2)}
9444
9777
  `);
9445
9778
  }
9446
9779
  function readExistingJson(path) {
9447
9780
  try {
9448
- return JSON.parse(readFileSync13(path, "utf8"));
9781
+ return JSON.parse(readFileSync14(path, "utf8"));
9449
9782
  } catch {
9450
9783
  return null;
9451
9784
  }
@@ -9454,9 +9787,25 @@ function readExistingJson(path) {
9454
9787
  // src/cli/commands/update.ts
9455
9788
  import { Command as Command8 } from "commander";
9456
9789
  function createUpdateCommand() {
9457
- return new Command8("update").description("Update framework-managed artifacts").option("--project-root <path>", "Project root", process.cwd()).action(async (options) => {
9458
- const report = await new FrameworkUpdater().run(options.projectRoot);
9459
- console.log(JSON.stringify(report, null, 2));
9790
+ return new Command8("update").description("Update framework-managed artifacts").option("--project-root <path>", "Project root", process.cwd()).option("--silent", "Suppress output; write results to audit log only").action(async (options) => {
9791
+ try {
9792
+ const report = await new FrameworkUpdater().run(options.projectRoot);
9793
+ if (!options.silent) {
9794
+ console.log(JSON.stringify(report, null, 2));
9795
+ }
9796
+ appendAuditLog2(options.projectRoot, report.previous_version, report.target_version);
9797
+ } catch (err) {
9798
+ if (!options.silent) {
9799
+ throw err;
9800
+ }
9801
+ appendAuditLogFailure(
9802
+ options.projectRoot,
9803
+ null,
9804
+ VERSION,
9805
+ err instanceof Error ? err.message : String(err)
9806
+ );
9807
+ process.exit(0);
9808
+ }
9460
9809
  });
9461
9810
  }
9462
9811