devworkspace 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +127 -3
- package/dist/agents/claude/adapter.d.ts +10 -0
- package/dist/agents/claude/adapter.js +97 -0
- package/dist/agents/claude/adapter.js.map +1 -0
- package/dist/agents/claude/content.d.ts +7 -0
- package/dist/agents/claude/content.js +136 -0
- package/dist/agents/claude/content.js.map +1 -0
- package/dist/agents/types.d.ts +26 -0
- package/dist/agents/types.js +2 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/cli/args.d.ts +194 -0
- package/dist/cli/args.js +93 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/help.d.ts +10 -0
- package/dist/cli/help.js +169 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +19 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/render.d.ts +39 -0
- package/dist/cli/render.js +211 -0
- package/dist/cli/render.js.map +1 -0
- package/dist/cli/run.d.ts +10 -0
- package/dist/cli/run.js +329 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/config/schemas.d.ts +76 -0
- package/dist/config/schemas.js +83 -0
- package/dist/config/schemas.js.map +1 -0
- package/dist/config/validate.d.ts +22 -0
- package/dist/config/validate.js +76 -0
- package/dist/config/validate.js.map +1 -0
- package/dist/core/migrations.d.ts +15 -0
- package/dist/core/migrations.js +34 -0
- package/dist/core/migrations.js.map +1 -0
- package/dist/core/version.d.ts +2 -0
- package/dist/core/version.js +12 -0
- package/dist/core/version.js.map +1 -0
- package/dist/core/versioned.d.ts +13 -0
- package/dist/core/versioned.js +16 -0
- package/dist/core/versioned.js.map +1 -0
- package/dist/core/workspace.d.ts +34 -0
- package/dist/core/workspace.js +92 -0
- package/dist/core/workspace.js.map +1 -0
- package/dist/decisions/decision.d.ts +31 -0
- package/dist/decisions/decision.js +123 -0
- package/dist/decisions/decision.js.map +1 -0
- package/dist/discovery/commands.d.ts +12 -0
- package/dist/discovery/commands.js +92 -0
- package/dist/discovery/commands.js.map +1 -0
- package/dist/discovery/detect.d.ts +72 -0
- package/dist/discovery/detect.js +421 -0
- package/dist/discovery/detect.js.map +1 -0
- package/dist/discovery/ignore.d.ts +6 -0
- package/dist/discovery/ignore.js +76 -0
- package/dist/discovery/ignore.js.map +1 -0
- package/dist/discovery/scan.d.ts +24 -0
- package/dist/discovery/scan.js +132 -0
- package/dist/discovery/scan.js.map +1 -0
- package/dist/errors/index.d.ts +25 -0
- package/dist/errors/index.js +39 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/git/git.d.ts +27 -0
- package/dist/git/git.js +97 -0
- package/dist/git/git.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/agent-skills.d.ts +10 -0
- package/dist/integrations/agent-skills.js +45 -0
- package/dist/integrations/agent-skills.js.map +1 -0
- package/dist/integrations/graphify.d.ts +7 -0
- package/dist/integrations/graphify.js +54 -0
- package/dist/integrations/graphify.js.map +1 -0
- package/dist/integrations/index.d.ts +6 -0
- package/dist/integrations/index.js +30 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/integrations/omniroute.d.ts +7 -0
- package/dist/integrations/omniroute.js +24 -0
- package/dist/integrations/omniroute.js.map +1 -0
- package/dist/integrations/ponytail.d.ts +10 -0
- package/dist/integrations/ponytail.js +58 -0
- package/dist/integrations/ponytail.js.map +1 -0
- package/dist/integrations/probe.d.ts +6 -0
- package/dist/integrations/probe.js +41 -0
- package/dist/integrations/probe.js.map +1 -0
- package/dist/integrations/types.d.ts +25 -0
- package/dist/integrations/types.js +2 -0
- package/dist/integrations/types.js.map +1 -0
- package/dist/knowledge/facts.d.ts +43 -0
- package/dist/knowledge/facts.js +195 -0
- package/dist/knowledge/facts.js.map +1 -0
- package/dist/portability/bundle.d.ts +22 -0
- package/dist/portability/bundle.js +102 -0
- package/dist/portability/bundle.js.map +1 -0
- package/dist/prompts/onboarding.d.ts +19 -0
- package/dist/prompts/onboarding.js +53 -0
- package/dist/prompts/onboarding.js.map +1 -0
- package/dist/prompts/refresh.d.ts +15 -0
- package/dist/prompts/refresh.js +33 -0
- package/dist/prompts/refresh.js.map +1 -0
- package/dist/prompts/requirements.d.ts +9 -0
- package/dist/prompts/requirements.js +34 -0
- package/dist/prompts/requirements.js.map +1 -0
- package/dist/security/files.d.ts +5 -0
- package/dist/security/files.js +44 -0
- package/dist/security/files.js.map +1 -0
- package/dist/security/redact.d.ts +13 -0
- package/dist/security/redact.js +43 -0
- package/dist/security/redact.js.map +1 -0
- package/dist/services/agent.d.ts +11 -0
- package/dist/services/agent.js +42 -0
- package/dist/services/agent.js.map +1 -0
- package/dist/services/analysis.d.ts +45 -0
- package/dist/services/analysis.js +151 -0
- package/dist/services/analysis.js.map +1 -0
- package/dist/services/common.d.ts +18 -0
- package/dist/services/common.js +18 -0
- package/dist/services/common.js.map +1 -0
- package/dist/services/doctor.d.ts +27 -0
- package/dist/services/doctor.js +223 -0
- package/dist/services/doctor.js.map +1 -0
- package/dist/services/init.d.ts +18 -0
- package/dist/services/init.js +135 -0
- package/dist/services/init.js.map +1 -0
- package/dist/services/knowledge.d.ts +40 -0
- package/dist/services/knowledge.js +72 -0
- package/dist/services/knowledge.js.map +1 -0
- package/dist/services/migrate.d.ts +16 -0
- package/dist/services/migrate.js +54 -0
- package/dist/services/migrate.js.map +1 -0
- package/dist/services/portability.d.ts +27 -0
- package/dist/services/portability.js +155 -0
- package/dist/services/portability.js.map +1 -0
- package/dist/services/records.d.ts +8 -0
- package/dist/services/records.js +26 -0
- package/dist/services/records.js.map +1 -0
- package/dist/services/refresh.d.ts +22 -0
- package/dist/services/refresh.js +64 -0
- package/dist/services/refresh.js.map +1 -0
- package/dist/services/status.d.ts +57 -0
- package/dist/services/status.js +111 -0
- package/dist/services/status.js.map +1 -0
- package/dist/services/tasks.d.ts +44 -0
- package/dist/services/tasks.js +136 -0
- package/dist/services/tasks.js.map +1 -0
- package/dist/sessions/session.d.ts +19 -0
- package/dist/sessions/session.js +53 -0
- package/dist/sessions/session.js.map +1 -0
- package/dist/tasks/routing.d.ts +27 -0
- package/dist/tasks/routing.js +206 -0
- package/dist/tasks/routing.js.map +1 -0
- package/dist/tasks/task.d.ts +90 -0
- package/dist/tasks/task.js +275 -0
- package/dist/tasks/task.js.map +1 -0
- package/dist/utils/cleanup.d.ts +3 -0
- package/dist/utils/cleanup.js +18 -0
- package/dist/utils/cleanup.js.map +1 -0
- package/dist/utils/fs.d.ts +17 -0
- package/dist/utils/fs.js +164 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/lock.d.ts +6 -0
- package/dist/utils/lock.js +77 -0
- package/dist/utils/lock.js.map +1 -0
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.js +18 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/paths.d.ts +18 -0
- package/dist/utils/paths.js +95 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/proc.d.ts +22 -0
- package/dist/utils/proc.js +74 -0
- package/dist/utils/proc.js.map +1 -0
- package/docs/architecture.md +53 -0
- package/docs/benchmarking.md +33 -0
- package/docs/claude.md +46 -0
- package/docs/contributing.md +64 -0
- package/docs/getting-started.md +68 -0
- package/docs/integrations.md +51 -0
- package/docs/knowledge.md +66 -0
- package/docs/portability.md +40 -0
- package/docs/routing.md +51 -0
- package/docs/security.md +44 -0
- package/docs/testing.md +58 -0
- package/docs/troubleshooting.md +26 -0
- package/package.json +68 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.js","sourceRoot":"","sources":["../../src/services/records.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAqC,MAAM,0BAA0B,CAAC;AACzG,OAAO,EAAE,YAAY,EAAE,WAAW,EAAmC,MAAM,wBAAwB,CAAC;AACpG,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAuB,MAAM,aAAa,CAAC;AAElE,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,CAAiB,EAAE,KAAoB;IAC1E,OAAO,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,CAAiB;IAClD,MAAM,EAAE,GAAG,MAAM,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,CAAC,MAAM,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,CAAiB,EAAE,MAAc;IACjE,MAAM,CAAC,GAAG,CAAC,MAAM,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IACnE,IAAI,CAAC,CAAC;QAAE,MAAM,IAAI,iBAAiB,CAAC,WAAW,EAAE,sBAAsB,MAAM,GAAG,CAAC,CAAC;IAClF,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,CAAiB,EAAE,KAAmB;IACxE,OAAO,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,CAAiB,EAAE,KAAK,GAAG,EAAE;IAC7D,MAAM,EAAE,GAAG,MAAM,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,YAAY,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;AACjC,CAAC","sourcesContent":["import { requireWorkspace } from '../core/workspace.js';\nimport { addDecision, listDecisions, type Decision, type DecisionInput } from '../decisions/decision.js';\nimport { listSessions, saveSession, type Session, type SessionInput } from '../sessions/session.js';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { isoNow, mutate, type ServiceOptions } from './common.js';\n\nexport async function recordDecision(o: ServiceOptions, input: DecisionInput): Promise<Decision> {\n return mutate(o, (ws) => addDecision(ws, input, isoNow(o)));\n}\n\nexport async function getDecisions(o: ServiceOptions): Promise<Decision[]> {\n const ws = await requireWorkspace(o.cwd);\n return (await listDecisions(ws)).decisions;\n}\n\nexport async function getDecision(o: ServiceOptions, number: number): Promise<Decision> {\n const d = (await getDecisions(o)).find((x) => x.number === number);\n if (!d) throw new DevWorkspaceError('NOT_FOUND', `No decision number ${number}.`);\n return d;\n}\n\nexport async function recordSession(o: ServiceOptions, input: SessionInput): Promise<Session> {\n return mutate(o, (ws) => saveSession(ws, input, isoNow(o)));\n}\n\nexport async function getSessions(o: ServiceOptions, limit = 10): Promise<Session[]> {\n const ws = await requireWorkspace(o.cwd);\n return listSessions(ws, limit);\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ServiceOptions } from './common.js';
|
|
2
|
+
export interface RefreshResult {
|
|
3
|
+
dryRun: boolean;
|
|
4
|
+
headChanged: boolean;
|
|
5
|
+
autoRefreshed: string[];
|
|
6
|
+
retired: string[];
|
|
7
|
+
stale: {
|
|
8
|
+
key: string;
|
|
9
|
+
value: string;
|
|
10
|
+
reason: string;
|
|
11
|
+
}[];
|
|
12
|
+
needsReview: {
|
|
13
|
+
key: string;
|
|
14
|
+
reason: string;
|
|
15
|
+
}[];
|
|
16
|
+
prompt: string | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Incremental refresh. Deterministic facts are re-derived on the spot; everything else is re-hashed against its cited
|
|
20
|
+
* sources and only the facts whose evidence changed become STALE and enter the (optional) agent refresh prompt.
|
|
21
|
+
*/
|
|
22
|
+
export declare function refreshWorkspace(o: ServiceOptions): Promise<RefreshResult>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { SCHEMA_VERSION, validateAnalysisState } from '../config/schemas.js';
|
|
3
|
+
import { loadProject } from '../core/workspace.js';
|
|
4
|
+
import { readVersioned, writeVersioned } from '../core/versioned.js';
|
|
5
|
+
import { packageVersion } from '../core/version.js';
|
|
6
|
+
import { applyStaleness, loadFacts, saveFacts } from '../knowledge/facts.js';
|
|
7
|
+
import { buildRefreshPrompt } from '../prompts/refresh.js';
|
|
8
|
+
import { pathExists, sha256Text, writeJsonIfChanged, writeTextIfChanged } from '../utils/fs.js';
|
|
9
|
+
import { applyDetectorFacts, runDiscovery, writeCommands } from './analysis.js';
|
|
10
|
+
import { isoNow, mutate } from './common.js';
|
|
11
|
+
/**
|
|
12
|
+
* Incremental refresh. Deterministic facts are re-derived on the spot; everything else is re-hashed against its cited
|
|
13
|
+
* sources and only the facts whose evidence changed become STALE and enter the (optional) agent refresh prompt.
|
|
14
|
+
*/
|
|
15
|
+
export async function refreshWorkspace(o) {
|
|
16
|
+
return mutate(o, async (ws) => {
|
|
17
|
+
const project = await loadProject(ws);
|
|
18
|
+
const now = isoNow(o);
|
|
19
|
+
const run = await runDiscovery(ws, project.name, project.settings.maxFiles);
|
|
20
|
+
let previous = null;
|
|
21
|
+
if (await pathExists(ws.analysisState)) {
|
|
22
|
+
previous = (await readVersioned(ws.analysisState, 'analysis', SCHEMA_VERSION, validateAnalysisState)).data;
|
|
23
|
+
}
|
|
24
|
+
const headChanged = !!previous && previous.gitHead !== run.gitHead;
|
|
25
|
+
if (o.dryRun) {
|
|
26
|
+
const facts = await loadFacts(ws);
|
|
27
|
+
const probe = structuredClone(facts);
|
|
28
|
+
const rep = await applyStaleness(ws.root, probe, now);
|
|
29
|
+
return {
|
|
30
|
+
dryRun: true,
|
|
31
|
+
headChanged,
|
|
32
|
+
autoRefreshed: [],
|
|
33
|
+
retired: [],
|
|
34
|
+
stale: rep.newlyStale.map((s) => ({ ...s, value: probe.facts.find((f) => f.key === s.key)?.value ?? '' })),
|
|
35
|
+
needsReview: rep.needsReview,
|
|
36
|
+
prompt: null,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
await writeJsonIfChanged(ws.discovery, run.discovery);
|
|
40
|
+
await writeCommands(ws, run.discovery.commands);
|
|
41
|
+
const det = await applyDetectorFacts(ws, run.discovery, now);
|
|
42
|
+
const facts = await loadFacts(ws);
|
|
43
|
+
const rep = await applyStaleness(ws.root, facts, now);
|
|
44
|
+
await saveFacts(ws, facts);
|
|
45
|
+
const stale = facts.facts.filter((f) => f.status === 'STALE' && f.sourceType !== 'detector').map((f) => ({ key: f.key, value: f.value, reason: f.staleReason ?? 'evidence changed' }));
|
|
46
|
+
let prompt = null;
|
|
47
|
+
if (stale.length > 0 || rep.needsReview.length > 0) {
|
|
48
|
+
await writeTextIfChanged(ws.refreshPrompt, buildRefreshPrompt({ projectName: project.name, stale, needsReview: rep.needsReview, autoRefreshed: det.written, cliVersion: packageVersion() }));
|
|
49
|
+
prompt = path.relative(ws.root, ws.refreshPrompt).replace(/\\/g, '/');
|
|
50
|
+
}
|
|
51
|
+
const state = {
|
|
52
|
+
schemaVersion: SCHEMA_VERSION,
|
|
53
|
+
analyzedAt: previous?.analyzedAt ?? now,
|
|
54
|
+
refreshedAt: now,
|
|
55
|
+
gitHead: run.gitHead,
|
|
56
|
+
fileCount: run.discovery.scan.fileCount,
|
|
57
|
+
truncated: run.discovery.scan.truncated,
|
|
58
|
+
discoveryHash: sha256Text(JSON.stringify(run.discovery)),
|
|
59
|
+
};
|
|
60
|
+
await writeVersioned(ws, ws.analysisState, state);
|
|
61
|
+
return { dryRun: false, headChanged, autoRefreshed: det.written, retired: det.retired, stale, needsReview: rep.needsReview, prompt };
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/services/refresh.ts"],"names":[],"mappings":"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","sourcesContent":["import path from 'node:path';\nimport { SCHEMA_VERSION, validateAnalysisState, type AnalysisState } from '../config/schemas.js';\nimport { loadProject } from '../core/workspace.js';\nimport { readVersioned, writeVersioned } from '../core/versioned.js';\nimport { packageVersion } from '../core/version.js';\nimport { applyStaleness, loadFacts, saveFacts } from '../knowledge/facts.js';\nimport { buildRefreshPrompt } from '../prompts/refresh.js';\nimport { pathExists, sha256Text, writeJsonIfChanged, writeTextIfChanged } from '../utils/fs.js';\nimport { applyDetectorFacts, runDiscovery, writeCommands } from './analysis.js';\nimport { isoNow, mutate, type ServiceOptions } from './common.js';\n\nexport interface RefreshResult {\n dryRun: boolean;\n headChanged: boolean;\n autoRefreshed: string[];\n retired: string[];\n stale: { key: string; value: string; reason: string }[];\n needsReview: { key: string; reason: string }[];\n prompt: string | null;\n}\n\n/**\n * Incremental refresh. Deterministic facts are re-derived on the spot; everything else is re-hashed against its cited\n * sources and only the facts whose evidence changed become STALE and enter the (optional) agent refresh prompt.\n */\nexport async function refreshWorkspace(o: ServiceOptions): Promise<RefreshResult> {\n return mutate(o, async (ws) => {\n const project = await loadProject(ws);\n const now = isoNow(o);\n const run = await runDiscovery(ws, project.name, project.settings.maxFiles);\n let previous: AnalysisState | null = null;\n if (await pathExists(ws.analysisState)) {\n previous = (await readVersioned(ws.analysisState, 'analysis', SCHEMA_VERSION, validateAnalysisState)).data;\n }\n const headChanged = !!previous && previous.gitHead !== run.gitHead;\n\n if (o.dryRun) {\n const facts = await loadFacts(ws);\n const probe = structuredClone(facts);\n const rep = await applyStaleness(ws.root, probe, now);\n return {\n dryRun: true,\n headChanged,\n autoRefreshed: [],\n retired: [],\n stale: rep.newlyStale.map((s) => ({ ...s, value: probe.facts.find((f) => f.key === s.key)?.value ?? '' })),\n needsReview: rep.needsReview,\n prompt: null,\n };\n }\n\n await writeJsonIfChanged(ws.discovery, run.discovery);\n await writeCommands(ws, run.discovery.commands);\n const det = await applyDetectorFacts(ws, run.discovery, now);\n\n const facts = await loadFacts(ws);\n const rep = await applyStaleness(ws.root, facts, now);\n await saveFacts(ws, facts);\n\n const stale = facts.facts.filter((f) => f.status === 'STALE' && f.sourceType !== 'detector').map((f) => ({ key: f.key, value: f.value, reason: f.staleReason ?? 'evidence changed' }));\n let prompt: string | null = null;\n if (stale.length > 0 || rep.needsReview.length > 0) {\n await writeTextIfChanged(\n ws.refreshPrompt,\n buildRefreshPrompt({ projectName: project.name, stale, needsReview: rep.needsReview, autoRefreshed: det.written, cliVersion: packageVersion() }),\n );\n prompt = path.relative(ws.root, ws.refreshPrompt).replace(/\\\\/g, '/');\n }\n\n const state: AnalysisState = {\n schemaVersion: SCHEMA_VERSION,\n analyzedAt: previous?.analyzedAt ?? now,\n refreshedAt: now,\n gitHead: run.gitHead,\n fileCount: run.discovery.scan.fileCount,\n truncated: run.discovery.scan.truncated,\n discoveryHash: sha256Text(JSON.stringify(run.discovery)),\n };\n await writeVersioned(ws, ws.analysisState, state);\n return { dryRun: false, headChanged, autoRefreshed: det.written, retired: det.retired, stale, needsReview: rep.needsReview, prompt };\n });\n}\n"]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Ws } from '../core/workspace.js';
|
|
2
|
+
import { type IntegrationStatus } from '../integrations/index.js';
|
|
3
|
+
import { type ServiceOptions } from './common.js';
|
|
4
|
+
export interface TaskBrief {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
type: string;
|
|
8
|
+
status: string;
|
|
9
|
+
updatedAt: string;
|
|
10
|
+
next: string;
|
|
11
|
+
}
|
|
12
|
+
export interface StatusResult {
|
|
13
|
+
initialized: boolean;
|
|
14
|
+
root: string | null;
|
|
15
|
+
project: {
|
|
16
|
+
name: string;
|
|
17
|
+
id: string;
|
|
18
|
+
} | null;
|
|
19
|
+
workspace: 'Healthy' | 'Needs attention' | 'Not initialized' | 'Unreadable';
|
|
20
|
+
workspaceProblem?: string;
|
|
21
|
+
git: {
|
|
22
|
+
available: boolean;
|
|
23
|
+
isRepo: boolean;
|
|
24
|
+
branch: string | null;
|
|
25
|
+
workingTree: 'Clean' | 'Modified' | 'Unknown';
|
|
26
|
+
changes: number;
|
|
27
|
+
};
|
|
28
|
+
knowledge: {
|
|
29
|
+
state: 'Current' | 'Stale' | 'Empty' | 'Unknown';
|
|
30
|
+
facts: number;
|
|
31
|
+
stale: number;
|
|
32
|
+
analyzedAt: string | null;
|
|
33
|
+
};
|
|
34
|
+
onboarding: {
|
|
35
|
+
instructionsInstalled: boolean;
|
|
36
|
+
knowledgeWritten: boolean;
|
|
37
|
+
ready: boolean;
|
|
38
|
+
};
|
|
39
|
+
integrations: IntegrationStatus[];
|
|
40
|
+
activeTasks: TaskBrief[];
|
|
41
|
+
verification: {
|
|
42
|
+
id: string;
|
|
43
|
+
available: boolean;
|
|
44
|
+
command: string | null;
|
|
45
|
+
required: string;
|
|
46
|
+
}[];
|
|
47
|
+
lastSession: {
|
|
48
|
+
id: string;
|
|
49
|
+
goal: string;
|
|
50
|
+
next: string;
|
|
51
|
+
} | null;
|
|
52
|
+
corruptTasks: number;
|
|
53
|
+
}
|
|
54
|
+
export declare function instructionsInstalled(ws: Ws): Promise<boolean>;
|
|
55
|
+
export declare function knowledgeWritten(ws: Ws): Promise<boolean>;
|
|
56
|
+
/** Cheap overview: reads small workspace files and Git status only. Never hashes or scans source files. */
|
|
57
|
+
export declare function getStatus(o: ServiceOptions): Promise<StatusResult>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { SCHEMA_VERSION, validateAnalysisState, validateCommands } from '../config/schemas.js';
|
|
4
|
+
import { findWorkspaceRoot, loadProject, wsPaths } from '../core/workspace.js';
|
|
5
|
+
import { readVersioned } from '../core/versioned.js';
|
|
6
|
+
import { getGitState } from '../git/git.js';
|
|
7
|
+
import { detectIntegrations } from '../integrations/index.js';
|
|
8
|
+
import { loadFacts } from '../knowledge/facts.js';
|
|
9
|
+
import { listSessions } from '../sessions/session.js';
|
|
10
|
+
import { listTasks, nextRequiredStep } from '../tasks/task.js';
|
|
11
|
+
import { pathExists } from '../utils/fs.js';
|
|
12
|
+
import { SECTION_END } from '../agents/claude/content.js';
|
|
13
|
+
import { SKILL_PATH } from '../agents/claude/content.js';
|
|
14
|
+
import { integrationCtx } from './common.js';
|
|
15
|
+
const brief = (t) => ({ id: t.id, title: t.title, type: t.type, status: t.status, updatedAt: t.updatedAt, next: nextRequiredStep(t) });
|
|
16
|
+
export async function instructionsInstalled(ws) {
|
|
17
|
+
for (const rel of ['CLAUDE.md', '.claude/CLAUDE.md']) {
|
|
18
|
+
try {
|
|
19
|
+
const text = await fs.readFile(path.join(ws.root, ...rel.split('/')), 'utf8');
|
|
20
|
+
if (text.includes('<!-- devworkspace:start') && text.includes(SECTION_END) && (await pathExists(path.join(ws.root, ...SKILL_PATH.split('/')))))
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
/* try next */
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
export async function knowledgeWritten(ws) {
|
|
30
|
+
try {
|
|
31
|
+
return (await fs.stat(path.join(ws.contextDir, 'architecture.md'))).size >= 200;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Cheap overview: reads small workspace files and Git status only. Never hashes or scans source files. */
|
|
38
|
+
export async function getStatus(o) {
|
|
39
|
+
const root = await findWorkspaceRoot(o.cwd);
|
|
40
|
+
const git = await getGitState(root ?? o.cwd);
|
|
41
|
+
const gitBrief = {
|
|
42
|
+
available: git.available,
|
|
43
|
+
isRepo: git.isRepo,
|
|
44
|
+
branch: git.branch,
|
|
45
|
+
workingTree: git.isRepo ? (git.dirty ? 'Modified' : 'Clean') : 'Unknown',
|
|
46
|
+
changes: git.entries.length,
|
|
47
|
+
};
|
|
48
|
+
const base = {
|
|
49
|
+
initialized: false,
|
|
50
|
+
root,
|
|
51
|
+
project: null,
|
|
52
|
+
workspace: 'Not initialized',
|
|
53
|
+
git: gitBrief,
|
|
54
|
+
knowledge: { state: 'Unknown', facts: 0, stale: 0, analyzedAt: null },
|
|
55
|
+
onboarding: { instructionsInstalled: false, knowledgeWritten: false, ready: false },
|
|
56
|
+
integrations: [],
|
|
57
|
+
activeTasks: [],
|
|
58
|
+
verification: [],
|
|
59
|
+
lastSession: null,
|
|
60
|
+
corruptTasks: 0,
|
|
61
|
+
};
|
|
62
|
+
if (!root)
|
|
63
|
+
return base;
|
|
64
|
+
const ws = wsPaths(root);
|
|
65
|
+
base.initialized = true;
|
|
66
|
+
try {
|
|
67
|
+
const project = await loadProject(ws);
|
|
68
|
+
base.project = { name: project.name, id: project.projectId };
|
|
69
|
+
base.workspace = 'Healthy';
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
return { ...base, workspace: 'Unreadable', workspaceProblem: e.message };
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const facts = await loadFacts(ws);
|
|
76
|
+
const stale = facts.facts.filter((f) => f.status === 'STALE').length;
|
|
77
|
+
let analyzedAt = null;
|
|
78
|
+
if (await pathExists(ws.analysisState)) {
|
|
79
|
+
analyzedAt = (await readVersioned(ws.analysisState, 'analysis', SCHEMA_VERSION, validateAnalysisState)).data.analyzedAt;
|
|
80
|
+
}
|
|
81
|
+
base.knowledge = { facts: facts.facts.length, stale, analyzedAt, state: facts.facts.length === 0 ? 'Empty' : stale > 0 ? 'Stale' : 'Current' };
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
base.workspace = 'Needs attention';
|
|
85
|
+
base.workspaceProblem = e.message;
|
|
86
|
+
}
|
|
87
|
+
if (await pathExists(ws.commands)) {
|
|
88
|
+
try {
|
|
89
|
+
const commands = (await readVersioned(ws.commands, 'commands', SCHEMA_VERSION, validateCommands)).data;
|
|
90
|
+
base.verification = commands.checks.map((c) => ({ id: c.id, available: c.available, command: c.command, required: c.required }));
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
base.workspace = 'Needs attention';
|
|
94
|
+
base.workspaceProblem ??= e.message;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const { tasks, corrupt } = await listTasks(ws);
|
|
98
|
+
base.activeTasks = tasks.map(brief);
|
|
99
|
+
base.corruptTasks = corrupt.length;
|
|
100
|
+
if (corrupt.length > 0)
|
|
101
|
+
base.workspace = 'Needs attention';
|
|
102
|
+
const sessions = await listSessions(ws, 1);
|
|
103
|
+
if (sessions[0])
|
|
104
|
+
base.lastSession = { id: sessions[0].id, goal: sessions[0].goal, next: sessions[0].next };
|
|
105
|
+
base.integrations = await detectIntegrations(integrationCtx(o, root));
|
|
106
|
+
base.onboarding.instructionsInstalled = await instructionsInstalled(ws);
|
|
107
|
+
base.onboarding.knowledgeWritten = await knowledgeWritten(ws);
|
|
108
|
+
base.onboarding.ready = base.onboarding.instructionsInstalled && base.onboarding.knowledgeWritten && base.knowledge.state !== 'Empty';
|
|
109
|
+
return base;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/services/status.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { SCHEMA_VERSION, validateAnalysisState, validateCommands, type CommandEntry } from '../config/schemas.js';\nimport { findWorkspaceRoot, loadProject, wsPaths, type Ws } from '../core/workspace.js';\nimport { readVersioned } from '../core/versioned.js';\nimport { getGitState } from '../git/git.js';\nimport { detectIntegrations, type IntegrationStatus } from '../integrations/index.js';\nimport { loadFacts } from '../knowledge/facts.js';\nimport { listSessions } from '../sessions/session.js';\nimport { listTasks, nextRequiredStep, type Task } from '../tasks/task.js';\nimport { pathExists } from '../utils/fs.js';\nimport { SECTION_END } from '../agents/claude/content.js';\nimport { SKILL_PATH } from '../agents/claude/content.js';\nimport { integrationCtx, type ServiceOptions } from './common.js';\n\nexport interface TaskBrief {\n id: string;\n title: string;\n type: string;\n status: string;\n updatedAt: string;\n next: string;\n}\n\nexport interface StatusResult {\n initialized: boolean;\n root: string | null;\n project: { name: string; id: string } | null;\n workspace: 'Healthy' | 'Needs attention' | 'Not initialized' | 'Unreadable';\n workspaceProblem?: string;\n git: { available: boolean; isRepo: boolean; branch: string | null; workingTree: 'Clean' | 'Modified' | 'Unknown'; changes: number };\n knowledge: { state: 'Current' | 'Stale' | 'Empty' | 'Unknown'; facts: number; stale: number; analyzedAt: string | null };\n onboarding: { instructionsInstalled: boolean; knowledgeWritten: boolean; ready: boolean };\n integrations: IntegrationStatus[];\n activeTasks: TaskBrief[];\n verification: { id: string; available: boolean; command: string | null; required: string }[];\n lastSession: { id: string; goal: string; next: string } | null;\n corruptTasks: number;\n}\n\nconst brief = (t: Task): TaskBrief => ({ id: t.id, title: t.title, type: t.type, status: t.status, updatedAt: t.updatedAt, next: nextRequiredStep(t) });\n\nexport async function instructionsInstalled(ws: Ws): Promise<boolean> {\n for (const rel of ['CLAUDE.md', '.claude/CLAUDE.md']) {\n try {\n const text = await fs.readFile(path.join(ws.root, ...rel.split('/')), 'utf8');\n if (text.includes('<!-- devworkspace:start') && text.includes(SECTION_END) && (await pathExists(path.join(ws.root, ...SKILL_PATH.split('/'))))) return true;\n } catch {\n /* try next */\n }\n }\n return false;\n}\n\nexport async function knowledgeWritten(ws: Ws): Promise<boolean> {\n try {\n return (await fs.stat(path.join(ws.contextDir, 'architecture.md'))).size >= 200;\n } catch {\n return false;\n }\n}\n\n/** Cheap overview: reads small workspace files and Git status only. Never hashes or scans source files. */\nexport async function getStatus(o: ServiceOptions): Promise<StatusResult> {\n const root = await findWorkspaceRoot(o.cwd);\n const git = await getGitState(root ?? o.cwd);\n const gitBrief: StatusResult['git'] = {\n available: git.available,\n isRepo: git.isRepo,\n branch: git.branch,\n workingTree: git.isRepo ? (git.dirty ? 'Modified' : 'Clean') : 'Unknown',\n changes: git.entries.length,\n };\n const base: StatusResult = {\n initialized: false,\n root,\n project: null,\n workspace: 'Not initialized',\n git: gitBrief,\n knowledge: { state: 'Unknown', facts: 0, stale: 0, analyzedAt: null },\n onboarding: { instructionsInstalled: false, knowledgeWritten: false, ready: false },\n integrations: [],\n activeTasks: [],\n verification: [],\n lastSession: null,\n corruptTasks: 0,\n };\n if (!root) return base;\n\n const ws = wsPaths(root);\n base.initialized = true;\n try {\n const project = await loadProject(ws);\n base.project = { name: project.name, id: project.projectId };\n base.workspace = 'Healthy';\n } catch (e) {\n return { ...base, workspace: 'Unreadable', workspaceProblem: (e as Error).message };\n }\n\n try {\n const facts = await loadFacts(ws);\n const stale = facts.facts.filter((f) => f.status === 'STALE').length;\n let analyzedAt: string | null = null;\n if (await pathExists(ws.analysisState)) {\n analyzedAt = (await readVersioned(ws.analysisState, 'analysis', SCHEMA_VERSION, validateAnalysisState)).data.analyzedAt;\n }\n base.knowledge = { facts: facts.facts.length, stale, analyzedAt, state: facts.facts.length === 0 ? 'Empty' : stale > 0 ? 'Stale' : 'Current' };\n } catch (e) {\n base.workspace = 'Needs attention';\n base.workspaceProblem = (e as Error).message;\n }\n\n if (await pathExists(ws.commands)) {\n try {\n const commands = (await readVersioned(ws.commands, 'commands', SCHEMA_VERSION, validateCommands)).data;\n base.verification = commands.checks.map((c: CommandEntry) => ({ id: c.id, available: c.available, command: c.command, required: c.required }));\n } catch (e) {\n base.workspace = 'Needs attention';\n base.workspaceProblem ??= (e as Error).message;\n }\n }\n\n const { tasks, corrupt } = await listTasks(ws);\n base.activeTasks = tasks.map(brief);\n base.corruptTasks = corrupt.length;\n if (corrupt.length > 0) base.workspace = 'Needs attention';\n const sessions = await listSessions(ws, 1);\n if (sessions[0]) base.lastSession = { id: sessions[0].id, goal: sessions[0].goal, next: sessions[0].next };\n\n base.integrations = await detectIntegrations(integrationCtx(o, root));\n base.onboarding.instructionsInstalled = await instructionsInstalled(ws);\n base.onboarding.knowledgeWritten = await knowledgeWritten(ws);\n base.onboarding.ready = base.onboarding.instructionsInstalled && base.onboarding.knowledgeWritten && base.knowledge.state !== 'Empty';\n return base;\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type Classification, type WorkflowSpec } from '../tasks/routing.js';
|
|
2
|
+
import { listTasks, type GateResult, type Task } from '../tasks/task.js';
|
|
3
|
+
import { type ServiceOptions } from './common.js';
|
|
4
|
+
export interface CreateTaskResult {
|
|
5
|
+
task: Task;
|
|
6
|
+
classification: Classification;
|
|
7
|
+
workflow: WorkflowSpec;
|
|
8
|
+
}
|
|
9
|
+
export declare function createTask(o: ServiceOptions, input: {
|
|
10
|
+
text: string;
|
|
11
|
+
type?: string;
|
|
12
|
+
title?: string;
|
|
13
|
+
}): Promise<CreateTaskResult>;
|
|
14
|
+
export declare function showTask(o: ServiceOptions, id: string): Promise<{
|
|
15
|
+
task: Task;
|
|
16
|
+
gate: GateResult;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function listTasksService(o: ServiceOptions, includeCompleted?: boolean): ReturnType<typeof listTasks>;
|
|
19
|
+
export interface TaskPatch {
|
|
20
|
+
title?: string;
|
|
21
|
+
summary?: string;
|
|
22
|
+
criteria?: string[];
|
|
23
|
+
plan?: string[];
|
|
24
|
+
areas?: string[];
|
|
25
|
+
decisions?: string[];
|
|
26
|
+
blockers?: string[];
|
|
27
|
+
clearBlockers?: boolean;
|
|
28
|
+
require?: string[];
|
|
29
|
+
}
|
|
30
|
+
export declare function updateTask(o: ServiceOptions, id: string, patch: TaskPatch): Promise<Task>;
|
|
31
|
+
export declare function setTaskStatus(o: ServiceOptions, id: string, status: string, note?: string): Promise<{
|
|
32
|
+
task: Task;
|
|
33
|
+
gate: GateResult | null;
|
|
34
|
+
}>;
|
|
35
|
+
export declare function recordVerification(o: ServiceOptions, id: string, check: string, result: string, extra?: {
|
|
36
|
+
reason?: string;
|
|
37
|
+
detail?: string;
|
|
38
|
+
}): Promise<Task>;
|
|
39
|
+
export declare function recordTests(o: ServiceOptions, id: string, phase: 'baseline' | 'after', passed: number, failed: number): Promise<Task>;
|
|
40
|
+
export declare function attestTask(o: ServiceOptions, id: string, flags: {
|
|
41
|
+
userWorkIntact?: boolean;
|
|
42
|
+
knowledgeReviewed?: boolean;
|
|
43
|
+
finalStateInspected?: boolean;
|
|
44
|
+
}): Promise<Task>;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { SCHEMA_VERSION, validateCommands } from '../config/schemas.js';
|
|
2
|
+
import { DevWorkspaceError } from '../errors/index.js';
|
|
3
|
+
import { loadProject, requireWorkspace } from '../core/workspace.js';
|
|
4
|
+
import { readVersioned } from '../core/versioned.js';
|
|
5
|
+
import { classifyRequest, WORKFLOWS } from '../tasks/routing.js';
|
|
6
|
+
import { assertNoSecrets, createTaskObject, evaluateGate, listTasks, loadTask, requiredChecksFor, saveTask, transitionTask, TASK_STATUSES, TASK_TYPES, VERIFY_STATUSES, } from '../tasks/task.js';
|
|
7
|
+
import { pathExists } from '../utils/fs.js';
|
|
8
|
+
import { isoNow, mutate } from './common.js';
|
|
9
|
+
async function loadContract(ws) {
|
|
10
|
+
if (!(await pathExists(ws.commands)))
|
|
11
|
+
return [];
|
|
12
|
+
return (await readVersioned(ws.commands, 'commands', SCHEMA_VERSION, validateCommands)).data.checks;
|
|
13
|
+
}
|
|
14
|
+
export async function createTask(o, input) {
|
|
15
|
+
return mutate(o, async (ws) => {
|
|
16
|
+
const project = await loadProject(ws);
|
|
17
|
+
const classification = classifyRequest(input.text);
|
|
18
|
+
let type = classification.type;
|
|
19
|
+
if (input.type) {
|
|
20
|
+
const up = input.type.toUpperCase();
|
|
21
|
+
if (!TASK_TYPES.includes(up)) {
|
|
22
|
+
throw new DevWorkspaceError('USAGE', `Unknown task type "${input.type}".`, { hint: `Use one of: ${TASK_TYPES.join(', ')}.` });
|
|
23
|
+
}
|
|
24
|
+
type = up;
|
|
25
|
+
}
|
|
26
|
+
const contract = await loadContract(ws);
|
|
27
|
+
let verification = requiredChecksFor(type, contract, project.settings.review);
|
|
28
|
+
// Explicit user constraints stay visible: the check is kept but recorded as skipped at the user's request.
|
|
29
|
+
const skipChecks = new Set();
|
|
30
|
+
if (classification.constraints.includes('no-e2e'))
|
|
31
|
+
skipChecks.add('e2e');
|
|
32
|
+
if (classification.constraints.includes('no-tests'))
|
|
33
|
+
skipChecks.add('unit').add('integration').add('e2e');
|
|
34
|
+
verification = verification.map((v) => (skipChecks.has(v.check) ? { ...v, status: 'SKIPPED', reason: 'user-override' } : v));
|
|
35
|
+
const title = (input.title ?? input.text).replace(/\s+/g, ' ').trim().slice(0, 100);
|
|
36
|
+
const task = createTaskObject({ title, type, description: input.text, verification, constraints: classification.constraints }, isoNow(o));
|
|
37
|
+
await saveTask(ws, task);
|
|
38
|
+
return { task, classification: { ...classification, type }, workflow: WORKFLOWS[type] };
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export async function showTask(o, id) {
|
|
42
|
+
const ws = await requireWorkspace(o.cwd);
|
|
43
|
+
const task = await loadTask(ws, id);
|
|
44
|
+
return { task, gate: evaluateGate(task) };
|
|
45
|
+
}
|
|
46
|
+
export async function listTasksService(o, includeCompleted = false) {
|
|
47
|
+
const ws = await requireWorkspace(o.cwd);
|
|
48
|
+
return listTasks(ws, { includeCompleted });
|
|
49
|
+
}
|
|
50
|
+
export async function updateTask(o, id, patch) {
|
|
51
|
+
return mutate(o, async (ws) => {
|
|
52
|
+
const task = await loadTask(ws, id);
|
|
53
|
+
if (patch.title)
|
|
54
|
+
task.title = patch.title.trim().slice(0, 200);
|
|
55
|
+
if (patch.summary !== undefined)
|
|
56
|
+
task.completionSummary = patch.summary.trim().slice(0, 4000);
|
|
57
|
+
task.acceptanceCriteria.push(...(patch.criteria ?? []));
|
|
58
|
+
task.plan.push(...(patch.plan ?? []));
|
|
59
|
+
task.affectedAreas.push(...(patch.areas ?? []).filter((a) => !task.affectedAreas.includes(a)));
|
|
60
|
+
task.decisions.push(...(patch.decisions ?? []));
|
|
61
|
+
if (patch.clearBlockers)
|
|
62
|
+
task.blockers = [];
|
|
63
|
+
task.blockers.push(...(patch.blockers ?? []));
|
|
64
|
+
for (const check of patch.require ?? []) {
|
|
65
|
+
if (!task.verification.some((v) => v.check === check))
|
|
66
|
+
task.verification.push({ check, required: true, status: 'PENDING' });
|
|
67
|
+
}
|
|
68
|
+
task.updatedAt = isoNow(o);
|
|
69
|
+
assertNoSecrets(task);
|
|
70
|
+
await saveTask(ws, task);
|
|
71
|
+
return task;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export async function setTaskStatus(o, id, status, note) {
|
|
75
|
+
const up = status.toUpperCase();
|
|
76
|
+
if (!TASK_STATUSES.includes(up)) {
|
|
77
|
+
throw new DevWorkspaceError('USAGE', `Unknown task status "${status}".`, { hint: `Use one of: ${TASK_STATUSES.join(', ')}.` });
|
|
78
|
+
}
|
|
79
|
+
return mutate(o, async (ws) => {
|
|
80
|
+
const task = await loadTask(ws, id);
|
|
81
|
+
const gate = transitionTask(task, up, isoNow(o), note);
|
|
82
|
+
if (up === 'BLOCKED' && note)
|
|
83
|
+
task.blockers.push(note.slice(0, 300));
|
|
84
|
+
await saveTask(ws, task);
|
|
85
|
+
return { task, gate };
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export async function recordVerification(o, id, check, result, extra = {}) {
|
|
89
|
+
const status = result.toUpperCase();
|
|
90
|
+
const normalized = status === 'SKIP' ? 'SKIPPED' : status;
|
|
91
|
+
if (!VERIFY_STATUSES.includes(normalized)) {
|
|
92
|
+
throw new DevWorkspaceError('USAGE', `Unknown result "${result}".`, { hint: 'Use pass, fail, skip or pending.' });
|
|
93
|
+
}
|
|
94
|
+
return mutate(o, async (ws) => {
|
|
95
|
+
const task = await loadTask(ws, id);
|
|
96
|
+
let item = task.verification.find((v) => v.check === check);
|
|
97
|
+
if (!item) {
|
|
98
|
+
item = { check, required: false, status: 'PENDING' };
|
|
99
|
+
task.verification.push(item);
|
|
100
|
+
}
|
|
101
|
+
item.status = normalized;
|
|
102
|
+
item.updatedAt = isoNow(o);
|
|
103
|
+
if (extra.reason)
|
|
104
|
+
item.reason = extra.reason;
|
|
105
|
+
else
|
|
106
|
+
delete item.reason;
|
|
107
|
+
if (extra.detail)
|
|
108
|
+
item.detail = extra.detail.slice(0, 500);
|
|
109
|
+
task.updatedAt = item.updatedAt;
|
|
110
|
+
assertNoSecrets(task);
|
|
111
|
+
await saveTask(ws, task);
|
|
112
|
+
return task;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
export async function recordTests(o, id, phase, passed, failed) {
|
|
116
|
+
if (!Number.isInteger(passed) || !Number.isInteger(failed) || passed < 0 || failed < 0) {
|
|
117
|
+
throw new DevWorkspaceError('USAGE', '--passed and --failed must be non-negative integers.');
|
|
118
|
+
}
|
|
119
|
+
return mutate(o, async (ws) => {
|
|
120
|
+
const task = await loadTask(ws, id);
|
|
121
|
+
task.tests[phase] = { passed, failed };
|
|
122
|
+
task.updatedAt = isoNow(o);
|
|
123
|
+
await saveTask(ws, task);
|
|
124
|
+
return task;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
export async function attestTask(o, id, flags) {
|
|
128
|
+
return mutate(o, async (ws) => {
|
|
129
|
+
const task = await loadTask(ws, id);
|
|
130
|
+
task.attestations = { ...task.attestations, ...Object.fromEntries(Object.entries(flags).filter(([, v]) => v !== undefined)) };
|
|
131
|
+
task.updatedAt = isoNow(o);
|
|
132
|
+
await saveTask(ws, task);
|
|
133
|
+
return task;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=tasks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks.js","sourceRoot":"","sources":["../../src/services/tasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAqB,MAAM,sBAAsB,CAAC;AAC3F,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAW,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,SAAS,EAA0C,MAAM,qBAAqB,CAAC;AACzG,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,iBAAiB,EACjB,QAAQ,EACR,cAAc,EAMd,aAAa,EACb,UAAU,EACV,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,EAAuB,MAAM,aAAa,CAAC;AAElE,KAAK,UAAU,YAAY,CAAC,EAAM;IAChC,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAChD,OAAO,CAAC,MAAM,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;AACtG,CAAC;AAQD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,CAAiB,EAAE,KAAsD;IACxG,OAAO,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QAC5B,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,EAAE,CAAC,CAAC;QACtC,MAAM,cAAc,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,IAAI,GAAa,cAAc,CAAC,IAAI,CAAC;QACzC,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YACf,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACpC,IAAI,CAAE,UAAgC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;gBACpD,MAAM,IAAI,iBAAiB,CAAC,OAAO,EAAE,sBAAsB,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,EAAE,eAAe,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YAChI,CAAC;YACD,IAAI,GAAG,EAAc,CAAC;QACxB,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,EAAE,CAAC,CAAC;QACxC,IAAI,YAAY,GAAG,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC9E,2GAA2G;QAC3G,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;QACrC,IAAI,cAAc,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzE,IAAI,cAAc,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC;YAAE,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC1G,YAAY,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,SAAkB,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtI,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACpF,MAAM,IAAI,GAAG,gBAAgB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1I,MAAM,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,GAAG,cAAc,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;IAC1F,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,CAAiB,EAAE,EAAU;IAC1D,MAAM,EAAE,GAAG,MAAM,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACpC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;AAC5C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,CAAiB,EAAE,gBAAgB,GAAG,KAAK;IAChF,MAAM,EAAE,GAAG,MAAM,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzC,OAAO,SAAS,CAAC,EAAE,EAAE,EAAE,gBAAgB,EAAE,CAAC,CAAC;AAC7C,CAAC;AAcD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,CAAiB,EAAE,EAAU,EAAE,KAAgB;IAC9E,OAAO,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QAC5B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACpC,IAAI,KAAK,CAAC,KAAK;YAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC/D,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAC9F,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;QACtC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/F,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC;QAChD,IAAI,KAAK,CAAC,aAAa;YAAE,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;QAC5C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC;QAC9C,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;YACxC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC;gBAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QAC9H,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3B,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,CAAiB,EAAE,EAAU,EAAE,MAAc,EAAE,IAAa;IAC9F,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IAChC,IAAI,CAAE,aAAmC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,eAAe,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACjI,CAAC;IACD,OAAO,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QAC5B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,EAAgB,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACrE,IAAI,EAAE,KAAK,SAAS,IAAI,IAAI;YAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACrE,MAAM,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACzB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,CAAiB,EACjB,EAAU,EACV,KAAa,EACb,MAAc,EACd,QAA8C,EAAE;IAEhD,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IACpC,MAAM,UAAU,GAAG,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;IAC1D,IAAI,CAAE,eAAqC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,iBAAiB,CAAC,OAAO,EAAE,mBAAmB,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,kCAAkC,EAAE,CAAC,CAAC;IACpH,CAAC;IACD,OAAO,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QAC5B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACpC,IAAI,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;QAC5D,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;YACrD,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,UAA0B,CAAC;QACzC,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3B,IAAI,KAAK,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;;YACxC,OAAO,IAAI,CAAC,MAAM,CAAC;QACxB,IAAI,KAAK,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC3D,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,CAAiB,EAAE,EAAU,EAAE,KAA2B,EAAE,MAAc,EAAE,MAAc;IAC1H,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QACvF,MAAM,IAAI,iBAAiB,CAAC,OAAO,EAAE,sDAAsD,CAAC,CAAC;IAC/F,CAAC;IACD,OAAO,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QAC5B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QACvC,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,CAAiB,EACjB,EAAU,EACV,KAA+F;IAE/F,OAAO,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QAC5B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,EAAE,CAAC;QAC9H,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { SCHEMA_VERSION, validateCommands, type CommandEntry } from '../config/schemas.js';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { loadProject, requireWorkspace, type Ws } from '../core/workspace.js';\nimport { readVersioned } from '../core/versioned.js';\nimport { classifyRequest, WORKFLOWS, type Classification, type WorkflowSpec } from '../tasks/routing.js';\nimport {\n assertNoSecrets,\n createTaskObject,\n evaluateGate,\n listTasks,\n loadTask,\n requiredChecksFor,\n saveTask,\n transitionTask,\n type GateResult,\n type Task,\n type TaskStatus,\n type TaskType,\n type VerifyStatus,\n TASK_STATUSES,\n TASK_TYPES,\n VERIFY_STATUSES,\n} from '../tasks/task.js';\nimport { pathExists } from '../utils/fs.js';\nimport { isoNow, mutate, type ServiceOptions } from './common.js';\n\nasync function loadContract(ws: Ws): Promise<CommandEntry[]> {\n if (!(await pathExists(ws.commands))) return [];\n return (await readVersioned(ws.commands, 'commands', SCHEMA_VERSION, validateCommands)).data.checks;\n}\n\nexport interface CreateTaskResult {\n task: Task;\n classification: Classification;\n workflow: WorkflowSpec;\n}\n\nexport async function createTask(o: ServiceOptions, input: { text: string; type?: string; title?: string }): Promise<CreateTaskResult> {\n return mutate(o, async (ws) => {\n const project = await loadProject(ws);\n const classification = classifyRequest(input.text);\n let type: TaskType = classification.type;\n if (input.type) {\n const up = input.type.toUpperCase();\n if (!(TASK_TYPES as readonly string[]).includes(up)) {\n throw new DevWorkspaceError('USAGE', `Unknown task type \"${input.type}\".`, { hint: `Use one of: ${TASK_TYPES.join(', ')}.` });\n }\n type = up as TaskType;\n }\n const contract = await loadContract(ws);\n let verification = requiredChecksFor(type, contract, project.settings.review);\n // Explicit user constraints stay visible: the check is kept but recorded as skipped at the user's request.\n const skipChecks = new Set<string>();\n if (classification.constraints.includes('no-e2e')) skipChecks.add('e2e');\n if (classification.constraints.includes('no-tests')) skipChecks.add('unit').add('integration').add('e2e');\n verification = verification.map((v) => (skipChecks.has(v.check) ? { ...v, status: 'SKIPPED' as const, reason: 'user-override' } : v));\n const title = (input.title ?? input.text).replace(/\\s+/g, ' ').trim().slice(0, 100);\n const task = createTaskObject({ title, type, description: input.text, verification, constraints: classification.constraints }, isoNow(o));\n await saveTask(ws, task);\n return { task, classification: { ...classification, type }, workflow: WORKFLOWS[type] };\n });\n}\n\nexport async function showTask(o: ServiceOptions, id: string): Promise<{ task: Task; gate: GateResult }> {\n const ws = await requireWorkspace(o.cwd);\n const task = await loadTask(ws, id);\n return { task, gate: evaluateGate(task) };\n}\n\nexport async function listTasksService(o: ServiceOptions, includeCompleted = false): ReturnType<typeof listTasks> {\n const ws = await requireWorkspace(o.cwd);\n return listTasks(ws, { includeCompleted });\n}\n\nexport interface TaskPatch {\n title?: string;\n summary?: string;\n criteria?: string[];\n plan?: string[];\n areas?: string[];\n decisions?: string[];\n blockers?: string[];\n clearBlockers?: boolean;\n require?: string[];\n}\n\nexport async function updateTask(o: ServiceOptions, id: string, patch: TaskPatch): Promise<Task> {\n return mutate(o, async (ws) => {\n const task = await loadTask(ws, id);\n if (patch.title) task.title = patch.title.trim().slice(0, 200);\n if (patch.summary !== undefined) task.completionSummary = patch.summary.trim().slice(0, 4000);\n task.acceptanceCriteria.push(...(patch.criteria ?? []));\n task.plan.push(...(patch.plan ?? []));\n task.affectedAreas.push(...(patch.areas ?? []).filter((a) => !task.affectedAreas.includes(a)));\n task.decisions.push(...(patch.decisions ?? []));\n if (patch.clearBlockers) task.blockers = [];\n task.blockers.push(...(patch.blockers ?? []));\n for (const check of patch.require ?? []) {\n if (!task.verification.some((v) => v.check === check)) task.verification.push({ check, required: true, status: 'PENDING' });\n }\n task.updatedAt = isoNow(o);\n assertNoSecrets(task);\n await saveTask(ws, task);\n return task;\n });\n}\n\nexport async function setTaskStatus(o: ServiceOptions, id: string, status: string, note?: string): Promise<{ task: Task; gate: GateResult | null }> {\n const up = status.toUpperCase();\n if (!(TASK_STATUSES as readonly string[]).includes(up)) {\n throw new DevWorkspaceError('USAGE', `Unknown task status \"${status}\".`, { hint: `Use one of: ${TASK_STATUSES.join(', ')}.` });\n }\n return mutate(o, async (ws) => {\n const task = await loadTask(ws, id);\n const gate = transitionTask(task, up as TaskStatus, isoNow(o), note);\n if (up === 'BLOCKED' && note) task.blockers.push(note.slice(0, 300));\n await saveTask(ws, task);\n return { task, gate };\n });\n}\n\nexport async function recordVerification(\n o: ServiceOptions,\n id: string,\n check: string,\n result: string,\n extra: { reason?: string; detail?: string } = {},\n): Promise<Task> {\n const status = result.toUpperCase();\n const normalized = status === 'SKIP' ? 'SKIPPED' : status;\n if (!(VERIFY_STATUSES as readonly string[]).includes(normalized)) {\n throw new DevWorkspaceError('USAGE', `Unknown result \"${result}\".`, { hint: 'Use pass, fail, skip or pending.' });\n }\n return mutate(o, async (ws) => {\n const task = await loadTask(ws, id);\n let item = task.verification.find((v) => v.check === check);\n if (!item) {\n item = { check, required: false, status: 'PENDING' };\n task.verification.push(item);\n }\n item.status = normalized as VerifyStatus;\n item.updatedAt = isoNow(o);\n if (extra.reason) item.reason = extra.reason;\n else delete item.reason;\n if (extra.detail) item.detail = extra.detail.slice(0, 500);\n task.updatedAt = item.updatedAt;\n assertNoSecrets(task);\n await saveTask(ws, task);\n return task;\n });\n}\n\nexport async function recordTests(o: ServiceOptions, id: string, phase: 'baseline' | 'after', passed: number, failed: number): Promise<Task> {\n if (!Number.isInteger(passed) || !Number.isInteger(failed) || passed < 0 || failed < 0) {\n throw new DevWorkspaceError('USAGE', '--passed and --failed must be non-negative integers.');\n }\n return mutate(o, async (ws) => {\n const task = await loadTask(ws, id);\n task.tests[phase] = { passed, failed };\n task.updatedAt = isoNow(o);\n await saveTask(ws, task);\n return task;\n });\n}\n\nexport async function attestTask(\n o: ServiceOptions,\n id: string,\n flags: { userWorkIntact?: boolean; knowledgeReviewed?: boolean; finalStateInspected?: boolean },\n): Promise<Task> {\n return mutate(o, async (ws) => {\n const task = await loadTask(ws, id);\n task.attestations = { ...task.attestations, ...Object.fromEntries(Object.entries(flags).filter(([, v]) => v !== undefined)) };\n task.updatedAt = isoNow(o);\n await saveTask(ws, task);\n return task;\n });\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Ws } from '../core/workspace.js';
|
|
2
|
+
export interface Session {
|
|
3
|
+
schemaVersion: number;
|
|
4
|
+
id: string;
|
|
5
|
+
createdAt: string;
|
|
6
|
+
goal: string;
|
|
7
|
+
completed: string[];
|
|
8
|
+
areas: string[];
|
|
9
|
+
testsRun: string[];
|
|
10
|
+
decisions: string[];
|
|
11
|
+
remaining: string[];
|
|
12
|
+
next: string;
|
|
13
|
+
taskId?: string;
|
|
14
|
+
}
|
|
15
|
+
export type SessionInput = Omit<Session, 'schemaVersion' | 'id' | 'createdAt'>;
|
|
16
|
+
export declare function validateSession(v: unknown, file?: string): Session;
|
|
17
|
+
/** Sessions are short summaries, never transcripts. */
|
|
18
|
+
export declare function saveSession(ws: Ws, input: SessionInput, now: string): Promise<Session>;
|
|
19
|
+
export declare function listSessions(ws: Ws, limit?: number): Promise<Session[]>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { Check } from '../config/validate.js';
|
|
4
|
+
import { DevWorkspaceError } from '../errors/index.js';
|
|
5
|
+
import { SCHEMA_VERSION } from '../config/schemas.js';
|
|
6
|
+
import { atomicWriteJson, pathExists, randomId } from '../utils/fs.js';
|
|
7
|
+
import { readVersioned } from '../core/versioned.js';
|
|
8
|
+
import { containsSecret } from '../security/redact.js';
|
|
9
|
+
export function validateSession(v, file = 'session') {
|
|
10
|
+
const c = new Check(file);
|
|
11
|
+
const o = c.obj(v);
|
|
12
|
+
c.at_('schemaVersion').int(o.schemaVersion, 1);
|
|
13
|
+
c.at_('id').str(o.id, { max: 64 });
|
|
14
|
+
c.at_('createdAt').iso(o.createdAt);
|
|
15
|
+
c.at_('goal').str(o.goal, { max: 1000 });
|
|
16
|
+
for (const k of ['completed', 'areas', 'testsRun', 'decisions', 'remaining'])
|
|
17
|
+
c.at_(k).strArr(o[k], 200);
|
|
18
|
+
c.at_('next').str(o.next, { max: 1000 });
|
|
19
|
+
return o;
|
|
20
|
+
}
|
|
21
|
+
/** Sessions are short summaries, never transcripts. */
|
|
22
|
+
export async function saveSession(ws, input, now) {
|
|
23
|
+
if (!input.goal.trim())
|
|
24
|
+
throw new DevWorkspaceError('USAGE', 'A session summary needs a goal.');
|
|
25
|
+
const session = {
|
|
26
|
+
schemaVersion: SCHEMA_VERSION,
|
|
27
|
+
id: `${now.slice(0, 10).replace(/-/g, '')}-${randomId(2)}`,
|
|
28
|
+
createdAt: now,
|
|
29
|
+
...input,
|
|
30
|
+
};
|
|
31
|
+
validateSession(session);
|
|
32
|
+
if (containsSecret(JSON.stringify(session))) {
|
|
33
|
+
throw new DevWorkspaceError('SECURITY', 'Session summary looks like it contains a secret; refusing to store it.');
|
|
34
|
+
}
|
|
35
|
+
await atomicWriteJson(path.join(ws.sessionsDir, `${session.id}.json`), session);
|
|
36
|
+
return session;
|
|
37
|
+
}
|
|
38
|
+
export async function listSessions(ws, limit = 20) {
|
|
39
|
+
if (!(await pathExists(ws.sessionsDir)))
|
|
40
|
+
return [];
|
|
41
|
+
const names = (await fs.readdir(ws.sessionsDir)).filter((n) => n.endsWith('.json')).sort().reverse().slice(0, limit);
|
|
42
|
+
const out = [];
|
|
43
|
+
for (const n of names) {
|
|
44
|
+
try {
|
|
45
|
+
out.push((await readVersioned(path.join(ws.sessionsDir, n), 'session', SCHEMA_VERSION, validateSession)).data);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
/* corrupt session summaries are reported by doctor, not fatal here */
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/sessions/session.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { Check } from '../config/validate.js';\nimport { DevWorkspaceError } from '../errors/index.js';\nimport { SCHEMA_VERSION } from '../config/schemas.js';\nimport { atomicWriteJson, pathExists, randomId } from '../utils/fs.js';\nimport { readVersioned } from '../core/versioned.js';\nimport type { Ws } from '../core/workspace.js';\nimport { containsSecret } from '../security/redact.js';\n\nexport interface Session {\n schemaVersion: number;\n id: string;\n createdAt: string;\n goal: string;\n completed: string[];\n areas: string[];\n testsRun: string[];\n decisions: string[];\n remaining: string[];\n next: string;\n taskId?: string;\n}\n\nexport type SessionInput = Omit<Session, 'schemaVersion' | 'id' | 'createdAt'>;\n\nexport function validateSession(v: unknown, file = 'session'): Session {\n const c = new Check(file);\n const o = c.obj(v);\n c.at_('schemaVersion').int(o.schemaVersion, 1);\n c.at_('id').str(o.id, { max: 64 });\n c.at_('createdAt').iso(o.createdAt);\n c.at_('goal').str(o.goal, { max: 1000 });\n for (const k of ['completed', 'areas', 'testsRun', 'decisions', 'remaining'] as const) c.at_(k).strArr(o[k], 200);\n c.at_('next').str(o.next, { max: 1000 });\n return o as unknown as Session;\n}\n\n/** Sessions are short summaries, never transcripts. */\nexport async function saveSession(ws: Ws, input: SessionInput, now: string): Promise<Session> {\n if (!input.goal.trim()) throw new DevWorkspaceError('USAGE', 'A session summary needs a goal.');\n const session: Session = {\n schemaVersion: SCHEMA_VERSION,\n id: `${now.slice(0, 10).replace(/-/g, '')}-${randomId(2)}`,\n createdAt: now,\n ...input,\n };\n validateSession(session);\n if (containsSecret(JSON.stringify(session))) {\n throw new DevWorkspaceError('SECURITY', 'Session summary looks like it contains a secret; refusing to store it.');\n }\n await atomicWriteJson(path.join(ws.sessionsDir, `${session.id}.json`), session);\n return session;\n}\n\nexport async function listSessions(ws: Ws, limit = 20): Promise<Session[]> {\n if (!(await pathExists(ws.sessionsDir))) return [];\n const names = (await fs.readdir(ws.sessionsDir)).filter((n) => n.endsWith('.json')).sort().reverse().slice(0, limit);\n const out: Session[] = [];\n for (const n of names) {\n try {\n out.push((await readVersioned(path.join(ws.sessionsDir, n), 'session', SCHEMA_VERSION, validateSession)).data);\n } catch {\n /* corrupt session summaries are reported by doctor, not fatal here */\n }\n }\n return out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { TaskType } from './task.js';
|
|
2
|
+
export type Constraint = 'no-modify' | 'plan-only' | 'no-commit' | 'no-e2e' | 'no-tests' | 'no-fix';
|
|
3
|
+
export interface WorkflowSpec {
|
|
4
|
+
type: TaskType;
|
|
5
|
+
/** Highest context level normally needed (1 instructions … 6 tests/implementation). */
|
|
6
|
+
contextDepth: 1 | 2 | 3 | 4 | 5 | 6;
|
|
7
|
+
readOnly: boolean;
|
|
8
|
+
trackTask: boolean;
|
|
9
|
+
steps: string[];
|
|
10
|
+
verification: string;
|
|
11
|
+
agentSkills: string[];
|
|
12
|
+
ponytail: string;
|
|
13
|
+
knowledgeUpdate: string;
|
|
14
|
+
}
|
|
15
|
+
/** Single source of truth for routing. The generated Claude skill and docs/routing.md are rendered from this table. */
|
|
16
|
+
export declare const WORKFLOWS: Record<TaskType, WorkflowSpec>;
|
|
17
|
+
export declare function detectConstraints(text: string): Constraint[];
|
|
18
|
+
export interface Classification {
|
|
19
|
+
type: TaskType;
|
|
20
|
+
confidence: 'high' | 'low';
|
|
21
|
+
constraints: Constraint[];
|
|
22
|
+
/** True when the request must not change files (question type, or an explicit user override). */
|
|
23
|
+
readOnly: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Heuristic classifier used to propose a task type. The agent may override it; ambiguous input defaults to the safest (read-only) type. */
|
|
26
|
+
export declare function classifyRequest(text: string): Classification;
|
|
27
|
+
export declare function renderRoutingTable(): string;
|