@aperant/framework 0.15.0 → 0.16.0
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 +19 -0
- package/agents/{apt-pr-review-xplatform.md → apt-pr-review-crossplatform.md} +1 -1
- package/dist/cli/commands/init.d.mts.map +1 -1
- package/dist/cli/commands/init.mjs +30 -1
- package/dist/cli/commands/init.mjs.map +1 -1
- package/dist/cli/commands/pr-review/detect-specialists.d.mts +5 -5
- package/dist/cli/commands/pr-review/detect-specialists.d.mts.map +1 -1
- package/dist/cli/commands/pr-review/detect-specialists.mjs +49 -45
- package/dist/cli/commands/pr-review/detect-specialists.mjs.map +1 -1
- package/dist/cli/install/legacy-paths.d.mts.map +1 -1
- package/dist/cli/install/legacy-paths.mjs +1 -0
- package/dist/cli/install/legacy-paths.mjs.map +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +2 -2
- package/dist/plugin/agents/{apt-pr-review-xplatform.md → apt-pr-review-crossplatform.md} +1 -1
- package/dist/plugin/skills/apt-pr-review/SKILL.md +13 -13
- package/package.json +1 -1
- package/skills/apt-pr-review/SKILL.md +13 -13
- package/src/cli/commands/init.mjs +29 -0
- package/src/cli/commands/pr-review/detect-specialists.mjs +51 -45
- package/src/cli/install/legacy-paths.mjs +1 -0
- /package/prompts/github/{pr_xplatform_agent.md → pr_crossplatform_agent.md} +0 -0
|
@@ -480,12 +480,12 @@ The detector returns:
|
|
|
480
480
|
```json
|
|
481
481
|
{
|
|
482
482
|
"i18n": { "engage": true, "signals": [{ "kind": "...", "detail": "...", "tier": "strong|weak" }], "reason": "strong: locale-file src/locales/fr.json" },
|
|
483
|
-
"
|
|
483
|
+
"crossplatform": { "engage": false, "signals": [], "reason": "no signals" }
|
|
484
484
|
}
|
|
485
485
|
```
|
|
486
486
|
|
|
487
487
|
Engagement precedence (the detector encodes it; you just read the result):
|
|
488
|
-
config `pr_review.specialists.{i18n,
|
|
488
|
+
config `pr_review.specialists.{i18n,crossplatform}` ∈ `auto|always|never` (default
|
|
489
489
|
`auto`) is checked FIRST and is absolute — `always` engages with no scan,
|
|
490
490
|
`never` skips with no scan (even when the diff introduces the first locale
|
|
491
491
|
file). Otherwise `auto` defers to the STRONG/WEAK tier scan.
|
|
@@ -500,17 +500,17 @@ verbatim, e.g. `config override: never`):
|
|
|
500
500
|
// metadata.json (additive key)
|
|
501
501
|
"conditional_specialists": {
|
|
502
502
|
"i18n": { "engaged": true, "reason": "strong: locale-file src/locales/fr.json" },
|
|
503
|
-
"
|
|
503
|
+
"crossplatform": { "engaged": false, "reason": "no signals" }
|
|
504
504
|
}
|
|
505
505
|
```
|
|
506
506
|
|
|
507
507
|
```
|
|
508
508
|
i18n: ENGAGED (strong: locale-file src/locales/fr.json)
|
|
509
|
-
|
|
509
|
+
crossplatform: SKIPPED (no platform signals; config: auto)
|
|
510
510
|
```
|
|
511
511
|
|
|
512
|
-
Store `result.i18n.engage` and `result.
|
|
513
|
-
`
|
|
512
|
+
Store `result.i18n.engage` and `result.crossplatform.engage` as `ENGAGE_I18N` /
|
|
513
|
+
`ENGAGE_CROSSPLATFORM` for Phase 3.
|
|
514
514
|
|
|
515
515
|
**R3: pin the reviewed head SHA (PR reviews only).** After `gh pr diff` fetches into `pr-review-${REVIEW_ID}`, capture the SHA and pass it to `pr-review start-round`. All subsequent evidence reads within this round should reference this pinned SHA, not a fresh `gh pr view` call — otherwise a mid-round force-push invalidates cited line numbers.
|
|
516
516
|
|
|
@@ -839,7 +839,7 @@ DO NOT return full error output in your response -- write it to the file.
|
|
|
839
839
|
node packages/framework/bin/apt-tools.mjs workflow advance . --id ${REVIEW_ID} --phase specialist-review
|
|
840
840
|
```
|
|
841
841
|
|
|
842
|
-
**CRITICAL: Spawn all SELECTED review agents (6-8) in ONE message using parallel Agent tool calls.** The 6 core specialists ALWAYS spawn. Additionally append the **i18n** agent when `ENGAGE_I18N` is true and the **cross-platform** agent when `
|
|
842
|
+
**CRITICAL: Spawn all SELECTED review agents (6-8) in ONE message using parallel Agent tool calls.** The 6 core specialists ALWAYS spawn. Additionally append the **i18n** agent when `ENGAGE_I18N` is true and the **cross-platform** agent when `ENGAGE_CROSSPLATFORM` is true (from Step 4b). So the set is 6, 7, or 8 agents — all in the SAME message.
|
|
843
843
|
|
|
844
844
|
Each agent receives:
|
|
845
845
|
1. The changed file list
|
|
@@ -909,10 +909,10 @@ Agent(
|
|
|
909
909
|
prompt: "{CONSTRUCTED_PROMPT_WITH_FILLED_PLACEHOLDERS}",
|
|
910
910
|
)
|
|
911
911
|
|
|
912
|
-
# Append ONLY if
|
|
912
|
+
# Append ONLY if ENGAGE_CROSSPLATFORM is true:
|
|
913
913
|
Agent(
|
|
914
|
-
name: "
|
|
915
|
-
subagent_type: "apt-pr-review-
|
|
914
|
+
name: "crossplatform-reviewer",
|
|
915
|
+
subagent_type: "apt-pr-review-crossplatform",
|
|
916
916
|
description: "Cross-platform review of PR changes",
|
|
917
917
|
prompt: "{CONSTRUCTED_PROMPT_WITH_FILLED_PLACEHOLDERS}",
|
|
918
918
|
)
|
|
@@ -1027,7 +1027,7 @@ Review Directory: {REVIEW_DIR}
|
|
|
1027
1027
|
|
|
1028
1028
|
Read all finding files from {REVIEW_DIR}/findings/ (excluding validated.md if it exists).
|
|
1029
1029
|
The core finding files are: security.md, quality.md, logic.md, codebase-fit.md, test-integrity.md, performance.md.
|
|
1030
|
-
Additionally read i18n.md and/or
|
|
1030
|
+
Additionally read i18n.md and/or crossplatform.md when present (the conditional specialists write them only when engaged).
|
|
1031
1031
|
|
|
1032
1032
|
For each finding in each file, run the hypothesis-validation process from your instructions.
|
|
1033
1033
|
|
|
@@ -1935,7 +1935,7 @@ Fix agents self-report their outcome via a status line (`FIXED: X | FAILED: Y |
|
|
|
1935
1935
|
| `review.max_iterations` | number | 4 | Maximum fix-review iterations before stopping. |
|
|
1936
1936
|
| `review.optional_agents` | string[] | [] | Reserved for future use. Additional specialist agents to spawn beyond the core 6. |
|
|
1937
1937
|
| `pr_review.specialists.i18n` | `"auto"\|"always"\|"never"` | `"auto"` | Override for the conditional i18n specialist. `always` forces engage, `never` forces skip (over the detector AND over a diff-introduced signal), `auto`/absent defers to `detect-specialists`. Override is the TOP of the precedence chain (ID-01a). |
|
|
1938
|
-
| `pr_review.specialists.
|
|
1938
|
+
| `pr_review.specialists.crossplatform` | `"auto"\|"always"\|"never"` | `"auto"` | Override for the conditional cross-platform specialist. Same semantics as `pr_review.specialists.i18n`. |
|
|
1939
1939
|
|
|
1940
1940
|
---
|
|
1941
1941
|
|
|
@@ -1964,7 +1964,7 @@ Agent(
|
|
|
1964
1964
|
| Test-integrity reviewer | `.claude/agents/apt-pr-review-test-integrity.md` | Read, Write, Grep, Glob | 3 |
|
|
1965
1965
|
| Performance reviewer | `.claude/agents/apt-pr-review-performance.md` | Read, Write, Grep, Glob | 3 |
|
|
1966
1966
|
| i18n reviewer **(conditional)** | `.claude/agents/apt-pr-review-i18n.md` | Read, Write, Grep, Glob | 3 — spawned only when `detect-specialists` engages i18n (or config `always`) |
|
|
1967
|
-
| Cross-platform reviewer **(conditional)** | `.claude/agents/apt-pr-review-
|
|
1967
|
+
| Cross-platform reviewer **(conditional)** | `.claude/agents/apt-pr-review-crossplatform.md` | Read, Write, Grep, Glob | 3 — spawned only when `detect-specialists` engages crossplatform (or config `always`) |
|
|
1968
1968
|
| Finding validator | `.claude/agents/apt-pr-review-validator.md` | Read, Write, Grep, Glob | 4 |
|
|
1969
1969
|
| Fix agent (x3) | `.claude/agents/apt-pr-review-fixer.md` | Read, Write, Edit, Bash, Grep, Glob | 6 |
|
|
1970
1970
|
| Self-reviewer | `.claude/agents/apt-pr-review-self-reviewer.md` | Read, Write, Edit, Bash, Grep, Glob | 7 |
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
mkdirSync,
|
|
24
24
|
readdirSync,
|
|
25
25
|
readFileSync,
|
|
26
|
+
unlinkSync,
|
|
26
27
|
writeFileSync,
|
|
27
28
|
} from 'node:fs'
|
|
28
29
|
import { basename, dirname, join, resolve } from 'node:path'
|
|
@@ -1276,6 +1277,34 @@ export async function cmdInit(projectDir, extraArgs = []) {
|
|
|
1276
1277
|
}
|
|
1277
1278
|
}
|
|
1278
1279
|
|
|
1280
|
+
// Orphan-removal sweep — the cross-platform PR-review agent was renamed
|
|
1281
|
+
// from `apt-pr-review-xplatform` to `apt-pr-review-crossplatform`. The
|
|
1282
|
+
// install pipeline writes the current agent set + a fresh manifest but
|
|
1283
|
+
// has NO stale-removal pass (readManifest is consumed only by
|
|
1284
|
+
// skill-installs.mjs + check-version.mjs, never pipeline.mjs), so a
|
|
1285
|
+
// rename leaves the OLD-named file orphaned in every active runtime
|
|
1286
|
+
// root. Targeted, best-effort unlink of that ONE basename from each
|
|
1287
|
+
// requested runtime's install root (the agent lands under different
|
|
1288
|
+
// per-runtime subdirs: claude→agents, pi→skills, windsurf→workflows).
|
|
1289
|
+
// Mirrors runLegacyCleanup's conservative idempotent posture; ENOENT
|
|
1290
|
+
// and any other unlink error is swallowed.
|
|
1291
|
+
const ORPHANED_AGENT_BASENAME = 'apt-pr-review-xplatform.md'
|
|
1292
|
+
const ORPHAN_AGENT_SUBDIRS = ['agents', 'skills', 'workflows', 'rules']
|
|
1293
|
+
for (const runtimeId of effectiveAnswers.runtimes) {
|
|
1294
|
+
const runtime = getRuntime(runtimeId)
|
|
1295
|
+
if (!runtime?.installRoot) continue
|
|
1296
|
+
for (const subdir of ORPHAN_AGENT_SUBDIRS) {
|
|
1297
|
+
const orphanPath = join(installTarget, runtime.installRoot, subdir, ORPHANED_AGENT_BASENAME)
|
|
1298
|
+
if (existsSync(orphanPath)) {
|
|
1299
|
+
try {
|
|
1300
|
+
unlinkSync(orphanPath)
|
|
1301
|
+
} catch {
|
|
1302
|
+
// best-effort — a missing/locked orphan must not fail init
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1279
1308
|
const results = await installRuntimes(
|
|
1280
1309
|
effectiveAnswers.runtimes,
|
|
1281
1310
|
canonicalRoot,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* commands/pr-review/detect-specialists.mjs — deterministic detector for the
|
|
3
3
|
* two CONDITIONAL /apt:pr-review specialists (i18n + cross-platform).
|
|
4
4
|
*
|
|
5
|
-
* Restores the 2.8 `i18n-reviewer` + `
|
|
5
|
+
* Restores the 2.8 `i18n-reviewer` + `crossplatform` perspectives but gates their
|
|
6
6
|
* Phase-3 spawn on a reproducible, language/platform/ecosystem-agnostic scan
|
|
7
7
|
* with a config override on top. The SKILL orchestrator NEVER decides
|
|
8
8
|
* engagement by reading the diff itself — it calls this detector and obeys the
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Engagement model (spec ID-01/ID-01a/ID-02/ID-02a/ID-03/ID-03a/ID-03b):
|
|
12
12
|
*
|
|
13
13
|
* 1. Config check FIRST and short-circuit (ID-01a). `.aperant/config.json`
|
|
14
|
-
* pr_review.specialists.{i18n,
|
|
14
|
+
* pr_review.specialists.{i18n,crossplatform} ∈ {auto|always|never}, default
|
|
15
15
|
* auto. `always` → engage (no scan); `never` → skip (no scan); only
|
|
16
16
|
* `auto`/absent proceeds. Config is the TOP of the precedence chain — a
|
|
17
17
|
* diff-introduced STRONG signal does NOT override `never`.
|
|
@@ -44,7 +44,7 @@ import { err, ok } from '../../util/result.mjs'
|
|
|
44
44
|
* @typedef {{ engage: boolean, signals: Signal[], reason: string }} SpecialistDecision
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
|
-
// Manifests scanned for dependency-substring i18n/
|
|
47
|
+
// Manifests scanned for dependency-substring i18n/crossplatform library markers.
|
|
48
48
|
// No YAML/TOML parse — substring/regex on the raw text, mirroring
|
|
49
49
|
// toolchain-detect.mjs:isBerryNodeLinker (R2).
|
|
50
50
|
const MANIFEST_FILES = [
|
|
@@ -334,10 +334,10 @@ function scanSourceForI18nTokens(detailLabel, text) {
|
|
|
334
334
|
return found
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
// ──
|
|
337
|
+
// ── crossplatform matchers ──────────────────────────────────────────────────────
|
|
338
338
|
|
|
339
339
|
// Cross-platform app frameworks (STRONG) matched as manifest substrings.
|
|
340
|
-
const
|
|
340
|
+
const CROSSPLATFORM_FRAMEWORK_SUBSTRINGS = [
|
|
341
341
|
'electron',
|
|
342
342
|
'@tauri-apps',
|
|
343
343
|
'react-native',
|
|
@@ -358,13 +358,13 @@ const XPL_FRAMEWORK_SUBSTRINGS = [
|
|
|
358
358
|
* @param {string} text
|
|
359
359
|
* @returns {Array<{kind:string, detail:string, tier:string}>}
|
|
360
360
|
*/
|
|
361
|
-
function
|
|
361
|
+
function scanManifestForCrossplatform(manifestName, text) {
|
|
362
362
|
const found = []
|
|
363
363
|
const lower = text.toLowerCase()
|
|
364
|
-
for (const fw of
|
|
364
|
+
for (const fw of CROSSPLATFORM_FRAMEWORK_SUBSTRINGS) {
|
|
365
365
|
if (lower.includes(fw)) {
|
|
366
366
|
found.push({
|
|
367
|
-
kind: '
|
|
367
|
+
kind: 'crossplatform-framework',
|
|
368
368
|
detail: `${fw} in ${manifestName}`,
|
|
369
369
|
tier: 'strong',
|
|
370
370
|
})
|
|
@@ -378,7 +378,7 @@ function scanManifestForXplatform(manifestName, text) {
|
|
|
378
378
|
|
|
379
379
|
// Compile-time platform guards (STRONG) — source-token regexes spanning
|
|
380
380
|
// Rust / Go / C/C++ / CMake / Swift.
|
|
381
|
-
const
|
|
381
|
+
const CROSSPLATFORM_COMPILE_GUARD_RES = [
|
|
382
382
|
{ re: /#\[cfg\(target_os/, label: '#[cfg(target_os' },
|
|
383
383
|
{ re: /\bcfg!\s*\(\s*windows\b/, label: 'cfg!(windows)' },
|
|
384
384
|
{ re: /\/\/go:build\s+(windows|darwin|linux)/, label: '//go:build <os>' },
|
|
@@ -393,7 +393,7 @@ const XPL_COMPILE_GUARD_RES = [
|
|
|
393
393
|
]
|
|
394
394
|
|
|
395
395
|
// Lone runtime platform checks (WEAK).
|
|
396
|
-
const
|
|
396
|
+
const CROSSPLATFORM_WEAK_TOKEN_RES = [
|
|
397
397
|
{ re: /\bprocess\.platform\b/, label: 'process.platform' },
|
|
398
398
|
{ re: /\bos\.platform\s*\(/, label: 'os.platform()' },
|
|
399
399
|
{ re: /\bsys\.platform\b/, label: 'sys.platform' },
|
|
@@ -405,28 +405,28 @@ const XPL_WEAK_TOKEN_RES = [
|
|
|
405
405
|
]
|
|
406
406
|
|
|
407
407
|
/**
|
|
408
|
-
* Scan one source blob for
|
|
408
|
+
* Scan one source blob for crossplatform compile-guard (STRONG) + lone runtime
|
|
409
409
|
* (WEAK) tokens.
|
|
410
410
|
*
|
|
411
411
|
* @param {string} detailLabel
|
|
412
412
|
* @param {string} text
|
|
413
413
|
* @returns {Array<{kind:string, detail:string, tier:string}>}
|
|
414
414
|
*/
|
|
415
|
-
function
|
|
415
|
+
function scanSourceForCrossplatformTokens(detailLabel, text) {
|
|
416
416
|
const found = []
|
|
417
|
-
for (const { re, label } of
|
|
417
|
+
for (const { re, label } of CROSSPLATFORM_COMPILE_GUARD_RES) {
|
|
418
418
|
if (re.test(text)) {
|
|
419
419
|
found.push({
|
|
420
|
-
kind: '
|
|
420
|
+
kind: 'crossplatform-guard',
|
|
421
421
|
detail: `${label} in ${detailLabel}`,
|
|
422
422
|
tier: 'strong',
|
|
423
423
|
})
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
|
-
for (const { re, label } of
|
|
426
|
+
for (const { re, label } of CROSSPLATFORM_WEAK_TOKEN_RES) {
|
|
427
427
|
if (re.test(text)) {
|
|
428
428
|
found.push({
|
|
429
|
-
kind: '
|
|
429
|
+
kind: 'crossplatform-runtime',
|
|
430
430
|
detail: `${label} in ${detailLabel}`,
|
|
431
431
|
tier: 'weak',
|
|
432
432
|
})
|
|
@@ -496,7 +496,7 @@ function detectCiMatrix(workflowName, text) {
|
|
|
496
496
|
}
|
|
497
497
|
if (osFamilies.size > 1) {
|
|
498
498
|
return {
|
|
499
|
-
kind: '
|
|
499
|
+
kind: 'crossplatform-ci-matrix',
|
|
500
500
|
detail: `multi-OS CI matrix in ${workflowName} (${osFamilies.size} OS families)`,
|
|
501
501
|
tier: 'strong',
|
|
502
502
|
}
|
|
@@ -519,7 +519,7 @@ function detectPlatformDirSiblings(relPaths) {
|
|
|
519
519
|
const hasLinuxDir = relPaths.some((p) => p.split('/').includes('linux'))
|
|
520
520
|
if (hasWindowsDir && hasMacosDir && hasLinuxDir) {
|
|
521
521
|
found.push({
|
|
522
|
-
kind: '
|
|
522
|
+
kind: 'crossplatform-dir-siblings',
|
|
523
523
|
detail: 'platform dir siblings windows/ + macos/ + linux/ co-present',
|
|
524
524
|
tier: 'strong',
|
|
525
525
|
})
|
|
@@ -531,7 +531,7 @@ function detectPlatformDirSiblings(relPaths) {
|
|
|
531
531
|
})
|
|
532
532
|
if (hasXcodeproj && hasGradle) {
|
|
533
533
|
found.push({
|
|
534
|
-
kind: '
|
|
534
|
+
kind: 'crossplatform-dir-siblings',
|
|
535
535
|
detail: '*.xcodeproj + build.gradle co-presence',
|
|
536
536
|
tier: 'strong',
|
|
537
537
|
})
|
|
@@ -545,12 +545,14 @@ function detectPlatformDirSiblings(relPaths) {
|
|
|
545
545
|
* @param {string[]} relPaths
|
|
546
546
|
* @returns {Array<{kind:string, detail:string, tier:string}>}
|
|
547
547
|
*/
|
|
548
|
-
function
|
|
548
|
+
function detectWeakCrossplatformFiles(relPaths) {
|
|
549
549
|
const found = []
|
|
550
550
|
for (const p of relPaths) {
|
|
551
551
|
const ext = extname(p.toLowerCase())
|
|
552
|
-
if (ext === '.plist')
|
|
553
|
-
|
|
552
|
+
if (ext === '.plist')
|
|
553
|
+
found.push({ kind: 'crossplatform-file', detail: `plist ${p}`, tier: 'weak' })
|
|
554
|
+
else if (ext === '.rc')
|
|
555
|
+
found.push({ kind: 'crossplatform-file', detail: `rc ${p}`, tier: 'weak' })
|
|
554
556
|
}
|
|
555
557
|
return found
|
|
556
558
|
}
|
|
@@ -731,7 +733,7 @@ function applyScope(baselineSignals, diffSignals, diffTouchedPaths, scope) {
|
|
|
731
733
|
* Read `.aperant/config.json:pr_review.specialists` (best-effort).
|
|
732
734
|
*
|
|
733
735
|
* @param {string} root
|
|
734
|
-
* @returns {{ i18n?: string,
|
|
736
|
+
* @returns {{ i18n?: string, crossplatform?: string }}
|
|
735
737
|
*/
|
|
736
738
|
function readSpecialistConfig(root) {
|
|
737
739
|
try {
|
|
@@ -759,15 +761,15 @@ function normalizeMode(v) {
|
|
|
759
761
|
/**
|
|
760
762
|
* The core detector. Pure over its inputs (reads the filesystem at `root` and
|
|
761
763
|
* the supplied `diffText`); returns the full
|
|
762
|
-
* `{ i18n,
|
|
764
|
+
* `{ i18n, crossplatform }` decision object.
|
|
763
765
|
*
|
|
764
766
|
* @param {object} opts
|
|
765
767
|
* @param {string} opts.root absolute project dir
|
|
766
768
|
* @param {string} [opts.diffText] unified-diff text (optional)
|
|
767
769
|
* @param {'repo'|'diff'} [opts.scope] default 'repo'
|
|
768
|
-
* @param {{i18n?:string,
|
|
770
|
+
* @param {{i18n?:string, crossplatform?:string}} [opts.config] override config
|
|
769
771
|
* (read from .aperant/config.json when omitted)
|
|
770
|
-
* @returns {{ i18n: SpecialistDecision,
|
|
772
|
+
* @returns {{ i18n: SpecialistDecision, crossplatform: SpecialistDecision }}
|
|
771
773
|
*/
|
|
772
774
|
export function detectSpecialists(opts) {
|
|
773
775
|
const root = resolve(opts.root)
|
|
@@ -776,7 +778,7 @@ export function detectSpecialists(opts) {
|
|
|
776
778
|
const diffText = opts.diffText ?? ''
|
|
777
779
|
|
|
778
780
|
const i18nMode = normalizeMode(config.i18n)
|
|
779
|
-
const
|
|
781
|
+
const crossplatformMode = normalizeMode(config.crossplatform)
|
|
780
782
|
|
|
781
783
|
// Parse diff once (shared by both specialists).
|
|
782
784
|
const diff = diffText ? parseUnifiedDiff(diffText) : { touchedPaths: [], addedText: '' }
|
|
@@ -791,17 +793,17 @@ export function detectSpecialists(opts) {
|
|
|
791
793
|
collectDiff: collectI18nDiff,
|
|
792
794
|
})
|
|
793
795
|
|
|
794
|
-
// ──
|
|
795
|
-
const
|
|
796
|
-
mode:
|
|
796
|
+
// ── crossplatform ───────────────────────────────────────────────────────────
|
|
797
|
+
const crossplatform = decideSpecialist({
|
|
798
|
+
mode: crossplatformMode,
|
|
797
799
|
root,
|
|
798
800
|
diff,
|
|
799
801
|
scope,
|
|
800
|
-
collectBaseline:
|
|
801
|
-
collectDiff:
|
|
802
|
+
collectBaseline: collectCrossplatformBaseline,
|
|
803
|
+
collectDiff: collectCrossplatformDiff,
|
|
802
804
|
})
|
|
803
805
|
|
|
804
|
-
return { i18n,
|
|
806
|
+
return { i18n, crossplatform }
|
|
805
807
|
}
|
|
806
808
|
|
|
807
809
|
/**
|
|
@@ -880,7 +882,7 @@ function collectI18nDiff(diff) {
|
|
|
880
882
|
return dedupeSignals(signals)
|
|
881
883
|
}
|
|
882
884
|
|
|
883
|
-
function
|
|
885
|
+
function collectCrossplatformBaseline(root) {
|
|
884
886
|
const signals = []
|
|
885
887
|
const files = walkFiles(root)
|
|
886
888
|
|
|
@@ -889,12 +891,12 @@ function collectXplatformBaseline(root) {
|
|
|
889
891
|
const abs = join(root, manifest)
|
|
890
892
|
if (!existsSync(abs)) continue
|
|
891
893
|
const text = readSafe(abs)
|
|
892
|
-
if (text) signals.push(...
|
|
894
|
+
if (text) signals.push(...scanManifestForCrossplatform(manifest, text))
|
|
893
895
|
}
|
|
894
896
|
// pubspec.yaml present → Flutter (its presence alone is the flutter marker).
|
|
895
897
|
if (existsSync(join(root, 'pubspec.yaml'))) {
|
|
896
898
|
signals.push({
|
|
897
|
-
kind: '
|
|
899
|
+
kind: 'crossplatform-framework',
|
|
898
900
|
detail: 'flutter (pubspec.yaml present)',
|
|
899
901
|
tier: 'strong',
|
|
900
902
|
})
|
|
@@ -930,9 +932,13 @@ function collectXplatformBaseline(root) {
|
|
|
930
932
|
) {
|
|
931
933
|
const text = readSafe(join(root, rel))
|
|
932
934
|
if (text) {
|
|
933
|
-
for (const { re, label } of
|
|
935
|
+
for (const { re, label } of CROSSPLATFORM_COMPILE_GUARD_RES) {
|
|
934
936
|
if (re.test(text)) {
|
|
935
|
-
signals.push({
|
|
937
|
+
signals.push({
|
|
938
|
+
kind: 'crossplatform-guard',
|
|
939
|
+
detail: `${label} in ${rel}`,
|
|
940
|
+
tier: 'strong',
|
|
941
|
+
})
|
|
936
942
|
}
|
|
937
943
|
}
|
|
938
944
|
}
|
|
@@ -941,25 +947,25 @@ function collectXplatformBaseline(root) {
|
|
|
941
947
|
|
|
942
948
|
// Platform dir siblings + weak files.
|
|
943
949
|
signals.push(...detectPlatformDirSiblings(files))
|
|
944
|
-
signals.push(...
|
|
950
|
+
signals.push(...detectWeakCrossplatformFiles(files))
|
|
945
951
|
|
|
946
952
|
return dedupeSignals(signals)
|
|
947
953
|
}
|
|
948
954
|
|
|
949
|
-
function
|
|
955
|
+
function collectCrossplatformDiff(diff) {
|
|
950
956
|
const signals = []
|
|
951
957
|
if (diff.addedText) {
|
|
952
958
|
const lowerAdded = diff.addedText.toLowerCase()
|
|
953
|
-
for (const fw of
|
|
959
|
+
for (const fw of CROSSPLATFORM_FRAMEWORK_SUBSTRINGS) {
|
|
954
960
|
if (lowerAdded.includes(fw)) {
|
|
955
961
|
signals.push({
|
|
956
|
-
kind: '
|
|
962
|
+
kind: 'crossplatform-framework',
|
|
957
963
|
detail: `${fw} (diff added line)`,
|
|
958
964
|
tier: 'strong',
|
|
959
965
|
})
|
|
960
966
|
}
|
|
961
967
|
}
|
|
962
|
-
signals.push(...
|
|
968
|
+
signals.push(...scanSourceForCrossplatformTokens('diff', diff.addedText))
|
|
963
969
|
}
|
|
964
970
|
// CI matrix introduced in the diff.
|
|
965
971
|
for (const rel of diff.touchedPaths) {
|
|
@@ -968,7 +974,7 @@ function collectXplatformDiff(diff) {
|
|
|
968
974
|
if (m) signals.push(m)
|
|
969
975
|
}
|
|
970
976
|
}
|
|
971
|
-
signals.push(...
|
|
977
|
+
signals.push(...detectWeakCrossplatformFiles(diff.touchedPaths))
|
|
972
978
|
return dedupeSignals(signals)
|
|
973
979
|
}
|
|
974
980
|
|
|
@@ -1018,6 +1024,6 @@ export function cmdDetectSpecialists(projectDir, extraArgs = []) {
|
|
|
1018
1024
|
command: 'pr-review-detect-specialists',
|
|
1019
1025
|
scope,
|
|
1020
1026
|
i18n: result.i18n,
|
|
1021
|
-
|
|
1027
|
+
crossplatform: result.crossplatform,
|
|
1022
1028
|
})
|
|
1023
1029
|
}
|
|
@@ -181,6 +181,7 @@ export const LEGACY_INSTALL_PATHS = Object.freeze({
|
|
|
181
181
|
'agents/apt-improver.md',
|
|
182
182
|
'agents/apt-planner.md',
|
|
183
183
|
'agents/apt-pr-review-codebase-fit.md',
|
|
184
|
+
'agents/apt-pr-review-crossplatform.md',
|
|
184
185
|
'agents/apt-pr-review-fixer.md',
|
|
185
186
|
'agents/apt-pr-review-i18n.md',
|
|
186
187
|
'agents/apt-pr-review-logic.md',
|
|
File without changes
|