Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/publish-vscode.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Publish VS Code extension

# Manual, vscode-only publish — decoupled from the tag-driven npm release so it
# never tries to republish the @loop-lang/* libs (which fail on a duplicate
# version). Bump packages/vscode/package.json `version`, then run this from the
# Actions tab. Uses the same VSCE_PAT secret as release.yml.
on:
workflow_dispatch:

jobs:
vscode:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run build # build all workspaces so parser/runtime/viz types exist
- run: npm run build -w loop-vscode # syncs the template library + bundles
- run: npx --yes @vscode/vsce@latest publish --no-dependencies
working-directory: packages/vscode
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ packages/cli/assets/
packages/cli/AGENTS.md
packages/cli/.claude/
.vercel

# Synced from the repo template library at build (single source of truth: /templates)
packages/vscode/templates/
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ Versions track the `@loop-lang/loop` installer package.
- `demo.loop` — a copy-and-edit sample pipeline (plan → implement → ship) at the
repo root for the README/tutorial.

### Changed
- **`loop-vscode` 0.4.0** — *New from template* now offers the full best-practice
library (`bugfix`, `feature`, `brownfield-feature`, `refactor`, `cicd-check`,
`security`, `clean-architecture`, `test-coverage`, `review-diff`, plus the
multi-file `greenfield-app`/`load-spec` flows), synced from the repo's
`templates/` at build (single source of truth) — replacing the stale
`bmad`/`foreach` bundles. Tutorial now states what the extension gives you and
lists the latest vocabulary it understands. *New Loop from template* is now
reachable from **File ▸ New File…** and an Explorer folder **right-click** (drops
the template straight into that folder), not just the Command Palette.
- A manual `Publish VS Code extension` workflow (`workflow_dispatch`) publishes
the extension on its own, decoupled from the tag-driven npm release.

## [0.5.0] — 2026-06-28

> `@loop-lang/loop` 0.5.0 · `@loop-lang/{parser,runtime,stdlib,viz}` 0.2.0
Expand Down
6 changes: 3 additions & 3 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -737,12 +737,12 @@ <h3 id="live">Watch it run — live dashboard <span class="badge b-inter">interm
<p>The page streams over Server-Sent Events (each event carries an id; the server replays on connect and dedupes on reconnect), is self-contained — no external assets — and binds to <code class="inl">127.0.0.1</code> only.</p>

<h3>In VS Code <span class="badge b-basic">optional — for hand-authoring</span></h3>
<p>Secondary to the chat — reach for the extension only when you want to write <code class="inl">.loop</code> files by hand in an editor.</p>
<p>Secondary to the chat — reach for the extension only when you want to write <code class="inl">.loop</code> files by hand in an editor. It turns a plain text file into a real authoring surface: it knows the grammar, catches a loop that can't finish before you run it, lets you launch a run without leaving the editor, and drops in a ready-made best-practice loop to start from. What you get:</p>
<ul>
<li>Syntax highlighting, hover docs, and tab-completion for every construct.</li>
<li>Syntax highlighting, hover docs, and context-aware tab-completion for every construct — including the latest vocabulary (<code class="inl">use skills</code>, <code class="inl">remember in</code>, <code class="inl">flow</code>, <code class="inl">for each</code>, and <code class="inl">done when the skill "…" approves / scores N or more</code>).</li>
<li>A <strong>▶ Run</strong> CodeLens above each definition. When you click it, you choose <em>where</em> it runs (the <code class="inl">loop.runMode</code> setting): <strong>open a Claude Code session</strong> — an interactive run in the integrated terminal where you watch every step and answer gates in chat — or the <strong>VS Code output panel</strong> (headless, with a live trace and native gate dialogs). Set <code class="inl">loop.runMode</code> to <code class="inl">ask</code> (prompt each time), <code class="inl">session</code>, or <code class="inl">output</code>.</li>
<li>A soft linter that nudges (never blocks): "this loop has no way to verify it's done", "add a thrash guard".</li>
<li><strong>LoopFlow: New from template</strong> — scaffold a ready-to-edit method bundle (a generic <code class="inl">for each</code> setup, or a BMAD A-to-Z one) into your workspace in one pick.</li>
<li><strong>New Loop from template</strong> — reachable from <em>File ▸ New File…</em>, an Explorer folder <em>right-click</em>, or the Command Palette — scaffolds a ready-to-edit, best-practice loop into your workspace in one pick, from the same library <code class="inl">loop init</code> ships: <code class="inl">bugfix</code>, <code class="inl">feature</code>, <code class="inl">brownfield-feature</code>, <code class="inl">refactor</code>; <code class="inl">cicd-check</code>, <code class="inl">security</code>, <code class="inl">clean-architecture</code>, <code class="inl">test-coverage</code>, <code class="inl">review-diff</code>; and the multi-file flows <code class="inl">greenfield-app</code> and <code class="inl">load-spec</code> (which pull in their supporting <code class="inl">discover</code>/<code class="inl">design</code>/<code class="inl">story-template</code> + <code class="inl">sprint.yaml</code>). Each lands with its <code class="inl"># TODO</code> lines marked for you to fill in.</li>
</ul>

<h3>Troubleshooting</h3>
Expand Down
1 change: 1 addition & 0 deletions packages/vscode/.vscodeignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
src/**
test/**
scripts/**
.vscode/**
tsconfig.json
**/*.ts
Expand Down
16 changes: 13 additions & 3 deletions packages/vscode/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "loop-vscode",
"displayName": "LoopFlow",
"version": "0.3.0",
"version": "0.4.0",
"description": "Syntax, formatting, best-practice templates, completion, and ▶ Run / Claude-session buttons for the LoopFlow (.loop) DSL.",
"license": "Apache-2.0",
"publisher": "loop-lang",
Expand Down Expand Up @@ -73,9 +73,18 @@
},
{
"command": "loop.newFromTemplate",
"title": "LoopFlow: New from template"
"title": "New Loop from template…",
"category": "LoopFlow"
}
],
"menus": {
"file/newFile": [
{ "command": "loop.newFromTemplate", "group": "loop" }
],
"explorer/context": [
{ "command": "loop.newFromTemplate", "when": "explorerResourceIsFolder", "group": "navigation@20" }
]
},
"configuration": {
"title": "LoopFlow",
"properties": {
Expand Down Expand Up @@ -114,7 +123,8 @@
},
"scripts": {
"vscode:prepublish": "npm run build",
"build": "tsc -p tsconfig.json && npm run bundle",
"sync:templates": "node scripts/sync-templates.mjs",
"build": "npm run sync:templates && tsc -p tsconfig.json && npm run bundle",
"bundle": "esbuild src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node --target=node18 --minify",
"package": "npx --yes @vscode/vsce@latest package --no-dependencies",
"test": "node --test test/*.test.mjs"
Expand Down
15 changes: 15 additions & 0 deletions packages/vscode/scripts/sync-templates.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copy the repo's canonical template library into the extension so the .vsix
// ships it. Single source of truth = ../../../templates. Run in `build` before
// packaging. ponytail: a copy, not a registry — the picker's file list lives in
// src/extension.ts.
import { cp, rm } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";

const here = dirname(fileURLToPath(import.meta.url));
const src = join(here, "..", "..", "..", "templates");
const dest = join(here, "..", "templates");

await rm(dest, { recursive: true, force: true });
await cp(src, dest, { recursive: true });
console.log(`synced templates → ${dest}`);
88 changes: 50 additions & 38 deletions packages/vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function activate(context: vscode.ExtensionContext) {
if (!target) return void vscode.window.showErrorMessage("Loop: no .loop file to run.");
runInSession(target);
}),
vscode.commands.registerCommand("loop.newFromTemplate", () => newFromTemplate(context))
vscode.commands.registerCommand("loop.newFromTemplate", (uri?: vscode.Uri) => newFromTemplate(context, uri))
);

// Live parse diagnostics (red squiggles) — debounced.
Expand All @@ -47,67 +47,79 @@ export function activate(context: vscode.ExtensionContext) {

export function deactivate() {}

async function newFromTemplate(context: vscode.ExtensionContext): Promise<void> {
// The bundled best-practice library (synced from the repo's /templates at build).
// `deps` are the shared support files a multi-file flow needs alongside its entry.
interface Tmpl { file: string; label: string; blurb: string; deps?: string[] }
const TEMPLATES: Tmpl[] = [
// Spec-driven — a whole app / a written backlog
{ file: "greenfield-app.loop", label: "greenfield-app", blurb: "Build an app A→Z: discover → design → per-story", deps: ["discover.loop", "design.loop", "story-template.loop", "sprint.yaml", "plan.md"] },
{ file: "load-spec.loop", label: "load-spec", blurb: "Deliver an existing backlog story by story", deps: ["story-template.loop", "sprint.yaml", "plan.md"] },
// Change — build / fix / restructure
{ file: "feature.loop", label: "feature", blurb: "Ship one feature: build → regression → security → 👤 review" },
{ file: "brownfield-feature.loop", label: "brownfield-feature", blurb: "Add a feature to an existing codebase without breaking it" },
{ file: "bugfix.loop", label: "bugfix", blurb: "Fix one bug, proven by a named test" },
{ file: "refactor.loop", label: "refactor", blurb: "Improve structure, behavior unchanged" },
// Quality gates — drive existing checks to green
{ file: "cicd-check.loop", label: "cicd-check", blurb: "Make every CI check pass locally" },
{ file: "security.loop", label: "security", blurb: "Security pass before shipping: sast → deps → 👤 secrets" },
{ file: "clean-architecture.loop", label: "clean-architecture", blurb: "Enforce architecture boundaries — deps point inward" },
{ file: "test-coverage.loop", label: "test-coverage", blurb: "Raise coverage to a threshold with real tests" },
{ file: "review-diff.loop", label: "review-diff", blurb: "Review + clean the current branch diff" },
];

async function newFromTemplate(context: vscode.ExtensionContext, folder?: vscode.Uri): Promise<void> {
const fs = await import("node:fs/promises");
const { join } = await import("node:path");
const templatesDir = join(context.extensionPath, "templates");

let names: string[];
try {
const ents = await fs.readdir(templatesDir, { withFileTypes: true });
names = ents.filter((d) => d.isDirectory()).map((d) => d.name);
} catch {
vscode.window.showErrorMessage("Loop: no templates bundled with the extension.");
return;
}
if (names.length === 0) { vscode.window.showErrorMessage("Loop: no templates found."); return; }

const blurbs: Record<string, string> = {
foreach: "Generic: iterate a plan, run a checklist per item",
bmad: "BMAD A-to-Z: discover → design → per-story checklist",
};
const pick = await vscode.window.showQuickPick(
names.map((n) => ({ label: n, description: blurbs[n] ?? "" })),
TEMPLATES.map((t) => ({ label: t.label, description: t.blurb, tmpl: t })),
{ placeHolder: "Choose a Loop template" }
);
if (!pick) return;

const ws = vscode.workspace.workspaceFolders?.[0];
if (!ws) { vscode.window.showErrorMessage("Loop: open a folder first."); return; }

const sub = await vscode.window.showInputBox({
prompt: "Copy the template into which folder (relative to the workspace)?",
value: pick.label,
});
if (sub === undefined) return;

const srcDir = join(templatesDir, pick.label);
const destDir = join(ws.uri.fsPath, sub);
// Right-clicked an Explorer folder → drop it there, no prompt. Otherwise ask,
// relative to the workspace root.
let destDir: string;
if (folder) {
destDir = folder.fsPath;
} else {
const ws = vscode.workspace.workspaceFolders?.[0];
if (!ws) { vscode.window.showErrorMessage("Loop: open a folder first."); return; }
const sub = await vscode.window.showInputBox({
prompt: "Copy into which folder (relative to the workspace)?",
value: ".",
});
if (sub === undefined) return;
destDir = join(ws.uri.fsPath, sub);
}
await fs.mkdir(destDir, { recursive: true });
const where = vscode.workspace.asRelativePath(destDir);

const files = await fs.readdir(srcDir);
const files = [pick.tmpl.file, ...(pick.tmpl.deps ?? [])];
let copied = 0;
let skipped = 0;
for (const f of files) {
const dest = join(destDir, f);
let exists = false;
try { await fs.access(dest); exists = true; } catch { /* missing → copy */ }
if (exists) {
const ans = await vscode.window.showWarningMessage(`${f} already exists in ${sub}. Overwrite?`, "Overwrite", "Skip");
const ans = await vscode.window.showWarningMessage(`${f} already exists in ${where}. Overwrite?`, "Overwrite", "Skip");
if (ans !== "Overwrite") { skipped++; continue; }
}
await fs.copyFile(join(srcDir, f), dest);
copied++;
try {
await fs.copyFile(join(templatesDir, f), dest);
copied++;
} catch {
vscode.window.showWarningMessage(`Loop: template file "${f}" is missing from the extension bundle.`);
}
}

vscode.window.showInformationMessage(`Loop: copied ${copied} file(s) to ${sub}${skipped ? `, skipped ${skipped}` : ""}.`);
vscode.window.showInformationMessage(`Loop: copied ${copied} file(s) to ${where}${skipped ? `, skipped ${skipped}` : ""}. Edit the # TODO lines before running.`);

// Open the entry .loop so the user lands on the thing to run.
const entry = files.find((f) => /^(epic|deliver)\.loop$/.test(f)) ?? files.find((f) => f.endsWith(".loop"));
if (entry) {
const doc = await vscode.workspace.openTextDocument(join(destDir, entry));
await vscode.window.showTextDocument(doc);
}
const doc = await vscode.workspace.openTextDocument(join(destDir, pick.tmpl.file));
await vscode.window.showTextDocument(doc);
}

/** ▶ Run lens above every loop/pipeline definition. */
Expand Down
37 changes: 0 additions & 37 deletions packages/vscode/templates/bmad/README.md

This file was deleted.

7 changes: 0 additions & 7 deletions packages/vscode/templates/bmad/design.loop

This file was deleted.

9 changes: 0 additions & 9 deletions packages/vscode/templates/bmad/discover.loop

This file was deleted.

7 changes: 0 additions & 7 deletions packages/vscode/templates/bmad/epic.loop

This file was deleted.

7 changes: 0 additions & 7 deletions packages/vscode/templates/bmad/sprint.yaml

This file was deleted.

21 changes: 0 additions & 21 deletions packages/vscode/templates/bmad/story-template.loop

This file was deleted.

26 changes: 0 additions & 26 deletions packages/vscode/templates/foreach/README.md

This file was deleted.

5 changes: 0 additions & 5 deletions packages/vscode/templates/foreach/deliver.loop

This file was deleted.

Loading
Loading