@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.
@@ -0,0 +1,129 @@
1
+ // SDK-version currency guidance for `vise plan` (advisory, never gates).
2
+ //
3
+ // Why this exists: an agent (or a copy-pasted example) can pin an arbitrary, stale
4
+ // SDK version — e.g. @amityco/ts-sdk@6.0.0 when 7.x is current — which silently
5
+ // loses newer APIs (a real run opted out of `story` because StoryRepository didn't
6
+ // exist in 6.0.0). The social.plus docs Vise fetches don't expose a parseable
7
+ // version (changelogs are feature-organized), but the npm registry does, exactly
8
+ // and live. So for the npm-published SDKs (TypeScript / React Native) we look up the
9
+ // registry `latest`; for Android/iOS/Flutter (Maven/CocoaPods/pub — multi-registry)
10
+ // we give version-agnostic "install latest, then pin" guidance.
11
+ //
12
+ // This runs in the PLAN layer (which already does network I/O for docs), NOT in the
13
+ // deterministic offline validators. It degrades gracefully: any fetch failure falls
14
+ // back to version-agnostic guidance.
15
+ import { fetchTextWithTimeout } from "./docs.js";
16
+ // npm package per platform. React Native ships a distinct package with its own
17
+ // version line. Android/iOS/Flutter are not npm-published → undefined.
18
+ function npmPackageFor(platform) {
19
+ if (platform === "typescript")
20
+ return "@amityco/ts-sdk";
21
+ if (platform === "react-native")
22
+ return "@amityco/ts-sdk-react-native";
23
+ return undefined;
24
+ }
25
+ const VERSION_AGNOSTIC = "Greenfield install: add the social.plus SDK at its latest version, then pin the resolved version in your manifest for reproducible CI. Do not copy a version number from examples, docs snippets, or memory — it is likely stale.";
26
+ function majorOf(version) {
27
+ if (!version)
28
+ return null;
29
+ const m = version.match(/(\d+)\.\d+(?:\.\d+)?/); // tolerates ^, ~, >=, ranges
30
+ return m ? Number(m[1]) : null;
31
+ }
32
+ // Pure logic: given the platform, the project's currently-pinned version (or null for
33
+ // greenfield), and the registry latest (or null when unavailable), produce guidance.
34
+ // Separated from the fetch so it is deterministically testable.
35
+ export function composeSdkVersionGuidance(platform, currentPinned, latest) {
36
+ const pkg = npmPackageFor(platform);
37
+ // Non-npm platforms, or npm lookup unavailable (offline) → version-agnostic guidance,
38
+ // but only worth surfacing for a greenfield project (no existing pin to respect).
39
+ if (!latest) {
40
+ return currentPinned ? undefined : { kind: "info", advice: VERSION_AGNOSTIC };
41
+ }
42
+ if (!currentPinned) {
43
+ return {
44
+ kind: "info",
45
+ latest,
46
+ advice: `Greenfield install: use ${pkg}@${latest} (current latest on npm) and pin it in package.json. Do not copy an older version from examples or memory.`,
47
+ };
48
+ }
49
+ const curMajor = majorOf(currentPinned);
50
+ const latMajor = majorOf(latest);
51
+ if (curMajor !== null && latMajor !== null && curMajor < latMajor) {
52
+ return {
53
+ kind: "fyi",
54
+ latest,
55
+ current: currentPinned,
56
+ advice: `This project pins ${pkg} ${currentPinned}, but the current latest is ${latest} (major ${latMajor}). A major upgrade is a separate, out-of-scope decision for this task — flagging for awareness only; do not upgrade as part of this change.`,
57
+ };
58
+ }
59
+ // On the current major (or ahead) — nothing to surface.
60
+ return undefined;
61
+ }
62
+ // Read the project's pinned version of `pkgName` from package.json (null if absent or
63
+ // a non-pinned placeholder). Best-effort; never throws.
64
+ export function pinnedVersion(packageJsonText, pkgName) {
65
+ if (!packageJsonText)
66
+ return null;
67
+ try {
68
+ const pkg = JSON.parse(packageJsonText);
69
+ const deps = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
70
+ const v = deps[pkgName];
71
+ if (typeof v !== "string")
72
+ return null;
73
+ if (/^(?:latest|\*|x|X)$/i.test(v.trim()))
74
+ return null; // floating — the pinned rule handles this
75
+ return v.trim();
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ const cache = new Map();
82
+ const REGISTRY_TTL_MS = 60 * 60 * 1000; // 1h, mirrors the docs cache TTL
83
+ // Tight, dedicated timeout — NOT the 15s docs default. Every CLI `vise plan` is a
84
+ // fresh process with a cold cache, so an unreachable registry would otherwise add the
85
+ // full docs timeout to a core command for a merely-advisory field. Bounded at ~2.5s.
86
+ const REGISTRY_TIMEOUT_MS = 2500;
87
+ function nowMs() {
88
+ // Date.now is unavailable in some sandboxed contexts; guard so a lookup failure
89
+ // never throws (it just skips caching).
90
+ try {
91
+ return Date.now();
92
+ }
93
+ catch {
94
+ return 0;
95
+ }
96
+ }
97
+ // Fetch the registry `latest` dist-tag for a scoped npm package. Returns null on any
98
+ // failure (offline, timeout, parse error) so the plan degrades gracefully.
99
+ export async function fetchNpmLatest(pkg) {
100
+ const now = nowMs();
101
+ const cached = cache.get(pkg);
102
+ if (cached && now > 0 && now - cached.at < REGISTRY_TTL_MS)
103
+ return cached.version;
104
+ let version = null;
105
+ try {
106
+ const base = (process.env.NPM_REGISTRY_BASE_URL ?? "https://registry.npmjs.org").replace(/\/+$/, "");
107
+ const url = `${base}/-/package/${pkg.replace("/", "%2F")}/dist-tags`;
108
+ const body = await fetchTextWithTimeout(url, "application/json", REGISTRY_TIMEOUT_MS);
109
+ const parsed = JSON.parse(body);
110
+ version = typeof parsed?.latest === "string" ? parsed.latest : null;
111
+ }
112
+ catch {
113
+ version = null;
114
+ }
115
+ if (now > 0)
116
+ cache.set(pkg, { version, at: now });
117
+ return version;
118
+ }
119
+ // Plan-layer entry point: produce advisory SDK-version guidance for a project.
120
+ export async function sdkVersionGuidance(platform, packageJsonText) {
121
+ const pkg = npmPackageFor(platform);
122
+ if (!pkg) {
123
+ // Android/iOS/Flutter — version-agnostic, greenfield-oriented guidance only.
124
+ return { kind: "info", advice: VERSION_AGNOSTIC };
125
+ }
126
+ const latest = await fetchNpmLatest(pkg);
127
+ const currentPinned = pinnedVersion(packageJsonText, pkg);
128
+ return composeSdkVersionGuidance(platform, currentPinned, latest);
129
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amityco/social-plus-vise",
3
- "version": "0.11.0",
3
+ "version": "0.12.2",
4
4
  "description": "Skill-guided deterministic CLI for social.plus SDK integration assistance.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -58,12 +58,25 @@
58
58
  "test:rule-coverage": "npm run build && node test/run-rule-coverage.mjs",
59
59
  "test:happy-path-clean": "npm run build && node test/run-happy-path-clean.mjs",
60
60
  "test:fixture-symmetry": "npm run build && node test/run-fixture-symmetry.mjs",
61
+ "test:nonui-skip": "npm run build && node test/run-nonui-layer-skip.mjs",
62
+ "test:sdk-version": "npm run build && node test/run-sdk-version.mjs",
61
63
  "typecheck": "tsc -p tsconfig.json --noEmit",
62
64
  "test:e2e-package": "npm run build && node test/run-e2e-package.mjs",
63
- "validate": "npm run typecheck && npm test && npm run test:mcp && npm run test:cli && npm run test:docs && npm run test:ast && npm run test:compliance && npm run test:rule-coverage && npm run test:readme-coverage && npm run test:happy-path-clean && npm run test:fixture-symmetry && npm run test:improvements && npm run test:debug && npm run test:preflight && npm run test:e2e-package && npm run pack:check",
65
+ "validate": "npm run typecheck && npm test && npm run test:mcp && npm run test:cli && npm run test:docs && npm run test:ast && npm run test:design-extract && npm run test:capabilities && npm run test:classify && npm run test:compliance && npm run test:rule-coverage && npm run test:readme-coverage && npm run test:happy-path-clean && npm run test:fixture-symmetry && npm run test:nonui-skip && npm run test:sdk-version && npm run test:native-idioms && npm run test:grader-facts && npm run test:ground-truth && npm run test:improvements && npm run test:debug && npm run test:preflight && npm run test:e2e-package && npm run pack:check",
64
66
  "test:ast": "node test/run-ast-helpers.mjs",
67
+ "test:design-extract": "npm run build && node test/run-design-extract.mjs",
68
+ "test:capabilities": "npm run build && node test/run-capabilities.mjs",
69
+ "test:classify": "npm run build && node test/run-classify.mjs",
65
70
  "test:debug": "npm run build && node test/run-debug.mjs",
66
- "test:preflight": "npm run build && node test/run-preflight.mjs"
71
+ "test:preflight": "npm run build && node test/run-preflight.mjs",
72
+ "test:native-idioms": "npm run build && node test/run-native-idioms.mjs",
73
+ "test:grader-facts": "node test/run-grader-facts.mjs",
74
+ "test:ground-truth": "node test/run-ground-truth.mjs",
75
+ "bench:tp": "npm run build && node bench/tp-dashboard.mjs",
76
+ "bench:ground-truth": "node bench/ground-truth.mjs",
77
+ "bench:symbols-drift": "node bench/grader/build-symbols.mjs --check",
78
+ "bench:audit-boundaries": "node bench/audit-marker-boundaries.mjs",
79
+ "bench:gate": "npm run build && node bench/tp-dashboard.mjs && node test/run-fixture-symmetry.mjs && node test/run-native-idioms.mjs && node test/run-grader-facts.mjs && node test/run-happy-path-clean.mjs"
67
80
  },
68
81
  "dependencies": {
69
82
  "@modelcontextprotocol/sdk": "^1.12.0",
@@ -722,78 +722,6 @@
722
722
  }
723
723
  }
724
724
  },
725
- {
726
- "id": "typescript.comment.reference-type-enum",
727
- "version": 1,
728
- "title": "TypeScript comment reference type must be enum",
729
- "severity": "warning",
730
- "rationale": "Comment reference types (e.g., Post, Article) are typed as enums in most SDKs. Using raw strings bypasses type checking and causes silent failures if the server expects a specific casing.",
731
- "applies_when": {
732
- "platforms": [
733
- "typescript"
734
- ],
735
- "outcomes": [
736
- "add-comment",
737
- "validate-setup"
738
- ]
739
- },
740
- "enforcement": {
741
- "deterministic": [
742
- {
743
- "check": "validator-finding-absent",
744
- "finding_rule_id": "typescript.comment.reference-type-enum"
745
- }
746
- ],
747
- "attestation": {
748
- "allowed": true,
749
- "host_agent_min_confidence": "high",
750
- "human_allowed": true,
751
- "evidence_required": [
752
- {
753
- "field": "reference_type_source",
754
- "description": "How reference types are strongly typed.",
755
- "upload_policy": "upload-with-consent"
756
- }
757
- ]
758
- }
759
- }
760
- },
761
- {
762
- "id": "react-native.comment.reference-type-enum",
763
- "version": 1,
764
- "title": "React Native comment reference type must be enum",
765
- "severity": "warning",
766
- "rationale": "Comment reference types (e.g., Post, Article) are typed as enums in most SDKs. Using raw strings bypasses type checking and causes silent failures if the server expects a specific casing.",
767
- "applies_when": {
768
- "platforms": [
769
- "react-native"
770
- ],
771
- "outcomes": [
772
- "add-comment",
773
- "validate-setup"
774
- ]
775
- },
776
- "enforcement": {
777
- "deterministic": [
778
- {
779
- "check": "validator-finding-absent",
780
- "finding_rule_id": "react-native.comment.reference-type-enum"
781
- }
782
- ],
783
- "attestation": {
784
- "allowed": true,
785
- "host_agent_min_confidence": "high",
786
- "human_allowed": true,
787
- "evidence_required": [
788
- {
789
- "field": "reference_type_source",
790
- "description": "How reference types are strongly typed.",
791
- "upload_policy": "upload-with-consent"
792
- }
793
- ]
794
- }
795
- }
796
- },
797
725
  {
798
726
  "id": "android.comment.reference-type-enum",
799
727
  "version": 1,