smartlisa 0.1.11 → 0.1.12
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/.claude-plugin/plugin.json +1 -1
- package/dist/cli.js +425 -330
- package/package.json +1 -1
- package/skills/lisa/SKILL.md +28 -26
- package/skills/lisa/references/commands.md +2 -2
- package/skills/lisa/references/examples.md +42 -44
- package/skills/lisa-work/SKILL.md +7 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa",
|
|
3
3
|
"description": "Project planning with milestones, epics, and stories. Break down work, track progress, and implement with full context.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.12",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rodrigo Klosowski",
|
|
7
7
|
"url": "https://rklosowski.com/"
|
package/dist/cli.js
CHANGED
|
@@ -93,6 +93,7 @@ var ValueSchema = z.object({
|
|
|
93
93
|
var DiscoveryContextSchema = z.object({
|
|
94
94
|
problem: z.string().optional(),
|
|
95
95
|
vision: z.string().optional(),
|
|
96
|
+
users: z.array(z.string()).default([]),
|
|
96
97
|
values: z.array(ValueSchema).default([]),
|
|
97
98
|
success_criteria: z.array(z.string()).default([]),
|
|
98
99
|
gathered: z.string().datetime().optional()
|
|
@@ -118,8 +119,6 @@ var DiscoveryDepth = z.enum(["quick", "standard", "deep"]);
|
|
|
118
119
|
var DiscoveryHistorySchema = z.object({
|
|
119
120
|
entries: z.array(DiscoveryHistoryEntrySchema).default([]),
|
|
120
121
|
started: z.string().datetime().optional(),
|
|
121
|
-
completed: z.string().datetime().optional(),
|
|
122
|
-
is_complete: z.boolean().default(false),
|
|
123
122
|
// Depth preference - affects which topics Claude explores
|
|
124
123
|
depth_preference: DiscoveryDepth.optional(),
|
|
125
124
|
// Last activity timestamp for continuous discovery
|
|
@@ -199,12 +198,13 @@ var EpicSchema = z.object({
|
|
|
199
198
|
coverage: z.number().default(0)
|
|
200
199
|
})
|
|
201
200
|
});
|
|
201
|
+
var StoryType = z.enum(["feature", "bug", "chore", "spike"]);
|
|
202
202
|
var StorySchema = z.object({
|
|
203
203
|
id: z.string(),
|
|
204
204
|
// E1.S1, E1.S2, etc.
|
|
205
205
|
title: z.string(),
|
|
206
206
|
description: z.string(),
|
|
207
|
-
type:
|
|
207
|
+
type: StoryType,
|
|
208
208
|
requirements: z.array(z.string()).default([]),
|
|
209
209
|
// E1.R1, E1.R2, etc.
|
|
210
210
|
acceptance_criteria: z.array(z.string()).default([]),
|
|
@@ -215,6 +215,31 @@ var StorySchema = z.object({
|
|
|
215
215
|
assignee: z.string().nullable().default(null),
|
|
216
216
|
blocked_reason: z.string().optional()
|
|
217
217
|
});
|
|
218
|
+
var StoryInputSchema = z.object({
|
|
219
|
+
id: z.string().optional(),
|
|
220
|
+
// Auto-generated if not provided
|
|
221
|
+
title: z.string().min(1, "Story title is required"),
|
|
222
|
+
description: z.string().min(1, "Story description is required"),
|
|
223
|
+
type: StoryType.default("feature"),
|
|
224
|
+
requirements: z.array(z.string()).default([]),
|
|
225
|
+
acceptance_criteria: z.array(z.string()).default([]).transform(
|
|
226
|
+
(arr) => (
|
|
227
|
+
// Also accept 'criteria' as alias
|
|
228
|
+
arr
|
|
229
|
+
)
|
|
230
|
+
),
|
|
231
|
+
criteria: z.array(z.string()).optional(),
|
|
232
|
+
// Alias for acceptance_criteria
|
|
233
|
+
dependencies: z.array(z.string()).default([]),
|
|
234
|
+
estimated_points: z.number().optional(),
|
|
235
|
+
status: StoryStatus.default("todo"),
|
|
236
|
+
assignee: z.string().nullable().default(null),
|
|
237
|
+
blocked_reason: z.string().optional()
|
|
238
|
+
}).transform((data) => {
|
|
239
|
+
const acceptance_criteria = data.criteria?.length ? [...data.acceptance_criteria, ...data.criteria] : data.acceptance_criteria;
|
|
240
|
+
const { criteria, ...rest } = data;
|
|
241
|
+
return { ...rest, acceptance_criteria };
|
|
242
|
+
});
|
|
218
243
|
var StoriesFileSchema = z.object({
|
|
219
244
|
epic_id: z.string(),
|
|
220
245
|
stories: z.array(StorySchema).default([]),
|
|
@@ -811,13 +836,13 @@ var StateManager = class {
|
|
|
811
836
|
await this.writeStuckQueue({ stuck: [], resolved: [] });
|
|
812
837
|
await this.writeFeedbackQueue({ feedback: [], incorporated: [] });
|
|
813
838
|
await this.writeDiscoveryContext({
|
|
839
|
+
users: [],
|
|
814
840
|
values: [],
|
|
815
841
|
success_criteria: []
|
|
816
842
|
});
|
|
817
843
|
await this.writeConstraints({ constraints: [] });
|
|
818
844
|
await this.writeDiscoveryHistory({
|
|
819
|
-
entries: []
|
|
820
|
-
is_complete: false
|
|
845
|
+
entries: []
|
|
821
846
|
});
|
|
822
847
|
await this.writeMilestoneIndex({ milestones: [] });
|
|
823
848
|
const defaultConfig = {
|
|
@@ -894,8 +919,8 @@ var StateManager = class {
|
|
|
894
919
|
async readDiscoveryHistory() {
|
|
895
920
|
return this.adapter.readJson(PATHS.discovery.history, DiscoveryHistorySchema);
|
|
896
921
|
}
|
|
897
|
-
async writeDiscoveryHistory(
|
|
898
|
-
await this.adapter.writeJson(PATHS.discovery.history,
|
|
922
|
+
async writeDiscoveryHistory(history2) {
|
|
923
|
+
await this.adapter.writeJson(PATHS.discovery.history, history2);
|
|
899
924
|
}
|
|
900
925
|
// --------------------------------------------------------------------------
|
|
901
926
|
// Milestones
|
|
@@ -1299,11 +1324,11 @@ function success(data, sections = [], aiGuidance) {
|
|
|
1299
1324
|
aiGuidance
|
|
1300
1325
|
};
|
|
1301
1326
|
}
|
|
1302
|
-
function error(message, code,
|
|
1327
|
+
function error(message, code, sections) {
|
|
1303
1328
|
return {
|
|
1304
1329
|
status: "error",
|
|
1305
|
-
data,
|
|
1306
|
-
sections: [{ type: "text", content: message, style: "error" }],
|
|
1330
|
+
data: null,
|
|
1331
|
+
sections: sections ?? [{ type: "text", content: message, style: "error" }],
|
|
1307
1332
|
error: message,
|
|
1308
1333
|
errorCode: code
|
|
1309
1334
|
};
|
|
@@ -1741,10 +1766,231 @@ function getHowGuidance(storyId, hasArchitecture) {
|
|
|
1741
1766
|
);
|
|
1742
1767
|
}
|
|
1743
1768
|
|
|
1769
|
+
// src/core/prompts/discovery.ts
|
|
1770
|
+
function getDiscoveryGuidance(context2, _history, constraints, depth, gaps, justInitialized) {
|
|
1771
|
+
const hasGaps = gaps.length > 0;
|
|
1772
|
+
const hasSomeContext = !!context2?.problem || !!context2?.vision;
|
|
1773
|
+
let situation;
|
|
1774
|
+
if (justInitialized) {
|
|
1775
|
+
situation = "Project initialized, starting discovery";
|
|
1776
|
+
} else if (!hasSomeContext) {
|
|
1777
|
+
situation = "Discovery starting - no context gathered yet";
|
|
1778
|
+
} else if (hasGaps) {
|
|
1779
|
+
situation = `Discovery in progress (${depth} depth) - gaps in: ${gaps.join(", ")}`;
|
|
1780
|
+
} else {
|
|
1781
|
+
situation = "Discovery has sufficient context - ready to move to planning when you are";
|
|
1782
|
+
}
|
|
1783
|
+
const instructions = [];
|
|
1784
|
+
if (!hasSomeContext) {
|
|
1785
|
+
instructions.push("Start a discovery conversation to deeply understand the project");
|
|
1786
|
+
instructions.push("Capture the problem domain, users, values, and constraints");
|
|
1787
|
+
instructions.push(`Ask the user: "${getStarterQuestion(gaps[0] || "problem")}"`);
|
|
1788
|
+
instructions.push("If this is an existing codebase, briefly acknowledge what you learned (1-2 sentences), then ask the question");
|
|
1789
|
+
instructions.push("Focus on understanding, not on what to build next");
|
|
1790
|
+
} else {
|
|
1791
|
+
instructions.push("Continue the discovery conversation naturally");
|
|
1792
|
+
instructions.push("Run 'lisa status context' first to review what's been discovered so far");
|
|
1793
|
+
instructions.push("Run 'lisa discover history' to see all Q&A entries recorded");
|
|
1794
|
+
}
|
|
1795
|
+
instructions.push("Ask follow-up questions based on user's answers");
|
|
1796
|
+
instructions.push("For short answers on important topics, probe deeper");
|
|
1797
|
+
instructions.push("For detailed answers, acknowledge and move on");
|
|
1798
|
+
if (gaps.includes("other")) {
|
|
1799
|
+
instructions.push("For research/benchmarks: use web search to find competitors and best practices");
|
|
1800
|
+
}
|
|
1801
|
+
instructions.push("Record key insights as you go");
|
|
1802
|
+
instructions.push(
|
|
1803
|
+
"When user indicates what they want to do next (plan, add milestones/epics, continue discovery, etc.), follow their lead"
|
|
1804
|
+
);
|
|
1805
|
+
const commands = [
|
|
1806
|
+
{
|
|
1807
|
+
command: "discover add-entry",
|
|
1808
|
+
args: "--category <cat> --question '<q>' --answer '<a>'",
|
|
1809
|
+
description: "Record a discovery insight",
|
|
1810
|
+
when: "After gathering important context"
|
|
1811
|
+
},
|
|
1812
|
+
{
|
|
1813
|
+
command: "plan milestones",
|
|
1814
|
+
description: "View/create milestones",
|
|
1815
|
+
when: "When user wants to structure work"
|
|
1816
|
+
},
|
|
1817
|
+
{
|
|
1818
|
+
command: "plan add-milestone",
|
|
1819
|
+
args: "--name '<name>' --description '<desc>'",
|
|
1820
|
+
description: "Add a milestone directly",
|
|
1821
|
+
when: "When user has a specific milestone in mind"
|
|
1822
|
+
},
|
|
1823
|
+
{
|
|
1824
|
+
command: "discover --deep",
|
|
1825
|
+
description: "Switch to deep discovery",
|
|
1826
|
+
when: "If user wants more thorough exploration"
|
|
1827
|
+
},
|
|
1828
|
+
{
|
|
1829
|
+
command: "discover --quick",
|
|
1830
|
+
description: "Switch to quick discovery",
|
|
1831
|
+
when: "If user wants to move faster"
|
|
1832
|
+
}
|
|
1833
|
+
];
|
|
1834
|
+
return {
|
|
1835
|
+
situation,
|
|
1836
|
+
instructions,
|
|
1837
|
+
commands,
|
|
1838
|
+
context: {
|
|
1839
|
+
depth,
|
|
1840
|
+
gaps,
|
|
1841
|
+
// Full discovery context so AI knows what's been learned
|
|
1842
|
+
discovery: context2 ? {
|
|
1843
|
+
problem: context2.problem,
|
|
1844
|
+
vision: context2.vision,
|
|
1845
|
+
values: context2.values,
|
|
1846
|
+
successCriteria: context2.success_criteria
|
|
1847
|
+
} : null,
|
|
1848
|
+
constraints: constraints?.constraints || []
|
|
1849
|
+
}
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
function getStarterQuestion(category) {
|
|
1853
|
+
const starters = {
|
|
1854
|
+
problem: "What problem are we solving?",
|
|
1855
|
+
vision: "What does the ideal end state look like?",
|
|
1856
|
+
users: "Who are the primary users?",
|
|
1857
|
+
values: "What's the most important quality this solution must have?",
|
|
1858
|
+
constraints: "What's your current tech stack and team setup?",
|
|
1859
|
+
success: "How will we know if this is successful?",
|
|
1860
|
+
other: "What existing solutions have you looked at?"
|
|
1861
|
+
};
|
|
1862
|
+
return starters[category] || starters.problem;
|
|
1863
|
+
}
|
|
1864
|
+
function getAddEntryGuidance() {
|
|
1865
|
+
return {
|
|
1866
|
+
situation: "Discovery entry recorded",
|
|
1867
|
+
instructions: [
|
|
1868
|
+
"Continue the conversation naturally based on what user wants",
|
|
1869
|
+
"You can: continue discovery, move to planning, add milestones/epics, or follow user's lead"
|
|
1870
|
+
],
|
|
1871
|
+
commands: [
|
|
1872
|
+
{
|
|
1873
|
+
command: "discover add-entry",
|
|
1874
|
+
args: "--category <cat> --question '<q>' --answer '<a>'",
|
|
1875
|
+
description: "Record another insight",
|
|
1876
|
+
when: "After gathering context"
|
|
1877
|
+
},
|
|
1878
|
+
{
|
|
1879
|
+
command: "plan milestones",
|
|
1880
|
+
description: "View/create milestones",
|
|
1881
|
+
when: "When ready to structure work"
|
|
1882
|
+
},
|
|
1883
|
+
{
|
|
1884
|
+
command: "plan add-milestone",
|
|
1885
|
+
args: "--name '<name>' --description '<desc>'",
|
|
1886
|
+
description: "Add milestone directly",
|
|
1887
|
+
when: "When user has specific milestone"
|
|
1888
|
+
}
|
|
1889
|
+
]
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
var EPIC_DISCOVERY_QUESTIONS = [
|
|
1893
|
+
{ id: "problem", question: "What problem does this epic solve?", required: true },
|
|
1894
|
+
{ id: "scope", question: "What's in scope for this epic?", required: true },
|
|
1895
|
+
{ id: "out_of_scope", question: "What's explicitly out of scope?", required: false },
|
|
1896
|
+
{ id: "constraints", question: "Are there any technical constraints specific to this epic?", required: false },
|
|
1897
|
+
{ id: "success", question: "What does success look like for this epic?", required: true }
|
|
1898
|
+
];
|
|
1899
|
+
var MILESTONE_DISCOVERY_QUESTIONS = [
|
|
1900
|
+
{ id: "goal", question: "What's the goal of this milestone?", required: true },
|
|
1901
|
+
{ id: "done", question: "What does 'done' look like for this milestone?", required: true },
|
|
1902
|
+
{ id: "dependencies", question: "Are there any dependencies or blockers for this milestone?", required: false }
|
|
1903
|
+
];
|
|
1904
|
+
function getElementDiscoveryGuidance(elementType, elementName, discovery) {
|
|
1905
|
+
const questions = elementType === "epic" ? EPIC_DISCOVERY_QUESTIONS : MILESTONE_DISCOVERY_QUESTIONS;
|
|
1906
|
+
const answeredQuestions = new Set(discovery.history.map((e) => e.question));
|
|
1907
|
+
const remaining = questions.filter((q) => !answeredQuestions.has(q.question));
|
|
1908
|
+
const requiredRemaining = remaining.filter((q) => q.required);
|
|
1909
|
+
let situation;
|
|
1910
|
+
if (discovery.status === "complete") {
|
|
1911
|
+
situation = `${elementType} discovery complete for ${elementName}`;
|
|
1912
|
+
} else if (remaining.length === 0) {
|
|
1913
|
+
situation = `All questions answered for ${elementType} ${elementName}, ready to complete`;
|
|
1914
|
+
} else {
|
|
1915
|
+
situation = `${elementType} discovery in progress for ${elementName} - ${remaining.length} questions remaining`;
|
|
1916
|
+
}
|
|
1917
|
+
const instructions = [];
|
|
1918
|
+
if (discovery.status === "complete") {
|
|
1919
|
+
instructions.push("Discovery is complete. You can still add more context if needed.");
|
|
1920
|
+
} else {
|
|
1921
|
+
instructions.push("Ask the remaining questions conversationally, one at a time");
|
|
1922
|
+
instructions.push("Record answers using the add-entry command");
|
|
1923
|
+
if (remaining.length > 0) {
|
|
1924
|
+
instructions.push(`Start with: "${remaining[0].question}"`);
|
|
1925
|
+
}
|
|
1926
|
+
if (remaining.length === 0) {
|
|
1927
|
+
instructions.push("All questions answered - complete the discovery");
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
const commands = [
|
|
1931
|
+
{
|
|
1932
|
+
command: `discover add-element-entry`,
|
|
1933
|
+
args: `--element-type ${elementType} --element-id ${discovery.element_id} --category <cat> --question '<q>' --answer '<a>'`,
|
|
1934
|
+
description: "Record an answer",
|
|
1935
|
+
when: "After getting an answer from the user"
|
|
1936
|
+
}
|
|
1937
|
+
];
|
|
1938
|
+
if (remaining.length === 0 && discovery.status !== "complete") {
|
|
1939
|
+
commands.push({
|
|
1940
|
+
command: `discover complete-element`,
|
|
1941
|
+
args: `--element-type ${elementType} --element-id ${discovery.element_id}`,
|
|
1942
|
+
description: "Mark discovery complete",
|
|
1943
|
+
when: "When all required questions are answered"
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
return {
|
|
1947
|
+
situation,
|
|
1948
|
+
instructions,
|
|
1949
|
+
commands,
|
|
1950
|
+
context: {
|
|
1951
|
+
elementType,
|
|
1952
|
+
elementId: discovery.element_id,
|
|
1953
|
+
remainingQuestions: remaining.map((q) => ({
|
|
1954
|
+
id: q.id,
|
|
1955
|
+
question: q.question,
|
|
1956
|
+
required: q.required
|
|
1957
|
+
})),
|
|
1958
|
+
gatheredContext: {
|
|
1959
|
+
problem: discovery.problem,
|
|
1960
|
+
scope: discovery.scope,
|
|
1961
|
+
outOfScope: discovery.out_of_scope,
|
|
1962
|
+
successCriteria: discovery.success_criteria,
|
|
1963
|
+
constraints: discovery.constraints.length
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
function getNotInitializedGuidance() {
|
|
1969
|
+
return {
|
|
1970
|
+
situation: "No Lisa project found in this directory",
|
|
1971
|
+
instructions: [
|
|
1972
|
+
"Ask the user for a project name, then run 'discover init' to initialize"
|
|
1973
|
+
],
|
|
1974
|
+
commands: [
|
|
1975
|
+
{
|
|
1976
|
+
command: "discover init",
|
|
1977
|
+
args: "{ name: '<project name>' }",
|
|
1978
|
+
description: "Initialize Lisa project",
|
|
1979
|
+
when: "After getting the project name from user"
|
|
1980
|
+
}
|
|
1981
|
+
]
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1744
1985
|
// src/core/commands/status.ts
|
|
1745
1986
|
async function overview(state) {
|
|
1746
1987
|
if (!await state.isInitialized()) {
|
|
1747
|
-
|
|
1988
|
+
const sections2 = [
|
|
1989
|
+
section.info("No Lisa project found in this directory."),
|
|
1990
|
+
section.blank(),
|
|
1991
|
+
section.info("Run 'discover init' to start planning.")
|
|
1992
|
+
];
|
|
1993
|
+
return success(null, sections2, getNotInitializedGuidance());
|
|
1748
1994
|
}
|
|
1749
1995
|
const project = await state.readProject();
|
|
1750
1996
|
const index = await state.readMilestoneIndex();
|
|
@@ -2395,162 +2641,6 @@ async function how(state, options) {
|
|
|
2395
2641
|
return success(data, sections, aiGuidance);
|
|
2396
2642
|
}
|
|
2397
2643
|
|
|
2398
|
-
// src/core/prompts/discovery.ts
|
|
2399
|
-
function getDiscoveryGuidance(context2, history, _constraints, depth, gaps) {
|
|
2400
|
-
const isComplete = history?.is_complete || false;
|
|
2401
|
-
const hasGaps = gaps.length > 0;
|
|
2402
|
-
const hasSomeContext = !!context2?.problem || !!context2?.vision;
|
|
2403
|
-
let situation;
|
|
2404
|
-
if (isComplete) {
|
|
2405
|
-
situation = "Discovery complete, can continue anytime to add more context";
|
|
2406
|
-
} else if (!hasSomeContext) {
|
|
2407
|
-
situation = "Discovery starting - no context gathered yet";
|
|
2408
|
-
} else if (hasGaps) {
|
|
2409
|
-
situation = `Discovery in progress (${depth} depth) - gaps in: ${gaps.join(", ")}`;
|
|
2410
|
-
} else {
|
|
2411
|
-
situation = "Discovery in progress - sufficient context for current depth, consider completing";
|
|
2412
|
-
}
|
|
2413
|
-
const instructions = [];
|
|
2414
|
-
if (!hasSomeContext) {
|
|
2415
|
-
instructions.push("Start a natural conversation to understand the project");
|
|
2416
|
-
instructions.push(`Ask about: "${getStarterQuestion(gaps[0] || "problem")}"`);
|
|
2417
|
-
} else {
|
|
2418
|
-
instructions.push("Continue the discovery conversation naturally");
|
|
2419
|
-
}
|
|
2420
|
-
instructions.push("Ask follow-up questions based on user's answers");
|
|
2421
|
-
instructions.push("For short answers on important topics, probe deeper");
|
|
2422
|
-
instructions.push("For detailed answers, acknowledge and move on");
|
|
2423
|
-
if (gaps.includes("other")) {
|
|
2424
|
-
instructions.push("For research/benchmarks: use web search to find competitors and best practices");
|
|
2425
|
-
}
|
|
2426
|
-
instructions.push("Record key insights as you go");
|
|
2427
|
-
if (!hasGaps && !isComplete) {
|
|
2428
|
-
instructions.push("Sufficient context gathered for this depth - suggest completing discovery");
|
|
2429
|
-
}
|
|
2430
|
-
const commands = [
|
|
2431
|
-
{
|
|
2432
|
-
command: "discover add-entry",
|
|
2433
|
-
args: "--category <cat> --question '<q>' --answer '<a>'",
|
|
2434
|
-
description: "Record a discovery insight",
|
|
2435
|
-
when: "After gathering important context"
|
|
2436
|
-
}
|
|
2437
|
-
];
|
|
2438
|
-
if (!hasGaps || isComplete) {
|
|
2439
|
-
commands.push({
|
|
2440
|
-
command: "discover complete",
|
|
2441
|
-
description: "Mark discovery as complete checkpoint",
|
|
2442
|
-
when: "When sufficient context gathered"
|
|
2443
|
-
});
|
|
2444
|
-
}
|
|
2445
|
-
commands.push({
|
|
2446
|
-
command: "discover --deep",
|
|
2447
|
-
description: "Switch to deep discovery",
|
|
2448
|
-
when: "If user wants more thorough exploration"
|
|
2449
|
-
});
|
|
2450
|
-
commands.push({
|
|
2451
|
-
command: "discover --quick",
|
|
2452
|
-
description: "Switch to quick discovery",
|
|
2453
|
-
when: "If user wants to move faster"
|
|
2454
|
-
});
|
|
2455
|
-
return {
|
|
2456
|
-
situation,
|
|
2457
|
-
instructions,
|
|
2458
|
-
commands,
|
|
2459
|
-
context: {
|
|
2460
|
-
depth,
|
|
2461
|
-
gaps
|
|
2462
|
-
}
|
|
2463
|
-
};
|
|
2464
|
-
}
|
|
2465
|
-
function getStarterQuestion(category) {
|
|
2466
|
-
const starters = {
|
|
2467
|
-
problem: "What problem are we solving?",
|
|
2468
|
-
vision: "What does the ideal end state look like?",
|
|
2469
|
-
users: "Who are the primary users?",
|
|
2470
|
-
values: "What's the most important quality this solution must have?",
|
|
2471
|
-
constraints: "What's your current tech stack and team setup?",
|
|
2472
|
-
success: "How will we know if this is successful?",
|
|
2473
|
-
other: "What existing solutions have you looked at?"
|
|
2474
|
-
};
|
|
2475
|
-
return starters[category] || starters.problem;
|
|
2476
|
-
}
|
|
2477
|
-
var EPIC_DISCOVERY_QUESTIONS = [
|
|
2478
|
-
{ id: "problem", question: "What problem does this epic solve?", required: true },
|
|
2479
|
-
{ id: "scope", question: "What's in scope for this epic?", required: true },
|
|
2480
|
-
{ id: "out_of_scope", question: "What's explicitly out of scope?", required: false },
|
|
2481
|
-
{ id: "constraints", question: "Are there any technical constraints specific to this epic?", required: false },
|
|
2482
|
-
{ id: "success", question: "What does success look like for this epic?", required: true }
|
|
2483
|
-
];
|
|
2484
|
-
var MILESTONE_DISCOVERY_QUESTIONS = [
|
|
2485
|
-
{ id: "goal", question: "What's the goal of this milestone?", required: true },
|
|
2486
|
-
{ id: "done", question: "What does 'done' look like for this milestone?", required: true },
|
|
2487
|
-
{ id: "dependencies", question: "Are there any dependencies or blockers for this milestone?", required: false }
|
|
2488
|
-
];
|
|
2489
|
-
function getElementDiscoveryGuidance(elementType, elementName, discovery) {
|
|
2490
|
-
const questions = elementType === "epic" ? EPIC_DISCOVERY_QUESTIONS : MILESTONE_DISCOVERY_QUESTIONS;
|
|
2491
|
-
const answeredQuestions = new Set(discovery.history.map((e) => e.question));
|
|
2492
|
-
const remaining = questions.filter((q) => !answeredQuestions.has(q.question));
|
|
2493
|
-
const requiredRemaining = remaining.filter((q) => q.required);
|
|
2494
|
-
let situation;
|
|
2495
|
-
if (discovery.status === "complete") {
|
|
2496
|
-
situation = `${elementType} discovery complete for ${elementName}`;
|
|
2497
|
-
} else if (remaining.length === 0) {
|
|
2498
|
-
situation = `All questions answered for ${elementType} ${elementName}, ready to complete`;
|
|
2499
|
-
} else {
|
|
2500
|
-
situation = `${elementType} discovery in progress for ${elementName} - ${remaining.length} questions remaining`;
|
|
2501
|
-
}
|
|
2502
|
-
const instructions = [];
|
|
2503
|
-
if (discovery.status === "complete") {
|
|
2504
|
-
instructions.push("Discovery is complete. You can still add more context if needed.");
|
|
2505
|
-
} else {
|
|
2506
|
-
instructions.push("Ask the remaining questions conversationally, one at a time");
|
|
2507
|
-
instructions.push("Record answers using the add-entry command");
|
|
2508
|
-
if (remaining.length > 0) {
|
|
2509
|
-
instructions.push(`Start with: "${remaining[0].question}"`);
|
|
2510
|
-
}
|
|
2511
|
-
if (remaining.length === 0) {
|
|
2512
|
-
instructions.push("All questions answered - complete the discovery");
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
const commands = [
|
|
2516
|
-
{
|
|
2517
|
-
command: `discover add-element-entry`,
|
|
2518
|
-
args: `--element-type ${elementType} --element-id ${discovery.element_id} --category <cat> --question '<q>' --answer '<a>'`,
|
|
2519
|
-
description: "Record an answer",
|
|
2520
|
-
when: "After getting an answer from the user"
|
|
2521
|
-
}
|
|
2522
|
-
];
|
|
2523
|
-
if (remaining.length === 0 && discovery.status !== "complete") {
|
|
2524
|
-
commands.push({
|
|
2525
|
-
command: `discover complete-element`,
|
|
2526
|
-
args: `--element-type ${elementType} --element-id ${discovery.element_id}`,
|
|
2527
|
-
description: "Mark discovery complete",
|
|
2528
|
-
when: "When all required questions are answered"
|
|
2529
|
-
});
|
|
2530
|
-
}
|
|
2531
|
-
return {
|
|
2532
|
-
situation,
|
|
2533
|
-
instructions,
|
|
2534
|
-
commands,
|
|
2535
|
-
context: {
|
|
2536
|
-
elementType,
|
|
2537
|
-
elementId: discovery.element_id,
|
|
2538
|
-
remainingQuestions: remaining.map((q) => ({
|
|
2539
|
-
id: q.id,
|
|
2540
|
-
question: q.question,
|
|
2541
|
-
required: q.required
|
|
2542
|
-
})),
|
|
2543
|
-
gatheredContext: {
|
|
2544
|
-
problem: discovery.problem,
|
|
2545
|
-
scope: discovery.scope,
|
|
2546
|
-
outOfScope: discovery.out_of_scope,
|
|
2547
|
-
successCriteria: discovery.success_criteria,
|
|
2548
|
-
constraints: discovery.constraints.length
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
};
|
|
2552
|
-
}
|
|
2553
|
-
|
|
2554
2644
|
// src/core/commands/discover.ts
|
|
2555
2645
|
var DISCOVERY_GUIDANCE_DATA = [
|
|
2556
2646
|
{
|
|
@@ -2660,37 +2750,32 @@ var DISCOVERY_GUIDANCE_DATA = [
|
|
|
2660
2750
|
includedIn: ["standard", "deep"]
|
|
2661
2751
|
}
|
|
2662
2752
|
];
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
{ id: "vision", category: "vision", question: "What does success look like?", required: true },
|
|
2666
|
-
{ id: "users", category: "users", question: "Who are the primary users?", required: false },
|
|
2667
|
-
{ id: "value1", category: "values", question: "What is the most important quality?", required: false },
|
|
2668
|
-
{ id: "success1", category: "success", question: "How will we know if this is successful?", required: true }
|
|
2669
|
-
];
|
|
2670
|
-
function checkCategoryHasContent(category, context2, constraints) {
|
|
2753
|
+
function checkCategoryHasContent(category, context2, constraints, history2) {
|
|
2754
|
+
const hasHistoryEntry = history2?.entries.some((e) => e.category === category) || false;
|
|
2671
2755
|
switch (category) {
|
|
2672
2756
|
case "problem":
|
|
2673
|
-
return !!context2?.problem;
|
|
2757
|
+
return !!context2?.problem || hasHistoryEntry;
|
|
2674
2758
|
case "vision":
|
|
2675
|
-
return !!context2?.vision;
|
|
2759
|
+
return !!context2?.vision || hasHistoryEntry;
|
|
2676
2760
|
case "users":
|
|
2677
|
-
return
|
|
2678
|
-
// Stored in history
|
|
2761
|
+
return context2?.users && context2.users.length > 0 || hasHistoryEntry;
|
|
2679
2762
|
case "values":
|
|
2680
|
-
return context2?.values && context2.values.length > 0 ||
|
|
2763
|
+
return context2?.values && context2.values.length > 0 || hasHistoryEntry;
|
|
2681
2764
|
case "constraints":
|
|
2682
|
-
return constraints?.constraints && constraints.constraints.length > 0 ||
|
|
2765
|
+
return constraints?.constraints && constraints.constraints.length > 0 || hasHistoryEntry;
|
|
2683
2766
|
case "success":
|
|
2684
|
-
return context2?.success_criteria && context2.success_criteria.length > 0 ||
|
|
2767
|
+
return context2?.success_criteria && context2.success_criteria.length > 0 || hasHistoryEntry;
|
|
2768
|
+
case "other":
|
|
2769
|
+
return hasHistoryEntry;
|
|
2685
2770
|
default:
|
|
2686
2771
|
return false;
|
|
2687
2772
|
}
|
|
2688
2773
|
}
|
|
2689
|
-
function findDiscoveryGaps(context2, constraints, depth) {
|
|
2774
|
+
function findDiscoveryGaps(context2, constraints, history2, depth) {
|
|
2690
2775
|
const gaps = [];
|
|
2691
2776
|
const guidanceForDepth = DISCOVERY_GUIDANCE_DATA.filter((g) => g.includedIn.includes(depth));
|
|
2692
2777
|
for (const guidance of guidanceForDepth) {
|
|
2693
|
-
if (!checkCategoryHasContent(guidance.category, context2, constraints)) {
|
|
2778
|
+
if (!checkCategoryHasContent(guidance.category, context2, constraints, history2)) {
|
|
2694
2779
|
gaps.push(guidance.category);
|
|
2695
2780
|
}
|
|
2696
2781
|
}
|
|
@@ -2715,6 +2800,7 @@ function createEmptyElementDiscovery(elementType, elementId, source) {
|
|
|
2715
2800
|
}
|
|
2716
2801
|
async function updateContextFromEntry(state, entry) {
|
|
2717
2802
|
const context2 = await state.readDiscoveryContext() || {
|
|
2803
|
+
users: [],
|
|
2718
2804
|
values: [],
|
|
2719
2805
|
success_criteria: []
|
|
2720
2806
|
};
|
|
@@ -2730,6 +2816,12 @@ async function updateContextFromEntry(state, entry) {
|
|
|
2730
2816
|
context2.gathered = now();
|
|
2731
2817
|
await state.writeDiscoveryContext(context2);
|
|
2732
2818
|
break;
|
|
2819
|
+
case "users":
|
|
2820
|
+
if (!context2.users) context2.users = [];
|
|
2821
|
+
context2.users.push(entry.answer);
|
|
2822
|
+
context2.gathered = now();
|
|
2823
|
+
await state.writeDiscoveryContext(context2);
|
|
2824
|
+
break;
|
|
2733
2825
|
case "values":
|
|
2734
2826
|
const valueId = `V${context2.values.length + 1}`;
|
|
2735
2827
|
const value = {
|
|
@@ -2778,28 +2870,10 @@ async function init(state, options) {
|
|
|
2778
2870
|
const projectName = options.name || "Untitled Project";
|
|
2779
2871
|
const project = await state.initialize(projectName);
|
|
2780
2872
|
const sections = [
|
|
2781
|
-
section.
|
|
2782
|
-
section.success(`Created .lisa/ directory`),
|
|
2783
|
-
section.success(`Project: ${project.name}`),
|
|
2784
|
-
section.success(`ID: ${project.id}`),
|
|
2785
|
-
section.blank(),
|
|
2786
|
-
section.info("Next step: Run discovery to gather project context")
|
|
2873
|
+
section.success(`Lisa initialized: ${project.name}`)
|
|
2787
2874
|
];
|
|
2788
|
-
const
|
|
2789
|
-
|
|
2790
|
-
instructions: [
|
|
2791
|
-
"Start a natural discovery conversation to gather project context",
|
|
2792
|
-
"Ask about the problem being solved, vision, constraints, and success criteria",
|
|
2793
|
-
"Keep it conversational - don't list upcoming steps or announce what you'll do"
|
|
2794
|
-
],
|
|
2795
|
-
commands: [
|
|
2796
|
-
{
|
|
2797
|
-
command: "discover",
|
|
2798
|
-
description: "Start or continue discovery conversation",
|
|
2799
|
-
when: "To gather project context"
|
|
2800
|
-
}
|
|
2801
|
-
]
|
|
2802
|
-
};
|
|
2875
|
+
const defaultGaps = ["problem", "vision", "users", "values", "constraints", "success"];
|
|
2876
|
+
const aiGuidance = getDiscoveryGuidance(null, null, null, "standard", defaultGaps, true);
|
|
2803
2877
|
return success({ project: { id: project.id, name: project.name } }, sections, aiGuidance);
|
|
2804
2878
|
}
|
|
2805
2879
|
async function status(state) {
|
|
@@ -2808,87 +2882,122 @@ async function status(state) {
|
|
|
2808
2882
|
}
|
|
2809
2883
|
const context2 = await state.readDiscoveryContext();
|
|
2810
2884
|
const constraints = await state.readConstraints();
|
|
2811
|
-
const
|
|
2812
|
-
const
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2885
|
+
const history2 = await state.readDiscoveryHistory();
|
|
2886
|
+
const coreCategories = [
|
|
2887
|
+
"problem",
|
|
2888
|
+
"vision",
|
|
2889
|
+
"users",
|
|
2890
|
+
"values",
|
|
2891
|
+
"constraints",
|
|
2892
|
+
"success"
|
|
2893
|
+
];
|
|
2894
|
+
const completedCategories = coreCategories.filter(
|
|
2895
|
+
(cat) => checkCategoryHasContent(cat, context2, constraints, history2)
|
|
2896
|
+
);
|
|
2817
2897
|
const progress = {
|
|
2818
|
-
answeredRequired,
|
|
2819
|
-
totalRequired:
|
|
2820
|
-
percent:
|
|
2898
|
+
answeredRequired: completedCategories.length,
|
|
2899
|
+
totalRequired: coreCategories.length,
|
|
2900
|
+
percent: Math.round(completedCategories.length / coreCategories.length * 100)
|
|
2821
2901
|
};
|
|
2822
2902
|
const data = {
|
|
2823
2903
|
context: context2,
|
|
2824
2904
|
constraints,
|
|
2825
|
-
history,
|
|
2826
|
-
progress
|
|
2827
|
-
isComplete: history?.is_complete || false
|
|
2905
|
+
history: history2,
|
|
2906
|
+
progress
|
|
2828
2907
|
};
|
|
2829
2908
|
const sections = [
|
|
2830
2909
|
section.header("Discovery Status"),
|
|
2831
2910
|
section.subheader("Progress"),
|
|
2832
|
-
section.progress(progress.answeredRequired, progress.totalRequired, "
|
|
2911
|
+
section.progress(progress.answeredRequired, progress.totalRequired, "Core categories"),
|
|
2833
2912
|
section.blank(),
|
|
2834
2913
|
section.subheader("Context Gathered")
|
|
2835
2914
|
];
|
|
2836
|
-
if (context2
|
|
2837
|
-
sections.push(section.success(`Problem: ${context2
|
|
2915
|
+
if (checkCategoryHasContent("problem", context2, constraints, history2)) {
|
|
2916
|
+
sections.push(section.success(`Problem: ${context2?.problem?.slice(0, 60) || "(in history)"}${context2?.problem && context2.problem.length > 60 ? "..." : ""}`));
|
|
2838
2917
|
} else {
|
|
2839
2918
|
sections.push(section.dim(" Problem: Not yet defined"));
|
|
2840
2919
|
}
|
|
2841
|
-
if (context2
|
|
2842
|
-
sections.push(section.success(`Vision: ${context2
|
|
2920
|
+
if (checkCategoryHasContent("vision", context2, constraints, history2)) {
|
|
2921
|
+
sections.push(section.success(`Vision: ${context2?.vision?.slice(0, 60) || "(in history)"}${context2?.vision && context2.vision.length > 60 ? "..." : ""}`));
|
|
2843
2922
|
} else {
|
|
2844
2923
|
sections.push(section.dim(" Vision: Not yet defined"));
|
|
2845
2924
|
}
|
|
2846
|
-
if (
|
|
2847
|
-
sections.push(section.success(`
|
|
2925
|
+
if (checkCategoryHasContent("users", context2, constraints, history2)) {
|
|
2926
|
+
sections.push(section.success(`Users: ${context2?.users?.length || 0} defined`));
|
|
2927
|
+
} else {
|
|
2928
|
+
sections.push(section.dim(" Users: None defined"));
|
|
2929
|
+
}
|
|
2930
|
+
if (checkCategoryHasContent("values", context2, constraints, history2)) {
|
|
2931
|
+
sections.push(section.success(`Values: ${context2?.values?.length || 0} defined`));
|
|
2848
2932
|
} else {
|
|
2849
2933
|
sections.push(section.dim(" Values: None defined"));
|
|
2850
2934
|
}
|
|
2851
|
-
if (
|
|
2852
|
-
sections.push(section.success(`Success Criteria: ${context2
|
|
2935
|
+
if (checkCategoryHasContent("success", context2, constraints, history2)) {
|
|
2936
|
+
sections.push(section.success(`Success Criteria: ${context2?.success_criteria?.length || 0} defined`));
|
|
2853
2937
|
} else {
|
|
2854
2938
|
sections.push(section.dim(" Success Criteria: None defined"));
|
|
2855
2939
|
}
|
|
2856
2940
|
sections.push(section.blank());
|
|
2857
2941
|
sections.push(section.subheader("Constraints"));
|
|
2858
|
-
if (constraints
|
|
2859
|
-
for (const c of constraints
|
|
2942
|
+
if (checkCategoryHasContent("constraints", context2, constraints, history2)) {
|
|
2943
|
+
for (const c of constraints?.constraints || []) {
|
|
2860
2944
|
sections.push(section.text(` [${c.type}] ${c.constraint}`));
|
|
2861
2945
|
}
|
|
2862
2946
|
} else {
|
|
2863
2947
|
sections.push(section.dim(" No constraints defined"));
|
|
2864
2948
|
}
|
|
2865
2949
|
sections.push(section.blank());
|
|
2866
|
-
|
|
2867
|
-
|
|
2950
|
+
const hasContext = checkCategoryHasContent("problem", context2, constraints, history2) || checkCategoryHasContent("vision", context2, constraints, history2);
|
|
2951
|
+
if (hasContext && progress.percent >= 50) {
|
|
2952
|
+
sections.push(section.success("Ready for planning - or continue adding context"));
|
|
2868
2953
|
} else {
|
|
2869
|
-
sections.push(section.
|
|
2954
|
+
sections.push(section.info("Continue discovery to gather more context"));
|
|
2870
2955
|
}
|
|
2871
2956
|
return success(data, sections);
|
|
2872
2957
|
}
|
|
2958
|
+
async function history(state) {
|
|
2959
|
+
if (!await state.isInitialized()) {
|
|
2960
|
+
return error("No Lisa project found.", "NOT_INITIALIZED");
|
|
2961
|
+
}
|
|
2962
|
+
const historyData = await state.readDiscoveryHistory();
|
|
2963
|
+
const entries = historyData?.entries || [];
|
|
2964
|
+
const sections = [
|
|
2965
|
+
section.header("Discovery History"),
|
|
2966
|
+
section.info(`${entries.length} entries recorded`),
|
|
2967
|
+
section.blank()
|
|
2968
|
+
];
|
|
2969
|
+
if (entries.length === 0) {
|
|
2970
|
+
sections.push(section.dim("No discovery entries yet. Run 'lisa discover' to start."));
|
|
2971
|
+
} else {
|
|
2972
|
+
for (const entry of entries) {
|
|
2973
|
+
const date = new Date(entry.timestamp).toLocaleDateString();
|
|
2974
|
+
sections.push(section.subheader(`[${entry.category}] ${date}`));
|
|
2975
|
+
sections.push(section.dim(` Q: ${entry.question}`));
|
|
2976
|
+
sections.push(section.text(` A: ${entry.answer}`));
|
|
2977
|
+
sections.push(section.blank());
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
return success({ entries, count: entries.length }, sections);
|
|
2981
|
+
}
|
|
2873
2982
|
async function start(state, options = {}) {
|
|
2874
2983
|
if (!await state.isInitialized()) {
|
|
2875
2984
|
return error("No Lisa project found. Run 'discover init' first.", "NOT_INITIALIZED");
|
|
2876
2985
|
}
|
|
2877
|
-
const
|
|
2986
|
+
const history2 = await state.readDiscoveryHistory();
|
|
2878
2987
|
const context2 = await state.readDiscoveryContext();
|
|
2879
2988
|
const constraints = await state.readConstraints();
|
|
2880
|
-
const depth = options.depth ||
|
|
2881
|
-
if (options.depth &&
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
await state.writeDiscoveryHistory(
|
|
2989
|
+
const depth = options.depth || history2?.depth_preference || "standard";
|
|
2990
|
+
if (options.depth && history2 && history2.depth_preference !== options.depth) {
|
|
2991
|
+
history2.depth_preference = options.depth;
|
|
2992
|
+
history2.last_active = now();
|
|
2993
|
+
await state.writeDiscoveryHistory(history2);
|
|
2885
2994
|
}
|
|
2886
|
-
const gaps = findDiscoveryGaps(context2, constraints, depth);
|
|
2995
|
+
const gaps = findDiscoveryGaps(context2, constraints, history2, depth);
|
|
2887
2996
|
const data = {
|
|
2888
2997
|
depth,
|
|
2889
2998
|
context: context2,
|
|
2890
2999
|
constraints,
|
|
2891
|
-
history,
|
|
3000
|
+
history: history2,
|
|
2892
3001
|
gaps
|
|
2893
3002
|
};
|
|
2894
3003
|
const depthLabels = {
|
|
@@ -2937,7 +3046,7 @@ async function start(state, options = {}) {
|
|
|
2937
3046
|
sections.push(section.blank());
|
|
2938
3047
|
const guidanceForDepth = DISCOVERY_GUIDANCE_DATA.filter((g) => g.includedIn.includes(depth));
|
|
2939
3048
|
for (const guidance of guidanceForDepth) {
|
|
2940
|
-
const hasContent = checkCategoryHasContent(guidance.category, context2, constraints);
|
|
3049
|
+
const hasContent = checkCategoryHasContent(guidance.category, context2, constraints, history2);
|
|
2941
3050
|
const icon = hasContent ? "\u2713" : "\u25CB";
|
|
2942
3051
|
const statusText = hasContent ? "(has content)" : "(explore this)";
|
|
2943
3052
|
sections.push(section.text(` ${icon} [${guidance.category}] ${guidance.purpose} ${statusText}`));
|
|
@@ -2949,16 +3058,16 @@ async function start(state, options = {}) {
|
|
|
2949
3058
|
sections.push(section.blank());
|
|
2950
3059
|
sections.push(section.divider());
|
|
2951
3060
|
sections.push(section.blank());
|
|
2952
|
-
const aiGuidance = getDiscoveryGuidance(context2,
|
|
3061
|
+
const aiGuidance = getDiscoveryGuidance(context2, history2, constraints, depth, gaps);
|
|
2953
3062
|
return success(data, sections, aiGuidance);
|
|
2954
3063
|
}
|
|
2955
3064
|
async function addEntry(state, options) {
|
|
2956
3065
|
if (!await state.isInitialized()) {
|
|
2957
3066
|
return error("No Lisa project found.", "NOT_INITIALIZED");
|
|
2958
3067
|
}
|
|
2959
|
-
let
|
|
2960
|
-
if (!
|
|
2961
|
-
|
|
3068
|
+
let history2 = await state.readDiscoveryHistory();
|
|
3069
|
+
if (!history2) {
|
|
3070
|
+
history2 = { entries: [] };
|
|
2962
3071
|
}
|
|
2963
3072
|
const entry = {
|
|
2964
3073
|
timestamp: now(),
|
|
@@ -2966,56 +3075,17 @@ async function addEntry(state, options) {
|
|
|
2966
3075
|
answer: options.answer,
|
|
2967
3076
|
category: options.category
|
|
2968
3077
|
};
|
|
2969
|
-
|
|
2970
|
-
if (!
|
|
2971
|
-
|
|
3078
|
+
history2.entries.push(entry);
|
|
3079
|
+
if (!history2.started) {
|
|
3080
|
+
history2.started = now();
|
|
2972
3081
|
}
|
|
2973
|
-
|
|
2974
|
-
await state.writeDiscoveryHistory(
|
|
3082
|
+
history2.last_active = now();
|
|
3083
|
+
await state.writeDiscoveryHistory(history2);
|
|
2975
3084
|
await updateContextFromEntry(state, entry);
|
|
2976
3085
|
const sections = [
|
|
2977
3086
|
section.success(`Added ${options.category} entry`)
|
|
2978
3087
|
];
|
|
2979
|
-
return success({ entry }, sections);
|
|
2980
|
-
}
|
|
2981
|
-
async function complete(state) {
|
|
2982
|
-
if (!await state.isInitialized()) {
|
|
2983
|
-
return error("No Lisa project found.", "NOT_INITIALIZED");
|
|
2984
|
-
}
|
|
2985
|
-
const history = await state.readDiscoveryHistory();
|
|
2986
|
-
if (!history) {
|
|
2987
|
-
return error("No discovery history found.", "NO_HISTORY");
|
|
2988
|
-
}
|
|
2989
|
-
history.is_complete = true;
|
|
2990
|
-
history.completed = now();
|
|
2991
|
-
history.last_active = now();
|
|
2992
|
-
await state.writeDiscoveryHistory(history);
|
|
2993
|
-
const sections = [
|
|
2994
|
-
section.success("Discovery checkpoint saved!"),
|
|
2995
|
-
section.blank(),
|
|
2996
|
-
section.info("You can continue discovery anytime with: discover"),
|
|
2997
|
-
section.info("Next step: Generate milestones with 'plan milestones'")
|
|
2998
|
-
];
|
|
2999
|
-
const aiGuidance = {
|
|
3000
|
-
situation: "Discovery marked complete, ready for milestone planning",
|
|
3001
|
-
instructions: [
|
|
3002
|
-
"Discovery is complete but can be continued anytime",
|
|
3003
|
-
"Next step is to generate milestones based on discovery context"
|
|
3004
|
-
],
|
|
3005
|
-
commands: [
|
|
3006
|
-
{
|
|
3007
|
-
command: "plan milestones",
|
|
3008
|
-
description: "Generate milestones from discovery",
|
|
3009
|
-
when: "To create the project roadmap"
|
|
3010
|
-
},
|
|
3011
|
-
{
|
|
3012
|
-
command: "discover",
|
|
3013
|
-
description: "Continue adding discovery context",
|
|
3014
|
-
when: "If more context is needed"
|
|
3015
|
-
}
|
|
3016
|
-
]
|
|
3017
|
-
};
|
|
3018
|
-
return success({ completed: true }, sections, aiGuidance);
|
|
3088
|
+
return success({ entry }, sections, getAddEntryGuidance());
|
|
3019
3089
|
}
|
|
3020
3090
|
async function findEpicByIdOrSlug(state, epicIdOrSlug) {
|
|
3021
3091
|
const epicDirs = await state.listEpicDirs();
|
|
@@ -3455,6 +3525,30 @@ function getStoriesGuidance(ctx) {
|
|
|
3455
3525
|
}
|
|
3456
3526
|
};
|
|
3457
3527
|
}
|
|
3528
|
+
function getAddEpicGuidance(epicId) {
|
|
3529
|
+
return {
|
|
3530
|
+
situation: `Epic ${epicId} created, ready for discovery or PRD`,
|
|
3531
|
+
instructions: [
|
|
3532
|
+
"Ask user if they want to run discovery for this epic",
|
|
3533
|
+
"Discovery helps gather scope, constraints, and success criteria",
|
|
3534
|
+
"If they skip, proceed directly to PRD generation"
|
|
3535
|
+
],
|
|
3536
|
+
commands: [
|
|
3537
|
+
{
|
|
3538
|
+
command: "discover element",
|
|
3539
|
+
args: `{ elementType: 'epic', elementId: '${epicId}' }`,
|
|
3540
|
+
description: "Run epic discovery",
|
|
3541
|
+
when: "To gather more context before PRD"
|
|
3542
|
+
},
|
|
3543
|
+
{
|
|
3544
|
+
command: "plan epic",
|
|
3545
|
+
args: epicId,
|
|
3546
|
+
description: "Plan epic (PRD generation)",
|
|
3547
|
+
when: "To skip discovery and proceed to PRD"
|
|
3548
|
+
}
|
|
3549
|
+
]
|
|
3550
|
+
};
|
|
3551
|
+
}
|
|
3458
3552
|
|
|
3459
3553
|
// src/core/commands/plan.ts
|
|
3460
3554
|
async function showMilestones(state) {
|
|
@@ -3463,11 +3557,10 @@ async function showMilestones(state) {
|
|
|
3463
3557
|
}
|
|
3464
3558
|
const index = await state.readMilestoneIndex();
|
|
3465
3559
|
const context2 = await state.readDiscoveryContext();
|
|
3466
|
-
const history = await state.readDiscoveryHistory();
|
|
3467
|
-
const discoveryComplete = history?.is_complete || false;
|
|
3468
3560
|
const sections = [section.header("Milestones")];
|
|
3469
|
-
|
|
3470
|
-
|
|
3561
|
+
const hasContext = !!context2?.problem || !!context2?.vision;
|
|
3562
|
+
if (!hasContext) {
|
|
3563
|
+
sections.push(section.warning("No discovery context found. Run discovery first."));
|
|
3471
3564
|
sections.push(section.dim(" Run: discover"));
|
|
3472
3565
|
return success(
|
|
3473
3566
|
{
|
|
@@ -3709,29 +3802,7 @@ async function addEpic(state, options) {
|
|
|
3709
3802
|
section.blank(),
|
|
3710
3803
|
section.info("Next: Run discovery for this epic or proceed to PRD")
|
|
3711
3804
|
];
|
|
3712
|
-
|
|
3713
|
-
situation: `Epic ${epicId} created, ready for discovery or PRD`,
|
|
3714
|
-
instructions: [
|
|
3715
|
-
"Ask user if they want to run discovery for this epic",
|
|
3716
|
-
"Discovery helps gather scope, constraints, and success criteria",
|
|
3717
|
-
"If they skip, proceed directly to PRD generation"
|
|
3718
|
-
],
|
|
3719
|
-
commands: [
|
|
3720
|
-
{
|
|
3721
|
-
command: "discover element",
|
|
3722
|
-
args: `{ elementType: 'epic', elementId: '${epicId}' }`,
|
|
3723
|
-
description: "Run epic discovery",
|
|
3724
|
-
when: "To gather more context before PRD"
|
|
3725
|
-
},
|
|
3726
|
-
{
|
|
3727
|
-
command: "plan epic",
|
|
3728
|
-
args: epicId,
|
|
3729
|
-
description: "Plan epic (PRD generation)",
|
|
3730
|
-
when: "To skip discovery and proceed to PRD"
|
|
3731
|
-
}
|
|
3732
|
-
]
|
|
3733
|
-
};
|
|
3734
|
-
return success({ epic }, sections, aiGuidance);
|
|
3805
|
+
return success({ epic }, sections, getAddEpicGuidance(epicId));
|
|
3735
3806
|
}
|
|
3736
3807
|
async function planEpic(state, options) {
|
|
3737
3808
|
if (!await state.isInitialized()) {
|
|
@@ -3983,15 +4054,39 @@ async function saveStories(state, options) {
|
|
|
3983
4054
|
if (!epicDir) {
|
|
3984
4055
|
return error(`Epic ${options.epicId} not found.`, "NOT_FOUND");
|
|
3985
4056
|
}
|
|
4057
|
+
const validatedStories = [];
|
|
4058
|
+
const validationErrors = [];
|
|
4059
|
+
for (let i = 0; i < options.stories.length; i++) {
|
|
4060
|
+
const storyInput = options.stories[i];
|
|
4061
|
+
const result = StoryInputSchema.safeParse(storyInput);
|
|
4062
|
+
if (!result.success) {
|
|
4063
|
+
const issues = result.error.issues.map((issue) => {
|
|
4064
|
+
const path3 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
|
|
4065
|
+
return `${path3}${issue.message}`;
|
|
4066
|
+
});
|
|
4067
|
+
validationErrors.push(`Story ${i + 1} (${storyInput.title || "untitled"}): ${issues.join(", ")}`);
|
|
4068
|
+
} else {
|
|
4069
|
+
const story2 = {
|
|
4070
|
+
...result.data,
|
|
4071
|
+
id: result.data.id || `${options.epicId}.S${i + 1}`
|
|
4072
|
+
};
|
|
4073
|
+
validatedStories.push(story2);
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
if (validationErrors.length > 0) {
|
|
4077
|
+
const sections2 = [
|
|
4078
|
+
section.error("Story validation failed:"),
|
|
4079
|
+
...validationErrors.map((err) => section.dim(` \u2022 ${err}`)),
|
|
4080
|
+
section.blank(),
|
|
4081
|
+
section.info("Required fields: title, description"),
|
|
4082
|
+
section.info("Optional fields: type (feature|bug|chore|spike), requirements, criteria, dependencies")
|
|
4083
|
+
];
|
|
4084
|
+
return error(validationErrors.join("\n"), "VALIDATION_ERROR", sections2);
|
|
4085
|
+
}
|
|
3986
4086
|
const slug = epicDir.split("-").slice(1).join("-");
|
|
3987
4087
|
const storiesFile = {
|
|
3988
4088
|
epic_id: options.epicId,
|
|
3989
|
-
stories:
|
|
3990
|
-
...s,
|
|
3991
|
-
id: s.id || `${options.epicId}.S${i + 1}`,
|
|
3992
|
-
status: s.status || "todo",
|
|
3993
|
-
assignee: s.assignee || null
|
|
3994
|
-
})),
|
|
4089
|
+
stories: validatedStories,
|
|
3995
4090
|
coverage: {},
|
|
3996
4091
|
validation: {
|
|
3997
4092
|
coverage_complete: false,
|
|
@@ -5438,10 +5533,6 @@ var LisaEngine = class {
|
|
|
5438
5533
|
* Add a discovery entry
|
|
5439
5534
|
*/
|
|
5440
5535
|
addEntry: (options) => addEntry(this.state, options),
|
|
5441
|
-
/**
|
|
5442
|
-
* Mark discovery as complete
|
|
5443
|
-
*/
|
|
5444
|
-
complete: () => complete(this.state),
|
|
5445
5536
|
/**
|
|
5446
5537
|
* Show discovery status
|
|
5447
5538
|
*/
|
|
@@ -5457,7 +5548,11 @@ var LisaEngine = class {
|
|
|
5457
5548
|
/**
|
|
5458
5549
|
* Complete element discovery
|
|
5459
5550
|
*/
|
|
5460
|
-
completeElement: (options) => completeElement(this.state, options)
|
|
5551
|
+
completeElement: (options) => completeElement(this.state, options),
|
|
5552
|
+
/**
|
|
5553
|
+
* Show discovery history (all Q&A entries)
|
|
5554
|
+
*/
|
|
5555
|
+
history: () => history(this.state)
|
|
5461
5556
|
};
|
|
5462
5557
|
// ==========================================================================
|
|
5463
5558
|
// Plan Commands
|
|
@@ -5901,7 +5996,7 @@ function showDiscoverHelp() {
|
|
|
5901
5996
|
console.log(" (none) Start or continue discovery session");
|
|
5902
5997
|
console.log(" init <name> Initialize a new project");
|
|
5903
5998
|
console.log(" status Show discovery progress and gaps");
|
|
5904
|
-
console.log("
|
|
5999
|
+
console.log(" history Show all discovery Q&A entries");
|
|
5905
6000
|
console.log(" add-entry Add a discovery entry (project-level)");
|
|
5906
6001
|
console.log(" epic <id> Start discovery for a specific epic");
|
|
5907
6002
|
console.log(" milestone <id> Start discovery for a milestone");
|
|
@@ -6187,8 +6282,8 @@ async function main() {
|
|
|
6187
6282
|
handleResult(result);
|
|
6188
6283
|
break;
|
|
6189
6284
|
}
|
|
6190
|
-
case "
|
|
6191
|
-
const result = await engine.discover.
|
|
6285
|
+
case "history": {
|
|
6286
|
+
const result = await engine.discover.history();
|
|
6192
6287
|
handleResult(result);
|
|
6193
6288
|
break;
|
|
6194
6289
|
}
|
package/package.json
CHANGED
package/skills/lisa/SKILL.md
CHANGED
|
@@ -8,58 +8,60 @@ user-invocable: true
|
|
|
8
8
|
|
|
9
9
|
Plan and organize projects into milestones, epics, and stories.
|
|
10
10
|
|
|
11
|
+
## Core Principles
|
|
12
|
+
|
|
13
|
+
1. **Follow command output instructions.** Every Lisa command returns AI guidance with next steps. Read and follow them exactly.
|
|
14
|
+
|
|
15
|
+
2. **Present before creating.** When the user describes work to be done:
|
|
16
|
+
- Summarize what you understood
|
|
17
|
+
- Propose the milestones, epics, or stories you plan to create
|
|
18
|
+
- Wait for user confirmation before running any `add-milestone`, `add-epic`, or `add-story` commands
|
|
19
|
+
- Never auto-create artifacts without explicit approval
|
|
20
|
+
|
|
11
21
|
## Quick Start
|
|
12
22
|
|
|
13
23
|
```bash
|
|
14
|
-
# Check project
|
|
24
|
+
# Check if project exists
|
|
15
25
|
lisa status
|
|
16
26
|
|
|
17
|
-
#
|
|
27
|
+
# New project - get name first, then:
|
|
18
28
|
lisa discover init "Project Name"
|
|
19
29
|
|
|
20
|
-
# View
|
|
30
|
+
# View board
|
|
21
31
|
lisa status board
|
|
22
32
|
```
|
|
23
33
|
|
|
24
|
-
##
|
|
25
|
-
|
|
26
|
-
### New Project
|
|
34
|
+
## New Project Flow
|
|
27
35
|
|
|
28
|
-
1.
|
|
29
|
-
2.
|
|
30
|
-
3. **
|
|
31
|
-
4. **Discovery conversation** - Natural Q&A about problem, vision, constraints
|
|
32
|
-
- For brownfield: suggest answers based on what you read
|
|
36
|
+
1. Ask user for project name
|
|
37
|
+
2. Run `lisa discover init "Project Name"`
|
|
38
|
+
3. **Follow the INSTRUCTIONS section** in the output - it tells you what to do next
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
## Existing Project
|
|
35
41
|
|
|
36
|
-
1.
|
|
37
|
-
2.
|
|
38
|
-
3. **Add to plan** or **work on stories** as needed
|
|
42
|
+
1. Run `lisa status` to see current state
|
|
43
|
+
2. Follow the guidance in the output
|
|
39
44
|
|
|
40
|
-
## Commands
|
|
45
|
+
## Commands
|
|
41
46
|
|
|
42
47
|
| Action | Command |
|
|
43
48
|
|--------|---------|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
| Start discovery | `lisa discover init "Name"` |
|
|
49
|
+
| Status | `lisa status` |
|
|
50
|
+
| Board | `lisa status board` |
|
|
51
|
+
| Initialize | `lisa discover init "Name"` |
|
|
48
52
|
| Continue discovery | `lisa discover` |
|
|
53
|
+
| Add discovery entry | `lisa discover add-entry --category <cat> --question '<q>' --answer '<a>'` |
|
|
49
54
|
| View milestones | `lisa plan milestones` |
|
|
50
55
|
| Add milestone | `lisa plan add-milestone --name 'Name' --description 'Desc'` |
|
|
51
56
|
| Add epic | `lisa plan add-epic --milestone M1 --name 'Name' --description 'Desc'` |
|
|
52
57
|
| Generate stories | `lisa plan stories E1` |
|
|
53
58
|
| Mark progress | `lisa feedback mark <id> <status>` |
|
|
54
|
-
| Validate plan | `lisa validate` |
|
|
55
59
|
|
|
56
60
|
## ID Formats
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
| Epic | `E#` | `E1` |
|
|
62
|
-
| Story | `E#.S#` | `E1.S2` |
|
|
62
|
+
- Milestone: `M1`, `M2`
|
|
63
|
+
- Epic: `E1`, `E2`
|
|
64
|
+
- Story: `E1.S1`, `E1.S2`
|
|
63
65
|
|
|
64
66
|
## References
|
|
65
67
|
|
|
@@ -15,7 +15,7 @@ lisa discover [subcommand] [options]
|
|
|
15
15
|
| *(none)* | Start or continue discovery session |
|
|
16
16
|
| `init <name>` | Initialize new project |
|
|
17
17
|
| `status` | Show discovery progress and gaps |
|
|
18
|
-
| `
|
|
18
|
+
| `history` | Show all discovery Q&A entries |
|
|
19
19
|
| `add-entry` | Add discovery entry manually |
|
|
20
20
|
| `epic <id>` | Start discovery for specific epic |
|
|
21
21
|
| `milestone <id>` | Start discovery for milestone |
|
|
@@ -32,7 +32,7 @@ lisa discover [subcommand] [options]
|
|
|
32
32
|
|
|
33
33
|
| Option | Description |
|
|
34
34
|
|--------|-------------|
|
|
35
|
-
| `--category <cat>` | Category: `problem`, `
|
|
35
|
+
| `--category <cat>` | Category: `problem`, `vision`, `users`, `values`, `constraints`, `success`, `other` |
|
|
36
36
|
| `--question '<q>'` | The discovery question |
|
|
37
37
|
| `--answer '<a>'` | The answer/information |
|
|
38
38
|
|
|
@@ -9,77 +9,75 @@ User says: "Help me plan a todo app"
|
|
|
9
9
|
```bash
|
|
10
10
|
# 1. Initialize the project
|
|
11
11
|
lisa discover init "Todo App"
|
|
12
|
-
|
|
13
|
-
# 2. Start discovery (ask about problem, users, goals)
|
|
14
|
-
lisa discover
|
|
15
|
-
|
|
16
|
-
# 3. Check discovery progress
|
|
17
|
-
lisa discover status
|
|
18
|
-
|
|
19
|
-
# 4. For comprehensive planning, use deep mode
|
|
20
|
-
lisa discover --deep
|
|
12
|
+
# 2. Follow the INSTRUCTIONS in the output (it will tell you to ask discovery questions)
|
|
21
13
|
```
|
|
22
14
|
|
|
23
15
|
**Key points:**
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
16
|
+
- Ask user for project name before initializing
|
|
17
|
+
- After init, follow the AI guidance in the command output
|
|
18
|
+
- The guidance will prompt you to start a discovery conversation
|
|
27
19
|
|
|
28
20
|
---
|
|
29
21
|
|
|
30
|
-
## Example 2:
|
|
22
|
+
## Example 2: Existing Codebase
|
|
31
23
|
|
|
32
24
|
User says: "Help me plan features for my existing app"
|
|
33
25
|
|
|
34
26
|
```bash
|
|
35
|
-
# 1. Initialize
|
|
27
|
+
# 1. Initialize
|
|
36
28
|
lisa discover init "My Existing App"
|
|
37
|
-
|
|
38
|
-
# 2. Analyze the codebase first
|
|
39
|
-
lisa discover codebase
|
|
40
|
-
|
|
41
|
-
# 3. Continue with regular discovery
|
|
42
|
-
lisa discover
|
|
29
|
+
# 2. Follow the INSTRUCTIONS - acknowledge what you learned, then ask discovery questions
|
|
43
30
|
```
|
|
44
31
|
|
|
45
32
|
**Key points:**
|
|
46
|
-
-
|
|
47
|
-
-
|
|
33
|
+
- Briefly acknowledge what you learned from the codebase (1-2 sentences)
|
|
34
|
+
- Then ask the discovery question (e.g., "What problem are we solving?")
|
|
35
|
+
- Do NOT offer menu options - just ask the question
|
|
48
36
|
|
|
49
37
|
---
|
|
50
38
|
|
|
51
39
|
## Example 3: Creating the Roadmap
|
|
52
40
|
|
|
53
|
-
User says: "
|
|
54
|
-
|
|
41
|
+
User says: "I have some tickets related to knowledge improvements... need to edit digests, massage prompts..."
|
|
42
|
+
|
|
43
|
+
**Step 1: Summarize what you understood**
|
|
44
|
+
> "I understand you have work around Knowledge V1 MVP with several areas:
|
|
45
|
+
> - Editing knowledge evaluation digests
|
|
46
|
+
> - Prompt improvements for interview questions
|
|
47
|
+
> - New action set digests
|
|
48
|
+
> - Deprecating old digest sections
|
|
49
|
+
>
|
|
50
|
+
> Here's what I propose to create:
|
|
51
|
+
>
|
|
52
|
+
> **Milestone: Knowledge V1 MVP**
|
|
53
|
+
> - Epic 1: Digest Editing - ability to edit evaluation digests
|
|
54
|
+
> - Epic 2: Interview Question Improvements - prompt massage for focus
|
|
55
|
+
> - Epic 3: Action Set Digests - new digest type
|
|
56
|
+
> - Epic 4: Digest Cleanup - deprecate pre-arrival sections
|
|
57
|
+
>
|
|
58
|
+
> Does this structure look right? Any adjustments?"
|
|
59
|
+
|
|
60
|
+
**Step 2: Wait for user confirmation**
|
|
61
|
+
|
|
62
|
+
**Step 3: Only after approval, create the artifacts**
|
|
55
63
|
```bash
|
|
56
|
-
# 1. Check discovery is sufficient
|
|
57
|
-
lisa discover status
|
|
58
|
-
|
|
59
|
-
# 2. Generate/view milestones
|
|
60
|
-
lisa plan milestones
|
|
61
|
-
|
|
62
|
-
# 3. Add milestones manually if needed
|
|
63
64
|
lisa plan add-milestone \
|
|
64
|
-
--name 'MVP' \
|
|
65
|
-
--description 'Core
|
|
66
|
-
|
|
67
|
-
lisa plan add-milestone \
|
|
68
|
-
--name 'Collaboration' \
|
|
69
|
-
--description 'Shared lists and real-time sync'
|
|
70
|
-
|
|
71
|
-
# 4. Generate epics for milestone
|
|
72
|
-
lisa plan epics M1
|
|
65
|
+
--name 'Knowledge V1 MVP' \
|
|
66
|
+
--description 'Core knowledge system improvements including digest editing and cleanup'
|
|
73
67
|
|
|
74
|
-
# 5. Add epics manually if needed
|
|
75
68
|
lisa plan add-epic \
|
|
76
69
|
--milestone M1 \
|
|
77
|
-
--name '
|
|
78
|
-
--description '
|
|
70
|
+
--name 'Digest Editing' \
|
|
71
|
+
--description 'Enable editing of knowledge evaluation digests'
|
|
72
|
+
# ... etc
|
|
79
73
|
```
|
|
80
74
|
|
|
81
75
|
**Key points:**
|
|
82
|
-
-
|
|
76
|
+
- **ALWAYS present proposed structure before creating**
|
|
77
|
+
- Summarize user's intent first
|
|
78
|
+
- List milestones and epics you plan to create
|
|
79
|
+
- Wait for explicit approval
|
|
80
|
+
- Never auto-create artifacts
|
|
83
81
|
|
|
84
82
|
---
|
|
85
83
|
|
|
@@ -73,6 +73,13 @@ lisa feedback mark E1.S2 blocked --reason "Describe the issue"
|
|
|
73
73
|
|
|
74
74
|
**Important:** Never mark done without user confirmation.
|
|
75
75
|
|
|
76
|
+
## Execution
|
|
77
|
+
|
|
78
|
+
Run Lisa commands with:
|
|
79
|
+
```bash
|
|
80
|
+
lisa <command> # if installed globally
|
|
81
|
+
```
|
|
82
|
+
|
|
76
83
|
## Commands Reference
|
|
77
84
|
|
|
78
85
|
| Action | Command |
|