diff --git a/.github/workflows/vscode-marketplace.yml b/.github/workflows/vscode-marketplace.yml new file mode 100644 index 0000000..c53e4f6 --- /dev/null +++ b/.github/workflows/vscode-marketplace.yml @@ -0,0 +1,140 @@ +name: Publish VS Code Marketplace extension + +on: + workflow_dispatch: + inputs: + release_ref: + description: "Immutable repository tag to package (for example v0.28.0)" + required: true + type: string + publish: + description: "Publish the verified VSIX to the VS Code Marketplace after protected-environment review" + required: true + default: false + type: boolean + +permissions: + contents: read + +concurrency: + group: vscode-marketplace-${{ inputs.release_ref }} + cancel-in-progress: false + +jobs: + authorize: + name: authorize protected VS Code publication + if: inputs.publish == true + runs-on: ubuntu-latest + environment: vscode-marketplace + steps: + - name: Require the scoped VS Code publisher token before candidate work + shell: bash + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + run: | + set -euo pipefail + test -n "$VSCE_PAT" || { + echo "VSCE_PAT is required in the vscode-marketplace environment." >&2 + exit 1 + } + + validate: + name: validate immutable VS Code candidate + needs: authorize + if: ${{ always() && (inputs.publish == false || needs.authorize.result == 'success') }} + runs-on: ubuntu-latest + defaults: + run: + working-directory: editors/vscode + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ inputs.release_ref }} + fetch-depth: 0 + - name: Require an immutable release tag + shell: bash + env: + RELEASE_REF: ${{ inputs.release_ref }} + run: | + set -euo pipefail + [[ "$RELEASE_REF" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$ ]] || { + echo "release_ref must be an immutable tag such as v0.28.0." >&2 + exit 1 + } + test "$(git rev-list -n 1 "$RELEASE_REF")" = "$(git rev-parse HEAD)" || { + echo "Checked-out commit does not match $RELEASE_REF." >&2 + exit 1 + } + git tag --points-at HEAD --format='%(refname:short)' | grep -Fx "$RELEASE_REF" >/dev/null || { + echo "$RELEASE_REF is not an immutable tag pointing at HEAD." >&2 + exit 1 + } + - uses: actions/setup-node@v7.0.0 + with: + node-version: "22" + cache: npm + cache-dependency-path: editors/vscode/package-lock.json + - name: Install, audit, test, and package + run: | + set -euo pipefail + npm ci + npm run audit + npm test + npx vsce package --no-dependencies --out factoryline-vscode.vsix + - name: Seal the verified VS Code candidate + shell: bash + env: + RELEASE_REF: ${{ inputs.release_ref }} + run: | + set -euo pipefail + test -f factoryline-vscode.vsix + mkdir -p "$GITHUB_WORKSPACE/vscode-marketplace-candidate" + cp factoryline-vscode.vsix "$GITHUB_WORKSPACE/vscode-marketplace-candidate/" + sha256sum factoryline-vscode.vsix > "$GITHUB_WORKSPACE/vscode-marketplace-candidate/SHA256SUMS.txt" + printf 'release_ref=%s\ncommit=%s\npublisher=zrk222\n' \ + "$RELEASE_REF" "$(git rev-parse HEAD)" > "$GITHUB_WORKSPACE/vscode-marketplace-candidate/manifest.txt" + - uses: actions/upload-artifact@v7.0.1 + with: + name: vscode-marketplace-candidate-${{ inputs.release_ref }} + path: vscode-marketplace-candidate/ + if-no-files-found: error + retention-days: 14 + + publish: + name: publish verified VS Code candidate + if: ${{ always() && inputs.publish == true && needs.authorize.result == 'success' && needs.validate.result == 'success' }} + needs: [authorize, validate] + runs-on: ubuntu-latest + environment: vscode-marketplace + steps: + - uses: actions/download-artifact@v8.0.1 + with: + name: vscode-marketplace-candidate-${{ inputs.release_ref }} + path: vscode-marketplace-candidate + - name: Require the scoped VS Code publisher token + shell: bash + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + run: | + set -euo pipefail + test -n "$VSCE_PAT" || { + echo "VSCE_PAT is required in the vscode-marketplace environment." >&2 + exit 1 + } + - name: Verify sealed candidate + shell: bash + run: | + set -euo pipefail + cd vscode-marketplace-candidate + sha256sum --check SHA256SUMS.txt + test -f manifest.txt + test -f factoryline-vscode.vsix + - name: Publish verified VSIX to VS Code Marketplace + shell: bash + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + run: | + set -euo pipefail + npx --yes @vscode/vsce@3.2.1 publish \ + --packagePath vscode-marketplace-candidate/factoryline-vscode.vsix \ + --pat "$VSCE_PAT" diff --git a/.mcp/server.json b/.mcp/server.json new file mode 100644 index 0000000..20f9009 --- /dev/null +++ b/.mcp/server.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", + "name": "io.github.zrk222/code-factory", + "title": "Code Factory proof context", + "description": "Read-only Graph Ops and proof context for AI coding clients.", + "version": "0.28.0", + "websiteUrl": "https://zrk222-code-factory.static.hf.space", + "repository": { + "url": "https://github.com/zrk222/code-factory", + "source": "github" + }, + "packages": [ + { + "registryType": "pypi", + "registryBaseUrl": "https://pypi.org", + "identifier": "factoryline-code-factory", + "version": "0.28.0", + "runtimeHint": "uvx", + "transport": { + "type": "stdio" + }, + "runtimeArguments": [ + { + "type": "positional", + "value": "factory" + } + ], + "packageArguments": [ + { + "type": "positional", + "value": "mcp" + }, + { + "type": "positional", + "value": "serve" + } + ] + } + ] +} diff --git a/README.md b/README.md index a0df9ef..6aa5192 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,16 @@ -# code-factory +# Code Factory + + [![CI](https://github.com/zrk222/code-factory/actions/workflows/ci.yml/badge.svg)](https://github.com/zrk222/code-factory/actions/workflows/ci.yml) [![PyPI](https://img.shields.io/pypi/v/factoryline-code-factory.svg)](https://pypi.org/project/factoryline-code-factory/) [![Python](https://img.shields.io/pypi/pyversions/factoryline-code-factory.svg)](https://pypi.org/project/factoryline-code-factory/) [![Hugging Face Space](https://img.shields.io/badge/Hugging%20Face-Space-ffd21e.svg)](https://huggingface.co/spaces/zrk222/code-factory) +[![GitHub stars](https://img.shields.io/github/stars/zrk222/code-factory?style=social)](https://github.com/zrk222/code-factory/stargazers) +[![Latest release](https://img.shields.io/github/v/release/zrk222/code-factory)](https://github.com/zrk222/code-factory/releases/latest) -> One intent, seven software targets, and proof that the gates reject -> deliberately sabotaged builds. +> Turn a plain-language outcome into a reviewable MVP with deterministic proof— +> without pretending generated code is production-ready. > **Why pay for opaque app generators?** Create a reviewable MVP starting state > in minutes—with source-bound receipts, a clear proof path, and an output you @@ -21,7 +25,32 @@ factory studio --root .\my-mvp [Watch the exact shipped UI in 60 seconds](https://github.com/zrk222/code-factory/releases/download/v0.28.0/code-factory-quickstart-v0171.mp4) to see the outcome-to-MVP path, then open **Graph Ops** in the local Studio to -inspect the evidence path and next action. +inspect the evidence path and next action. Prefer a browser preview? Open the +[live Hugging Face Space](https://zrk222-code-factory.static.hf.space). + +**Choose the shortest path:** + +| Goal | Start here | What you get | +| --- | --- | --- | +| Create | `factory mvp "..."` | A contained, app-shaped starting state | +| Inspect | `factory studio --root .` | Graph Ops, receipts, and one next action | +| Connect | [Cursor or OpenCode MCP](docs/AI_CLIENTS.md) | Portable, local proof context | +| Prove | `factory verify --root .` | A fail-closed decision with evidence | + +## Verify in GitHub Actions + +Use the root composite action when a pull request or release should carry the +same proof decision as a local run: + +```yaml +- uses: zrk222/code-factory@v0.28.1 + with: + feature: my-feature +``` + +It uploads the JSON decision and manifest as a workflow artifact, and fails the +job when the evidence is incomplete. See [GitHub Marketplace action](docs/GITHUB_MARKETPLACE_ACTION.md) +for the input contract and publication boundary. ## What Code Factory is @@ -33,6 +62,10 @@ gates; then show what is proven, missing, blocked, or safe to do next. Factory Studio and the editor integrations make the same workflow visible without granting them release, credential, approval, or publishing authority. +It works with the local workflows developers already use—including Codex, +Claude Code, Cursor, OpenCode, VS Code, and JetBrains—through the same +receipt-bound contract rather than a provider-specific lock-in. + It is designed for two speeds: use `factory mvp "..."` to get a novice from idea to an inspectable MVP path, or compose capability packs, signed receipts, proof reuse, Graph Ops impact analysis, and supervised enterprise controls for diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..18d5273 --- /dev/null +++ b/action.yml @@ -0,0 +1,70 @@ +name: Code Factory proof verification +description: Run a fail-closed Code Factory shippability decision and retain its JSON proof artifact. +author: zrk222 +branding: + icon: shield + color: green + +inputs: + feature: + description: Existing feature id whose local receipts and verifier evidence should be summarized. + required: true + root: + description: Workspace root passed to Code Factory. + required: false + default: . + package-version: + description: Published factoryline-code-factory version to run. + required: false + default: 0.28.0 + artifact-name: + description: Artifact name used for the JSON proof output. + required: false + default: code-factory-proof + +runs: + using: composite + steps: + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + - name: Install Code Factory + shell: bash + env: + FACTORY_PACKAGE_VERSION: ${{ inputs.package-version }} + run: | + set -euo pipefail + python -m pip install --disable-pip-version-check \ + "factoryline-code-factory==${FACTORY_PACKAGE_VERSION}" + - name: Run fail-closed proof decision + shell: bash + env: + FACTORY_FEATURE: ${{ inputs.feature }} + FACTORY_ROOT: ${{ inputs.root }} + run: | + set -euo pipefail + proof_dir="$GITHUB_WORKSPACE/factory-proof" + mkdir -p "$proof_dir" + set +e + factory verify "$FACTORY_FEATURE" --root "$FACTORY_ROOT" --json \ + | tee "$proof_dir/verify.json" + status=${PIPESTATUS[0]} + set -e + printf 'exit_code=%s\nfeature=%s\n' "$status" "$FACTORY_FEATURE" \ + > "$proof_dir/manifest.txt" + echo "FACTORY_VERIFY_EXIT=$status" >> "$GITHUB_ENV" + exit "$status" + - name: Upload proof artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: factory-proof/ + if-no-files-found: error + - name: Preserve the proof decision as the action result + if: always() + shell: bash + run: | + set -euo pipefail + test "${FACTORY_VERIFY_EXIT:-1}" -eq 0 diff --git a/deploy/huggingface/README.md b/deploy/huggingface/README.md index 01b9654..4c31acf 100644 --- a/deploy/huggingface/README.md +++ b/deploy/huggingface/README.md @@ -12,10 +12,13 @@ tags: - devops - software-quality - mcp + - model-context-protocol + - cursor + - opencode - local-first pinned: false license: apache-2.0 -short_description: Reviewable MVPs with verifier, repair, and workspace proof +short_description: Proof-backed AI coding workflows with local MCP context --- # Code Factory @@ -34,6 +37,18 @@ The canonical Python package is Release `v0.28.0` is archived under the repository's stable Zenodo concept DOI at [Zenodo](https://doi.org/10.5281/zenodo.21381405). +Use the same local proof context from Cursor or OpenCode through the documented +[MCP connection](https://github.com/zrk222/code-factory/blob/main/docs/AI_CLIENTS.md). +The client connection is local and read-only; it does not upload source or +grant provider, credential, deployment, or publishing authority. + +Try the public **Proof Playground** before installing: enter an outcome and +watch a deterministic browser-only preview assemble intent, checks, and a +local proof reference. It is deliberately not a production mission, does not +call a model, and never sends the text away. The same page includes copyable +recipes for repository readiness, contained MVPs, Graph Ops, independent +verification, Cursor, and OpenCode. + Before a PRD becomes a scaffold, run `factory prd grill PRD.md --root .` for a capped, source-bound clarification sheet with recommendations and answer stubs. It never rewrites the PRD, invents answers, calls a model, or authorizes a diff --git a/deploy/huggingface/index.html b/deploy/huggingface/index.html index 62a53fb..59b0aab 100644 --- a/deploy/huggingface/index.html +++ b/deploy/huggingface/index.html @@ -67,15 +67,63 @@ pre { overflow-x: auto; margin: 0; padding: 24px; border: 1px solid rgba(255,255,255,.14); border-radius: 18px; background: #0d0d0b; color: #ffd47c; font: 600 .95rem/1.7 ui-monospace, SFMono-Regular, Consolas, monospace; } .gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; } .gallery img { display: block; width: 100%; border-radius: 18px; border: 1px solid var(--line); background: var(--cream); } + .playground-section { padding-top: 3vw; } + .playground { display: grid; grid-template-columns: .82fr 1.18fr; gap: 20px; padding: clamp(20px, 4vw, 34px); border: 1px solid var(--line); border-radius: var(--radius); background: linear-gradient(135deg, rgba(255,255,255,.88), rgba(255,241,207,.9)); box-shadow: var(--shadow); } + .playground-input { padding: 18px 10px 18px 6px; } + .playground-input h3 { margin: 10px 0 8px; font-size: clamp(1.6rem, 3vw, 2.4rem); letter-spacing: -.045em; line-height: 1.5; } + .bake-action { background: var(--amber); color: var(--ink); border-color: var(--amber); } + .bake-action:hover { background: #ffc65d; } + .playground-input p { color: var(--muted); } + .field-label { display: block; margin: 24px 0 8px; font-size: .9rem; font-weight: 850; letter-spacing: .08em; text-transform: uppercase; } + textarea { display: block; width: 100%; min-height: 116px; resize: vertical; padding: 15px 16px; border: 1px solid var(--line); border-radius: 16px; color: var(--ink); background: rgba(255,255,255,.82); font: inherit; line-height: 1.5; } + textarea:focus { outline: 3px solid rgba(7,95,216,.22); border-color: var(--focus); } + .preset-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 20px; } + .preset { min-height: 40px; padding: .55rem .78rem; border: 1px solid var(--line); border-radius: 999px; background: rgba(255,255,255,.68); color: var(--muted); font: 750 .85rem/1.2 inherit; cursor: pointer; } + .preset:hover, .preset:focus-visible { border-color: var(--amber-dark); color: var(--ink); background: white; } + .fine-print { margin: 12px 0 0; color: var(--muted); font-size: .86rem; line-height: 1.5; } + .receipt-panel { min-height: 390px; padding: 24px; border: 1px solid rgba(255,255,255,.18); border-radius: 22px; background: var(--ink); color: white; position: relative; overflow: hidden; } + .receipt-panel::after { content: ""; position: absolute; inset: auto -20% -60% 20%; height: 70%; background: radial-gradient(circle, rgba(244,169,40,.22), transparent 64%); pointer-events: none; } + .receipt-empty { display: grid; min-height: 340px; place-items: center; text-align: center; color: #d9d3c7; position: relative; z-index: 1; } + .receipt-empty .oven { display: grid; place-items: center; width: 78px; height: 78px; margin: 0 auto 16px; border: 1px solid rgba(255,255,255,.25); border-radius: 24px; background: #2a2820; color: var(--amber); font-size: 2.2rem; } + .receipt-result { position: relative; z-index: 1; } + .receipt-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding-bottom: 18px; border-bottom: 1px solid rgba(255,255,255,.15); } + .receipt-top h3 { margin: 0; font-size: 1.45rem; letter-spacing: -.03em; } + .receipt-top p { margin: 4px 0 0; color: #cfc7b7; font-size: .9rem; } + .status-badge { display: inline-flex; align-items: center; gap: 6px; padding: 6px 9px; border-radius: 999px; background: rgba(99,208,132,.16); color: #a9efb8; font-size: .78rem; font-weight: 850; white-space: nowrap; } + .receipt-stages { display: grid; gap: 10px; margin: 20px 0; } + .receipt-stage { display: grid; grid-template-columns: 28px 1fr auto; gap: 10px; align-items: center; padding: 10px 12px; border: 1px solid rgba(255,255,255,.11); border-radius: 12px; color: #bdb5a7; transition: background-color .25s ease, color .25s ease, transform .25s ease; } + .receipt-stage.complete { background: rgba(99,208,132,.1); color: white; transform: translateX(3px); } + .stage-dot { display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; background: #3b382e; color: #cfc7b7; font-size: .74rem; font-weight: 900; } + .complete .stage-dot { background: #398354; color: white; } + .stage-name { font-weight: 800; } + .stage-detail { color: #aaa294; font-size: .82rem; } + .complete .stage-detail { color: #a9efb8; } + .receipt-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-top: 18px; color: #bdb5a7; font-size: .82rem; } + .copy-button { min-height: 42px; padding: .62rem .84rem; border: 1px solid rgba(255,255,255,.2); border-radius: 11px; background: #2a2820; color: white; font: 800 .85rem/1.2 inherit; cursor: pointer; } + .copy-button:hover, .copy-button:focus-visible { border-color: var(--amber); color: #ffd47c; } + .recipe-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; } + .recipe-card { display: flex; flex-direction: column; gap: 12px; min-height: 250px; padding: 22px; border: 1px solid var(--line); border-radius: 20px; background: rgba(255,255,255,.68); } + .recipe-card h3 { margin: 0; font-size: 1.2rem; letter-spacing: -.02em; } + .recipe-card p { margin: 0; color: var(--muted); font-size: .94rem; line-height: 1.5; } + .recipe-card code { display: block; margin-top: auto; padding: 10px; border-radius: 10px; background: #f2e8d1; color: var(--amber-dark); font: 700 .78rem/1.4 ui-monospace, SFMono-Regular, Consolas, monospace; overflow-wrap: anywhere; } + .recipe-card .copy-button { align-self: flex-start; border-color: var(--line); background: white; color: var(--ink); } + .recipe-card .copy-button:hover, .recipe-card .copy-button:focus-visible { border-color: var(--amber-dark); color: var(--amber-dark); } + .mcp-strip { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: stretch; } + .mcp-strip .proof { padding: 30px; } + .mcp-note { display: flex; align-items: center; gap: 10px; margin-top: 14px; color: var(--green); font-size: .9rem; font-weight: 750; } + .toast { position: fixed; right: 20px; bottom: 20px; z-index: 30; padding: 12px 16px; border-radius: 12px; background: var(--ink); color: white; box-shadow: var(--shadow); font-weight: 750; opacity: 0; transform: translateY(10px); pointer-events: none; transition: opacity .2s ease, transform .2s ease; } + .toast.visible { opacity: 1; transform: translateY(0); } + @keyframes oven-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(244,169,40,.12); } 50% { box-shadow: 0 0 0 12px rgba(244,169,40,0); } } + .is-baking .oven { animation: oven-pulse 1.3s ease-in-out infinite; } .boundary { margin-top: 18px; color: var(--muted); font-size: .95rem; text-align: center; } .final { text-align: center; padding-bottom: 120px; } .final h2 { max-width: 16ch; margin: 0 auto 28px; } footer { display: flex; justify-content: space-between; gap: 20px; padding: 28px 0 42px; border-top: 1px solid var(--line); color: var(--muted); font-size: .95rem; } @media (max-width: 50rem) { nav a:not(.button) { display: none; } - .hero, .section-head, .proof { grid-template-columns: 1fr; } + .hero, .section-head, .proof, .playground, .mcp-strip { grid-template-columns: 1fr; } .hero { padding-top: 48px; } - .grid, .gallery { grid-template-columns: 1fr; } + .grid, .gallery, .recipe-grid { grid-template-columns: 1fr; } h1 { font-size: clamp(3.2rem, 17vw, 5.4rem); } section { padding: 68px 0; } footer { flex-direction: column; } @@ -84,6 +132,9 @@ html { scroll-behavior: auto; } .button { transition: none; } .button:hover { transform: none; } + .receipt-stage { transition: none; } + .is-baking .oven { animation: none; } + .toast { transition: none; } } @@ -93,6 +144,8 @@
CF Code Factory