@codyswann/lisa 2.128.1 → 2.129.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/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/scripts/doctor-report.mjs +136 -0
- package/plugins/lisa/scripts/plugin-sync-explain.mjs +64 -7
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/doctor-report.mjs +136 -0
- package/plugins/lisa-agy/scripts/plugin-sync-explain.mjs +64 -7
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/scripts/doctor-report.mjs +136 -0
- package/plugins/lisa-copilot/scripts/plugin-sync-explain.mjs +64 -7
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/scripts/doctor-report.mjs +136 -0
- package/plugins/lisa-cursor/scripts/plugin-sync-explain.mjs +64 -7
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/commands/exploratory-qa.md +1 -1
- package/plugins/lisa-expo/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/lisa-expo-agy/commands/exploratory-qa.md +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-agy/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/commands/exploratory-qa.md +1 -1
- package/plugins/lisa-expo-copilot/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/commands/exploratory-qa.md +1 -1
- package/plugins/lisa-expo-cursor/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/commands/exploratory-qa.md +1 -1
- package/plugins/lisa-harper-fabric/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/lisa-harper-fabric-agy/commands/exploratory-qa.md +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/commands/exploratory-qa.md +1 -1
- package/plugins/lisa-harper-fabric-copilot/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/commands/exploratory-qa.md +1 -1
- package/plugins/lisa-harper-fabric-cursor/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/commands/exploratory-qa.md +1 -1
- package/plugins/lisa-rails/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/lisa-rails-agy/commands/exploratory-qa.md +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-agy/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/commands/exploratory-qa.md +1 -1
- package/plugins/lisa-rails-copilot/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/commands/exploratory-qa.md +1 -1
- package/plugins/lisa-rails-cursor/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/scripts/doctor-report.mjs +136 -0
- package/plugins/src/base/scripts/plugin-sync-explain.mjs +64 -7
- package/plugins/src/expo/commands/exploratory-qa.md +1 -1
- package/plugins/src/expo/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/src/harper-fabric/commands/exploratory-qa.md +1 -1
- package/plugins/src/harper-fabric/skills/exploratory-qa/SKILL.md +7 -4
- package/plugins/src/rails/commands/exploratory-qa.md +1 -1
- package/plugins/src/rails/skills/exploratory-qa/SKILL.md +7 -4
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* repo adds real readiness probes. Keep this file dependency-free so future
|
|
7
7
|
* doctor scripts can reuse it from plugin distributions and downstream repos.
|
|
8
8
|
*/
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import process from "node:process";
|
|
12
|
+
|
|
13
|
+
import { getPluginSyncResult } from "./plugin-sync-explain.mjs";
|
|
9
14
|
|
|
10
15
|
export const DOCTOR_STATUSES = ["PASS", "WARN", "FAIL", "SKIP"];
|
|
11
16
|
export const DOCTOR_VERDICTS = ["READY", "READY_WITH_WARNINGS", "NOT_READY"];
|
|
@@ -34,6 +39,85 @@ export const DOCTOR_VERDICTS = ["READY", "READY_WITH_WARNINGS", "NOT_READY"];
|
|
|
34
39
|
* }} DoctorReportInput
|
|
35
40
|
*/
|
|
36
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} root
|
|
44
|
+
* @returns {DoctorGroup}
|
|
45
|
+
*/
|
|
46
|
+
export function createPluginSyncDoctorGroup(root = process.cwd()) {
|
|
47
|
+
const repoRoot = path.resolve(root);
|
|
48
|
+
if (
|
|
49
|
+
!existsSync(path.join(repoRoot, "plugins", "src")) &&
|
|
50
|
+
!existsSync(path.join(repoRoot, "plugins"))
|
|
51
|
+
) {
|
|
52
|
+
return {
|
|
53
|
+
id: "plugin-sync",
|
|
54
|
+
title: "Plugin source/generated sync",
|
|
55
|
+
checks: [
|
|
56
|
+
{
|
|
57
|
+
id: "plugin-sync",
|
|
58
|
+
status: "SKIP",
|
|
59
|
+
summary: "plugin sync check is not applicable",
|
|
60
|
+
observed:
|
|
61
|
+
"No plugins/ or plugins/src/ directory was found in this repository.",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const result = getPluginSyncResult(repoRoot);
|
|
69
|
+
if (!result.readOnly) {
|
|
70
|
+
return {
|
|
71
|
+
id: "plugin-sync",
|
|
72
|
+
title: "Plugin source/generated sync",
|
|
73
|
+
checks: [
|
|
74
|
+
{
|
|
75
|
+
id: "plugin-sync",
|
|
76
|
+
status: "FAIL",
|
|
77
|
+
summary: "plugin sync readiness check mutated the working tree",
|
|
78
|
+
observed:
|
|
79
|
+
"Git status changed while collecting plugin sync evidence.",
|
|
80
|
+
remediation:
|
|
81
|
+
"Run `git status --short`, inspect the unexpected changes, and fix plugin-sync-explain before trusting doctor output.",
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
id: "plugin-sync",
|
|
89
|
+
title: "Plugin source/generated sync",
|
|
90
|
+
checks: [
|
|
91
|
+
{
|
|
92
|
+
id: "plugin-sync",
|
|
93
|
+
status: result.verdict,
|
|
94
|
+
summary:
|
|
95
|
+
result.verdict === "PASS"
|
|
96
|
+
? "plugin source and generated artifacts are in sync"
|
|
97
|
+
: `plugin sync drift detected: ${result.driftClass}`,
|
|
98
|
+
observed: renderPluginSyncObserved(result),
|
|
99
|
+
remediation: renderPluginSyncRemediation(result),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
id: "plugin-sync",
|
|
106
|
+
title: "Plugin source/generated sync",
|
|
107
|
+
checks: [
|
|
108
|
+
{
|
|
109
|
+
id: "plugin-sync",
|
|
110
|
+
status: "FAIL",
|
|
111
|
+
summary: "plugin sync readiness check failed",
|
|
112
|
+
observed: error instanceof Error ? error.message : String(error),
|
|
113
|
+
remediation:
|
|
114
|
+
"Run `/lisa:plugin-sync-explain` or `bun run check:plugins` to inspect plugin sync health directly.",
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
37
121
|
/**
|
|
38
122
|
* @param {readonly DoctorGroup[]} groups
|
|
39
123
|
* @returns {DoctorVerdict}
|
|
@@ -141,3 +225,55 @@ function normalizeCheck(check) {
|
|
|
141
225
|
status: normalizedStatus,
|
|
142
226
|
};
|
|
143
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @param {import("./plugin-sync-explain.mjs").PluginSyncResult} result
|
|
231
|
+
* @returns {string}
|
|
232
|
+
*/
|
|
233
|
+
function renderPluginSyncObserved(result) {
|
|
234
|
+
if (result.verdict === "PASS") {
|
|
235
|
+
return "Drift class IN_SYNC; plugin sync evidence was collected read-only.";
|
|
236
|
+
}
|
|
237
|
+
const paths = result.affectedPaths.length
|
|
238
|
+
? result.affectedPaths.join(", ")
|
|
239
|
+
: "none";
|
|
240
|
+
return `Drift class ${result.driftClass}; affected paths: ${paths}.`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @param {import("./plugin-sync-explain.mjs").PluginSyncResult} result
|
|
245
|
+
* @returns {string | undefined}
|
|
246
|
+
*/
|
|
247
|
+
function renderPluginSyncRemediation(result) {
|
|
248
|
+
if (result.verdict === "PASS") {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const nextAction = pluginSyncNextAction(result.driftClass);
|
|
253
|
+
const details = result.remediations
|
|
254
|
+
.map(item => `${item.path}: ${item.nextAction}`)
|
|
255
|
+
.join(" ");
|
|
256
|
+
const explain =
|
|
257
|
+
"Run `/lisa:plugin-sync-explain` or `bun run check:plugins` for the detailed drift report.";
|
|
258
|
+
|
|
259
|
+
return [nextAction, details, explain].filter(Boolean).join(" ");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @param {string} driftClass
|
|
264
|
+
* @returns {string}
|
|
265
|
+
*/
|
|
266
|
+
function pluginSyncNextAction(driftClass) {
|
|
267
|
+
switch (driftClass) {
|
|
268
|
+
case "SOURCE_NOT_BUILT":
|
|
269
|
+
return "Next action: run `bun run build:plugins && bun run check:plugins`, then commit source plus regenerated plugin artifacts.";
|
|
270
|
+
case "OUT_OF_SYNC":
|
|
271
|
+
return "Next action: review source and generated plugin changes, keep `plugins/src` authoritative, then run `bun run build:plugins && bun run check:plugins`.";
|
|
272
|
+
case "GENERATED_ONLY":
|
|
273
|
+
return "Next action: move generated-only edits upstream to `plugins/src`, or remove the generated artifact drift if it should not ship.";
|
|
274
|
+
case "MARKETPLACE_REGISTRATION_DRIFT":
|
|
275
|
+
return "Next action: align marketplace registration with the built plugin manifests, or remove stale marketplace entries.";
|
|
276
|
+
default:
|
|
277
|
+
return `Next action: inspect plugin sync drift class ${driftClass}.`;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -50,6 +50,25 @@ const GIT_BIN = "/usr/bin/git";
|
|
|
50
50
|
* readonly readOnly: boolean
|
|
51
51
|
* readonly text: string
|
|
52
52
|
* }} PluginSyncReport
|
|
53
|
+
*
|
|
54
|
+
* @typedef {{
|
|
55
|
+
* readonly path: string
|
|
56
|
+
* readonly counterpart?: string
|
|
57
|
+
* readonly classification: string
|
|
58
|
+
* readonly nextAction: string
|
|
59
|
+
* }} PluginSyncRemediation
|
|
60
|
+
*
|
|
61
|
+
* @typedef {{
|
|
62
|
+
* readonly root: string
|
|
63
|
+
* readonly verdict: "PASS" | "WARN"
|
|
64
|
+
* readonly driftClass: string
|
|
65
|
+
* readonly affectedPaths: readonly string[]
|
|
66
|
+
* readonly remediations: readonly PluginSyncRemediation[]
|
|
67
|
+
* readonly findings: readonly PluginSyncFinding[]
|
|
68
|
+
* readonly statusBefore: string
|
|
69
|
+
* readonly statusAfter: string
|
|
70
|
+
* readonly readOnly: boolean
|
|
71
|
+
* }} PluginSyncResult
|
|
53
72
|
*/
|
|
54
73
|
|
|
55
74
|
/**
|
|
@@ -57,6 +76,25 @@ const GIT_BIN = "/usr/bin/git";
|
|
|
57
76
|
* @returns {PluginSyncReport}
|
|
58
77
|
*/
|
|
59
78
|
export function explainPluginSync(root = process.cwd()) {
|
|
79
|
+
const result = getPluginSyncResult(root);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
root: result.root,
|
|
83
|
+
findings: result.findings,
|
|
84
|
+
statusBefore: result.statusBefore,
|
|
85
|
+
statusAfter: result.statusAfter,
|
|
86
|
+
readOnly: result.readOnly,
|
|
87
|
+
text: renderPluginSyncReport(result),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Return a structured, read-only plugin sync result for readiness surfaces such
|
|
93
|
+
* as doctor. CLI callers should still render this through renderPluginSyncReport.
|
|
94
|
+
* @param {string} root
|
|
95
|
+
* @returns {PluginSyncResult}
|
|
96
|
+
*/
|
|
97
|
+
export function getPluginSyncResult(root = process.cwd()) {
|
|
60
98
|
const repoRoot = path.resolve(root);
|
|
61
99
|
const statusBefore = gitStatus(repoRoot);
|
|
62
100
|
const statusEntries = parseGitStatus(statusBefore);
|
|
@@ -71,20 +109,23 @@ export function explainPluginSync(root = process.cwd()) {
|
|
|
71
109
|
];
|
|
72
110
|
const statusAfter = gitStatus(repoRoot);
|
|
73
111
|
const readOnly = statusAfter === statusBefore;
|
|
112
|
+
const driftClass = highestClassification(findings);
|
|
74
113
|
|
|
75
114
|
return {
|
|
76
115
|
root: repoRoot,
|
|
116
|
+
verdict: findings.length === 0 ? "PASS" : "WARN",
|
|
117
|
+
driftClass,
|
|
118
|
+
affectedPaths: affectedPaths(findings),
|
|
119
|
+
remediations: findings.map(finding => ({
|
|
120
|
+
path: finding.path,
|
|
121
|
+
counterpart: finding.counterpart,
|
|
122
|
+
classification: finding.classification,
|
|
123
|
+
nextAction: finding.nextAction,
|
|
124
|
+
})),
|
|
77
125
|
findings,
|
|
78
126
|
statusBefore,
|
|
79
127
|
statusAfter,
|
|
80
128
|
readOnly,
|
|
81
|
-
text: renderPluginSyncReport({
|
|
82
|
-
root: repoRoot,
|
|
83
|
-
findings,
|
|
84
|
-
statusBefore,
|
|
85
|
-
statusAfter,
|
|
86
|
-
readOnly,
|
|
87
|
-
}),
|
|
88
129
|
};
|
|
89
130
|
}
|
|
90
131
|
|
|
@@ -142,6 +183,22 @@ function highestClassification(findings) {
|
|
|
142
183
|
return "IN_SYNC";
|
|
143
184
|
}
|
|
144
185
|
|
|
186
|
+
/**
|
|
187
|
+
* @param {readonly PluginSyncFinding[]} findings
|
|
188
|
+
* @returns {string[]}
|
|
189
|
+
*/
|
|
190
|
+
function affectedPaths(findings) {
|
|
191
|
+
return [
|
|
192
|
+
...new Set(
|
|
193
|
+
findings.flatMap(finding =>
|
|
194
|
+
[finding.path, finding.counterpart].filter(
|
|
195
|
+
pathValue => typeof pathValue === "string" && pathValue.length > 0
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
),
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
|
|
145
202
|
/**
|
|
146
203
|
* @param {readonly { readonly code: string, readonly file: string }[]} entries
|
|
147
204
|
* @param {ReadonlyMap<string, Buffer> | undefined} expectedGeneratedFiles
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* repo adds real readiness probes. Keep this file dependency-free so future
|
|
7
7
|
* doctor scripts can reuse it from plugin distributions and downstream repos.
|
|
8
8
|
*/
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import process from "node:process";
|
|
12
|
+
|
|
13
|
+
import { getPluginSyncResult } from "./plugin-sync-explain.mjs";
|
|
9
14
|
|
|
10
15
|
export const DOCTOR_STATUSES = ["PASS", "WARN", "FAIL", "SKIP"];
|
|
11
16
|
export const DOCTOR_VERDICTS = ["READY", "READY_WITH_WARNINGS", "NOT_READY"];
|
|
@@ -34,6 +39,85 @@ export const DOCTOR_VERDICTS = ["READY", "READY_WITH_WARNINGS", "NOT_READY"];
|
|
|
34
39
|
* }} DoctorReportInput
|
|
35
40
|
*/
|
|
36
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} root
|
|
44
|
+
* @returns {DoctorGroup}
|
|
45
|
+
*/
|
|
46
|
+
export function createPluginSyncDoctorGroup(root = process.cwd()) {
|
|
47
|
+
const repoRoot = path.resolve(root);
|
|
48
|
+
if (
|
|
49
|
+
!existsSync(path.join(repoRoot, "plugins", "src")) &&
|
|
50
|
+
!existsSync(path.join(repoRoot, "plugins"))
|
|
51
|
+
) {
|
|
52
|
+
return {
|
|
53
|
+
id: "plugin-sync",
|
|
54
|
+
title: "Plugin source/generated sync",
|
|
55
|
+
checks: [
|
|
56
|
+
{
|
|
57
|
+
id: "plugin-sync",
|
|
58
|
+
status: "SKIP",
|
|
59
|
+
summary: "plugin sync check is not applicable",
|
|
60
|
+
observed:
|
|
61
|
+
"No plugins/ or plugins/src/ directory was found in this repository.",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const result = getPluginSyncResult(repoRoot);
|
|
69
|
+
if (!result.readOnly) {
|
|
70
|
+
return {
|
|
71
|
+
id: "plugin-sync",
|
|
72
|
+
title: "Plugin source/generated sync",
|
|
73
|
+
checks: [
|
|
74
|
+
{
|
|
75
|
+
id: "plugin-sync",
|
|
76
|
+
status: "FAIL",
|
|
77
|
+
summary: "plugin sync readiness check mutated the working tree",
|
|
78
|
+
observed:
|
|
79
|
+
"Git status changed while collecting plugin sync evidence.",
|
|
80
|
+
remediation:
|
|
81
|
+
"Run `git status --short`, inspect the unexpected changes, and fix plugin-sync-explain before trusting doctor output.",
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
id: "plugin-sync",
|
|
89
|
+
title: "Plugin source/generated sync",
|
|
90
|
+
checks: [
|
|
91
|
+
{
|
|
92
|
+
id: "plugin-sync",
|
|
93
|
+
status: result.verdict,
|
|
94
|
+
summary:
|
|
95
|
+
result.verdict === "PASS"
|
|
96
|
+
? "plugin source and generated artifacts are in sync"
|
|
97
|
+
: `plugin sync drift detected: ${result.driftClass}`,
|
|
98
|
+
observed: renderPluginSyncObserved(result),
|
|
99
|
+
remediation: renderPluginSyncRemediation(result),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
id: "plugin-sync",
|
|
106
|
+
title: "Plugin source/generated sync",
|
|
107
|
+
checks: [
|
|
108
|
+
{
|
|
109
|
+
id: "plugin-sync",
|
|
110
|
+
status: "FAIL",
|
|
111
|
+
summary: "plugin sync readiness check failed",
|
|
112
|
+
observed: error instanceof Error ? error.message : String(error),
|
|
113
|
+
remediation:
|
|
114
|
+
"Run `/lisa:plugin-sync-explain` or `bun run check:plugins` to inspect plugin sync health directly.",
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
37
121
|
/**
|
|
38
122
|
* @param {readonly DoctorGroup[]} groups
|
|
39
123
|
* @returns {DoctorVerdict}
|
|
@@ -141,3 +225,55 @@ function normalizeCheck(check) {
|
|
|
141
225
|
status: normalizedStatus,
|
|
142
226
|
};
|
|
143
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* @param {import("./plugin-sync-explain.mjs").PluginSyncResult} result
|
|
231
|
+
* @returns {string}
|
|
232
|
+
*/
|
|
233
|
+
function renderPluginSyncObserved(result) {
|
|
234
|
+
if (result.verdict === "PASS") {
|
|
235
|
+
return "Drift class IN_SYNC; plugin sync evidence was collected read-only.";
|
|
236
|
+
}
|
|
237
|
+
const paths = result.affectedPaths.length
|
|
238
|
+
? result.affectedPaths.join(", ")
|
|
239
|
+
: "none";
|
|
240
|
+
return `Drift class ${result.driftClass}; affected paths: ${paths}.`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @param {import("./plugin-sync-explain.mjs").PluginSyncResult} result
|
|
245
|
+
* @returns {string | undefined}
|
|
246
|
+
*/
|
|
247
|
+
function renderPluginSyncRemediation(result) {
|
|
248
|
+
if (result.verdict === "PASS") {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const nextAction = pluginSyncNextAction(result.driftClass);
|
|
253
|
+
const details = result.remediations
|
|
254
|
+
.map(item => `${item.path}: ${item.nextAction}`)
|
|
255
|
+
.join(" ");
|
|
256
|
+
const explain =
|
|
257
|
+
"Run `/lisa:plugin-sync-explain` or `bun run check:plugins` for the detailed drift report.";
|
|
258
|
+
|
|
259
|
+
return [nextAction, details, explain].filter(Boolean).join(" ");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @param {string} driftClass
|
|
264
|
+
* @returns {string}
|
|
265
|
+
*/
|
|
266
|
+
function pluginSyncNextAction(driftClass) {
|
|
267
|
+
switch (driftClass) {
|
|
268
|
+
case "SOURCE_NOT_BUILT":
|
|
269
|
+
return "Next action: run `bun run build:plugins && bun run check:plugins`, then commit source plus regenerated plugin artifacts.";
|
|
270
|
+
case "OUT_OF_SYNC":
|
|
271
|
+
return "Next action: review source and generated plugin changes, keep `plugins/src` authoritative, then run `bun run build:plugins && bun run check:plugins`.";
|
|
272
|
+
case "GENERATED_ONLY":
|
|
273
|
+
return "Next action: move generated-only edits upstream to `plugins/src`, or remove the generated artifact drift if it should not ship.";
|
|
274
|
+
case "MARKETPLACE_REGISTRATION_DRIFT":
|
|
275
|
+
return "Next action: align marketplace registration with the built plugin manifests, or remove stale marketplace entries.";
|
|
276
|
+
default:
|
|
277
|
+
return `Next action: inspect plugin sync drift class ${driftClass}.`;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -50,6 +50,25 @@ const GIT_BIN = "/usr/bin/git";
|
|
|
50
50
|
* readonly readOnly: boolean
|
|
51
51
|
* readonly text: string
|
|
52
52
|
* }} PluginSyncReport
|
|
53
|
+
*
|
|
54
|
+
* @typedef {{
|
|
55
|
+
* readonly path: string
|
|
56
|
+
* readonly counterpart?: string
|
|
57
|
+
* readonly classification: string
|
|
58
|
+
* readonly nextAction: string
|
|
59
|
+
* }} PluginSyncRemediation
|
|
60
|
+
*
|
|
61
|
+
* @typedef {{
|
|
62
|
+
* readonly root: string
|
|
63
|
+
* readonly verdict: "PASS" | "WARN"
|
|
64
|
+
* readonly driftClass: string
|
|
65
|
+
* readonly affectedPaths: readonly string[]
|
|
66
|
+
* readonly remediations: readonly PluginSyncRemediation[]
|
|
67
|
+
* readonly findings: readonly PluginSyncFinding[]
|
|
68
|
+
* readonly statusBefore: string
|
|
69
|
+
* readonly statusAfter: string
|
|
70
|
+
* readonly readOnly: boolean
|
|
71
|
+
* }} PluginSyncResult
|
|
53
72
|
*/
|
|
54
73
|
|
|
55
74
|
/**
|
|
@@ -57,6 +76,25 @@ const GIT_BIN = "/usr/bin/git";
|
|
|
57
76
|
* @returns {PluginSyncReport}
|
|
58
77
|
*/
|
|
59
78
|
export function explainPluginSync(root = process.cwd()) {
|
|
79
|
+
const result = getPluginSyncResult(root);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
root: result.root,
|
|
83
|
+
findings: result.findings,
|
|
84
|
+
statusBefore: result.statusBefore,
|
|
85
|
+
statusAfter: result.statusAfter,
|
|
86
|
+
readOnly: result.readOnly,
|
|
87
|
+
text: renderPluginSyncReport(result),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Return a structured, read-only plugin sync result for readiness surfaces such
|
|
93
|
+
* as doctor. CLI callers should still render this through renderPluginSyncReport.
|
|
94
|
+
* @param {string} root
|
|
95
|
+
* @returns {PluginSyncResult}
|
|
96
|
+
*/
|
|
97
|
+
export function getPluginSyncResult(root = process.cwd()) {
|
|
60
98
|
const repoRoot = path.resolve(root);
|
|
61
99
|
const statusBefore = gitStatus(repoRoot);
|
|
62
100
|
const statusEntries = parseGitStatus(statusBefore);
|
|
@@ -71,20 +109,23 @@ export function explainPluginSync(root = process.cwd()) {
|
|
|
71
109
|
];
|
|
72
110
|
const statusAfter = gitStatus(repoRoot);
|
|
73
111
|
const readOnly = statusAfter === statusBefore;
|
|
112
|
+
const driftClass = highestClassification(findings);
|
|
74
113
|
|
|
75
114
|
return {
|
|
76
115
|
root: repoRoot,
|
|
116
|
+
verdict: findings.length === 0 ? "PASS" : "WARN",
|
|
117
|
+
driftClass,
|
|
118
|
+
affectedPaths: affectedPaths(findings),
|
|
119
|
+
remediations: findings.map(finding => ({
|
|
120
|
+
path: finding.path,
|
|
121
|
+
counterpart: finding.counterpart,
|
|
122
|
+
classification: finding.classification,
|
|
123
|
+
nextAction: finding.nextAction,
|
|
124
|
+
})),
|
|
77
125
|
findings,
|
|
78
126
|
statusBefore,
|
|
79
127
|
statusAfter,
|
|
80
128
|
readOnly,
|
|
81
|
-
text: renderPluginSyncReport({
|
|
82
|
-
root: repoRoot,
|
|
83
|
-
findings,
|
|
84
|
-
statusBefore,
|
|
85
|
-
statusAfter,
|
|
86
|
-
readOnly,
|
|
87
|
-
}),
|
|
88
129
|
};
|
|
89
130
|
}
|
|
90
131
|
|
|
@@ -142,6 +183,22 @@ function highestClassification(findings) {
|
|
|
142
183
|
return "IN_SYNC";
|
|
143
184
|
}
|
|
144
185
|
|
|
186
|
+
/**
|
|
187
|
+
* @param {readonly PluginSyncFinding[]} findings
|
|
188
|
+
* @returns {string[]}
|
|
189
|
+
*/
|
|
190
|
+
function affectedPaths(findings) {
|
|
191
|
+
return [
|
|
192
|
+
...new Set(
|
|
193
|
+
findings.flatMap(finding =>
|
|
194
|
+
[finding.path, finding.counterpart].filter(
|
|
195
|
+
pathValue => typeof pathValue === "string" && pathValue.length > 0
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
),
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
|
|
145
202
|
/**
|
|
146
203
|
* @param {readonly { readonly code: string, readonly file: string }[]} entries
|
|
147
204
|
* @param {ReadonlyMap<string, Buffer> | undefined} expectedGeneratedFiles
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Run a first-time-user exploratory QA walkthrough: experience the app like a brand-new human user, clicking through to find anything confusing, broken, or hard to understand (machine-style labels, slow or unclear loads, cramped or cut-off UI, inconsistent UX, awkward scroll behavior) across all breakpoints, and file each finding (bug or usability issue) as a tracked work item via lisa:tracker-write. The optional ready flag marks tickets build-ready (auto-picked-up by lisa:intake) or leaves them in the backlog for human triage (default). For gaps in the automated Playwright suite, use e2e-coverage-gaps instead."
|
|
2
|
+
description: "Run a first-time-user exploratory QA walkthrough: experience the app like a brand-new human user, clicking through to find anything confusing, broken, or hard to understand (human-facing jargon, machine-style labels, slow or unclear loads, cramped or cut-off UI, inconsistent UX, awkward scroll behavior) across all breakpoints, and file each finding (bug or usability issue) as a tracked work item via lisa:tracker-write. The optional ready flag marks tickets build-ready (auto-picked-up by lisa:intake) or leaves them in the backlog for human triage (default). For gaps in the automated Playwright suite, use e2e-coverage-gaps instead."
|
|
3
3
|
allowed-tools: ["Skill"]
|
|
4
4
|
argument-hint: "[target-url | env] [ready=true|false]"
|
|
5
5
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: exploratory-qa
|
|
3
|
-
description: First-time-user exploratory QA walkthrough for web apps that FEEDS THE LIFECYCLE. Use when asked to experience an app the way a brand-new human user would — landing cold on the home page and clicking through to find anything confusing, broken, or hard to understand (machine-style labels, slow or unclear loads, cramped or cut-off UI, inconsistent/non-standard UX, awkward scroll behavior, unclear affordances) across all breakpoints. Instead of writing a report file, it files every finding as a tracked work item via lisa:tracker-write (bugs and usability/UX issues). A `ready` parameter controls whether those tickets are created build-ready (auto-picked-up by lisa:intake) or left in the backlog for human triage (default). For gaps in the automated Playwright test suite, use the e2e-coverage-gaps skill instead.
|
|
3
|
+
description: First-time-user exploratory QA walkthrough for web apps that FEEDS THE LIFECYCLE. Use when asked to experience an app the way a brand-new human user would — landing cold on the home page and clicking through to find anything confusing, broken, or hard to understand (human-facing jargon, machine-style labels, slow or unclear loads, cramped or cut-off UI, inconsistent/non-standard UX, awkward scroll behavior, unclear affordances) across all breakpoints. Instead of writing a report file, it files every finding as a tracked work item via lisa:tracker-write (bugs and usability/UX issues). A `ready` parameter controls whether those tickets are created build-ready (auto-picked-up by lisa:intake) or left in the backlog for human triage (default). For gaps in the automated Playwright test suite, use the e2e-coverage-gaps skill instead.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Exploratory QA
|
|
@@ -49,9 +49,12 @@ filed as a tracked work item so it enters the Lisa lifecycle — no static repor
|
|
|
49
49
|
Click through the visible paths and actually attempt real tasks — a first-time user explores, makes
|
|
50
50
|
mistakes, and tries the obvious thing. Cover at least these dimensions unless the user narrows scope:
|
|
51
51
|
|
|
52
|
-
- **Comprehension & labeling:**
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
- **Comprehension & labeling:** human-facing copy must sound like something a normal first-time user
|
|
53
|
+
would understand. Flag machine-style or developer labels shown to users (raw IDs, enum keys,
|
|
54
|
+
`snake_case`, `null`/`undefined`, untranslated i18n keys), admin/database terms such as
|
|
55
|
+
"metadata", implementation identifiers such as slugs, unexplained domain jargon, unclear
|
|
56
|
+
button/menu names, and icons with no discernible meaning. If a heading, label, or field would make a
|
|
57
|
+
non-technical user ask "what does that mean?", file a usability/clarity ticket with plainer wording.
|
|
55
58
|
- **Navigation clarity:** is it obvious how to get somewhere and back? Dead ends, hidden entry points,
|
|
56
59
|
surprising redirects, broken links, no clear "home".
|
|
57
60
|
- **Visual/layout quality:** cut-off or truncated text, overlap, cramped/crowded density, offscreen or
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Run a first-time-user exploratory QA walkthrough: experience the app like a brand-new human user, clicking through to find anything confusing, broken, or hard to understand (machine-style labels, slow or unclear loads, cramped or cut-off UI, inconsistent UX, awkward scroll behavior) across all breakpoints, and file each finding (bug or usability issue) as a tracked work item via lisa:tracker-write. The optional ready flag marks tickets build-ready (auto-picked-up by lisa:intake) or leaves them in the backlog for human triage (default). For gaps in the automated Playwright suite, use e2e-coverage-gaps instead."
|
|
2
|
+
description: "Run a first-time-user exploratory QA walkthrough: experience the app like a brand-new human user, clicking through to find anything confusing, broken, or hard to understand (human-facing jargon, machine-style labels, slow or unclear loads, cramped or cut-off UI, inconsistent UX, awkward scroll behavior) across all breakpoints, and file each finding (bug or usability issue) as a tracked work item via lisa:tracker-write. The optional ready flag marks tickets build-ready (auto-picked-up by lisa:intake) or leaves them in the backlog for human triage (default). For gaps in the automated Playwright suite, use e2e-coverage-gaps instead."
|
|
3
3
|
allowed-tools: ["Skill"]
|
|
4
4
|
argument-hint: "[target-url | env] [ready=true|false]"
|
|
5
5
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: exploratory-qa
|
|
3
|
-
description: First-time-user exploratory QA walkthrough for web apps that FEEDS THE LIFECYCLE. Use when asked to experience an app the way a brand-new human user would — landing cold on the home page and clicking through to find anything confusing, broken, or hard to understand (machine-style labels, slow or unclear loads, cramped or cut-off UI, inconsistent/non-standard UX, awkward scroll behavior, unclear affordances) across all breakpoints. Instead of writing a report file, it files every finding as a tracked work item via lisa:tracker-write (bugs and usability/UX issues). A `ready` parameter controls whether those tickets are created build-ready (auto-picked-up by lisa:intake) or left in the backlog for human triage (default). For gaps in the automated Playwright test suite, use the e2e-coverage-gaps skill instead.
|
|
3
|
+
description: First-time-user exploratory QA walkthrough for web apps that FEEDS THE LIFECYCLE. Use when asked to experience an app the way a brand-new human user would — landing cold on the home page and clicking through to find anything confusing, broken, or hard to understand (human-facing jargon, machine-style labels, slow or unclear loads, cramped or cut-off UI, inconsistent/non-standard UX, awkward scroll behavior, unclear affordances) across all breakpoints. Instead of writing a report file, it files every finding as a tracked work item via lisa:tracker-write (bugs and usability/UX issues). A `ready` parameter controls whether those tickets are created build-ready (auto-picked-up by lisa:intake) or left in the backlog for human triage (default). For gaps in the automated Playwright test suite, use the e2e-coverage-gaps skill instead.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Exploratory QA
|
|
@@ -49,9 +49,12 @@ filed as a tracked work item so it enters the Lisa lifecycle — no static repor
|
|
|
49
49
|
Click through the visible paths and actually attempt real tasks — a first-time user explores, makes
|
|
50
50
|
mistakes, and tries the obvious thing. Cover at least these dimensions unless the user narrows scope:
|
|
51
51
|
|
|
52
|
-
- **Comprehension & labeling:**
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
- **Comprehension & labeling:** human-facing copy must sound like something a normal first-time user
|
|
53
|
+
would understand. Flag machine-style or developer labels shown to users (raw IDs, enum keys,
|
|
54
|
+
`snake_case`, `null`/`undefined`, untranslated i18n keys), admin/database terms such as
|
|
55
|
+
"metadata", implementation identifiers such as slugs, unexplained domain jargon, unclear
|
|
56
|
+
button/menu names, and icons with no discernible meaning. If a heading, label, or field would make a
|
|
57
|
+
non-technical user ask "what does that mean?", file a usability/clarity ticket with plainer wording.
|
|
55
58
|
- **Navigation clarity:** is it obvious how to get somewhere and back? Dead ends, hidden entry points,
|
|
56
59
|
surprising redirects, broken links, no clear "home".
|
|
57
60
|
- **Visual/layout quality:** cut-off or truncated text, overlap, cramped/crowded density, offscreen or
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Run a first-time-user exploratory QA walkthrough: experience the app like a brand-new human user, clicking through to find anything confusing, broken, or hard to understand (machine-style labels, slow or unclear loads, cramped or cut-off UI, inconsistent UX, awkward scroll behavior) across all breakpoints, and file each finding (bug or usability issue) as a tracked work item via lisa:tracker-write. The optional ready flag marks tickets build-ready (auto-picked-up by lisa:intake) or leaves them in the backlog for human triage (default). For gaps in the automated Playwright suite, use e2e-coverage-gaps instead."
|
|
2
|
+
description: "Run a first-time-user exploratory QA walkthrough: experience the app like a brand-new human user, clicking through to find anything confusing, broken, or hard to understand (human-facing jargon, machine-style labels, slow or unclear loads, cramped or cut-off UI, inconsistent UX, awkward scroll behavior) across all breakpoints, and file each finding (bug or usability issue) as a tracked work item via lisa:tracker-write. The optional ready flag marks tickets build-ready (auto-picked-up by lisa:intake) or leaves them in the backlog for human triage (default). For gaps in the automated Playwright suite, use e2e-coverage-gaps instead."
|
|
3
3
|
allowed-tools: ["Skill"]
|
|
4
4
|
argument-hint: "[target-url | env] [ready=true|false]"
|
|
5
5
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: exploratory-qa
|
|
3
|
-
description: First-time-user exploratory QA walkthrough for web apps that FEEDS THE LIFECYCLE. Use when asked to experience an app the way a brand-new human user would — landing cold on the home page and clicking through to find anything confusing, broken, or hard to understand (machine-style labels, slow or unclear loads, cramped or cut-off UI, inconsistent/non-standard UX, awkward scroll behavior, unclear affordances) across all breakpoints. Instead of writing a report file, it files every finding as a tracked work item via lisa:tracker-write (bugs and usability/UX issues). A `ready` parameter controls whether those tickets are created build-ready (auto-picked-up by lisa:intake) or left in the backlog for human triage (default). For gaps in the automated Playwright test suite, use the e2e-coverage-gaps skill instead.
|
|
3
|
+
description: First-time-user exploratory QA walkthrough for web apps that FEEDS THE LIFECYCLE. Use when asked to experience an app the way a brand-new human user would — landing cold on the home page and clicking through to find anything confusing, broken, or hard to understand (human-facing jargon, machine-style labels, slow or unclear loads, cramped or cut-off UI, inconsistent/non-standard UX, awkward scroll behavior, unclear affordances) across all breakpoints. Instead of writing a report file, it files every finding as a tracked work item via lisa:tracker-write (bugs and usability/UX issues). A `ready` parameter controls whether those tickets are created build-ready (auto-picked-up by lisa:intake) or left in the backlog for human triage (default). For gaps in the automated Playwright test suite, use the e2e-coverage-gaps skill instead.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Exploratory QA
|
|
@@ -49,9 +49,12 @@ filed as a tracked work item so it enters the Lisa lifecycle — no static repor
|
|
|
49
49
|
Click through the visible paths and actually attempt real tasks — a first-time user explores, makes
|
|
50
50
|
mistakes, and tries the obvious thing. Cover at least these dimensions unless the user narrows scope:
|
|
51
51
|
|
|
52
|
-
- **Comprehension & labeling:**
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
- **Comprehension & labeling:** human-facing copy must sound like something a normal first-time user
|
|
53
|
+
would understand. Flag machine-style or developer labels shown to users (raw IDs, enum keys,
|
|
54
|
+
`snake_case`, `null`/`undefined`, untranslated i18n keys), admin/database terms such as
|
|
55
|
+
"metadata", implementation identifiers such as slugs, unexplained domain jargon, unclear
|
|
56
|
+
button/menu names, and icons with no discernible meaning. If a heading, label, or field would make a
|
|
57
|
+
non-technical user ask "what does that mean?", file a usability/clarity ticket with plainer wording.
|
|
55
58
|
- **Navigation clarity:** is it obvious how to get somewhere and back? Dead ends, hidden entry points,
|
|
56
59
|
surprising redirects, broken links, no clear "home".
|
|
57
60
|
- **Visual/layout quality:** cut-off or truncated text, overlap, cramped/crowded density, offscreen or
|