@amityco/social-plus-vise 0.11.0 → 0.12.2

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/dist/tools/ast.js CHANGED
@@ -67,13 +67,38 @@ function getParser(language) {
67
67
  }
68
68
  return parser;
69
69
  }
70
+ // node-tree-sitter's native parser rejects sufficiently large inputs with a
71
+ // native "Invalid argument" error (~32KB string limit). It IS catchable, but an
72
+ // unguarded caller would otherwise abort the whole validate run. We short-circuit
73
+ // before the native call so callers get a clean, documented, catchable error and
74
+ // can degrade to regex-only for that file. Real codebases routinely have files
75
+ // past this size (1000+ line activities/view controllers).
76
+ export const MAX_PARSE_BYTES = 30000;
70
77
  /**
71
78
  * Parse source content into a tree-sitter syntax tree.
79
+ * Throws on oversized input (see MAX_PARSE_BYTES) — callers that want graceful
80
+ * degradation should use tryParse or wrap this in try/catch.
72
81
  */
73
82
  export function parse(language, source) {
83
+ if (Buffer.byteLength(source, "utf8") > MAX_PARSE_BYTES) {
84
+ throw new Error(`source exceeds tree-sitter parse limit (${Buffer.byteLength(source, "utf8")} bytes > ${MAX_PARSE_BYTES})`);
85
+ }
74
86
  const parser = getParser(language);
75
87
  return parser.parse(source);
76
88
  }
89
+ /**
90
+ * Parse, returning null instead of throwing when the source can't be parsed
91
+ * (oversized input, native parser error). Lets validators skip AST analysis for
92
+ * one file and fall back to regex without aborting the run.
93
+ */
94
+ export function tryParse(language, source) {
95
+ try {
96
+ return parse(language, source);
97
+ }
98
+ catch {
99
+ return null;
100
+ }
101
+ }
77
102
  /**
78
103
  * Find all call expressions in the tree whose callee matches a pattern.
79
104
  * Returns normalised callee strings and argument nodes.
@@ -2,9 +2,11 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { assessProjectCompleteness } from "../capabilities.js";
5
6
  import { classifyOutcome } from "../outcomes.js";
6
7
  import { objectInput, optionalStringField, stringField, textResult } from "../types.js";
7
8
  import { packageVersion } from "../version.js";
9
+ import { readDesignContract } from "./design.js";
8
10
  import { inspectProject, validateSetup } from "./project.js";
9
11
  const complianceDirName = "sp-vise";
10
12
  const attestationsDirName = "attestations";
@@ -230,6 +232,7 @@ export async function initCompliance(repoPath, request, surfacePath) {
230
232
  const refs = rules.map(ruleRef); // minimal shape — stable digest input
231
233
  const fileRefs = rules.map(ruleRefForFile); // adds title for human/agent readers
232
234
  const engagement = await readEngagement(repoRoot);
235
+ const designContract = await readDesignContract(repoRoot);
233
236
  const compliance = {
234
237
  schema_version: schemaVersion,
235
238
  foundry_version: packageVersion,
@@ -240,6 +243,15 @@ export async function initCompliance(repoPath, request, surfacePath) {
240
243
  engagement_id: engagement?.engagement_id,
241
244
  surface: inspection.selectedSurface ? { path: inspection.selectedSurface.path, platforms: inspection.selectedSurface.platforms } : undefined,
242
245
  rules: fileRefs, // file carries titles
246
+ design_contract: designContract
247
+ ? {
248
+ digest: designContract.digest,
249
+ strength: designContract.stats.strength,
250
+ source_kind: designContract.source.kind,
251
+ declared_tokens: designContract.stats.declared_tokens,
252
+ inferred_tokens: designContract.stats.inferred_tokens,
253
+ }
254
+ : undefined,
243
255
  };
244
256
  await mkdir(attestationsDir(repoRoot), { recursive: true });
245
257
  await writeJson(compliancePath(repoRoot), compliance);
@@ -267,6 +279,7 @@ export async function initCompliance(repoPath, request, surfacePath) {
267
279
  surfacePath: inspection.selectedSurface?.path,
268
280
  rules: refs.length,
269
281
  engagement_id: engagement?.engagement_id,
282
+ ...(compliance.design_contract && { design_contract: compliance.design_contract }),
270
283
  ...(warnings.length > 0 && { warnings }),
271
284
  nextStep: "Run vise check, then implement until rules pass deterministically or are attested.",
272
285
  };
@@ -426,6 +439,10 @@ export async function checkCompliance(repoPath) {
426
439
  const needsAttestation = results.some((result) => result.status === "attestation-needed" || result.status === "stale");
427
440
  // Precedence: blocked (exit 3) > deterministic-failures (2) > needs-attestation (1) > green (0).
428
441
  // Contract drift (exit 4) is handled earlier and short-circuits the loop.
442
+ // Advisory feature-completeness — surfaced but NEVER part of status/exitCode
443
+ // (completeness is a "this is missing" claim, structurally FP-prone; see the
444
+ // validation-boundaries principle). Failure to assess is silently ignored.
445
+ const completeness = (await assessProjectCompleteness(inspection.effectiveRoot, compliance.outcome).catch(() => null)) ?? undefined;
429
446
  // Blocked wins because the agent cannot proceed without customer input;
430
447
  // surfacing a smaller failure first would distract from the real blocker.
431
448
  return {
@@ -441,6 +458,7 @@ export async function checkCompliance(repoPath) {
441
458
  surfacePath: compliance.surface?.path,
442
459
  summary,
443
460
  rules: results,
461
+ ...(completeness && (completeness.missing.length > 0 || completeness.optedOut.length > 0 || completeness.present.length > 0) ? { completeness } : {}),
444
462
  };
445
463
  }
446
464
  export async function syncCompliance(repoPath) {
@@ -537,12 +555,14 @@ export async function statusCompliance(repoPath) {
537
555
  const repoRoot = path.resolve(repoPath);
538
556
  const check = await checkCompliance(repoPath);
539
557
  const engagement = await readEngagement(repoRoot);
558
+ const compliance = await readCompliance(repoRoot).catch(() => null);
540
559
  return {
541
560
  status: check.status,
542
561
  exitCode: check.exitCode,
543
562
  outcome: check.outcome,
544
563
  surfacePath: check.surfacePath,
545
564
  summary: check.summary,
565
+ ...(compliance?.design_contract && { design_contract: compliance.design_contract }),
546
566
  ...(engagement && {
547
567
  engagement: {
548
568
  engagement_id: engagement.engagement_id,