29
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OpenSpec — 仕様駆動開発のナレッジをいい感じに循環させる(Amplify + AgentCoreハンズオン付き)

29
Last updated at Posted at 2026-03-09

前書き

コーディングエージェントに仕様駆動開発をさせると、機能開発は早いけど 「なぜその設計にしたのか」「どんな要件だったのか」が消えていく 問題、感じたことありませんか:frowning2::point_up_tone1:

C3CE466B-FDFD-45D5-B9E2-56CAD91C781E_4_5005_c.jpeg

OpenSpec を使うと、仕様・設計判断など、簡単にプロジェクトのナレッジとして残すことができます。

この記事は 2 部 構成です。
前半では OpenSpec の仕組みとナレッジ蓄積の考え方 について紹介します。

openspec_bg.png

後半では PPTX レビューアプリ(Amplify + AgentCore)を作るハンズオンを通じて実際のワークフローを体験します。

63725C24-6733-451D-B274-8EAA9549EA6F.jpeg

OpenSpec とは

コーディングエージェントのための仕様駆動開発フレームワークです。
企画・設計・仕様・タスクの生成を行う仕様駆動の基本機能に加え、完了した仕様をプロジェクトのナレッジとして蓄積し、次の開発サイクルで自動参照させる仕組みが組み込まれています。

slide-knowledge-loop.jpeg

Spec Kit / Kiro / cc-sdd など他の仕様駆動ツールも企画〜タスク生成の流れは持ちますが、完了後のナレッジの再利用する仕組みは独自の実装が必要です。
OpenSpec はこの蓄積→参照のループをデフォルトで内蔵している点が異なります。

インストールはシンプルで、Node.jsがインストール済みであれば、グローバルにインストールしたあと、プロジェクト配下で初期化するだけです。

npm install -g @fission-ai/openspec@latest

cd your-project
openspec init

OpenSpec の操作はすべて エージェントスキルとして提供されます。
Claude CodeCodexAntigravity など、スキルを読み込めるコーディングエージェントであれば何でも利用できます。

ナレッジ蓄積のワークフロー

OpenSpecで主に使うコマンドは下記4つです。

コマンド 役割
/opsx:new 変更を開始する(企画の起点)
/opsx:continue 次のアーティファクトを生成する(proposal → design → specs → tasks)
/opsx:apply タスクをもとにコードを実装する
/opsx:archive 完了した仕様をナレッジとして蓄積する

/opsx:new で変更を開始し、/opsx:continueを4回実行することで proposal → design → specs → tasks の 4 つのアーティファクトを順に生成します。

/opsx:apply で実装し、/opsx:archive で完了した仕様をナレッジとして蓄積します。

slide-workflow.jpeg

このワークフローでは、下記のディレクトリ構造でファイルを管理してます。Gitのブランチ戦略に例えると、changes/<task-name>/ が作業ブランチ、specs/ が mainブランチに相当します。

/opsx:new を実行すると、作業ディレクトリ changes/<task-name>/ が作成されます。
/opsx:archive を実行すると、作業中の内容が archive/ に保存され、仕様だけが specs/ にコピーされます。

3465FC11-F6D3-485E-9CF5-BB9D63AB855B.jpeg

specs/の内容は次回の作業で自動的に参照されます:relaxed::point_up_tone1:

ハンズオン: PPTX レビューアプリを作る

ここからは実際に手を動かして、OpenSpec ワークフローを体験していきます、完成してるリポジトリはこちらです。

技術スタック

カテゴリ 技術
フロントエンド React 19 + Vite 8 + Tailwind CSS 4
認証 Amazon Cognito (Amplify Auth)
PPTX 解析 Lambda (Docker, ARM64) + API Gateway V2
AI エージェント Amazon Bedrock AgentCore + AI SDK
IaC AWS CDK (Amplify Gen2 経由)
AI 開発ツール Claude Code + OpenSpec

事前準備

AWS アカウントの準備

  • AWS アカウントを作成

  • Docker Desktop をインストール(CDK でのコンテナビルドに必要)

Docker は、Lambda のコンテナイメージビルドや Amplify サンドボックスでのローカル開発時に使用します。

Claude CodeとOpenSpecのインストール

// openspecインストール
npm install -g @fission-ai/openspec@latest

1. プロジェクト初期化

  • プロジェクトディレクトリを作成
mkdir ai-manager && cd ai-manager

OpenSpec の初期化

  • OpenSpec を初期化
openspec init

ツール選択画面が表示されます。矢印キーで移動、Enter で追加、Tab で決定です。Claude Code を選択してください。

9891A9BE-2845-4E61-BC01-41C240DA491E.jpeg

openspec/ ディレクトリが作成されます。

openspec/
├── config.yaml          # プロジェクト設定
├── changes/             # 変更管理
│   └── archive/         # アーカイブ済み
└── specs/               # 仕様ファイル
  • config.yaml にプロジェクトのコンテキスト(技術スタック等)を記述
schema: spec-driven

context: |
  Tech stack: TypeScript, React 19, Vite 8, AWS Amplify Gen2, AWS CDK
  Backend: Lambda (Docker, ARM64, Node 22), API Gateway V2
  Frontend: Tailwind CSS 4, Radix UI, Lucide React, CVA
  認証: Amazon Cognito (Amplify Auth) + JWT認証
  パスエイリアス: @/* -> src/*
  言語: 日本語でドキュメント作成

MCP サーバーの設定

  • プロジェクタ配下に.mcp.jsonを作ってに以下の MCP サーバーを追加
MCP サーバー 用途
aws-knowledge-mcp-server AWS ドキュメントの検索・閲覧
awslabs.aws-api-mcp-server AWS API の確認
awslabs.cdk-mcp-server CDK 関連の操作支援
drawio Draw.io ファイルの読み書き
.mcp.json(長いので折りたたみ)
.mcp.json
{
    "mcpServers": {
        "aws-knowledge-mcp-server": {
            "command": "uvx",
            "args": [
                "fastmcp",
                "run",
                "https://knowledge-mcp.global.api.aws"
            ]
        },
        "awslabs.aws-api-mcp-server": {
            "command": "uvx",
            "args": [
                "awslabs.aws-api-mcp-server@latest"
            ],
            "env": {
                "AWS_REGION": "us-east-1"
            },
            "disabled": false,
            "autoApprove": []
        },
        "awslabs.aws-iac-mcp-server": {
          "command": "uvx",
          "args": ["awslabs.aws-iac-mcp-server@latest"],
          "env": {
            "AWS_PROFILE": "your-named-profile",
            "FASTMCP_LOG_LEVEL": "ERROR"
          },
          "disabled": false,
          "autoApprove": []
        },
        "drawio": {
            "command": "npx",
            "args": [
                "@drawio/mcp"
            ]
        }
    }
}

MCP サーバーを設定しておくと、Claude Code が AWS のドキュメントを直接検索してくれるので、「AgentCore の設定方法って何だっけ?」と聞くだけで最新ドキュメントを引っ張ってきてくれます。

スキルの追加

  • Vercel のフロントエンド開発ベストプラクティススキルも追加

npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-react-best-practices

24993BF7-824A-43D7-9EB8-C2256B8F7DA1.jpeg

スキルのバージョンは skills-lock.json でハッシュ固定されるので、チームメンバーが同じバージョンのスキルを使えます。再現性が大事!

Vite + React + Amplify の初期化

# Vite + React (TypeScript) テンプレート
npm create vite@latest . -- --template react-ts

すでにファイルが存在するため、Ignore files and continueを選びましょう。

E70B538D-D403-40A3-A84A-7249E6CB702C_4_5005_c.jpeg

# Amplify Gen2 バックエンド
npm create amplify@latest

# 依存関係インストール
npm install

これでおおよそ以下の構造ができあがります。

/ai-manager
├── amplify/           # バックエンド + インフラ
│   ├── auth/
│   │   └── resource.ts  # 認証設定(Cognito)
│   └── backend.ts       # バックエンド定義
├── src/               # フロントエンド
│   ├── App.tsx
│   ├── main.tsx
│   └── index.css
└── 各種設定ファイル

2. UI 基盤 + 認証

Tailwind CSS 4 + UI コンポーネント

  • 必要なパッケージをインストール
npm install tailwindcss @tailwindcss/vite class-variance-authority clsx tailwind-merge @radix-ui/react-slot lucide-react

Tailwind CSS 4 は Vite プラグインとして動作するので、tailwind.config.js や PostCSS 設定が不要になりました。セットアップが超シンプル!

  • Vite 設定に Tailwind プラグインと @ パスエイリアスを追加
vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
import path from 'path'

export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
})
  • tsconfig.app.json にもパスエイリアスを追加(エディタの型解決用)
tsconfig.app.json
{
  "compilerOptions": {
    // ...既存の設定に追加
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["./src/*"]
    }
  }
}
  • index.css を下記のように書き直してください — Tailwind CSS 4 では @import 'tailwindcss' だけでOK。テーマカラーも定義
src/index.css
@import 'tailwindcss';

@theme {
  --color-teal: #0D6E6E;
  --color-teal-light: #E8F4F4;
  --color-warning: #E07B54;
  --color-warning-light: #FFF3E0;
  --color-error: #D32F2F;
  --color-error-light: #FFEBEE;
  --color-text-primary: #1A1A1A;
  --color-text-secondary: #666666;
  --color-text-muted: #888888;
  --color-border: #E5E5E5;
  --color-bg-light: #F5F5F5;
}
  • cn ヘルパーを作成(clsx + tailwind-merge でクラス名を安全に結合)
src/lib/utils.ts
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
  • shadcn/ui のパターンを参考に UI コンポーネントを手作り
src/components/ui/button.tsx
src/components/ui/button.tsx
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import type { ButtonHTMLAttributes } from 'react'
import { cn } from '@/lib/utils'

const buttonVariants = cva(
  'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
  {
    variants: {
      variant: {
        default: 'bg-teal text-white hover:bg-teal/90',
        destructive: 'bg-red-600 text-white hover:bg-red-700',
        outline: 'border border-gray-300 bg-white hover:bg-gray-50 text-gray-700',
        secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
        ghost: 'hover:bg-gray-100 text-gray-700',
        link: 'text-blue-600 underline-offset-4 hover:underline',
      },
      size: {
        default: 'h-10 px-4 py-2',
        sm: 'h-9 rounded-md px-3',
        lg: 'h-11 rounded-md px-8',
        icon: 'h-10 w-10',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
)

export interface ButtonProps
  extends ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean
}

export function Button({
  className,
  variant,
  size,
  asChild = false,
  ...props
}: ButtonProps) {
  const Comp = asChild ? Slot : 'button'
  return (
    <Comp
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  )
}

CVA(class-variance-authority)を使うと、コンポーネントのバリアント(default, outline, ghost 等)を型安全に定義できます。shadcn/ui が採用しているのと同じパターンを手書きで再現しています。

Amplify 認証接続

  • Amplify UI をインストール
npm install @aws-amplify/ui-react

amplify_outputs.jsonnpx ampx sandbox 実行後に生成されるため、デプロイ前でもビルドが通るように スタブファイル を用意します。

amplify_outputs.stub.json
{
  "version": "1.4",
  "auth": {
    "user_pool_id": "PLACEHOLDER",
    "aws_region": "ap-northeast-1",
    "user_pool_client_id": "PLACEHOLDER",
    "identity_pool_id": "PLACEHOLDER",
    "standard_required_attributes": ["email"],
    "username_attributes": ["email"],
    "user_verification_types": ["email"],
    "password_policy": {
      "min_length": 8,
      "require_lowercase": true,
      "require_uppercase": true,
      "require_numbers": true,
      "require_symbols": true
    }
  }
}
  • エントリーポイント — import.meta.glob で動的に読み込み、ファイルが無ければスタブにフォールバック
src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { Authenticator } from '@aws-amplify/ui-react'
import { Amplify } from 'aws-amplify'
import '@aws-amplify/ui-react/styles.css'
import './index.css'
import App from './App.tsx'
import stubConfig from '../amplify_outputs.stub.json'

// amplify_outputs.json は npx ampx sandbox 実行後に生成される
// 未デプロイ時は stub を使用
const realConfigs = import.meta.glob('../amplify_outputs.json', { eager: true }) as Record<string, { default: typeof stubConfig }>
const config = Object.values(realConfigs)[0]?.default ?? stubConfig

Amplify.configure(config)

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <Authenticator>
      <App />
    </Authenticator>
  </StrictMode>,
)

import.meta.glob + スタブのパターンは、Amplify バックエンド未デプロイでもフロントエンドのビルド・起動ができるので、CI/CD やチーム開発で重宝します。

3. 仕様駆動で PPTX レビュー機能を実装

ここからが OpenSpec の本領発揮パートです。仕様を先に定義 → 実装 の流れで進めます。
実際に生成された仕様ファイルはモデルや追加情報によって内容が変わるため、成果物とサンプルを確認しながら進めてください:relaxed::point_up_tone1:

OpenSpec で仕様を定義

  • /opsx:new pptx-upload-and-parse で変更を開始、/opsx:new存在しない場合/opsx:proposeで代用できます。
    • openspec/changes/pptx-upload-and-parseフォルダが作成されます。
  • /opsx:continue を繰り返して Proposal → Design → Specs → Tasks まで進める
openspec/changes/pptx-upload-and-parse/proposal.md
openspec/changes/pptx-upload-and-parse/proposal.md
## Why

社内のプレゼン資料(PPTX)のレビュープロセスが属人的で時間がかかっている。AI エージェントにPPTX資料の内容・構成をレビューさせることで、資料の品質を素早く向上させたい。そのために、PPTX ファイルをアップロードして中身を解析し、AI が理解できる形式に変換する仕組みが必要。

## What Changes

- PPTX ファイルをアップロードする UI を追加
- バックエンドで PPTX を解析し、スライドごとのテキスト・構造を抽出
- 抽出したテキストデータを AI エージェントに渡してレビューコメントを生成
- レビュー結果をユーザーに表示

## Capabilities

### New Capabilities

- `pptx-upload`: PPTX ファイルのアップロード UI
- `pptx-parse`: PPTX ファイルの解析(テキスト・構造の抽出)
- `pptx-review`: AI エージェントによる資料の内容・構成レビュー

### Modified Capabilities

(なし)

## Impact

- **フロントエンド**: ファイルアップロード用の新規ページ/コンポーネントを追加
- **バックエンド**: PPTX 解析用の Lambda 関数を新規作成
- **インフラ**: Lambda(解析処理)の追加
- **依存ライブラリ**: PPTX 解析ライブラリ(例: pptx-parser, officegen 等)の導入
- **API**: アップロード・解析・レビュー用のエンドポイントを追加

Design作成する際に、外部のナレッジを取り込んだ方が精度が高くなりますが、今回は用意したdesign.mdを使いましょう。

 /opsx:continue AIエージェントはAgentCoreにデプロイしたい、デプロイ方法必要に応じてmcp使って調査してください 
openspec/changes/pptx-upload-and-parse/design.md
openspec/changes/pptx-upload-and-parse/design.md
## Context

本プロジェクトは AWS Amplify Gen2 ベースの React アプリケーションで、現在は Cognito 認証のみが実装されている。PPTX ファイルをアップロードし、AI エージェントにレビューさせる機能を新規追加する。

現状のフロントエンドには `App.tsx` にアップロード用のプレースホルダー UI(破線ボーダーの領域)が既に存在する。バックエンドには Lambda 関数や API エンドポイントはまだ存在しない。

## Goals / Non-Goals

**Goals:**

- PPTX ファイルをブラウザからアップロードし、スライドごとのテキスト・構造を抽出できる
- 抽出結果を AI に渡してレビューコメントを生成し、ユーザーに表示する
- 認証済みユーザーのみがアップロード・レビュー機能を利用できる

**Non-Goals:**

- PPTX 内の画像・グラフ・アニメーションの解析(テキストと構造のみ対象)
- PPTX ファイルの編集・再生成
- レビュー結果の永続化・履歴管理(初期リリースでは都度実行)
- PPTX 以外のファイル形式(PDF、DOCX 等)への対応

## Decisions

### 1. ファイルアップロード方式: Lambda 直接アップロード

**選択**: API Gateway + Lambda にファイルを直接 POST する

**理由**: S3 presigned URL 方式(S3 にアップロード → S3 イベントで Lambda 起動)も検討したが、以下の理由で直接アップロードを採用する。

- PPTX ファイルは通常数 MB 程度で、API Gateway のペイロード上限(10MB)に収まる
- S3 バケットの追加管理が不要でインフラがシンプル
- アップロード → 解析 → レスポンスを同期的に処理でき、フロントエンドの実装が容易
- ファイルの永続化は Non-Goal のため、一時保存の仕組みが不要

**代替案**: S3 presigned URL + Lambda トリガー。大容量ファイルや永続化が必要になった場合はこちらに移行する。

### 2. PPTX 解析ライブラリ: pptx-composer ではなく直接 XML 解析

**選択**: `xml2js`(または `fast-xml-parser`)を使って PPTX(ZIP 内の XML)を直接パースする

**理由**:

- PPTX は ZIP 形式で、中身は XML ファイル群(`ppt/slides/slide*.xml`- テキスト抽出のみが目的なので、専用ライブラリのオーバーヘッドは不要
- `jszip` で ZIP を展開し、XML からテキストノード(`<a:t>` タグ)を抽出するシンプルな実装で十分
- Docker Lambda(Node 22)で動作するため、ネイティブ依存のないピュア JS ライブラリが望ましい

**代替案**: `python-pptx`(Python Lambda)。より高機能だが、技術スタックが TypeScript に統一されているため不採用。

### 3. AI レビュー: Bedrock AgentCore Runtime

**選択**: Bedrock AgentCore Runtime でレビューエージェントを構築する(diff_workflow プロジェクトと同じパターン)

**構成**:

- `amplify/agent/` ディレクトリに AgentCore アプリケーションを配置
- `BedrockAgentCoreApp``bedrock-agentcore` SDK)でランタイムを構成
- Vercel AI SDK(`ai` + `@ai-sdk/amazon-bedrock`)で Claude モデルを呼び出し
- Docker コンテナ(Node 22)として AgentCore にデプロイ
- CDK で `@aws-cdk/aws-bedrock-agentcore-alpha``Runtime` コンストラクトを使用
- Cognito 認証と連携(`RuntimeAuthorizerConfiguration.usingCognito`**理由**:

- AgentCore Runtime はエージェント実行に最適化されたマネージドサービスで、Lambda のタイムアウト制約(最大 15 分)を気にせずストリーミング応答が可能
- Cognito 認証を直接統合でき、既存の認証基盤をそのまま活用
- ツール呼び出し(`ToolLoopAgent`)やストリーミングレスポンスが標準サポート
- 同プロジェクト内の diff_workflow で実績があり、パターンが確立されている

**代替案**: Lambda から直接 Bedrock API を呼び出す。シンプルだが、ストリーミング応答やツールループが扱いにくく、AgentCore の方がエージェント用途に適している。

### 4. API 設計: Lambda(解析)+ AgentCore(レビュー)の 2 系統

**選択**: PPTX 解析は API Gateway + Lambda、AI レビューは AgentCore Runtime エンドポイント

| 処理 | エンドポイント | 基盤 | 説明 |
|---|---|---|---|
| PPTX 解析 | API Gateway `/api/pptx/parse` | Lambda | PPTX ファイルを受け取り、スライドごとのテキスト・構造を JSON で返す |
| AI レビュー | AgentCore Runtime URL | AgentCore | 解析済みテキストを受け取り、ストリーミングでレビューコメントを返す |

**理由**:

- PPTX 解析は単純な変換処理のため Lambda が適切(短時間で完了、同期レスポンス)
- AI レビューはストリーミング応答が必要で、AgentCore Runtime が最適
- 解析結果をユーザーに先に表示し、その後レビューを実行する UX が可能
- レビューのみ再実行したい場合に解析をスキップできる
- フロントエンドからは Cognito トークンで両方のエンドポイントに認証アクセス

**代替案**: 1 エンドポイントで解析 → レビューを一括処理。シンプルだが、レビュー生成に時間がかかるため UX が悪化する。

### 5. バックエンド構成: Lambda(解析)+ AgentCore Runtime(レビュー)

**選択**: 2 つの異なる実行基盤を用途に応じて使い分け

- `amplify/functions/pptx-parse/`: PPTX 解析用 Lambda(Docker, ARM64, Node 22, jszip + xml2js)
- `amplify/agent/`: AI レビュー用 AgentCore Runtime(Docker, Node 22, bedrock-agentcore + ai SDK)

**理由**:

- PPTX 解析は短時間で完了する同期処理のため Lambda が適切
- AI レビューはストリーミング・ツールループが必要なため AgentCore Runtime が適切
- CDK カスタマイズで `createAgentCoreRuntime()` を定義し、Cognito 認証・Bedrock 権限を一括設定
- `deploy-time-build` で ARM64 Docker イメージを CodeBuild でビルド

### 6. フロントエンド: 既存 App.tsx のプレースホルダーを拡張

**選択**: `App.tsx` のプレースホルダー部分をアップロード UI に置き換え、結果表示はモーダルまたは同一ページ内に展開

**理由**:

- 既にプレースホルダー UI が存在するため、自然な拡張ポイント
- 初期リリースではシングルページで完結させ、ルーティングは追加しない
- 既存の Card、Button、Alert、Spinner コンポーネントを活用

## Risks / Trade-offs

- **API Gateway 10MB 制限** → 大きな PPTX はアップロードできない。ユーザーにファイルサイズ上限を表示し、超過時はエラーメッセージを返す。将来的には S3 presigned URL 方式に移行可能。

- **Lambda コールドスタート** → Docker Lambda(解析用)は初回起動が遅い(数秒)。Spinner で待機状態を表示し、UX への影響を軽減。

- **AgentCore レスポンス時間** → AI レビュー生成に 10〜30 秒程度かかる可能性がある。AgentCore のストリーミング応答を活用し、フロントエンドでリアルタイムにテキストを表示することで体感待ち時間を軽減する。

- **PPTX パース精度** → 直接 XML 解析のため、複雑なレイアウト(SmartArt、グループ化されたテキストボックス等)ではテキスト抽出漏れの可能性がある。主要なテキスト要素(タイトル、本文、ノート)を優先的に抽出する。

- **Bedrock モデルのリージョン制約** → Claude モデルが利用可能なリージョンが限定される。AgentCore Runtime 内で `@ai-sdk/amazon-bedrock` のリージョン設定を適切に行う必要がある。

- **AgentCore の CDK アルファ版**`@aws-cdk/aws-bedrock-agentcore-alpha` はアルファ版のため、API が変更される可能性がある。diff_workflow での実績があるバージョンに固定して利用する。

## Open Questions

- Bedrock で使用する Claude モデルのバージョン(Haiku / Sonnet)をどちらにするか?コストと品質のバランスで決定が必要。
- レビュー観点のカスタマイズ(構成チェック、誤字脱字、表現改善など)をユーザーが選べるようにするか?初期リリースでは固定で良いか。

今回は以下の 3 つの仕様(Spec)が生成されました。

スペック 内容
pptx-upload ファイル選択 UI、形式バリデーション、10MB サイズ制限、認証必須
pptx-parse PPTX 受信・展開、スライドテキスト抽出、タイトル/本文/ノート分類、JSON レスポンス
pptx-review AgentCore によるレビュー実行、SSE ストリーミング、4 観点(構成・明確さ・情報量・表現)、スライド別コメント
openspec/changes/pptx-upload-and-parse/specs/pptx-upload/spec.md
openspec/changes/pptx-upload-and-parse/specs/pptx-upload/spec.md
## ADDED Requirements

### Requirement: ファイル選択 UI
システムは PPTX ファイルを選択するためのドラッグ&ドロップ対応のアップロードエリアを提供しなければならない(SHALL)。ファイル選択ボタンによるクリック選択も併用できなければならない(MUST)。

#### Scenario: ファイルをドラッグ&ドロップで選択
- **WHEN** ユーザーが PPTX ファイルをアップロードエリアにドラッグ&ドロップする
- **THEN** システムはファイルを受け付け、ファイル名とサイズを表示する

#### Scenario: ファイルをクリックで選択
- **WHEN** ユーザーがアップロードエリアをクリックしてファイルダイアログから PPTX ファイルを選択する
- **THEN** システムはファイルを受け付け、ファイル名とサイズを表示する

### Requirement: ファイル形式バリデーション
システムは `.pptx` 形式のファイルのみを受け付けなければならない(MUST)。

#### Scenario: PPTX 以外のファイルを選択
- **WHEN** ユーザーが `.pptx` 以外の拡張子のファイルを選択する
- **THEN** システムはエラーメッセージ「PPTX ファイルのみアップロードできます」を表示し、ファイルを受け付けない

#### Scenario: 正しい PPTX ファイルを選択
- **WHEN** ユーザーが `.pptx` 拡張子のファイルを選択する
- **THEN** システムはファイルを受け付けて次のステップに進める

### Requirement: ファイルサイズ制限
システムは 10MB を超える PPTX ファイルを拒否しなければならない(MUST)。

#### Scenario: 10MB を超えるファイルを選択
- **WHEN** ユーザーが 10MB を超える PPTX ファイルを選択する
- **THEN** システムはエラーメッセージ「ファイルサイズが上限(10MB)を超えています」を表示し、ファイルを受け付けない

#### Scenario: 10MB 以内のファイルを選択
- **WHEN** ユーザーが 10MB 以内の PPTX ファイルを選択する
- **THEN** システムはファイルを受け付ける

### Requirement: アップロード進捗表示
システムはファイルアップロード中にローディング状態を表示しなければならない(MUST)。アップロード中はファイル選択操作を無効にしなければならない(SHALL)。

#### Scenario: アップロード中の UI 状態
- **WHEN** ユーザーがファイルを選択し解析リクエストが送信される
- **THEN** システムはスピナーを表示し、アップロードエリアを非活性にする

#### Scenario: アップロード完了
- **WHEN** 解析リクエストが正常に完了する
- **THEN** システムはスピナーを非表示にし、解析結果の表示に遷移する

### Requirement: 認証必須
アップロード機能は認証済みユーザーのみが利用できなければならない(MUST)。

#### Scenario: 未認証ユーザーのアクセス
- **WHEN** 未認証ユーザーがアプリケーションにアクセスする
- **THEN** システムは Cognito 認証画面を表示し、アップロード UI を表示しない

#### Scenario: 認証済みユーザーのアクセス
- **WHEN** 認証済みユーザーがアプリケーションにアクセスする
- **THEN** システムはアップロード UI を表示する
openspec/changes/pptx-upload-and-parse/specs/pptx-parse/spec.md
openspec/changes/pptx-upload-and-parse/specs/pptx-parse/spec.md
## ADDED Requirements

### Requirement: PPTX ファイル受信
Lambda 関数は API Gateway 経由で POST された PPTX ファイル(Base64 エンコード)を受信し、デコードできなければならない(MUST)。

#### Scenario: 正常な PPTX ファイルの受信
- **WHEN** フロントエンドから Base64 エンコードされた PPTX ファイルが POST される
- **THEN** Lambda はファイルをデコードし、ZIP として展開処理に渡す

#### Scenario: 不正なファイルの受信
- **WHEN** PPTX 形式でないファイル(破損ファイルや別形式のファイル)が POST される
- **THEN** Lambda はステータスコード 400 とエラーメッセージ「無効な PPTX ファイルです」を返す

### Requirement: スライドテキスト抽出
システムは PPTX ファイル内の各スライドから、タイトル・本文テキストを抽出しなければならない(MUST)。抽出には `jszip` で ZIP を展開し、`ppt/slides/slide*.xml` 内の `<a:t>` タグからテキストを取得する。

#### Scenario: 複数スライドのテキスト抽出
- **WHEN** 5 枚のスライドを含む PPTX ファイルが送信される
- **THEN** システムはスライド番号順に 5 つのスライドデータを返し、各スライドにテキスト内容が含まれる

#### Scenario: テキストのないスライド
- **WHEN** テキストを含まないスライド(画像のみ等)が存在する
- **THEN** システムは該当スライドを空のテキストとして返し、エラーにはしない

### Requirement: スライド構造の識別
システムはスライド内のテキストをタイトル(`<p:sp>``<p:nvSpPr>` で type が title/ctrTitle)と本文に分類しなければならない(SHALL)。

#### Scenario: タイトルと本文の分類
- **WHEN** タイトルと箇条書き本文を含むスライドが処理される
- **THEN** システムはタイトルテキストと本文テキストを区別して返す

#### Scenario: タイトルのないスライド
- **WHEN** タイトル要素を含まないスライドが処理される
- **THEN** システムはタイトルを空文字列として返し、全テキストを本文として扱う

### Requirement: スピーカーノート抽出
システムは各スライドのスピーカーノート(`ppt/notesSlides/notesSlide*.xml`)が存在する場合、テキストを抽出しなければならない(SHALL)。

#### Scenario: ノートありのスライド
- **WHEN** スピーカーノートが設定されたスライドが処理される
- **THEN** システムは該当スライドのレスポンスにノートテキストを含める

#### Scenario: ノートなしのスライド
- **WHEN** スピーカーノートが設定されていないスライドが処理される
- **THEN** システムは該当スライドのノートを空文字列として返す

### Requirement: JSON レスポンス形式
Lambda は抽出結果を構造化 JSON で返さなければならない(MUST)。レスポンスにはスライド総数とスライドごとのデータ(スライド番号、タイトル、本文テキスト、ノート)を含む。

#### Scenario: 正常レスポンス
- **WHEN** PPTX ファイルの解析が正常に完了する
- **THEN** システムは以下の構造の JSON を返す: `{ "totalSlides": number, "slides": [{ "slideNumber": number, "title": string, "body": string, "notes": string }] }`

#### Scenario: 解析エラー時のレスポンス
- **WHEN** PPTX ファイルの解析中にエラーが発生する
- **THEN** システムはステータスコード 500 と `{ "error": string }` 形式の JSON を返す

### Requirement: JWT 認証
API Gateway エンドポイントは Cognito JWT トークンによる認証を必須としなければならない(MUST)。

#### Scenario: 有効なトークンでのリクエスト
- **WHEN** 有効な Cognito JWT トークンを含むリクエストが送信される
- **THEN** Lambda が実行され、解析結果が返される

#### Scenario: トークンなしのリクエスト
- **WHEN** JWT トークンなしでリクエストが送信される
- **THEN** API Gateway はステータスコード 401 を返す
openspec/changes/pptx-upload-and-parse/specs/pptx-review/spec.md
openspec/changes/pptx-upload-and-parse/specs/pptx-review/spec.md
## ADDED Requirements

### Requirement: レビューリクエスト受付
AgentCore Runtime は解析済みスライドデータ(JSON)を受け取り、AI レビューを実行しなければならない(MUST)。

#### Scenario: 正常なレビューリクエスト
- **WHEN** フロントエンドから解析済みスライドデータ(slides 配列)を含むリクエストが送信される
- **THEN** AgentCore Runtime はリクエストを受け付け、レビュー処理を開始する

#### Scenario: 不正なリクエスト
- **WHEN** スライドデータを含まない、または不正な形式のリクエストが送信される
- **THEN** AgentCore Runtime はエラーメッセージを返す

### Requirement: ストリーミングレビュー応答
AgentCore Runtime は AI レビュー結果をストリーミング形式で返さなければならない(MUST)。フロントエンドはテキストをリアルタイムに表示できる。

#### Scenario: ストリーミング応答の受信
- **WHEN** レビューリクエストが処理される
- **THEN** AgentCore Runtime はテキストチャンクを逐次返し、フロントエンドはチャンクを受信するたびに画面に追加表示する

#### Scenario: レビュー完了
- **WHEN** AI がレビューコメントの生成を完了する
- **THEN** ストリームが正常に終了し、フロントエンドはレビュー完了状態を表示する

### Requirement: レビュー観点
AI エージェントは以下の観点でプレゼン資料をレビューしなければならない(SHALL):
- **構成**: スライドの流れ・論理展開が適切か
- **内容の明確さ**: 各スライドのメッセージが明確か
- **情報量**: 1 スライドあたりの情報量が適切か(過多・過少の指摘)
- **表現**: 誤字脱字、不自然な表現の指摘

#### Scenario: 構成に問題があるプレゼン資料のレビュー
- **WHEN** 結論が最初に提示されず論理展開が不明瞭な資料が送信される
- **THEN** AI は構成の改善提案を含むレビューコメントを返す

#### Scenario: 情報量が過多なスライドのレビュー
- **WHEN** 1 枚のスライドに大量のテキストが詰め込まれた資料が送信される
- **THEN** AI は情報量の削減やスライド分割を提案するコメントを返す

### Requirement: スライド単位のコメント
AI エージェントはレビューコメントをスライド番号に紐づけて返さなければならない(SHALL)。全体の総評も含む。

#### Scenario: スライド別コメントの生成
- **WHEN** 複数スライドの資料がレビューされる
- **THEN** レスポンスには各スライドへの個別コメントと、資料全体への総評が含まれる

#### Scenario: 問題のないスライド
- **WHEN** 特に指摘事項のないスライドが含まれる
- **THEN** AI は該当スライドに「特に問題なし」等のコメントを返すか、スキップする

### Requirement: Cognito 認証連携
AgentCore Runtime は Cognito 認証と連携し、認証済みユーザーからのリクエストのみを受け付けなければならない(MUST)。

#### Scenario: 認証済みユーザーからのリクエスト
- **WHEN** 有効な Cognito トークンを含むリクエストが AgentCore Runtime に送信される
- **THEN** リクエストが受け付けられ、レビュー処理が実行される

#### Scenario: 未認証リクエスト
- **WHEN** Cognito トークンなし、または無効なトークンでリクエストが送信される
- **THEN** AgentCore Runtime はリクエストを拒否する

### Requirement: レビュー結果の UI 表示
フロントエンドはレビュー結果をスライド別に見やすく表示しなければならない(MUST)。ストリーミング中はリアルタイムにテキストが追加表示される。

#### Scenario: レビュー結果の表示
- **WHEN** AI レビューが完了する
- **THEN** フロントエンドはスライド別のコメントと全体総評を Card コンポーネントで表示する

#### Scenario: ストリーミング中の表示
- **WHEN** AI レビューのストリーミング応答を受信中である
- **THEN** フロントエンドはスピナーとともに受信済みテキストをリアルタイムに表示する

#### Scenario: レビューエラー
- **WHEN** AI レビュー中にエラーが発生する
- **THEN** フロントエンドは Alert コンポーネントでエラーメッセージを表示し、再試行ボタンを提供する

仕様にはシナリオ(WHEN〜THEN)まで書かれるので、AI が何を作るべきか正確に理解でき、エッジケースの見落としも減ります。

仕様ファイルは openspec/specs/ にスペック名ごとのディレクトリで保存されます。/opsx:archive 時に自動で同期されるので、仕様と実装の乖離が起きにくい!

openspec/changes/pptx-upload-and-parse/tasks.md
openspec/changes/pptx-upload-and-parse/tasks.md
## 1. PPTX 解析 Lambda のセットアップ

- [ ] 1.1 `amplify/functions/pptx-parse/` ディレクトリを作成し、Dockerfile(Node 22, ARM64)と package.json を配置する
- [ ] 1.2 `amplify/functions/pptx-parse/resource.ts` に Lambda 関数定義(Docker, ARM64)を作成する
- [ ] 1.3 `amplify/backend.ts` に pptx-parse Lambda と API Gateway V2 エンドポイント(`POST /api/pptx/parse`)を追加する
- [ ] 1.4 API Gateway エンドポイントに Cognito JWT オーソライザーを設定する

## 2. PPTX 解析ロジックの実装

- [ ] 2.1 `jszip``fast-xml-parser` を pptx-parse Lambda の依存に追加する
- [ ] 2.2 Base64 エンコードされた PPTX ファイルを受信・デコードする Lambda ハンドラーを作成する
- [ ] 2.3 `jszip` で ZIP を展開し、`ppt/slides/slide*.xml` からスライドテキスト(`<a:t>` タグ)を抽出する関数を実装する
- [ ] 2.4 スライド内のタイトル要素(type: title/ctrTitle)と本文テキストを分類するロジックを実装する
- [ ] 2.5 `ppt/notesSlides/notesSlide*.xml` からスピーカーノートを抽出するロジックを実装する
- [ ] 2.6 レスポンス JSON(`{ totalSlides, slides: [{ slideNumber, title, body, notes }] }`)を返すようハンドラーを完成させる
- [ ] 2.7 不正ファイル(非 ZIP、破損ファイル)に対するエラーハンドリングを実装する

## 3. AgentCore Runtime のセットアップ

- [ ] 3.1 `amplify/agent/` ディレクトリを作成し、Dockerfile(Node 22)と package.json(`bedrock-agentcore`, `ai`, `@ai-sdk/amazon-bedrock`, `zod`)を配置する
- [ ] 3.2 `amplify/agent/resource.ts``createAgentCoreRuntime()` 関数を作成し、AgentCore Runtime CDK コンストラクト(`@aws-cdk/aws-bedrock-agentcore-alpha`)、Cognito 認証連携、Bedrock 権限を定義する
- [ ] 3.3 `amplify/backend.ts` に AgentCore Runtime のリソースを追加する
- [ ] 3.4 `deploy-time-build` で ARM64 Docker イメージをビルドする設定を追加する

## 4. AI レビューエージェントの実装

- [ ] 4.1 `amplify/agent/app.ts``BedrockAgentCoreApp` のエントリーポイントを作成する
- [ ] 4.2 リクエストスキーマ(解析済みスライドデータを受け取る Zod スキーマ)を定義する
- [ ] 4.3 プレゼン資料レビュー用のシステムプロンプト(構成・明確さ・情報量・表現の 4 観点、スライド単位コメント + 全体総評)を作成する
- [ ] 4.4 `ToolLoopAgent``@ai-sdk/amazon-bedrock` で Claude モデルを使ったレビューエージェントを実装する
- [ ] 4.5 ストリーミング応答(`text-delta` イベントの逐次送信)を実装する

## 5. フロントエンド: アップロード UI

- [ ] 5.1 `App.tsx` のプレースホルダー部分をドラッグ&ドロップ対応のアップロードコンポーネントに置き換える
- [ ] 5.2 ファイル形式バリデーション(`.pptx` のみ)とファイルサイズバリデーション(10MB 上限)を実装する
- [ ] 5.3 ファイル選択後にファイル名・サイズを表示する UI を実装する
- [ ] 5.4 アップロード中のローディング状態(Spinner 表示、アップロードエリア非活性化)を実装する

## 6. フロントエンド: 解析 API 呼び出し

- [ ] 6.1 Cognito JWT トークンを取得し、`POST /api/pptx/parse` に PPTX ファイルを Base64 で送信する関数を作成する
- [ ] 6.2 解析結果(スライド一覧)を画面に表示する UI を実装する(スライド番号、タイトル、本文テキスト、ノート)
- [ ] 6.3 解析エラー時の Alert 表示を実装する

## 7. フロントエンド: レビュー表示

- [ ] 7.1 AgentCore Runtime エンドポイントへのストリーミングリクエスト関数を作成する(Cognito トークン認証付き)
- [ ] 7.2 ストリーミング応答をリアルタイムに表示するレビュー結果コンポーネントを実装する
- [ ] 7.3 レビュー完了後にスライド別コメントと全体総評を Card コンポーネントで整形表示する
- [ ] 7.4 レビューエラー時の Alert 表示と再試行ボタンを実装する

## 8. Amplify 出力設定と結合テスト

- [ ] 8.1 `amplify_outputs` に API Gateway エンドポイント URL と AgentCore Runtime URL を出力する設定を追加する
- [ ] 8.2 フロントエンドで出力された URL を読み取り、API 呼び出しに使用するよう接続する
- [ ] 8.3 `npx ampx sandbox` でローカル環境を起動し、PPTX アップロード → 解析 → レビューの一連の流れを動作確認する

仕様が固まったら /opsx:apply でタスクを順番に実装します。
その際、AWS MCPを使ってベストプラクティス調査しながら実装進めてくださいと追加指示するとより精度が上がります。

生成されたコードとサンプルリポジトリの実装を照らし合わせて、適宜修正してください。:relaxed::point_up_tone1:

以下、実装の主要パートを紹介します。

PPTX 解析 Lambda

PPTX ファイルを受け取ってスライドのテキスト・構造を抽出する Lambda 関数です。Docker コンテナ(ARM64)で動かしています。

amplify/functions/pptx-parse/
├── Dockerfile
├── handler.ts      # Lambda ハンドラー
├── pptx-parser.ts  # PPTX 解析ロジック
├── resource.ts     # CDK リソース定義
└── package.json

仕組みはシンプルで、PPTX(実態は ZIP)を JSZip で展開 → fast-xml-parser でスライド XML を解析 → タイトル・本文・ノートをスライド単位で抽出して JSON で返します。

AgentCore でレビューエージェント

レビュー本体は Amazon Bedrock AgentCore で動かしています。TypeScript (AI SDK) で書いたエージェントを Docker コンテナとしてデプロイします。

amplify/agent/
├── Dockerfile
├── app.ts          # エージェント本体
├── resource.ts     # CDK リソース定義
└── package.json

AI SDK の ToolLoopAgent でストリーミング応答を生成し、AgentCore のランタイムに SSE で返しています。TypeScript で統一できるのが嬉しいポイント。

AgentCore ARN → URL 組み立て

AgentCore をフロントエンドから呼び出すとき、ARN を直接 fetch() の URL にしてしまう という罠があります。

// NG: ARN は URL ではない!
const res = await fetch(agentRuntimeArn, { ... })
// → "Fetch API cannot load arn:aws:bedrock-agentcore:..." エラー

正しくは AgentCore REST API のエンドポイント URL を組み立てます。

// OK: REST API エンドポイントを構築
const url = `https://bedrock-agentcore.ap-northeast-1.amazonaws.com/runtimes/${encodeURIComponent(agentRuntimeArn)}/invocations?qualifier=DEFAULT`
const res = await fetch(url, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${token}`,
    Accept: 'text/event-stream',
    'x-amzn-bedrock-agentcore-runtime-session-id': crypto.randomUUID(),
  },
  body: JSON.stringify({ slides: parseResult.slides }),
})

ARN に :/ が含まれるので、encodeURIComponent() でエンコードするのを忘れずに!

また、認証トークンは idToken ではなく accessToken を使います。idToken はユーザー属性情報用で、API 認証には適しません。

SSE ストリーミングの処理

AgentCore からの応答は SSE(Server-Sent Events)形式で返ってきます。

event: message
data: {"text": "レビュー内容の一部..."}

event: message
data: {"text": "続きのテキスト..."}

data: [DONE]

チャンクの境界で行が途切れることがあるので、lineBuf パターンで不完全行をバッファリングします。

const reader = res.body?.getReader()
const decoder = new TextDecoder()
let accumulated = ''
let lineBuf = ''

while (true) {
  const { done, value } = await reader.read()
  if (done) break

  // 前回の残りと結合してから行分割
  lineBuf += decoder.decode(value, { stream: true })
  const lines = lineBuf.split('\n')
  lineBuf = lines.pop() ?? ''  // 最後の不完全行を次回に持ち越し

  for (const line of lines) {
    if (!line.startsWith('data: ')) continue
    const data = line.slice(6)
    if (data === '[DONE]') continue

    try {
      const parsed = JSON.parse(data)
      if (parsed.text) {
        accumulated += parsed.text
        setReviewText(accumulated)  // リアルタイム表示
      }
    } catch {
      // JSON パース失敗は無視(不完全なチャンク)
    }
  }
}

lineBuf パターンがないと、チャンク境界で JSON パースエラーが頻発します。SSE を fetch + ReadableStream で処理するときの定番テクニックです。

バックエンド定義(CDK)

amplify/backend.ts に PPTX 解析 Lambda と AgentCore ランタイムの両方を接続します。

backend.addOutput() で出力した pptxParseApiUrlagentRuntimeArnamplify_outputs.json に入り、フロントエンドから参照できるようになります。

フロントエンド全体像

状態管理は upload → parsing → parsed → reviewing → reviewed のステートマシンで管理しています。

/opsx:verify で仕様と実装を突き合わせ

  • 実装が終わったら /opsx:verify を実行

OpenSpec が仕様ファイル(openspec/specs/pptx-*)と実際のコードを突き合わせて、漏れや齟齬を自動チェックしてくれます。たとえば「pptx-upload の仕様にファイルサイズ 10MB 制限があるけど、実装で MAX_FILE_SIZE が定義されてるか?」みたいなことを勝手に確認してくれる。人間がレビューチェックリストを作る手間が省けて便利!

  • 検証が通ったら /opsx:archive でアーカイブ → 仕様がメインの specs/ に同期される

これで 1 サイクル完了です:relaxed::point_up_tone1:

5. 動作確認

sandbox(開発環境)

ローカル開発には Amplify のサンドボックスを使います。自分専用の一時バックエンドが立ち上がって便利!

# sso ログイン
aws sso login --profile your-profile-name

# バックエンドのデプロイ(4分ほど待つ)
npx ampx sandbox
or
npx ampx sandbox --profile your-profile-name

# 別ターミナルでフロントエンド起動
npm run dev

サンドボックスはコードを編集するたびにバックエンドも自動更新されます。ホットデプロイ的に使えて便利!

動作確認完了したら、下記のコマンドでsandbox環境を削除しましょう。

npx ampx sandbox delete
or
npx ampx sandbox delete --profile your-profile-name

参考資料

29
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?