@looplia/looplia-cli 0.7.3 → 0.7.4
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/dist/{chunk-MHR5TPHE.js → chunk-PXCY2LDE.js} +729 -78
- package/dist/{chunk-VYGRYFSY.js → chunk-XKLZXCWO.js} +64 -1
- package/dist/{claude-agent-sdk-SQ6YU4VE.js → claude-agent-sdk-IC25DTKL.js} +3 -1
- package/dist/cli.js +69 -13
- package/dist/{dist-PMEIK6PJ.js → dist-LKL7WJ7K.js} +2 -2
- package/package.json +1 -1
- package/plugins/looplia-writer/workflows/writing-kit.md +1 -3
- package/plugins/looplia-core/hooks/hooks.json +0 -22
- package/plugins/looplia-core/scripts/hooks/compact-inject-state.sh +0 -36
- package/plugins/looplia-core/scripts/hooks/post-write-validate.sh +0 -81
- package/plugins/looplia-core/scripts/hooks/stop-guard.sh +0 -56
|
@@ -107,7 +107,7 @@ import {
|
|
|
107
107
|
unknownType,
|
|
108
108
|
util,
|
|
109
109
|
voidType
|
|
110
|
-
} from "./chunk-
|
|
110
|
+
} from "./chunk-PXCY2LDE.js";
|
|
111
111
|
import {
|
|
112
112
|
pathExists
|
|
113
113
|
} from "./chunk-VRBGWKZ6.js";
|
|
@@ -708,6 +708,66 @@ function parseWorkflow(content) {
|
|
|
708
708
|
instructions: body
|
|
709
709
|
};
|
|
710
710
|
}
|
|
711
|
+
function generateValidationManifest(definition) {
|
|
712
|
+
const steps = {};
|
|
713
|
+
for (const step of definition.steps) {
|
|
714
|
+
steps[step.id] = {
|
|
715
|
+
output: step.output,
|
|
716
|
+
validate: step.validate,
|
|
717
|
+
validated: false
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
return {
|
|
721
|
+
workflow: definition.name,
|
|
722
|
+
version: definition.version,
|
|
723
|
+
finalStepId: getFinalStep(definition),
|
|
724
|
+
steps
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function getExecutionOrder(definition) {
|
|
728
|
+
const stepsMap = new Map(definition.steps.map((s) => [s.id, s]));
|
|
729
|
+
const order = [];
|
|
730
|
+
const visited = /* @__PURE__ */ new Set();
|
|
731
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
732
|
+
function visit(id) {
|
|
733
|
+
if (visited.has(id)) {
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (visiting.has(id)) {
|
|
737
|
+
throw new Error(`Circular dependency detected: ${id}`);
|
|
738
|
+
}
|
|
739
|
+
visiting.add(id);
|
|
740
|
+
const step = stepsMap.get(id);
|
|
741
|
+
if (step?.needs) {
|
|
742
|
+
for (const dep of step.needs) {
|
|
743
|
+
if (!stepsMap.has(dep)) {
|
|
744
|
+
throw new Error(`Unknown dependency '${dep}' in step '${id}'`);
|
|
745
|
+
}
|
|
746
|
+
visit(dep);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
visiting.delete(id);
|
|
750
|
+
visited.add(id);
|
|
751
|
+
order.push(id);
|
|
752
|
+
}
|
|
753
|
+
for (const step of definition.steps) {
|
|
754
|
+
visit(step.id);
|
|
755
|
+
}
|
|
756
|
+
return order;
|
|
757
|
+
}
|
|
758
|
+
function getFinalStep(definition) {
|
|
759
|
+
for (const step of definition.steps) {
|
|
760
|
+
if (step.final) {
|
|
761
|
+
return step.id;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
const order = getExecutionOrder(definition);
|
|
765
|
+
const lastStep = order.at(-1);
|
|
766
|
+
if (!lastStep) {
|
|
767
|
+
throw new Error("Workflow has no steps");
|
|
768
|
+
}
|
|
769
|
+
return lastStep;
|
|
770
|
+
}
|
|
711
771
|
function isInputlessWorkflow(definition) {
|
|
712
772
|
if (definition.inputs?.some((input) => input.required)) {
|
|
713
773
|
return false;
|
|
@@ -810,6 +870,7 @@ var ValidationManifestSchema = external_exports.object({
|
|
|
810
870
|
version: external_exports.string().optional(),
|
|
811
871
|
sandboxId: external_exports.string().optional(),
|
|
812
872
|
createdAt: external_exports.string().optional(),
|
|
873
|
+
finalStepId: external_exports.string().optional(),
|
|
813
874
|
steps: external_exports.record(external_exports.string(), StepValidationStateSchema)
|
|
814
875
|
});
|
|
815
876
|
var ValidationCheckSchema = external_exports.object({
|
|
@@ -1131,6 +1192,8 @@ async function removeSkill(skillName, registry) {
|
|
|
1131
1192
|
|
|
1132
1193
|
export {
|
|
1133
1194
|
parseWorkflow,
|
|
1195
|
+
generateValidationManifest,
|
|
1196
|
+
getFinalStep,
|
|
1134
1197
|
isInputlessWorkflow,
|
|
1135
1198
|
extractWorkflowSkills,
|
|
1136
1199
|
validateUserProfile,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
clearClaudeCodePathCache,
|
|
11
11
|
createClaudeAgentExecutor,
|
|
12
12
|
createQueryLogger,
|
|
13
|
+
createWorkflowHooks,
|
|
13
14
|
ensureWorkspace,
|
|
14
15
|
executeAgenticQueryStreaming,
|
|
15
16
|
executeInteractiveQueryStreaming,
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
validateConfig,
|
|
32
33
|
writeLoopliaSettings,
|
|
33
34
|
writeUserProfile
|
|
34
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-PXCY2LDE.js";
|
|
35
36
|
import "./chunk-VRBGWKZ6.js";
|
|
36
37
|
import "./chunk-Y55L47HC.js";
|
|
37
38
|
export {
|
|
@@ -44,6 +45,7 @@ export {
|
|
|
44
45
|
clearClaudeCodePathCache,
|
|
45
46
|
createClaudeAgentExecutor,
|
|
46
47
|
createQueryLogger,
|
|
48
|
+
createWorkflowHooks,
|
|
47
49
|
ensureWorkspace,
|
|
48
50
|
executeAgenticQueryStreaming,
|
|
49
51
|
executeInteractiveQueryStreaming,
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
ensureWorkflowSkills,
|
|
4
4
|
extractWorkflowSkills,
|
|
5
5
|
findSkill,
|
|
6
|
+
generateValidationManifest,
|
|
6
7
|
getAvailableSkills,
|
|
8
|
+
getFinalStep,
|
|
7
9
|
getInstalledSkills,
|
|
8
10
|
installSkill,
|
|
9
11
|
isInputlessWorkflow,
|
|
@@ -12,7 +14,7 @@ import {
|
|
|
12
14
|
removeSkill,
|
|
13
15
|
updateSkill,
|
|
14
16
|
validateUserProfile
|
|
15
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-XKLZXCWO.js";
|
|
16
18
|
import "./chunk-QQGRKUSM.js";
|
|
17
19
|
import {
|
|
18
20
|
addSource,
|
|
@@ -29,6 +31,7 @@ import {
|
|
|
29
31
|
applyPreset,
|
|
30
32
|
copyPlugins,
|
|
31
33
|
createClaudeAgentExecutor,
|
|
34
|
+
createWorkflowHooks,
|
|
32
35
|
downloadRemotePlugins,
|
|
33
36
|
ensureWorkspace,
|
|
34
37
|
getConfigPath,
|
|
@@ -42,7 +45,7 @@ import {
|
|
|
42
45
|
removeLoopliaSettings,
|
|
43
46
|
writeLoopliaSettings,
|
|
44
47
|
writeUserProfile
|
|
45
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-PXCY2LDE.js";
|
|
46
49
|
import "./chunk-VRBGWKZ6.js";
|
|
47
50
|
import {
|
|
48
51
|
copyOutputsToDestination,
|
|
@@ -32868,7 +32871,7 @@ async function* analyzeDescriptionStreaming(description, workspace, questionCall
|
|
|
32868
32871
|
const { createSandboxDirectories: createSandboxDirectories2, generateSandboxId: generateSandboxId2 } = await import("./sandbox-XVMNWAJO.js");
|
|
32869
32872
|
const sandboxId = generateSandboxId2("build");
|
|
32870
32873
|
createSandboxDirectories2(workspace, sandboxId);
|
|
32871
|
-
const { executeInteractiveQueryStreaming } = await import("./claude-agent-sdk-
|
|
32874
|
+
const { executeInteractiveQueryStreaming } = await import("./claude-agent-sdk-IC25DTKL.js");
|
|
32872
32875
|
const prompt = `${buildAnalysisPrompt(description)} --sandbox-id ${sandboxId}`;
|
|
32873
32876
|
const generator = executeInteractiveQueryStreaming(
|
|
32874
32877
|
prompt,
|
|
@@ -34135,7 +34138,7 @@ async function* executeInteractiveStreamingBatch(prompt, workspace, questionCall
|
|
|
34135
34138
|
const sandboxId = generateSandboxId2("build");
|
|
34136
34139
|
createSandboxDirectories2(workspace, sandboxId);
|
|
34137
34140
|
const promptWithSandbox = `${prompt} --sandbox-id ${sandboxId}`;
|
|
34138
|
-
const { executeInteractiveQueryStreaming } = await import("./claude-agent-sdk-
|
|
34141
|
+
const { executeInteractiveQueryStreaming } = await import("./claude-agent-sdk-IC25DTKL.js");
|
|
34139
34142
|
const schema = {
|
|
34140
34143
|
type: "object",
|
|
34141
34144
|
properties: {
|
|
@@ -34456,8 +34459,12 @@ Usage:
|
|
|
34456
34459
|
Available presets:
|
|
34457
34460
|
ANTHROPIC_CLAUDE_HAIKU Anthropic Claude Haiku (default)
|
|
34458
34461
|
ANTHROPIC_CLAUDE_SONNET Anthropic Claude Sonnet
|
|
34462
|
+
CLAUDE_CODE_SUBSCRIPTION_HAIKU Claude Haiku via subscription (macOS)
|
|
34463
|
+
CLAUDE_CODE_SUBSCRIPTION_SONNET Claude Sonnet via subscription (macOS)
|
|
34464
|
+
CLAUDE_CODE_SUBSCRIPTION_OPUS Claude Opus via subscription (macOS)
|
|
34459
34465
|
ZENMUX_ANTHROPIC_HAIKU45 ZenMux Claude Haiku 4.5
|
|
34460
34466
|
ZENMUX_ZAI_GLM47 ZenMux GLM-4.7
|
|
34467
|
+
ZENMUX_ZAI_GLM47FLASHX ZenMux GLM-4.7-FlashX
|
|
34461
34468
|
ZENMUX_ZAI_GLM46VFLASH ZenMux GLM-4.6v-Flash
|
|
34462
34469
|
ZENMUX_MINIMAX_M21 ZenMux MiniMax-M2.1
|
|
34463
34470
|
ZENMUX_GOOGLE_GEMINI3FLASH ZenMux Gemini-3-Flash
|
|
@@ -34466,10 +34473,14 @@ Available presets:
|
|
|
34466
34473
|
ZENMUX_XAI_GROK41FAST ZenMux Grok-4.1-Fast
|
|
34467
34474
|
ZENMUX_DEEPSEEK_V32 ZenMux DeepSeek-v3.2
|
|
34468
34475
|
ZENMUX_MISTRAL_LARGE2512 ZenMux Mistral-Large-2512
|
|
34476
|
+
OPENROUTER_PRESET OpenRouter (user-configured preset)
|
|
34477
|
+
OPENROUTER_ZAI_GLM47FLASH OpenRouter GLM-4.7-Flash
|
|
34478
|
+
OLLAMA_GLM47_CLOUD Ollama GLM-4.7 Cloud
|
|
34479
|
+
OLLAMA_MINIMAX_M21_CLOUD Ollama MiniMax-M2.1 Cloud
|
|
34469
34480
|
|
|
34470
34481
|
Configuration keys for 'set':
|
|
34471
|
-
api-provider Provider type: anthropic, zenmux, custom
|
|
34472
|
-
base-url API base URL (for zenmux/custom)
|
|
34482
|
+
api-provider Provider type: anthropic, zenmux, openrouter, ollama, custom
|
|
34483
|
+
base-url API base URL (for zenmux/openrouter/ollama/custom)
|
|
34473
34484
|
auth-token Authentication token (fallback if env var not set)
|
|
34474
34485
|
main-model Model for main agent
|
|
34475
34486
|
executor-model Model for skill executor
|
|
@@ -34477,9 +34488,13 @@ Configuration keys for 'set':
|
|
|
34477
34488
|
API keys (set in .env - looplia auto-maps based on provider):
|
|
34478
34489
|
ANTHROPIC_API_KEY For Anthropic (direct)
|
|
34479
34490
|
ZENMUX_API_KEY For ZenMux (auto-mapped to ANTHROPIC_API_KEY)
|
|
34491
|
+
OPENROUTER_API_KEY For OpenRouter (auto-mapped to ANTHROPIC_AUTH_TOKEN)
|
|
34492
|
+
OLLAMA_API_KEY For Ollama (optional, defaults to "ollama")
|
|
34480
34493
|
|
|
34481
34494
|
Examples:
|
|
34482
34495
|
looplia config provider preset ZENMUX_ZAI_GLM47
|
|
34496
|
+
looplia config provider preset OPENROUTER_PRESET
|
|
34497
|
+
looplia config provider preset OLLAMA_GLM47_CLOUD
|
|
34483
34498
|
looplia config provider show
|
|
34484
34499
|
`);
|
|
34485
34500
|
}
|
|
@@ -34605,7 +34620,9 @@ async function showProviderConfig() {
|
|
|
34605
34620
|
console.log(` Preset: ${info.preset}`);
|
|
34606
34621
|
}
|
|
34607
34622
|
console.log(` Provider: ${info.provider}`);
|
|
34608
|
-
if (info.
|
|
34623
|
+
if (info.authTokenSource === "subscription") {
|
|
34624
|
+
console.log(" Auth Source: Claude Code Subscription (OAuth)");
|
|
34625
|
+
} else if (info.authToken) {
|
|
34609
34626
|
console.log(` Auth Token: ${maskAuthToken(info.authToken)}`);
|
|
34610
34627
|
}
|
|
34611
34628
|
console.log("\n Agent Models:");
|
|
@@ -35366,7 +35383,10 @@ function executeMock2(args) {
|
|
|
35366
35383
|
}
|
|
35367
35384
|
async function executeStreaming(prompt, workspace, workflowId, requiredSkills) {
|
|
35368
35385
|
const contentId = crypto.randomUUID();
|
|
35369
|
-
const executor = createClaudeAgentExecutor({
|
|
35386
|
+
const executor = createClaudeAgentExecutor({
|
|
35387
|
+
workspace,
|
|
35388
|
+
runHooks: createWorkflowHooks()
|
|
35389
|
+
});
|
|
35370
35390
|
const generator = executor.executePromptStreaming(prompt, {
|
|
35371
35391
|
workspace,
|
|
35372
35392
|
contentId,
|
|
@@ -35394,14 +35414,21 @@ async function executeStreaming(prompt, workspace, workflowId, requiredSkills) {
|
|
|
35394
35414
|
async function executeBatch2(prompt, workspace, workflowId, requiredSkills) {
|
|
35395
35415
|
console.error("\u23F3 Processing...");
|
|
35396
35416
|
const contentId = crypto.randomUUID();
|
|
35397
|
-
const executor = createClaudeAgentExecutor({
|
|
35417
|
+
const executor = createClaudeAgentExecutor({
|
|
35418
|
+
workspace,
|
|
35419
|
+
runHooks: createWorkflowHooks()
|
|
35420
|
+
});
|
|
35398
35421
|
const result = await executor.executePrompt(prompt, {
|
|
35399
35422
|
workspace,
|
|
35400
35423
|
contentId,
|
|
35401
35424
|
requiredSkills
|
|
35402
35425
|
});
|
|
35403
|
-
if (result.success
|
|
35404
|
-
return result.data
|
|
35426
|
+
if (result.success) {
|
|
35427
|
+
return result.data ?? {
|
|
35428
|
+
status: "success",
|
|
35429
|
+
workflowId,
|
|
35430
|
+
sessionId: result.sessionId
|
|
35431
|
+
};
|
|
35405
35432
|
}
|
|
35406
35433
|
return {
|
|
35407
35434
|
status: "error",
|
|
@@ -35508,6 +35535,27 @@ async function handleJitInstallation(workflowInfo) {
|
|
|
35508
35535
|
return true;
|
|
35509
35536
|
}
|
|
35510
35537
|
}
|
|
35538
|
+
function populateValidationManifest(workspace, sandboxId, workflowId, workflowInfo) {
|
|
35539
|
+
if (!workflowInfo?.parsed?.definition) {
|
|
35540
|
+
return;
|
|
35541
|
+
}
|
|
35542
|
+
const manifest = generateValidationManifest(workflowInfo.parsed.definition);
|
|
35543
|
+
const finalStepId = getFinalStep(workflowInfo.parsed.definition);
|
|
35544
|
+
const sandboxDir = join3(workspace, "sandbox", sandboxId);
|
|
35545
|
+
const validationPath = join3(sandboxDir, "validation.json");
|
|
35546
|
+
if (!existsSync4(validationPath)) {
|
|
35547
|
+
createInitialValidationJson(sandboxDir, sandboxId, workflowId);
|
|
35548
|
+
}
|
|
35549
|
+
if (!existsSync4(validationPath)) {
|
|
35550
|
+
return;
|
|
35551
|
+
}
|
|
35552
|
+
const existing = JSON.parse(
|
|
35553
|
+
readFileSync2(validationPath, "utf-8")
|
|
35554
|
+
);
|
|
35555
|
+
existing.steps = manifest.steps;
|
|
35556
|
+
existing.finalStepId = finalStepId;
|
|
35557
|
+
writeFileSync2(validationPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
35558
|
+
}
|
|
35511
35559
|
async function runRunCommand(args) {
|
|
35512
35560
|
const parsed = parseArgs3(args);
|
|
35513
35561
|
if (parsed.help) {
|
|
@@ -35524,6 +35572,14 @@ async function runRunCommand(args) {
|
|
|
35524
35572
|
const workflowInfo = parseWorkflowFile(workspace, parsed.workflowId);
|
|
35525
35573
|
const allowInputless = workflowInfo?.isInputless ?? false;
|
|
35526
35574
|
const sandboxId = resolveSandboxId(workspace, parsed, allowInputless);
|
|
35575
|
+
process.env.LOOPLIA_SANDBOX_ID = sandboxId;
|
|
35576
|
+
process.env.LOOPLIA_SANDBOX_ROOT = join3(workspace, "sandbox");
|
|
35577
|
+
populateValidationManifest(
|
|
35578
|
+
workspace,
|
|
35579
|
+
sandboxId,
|
|
35580
|
+
parsed.workflowId,
|
|
35581
|
+
workflowInfo
|
|
35582
|
+
);
|
|
35527
35583
|
if (!(parsed.mock || await handleJitInstallation(workflowInfo))) {
|
|
35528
35584
|
process.exit(1);
|
|
35529
35585
|
}
|
|
@@ -35622,7 +35678,7 @@ async function skillAdd(nameOrUrl, from) {
|
|
|
35622
35678
|
}
|
|
35623
35679
|
if (GITHUB_URL_PATTERN.test(nameOrUrl)) {
|
|
35624
35680
|
console.log(`Installing skill from URL: ${nameOrUrl}...`);
|
|
35625
|
-
const { installSkillFromUrl } = await import("./dist-
|
|
35681
|
+
const { installSkillFromUrl } = await import("./dist-LKL7WJ7K.js");
|
|
35626
35682
|
const result2 = await installSkillFromUrl(nameOrUrl, true);
|
|
35627
35683
|
switch (result2.status) {
|
|
35628
35684
|
case "installed":
|
|
@@ -35859,7 +35915,7 @@ async function runSkillCommand(args) {
|
|
|
35859
35915
|
}
|
|
35860
35916
|
|
|
35861
35917
|
// src/index.ts
|
|
35862
|
-
var VERSION = "0.7.
|
|
35918
|
+
var VERSION = "0.7.4";
|
|
35863
35919
|
function printHelp5() {
|
|
35864
35920
|
console.log(`
|
|
35865
35921
|
looplia - Content intelligence CLI (v${VERSION})
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
loadCompiledRegistry,
|
|
12
12
|
removeSkill,
|
|
13
13
|
updateSkill
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-XKLZXCWO.js";
|
|
15
15
|
import {
|
|
16
16
|
syncRegistrySources,
|
|
17
17
|
syncSource
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
CORE_SKILLS,
|
|
33
33
|
ensureWorkspace,
|
|
34
34
|
isCoreSkill
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-PXCY2LDE.js";
|
|
36
36
|
import "./chunk-VRBGWKZ6.js";
|
|
37
37
|
import "./chunk-Y55L47HC.js";
|
|
38
38
|
export {
|
package/package.json
CHANGED
|
@@ -39,9 +39,7 @@ steps:
|
|
|
39
39
|
Include meta information (difficulty, time to write, audience).
|
|
40
40
|
Calculate relevance scores based on user profile.
|
|
41
41
|
needs: [summary, ideas]
|
|
42
|
-
input:
|
|
43
|
-
- ${{ steps.summary.output }}
|
|
44
|
-
- ${{ steps.ideas.output }}
|
|
42
|
+
input: ["${{ steps.summary.output }}", "${{ steps.ideas.output }}"]
|
|
45
43
|
output: ${{ sandbox }}/outputs/writing-kit.json
|
|
46
44
|
final: true
|
|
47
45
|
validate:
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://code.claude.com/schemas/hooks.json",
|
|
3
|
-
"hooks": [
|
|
4
|
-
{
|
|
5
|
-
"event": "PostToolUse",
|
|
6
|
-
"matcher": "Write",
|
|
7
|
-
"command": "./scripts/hooks/post-write-validate.sh",
|
|
8
|
-
"description": "Auto-validate artifacts written to sandbox outputs"
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
"event": "Stop",
|
|
12
|
-
"command": "./scripts/hooks/stop-guard.sh",
|
|
13
|
-
"description": "Guard workflow completion - block until all validated"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"event": "SessionStart",
|
|
17
|
-
"matcher": "compact",
|
|
18
|
-
"command": "./scripts/hooks/compact-inject-state.sh",
|
|
19
|
-
"description": "Re-inject sandbox state after context compact"
|
|
20
|
-
}
|
|
21
|
-
]
|
|
22
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Context Compact State Injection Hook (v0.6.0)
|
|
3
|
-
# Triggered: When context is compacted (SessionStart:compact)
|
|
4
|
-
# Action: Re-inject current sandbox progress into new context
|
|
5
|
-
|
|
6
|
-
set -euo pipefail
|
|
7
|
-
|
|
8
|
-
# Find active sandbox
|
|
9
|
-
SANDBOX_BASE="${HOME}/.looplia/sandbox"
|
|
10
|
-
if [[ ! -d "$SANDBOX_BASE" ]]; then
|
|
11
|
-
exit 0
|
|
12
|
-
fi
|
|
13
|
-
|
|
14
|
-
# Sort by modification time (newest first) to get most recent sandbox
|
|
15
|
-
SANDBOX_DIR=$(ls -td "$SANDBOX_BASE"/*/ 2>/dev/null | head -1 | sed 's:/$::')
|
|
16
|
-
if [[ -z "$SANDBOX_DIR" ]]; then
|
|
17
|
-
exit 0
|
|
18
|
-
fi
|
|
19
|
-
|
|
20
|
-
VALIDATION_JSON="$SANDBOX_DIR/validation.json"
|
|
21
|
-
SANDBOX_ID=$(basename "$SANDBOX_DIR")
|
|
22
|
-
|
|
23
|
-
if [[ ! -f "$VALIDATION_JSON" ]]; then
|
|
24
|
-
exit 0
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
WORKFLOW=$(jq -r '.workflow // "unknown"' "$VALIDATION_JSON")
|
|
28
|
-
|
|
29
|
-
# Build progress summary (v0.6.0 uses "steps" not "outputs")
|
|
30
|
-
echo "=== Active Sandbox: $SANDBOX_ID ==="
|
|
31
|
-
echo "Workflow: $WORKFLOW"
|
|
32
|
-
echo ""
|
|
33
|
-
echo "Progress:"
|
|
34
|
-
jq -r '.steps | to_entries[] | " - \(.key): \(if .value.validated then "✓ validated" else "⏳ pending" end)"' "$VALIDATION_JSON"
|
|
35
|
-
echo ""
|
|
36
|
-
echo "Next: Complete pending steps in dependency order."
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Post-Write Artifact Validator Hook (v0.6.0)
|
|
3
|
-
# Triggered: When Write tool completes
|
|
4
|
-
# Action: Run semantic validation via validate.ts for sandbox outputs
|
|
5
|
-
|
|
6
|
-
set -euo pipefail
|
|
7
|
-
|
|
8
|
-
# Read JSON input from stdin
|
|
9
|
-
INPUT=$(cat)
|
|
10
|
-
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
|
11
|
-
|
|
12
|
-
# Only process sandbox/ files (matches sandbox/{id}/outputs/*.json pattern)
|
|
13
|
-
if [[ "$FILE_PATH" != *"/sandbox/"* ]]; then
|
|
14
|
-
exit 0
|
|
15
|
-
fi
|
|
16
|
-
|
|
17
|
-
# Check if it's in outputs/ directory
|
|
18
|
-
if [[ "$FILE_PATH" != *"/outputs/"* ]]; then
|
|
19
|
-
exit 0
|
|
20
|
-
fi
|
|
21
|
-
|
|
22
|
-
# Extract sandbox directory and artifact name
|
|
23
|
-
SANDBOX_DIR=$(dirname "$(dirname "$FILE_PATH")")
|
|
24
|
-
ARTIFACT=$(basename "$FILE_PATH" .json)
|
|
25
|
-
VALIDATION_JSON="$SANDBOX_DIR/validation.json"
|
|
26
|
-
|
|
27
|
-
# Check for validation.json
|
|
28
|
-
if [[ ! -f "$VALIDATION_JSON" ]]; then
|
|
29
|
-
exit 0
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
# Use file locking to prevent race conditions when multiple writes happen concurrently
|
|
33
|
-
# flock ensures exclusive access to validation.json during read-modify-write cycle
|
|
34
|
-
LOCK_FILE="${VALIDATION_JSON}.lock"
|
|
35
|
-
|
|
36
|
-
(
|
|
37
|
-
# Acquire exclusive lock (wait up to 30 seconds)
|
|
38
|
-
if ! flock -x -w 30 200; then
|
|
39
|
-
echo "Failed to acquire lock on validation.json" >&2
|
|
40
|
-
exit 1
|
|
41
|
-
fi
|
|
42
|
-
|
|
43
|
-
# Get validation criteria from validation.json (v0.6.0 uses "steps" not "outputs")
|
|
44
|
-
CRITERIA=$(jq -r --arg art "$ARTIFACT" '.steps[$art].validate // empty' "$VALIDATION_JSON" 2>/dev/null)
|
|
45
|
-
|
|
46
|
-
if [[ -z "$CRITERIA" || "$CRITERIA" == "null" ]]; then
|
|
47
|
-
# No criteria defined - just check JSON validity
|
|
48
|
-
if ! jq empty "$FILE_PATH" 2>/dev/null; then
|
|
49
|
-
echo "Validation failed for $ARTIFACT: Invalid JSON" >&2
|
|
50
|
-
exit 2
|
|
51
|
-
fi
|
|
52
|
-
else
|
|
53
|
-
# Run full semantic validation via validate.ts
|
|
54
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
55
|
-
VALIDATOR_SCRIPT="$SCRIPT_DIR/../../skills/workflow-validator/scripts/validate.ts"
|
|
56
|
-
|
|
57
|
-
if [[ -f "$VALIDATOR_SCRIPT" ]]; then
|
|
58
|
-
# Run the validation script
|
|
59
|
-
RESULT=$(bun "$VALIDATOR_SCRIPT" "$FILE_PATH" "$CRITERIA" 2>&1) || true
|
|
60
|
-
PASSED=$(echo "$RESULT" | jq -r '.passed // false' 2>/dev/null) || PASSED="false"
|
|
61
|
-
|
|
62
|
-
if [[ "$PASSED" != "true" ]]; then
|
|
63
|
-
echo "Semantic validation failed for $ARTIFACT:" >&2
|
|
64
|
-
echo "$RESULT" >&2
|
|
65
|
-
exit 2
|
|
66
|
-
fi
|
|
67
|
-
else
|
|
68
|
-
# Fallback to basic JSON check if validator script not found
|
|
69
|
-
if ! jq empty "$FILE_PATH" 2>/dev/null; then
|
|
70
|
-
echo "Validation failed for $ARTIFACT: Invalid JSON" >&2
|
|
71
|
-
exit 2
|
|
72
|
-
fi
|
|
73
|
-
fi
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
# Update validation.json to mark step as validated (v0.6.0 uses "steps")
|
|
77
|
-
jq --arg art "$ARTIFACT" '.steps[$art].validated = true' "$VALIDATION_JSON" > "${VALIDATION_JSON}.tmp"
|
|
78
|
-
mv "${VALIDATION_JSON}.tmp" "$VALIDATION_JSON"
|
|
79
|
-
echo "✓ Validated: $ARTIFACT.json" >&2
|
|
80
|
-
|
|
81
|
-
) 200>"$LOCK_FILE"
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Workflow Completion Guard Hook (v0.6.1)
|
|
3
|
-
# Triggered: When main agent attempts to stop
|
|
4
|
-
# Action: Block if any step has validated: false OR output files missing
|
|
5
|
-
|
|
6
|
-
set -euo pipefail
|
|
7
|
-
|
|
8
|
-
INPUT=$(cat)
|
|
9
|
-
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
|
|
10
|
-
|
|
11
|
-
# Prevent infinite loop
|
|
12
|
-
if [[ "$STOP_HOOK_ACTIVE" == "true" ]]; then
|
|
13
|
-
exit 0
|
|
14
|
-
fi
|
|
15
|
-
|
|
16
|
-
# Find active sandbox (most recently modified sandbox directory)
|
|
17
|
-
SANDBOX_BASE="${HOME}/.looplia/sandbox"
|
|
18
|
-
if [[ ! -d "$SANDBOX_BASE" ]]; then
|
|
19
|
-
exit 0
|
|
20
|
-
fi
|
|
21
|
-
|
|
22
|
-
# Sort by modification time (newest first) to get most recent sandbox
|
|
23
|
-
SANDBOX_DIR=$(ls -td "$SANDBOX_BASE"/*/ 2>/dev/null | head -1 | sed 's:/$::')
|
|
24
|
-
if [[ -z "$SANDBOX_DIR" ]]; then
|
|
25
|
-
exit 0
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
|
-
VALIDATION_JSON="$SANDBOX_DIR/validation.json"
|
|
29
|
-
if [[ ! -f "$VALIDATION_JSON" ]]; then
|
|
30
|
-
exit 0
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
# Check for missing output files first (more actionable feedback)
|
|
34
|
-
MISSING=""
|
|
35
|
-
for step in $(jq -r '.steps | keys[]' "$VALIDATION_JSON" 2>/dev/null); do
|
|
36
|
-
OUTPUT_PATH=$(jq -r --arg s "$step" '.steps[$s].output // empty' "$VALIDATION_JSON" 2>/dev/null)
|
|
37
|
-
if [[ -n "$OUTPUT_PATH" && ! -f "$OUTPUT_PATH" ]]; then
|
|
38
|
-
MISSING="$MISSING $step"
|
|
39
|
-
fi
|
|
40
|
-
done
|
|
41
|
-
|
|
42
|
-
if [[ -n "$MISSING" ]]; then
|
|
43
|
-
echo "{\"decision\": \"block\", \"reason\": \"Missing output files for steps:$MISSING. You MUST call the Write tool to create these files at the paths specified in the workflow.\"}"
|
|
44
|
-
exit 0
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
# Check all steps are validated (v0.6.0 uses "steps" not "outputs")
|
|
48
|
-
PENDING=$(jq -r '.steps | to_entries[] | select(.value.validated == false) | .key' "$VALIDATION_JSON" 2>/dev/null | tr '\n' ', ' | sed 's/,$//')
|
|
49
|
-
|
|
50
|
-
if [[ -n "$PENDING" ]]; then
|
|
51
|
-
echo "{\"decision\": \"block\", \"reason\": \"Workflow incomplete. Pending validation for steps: $PENDING. Output files exist but need validation - re-write them to trigger validation.\"}"
|
|
52
|
-
exit 0
|
|
53
|
-
fi
|
|
54
|
-
|
|
55
|
-
# All validated - allow stop
|
|
56
|
-
exit 0
|