更新了langgraph-plan-view,支持windows下使用 langgraph cli;更新了langgraph-vivado-install,要求安装langgraph cli,确定conda环境的名称为langgraph
This commit is contained in:
+347
@@ -0,0 +1,347 @@
|
|||||||
|
.system/.codex-system-skills.marker
|
||||||
|
.system/imagegen/agents/openai.yaml
|
||||||
|
.system/imagegen/assets/imagegen-small.svg
|
||||||
|
.system/imagegen/assets/imagegen.png
|
||||||
|
.system/imagegen/LICENSE.txt
|
||||||
|
.system/imagegen/references/cli.md
|
||||||
|
.system/imagegen/references/codex-network.md
|
||||||
|
.system/imagegen/references/image-api.md
|
||||||
|
.system/imagegen/references/prompting.md
|
||||||
|
.system/imagegen/references/sample-prompts.md
|
||||||
|
.system/imagegen/scripts/image_gen.py
|
||||||
|
.system/imagegen/scripts/remove_chroma_key.py
|
||||||
|
.system/imagegen/SKILL.md
|
||||||
|
.system/openai-docs/agents/openai.yaml
|
||||||
|
.system/openai-docs/assets/openai-small.svg
|
||||||
|
.system/openai-docs/assets/openai.png
|
||||||
|
.system/openai-docs/LICENSE.txt
|
||||||
|
.system/openai-docs/references/latest-model.md
|
||||||
|
.system/openai-docs/references/prompting-guide.md
|
||||||
|
.system/openai-docs/references/upgrade-guide.md
|
||||||
|
.system/openai-docs/scripts/resolve-latest-model-info.js
|
||||||
|
.system/openai-docs/SKILL.md
|
||||||
|
.system/plugin-creator/agents/openai.yaml
|
||||||
|
.system/plugin-creator/assets/plugin-creator-small.svg
|
||||||
|
.system/plugin-creator/assets/plugin-creator.png
|
||||||
|
.system/plugin-creator/references/plugin-json-spec.md
|
||||||
|
.system/plugin-creator/scripts/create_basic_plugin.py
|
||||||
|
.system/plugin-creator/scripts/validate_plugin.py
|
||||||
|
.system/plugin-creator/SKILL.md
|
||||||
|
.system/skill-creator/agents/openai.yaml
|
||||||
|
.system/skill-creator/assets/skill-creator-small.svg
|
||||||
|
.system/skill-creator/assets/skill-creator.png
|
||||||
|
.system/skill-creator/license.txt
|
||||||
|
.system/skill-creator/references/openai_yaml.md
|
||||||
|
.system/skill-creator/scripts/generate_openai_yaml.py
|
||||||
|
.system/skill-creator/scripts/init_skill.py
|
||||||
|
.system/skill-creator/scripts/quick_validate.py
|
||||||
|
.system/skill-creator/SKILL.md
|
||||||
|
.system/skill-installer/agents/openai.yaml
|
||||||
|
.system/skill-installer/assets/skill-installer-small.svg
|
||||||
|
.system/skill-installer/assets/skill-installer.png
|
||||||
|
.system/skill-installer/LICENSE.txt
|
||||||
|
.system/skill-installer/scripts/github_utils.py
|
||||||
|
.system/skill-installer/scripts/install-skill-from-github.py
|
||||||
|
.system/skill-installer/scripts/list-skills.py
|
||||||
|
.system/skill-installer/SKILL.md
|
||||||
|
deploy-to-vercel/resources/deploy-codex.sh
|
||||||
|
deploy-to-vercel/resources/deploy.sh
|
||||||
|
deploy-to-vercel/SKILL.md
|
||||||
|
claude-hud-statusline/SKILL.md
|
||||||
|
find-skills/SKILL.md
|
||||||
|
vercel-cli-with-tokens/SKILL.md
|
||||||
|
vercel-composition-patterns/AGENTS.md
|
||||||
|
vercel-composition-patterns/metadata.json
|
||||||
|
vercel-composition-patterns/README.md
|
||||||
|
vercel-composition-patterns/rules/_sections.md
|
||||||
|
vercel-composition-patterns/rules/_template.md
|
||||||
|
vercel-composition-patterns/rules/architecture-avoid-boolean-props.md
|
||||||
|
vercel-composition-patterns/rules/architecture-compound-components.md
|
||||||
|
vercel-composition-patterns/rules/patterns-children-over-render-props.md
|
||||||
|
vercel-composition-patterns/rules/patterns-explicit-variants.md
|
||||||
|
vercel-composition-patterns/rules/react19-no-forwardref.md
|
||||||
|
vercel-composition-patterns/rules/state-context-interface.md
|
||||||
|
vercel-composition-patterns/rules/state-decouple-implementation.md
|
||||||
|
vercel-composition-patterns/rules/state-lift-state.md
|
||||||
|
vercel-composition-patterns/SKILL.md
|
||||||
|
vercel-optimize/AGENTS.md
|
||||||
|
vercel-optimize/CONTRIBUTING.md
|
||||||
|
vercel-optimize/lib/auth-route.mjs
|
||||||
|
vercel-optimize/lib/budget-summary.mjs
|
||||||
|
vercel-optimize/lib/citations.mjs
|
||||||
|
vercel-optimize/lib/cost-coverage.mjs
|
||||||
|
vercel-optimize/lib/dedup-recs.mjs
|
||||||
|
vercel-optimize/lib/deep-dive.mjs
|
||||||
|
vercel-optimize/lib/display-labels.mjs
|
||||||
|
vercel-optimize/lib/extract-claims.mjs
|
||||||
|
vercel-optimize/lib/framework-support.mjs
|
||||||
|
vercel-optimize/lib/gates/build-minutes-fanout.mjs
|
||||||
|
vercel-optimize/lib/gates/cold-start.mjs
|
||||||
|
vercel-optimize/lib/gates/contract.mjs
|
||||||
|
vercel-optimize/lib/gates/cwv-poor.mjs
|
||||||
|
vercel-optimize/lib/gates/external-api-slow.mjs
|
||||||
|
vercel-optimize/lib/gates/hard-gates.mjs
|
||||||
|
vercel-optimize/lib/gates/index.mjs
|
||||||
|
vercel-optimize/lib/gates/isr-overrevalidation.mjs
|
||||||
|
vercel-optimize/lib/gates/middleware-heavy.mjs
|
||||||
|
vercel-optimize/lib/gates/observability-events-attribution.mjs
|
||||||
|
vercel-optimize/lib/gates/platform-bot-protection.mjs
|
||||||
|
vercel-optimize/lib/gates/platform-fluid-compute.mjs
|
||||||
|
vercel-optimize/lib/gates/region-misconfig.mjs
|
||||||
|
vercel-optimize/lib/gates/route-errors.mjs
|
||||||
|
vercel-optimize/lib/gates/scanner-driven.mjs
|
||||||
|
vercel-optimize/lib/gates/select-candidates.mjs
|
||||||
|
vercel-optimize/lib/gates/slow-route.mjs
|
||||||
|
vercel-optimize/lib/gates/types.d.ts
|
||||||
|
vercel-optimize/lib/gates/uncached-route.mjs
|
||||||
|
vercel-optimize/lib/gates/usage-spike-triage.mjs
|
||||||
|
vercel-optimize/lib/grade-recommendation.mjs
|
||||||
|
vercel-optimize/lib/impact-label.mjs
|
||||||
|
vercel-optimize/lib/impact-magnitude.mjs
|
||||||
|
vercel-optimize/lib/investigation-brief.mjs
|
||||||
|
vercel-optimize/lib/project-facts.mjs
|
||||||
|
vercel-optimize/lib/queries.mjs
|
||||||
|
vercel-optimize/lib/reconcile-candidates.mjs
|
||||||
|
vercel-optimize/lib/render-report.mjs
|
||||||
|
vercel-optimize/lib/repo-root.mjs
|
||||||
|
vercel-optimize/lib/route-normalize.mjs
|
||||||
|
vercel-optimize/lib/sanitizers/bot-protection-certainty.mjs
|
||||||
|
vercel-optimize/lib/sanitizers/cache-tag-invalidation-certainty.mjs
|
||||||
|
vercel-optimize/lib/sanitizers/count-correct.mjs
|
||||||
|
vercel-optimize/lib/sanitizers/function-duration-invocations.mjs
|
||||||
|
vercel-optimize/lib/sanitizers/index.mjs
|
||||||
|
vercel-optimize/lib/sanitizers/middleware-conflict.mjs
|
||||||
|
vercel-optimize/lib/sanitizers/missing-citation.mjs
|
||||||
|
vercel-optimize/lib/sanitizers/pre-release.mjs
|
||||||
|
vercel-optimize/lib/sanitizers/rate-limit.mjs
|
||||||
|
vercel-optimize/lib/sanitizers/rendering-mode-mislabel.mjs
|
||||||
|
vercel-optimize/lib/sanitizers/undeclared-dep.mjs
|
||||||
|
vercel-optimize/lib/sanitizers/vercel-directive-strip.mjs
|
||||||
|
vercel-optimize/lib/sanitizers/window-units.mjs
|
||||||
|
vercel-optimize/lib/scanners/cache-components-suspense-dedupe.mjs
|
||||||
|
vercel-optimize/lib/scanners/edge-heavy-import.mjs
|
||||||
|
vercel-optimize/lib/scanners/force-dynamic.mjs
|
||||||
|
vercel-optimize/lib/scanners/headers-in-page.mjs
|
||||||
|
vercel-optimize/lib/scanners/index.mjs
|
||||||
|
vercel-optimize/lib/scanners/large-static-asset.mjs
|
||||||
|
vercel-optimize/lib/scanners/max-age-without-s-maxage.mjs
|
||||||
|
vercel-optimize/lib/scanners/middleware-broad-matcher.mjs
|
||||||
|
vercel-optimize/lib/scanners/missing-cache-headers.mjs
|
||||||
|
vercel-optimize/lib/scanners/prisma-include-tree.mjs
|
||||||
|
vercel-optimize/lib/scanners/region-pin-in-config.mjs
|
||||||
|
vercel-optimize/lib/scanners/source-maps-production.mjs
|
||||||
|
vercel-optimize/lib/scanners/sveltekit-prerender-missing.mjs
|
||||||
|
vercel-optimize/lib/scanners/turbo-force-bypass.mjs
|
||||||
|
vercel-optimize/lib/scanners/unoptimized-image.mjs
|
||||||
|
vercel-optimize/lib/scanners/use-cache-date-stamp.mjs
|
||||||
|
vercel-optimize/lib/support-topics.mjs
|
||||||
|
vercel-optimize/lib/throttle.mjs
|
||||||
|
vercel-optimize/lib/util.mjs
|
||||||
|
vercel-optimize/lib/vercel.mjs
|
||||||
|
vercel-optimize/lib/verify-claim.mjs
|
||||||
|
vercel-optimize/lib/workspace-resolver.mjs
|
||||||
|
vercel-optimize/metadata.json
|
||||||
|
vercel-optimize/README.md
|
||||||
|
vercel-optimize/references/candidates.md
|
||||||
|
vercel-optimize/references/data-collection.md
|
||||||
|
vercel-optimize/references/docs-library.json
|
||||||
|
vercel-optimize/references/doctrine.md
|
||||||
|
vercel-optimize/references/observability-plus.md
|
||||||
|
vercel-optimize/references/playbooks/ai-application.md
|
||||||
|
vercel-optimize/references/playbooks/api-service.md
|
||||||
|
vercel-optimize/references/playbooks/content-site.md
|
||||||
|
vercel-optimize/references/playbooks/ecommerce.md
|
||||||
|
vercel-optimize/references/playbooks/marketing.md
|
||||||
|
vercel-optimize/references/playbooks/README.md
|
||||||
|
vercel-optimize/references/playbooks/saas.md
|
||||||
|
vercel-optimize/references/playbooks/sveltekit.md
|
||||||
|
vercel-optimize/references/recommendations.md
|
||||||
|
vercel-optimize/references/scanner-patterns.md
|
||||||
|
vercel-optimize/references/scoring.md
|
||||||
|
vercel-optimize/references/support-topics/astro-edge-middleware-scope.md
|
||||||
|
vercel-optimize/references/support-topics/astro-output-mode-and-isr.md
|
||||||
|
vercel-optimize/references/support-topics/auth-preserving-parallelization.md
|
||||||
|
vercel-optimize/references/support-topics/bot-protection-product-guardrails.md
|
||||||
|
vercel-optimize/references/support-topics/build-minutes-monorepo-fanout.md
|
||||||
|
vercel-optimize/references/support-topics/cache-components-static-shell-boundaries.md
|
||||||
|
vercel-optimize/references/support-topics/cache-components-suspense-dedupe-pitfall.md
|
||||||
|
vercel-optimize/references/support-topics/cdn-cache-auth-safety.md
|
||||||
|
vercel-optimize/references/support-topics/cold-start-initialization-bundle.md
|
||||||
|
vercel-optimize/references/support-topics/core-web-vitals-client-bottlenecks.md
|
||||||
|
vercel-optimize/references/support-topics/database-egress-pooling-region.md
|
||||||
|
vercel-optimize/references/support-topics/dynamic-rendering-traps.md
|
||||||
|
vercel-optimize/references/support-topics/external-api-critical-path-platform.md
|
||||||
|
vercel-optimize/references/support-topics/external-api-critical-path.md
|
||||||
|
vercel-optimize/references/support-topics/fast-data-transfer-payloads.md
|
||||||
|
vercel-optimize/references/support-topics/fluid-compute-caveats.md
|
||||||
|
vercel-optimize/references/support-topics/function-duration-io-and-after.md
|
||||||
|
vercel-optimize/references/support-topics/function-invocation-reduction.md
|
||||||
|
vercel-optimize/references/support-topics/function-region-misconfiguration-ttfb.md
|
||||||
|
vercel-optimize/references/support-topics/image-optimization-cost-control.md
|
||||||
|
vercel-optimize/references/support-topics/isr-revalidation-static-generation.md
|
||||||
|
vercel-optimize/references/support-topics/middleware-proxy-edge-cost.md
|
||||||
|
vercel-optimize/references/support-topics/next-fetch-revalidate-floor.md
|
||||||
|
vercel-optimize/references/support-topics/next-font-cls-self-hosting.md
|
||||||
|
vercel-optimize/references/support-topics/next-heavy-ui-lazy-load-boundaries.md
|
||||||
|
vercel-optimize/references/support-topics/next-image-lcp-preload-sizes.md
|
||||||
|
vercel-optimize/references/support-topics/next-route-handler-get-cache-defaults.md
|
||||||
|
vercel-optimize/references/support-topics/next-script-third-party-strategy.md
|
||||||
|
vercel-optimize/references/support-topics/nextjs-version-cache-semantics.md
|
||||||
|
vercel-optimize/references/support-topics/not-found-catchall-request-waste.md
|
||||||
|
vercel-optimize/references/support-topics/nuxt-route-rules-cache-isr.md
|
||||||
|
vercel-optimize/references/support-topics/observability-events-cost-attribution.md
|
||||||
|
vercel-optimize/references/support-topics/post-response-work-waituntil.md
|
||||||
|
vercel-optimize/references/support-topics/README.md
|
||||||
|
vercel-optimize/references/support-topics/route-error-durable-offload.md
|
||||||
|
vercel-optimize/references/support-topics/route-error-runtime-limits.md
|
||||||
|
vercel-optimize/references/support-topics/runtime-cache-reusable-data.md
|
||||||
|
vercel-optimize/references/support-topics/sveltekit-isr-prerender-safety.md
|
||||||
|
vercel-optimize/references/support-topics/sveltekit-split-cold-start-tradeoff.md
|
||||||
|
vercel-optimize/references/support-topics/usage-spike-triage.md
|
||||||
|
vercel-optimize/references/support-topics/use-cache-date-stamp-isr-write-amplifier.md
|
||||||
|
vercel-optimize/references/support-topics/use-cache-remote-shared-origin-data.md
|
||||||
|
vercel-optimize/references/verification.md
|
||||||
|
vercel-optimize/references/voice.md
|
||||||
|
vercel-optimize/scripts/budget-summary.mjs
|
||||||
|
vercel-optimize/scripts/build-docs.mjs
|
||||||
|
vercel-optimize/scripts/check-citations.mjs
|
||||||
|
vercel-optimize/scripts/check-docs-fresh.mjs
|
||||||
|
vercel-optimize/scripts/collect-signals.mjs
|
||||||
|
vercel-optimize/scripts/collect-sub-agent-outputs.mjs
|
||||||
|
vercel-optimize/scripts/deep-dive.mjs
|
||||||
|
vercel-optimize/scripts/gate-investigations.mjs
|
||||||
|
vercel-optimize/scripts/merge-signals.mjs
|
||||||
|
vercel-optimize/scripts/prepare-investigation-brief.mjs
|
||||||
|
vercel-optimize/scripts/reconcile-candidates.mjs
|
||||||
|
vercel-optimize/scripts/render-report.mjs
|
||||||
|
vercel-optimize/scripts/scan-codebase.mjs
|
||||||
|
vercel-optimize/scripts/verify-and-regen.mjs
|
||||||
|
vercel-optimize/scripts/verify-finding.mjs
|
||||||
|
vercel-optimize/SKILL.md
|
||||||
|
vercel-react-best-practices/AGENTS.md
|
||||||
|
vercel-react-best-practices/metadata.json
|
||||||
|
vercel-react-best-practices/README.md
|
||||||
|
vercel-react-best-practices/rules/_sections.md
|
||||||
|
vercel-react-best-practices/rules/_template.md
|
||||||
|
vercel-react-best-practices/rules/advanced-effect-event-deps.md
|
||||||
|
vercel-react-best-practices/rules/advanced-event-handler-refs.md
|
||||||
|
vercel-react-best-practices/rules/advanced-init-once.md
|
||||||
|
vercel-react-best-practices/rules/advanced-use-latest.md
|
||||||
|
vercel-react-best-practices/rules/async-api-routes.md
|
||||||
|
vercel-react-best-practices/rules/async-cheap-condition-before-await.md
|
||||||
|
vercel-react-best-practices/rules/async-defer-await.md
|
||||||
|
vercel-react-best-practices/rules/async-dependencies.md
|
||||||
|
vercel-react-best-practices/rules/async-parallel.md
|
||||||
|
vercel-react-best-practices/rules/async-suspense-boundaries.md
|
||||||
|
vercel-react-best-practices/rules/bundle-analyzable-paths.md
|
||||||
|
vercel-react-best-practices/rules/bundle-barrel-imports.md
|
||||||
|
vercel-react-best-practices/rules/bundle-conditional.md
|
||||||
|
vercel-react-best-practices/rules/bundle-defer-third-party.md
|
||||||
|
vercel-react-best-practices/rules/bundle-dynamic-imports.md
|
||||||
|
vercel-react-best-practices/rules/bundle-preload.md
|
||||||
|
vercel-react-best-practices/rules/client-event-listeners.md
|
||||||
|
vercel-react-best-practices/rules/client-localstorage-schema.md
|
||||||
|
vercel-react-best-practices/rules/client-passive-event-listeners.md
|
||||||
|
vercel-react-best-practices/rules/client-swr-dedup.md
|
||||||
|
vercel-react-best-practices/rules/js-batch-dom-css.md
|
||||||
|
vercel-react-best-practices/rules/js-cache-function-results.md
|
||||||
|
vercel-react-best-practices/rules/js-cache-property-access.md
|
||||||
|
vercel-react-best-practices/rules/js-cache-storage.md
|
||||||
|
vercel-react-best-practices/rules/js-combine-iterations.md
|
||||||
|
vercel-react-best-practices/rules/js-early-exit.md
|
||||||
|
vercel-react-best-practices/rules/js-flatmap-filter.md
|
||||||
|
vercel-react-best-practices/rules/js-hoist-regexp.md
|
||||||
|
vercel-react-best-practices/rules/js-index-maps.md
|
||||||
|
vercel-react-best-practices/rules/js-length-check-first.md
|
||||||
|
vercel-react-best-practices/rules/js-min-max-loop.md
|
||||||
|
vercel-react-best-practices/rules/js-request-idle-callback.md
|
||||||
|
vercel-react-best-practices/rules/js-set-map-lookups.md
|
||||||
|
vercel-react-best-practices/rules/js-tosorted-immutable.md
|
||||||
|
vercel-react-best-practices/rules/rendering-activity.md
|
||||||
|
vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md
|
||||||
|
vercel-react-best-practices/rules/rendering-conditional-render.md
|
||||||
|
vercel-react-best-practices/rules/rendering-content-visibility.md
|
||||||
|
vercel-react-best-practices/rules/rendering-hoist-jsx.md
|
||||||
|
vercel-react-best-practices/rules/rendering-hydration-no-flicker.md
|
||||||
|
vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md
|
||||||
|
vercel-react-best-practices/rules/rendering-resource-hints.md
|
||||||
|
vercel-react-best-practices/rules/rendering-script-defer-async.md
|
||||||
|
vercel-react-best-practices/rules/rendering-svg-precision.md
|
||||||
|
vercel-react-best-practices/rules/rendering-usetransition-loading.md
|
||||||
|
vercel-react-best-practices/rules/rerender-defer-reads.md
|
||||||
|
vercel-react-best-practices/rules/rerender-dependencies.md
|
||||||
|
vercel-react-best-practices/rules/rerender-derived-state-no-effect.md
|
||||||
|
vercel-react-best-practices/rules/rerender-derived-state.md
|
||||||
|
vercel-react-best-practices/rules/rerender-functional-setstate.md
|
||||||
|
vercel-react-best-practices/rules/rerender-lazy-state-init.md
|
||||||
|
vercel-react-best-practices/rules/rerender-memo-with-default-value.md
|
||||||
|
vercel-react-best-practices/rules/rerender-memo.md
|
||||||
|
vercel-react-best-practices/rules/rerender-move-effect-to-event.md
|
||||||
|
vercel-react-best-practices/rules/rerender-no-inline-components.md
|
||||||
|
vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md
|
||||||
|
vercel-react-best-practices/rules/rerender-split-combined-hooks.md
|
||||||
|
vercel-react-best-practices/rules/rerender-transitions.md
|
||||||
|
vercel-react-best-practices/rules/rerender-use-deferred-value.md
|
||||||
|
vercel-react-best-practices/rules/rerender-use-ref-transient-values.md
|
||||||
|
vercel-react-best-practices/rules/server-after-nonblocking.md
|
||||||
|
vercel-react-best-practices/rules/server-auth-actions.md
|
||||||
|
vercel-react-best-practices/rules/server-cache-lru.md
|
||||||
|
vercel-react-best-practices/rules/server-cache-react.md
|
||||||
|
vercel-react-best-practices/rules/server-dedup-props.md
|
||||||
|
vercel-react-best-practices/rules/server-hoist-static-io.md
|
||||||
|
vercel-react-best-practices/rules/server-no-shared-module-state.md
|
||||||
|
vercel-react-best-practices/rules/server-parallel-fetching.md
|
||||||
|
vercel-react-best-practices/rules/server-parallel-nested-fetching.md
|
||||||
|
vercel-react-best-practices/rules/server-serialization.md
|
||||||
|
vercel-react-best-practices/SKILL.md
|
||||||
|
vercel-react-native-skills/AGENTS.md
|
||||||
|
vercel-react-native-skills/metadata.json
|
||||||
|
vercel-react-native-skills/README.md
|
||||||
|
vercel-react-native-skills/rules/_sections.md
|
||||||
|
vercel-react-native-skills/rules/_template.md
|
||||||
|
vercel-react-native-skills/rules/animation-derived-value.md
|
||||||
|
vercel-react-native-skills/rules/animation-gesture-detector-press.md
|
||||||
|
vercel-react-native-skills/rules/animation-gpu-properties.md
|
||||||
|
vercel-react-native-skills/rules/design-system-compound-components.md
|
||||||
|
vercel-react-native-skills/rules/fonts-config-plugin.md
|
||||||
|
vercel-react-native-skills/rules/imports-design-system-folder.md
|
||||||
|
vercel-react-native-skills/rules/js-hoist-intl.md
|
||||||
|
vercel-react-native-skills/rules/list-performance-callbacks.md
|
||||||
|
vercel-react-native-skills/rules/list-performance-function-references.md
|
||||||
|
vercel-react-native-skills/rules/list-performance-images.md
|
||||||
|
vercel-react-native-skills/rules/list-performance-inline-objects.md
|
||||||
|
vercel-react-native-skills/rules/list-performance-item-expensive.md
|
||||||
|
vercel-react-native-skills/rules/list-performance-item-memo.md
|
||||||
|
vercel-react-native-skills/rules/list-performance-item-types.md
|
||||||
|
vercel-react-native-skills/rules/list-performance-virtualize.md
|
||||||
|
vercel-react-native-skills/rules/monorepo-native-deps-in-app.md
|
||||||
|
vercel-react-native-skills/rules/monorepo-single-dependency-versions.md
|
||||||
|
vercel-react-native-skills/rules/navigation-native-navigators.md
|
||||||
|
vercel-react-native-skills/rules/react-compiler-destructure-functions.md
|
||||||
|
vercel-react-native-skills/rules/react-compiler-reanimated-shared-values.md
|
||||||
|
vercel-react-native-skills/rules/react-state-dispatcher.md
|
||||||
|
vercel-react-native-skills/rules/react-state-fallback.md
|
||||||
|
vercel-react-native-skills/rules/react-state-minimize.md
|
||||||
|
vercel-react-native-skills/rules/rendering-no-falsy-and.md
|
||||||
|
vercel-react-native-skills/rules/rendering-text-in-text-component.md
|
||||||
|
vercel-react-native-skills/rules/scroll-position-no-state.md
|
||||||
|
vercel-react-native-skills/rules/state-ground-truth.md
|
||||||
|
vercel-react-native-skills/rules/ui-expo-image.md
|
||||||
|
vercel-react-native-skills/rules/ui-image-gallery.md
|
||||||
|
vercel-react-native-skills/rules/ui-measure-views.md
|
||||||
|
vercel-react-native-skills/rules/ui-menus.md
|
||||||
|
vercel-react-native-skills/rules/ui-native-modals.md
|
||||||
|
vercel-react-native-skills/rules/ui-pressable.md
|
||||||
|
vercel-react-native-skills/rules/ui-safe-area-scroll.md
|
||||||
|
vercel-react-native-skills/rules/ui-scrollview-content-inset.md
|
||||||
|
vercel-react-native-skills/rules/ui-styling.md
|
||||||
|
vercel-react-native-skills/SKILL.md
|
||||||
|
vercel-react-view-transitions/AGENTS.md
|
||||||
|
vercel-react-view-transitions/metadata.json
|
||||||
|
vercel-react-view-transitions/README.md
|
||||||
|
vercel-react-view-transitions/references/css-recipes.md
|
||||||
|
vercel-react-view-transitions/references/implementation.md
|
||||||
|
vercel-react-view-transitions/references/nextjs.md
|
||||||
|
vercel-react-view-transitions/references/patterns.md
|
||||||
|
vercel-react-view-transitions/SKILL.md
|
||||||
|
web-design-guidelines/SKILL.md
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# This directory is a Syncthing folder marker.
|
|
||||||
# Do not delete.
|
|
||||||
|
|
||||||
folderID: ggxeg-zoyv2
|
|
||||||
created: 2026-05-09T01:23:05-05:00
|
|
||||||
@@ -7,6 +7,29 @@ description: Use the LangGraph plan-review workflow from the current Claude Code
|
|||||||
|
|
||||||
Use this skill to run the LangGraph plan-review workflow. All scripts are bundled under the skill's own `scripts\` directory (`C:\Users\bowei\.claude\skills\langgraph-plan-review\scripts\`) — no dependency on `E:\LangGraph_Design\lab00`.
|
Use this skill to run the LangGraph plan-review workflow. All scripts are bundled under the skill's own `scripts\` directory (`C:\Users\bowei\.claude\skills\langgraph-plan-review\scripts\`) — no dependency on `E:\LangGraph_Design\lab00`.
|
||||||
|
|
||||||
|
## Pre-flight: Environment Setup
|
||||||
|
|
||||||
|
Before running the workflow for the first time (or when unsure the environment is ready), verify that the required packages are installed in the `langgraph` conda env:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Install langgraph dev server, SDK, and HTTP client
|
||||||
|
conda run -n langgraph pip install "langgraph-cli[inmem]" langgraph-sdk httpx
|
||||||
|
|
||||||
|
# Verify the SDK is importable
|
||||||
|
conda run -n langgraph python -c "from langgraph_sdk import get_sync_client; print('SDK OK')"
|
||||||
|
|
||||||
|
# Verify the langgraph dev command is available
|
||||||
|
conda run -n langgraph langgraph --version
|
||||||
|
```
|
||||||
|
|
||||||
|
| Package | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `langgraph-cli[inmem]` | Provides the `langgraph dev` server command |
|
||||||
|
| `langgraph-sdk` | Python client used by `run_workflow.py` to submit runs |
|
||||||
|
| `httpx` | HTTP health-check in `run_workflow.py` |
|
||||||
|
|
||||||
|
**Codex auth** (if not already set up): `C:\Users\bowei\.codex\auth.json` must contain a valid ChatGPT OAuth token. Run `codex auth login` if the file is missing.
|
||||||
|
|
||||||
## Invariants
|
## Invariants
|
||||||
|
|
||||||
- Use the current Claude Code session for all Claude Code work. Do not launch another `claude` process.
|
- Use the current Claude Code session for all Claude Code work. Do not launch another `claude` process.
|
||||||
@@ -22,13 +45,40 @@ Use this skill to run the LangGraph plan-review workflow. All scripts are bundle
|
|||||||
- Keep all workflow files under `<Claude launch work_dir>\Notes` unless the user explicitly overrides `-NotesDir`.
|
- Keep all workflow files under `<Claude launch work_dir>\Notes` unless the user explicitly overrides `-NotesDir`.
|
||||||
- `TaskId` is the numeric suffix, such as `01`; write the plan to `Notes\Task01.md`.
|
- `TaskId` is the numeric suffix, such as `01`; write the plan to `Notes\Task01.md`.
|
||||||
- `discussxx-yy.md` numbering is managed by the LangGraph app for Codex review files. If Claude rejects a review comment, create the next numbered discuss file manually before rerunning review.
|
- `discussxx-yy.md` numbering is managed by the LangGraph app for Codex review files. If Claude rejects a review comment, create the next numbered discuss file manually before rerunning review.
|
||||||
- The workflow uses LangGraph `interrupt()` and persists state in `workflow_state.db`.
|
- The workflow uses LangGraph `interrupt()` and persists state via the langgraph dev server.
|
||||||
- Each `run_langgraph.ps1` invocation advances the graph to the next interrupt or completion. Use `-Resume` to continue the same thread after completing the requested Claude Code action.
|
- Each `run_langgraph.ps1` invocation advances the graph to the next interrupt or completion. Use `-Resume` to continue the same thread after completing the requested Claude Code action.
|
||||||
|
|
||||||
|
## Dev Server Lifecycle
|
||||||
|
|
||||||
|
The `langgraph dev` server must be running for the entire duration of a workflow session.
|
||||||
|
Use `manage_dev_server.ps1` to start it before the first `run_langgraph.ps1` call and stop it after the workflow completes.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Start (idempotent — safe to call even if already running)
|
||||||
|
C:\Users\bowei\.claude\skills\langgraph-plan-review\scripts\manage_dev_server.ps1 start
|
||||||
|
|
||||||
|
# Stop (call once when workflow returns {"status":"done"})
|
||||||
|
C:\Users\bowei\.claude\skills\langgraph-plan-review\scripts\manage_dev_server.ps1 stop
|
||||||
|
|
||||||
|
# Status check
|
||||||
|
C:\Users\bowei\.claude\skills\langgraph-plan-review\scripts\manage_dev_server.ps1 status
|
||||||
|
```
|
||||||
|
|
||||||
|
- `start` waits up to 45 s for `http://127.0.0.1:2024/ok` to respond, then prints the Studio URL.
|
||||||
|
- `stop` kills the process by PID file and removes it; falls back to killing by port 2024 if the PID is stale.
|
||||||
|
- If the server is already running, `start` is a no-op.
|
||||||
|
- Server stdout/stderr logs are written to `%TEMP%\langgraph_dev_server.stdout.log` and `%TEMP%\langgraph_dev_server.stderr.log` for debugging.
|
||||||
|
- **Studio URL**: `https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024` — open in browser to track workflow execution in real time.
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
1. Determine `TaskId` from the user. If missing, ask for it.
|
1. Determine `TaskId` from the user. If missing, ask for it.
|
||||||
2. Start or resume the LangGraph app:
|
2. Start the dev server (idempotent):
|
||||||
|
```powershell
|
||||||
|
C:\Users\bowei\.claude\skills\langgraph-plan-review\scripts\manage_dev_server.ps1 start
|
||||||
|
```
|
||||||
|
Wait for the "Ready" confirmation before proceeding. Open the Studio URL in a browser for real-time tracking.
|
||||||
|
3. Start or resume the LangGraph app:
|
||||||
```powershell
|
```powershell
|
||||||
# Use double backslashes (\\) in the JSON string so Windows paths are valid JSON.
|
# Use double backslashes (\\) in the JSON string so Windows paths are valid JSON.
|
||||||
# $task_path / $latest_discuss_path / etc. are Python Template placeholders — keep them literal.
|
# $task_path / $latest_discuss_path / etc. are Python Template placeholders — keep them literal.
|
||||||
@@ -39,7 +89,7 @@ Use this skill to run the LangGraph plan-review workflow. All scripts are bundle
|
|||||||
-CodexReviewCommand $codexCmd `
|
-CodexReviewCommand $codexCmd `
|
||||||
-Timeout 300
|
-Timeout 300
|
||||||
```
|
```
|
||||||
3. Inspect the returned JSON. If `status` is `waiting`, perform `action_required` in the current Claude Code session:
|
4. Inspect the returned JSON. If `status` is `waiting`, perform `action_required` in the current Claude Code session:
|
||||||
- `write_plan`: write `Task<TaskId>.md` with project background, task objective, and concrete implementation plan, then resume with `-Resume 'true'`.
|
- `write_plan`: write `Task<TaskId>.md` with project background, task objective, and concrete implementation plan, then resume with `-Resume 'true'`.
|
||||||
- `decide`: read the discuss file and resume with `-Resume '{"accepts":true}'` or `-Resume '{"accepts":false,"reason":"..."}'`.
|
- `decide`: read the discuss file and resume with `-Resume '{"accepts":true}'` or `-Resume '{"accepts":false,"reason":"..."}'`.
|
||||||
- `revise`: revise `Task<TaskId>.md`, then resume with `-Resume 'true'`.
|
- `revise`: revise `Task<TaskId>.md`, then resume with `-Resume 'true'`.
|
||||||
@@ -48,10 +98,13 @@ Use this skill to run the LangGraph plan-review workflow. All scripts are bundle
|
|||||||
**Why `review_task.py`**: Using the shim keeps all codex-specific logic in Python, and the JSON array stays simple.
|
**Why `review_task.py`**: Using the shim keeps all codex-specific logic in Python, and the JSON array stays simple.
|
||||||
|
|
||||||
`run_langgraph.ps1` base64-encodes the JSON command and resume payload before passing them through `conda run`, so normal PowerShell single-quoted JSON can be used. **Windows paths inside the JSON string must use `\\` (double backslash) to produce valid JSON**, e.g. `C:\\Users\\bowei\\...`. `run_langgraph.ps1` automatically sets `PYTHONIOENCODING=utf-8` so prompts containing Unicode (Chinese, etc.) pass through correctly.
|
`run_langgraph.ps1` base64-encodes the JSON command and resume payload before passing them through `conda run`, so normal PowerShell single-quoted JSON can be used. **Windows paths inside the JSON string must use `\\` (double backslash) to produce valid JSON**, e.g. `C:\\Users\\bowei\\...`. `run_langgraph.ps1` automatically sets `PYTHONIOENCODING=utf-8` so prompts containing Unicode (Chinese, etc.) pass through correctly.
|
||||||
4. Inspect the command output after each resume.
|
5. Inspect the command output after each resume.
|
||||||
- Check `"status"` to determine next action: `"waiting"` means perform the next `action_required`; `"done"` means the workflow is complete.
|
- Check `"status"` to determine next action: `"waiting"` means perform the next `action_required`; `"done"` means the workflow is complete.
|
||||||
- If a new `latest_discuss_path` is present in the output, read that discuss file for context.
|
- If a new `latest_discuss_path` is present in the output, read that discuss file for context.
|
||||||
5. Stop when the workflow returns `{"status":"done", ...}`.
|
6. Stop when the workflow returns `{"status":"done", ...}`:
|
||||||
|
```powershell
|
||||||
|
C:\Users\bowei\.claude\skills\langgraph-plan-review\scripts\manage_dev_server.ps1 stop
|
||||||
|
```
|
||||||
|
|
||||||
## Rejection File Rule
|
## Rejection File Rule
|
||||||
|
|
||||||
@@ -82,3 +135,5 @@ Write the rejection content directly to `action_required.output_path`:
|
|||||||
- `review_task.py` persists the Codex session id through LangGraph state and uses `codex exec resume` on later review passes.
|
- `review_task.py` persists the Codex session id through LangGraph state and uses `codex exec resume` on later review passes.
|
||||||
- `review_task.py` runs Codex with the original Claude Code launch working directory as cwd.
|
- `review_task.py` runs Codex with the original Claude Code launch working directory as cwd.
|
||||||
- Codex review output is normalized to JSON for `agents.py`: `{"has_serious_issue":true/false,"comment":"...","codex_session_id":"..."}`.
|
- Codex review output is normalized to JSON for `agents.py`: `{"has_serious_issue":true/false,"comment":"...","codex_session_id":"..."}`.
|
||||||
|
- If the dev server restarts, `run_workflow.py` detects that the stored thread no longer exists and creates a new thread automatically.
|
||||||
|
- `run_workflow.py` passes the codex command and timeout as `config["configurable"]` per run — no server restart needed when switching projects.
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"dependencies": [
|
||||||
|
"langgraph",
|
||||||
|
"langgraph-checkpoint-sqlite"
|
||||||
|
],
|
||||||
|
"graphs": {
|
||||||
|
"plan_review": "./scripts/graph.py:graph"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
"""Module-level graph export for `langgraph dev`.
|
||||||
|
|
||||||
|
The Codex review command is injected per-run via config["configurable"],
|
||||||
|
so no runner is needed at build time. The server loads this module once;
|
||||||
|
every submitted run brings its own command through the LangGraph SDK client.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Ensure sibling modules (agents, workflow) are importable when langgraph dev
|
||||||
|
# loads this file from the skill root via ./scripts/graph.py:graph.
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent))
|
||||||
|
|
||||||
|
from workflow import build_workflow # noqa: E402
|
||||||
|
|
||||||
|
# langgraph dev manages its own persistence — do not pass a checkpointer here.
|
||||||
|
graph = build_workflow()
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Manage the langgraph dev server on Windows.
|
||||||
|
.DESCRIPTION
|
||||||
|
Start, stop, or check the status of the langgraph dev server.
|
||||||
|
The server starts from the skill root (where langgraph.json lives) so the
|
||||||
|
graph module is discovered correctly.
|
||||||
|
.PARAMETER Command
|
||||||
|
start | stop | status
|
||||||
|
.EXAMPLE
|
||||||
|
manage_dev_server.ps1 start # idempotent — safe to call if already running
|
||||||
|
manage_dev_server.ps1 stop # call once workflow returns {"status":"done"}
|
||||||
|
manage_dev_server.ps1 status # print running/stopped + Studio URL
|
||||||
|
#>
|
||||||
|
param([string]$Command = "status")
|
||||||
|
|
||||||
|
$SkillDir = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||||
|
$PidFile = Join-Path $env:TEMP "langgraph_dev_server.pid"
|
||||||
|
$StdoutLog = Join-Path $env:TEMP "langgraph_dev_server.stdout.log"
|
||||||
|
$StderrLog = Join-Path $env:TEMP "langgraph_dev_server.stderr.log"
|
||||||
|
$ApiUrl = "http://127.0.0.1:2024"
|
||||||
|
$MaxWait = 45 # seconds to wait for the server to become ready
|
||||||
|
|
||||||
|
# --- helpers -----------------------------------------------------------
|
||||||
|
|
||||||
|
function Test-HttpOk([string]$Url) {
|
||||||
|
try {
|
||||||
|
$r = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 3 -ErrorAction Stop
|
||||||
|
return ($r.StatusCode -ge 200 -and $r.StatusCode -lt 300)
|
||||||
|
} catch {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-StoredPid {
|
||||||
|
if (-not (Test-Path $PidFile)) { return 0 }
|
||||||
|
$raw = (Get-Content $PidFile -Raw -ErrorAction SilentlyContinue).Trim()
|
||||||
|
if (-not $raw) { return 0 }
|
||||||
|
[int]$raw
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-ServerRunning {
|
||||||
|
$pid = Get-StoredPid
|
||||||
|
if ($pid -eq 0) { return $false }
|
||||||
|
$proc = Get-Process -Id $pid -ErrorAction SilentlyContinue
|
||||||
|
if (-not $proc) { return $false }
|
||||||
|
return (Test-HttpOk "$ApiUrl/ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
function Find-LangGraphExe {
|
||||||
|
$up = $env:USERPROFILE
|
||||||
|
$candidates = @(
|
||||||
|
"$up\.conda\envs\langgraph\Scripts\langgraph.exe",
|
||||||
|
"$up\miniconda3\envs\langgraph\Scripts\langgraph.exe",
|
||||||
|
"$up\anaconda3\envs\langgraph\Scripts\langgraph.exe",
|
||||||
|
"$up\miniforge3\envs\langgraph\Scripts\langgraph.exe",
|
||||||
|
"$up\AppData\Local\miniconda3\envs\langgraph\Scripts\langgraph.exe",
|
||||||
|
"C:\ProgramData\miniconda3\envs\langgraph\Scripts\langgraph.exe",
|
||||||
|
"C:\ProgramData\anaconda3\envs\langgraph\Scripts\langgraph.exe"
|
||||||
|
)
|
||||||
|
foreach ($c in $candidates) {
|
||||||
|
if (Test-Path $c) { return $c }
|
||||||
|
}
|
||||||
|
# Try PATH
|
||||||
|
$cmd = Get-Command langgraph -ErrorAction SilentlyContinue
|
||||||
|
if ($cmd) { return $cmd.Source }
|
||||||
|
# Try resolving via conda
|
||||||
|
$condaCandidates = @($env:CONDA_EXE, "C:\ProgramData\miniconda3\Scripts\conda.exe",
|
||||||
|
"$up\AppData\Local\miniconda3\Scripts\conda.exe")
|
||||||
|
foreach ($conda in $condaCandidates) {
|
||||||
|
if ($conda -and (Test-Path $conda)) {
|
||||||
|
try {
|
||||||
|
$lgPath = (& $conda run -n langgraph python -c "import shutil; print(shutil.which('langgraph') or '')" 2>$null).Trim()
|
||||||
|
if ($lgPath -and (Test-Path $lgPath)) { return $lgPath }
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw ("Cannot find langgraph.exe in the 'langgraph' conda env. " +
|
||||||
|
"Install it: conda run -n langgraph pip install 'langgraph-cli[inmem]'")
|
||||||
|
}
|
||||||
|
|
||||||
|
function Stop-ServerProcess([int]$PidValue) {
|
||||||
|
$stopped = $false
|
||||||
|
if ($PidValue -gt 0) {
|
||||||
|
$proc = Get-Process -Id $PidValue -ErrorAction SilentlyContinue
|
||||||
|
if ($proc) {
|
||||||
|
Stop-Process -Id $PidValue -Force -ErrorAction SilentlyContinue
|
||||||
|
$stopped = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Fallback: kill whoever is listening on port 2024
|
||||||
|
if (-not $stopped) {
|
||||||
|
$conn = Get-NetTCPConnection -LocalPort 2024 -State Listen -ErrorAction SilentlyContinue |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($conn) {
|
||||||
|
Stop-Process -Id $conn.OwningProcess -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Host "[dev-server] Stopped process on port 2024 (PID $($conn.OwningProcess))"
|
||||||
|
$stopped = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- command dispatch ---------------------------------------------------
|
||||||
|
|
||||||
|
switch ($Command) {
|
||||||
|
|
||||||
|
"start" {
|
||||||
|
if (Test-ServerRunning) {
|
||||||
|
$pid = Get-StoredPid
|
||||||
|
Write-Host "[dev-server] Already running (PID $pid) at $ApiUrl"
|
||||||
|
Write-Host "[dev-server] Studio: https://smith.langchain.com/studio/?baseUrl=$ApiUrl"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$lgExe = Find-LangGraphExe
|
||||||
|
Write-Host "[dev-server] Starting langgraph dev server..."
|
||||||
|
Write-Host "[dev-server] exe: $lgExe"
|
||||||
|
Write-Host "[dev-server] dir: $SkillDir"
|
||||||
|
|
||||||
|
# Remove stale logs
|
||||||
|
Remove-Item $StdoutLog -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item $StderrLog -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
$proc = Start-Process -FilePath $lgExe `
|
||||||
|
-ArgumentList "dev", "--no-browser" `
|
||||||
|
-WorkingDirectory $SkillDir `
|
||||||
|
-RedirectStandardOutput $StdoutLog `
|
||||||
|
-RedirectStandardError $StderrLog `
|
||||||
|
-WindowStyle Hidden -PassThru
|
||||||
|
|
||||||
|
if (-not $proc) {
|
||||||
|
Write-Error "[dev-server] Failed to launch process"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$proc.Id | Out-File -FilePath $PidFile -Encoding ascii -NoNewline
|
||||||
|
|
||||||
|
# Wait for server to respond
|
||||||
|
$elapsed = 0
|
||||||
|
while (-not (Test-HttpOk "$ApiUrl/ok") -and $elapsed -lt $MaxWait) {
|
||||||
|
Start-Sleep -Seconds 1
|
||||||
|
$elapsed++
|
||||||
|
if ($proc.HasExited) {
|
||||||
|
Write-Error "[dev-server] Process exited unexpectedly (code $($proc.ExitCode))"
|
||||||
|
Write-Host "--- stdout ($StdoutLog) ---"
|
||||||
|
if (Test-Path $StdoutLog) { Get-Content $StdoutLog -Tail 30 }
|
||||||
|
Write-Host "--- stderr ($StderrLog) ---"
|
||||||
|
if (Test-Path $StderrLog) { Get-Content $StderrLog -Tail 30 }
|
||||||
|
Remove-Item $PidFile -ErrorAction SilentlyContinue
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($elapsed -ge $MaxWait) {
|
||||||
|
Write-Error "[dev-server] Server did not become ready within ${MaxWait}s"
|
||||||
|
Write-Host "--- stdout ($StdoutLog) ---"
|
||||||
|
if (Test-Path $StdoutLog) { Get-Content $StdoutLog -Tail 30 }
|
||||||
|
Write-Host "--- stderr ($StderrLog) ---"
|
||||||
|
if (Test-Path $StderrLog) { Get-Content $StderrLog -Tail 30 }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "[dev-server] Ready in ${elapsed}s"
|
||||||
|
Write-Host "[dev-server] API: $ApiUrl"
|
||||||
|
Write-Host "[dev-server] Studio: https://smith.langchain.com/studio/?baseUrl=$ApiUrl"
|
||||||
|
Write-Host "[dev-server] Logs: $StdoutLog / $StderrLog"
|
||||||
|
}
|
||||||
|
|
||||||
|
"stop" {
|
||||||
|
$pid = Get-StoredPid
|
||||||
|
if ((Stop-ServerProcess -PidValue $pid)) {
|
||||||
|
Write-Host "[dev-server] Stopped (PID $pid)"
|
||||||
|
} else {
|
||||||
|
Write-Host "[dev-server] Not running (nothing to stop)"
|
||||||
|
}
|
||||||
|
Remove-Item $PidFile -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
"status" {
|
||||||
|
if (Test-ServerRunning) {
|
||||||
|
$pid = Get-StoredPid
|
||||||
|
Write-Host "[dev-server] Running (PID $pid) at $ApiUrl"
|
||||||
|
Write-Host "[dev-server] Studio: https://smith.langchain.com/studio/?baseUrl=$ApiUrl"
|
||||||
|
} else {
|
||||||
|
Write-Host "[dev-server] Not running"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default {
|
||||||
|
Write-Error "Usage: manage_dev_server.ps1 {start|stop|status}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,12 @@
|
|||||||
|
"""run_workflow.py — submit workflow runs to the local langgraph dev server.
|
||||||
|
|
||||||
|
Instead of running the graph directly in-process, every invocation sends a
|
||||||
|
run to the langgraph dev server (http://127.0.0.1:2024) via the LangGraph
|
||||||
|
SDK sync client. This lets LangGraph Studio track execution in real time.
|
||||||
|
|
||||||
|
The Codex review command is forwarded in the run's config so the server does
|
||||||
|
not need to be restarted when the command changes between projects.
|
||||||
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -5,63 +14,68 @@ import base64
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from langgraph.checkpoint.sqlite import SqliteSaver
|
SERVER_URL = "http://127.0.0.1:2024"
|
||||||
from langgraph.types import Command
|
ASSISTANT_ID = "plan_review"
|
||||||
|
THREAD_MAP_FILENAME = "workflow_threads.json"
|
||||||
from agents import AgentRunner
|
|
||||||
from workflow import build_workflow, resolve_notes_dir, resolve_work_dir
|
|
||||||
|
|
||||||
|
|
||||||
DB_FILENAME = "workflow_state.db"
|
# ── path helpers ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _resolve_work_dir(value: str) -> Path:
|
||||||
|
return Path(value or ".").resolve()
|
||||||
|
|
||||||
|
|
||||||
def parse_command(value: str | None) -> list[str] | None:
|
def _resolve_notes_dir(work_dir: Path, value: str) -> Path:
|
||||||
if not value:
|
notes_dir = Path(value or "Notes")
|
||||||
return None
|
if not notes_dir.is_absolute():
|
||||||
try:
|
notes_dir = work_dir / notes_dir
|
||||||
parsed = json.loads(value)
|
return notes_dir.resolve()
|
||||||
except json.JSONDecodeError:
|
|
||||||
# Fallback: the string may contain Windows paths with unescaped backslashes
|
|
||||||
# that make it invalid JSON. Re-escape backslashes and retry before giving up.
|
# ── thread map (task_id → server thread_id) ──────────────────────────────────
|
||||||
|
|
||||||
|
def _load_thread_map(notes_dir: Path) -> dict:
|
||||||
|
path = notes_dir / THREAD_MAP_FILENAME
|
||||||
|
if path.exists():
|
||||||
try:
|
try:
|
||||||
import re
|
return json.loads(path.read_text(encoding="utf-8"))
|
||||||
# Replace bare backslashes (not already doubled) with \\
|
except (json.JSONDecodeError, OSError):
|
||||||
re_escaped = re.sub(r'\\(?![\\"/bfnrtu])', r'\\\\', value)
|
return {}
|
||||||
parsed = json.loads(re_escaped)
|
return {}
|
||||||
except json.JSONDecodeError:
|
|
||||||
stripped = value.strip()
|
|
||||||
if stripped.startswith("[") and stripped.endswith("]"):
|
|
||||||
# Last-resort comma split; strip surrounding quotes from each item.
|
|
||||||
parsed = [item.strip().strip('"').strip("'")
|
|
||||||
for item in stripped[1:-1].split(",")]
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
if not isinstance(parsed, list) or not all(isinstance(item, str) for item in parsed):
|
|
||||||
raise argparse.ArgumentTypeError("command must be a JSON string array")
|
|
||||||
return parsed
|
|
||||||
|
|
||||||
|
|
||||||
def parse_command_b64(value: str | None) -> list[str] | None:
|
def _save_thread_map(notes_dir: Path, mapping: dict) -> None:
|
||||||
if not value:
|
path = notes_dir / THREAD_MAP_FILENAME
|
||||||
return None
|
path.write_text(json.dumps(mapping, indent=2, ensure_ascii=False), encoding="utf-8")
|
||||||
try:
|
|
||||||
decoded = base64.b64decode(value).decode("utf-8")
|
|
||||||
except Exception as exc:
|
|
||||||
raise argparse.ArgumentTypeError(f"invalid base64 command: {exc}") from exc
|
|
||||||
return parse_command(decoded)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_json_b64(value: str | None):
|
# ── interrupt extraction ──────────────────────────────────────────────────────
|
||||||
if not value:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
decoded = base64.b64decode(value).decode("utf-8")
|
|
||||||
except Exception as exc:
|
|
||||||
raise argparse.ArgumentTypeError(f"invalid base64 JSON: {exc}") from exc
|
|
||||||
return json.loads(decoded)
|
|
||||||
|
|
||||||
|
def _extract_interrupt(state: dict):
|
||||||
|
"""Return the first interrupt value from a thread-state dict, or None."""
|
||||||
|
# Primary: tasks[].interrupts (langgraph-api >= 0.5)
|
||||||
|
for task in state.get("tasks", []):
|
||||||
|
for intr in task.get("interrupts", []):
|
||||||
|
if isinstance(intr, dict):
|
||||||
|
return intr.get("value", intr)
|
||||||
|
return intr
|
||||||
|
# Fallback: __interrupt__ channel value
|
||||||
|
interrupt_list = state.get("values", {}).get("__interrupt__", [])
|
||||||
|
if interrupt_list:
|
||||||
|
intr = interrupt_list[0]
|
||||||
|
return intr.value if hasattr(intr, "value") else intr
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _is_interrupted(state: dict) -> bool:
|
||||||
|
"""Return True when the thread is paused at an interrupt."""
|
||||||
|
return bool(state.get("next")) and _extract_interrupt(state) is not None
|
||||||
|
|
||||||
|
|
||||||
|
# ── core workflow runner ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
def run_workflow(
|
def run_workflow(
|
||||||
*,
|
*,
|
||||||
@@ -69,113 +83,210 @@ def run_workflow(
|
|||||||
user_prompt: str = "",
|
user_prompt: str = "",
|
||||||
notes_dir: str = "Notes",
|
notes_dir: str = "Notes",
|
||||||
work_dir: str = "",
|
work_dir: str = "",
|
||||||
runner: AgentRunner,
|
codex_review_command: list[str],
|
||||||
thread_id: str | None = None,
|
thread_id: str | None = None,
|
||||||
resume_payload=None,
|
resume_payload=None,
|
||||||
|
timeout: int = 600,
|
||||||
|
server_url: str = SERVER_URL,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
effective_thread = thread_id or task_id
|
try:
|
||||||
resolved_work_dir = resolve_work_dir(work_dir)
|
from langgraph_sdk import get_sync_client
|
||||||
resolved_notes_dir = resolve_notes_dir(resolved_work_dir, notes_dir)
|
except ImportError:
|
||||||
|
raise ImportError(
|
||||||
|
"langgraph_sdk not installed. Run: "
|
||||||
|
"conda run -n langgraph pip install langgraph-sdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
effective_task_id = thread_id or task_id
|
||||||
|
resolved_work_dir = _resolve_work_dir(work_dir)
|
||||||
|
resolved_notes_dir = _resolve_notes_dir(resolved_work_dir, notes_dir)
|
||||||
resolved_notes_dir.mkdir(parents=True, exist_ok=True)
|
resolved_notes_dir.mkdir(parents=True, exist_ok=True)
|
||||||
db_path = resolved_notes_dir / DB_FILENAME
|
|
||||||
|
|
||||||
with SqliteSaver.from_conn_string(str(db_path)) as checkpointer:
|
# ── verify server is up ───────────────────────────────────────────────────
|
||||||
app = build_workflow(runner, checkpointer=checkpointer)
|
try:
|
||||||
config = {"configurable": {"thread_id": effective_thread}}
|
httpx.get(f"{server_url}/ok", timeout=5).raise_for_status()
|
||||||
|
except Exception as exc:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"langgraph dev server not reachable at {server_url}.\n"
|
||||||
|
f"Start it first:\n"
|
||||||
|
f" PowerShell -File C:\\Users\\bowei\\.claude\\skills\\langgraph-plan-review\\scripts\\manage_dev_server.ps1 start\n"
|
||||||
|
f"Error: {exc}"
|
||||||
|
)
|
||||||
|
|
||||||
if resume_payload is not None:
|
client = get_sync_client(url=server_url)
|
||||||
result = app.invoke(Command(resume=resume_payload), config=config)
|
|
||||||
else:
|
# ── resolve server thread_id ──────────────────────────────────────────────
|
||||||
result = app.invoke(
|
thread_map = _load_thread_map(resolved_notes_dir)
|
||||||
{
|
server_thread_id: str | None = thread_map.get(effective_task_id)
|
||||||
"task_id": task_id,
|
|
||||||
"user_prompt": user_prompt,
|
# Verify the stored thread still exists on the server (server may have restarted)
|
||||||
"notes_dir": str(resolved_notes_dir),
|
if server_thread_id:
|
||||||
"work_dir": str(resolved_work_dir),
|
try:
|
||||||
},
|
client.threads.get(server_thread_id)
|
||||||
config=config,
|
except Exception:
|
||||||
|
logging.warning(
|
||||||
|
"Stored thread %s no longer exists on server (server restarted?); "
|
||||||
|
"creating a new thread.",
|
||||||
|
server_thread_id,
|
||||||
)
|
)
|
||||||
|
server_thread_id = None
|
||||||
|
|
||||||
interrupts = result.get("__interrupt__", [])
|
if not server_thread_id:
|
||||||
if interrupts:
|
thread = client.threads.create(
|
||||||
intr = interrupts[0]
|
metadata={"task_id": effective_task_id}
|
||||||
action_required = intr.value if hasattr(intr, "value") else intr
|
)
|
||||||
|
server_thread_id = thread["thread_id"]
|
||||||
|
thread_map[effective_task_id] = server_thread_id
|
||||||
|
_save_thread_map(resolved_notes_dir, thread_map)
|
||||||
|
|
||||||
|
# ── submit run ────────────────────────────────────────────────────────────
|
||||||
|
run_config = {
|
||||||
|
"configurable": {
|
||||||
|
"codex_review_command": codex_review_command,
|
||||||
|
"codex_timeout": timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resume_payload is not None:
|
||||||
|
run = client.runs.create(
|
||||||
|
server_thread_id,
|
||||||
|
ASSISTANT_ID,
|
||||||
|
command={"resume": resume_payload},
|
||||||
|
config=run_config,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
run = client.runs.create(
|
||||||
|
server_thread_id,
|
||||||
|
ASSISTANT_ID,
|
||||||
|
input={
|
||||||
|
"task_id": effective_task_id,
|
||||||
|
"user_prompt": user_prompt,
|
||||||
|
"notes_dir": str(resolved_notes_dir),
|
||||||
|
"work_dir": str(resolved_work_dir),
|
||||||
|
},
|
||||||
|
config=run_config,
|
||||||
|
)
|
||||||
|
|
||||||
|
run_id = run["run_id"]
|
||||||
|
logging.info("Submitted run %s on thread %s", run_id, server_thread_id)
|
||||||
|
|
||||||
|
# ── wait for completion or interrupt ──────────────────────────────────────
|
||||||
|
ACTIVE = {"pending", "running"}
|
||||||
|
deadline = time.monotonic() + timeout
|
||||||
|
run_status = run.get("status", "pending")
|
||||||
|
|
||||||
|
while run_status in ACTIVE and time.monotonic() < deadline:
|
||||||
|
time.sleep(1)
|
||||||
|
try:
|
||||||
|
final_run = client.runs.get(server_thread_id, run_id)
|
||||||
|
run_status = final_run.get("status", run_status)
|
||||||
|
except Exception as exc:
|
||||||
|
logging.debug("runs.get error (will retry): %s", exc)
|
||||||
|
|
||||||
|
if run_status in ACTIVE:
|
||||||
|
raise TimeoutError(
|
||||||
|
f"Run {run_id} did not complete within {timeout}s (status={run_status})"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── detect interrupt from thread state ────────────────────────────────────
|
||||||
|
state = client.threads.get_state(server_thread_id)
|
||||||
|
|
||||||
|
if _is_interrupted(state):
|
||||||
|
interrupt_value = _extract_interrupt(state)
|
||||||
return {
|
return {
|
||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"thread_id": effective_thread,
|
"thread_id": effective_task_id,
|
||||||
"db_path": str(db_path),
|
"server_thread_id": server_thread_id,
|
||||||
"action_required": action_required,
|
"action_required": interrupt_value,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if run_status not in ("success",):
|
||||||
|
error = final_run.get("error") or run_status
|
||||||
|
raise RuntimeError(f"Run ended with status '{run_status}': {error}")
|
||||||
|
|
||||||
|
# ── extract final state ───────────────────────────────────────────────────
|
||||||
|
values = state.get("values", {})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"status": "done",
|
"status": "done",
|
||||||
"thread_id": effective_thread,
|
"thread_id": effective_task_id,
|
||||||
"db_path": str(db_path),
|
"server_thread_id": server_thread_id,
|
||||||
"task_path": result.get("task_path", ""),
|
"task_path": values.get("task_path", ""),
|
||||||
"latest_discuss_path": result.get("latest_discuss_path", ""),
|
"latest_discuss_path": values.get("latest_discuss_path", ""),
|
||||||
"work_dir": result.get("work_dir", ""),
|
"work_dir": values.get("work_dir", ""),
|
||||||
"codex_session_id": result.get("codex_session_id", ""),
|
"codex_session_id": values.get("codex_session_id", ""),
|
||||||
"has_serious_issue": result.get("has_serious_issue", False),
|
"has_serious_issue": values.get("has_serious_issue", False),
|
||||||
"should_exit": result.get("should_exit", True),
|
"should_exit": values.get("should_exit", True),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ── CLI ───────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _parse_command(value: str | None) -> list[str] | None:
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
parsed = json.loads(value)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
import re
|
||||||
|
try:
|
||||||
|
parsed = json.loads(re.sub(r'\\(?![\\"/bfnrtu])', r'\\\\', value))
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
stripped = value.strip()
|
||||||
|
if stripped.startswith("[") and stripped.endswith("]"):
|
||||||
|
parsed = [i.strip().strip('"').strip("'") for i in stripped[1:-1].split(",")]
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
if not isinstance(parsed, list) or not all(isinstance(i, str) for i in parsed):
|
||||||
|
raise argparse.ArgumentTypeError("command must be a JSON string array")
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_b64(value: str | None) -> list[str] | None:
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
return _parse_command(base64.b64decode(value).decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_json_b64(value: str | None):
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
return json.loads(base64.b64decode(value).decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Run the LangGraph Claude/Codex review workflow."
|
description="Submit a plan-review workflow run to the local langgraph dev server."
|
||||||
)
|
|
||||||
parser.add_argument("--task-id", required=True, help="Task id, for example 02")
|
|
||||||
parser.add_argument("--prompt", default="", help="User request recorded for traceability")
|
|
||||||
parser.add_argument("--notes-dir", default="Notes", help="Directory for Task/discuss files")
|
|
||||||
parser.add_argument("--work-dir", default="", help="Claude Code launch working directory")
|
|
||||||
parser.add_argument(
|
|
||||||
"--codex-review-command",
|
|
||||||
type=parse_command,
|
|
||||||
default=None,
|
|
||||||
help="JSON argv for Codex review step.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--codex-review-command-b64",
|
|
||||||
type=parse_command_b64,
|
|
||||||
default=None,
|
|
||||||
help="Base64-encoded JSON argv for Codex review step.",
|
|
||||||
)
|
)
|
||||||
|
parser.add_argument("--task-id", required=True)
|
||||||
|
parser.add_argument("--prompt", default="")
|
||||||
|
parser.add_argument("--notes-dir", default="Notes")
|
||||||
|
parser.add_argument("--work-dir", default="")
|
||||||
|
parser.add_argument("--codex-review-command", type=_parse_command, default=None)
|
||||||
|
parser.add_argument("--codex-review-command-b64", type=_parse_b64, default=None)
|
||||||
parser.add_argument("--timeout", type=int, default=600)
|
parser.add_argument("--timeout", type=int, default=600)
|
||||||
parser.add_argument(
|
parser.add_argument("--thread-id", default="")
|
||||||
"--thread-id",
|
parser.add_argument("--resume", default="")
|
||||||
default="",
|
parser.add_argument("--resume-b64", default="")
|
||||||
help="Thread ID for resuming an existing run (defaults to task-id)",
|
parser.add_argument("--server-url", default=SERVER_URL)
|
||||||
)
|
parser.add_argument("--verbose", "-v", action="store_true")
|
||||||
parser.add_argument(
|
|
||||||
"--resume",
|
|
||||||
default="",
|
|
||||||
help=(
|
|
||||||
"JSON payload for Command(resume=...). "
|
|
||||||
"Use true for plan/revise/rejection steps; {\"accepts\":true} for decision step."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--resume-b64",
|
|
||||||
default="",
|
|
||||||
help="Base64-encoded JSON payload for Command(resume=...).",
|
|
||||||
)
|
|
||||||
parser.add_argument("--verbose", "-v", action="store_true", help="Enable debug logging to stderr.")
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
logging.basicConfig(level=logging.DEBUG, stream=sys.stderr,
|
logging.basicConfig(
|
||||||
format="%(asctime)s %(levelname)s %(message)s")
|
level=logging.DEBUG, stream=sys.stderr,
|
||||||
codex_review_command = args.codex_review_command_b64 or args.codex_review_command
|
format="%(asctime)s %(levelname)s %(message)s",
|
||||||
if not codex_review_command:
|
)
|
||||||
|
|
||||||
|
codex_cmd = args.codex_review_command_b64 or args.codex_review_command
|
||||||
|
if not codex_cmd:
|
||||||
parser.error("--codex-review-command or --codex-review-command-b64 is required")
|
parser.error("--codex-review-command or --codex-review-command-b64 is required")
|
||||||
|
|
||||||
runner = AgentRunner(
|
resume_payload = (
|
||||||
codex_review_command=codex_review_command,
|
_parse_json_b64(args.resume_b64) if args.resume_b64
|
||||||
timeout=args.timeout,
|
else (json.loads(args.resume) if args.resume else None)
|
||||||
)
|
|
||||||
# resume=True tells LangGraph to resume from the last interrupt without a specific value.
|
|
||||||
# Callers should pass 'true' (JSON boolean) for plan/revise/rejection steps.
|
|
||||||
resume_payload = parse_json_b64(args.resume_b64) if args.resume_b64 else (
|
|
||||||
json.loads(args.resume) if args.resume else None
|
|
||||||
)
|
)
|
||||||
|
|
||||||
output = run_workflow(
|
output = run_workflow(
|
||||||
@@ -183,9 +294,11 @@ def main() -> int:
|
|||||||
user_prompt=args.prompt,
|
user_prompt=args.prompt,
|
||||||
notes_dir=args.notes_dir,
|
notes_dir=args.notes_dir,
|
||||||
work_dir=args.work_dir,
|
work_dir=args.work_dir,
|
||||||
runner=runner,
|
codex_review_command=codex_cmd,
|
||||||
thread_id=args.thread_id or None,
|
thread_id=args.thread_id or None,
|
||||||
resume_payload=resume_payload,
|
resume_payload=resume_payload,
|
||||||
|
timeout=args.timeout,
|
||||||
|
server_url=args.server_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
print(json.dumps(output, ensure_ascii=False, indent=2))
|
print(json.dumps(output, ensure_ascii=False, indent=2))
|
||||||
|
|||||||
@@ -1,15 +1,30 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
|
from langchain_core.runnables import RunnableConfig
|
||||||
from langgraph.graph import END, START, StateGraph
|
from langgraph.graph import END, START, StateGraph
|
||||||
from langgraph.types import interrupt
|
from langgraph.types import interrupt
|
||||||
|
|
||||||
from agents import AgentRunner
|
from agents import AgentRunner
|
||||||
|
|
||||||
|
|
||||||
|
def _default_review_cmd() -> list[str]:
|
||||||
|
"""Return the real review_task.py command using the current interpreter."""
|
||||||
|
review_py = Path(__file__).parent / "review_task.py"
|
||||||
|
return [
|
||||||
|
sys.executable,
|
||||||
|
str(review_py),
|
||||||
|
"$task_path",
|
||||||
|
"$latest_discuss_path",
|
||||||
|
"$codex_session_id",
|
||||||
|
"$work_dir",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class WorkflowState(TypedDict):
|
class WorkflowState(TypedDict):
|
||||||
task_id: str
|
task_id: str
|
||||||
user_prompt: str
|
user_prompt: str
|
||||||
@@ -129,7 +144,7 @@ def values_from_state(state: WorkflowState) -> dict[str, str]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def build_workflow(runner: AgentRunner, *, checkpointer=None):
|
def build_workflow(*, checkpointer=None):
|
||||||
graph = StateGraph(WorkflowState)
|
graph = StateGraph(WorkflowState)
|
||||||
|
|
||||||
def initialize(state: WorkflowState) -> WorkflowState:
|
def initialize(state: WorkflowState) -> WorkflowState:
|
||||||
@@ -163,7 +178,11 @@ def build_workflow(runner: AgentRunner, *, checkpointer=None):
|
|||||||
validate_task_file(state["task_path"])
|
validate_task_file(state["task_path"])
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def review(state: WorkflowState) -> WorkflowState:
|
def review(state: WorkflowState, config: RunnableConfig) -> WorkflowState:
|
||||||
|
cfg = config.get("configurable", {})
|
||||||
|
codex_cmd: list[str] = cfg.get("codex_review_command") or _default_review_cmd()
|
||||||
|
timeout: int = int(cfg.get("codex_timeout", 600))
|
||||||
|
runner = AgentRunner(codex_review_command=codex_cmd, timeout=timeout)
|
||||||
result = runner.review(values_from_state(state))
|
result = runner.review(values_from_state(state))
|
||||||
updates = {
|
updates = {
|
||||||
**state,
|
**state,
|
||||||
|
|||||||
@@ -114,16 +114,40 @@ conda env list | grep "^langgraph"
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 2b. 安装所需 Python 包
|
### 2b. 安装所需 Python 包
|
||||||
|
|
||||||
|
一条命令安装全部依赖(包含 `/langgraph-plan-review` skill 所需包):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
conda run -n langgraph pip install langgraph langchain-anthropic pandas
|
conda run -n langgraph pip install \
|
||||||
|
langgraph \
|
||||||
|
langchain-anthropic \
|
||||||
|
pandas \
|
||||||
|
"langgraph-cli[inmem]" \
|
||||||
|
langgraph-sdk \
|
||||||
|
httpx \
|
||||||
|
langgraph-checkpoint-sqlite
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **为什么这些包?**
|
||||||
|
> | 包 | 用途 |
|
||||||
|
> |---|---|
|
||||||
|
> | `langgraph` | 核心图执行引擎 |
|
||||||
|
> | `langchain-anthropic` | Anthropic LLM 集成 |
|
||||||
|
> | `pandas` | 数据处理 |
|
||||||
|
> | `langgraph-cli[inmem]` | 提供 `langgraph dev` 服务器命令(`/langgraph-plan-review` 必需) |
|
||||||
|
> | `langgraph-sdk` | `run_workflow.py` 通过 SDK 向 dev server 提交 run(`/langgraph-plan-review` 必需) |
|
||||||
|
> | `httpx` | `run_workflow.py` 健康检查(`/langgraph-plan-review` 必需) |
|
||||||
|
> | `langgraph-checkpoint-sqlite` | dev server 状态持久化(`/langgraph-plan-review` 必需) |
|
||||||
|
|
||||||
验证安装:
|
验证安装:
|
||||||
```bash
|
```bash
|
||||||
conda run -n langgraph python -c "import langgraph; print('LangGraph OK:', langgraph.__version__)"
|
conda run -n langgraph python -c "import langgraph; print('LangGraph OK:', langgraph.__version__)"
|
||||||
|
conda run -n langgraph python -c "from langgraph_sdk import get_sync_client; print('langgraph-sdk OK')"
|
||||||
|
conda run -n langgraph python -c "import httpx; print('httpx OK')"
|
||||||
|
conda run -n langgraph langgraph --version
|
||||||
```
|
```
|
||||||
|
|
||||||
若导入失败,报告错误并停止,请用户检查网络或 pip 源配置。
|
若任一导入失败,报告错误并停止,请用户检查网络或 pip 源配置。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -273,7 +297,15 @@ conda run -n langgraph vivado -version
|
|||||||
"VIVADO_BIN_PATH": "<Step 3 确认的 Windows 格式路径>",
|
"VIVADO_BIN_PATH": "<Step 3 确认的 Windows 格式路径>",
|
||||||
"conda_env": "langgraph",
|
"conda_env": "langgraph",
|
||||||
"python_version": "3.11",
|
"python_version": "3.11",
|
||||||
"packages": ["langgraph", "langchain-anthropic", "pandas"]
|
"packages": [
|
||||||
|
"langgraph",
|
||||||
|
"langchain-anthropic",
|
||||||
|
"pandas",
|
||||||
|
"langgraph-cli[inmem]",
|
||||||
|
"langgraph-sdk",
|
||||||
|
"httpx",
|
||||||
|
"langgraph-checkpoint-sqlite"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -302,6 +334,55 @@ def test_langgraph():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def test_langgraph_plan_review_deps():
|
||||||
|
"""Verify packages required by the /langgraph-plan-review skill."""
|
||||||
|
ok = True
|
||||||
|
checks = [
|
||||||
|
("langgraph_sdk", "from langgraph_sdk import get_sync_client"),
|
||||||
|
("httpx", "import httpx"),
|
||||||
|
("langgraph_checkpoint_sqlite", "from langgraph.checkpoint.sqlite import SqliteSaver"),
|
||||||
|
]
|
||||||
|
for name, stmt in checks:
|
||||||
|
try:
|
||||||
|
exec(stmt)
|
||||||
|
import importlib.metadata
|
||||||
|
try:
|
||||||
|
ver = importlib.metadata.version(name.replace("_", "-"))
|
||||||
|
print(f"[OK] {name} OK (version: {ver})")
|
||||||
|
except importlib.metadata.PackageNotFoundError:
|
||||||
|
print(f"[OK] {name} importable")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[FAIL] {name}: {e}")
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
# Check langgraph dev CLI
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, "-m", "langgraph", "--version"],
|
||||||
|
capture_output=True, text=True, timeout=10
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
print(f"[OK] langgraph-cli OK: {result.stdout.strip() or result.stderr.strip()}")
|
||||||
|
else:
|
||||||
|
# Try via entry point script
|
||||||
|
import shutil
|
||||||
|
lg = shutil.which("langgraph")
|
||||||
|
if lg:
|
||||||
|
r2 = subprocess.run([lg, "--version"], capture_output=True, text=True, timeout=10)
|
||||||
|
if r2.returncode == 0:
|
||||||
|
print(f"[OK] langgraph dev CLI OK: {r2.stdout.strip()}")
|
||||||
|
else:
|
||||||
|
print(f"[WARN] langgraph CLI found but --version failed: {r2.stderr.strip()}")
|
||||||
|
else:
|
||||||
|
print("[FAIL] langgraph CLI not found on PATH — install: pip install 'langgraph-cli[inmem]'")
|
||||||
|
ok = False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[FAIL] langgraph CLI check error: {e}")
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
return ok
|
||||||
|
|
||||||
|
|
||||||
def test_vivado():
|
def test_vivado():
|
||||||
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "env_config.json")
|
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "env_config.json")
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
@@ -342,10 +423,14 @@ if __name__ == "__main__":
|
|||||||
print("LangGraph + Vivado smoke test")
|
print("LangGraph + Vivado smoke test")
|
||||||
print("=" * 50)
|
print("=" * 50)
|
||||||
ok_lg = test_langgraph()
|
ok_lg = test_langgraph()
|
||||||
|
print("-" * 50)
|
||||||
|
print("langgraph-plan-review deps:")
|
||||||
|
ok_pr = test_langgraph_plan_review_deps()
|
||||||
|
print("-" * 50)
|
||||||
ok_vv = test_vivado()
|
ok_vv = test_vivado()
|
||||||
print("=" * 50)
|
print("=" * 50)
|
||||||
if ok_lg and ok_vv:
|
if ok_lg and ok_pr and ok_vv:
|
||||||
print("[PASS] All checks passed. LangGraph + Vivado pipeline OK!")
|
print("[PASS] All checks passed. LangGraph + Vivado + plan-review pipeline OK!")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
else:
|
else:
|
||||||
print("[FAIL] Some checks failed. See above.")
|
print("[FAIL] Some checks failed. See above.")
|
||||||
|
|||||||
Reference in New Issue
Block a user