Skill: aikawabigsky309/pozzzi-charity-management-advisor (version 0.1.0)

--- name: management-advisor description: 噗滋（pozzzi）慈善 management-advisor Skill --- # management-advisor > ℹ️ **公益开源工具（v0.1.0）** > > 本 Skill 为噗滋（pozzzi）慈善开源工具集成员，由社区贡献者维护，遵循 MIT License。 > > **使用约束**（用户自负责任）： > - 🚫 禁止处理 14 岁以下未成年人个体级数据（《未成年人保护法》第 72-74 条） > - 🚫 禁止用于编造法规条文、文件编号、资质证书编号等 > - 🚫 不适用于公开募捐相关内容生成（建议使用专业工具） > - ⚠️ AI 生成内容**必须经人工审核后方可使用**，开发者不对使用结果承担责任 > - ⚠️ 本工具不构成专业的法律/财务/管理建议 > - ✅ 用户自带模型 API（混元/DeepSeek/豆包，均已各自备案） > - ✅ 噗滋（pozzzi）作为工具提供者，不接触/不存储用户数据 > > 详细法律声明：[LICENSE](https://github.com/kirinspark/pozzzi-charity/blob/main/LICENSE) | [免责声明](https://github.com/kirinspark/pozzzi-charity/blob/main/docs/legal/disclaimer.md) > 噗滋（pozzzi）慈善 Skill 集群成员

path: src/validators.js
content: 'use strict';

// -----------------------------------------------------------------------
// validators.js — 管理顾问 Skill 输入校验器
//
// 依赖：仅 Node.js 内置模块
// 合规依据：噗滋慈善管理顾问 Spec v1.0 + PIPL 第23/28条 + 慈善法第75/77条
// -----------------------------------------------------------------------

/**
 * 合并两个校验结果对象（将 src 的 errors/warnings 追加到 dest）
 * @param {{ errors: string[], warnings: string[] }} dest
 * @param {{ errors: string[], warnings: string[] }} src
 */
function mergeResult(dest, src) {
  dest.errors.push(...src.errors);
  dest.warnings.push(...src.warnings);
}

// -----------------------------------------------------------------------
// 枚举常量
// -----------------------------------------------------------------------

/**
 * org_type 有效枚举值
 * @type {string[]}
 */
const VALID_ORG_TYPES = ['foundation', 'social_group', 'social_service_org', 'other'];

/**
 * question_category 有效枚举值
 * - charity_law    : 政策法规问答
 * - compliance     : 合规操作指引
 * - governance     : 组织管理建议
 * - finance        : 财务相关
 * - hr             : 人力资源
 * - other          : 其他/混合
 *
 * @type {string[]}
 */
const VALID_QUESTION_CATEGORIES = [
  'charity_law',
  'policy_qa',
  'compliance',
  'governance',
  'finance',
  'hr',
  'other',
];

/**
 * urgency 有效枚举值
 * @type {string[]}
 */
const VALID_URGENCY = ['high', 'medium', 'low'];

/**
 * question 最大长度（字符数）
 */
const QUESTION_MAX_LENGTH = 500;

/**
 * question_context 最大长度（字符数）
 */
const QUESTION_CONTEXT_MAX_LENGTH = 200;

// -----------------------------------------------------------------------
// 1. 通用必填字段校验
// -----------------------------------------------------------------------

/**
 * 校验管理顾问通用必填字段。
 *
 * PII 安全注释：contact_name 在此仅做非空校验，
 * 不进入 Prompt，由 validatePIISafety 负责标记和排除。
 *
 * @param {object} input
 * @returns {{ valid: boolean, errors: string[], warnings: string[] }}
 */
function validateCommonInput(input) {
  const errors = [];
  const warnings = [];

  if (!input || typeof input !== 'object') {
    return { valid: false, errors: ['input 必须是对象'], warnings: [] };
  }

  // org_name — 非空字符串
  if (!input.org_name || typeof input.org_name !== 'string' || !input.org_name.trim()) {
    errors.push('org_name 为必填字段，不能为空');
  }

  // org_type — 枚举，必填
  if (!input.org_type) {
    errors.push('org_type 为必填字段');
  } else if (!VALID_ORG_TYPES.includes(input.org_type)) {
    errors.push(
      `org_type 值无效："${input.org_type}"，有效值为：${VALID_ORG_TYPES.join('/')}`,
    );
  }

  // org_province — 非空字符串，必填
  if (!input.org_province || typeof input.org_province !== 'string' || !input.org_province.trim()) {
    errors.push('org_province 为必填字段，不能为空');
  }

  // question — 非空字符串，必填，最长 500 字
  if (!input.question || typeof input.question !== 'string' || !input.question.trim()) {
    errors.push('question 为必填字段，不能为空');
  } else if (input.question.trim().length > QUESTION_MAX_LENGTH) {
    errors.push(
      `question 长度超出限制（当前 ${input.question.trim().length} 字，最大 ${QUESTION_MAX_LENGTH} 字）。请聚焦核心问题后重新提问`,
    );
  }

  // contact_name — 必填（PII，仅存本地日志，不进模型）
  if (
    !input.contact_name ||
    typeof input.contact_name !== 'string' ||
    !input.contact_name.trim()
  ) {
    errors.push('contact_name 为必填字段，不能为空');
  }

  return { valid: errors.length === 0, errors, warnings };
}

// -----------------------------------------------------------------------
// 2. 可选字段校验
// -----------------------------------------------------------------------

/**
 * 校验可选字段（question_category / question_context / urgency）。
 * 应在 validateCommonInput 通过后调用。
 *
 * @param {object} input
 * @returns {{ valid: boolean, errors: string[], warnings: string[] }}
 */
function validateOptionalInput(input) {
  const errors = [];
  const warnings = [];

  if (!input || typeof input !== 'object') {
    return { valid: false, errors: ['input 必须是对象'], warnings: [] };
  }

  // question_category — 可选枚举
  if (input.question_category !== undefined && input.question_category !== null) {
    if (!VALID_QUESTION_CATEGORIES.includes(input.question_category)) {
      errors.push(
        `question_category 值无效："${input.question_category}"，有效值为：${VALID_QUESTION_CATEGORIES.join('/')}`,
      );
    }
  }

  // question_context — 可选，如提供则最长 200 字
  if (input.question_context !== undefined && input.question_context !== null) {
    if (typeof input.question_context !== 'string') {
      errors.push('question_context 如提供则必须是字符串');
    } else if (input.question_context.trim().length > QUESTION_CONTEXT_MAX_LENGTH) {
      errors.push(
        `question_context 长度超出限制（当前 ${input.question_context.trim().length} 字，最大 ${QUESTION_CONTEXT_MAX_LENGTH} 字）`,
      );
    }
  }

  // urgency — 可选枚举
  if (input.urgency !== undefined && input.urgency !== null) {
    if (!VALID_URGENCY.includes(input.urgency)) {
      errors.push(
        `urgency 值无效："${input.urgency}"，有效值为：${VALID_URGENCY.join('/')}`,
      );
    }
  }

  return { valid: errors.length === 0, errors, warnings };
}

// -----------------------------------------------------------------------
// 3. PII 安全校验
// -----------------------------------------------------------------------

/**
 * PII 安全校验（管理顾问版）。
 *
 * 合规依据：PIPL 第23/28条、管理顾问 Spec v1.0
 * - 标记 contact_name 为 piiFields，供 prompt-builder 排除
 * - 检测 question / question_context 中疑似中文真实姓名 → warning 提示替换
 *
 * 注意：PII 拦截（blocked）由 index.js 在调用 pii-filter.filterPII() 时处理。
 * 本函数只做静态字段标记和 warning 预提示，不执行 blockOnDetect 逻辑。
 *
 * @param {object} input
 * @returns {{ piiFields: string[], warnings: string[] }}
 */
function validatePIISafety(input) {
  // contact_name 是唯一必须从 prompt 中排除的顶级 PII 字段
  const piiFields = ['contact_name'];
  const warnings = [];

  if (!input || typeof input !== 'object') {
    return { piiFields, warnings };
  }

  // 检测 question / question_context 中疑似中文真实姓名（关键词前缀形式）
  // 与 pii-filter 使用相同的模式逻辑，提前在 validate 阶段给用户友好提示
  const nameContextPattern = /(?:姓名|受益人|申请人|联系人|负责人|户主|监护人|家长|学生|志愿者)[：:]\s*[\u4e00-\u9fa5]{2,4}/;
  const phonePattern = /(?:(?:\+?0{0,2}86)[-\s]?)?1[3-9]\d{9}/;
  const idCardPattern = /[1-9]\d{5}(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dX]/i;

  const fieldsToCheck = [
    { field: 'question', label: '提问内容' },
    { field: 'question_context', label: '问题背景' },
  ];

  for (const { field, label } of fieldsToCheck) {
    const text = input[field];
    if (typeof text !== 'string' || !text.trim()) continue;

    if (nameContextPattern.test(text)) {
      warnings.push(
        `${label}（${field}）中检测到疑似真实姓名。请将姓名替换为职位描述（如"项目负责人""受益人A"），以保护相关人员隐私。`,
      );
    }
    if (phonePattern.test(text)) {
      warnings.push(
        `${label}（${field}）中检测到疑似手机号码。请删除手机号后重新提问，以保护个人隐私。`,
      );
    }
    if (idCardPattern.test(text)) {
      warnings.push(
        `${label}（${field}）中检测到疑似身份证号码。请删除身份证号后重新提问，以保护个人隐私。`,
      );
    }
  }

  return { piiFields, warnings };
}

// -----------------------------------------------------------------------
// 4. 总入口
// -----------------------------------------------------------------------

/**
 * 总校验入口。按顺序执行：
 * 1. 通用必填字段校验（validateCommonInput）
 * 2. 可选字段校验（validateOptionalInput）
 * 3. PII 安全校验（validatePIISafety，仅追加 warnings，不影响 valid）
 *
 * @param {object} input
 * @returns {{ valid: boolean, errors: string[], warnings: string[], piiFields: string[] }}
 */
function validate(input) {
  const result = { valid: true, errors: [], warnings: [], piiFields: [] };

  // Step 1: 通用必填校验
  const commonResult = validateCommonInput(input);
  mergeResult(result, commonResult);

  // Step 2: 可选字段校验（即使通用校验失败也继续，收集全部错误）
  const optionalResult = validateOptionalInput(input);
  mergeResult(result, optionalResult);

  // Step 3: PII 安全校验（仅追加 warnings/piiFields，不影响 valid）
  const piiResult = validatePIISafety(input);
  result.warnings.push(...piiResult.warnings);
  result.piiFields = piiResult.piiFields;

  // 汇总：有 error 则 valid = false
  result.valid = result.errors.length === 0;

  return result;
}

// -----------------------------------------------------------------------
// 导出
// -----------------------------------------------------------------------

module.exports = {
  validate,
  validateCommonInput,
  validateOptionalInput,
  validatePIISafety,
  VALID_ORG_TYPES,
  VALID_QUESTION_CATEGORIES,
  VALID_URGENCY,
  QUESTION_MAX_LENGTH,
  QUESTION_CONTEXT_MAX_LENGTH,
};


path: src/knowledge-base.js
content: 'use strict';

// -----------------------------------------------------------------------
// knowledge-base.js — 管理顾问 Skill 内嵌知识库（v1.0）
//
// 依赖：仅 Node.js 内置模块（fs, path）
// 职责：从 templates/*.md 加载知识库文件，按问题类别索引，
//       供 prompt-builder.js 将相关知识段落嵌入 system prompt。
//
// 架构说明：
//   - v1.0：知识内嵌 prompt（本文件），8000 tokens 以内
//   - v1.1（规划中）：迁移至向量数据库 RAG 检索
//
// 知识库文件（templates/ 目录）：
//   knowledge-charity-law.md   → policy_qa（慈善法/公募/信息公开）
//   knowledge-compliance-faq.md → compliance_ops + faq（年检/税务/劳动）
//   knowledge-governance.md    → governance（理事会/财务内控/人力）
//
// 问题类型映射（Spec 第4节）：
//   policy_qa      → knowledge-charity-law.md
//   compliance     → knowledge-compliance-faq.md（Segment B）
//   governance     → knowledge-governance.md
//   finance        → knowledge-compliance-faq.md（Segment D 税务部分）
//   hr             → knowledge-compliance-faq.md（Segment D 劳动用工）
//   other / mixed  → 全量知识段落
// -----------------------------------------------------------------------

const fs   = require('fs');
const path = require('path');

// -----------------------------------------------------------------------
// 知识库文件路径常量
// -----------------------------------------------------------------------

const KNOWLEDGE_DIR = path.join(__dirname, '..', 'templates');

const KB_FILES = {
  charity_law:  'knowledge-charity-law.md',
  compliance:   'knowledge-compliance-faq.md',
  governance:   'knowledge-governance.md',
};

// -----------------------------------------------------------------------
// YAML frontmatter 解析（轻量实现，不引入 yaml 库）
// -----------------------------------------------------------------------

/**
 * 白名单 frontmatter 字段（防供应链 prompt injection）
 * 只解析已知安全的元数据字段
 */
const ALLOWED_METADATA_KEYS = new Set([
  'version', 'source_url', 'verified_date', 'segment',
]);

/**
 * 解析 Markdown 文件中的 YAML frontmatter（--- 包裹的块）
 *
 * @param {string} raw - 原始文件内容
 * @returns {{ metadata: Object, content: string }}
 */
function parseFrontmatter(raw) {
  const frontmatterPattern = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
  const match = raw.match(frontmatterPattern);

  if (!match) {
    return { metadata: {}, content: raw };
  }

  const yamlBlock = match[1];
  const content   = match[2];
  const rawMeta   = {};

  for (const line of yamlBlock.split(/\r?\n/)) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith('#')) continue;

    const colonIdx = trimmed.indexOf(': ');
    const colonOnly = colonIdx === -1 ? trimmed.indexOf(':') : -1;
    const idx = colonIdx !== -1 ? colonIdx : colonOnly;
    if (idx === -1) continue;

    const key    = trimmed.slice(0, idx).trim();
    const rawVal = trimmed.slice(idx + (colonIdx !== -1 ? 2 : 1)).trim();

    rawMeta[key] = rawVal.replace(/^['"]|['"]$/g, '');
  }

  // 白名单过滤：只保留已知安全字段
  const metadata = {};
  for (const [k, v] of Object.entries(rawMeta)) {
    if (ALLOWED_METADATA_KEYS.has(k)) {
      metadata[k] = v;
    }
  }

  return { metadata, content };
}

// -----------------------------------------------------------------------
// 知识库加载缓存（进程内单例，避免每次请求重复读盘）
// -----------------------------------------------------------------------

/** @type {Map<string, { metadata: Object, content: string }>} */
const _cache = new Map();

/**
 * 加载单个知识库文件，解析 frontmatter 和正文。
 * 结果缓存在进程内存，重启后重新加载。
 *
 * @param {string} filename - 文件名（如 'knowledge-charity-law.md'）
 * @returns {{ metadata: Object, content: string }}
 * @throws {Error} 文件不存在或读取失败
 */
function loadKnowledgeFile(filename) {
  if (_cache.has(filename)) {
    return _cache.get(filename);
  }

  const filePath = path.join(KNOWLEDGE_DIR, filename);
  let raw;
  try {
    raw = fs.readFileSync(filePath, 'utf8');
  } catch {
    throw new Error(`知识库文件加载失败：${filename}（路径：${filePath}）`);
  }

  const parsed = parseFrontmatter(raw);
  _cache.set(filename, parsed);
  return parsed;
}

/**
 * 清除进程内知识库缓存（主要用于测试）
 */
function clearCache() {
  _cache.clear();
}

// -----------------------------------------------------------------------
// 问题类别 → 知识库文件映射
// -----------------------------------------------------------------------

/**
 * 按 question_category（或自动识别的内部类型）返回需要加载的知识库文件列表。
 *
 * 映射规则（与 Spec 第 4、7 节一致）：
 *   policy_qa  → charity_law
 *   compliance → compliance（含 B1-B4）
 *   governance → governance
 *   finance    → compliance（Segment D 税务部分）
 *   hr         → compliance（Segment D 劳动用工）
 *   other/mixed/undefined → 全量（全部三个文件）
 *
 * @param {string|undefined} category - question_category 枚举值或内部分类标签
 * @returns {string[]} 需要加载的文件名列表
 */
function resolveCategoryFiles(category) {
  const map = {
    policy_qa:  [KB_FILES.charity_law],
    charity_law: [KB_FILES.charity_law],
    compliance: [KB_FILES.compliance],
    governance: [KB_FILES.governance],
    finance:    [KB_FILES.compliance],
    hr:         [KB_FILES.compliance],
    other:      [KB_FILES.charity_law, KB_FILES.compliance, KB_FILES.governance],
  };

  const files = map[category];
  if (!files) {
    // 未知类别或 mixed → 全量
    return [KB_FILES.charity_law, KB_FILES.compliance, KB_FILES.governance];
  }
  return files;
}

// -----------------------------------------------------------------------
// 关键词自动分类（Spec 第 4 节，v1.0 关键词匹配）
// -----------------------------------------------------------------------

/**
 * 关键词 → 内部分类标签映射
 * 顺序重要：先匹配到的类型优先
 *
 * @type {Array<{ category: string, keywords: string[] }>}
 */
const KEYWORD_CATEGORY_MAP = [
  {
    category: 'policy_qa',
    keywords: [
      '慈善法', '法规', '政策', '修订', '要求规定', '违法', '处罚',
      '公募', '募捐', '基金会管理', '登记管理', '认定', '资格证',
    ],
  },
  {
    category: 'compliance',
    keywords: [
      '年检', '信息公开', '章程变更', '平台报告', '公示', '期限', '流程',
      '年度报告', '腾讯公益', '壹基金', '南都', '慈善中国', '年度检查',
    ],
  },
  {
    category: 'governance',
    keywords: [
      '理事会', '监事会', '章程', '决议', '法定代表人', '财务印章',
      '档案', '理事', '监事', '会议', '印章', '内控', '授权审批',
    ],
  },
  {
    category: 'finance',
    keywords: [
      '税务', '发票', '增值税', '企业所得税', '免税', '捐赠扣除',
      'PD资格', '税前扣除', '开票', '财务报告', '审计', '会计',
    ],
  },
  {
    category: 'hr',
    keywords: [
      '劳动合同', '社保', '个税', '志愿者', '实习', '试用期',
      '员工', '工资', '报销', '话费', '离职', '辞职', '解雇',
    ],
  },
];

/**
 * 根据问题文本进行关键词匹配，自动识别问题类别。
 *
 * 匹配逻辑：
 *   - 遍历 KEYWORD_CATEGORY_MAP，统计各类别的关键词命中数
 *   - 命中数最多的类别胜出
 *   - 多个类别命中数并列（且命中数>0）→ 返回 'other'（mixed，加载全量知识）
 *   - 无任何命中 → 返回 'other'
 *
 * @param {string} questionText - 用户提问文本
 * @returns {string} 识别到的内部类别标签
 */
function classifyQuestion(questionText) {
  if (typeof questionText !== 'string' || !questionText.trim()) {
    return 'other';
  }

  const counts = {};
  let maxCount = 0;

  for (const { category, keywords } of KEYWORD_CATEGORY_MAP) {
    let count = 0;
    for (const kw of keywords) {
      if (questionText.includes(kw)) {
        count++;
      }
    }
    counts[category] = count;
    if (count > maxCount) maxCount = count;
  }

  if (maxCount === 0) {
    return 'other';
  }

  // 找出并列最高的类别
  const topCategories = Object.entries(counts)
    .filter(([, c]) => c === maxCount)
    .map(([cat]) => cat);

  if (topCategories.length === 1) {
    return topCategories[0];
  }

  // 多类别并列 → mixed，加载全量
  return 'other';
}

// -----------------------------------------------------------------------
// 主入口：parseKnowledgeBase
// -----------------------------------------------------------------------

/**
 * 根据问题类别加载并返回相关知识库条目，供嵌入 system prompt。
 *
 * 流程：
 *   1. 根据 category 确定需要加载的文件列表
 *   2. 加载各文件（使用缓存）
 *   3. 返回知识库文本（按文件拼接，含 metadata 版本信息）
 *
 * @param {string|undefined} category - question_category 枚举值或 classifyQuestion 的返回值
 * @returns {{ text: string, sources: string[], version: string }}
 *   - text：知识库正文，供嵌入 prompt
 *   - sources：文件来源列表（用于日志）
 *   - version：知识库版本摘要（格式："v20260330"）
 */
function parseKnowledgeBase(category) {
  const filenames = resolveCategoryFiles(category);
  const parts     = [];
  const sources   = [];
  const versions  = [];

  for (const filename of filenames) {
    let parsed;
    try {
      parsed = loadKnowledgeFile(filename);
    } catch {
      // 知识库文件加载失败 → 记录错误但不抛出，返回降级内容
      parts.push(`[知识库加载失败：${filename}，请稍后重试]`);
      sources.push(filename);
      continue;
    }

    const { metadata, content } = parsed;
    parts.push(content);
    sources.push(filename);
    if (metadata.version) {
      versions.push(metadata.version);
    }
  }

  const text    = parts.join('\n\n---\n\n');
  const version = versions.length > 0
    ? versions[0]
    : 'unknown';

  return { text, sources, version };
}

// -----------------------------------------------------------------------
// 导出
// -----------------------------------------------------------------------

module.exports = {
  parseKnowledgeBase,
  classifyQuestion,
  loadKnowledgeFile,
  clearCache,
  resolveCategoryFiles,
  KB_FILES,
  KEYWORD_CATEGORY_MAP,
};


path: src/prompt-builder.js
content: 'use strict';

// -----------------------------------------------------------------------
// prompt-builder.js — 管理顾问 Skill Prompt 构建器
//
// 依赖：仅 Node.js 内置模块（fs, path）+ 本 Skill 内部模块（knowledge-base）
// 合规依据：噗滋慈善管理顾问 Spec v1.0
//           PIPL 第23/28条 + 生成式AI暂行办法第4/8/17条 + 慈善法第75/77条
//
// 核心特征（与其他 Skill 的差异）：
//   - 这是问答型 Skill：输入是用户自然语言问题，输出是咨询回答
//   - 知识库驱动：按问题类别加载对应知识库段落嵌入 system prompt
//   - temperature 0.4：准确为主，允许有表达灵活性（Spec 明确规定）
//   - 强制免责：每条回答必须标注"非法律意见"（Spec 第9节）
//   - 禁止编造：system prompt 明确禁止编造条文编号/政府文件编号
//
// PII 安全：
//   - contact_name 仅写入本地日志，绝不写入 prompt
//   - question / question_context 经 pii-filter 过滤后传入（由 index.js 负责）
//
// temperature 0.4：由调用方（index.js）设置，本文件不控制
// -----------------------------------------------------------------------

const knowledgeBase = require('./knowledge-base');

// -----------------------------------------------------------------------
// 内置 PII 排除字段（硬编码，不可缩减）
// -----------------------------------------------------------------------

/** 永远不进入 prompt 的顶级 PII 字段 */
const BUILTIN_PII_FIELDS = ['contact_name'];

// -----------------------------------------------------------------------
// 1. 强制合规约束（system prompt 中必须包含的规则）
// -----------------------------------------------------------------------

/**
 * 生成管理顾问强制合规约束文本（所有模型通用，不可删减）。
 *
 * 来源：Spec 第 8 节（绝对禁止）+ 第 9 节（合规要求）
 *
 * @returns {string}
 */
function getAdvisorConstraints() {
  return [
    '【管理顾问强制合规约束——不可违反】',
    '',
    '1. 禁止出具法律意见',
    '   → 不得给出"您应该/必须/合法/违法"等结论性法律判断',
    '   → 涉及法律问题时，明确说明这是参考信息，建议咨询专业律师',
    '',
    '2. 禁止编造法规条文编号',
    '   → 引用条文时只使用知识库中已记录的条款编号',
    '   → 不确定具体条款编号时，必须说明"建议核查具体条文"，不得给出推测编号',
    '   → 格式示例：（《慈善法》第72条）——仅在确认该条文时使用',
    '',
    '3. 禁止编造政府文件编号',
    '   → 只在知识库有记录的情况下引用文件编号（如"民发[20XX]X号"）',
    '   → 未记录时描述文件性质（如"民政部关于……的通知"），不给出具体编号',
    '',
    '4. 禁止处理诉讼/纠纷问题',
    '   → 涉及纠纷、行政处罚、诉讼、仲裁的问题，必须建议咨询律师，不做任何实质性处理指引',
    '',
    '5. 超出知识库范围时必须诚实声明',
    '   → 禁止以看似合理的方式编造回答',
    '   → 超出覆盖范围时，说明"超出当前知识库覆盖范围"并引导咨询权威渠道',
    '',
    '6. 地方差异必须提示',
    '   → 涉及各地民政局要求差异的内容，明确提示"以您所在地民政局通知为准"',
    '',
    '7. 每条回答末尾必须包含标准免责声明',
    '   → 声明不可省略，不可简化',
  ].join('\n');
}

// -----------------------------------------------------------------------
// 2. 输出格式要求
// -----------------------------------------------------------------------

/**
 * 生成输出格式说明（四段式结构，Spec 第5节）。
 *
 * @param {string} urgency - 'high' | 'medium' | 'low' | undefined
 * @returns {string}
 */
function getOutputFormatInstructions(urgency) {
  const urgencyGuide = {
    high:   '【紧急模式（urgency=high）】回答控制在150-300字，直接给出结论和最关键的行动建议，去掉背景解释。',
    medium: '【标准模式（urgency=medium）】回答控制在300-500字，包含背景+结论+建议行动。',
    low:    '【详尽模式（urgency=low）】回答控制在400-600字，包含背景解释+条文引用+多种情形说明+建议行动。',
  };

  const lengthGuide = urgencyGuide[urgency] || urgencyGuide['medium'];

  return [
    '【输出格式要求——严格遵守以下四段式结构】',
    '',
    '【问题理解】',
    '一句话重述用户问题，确认理解正确。',
    '',
    '【参考回答】',
    '核心回答内容。',
    '- 涉及具体数字/比例/期限时，必须标注依据法规/文件名称和条文编号（不确定时标注"建议核查具体条文"）',
    '- 涉及地方差异时，明确提示"以下以通用规则为参考，您所在地区可能有差异"',
    '- 知识库覆盖不足时，标注"[超出当前知识库覆盖范围，建议向主管民政部门或专业律师咨询]"，禁止编造',
    '',
    '【建议行动】',
    '具体可执行的下一步建议，2-4条，编号列出。',
    '如问题属于其他工具更适合处理的范围，提示"您可以使用噗滋 XX 助手完成这一步"。',
    '',
    '【重要提示】',
    'AI 生成，仅供参考，请核实数据。此回答为公益管理领域的参考信息，不构成法律意见。涉及具体法律问题、纠纷处理或行政处罚，建议咨询专业律师；涉及税务操作，建议咨询注册税务师或当地税务机关。法规条文建议核查最新有效版本。',
    '',
    lengthGuide,
  ].join('\n');
}

// -----------------------------------------------------------------------
// 3. buildSystemPrompt
// -----------------------------------------------------------------------

/**
 * 根据问题类别、urgency 和模型类型生成 system prompt。
 *
 * 结构（按顺序）：
 *   1. 角色定义（NGO 管理顾问）
 *   2. 强制合规约束（禁止编造/禁止法律意见/超出范围诚实声明）
 *   3. 内嵌知识库段落（按 category 加载）
 *   4. 输出格式要求（四段式结构）
 *   5. 模型专项优化（按 modelType）
 *
 * @param {string|undefined} category   - 问题类别（来自 validators 或自动分类）
 * @param {string|undefined} urgency    - 'high' | 'medium' | 'low'
 * @param {string} [modelType='hunyuan'] - 目标模型
 * @returns {{ systemPrompt: string, knowledgeSources: string[], knowledgeVersion: string }}
 */
function buildSystemPrompt(category, urgency, modelType) {
  const model = modelType || 'hunyuan';

  // 加载知识库
  const kb = knowledgeBase.parseKnowledgeBase(category);

  // 公共基础（所有模型通用）
  const roleDefinition = [
    '你是噗滋慈善的 NGO 管理顾问，专为中国中小型草根公益机构提供管理知识支持。',
    '你熟悉中国慈善法规、社会组织登记管理规定、非营利组织治理规范和公益行业实践。',
    '你的定位是知识型顾问，不是律师或会计师。你提供参考信息，帮助 NGO 了解相关规则，',
    '但不出具任何法律意见，不替代专业律师、会计师或主管部门的权威判断。',
  ].join('\n');

  const systemPrompt = [
    roleDefinition,
    '',
    getAdvisorConstraints(),
    '',
    '【内嵌知识库（请基于以下内容回答问题，超出范围时明确说明）】',
    `知识库版本：${kb.version}`,
    `来源文件：${kb.sources.join(', ')}`,
    '',
    kb.text,
    '',
    getOutputFormatInstructions(urgency),
    '',
    _getModelSpecificInstructions(model),
  ].join('\n');

  return {
    systemPrompt,
    knowledgeSources: kb.sources,
    knowledgeVersion: kb.version,
  };
}

/**
 * 模型专项优化指令（私有辅助函数）
 *
 * @param {string} model
 * @returns {string}
 */
function _getModelSpecificInstructions(model) {
  const instructions = {
    hunyuan: [
      '【混元专项指令】',
      '请用专业但易懂的语气回答，避免过于学术化的表达。',
      '重要规则和截止日期请加粗或使用列表突出显示。',
      '直接输出四段式回答，不要输出开场白或模板说明。',
    ].join('\n'),

    deepseek: [
      '【DeepSeek 专项指令】',
      '严格按照四段式结构输出，不添加额外分析说明。',
      '引用知识库内容时保持准确，不做额外推断。',
      '仅输出回答正文，不要输出开场白。',
    ].join('\n'),

    doubao: [
      '【豆包专项指令】',
      '采用专业规范的语气，避免过于活泼的表达。',
      '必须输出完整的四段式结构，不得省略任何段落。',
      '每段标题使用【】格式（如【参考回答】），不使用其他格式。',
    ].join('\n'),

    glm: [
      '【GLM 专项指令】',
      '注重内容的逻辑严谨性，确保知识引用与回答逻辑一致。',
      '禁止自行推断知识库以外的法规内容。',
      '仅输出回答正文，不要添加任何元注释。',
    ].join('\n'),
  };

  return instructions[model] || instructions['hunyuan'];
}

// -----------------------------------------------------------------------
// 4. buildUserPrompt
// -----------------------------------------------------------------------

/**
 * 构建用户 prompt。
 *
 * 安全约束（Spec PII 处理章节）：
 *   - piiFields 中列出的字段（contact_name）必须从 input 中排除
 *   - question / question_context 须在调用前经 pii-filter 过滤（由 index.js 负责）
 *   - 此函数接收的 filteredQuestion / filteredContext 已经是过滤后的文本
 *
 * @param {object} input             - 用户输入数据（已通过 validators.js 校验）
 * @param {string} filteredQuestion  - pii-filter 过滤后的提问文本
 * @param {string|undefined} filteredContext - pii-filter 过滤后的背景文本（可选）
 * @param {string} detectedCategory  - 自动识别或用户指定的问题类别
 * @param {string[]} piiFields       - 需要排除的 PII 字段名列表（传递给排除逻辑）
 * @param {string} [modelType='hunyuan']
 * @returns {string}
 */
function buildUserPrompt(input, filteredQuestion, filteredContext, detectedCategory, piiFields, modelType) {
  const model = modelType || 'hunyuan';

  // 构建排除集合（防御性处理：始终排除 contact_name）
  const excludeSet = new Set([
    ...BUILTIN_PII_FIELDS,
    ...(Array.isArray(piiFields) ? piiFields : []),
  ]);

  // 提取机构上下文（排除 PII 字段）
  const contextFields = ['org_name', 'org_type', 'org_province', 'urgency'];
  const ctx = {};
  for (const key of contextFields) {
    if (!excludeSet.has(key) && input[key] !== undefined) {
      ctx[key] = input[key];
    }
  }

  // 机构信息段
  const orgTypeLabels = {
    foundation:          '基金会',
    social_group:        '社会团体',
    social_service_org:  '社会服务机构（原民办非企业单位）',
    other:               '其他类型组织',
  };

  const orgTypeLabel = orgTypeLabels[ctx.org_type] || ctx.org_type || '未知类型';
  const urgencyLabel = { high: '今日必须', medium: '本周内', low: '了解性质' };
  const urgencyText  = urgencyLabel[ctx.urgency] || '未指定';
  const categoryLabel = {
    policy_qa:  '政策法规',
    charity_law: '慈善法规',
    compliance: '合规操作',
    governance: '组织管理',
    finance:    '财务税务',
    hr:         '人力资源',
    other:      '综合/其他',
  };
  const catLabel = categoryLabel[detectedCategory] || '综合/其他';

  const orgContext = [
    `机构名称：${ctx.org_name || '[未填写]'}`,
    `机构类型：${orgTypeLabel}`,
    `注册省份：${ctx.org_province || '[未填写]'}`,
    `问题类别：${catLabel}`,
    `紧急程度：${urgencyText}`,
  ].join('\n');

  // 用户问题段
  const questionSection = [
    `提问：`,
    filteredQuestion,
  ].join('\n');

  // 补充背景段（可选）
  const contextSection = filteredContext && filteredContext.trim()
    ? `\n补充背景：\n${filteredContext}`
    : '';

  // DeepSeek/GLM 使用 XML 标签分隔（提升准确率）
  if (model === 'deepseek' || model === 'glm') {
    return [
      '<org_context>',
      orgContext,
      '</org_context>',
      '<question>',
      questionSection,
      contextSection,
      '</question>',
      '<instruction>',
      '请基于知识库内容，严格按照四段式结构（【问题理解】【参考回答】【建议行动】【重要提示】）回答上述问题。',
      '</instruction>',
    ].join('\n');
  }

  // 混元/豆包及其他模型：普通格式
  return [
    `【机构信息】`,
    orgContext,
    '',
    `【用户提问】`,
    questionSection,
    contextSection,
    '',
    '请基于知识库内容，严格按照四段式结构（【问题理解】【参考回答】【建议行动】【重要提示】）回答上述问题。',
  ].join('\n');
}

// -----------------------------------------------------------------------
// 5. buildPrompt（主入口）
// -----------------------------------------------------------------------

/**
 * 组装完整 prompt messages 数组（OpenAI-compatible messages 格式）。
 *
 * 流程：
 *   1. 自动识别问题类别（如未由用户指定）
 *   2. buildSystemPrompt → 构建系统 prompt（含知识库嵌入）
 *   3. buildUserPrompt → 构建用户 prompt（排除 PII）
 *   4. 返回 messages 数组 + 元数据
 *
 * 注意：filteredQuestion / filteredContext 须在调用前经 pii-filter 处理（由 index.js 负责）
 *
 * @param {object} input             - 用户输入（已通过 validators.js 校验）
 * @param {string} filteredQuestion  - pii-filter 过滤后的提问文本
 * @param {string|undefined} filteredContext - pii-filter 过滤后的背景文本（可选）
 * @param {string} [modelType='hunyuan'] - 目标模型
 * @param {string[]} [externalPiiFields=[]] - 额外需要排除的 PII 字段
 * @returns {{
 *   messages: Array<{ role: string, content: string }>,
 *   detectedCategory: string,
 *   knowledgeSources: string[],
 *   knowledgeVersion: string
 * }}
 */
function buildPrompt(input, filteredQuestion, filteredContext, modelType, externalPiiFields) {
  const model = modelType || 'hunyuan';

  // 确定问题类别：优先使用用户指定（question_category），否则自动分类
  const detectedCategory = (input.question_category && input.question_category !== 'other')
    ? input.question_category
    : knowledgeBase.classifyQuestion(filteredQuestion || '');

  const { systemPrompt, knowledgeSources, knowledgeVersion } = buildSystemPrompt(
    detectedCategory,
    input.urgency,
    model,
  );

  // 合并 PII 字段
  const piiFields = [
    ...BUILTIN_PII_FIELDS,
    ...(Array.isArray(externalPiiFields) ? externalPiiFields : []),
  ];

  const userPrompt = buildUserPrompt(
    input,
    filteredQuestion,
    filteredContext,
    detectedCategory,
    piiFields,
    model,
  );

  return {
    messages: [
      { role: 'system', content: systemPrompt },
      { role: 'user', content: userPrompt },
    ],
    detectedCategory,
    knowledgeSources,
    knowledgeVersion,
  };
}

// -----------------------------------------------------------------------
// 导出
// -----------------------------------------------------------------------

module.exports = {
  buildPrompt,
  buildSystemPrompt,
  buildUserPrompt,
  getAdvisorConstraints,
  getOutputFormatInstructions,
  BUILTIN_PII_FIELDS,
};


path: src/index.js
content: 'use strict';

// ---------------------------------------------------------------------------
// index.js — 管理顾问 Skill 主工作流入口
//
// 依赖：仅 Node.js 内置模块（通过共享包间接使用 fs）
// 合规依据：噗滋慈善管理顾问 Spec v1.0 + charity CLAUDE.md 合规红线
//   - PII 不入模型（contact_name 由 prompt-builder 排除；question/context 经 pii-filter）
//   - AI 标识声明强制注入（disclaimer-injector）
//   - 非法律意见声明：每条回答末尾强制附加（由 prompt 约束 + disclaimer-injector 双重保障）
//   - 禁止编造条文编号：system prompt 强制规则
//   - 模型不可用时降级：返回知识库原文 + 免责声明
//   - 审计日志不含 question 原文，仅含 question_hash
//   - 日志保留 ≥6 个月（由 storage-adapter 强制）
// ---------------------------------------------------------------------------

const { injectDisclaimer } = require('../../../packages/shared/disclaimer-injector');
const { filterPII }        = require('../../../packages/shared/pii-filter');
const { validate }         = require('./validators');
const promptBuilder        = require('./prompt-builder');
const knowledgeBase        = require('./knowledge-base');

// ---------------------------------------------------------------------------
// Skill 元数据
// ---------------------------------------------------------------------------

/** Skill ID — 用于 storage.appendHistory 的分区键 */
const SKILL_ID = 'management-advisor';

/** Skill 当前版本 */
const SKILL_VERSION = '1.0.0';

/** Skill 显示名称 */
const SKILL_NAME = '管理顾问';

// ---------------------------------------------------------------------------
// maxTokens 配置（问答型 Skill，所有类型统一 2000）
// ---------------------------------------------------------------------------

/**
 * 所有问题类型统一 maxTokens = 2000（Spec 第6节 Step 4）
 *
 * urgency 控制输出字数通过 system prompt 指令实现，
 * maxTokens 不按 urgency 变化（避免截断）
 *
 * @param {string} _questionCategory - 未使用，保留参数以便 v1.1 扩展
 * @returns {number}
 */
function getMaxTokens(_questionCategory) {
  return 2000;
}

// ---------------------------------------------------------------------------
// 内部工具函数
// ---------------------------------------------------------------------------

/**
 * 计算字符串的简单哈希值（用于 audit log 中的 question_hash）
 * 不使用加密哈希，只做日志去重标识
 *
 * @param {string} str
 * @returns {string} 十六进制哈希字符串
 */
function simpleHash(str) {
  if (typeof str !== 'string') return '0';
  let hash = 5381;
  for (let i = 0; i < str.length; i++) {
    // djb2 算法
    hash = ((hash << 5) + hash) + str.charCodeAt(i);
    hash = hash & hash; // 转为 32 位整数
  }
  return (hash >>> 0).toString(16);
}

/**
 * 构建降级时的知识库原文输出（模型不可用时的备用回答）
 *
 * 管理顾问降级策略：
 *   modelClient 未提供或调用失败时，返回知识库原文 + 免责声明。
 *   降级输出仍然有效，不视为错误（degraded=true）。
 *
 * @param {object} input       - 用户输入
 * @param {string} kbText      - 相关知识库文本
 * @param {string} category    - 已识别的问题类别
 * @returns {string}
 */
function buildDegradedOutput(input, kbText, category) {
  const categoryLabels = {
    policy_qa:  '政策法规',
    charity_law: '慈善法规',
    compliance: '合规操作',
    governance: '组织管理',
    finance:    '财务税务',
    hr:         '人力资源',
    other:      '综合/其他',
  };
  const catLabel = categoryLabels[category] || '综合/其他';

  const lines = [
    `# 管理顾问（知识库参考模式）`,
    ``,
    `> 当前为降级模式：AI 模型暂时不可用，以下为 **${catLabel}** 相关知识库原文，供参考使用。`,
    ``,
    `## 您的提问`,
    ``,
    `${input.question || '[问题未填写]'}`,
    ``,
    `## 相关知识库内容`,
    ``,
    kbText || '[知识库内容加载失败]',
    ``,
    `---`,
    ``,
    `**重要说明**：以上为知识库原文，未经 AI 个性化处理。如需针对您具体情况的解答，请在 AI 服务恢复后重新提问。`,
    ``,
    `本回答不构成法律意见，建议就具体情况咨询专业律师、会计师或主管部门。`,
  ];
  return lines.join('\n');
}

// ---------------------------------------------------------------------------
// PII 过滤辅助函数
// ---------------------------------------------------------------------------

/**
 * 对提问文本执行 pii-filter，返回过滤结果。
 * blocked=true 时调用方必须中止，不得继续调用模型 API。
 *
 * @param {string} questionText
 * @returns {{ filtered: string, blocked: boolean, detected: Array }}
 */
function _filterQuestionText(questionText) {
  if (typeof questionText !== 'string' || !questionText) {
    return { filtered: '', blocked: false, detected: [] };
  }
  try {
    return filterPII(questionText);
  } catch (err) {
    // pii-filter 内部异常 → 保守处理：阻断调用
    return { filtered: '', blocked: true, detected: [], _error: err.message };
  }
}

// ---------------------------------------------------------------------------
// 主工作流
// ---------------------------------------------------------------------------

/**
 * 主工作流入口
 *
 * 管理顾问特有步骤：
 *   1. 输入校验（validate）
 *   2. PII 过滤（pii-filter 处理 question + question_context）
 *      → blocked:true 时提示用户删除个人信息，终止调用
 *   3. 自动问题分类（classifyQuestion，如未显式指定 question_category）
 *   4. Prompt 构建（promptBuilder.buildPrompt）
 *   5. 模型调用（temperature 0.4，maxTokens 2000）
 *   6. 注入 AI 标识声明（injectDisclaimer）
 *   7. 写审计日志（含 question_hash，不含 question 原文）
 *   8. 返回结果
 *
 * 降级策略（管理顾问独有）：
 *   - modelClient 未提供 → 返回知识库原文 + 免责声明（degraded=true）
 *   - 模型调用失败 → 降级返回知识库原文 + 免责声明（degraded=true）
 *
 * @param {object} input   - 用户输入对象（含 org_name/org_type/org_province/question 等）
 * @param {object} options - 配置对象
 * @param {object} [options.modelClient]     - model-gateway ModelClient 实例（依赖注入，可选）
 * @param {object} [options.storage]         - storage-adapter 实例（依赖注入）
 * @param {string} [options.modelType='hunyuan'] - 模型类型字符串，传给 promptBuilder
 * @returns {Promise<{
 *   success: boolean,
 *   answer?: string,
 *   metadata?: {
 *     model: string|null,
 *     provider: string|null,
 *     degraded: boolean,
 *     duration_ms: number,
 *     version: string,
 *     detectedCategory: string,
 *     knowledgeSources: string[],
 *     knowledgeVersion: string
 *   },
 *   warnings?: string[],
 *   errors?: string[]
 * }>}
 */
async function handleQuery(input, options = {}) {
  const { modelClient, storage, modelType = 'hunyuan' } = options;

  // -------------------------------------------------------------------
  // Step 1: 输入校验
  // -------------------------------------------------------------------
  const validationResult = validate(input);

  if (!validationResult.valid) {
    return {
      success: false,
      errors:  validationResult.errors,
      warnings: validationResult.warnings,
    };
  }

  // 有 warnings → 记录但不阻断
  const warnings = validationResult.warnings.slice();

  const startTime = Date.now();

  // -------------------------------------------------------------------
  // Step 2: PII 过滤（question + question_context）
  // blocked:true → 提示用户删除个人信息，终止调用
  // -------------------------------------------------------------------
  const questionText  = (input.question || '').trim();
  const contextText   = (input.question_context || '').trim();

  const questionFilter = _filterQuestionText(questionText);
  if (questionFilter.blocked) {
    return {
      success: false,
      errors: [
        '您的问题中包含个人信息（如姓名/手机号），为保护当事人隐私，请将姓名替换为职位描述' +
        '（如"项目负责人""受益人A"），删除手机号等识别信息后重新提问。',
      ],
      warnings,
    };
  }

  let filteredContext;
  if (contextText) {
    const contextFilter = _filterQuestionText(contextText);
    if (contextFilter.blocked) {
      return {
        success: false,
        errors: [
          '您填写的问题背景（question_context）中包含个人信息，请删除后重新提问。',
        ],
        warnings,
      };
    }
    filteredContext = contextFilter.filtered;
  }

  const filteredQuestion = questionFilter.filtered;
  const questionHash = simpleHash(questionText);

  // -------------------------------------------------------------------
  // Step 3: 自动分类（供降级路径和日志使用）
  // -------------------------------------------------------------------
  const detectedCategory = (input.question_category && input.question_category !== 'other')
    ? input.question_category
    : knowledgeBase.classifyQuestion(filteredQuestion);

  // -------------------------------------------------------------------
  // 降级判断：modelClient 未提供时直接返回知识库原文
  // -------------------------------------------------------------------
  if (!modelClient || typeof modelClient.chat !== 'function') {
    const durationMs = Date.now() - startTime;
    const kbResult   = knowledgeBase.parseKnowledgeBase(detectedCategory);
    const degradedContent = buildDegradedOutput(input, kbResult.text, detectedCategory);

    const { content: answerWithDisclaimer } = injectDisclaimer(
      degradedContent,
      {
        format:    'markdown',
        skillName: SKILL_NAME,
        modelName: '',
      },
    );

    await _safeAppendAuditLog(storage, {
      event:             'query_completed',
      org_name:          input.org_name,
      question_category: detectedCategory,
      question_hash:     questionHash,
      model_used:        null,
      provider:          null,
      degraded:          true,
      success:           true,
      duration_ms:       durationMs,
    });

    await _safeAppendHistory(storage, SKILL_ID, {
      org_name:          input.org_name,
      question_category: detectedCategory,
      timestamp:         new Date().toISOString(),
      model_used:        null,
      provider:          null,
      degraded:          true,
      success:           true,
      duration_ms:       durationMs,
    });

    return {
      success: true,
      answer:  answerWithDisclaimer,
      metadata: {
        model:             null,
        provider:          null,
        degraded:          true,
        duration_ms:       durationMs,
        version:           SKILL_VERSION,
        detectedCategory,
        knowledgeSources:  kbResult.sources,
        knowledgeVersion:  kbResult.version,
      },
      warnings,
    };
  }

  // -------------------------------------------------------------------
  // Step 4: Prompt 构建
  // -------------------------------------------------------------------
  try {
    const { messages, knowledgeSources, knowledgeVersion } = promptBuilder.buildPrompt(
      input,
      filteredQuestion,
      filteredContext,
      modelType,
      validationResult.piiFields,
    );

    // -------------------------------------------------------------------
    // Step 5: 模型调用（temperature 0.4，maxTokens 2000）
    // -------------------------------------------------------------------
    const maxTokens = getMaxTokens(detectedCategory);

    let chatResult;
    let degraded = false;

    try {
      chatResult = await modelClient.chat(messages, {
        temperature: 0.4,
        maxTokens,
      });
    } catch (modelErr) {
      // 模型调用失败 → 降级：返回知识库原文
      degraded = true;
      const durationMs = Date.now() - startTime;
      const kbResult   = knowledgeBase.parseKnowledgeBase(detectedCategory);
      const degradedContent = buildDegradedOutput(input, kbResult.text, detectedCategory);

      const { content: answerWithDisclaimer } = injectDisclaimer(
        degradedContent,
        {
          format:    'markdown',
          skillName: SKILL_NAME,
          modelName: '',
        },
      );

      await _safeAppendAuditLog(storage, {
        event:             'query_completed',
        org_name:          input.org_name,
        question_category: detectedCategory,
        question_hash:     questionHash,
        model_used:        null,
        provider:          null,
        degraded:          true,
        success:           true,
        duration_ms:       durationMs,
        degraded_reason:   modelErr.message,
      });

      await _safeAppendHistory(storage, SKILL_ID, {
        org_name:          input.org_name,
        question_category: detectedCategory,
        timestamp:         new Date().toISOString(),
        model_used:        null,
        provider:          null,
        degraded:          true,
        success:           true,
        duration_ms:       durationMs,
      });

      return {
        success: true,
        answer:  answerWithDisclaimer,
        metadata: {
          model:             null,
          provider:          null,
          degraded:          true,
          duration_ms:       durationMs,
          version:           SKILL_VERSION,
          detectedCategory,
          knowledgeSources:  kbResult.sources,
          knowledgeVersion:  kbResult.version,
        },
        warnings: [...warnings, `模型调用失败，已降级输出知识库原文：${modelErr.message}`],
      };
    }

    const durationMs = Date.now() - startTime;

    // -------------------------------------------------------------------
    // Step 6: 注入 AI 标识声明 + 免责提示（合规红线 P0）
    // -------------------------------------------------------------------
    const { content: answerWithDisclaimer } = injectDisclaimer(
      chatResult.content,
      {
        format:    'markdown',
        skillName: SKILL_NAME,
        modelName: chatResult.provider || chatResult.model,
      },
    );

    // -------------------------------------------------------------------
    // Step 7: 写入审计日志（不含 question 原文，仅含 question_hash）
    // -------------------------------------------------------------------
    await _safeAppendAuditLog(storage, {
      event:             'query_completed',
      org_name:          input.org_name,
      question_category: detectedCategory,
      question_hash:     questionHash,
      model_used:        chatResult.model,
      provider:          chatResult.provider,
      degraded:          chatResult.degraded || false,
      success:           true,
      duration_ms:       durationMs,
    });

    await _safeAppendHistory(storage, SKILL_ID, {
      org_name:          input.org_name,
      question_category: detectedCategory,
      timestamp:         new Date().toISOString(),
      model_used:        chatResult.model,
      provider:          chatResult.provider,
      degraded:          chatResult.degraded || false,
      success:           true,
      duration_ms:       durationMs,
    });

    // -------------------------------------------------------------------
    // Step 8: 返回结果
    // -------------------------------------------------------------------
    return {
      success: true,
      answer:  answerWithDisclaimer,
      metadata: {
        model:             chatResult.model,
        provider:          chatResult.provider,
        degraded:          chatResult.degraded || degraded,
        duration_ms:       durationMs,
        version:           SKILL_VERSION,
        detectedCategory,
        knowledgeSources,
        knowledgeVersion,
      },
      warnings,
    };

  } catch (err) {
    const durationMs = Date.now() - startTime;

    // 非模型调用的其他错误（如 prompt-builder 内部错误）— 写审计日志后返回失败
    await _safeAppendAuditLog(storage, {
      event:             'query_completed',
      org_name:          input.org_name,
      question_category: detectedCategory,
      question_hash:     questionHash,
      model_used:        null,
      success:           false,
      duration_ms:       durationMs,
      error_type:        err.name || 'Error',
    });

    return {
      success: false,
      errors:  [err.message],
      warnings,
    };
  }
}

// ---------------------------------------------------------------------------
// 内部辅助（日志写入容错）
// ---------------------------------------------------------------------------

/**
 * 安全写入审计日志（失败时 console.error，不抛出到主流程）
 *
 * @param {object|undefined} storage
 * @param {object} entry
 */
async function _safeAppendAuditLog(storage, entry) {
  if (!storage || typeof storage.appendAuditLog !== 'function') return;
  try {
    await Promise.resolve(storage.appendAuditLog(entry));
  } catch (err) {
    console.error('[management-advisor] 审计日志写入失败（非阻断）:', err.message);
  }
}

/**
 * 安全写入历史记录（失败时 console.error，不抛出到主流程）
 *
 * @param {object|undefined} storage
 * @param {string} skillId
 * @param {object} entry
 */
async function _safeAppendHistory(storage, skillId, entry) {
  if (!storage || typeof storage.appendHistory !== 'function') return;
  try {
    await Promise.resolve(storage.appendHistory(skillId, entry));
  } catch (err) {
    console.error('[management-advisor] 历史记录写入失败（非阻断）:', err.message);
  }
}

// ---------------------------------------------------------------------------
// 导出
// ---------------------------------------------------------------------------

module.exports = {
  handleQuery,
  getMaxTokens,
  simpleHash,
};


path: templates/knowledge-compliance-faq.md
content: ---
version: v20260330
source_url: https://www.mca.gov.cn
verified_date: 2026-03-30
segment: compliance_ops_faq
---

# 合规操作与常见问题知识库（Segment B + D）

> 知识库版本：v20260330
> 覆盖范围：年检流程、信息公开操作、章程变更程序、资助平台时间节点（Segment B）；劳动用工、税务合规、审计要求、信息安全（Segment D）
> 本段知识库覆盖范围：合规操作指引和高频 FAQ，操作细节有地方差异，建议向当地民政局或税务机关确认。

---

## B1. 社会组织年度检查（年检）

**B1.1 年检时间窗口**
通常为每年1月1日至4月30日，各省市民政局可能有不同截止日期（[以您所在地民政局通知为准，通常在4月底前，建议提前确认]）。

**B1.2 年检提交材料（通用参考，各地要求可能不同）**
1. 年度检查报告书（登记管理机关提供格式）
2. 上年度工作报告
3. 上年度财务会计报告（资产负债表、收支情况表等）
4. 资产变动及使用情况报告
5. 人员变动情况报告
6. 诚信承诺书（法定代表人签署）
7. 其他民政局要求的补充材料

**B1.3 年检提交方式**
多数地区已实现网上提交（通过中国社会组织公众服务平台 chinanpo.mca.gov.cn 或省级平台）；部分地区仍需线下提交或两者结合。建议提前登录所在省份民政局官网或平台确认流程。

**B1.4 年检逾期处理**
逾期未参加年检或年检不合格，登记管理机关可责令限期整改；情节严重的可列入经营异常名录或吊销登记证书。

---

## B2. 慈善组织年度报告

**B2.1 提交截止日期**
每年3月31日前（《慈善法》第72条）。

**B2.2 提交平台**
慈善中国（jz.mca.gov.cn）。

**B2.3 年度报告内容要求**
- 机构基本情况和组织架构
- 本年度慈善活动开展情况
- 财务收支情况（含捐款来源、支出用途）
- 受益人基本情况（聚合统计，不含个人信息）
- 主要资产状况变动
- 内部治理情况

**B2.4 年度报告 vs 年检区别**
- 年度报告：在慈善信息平台向社会公开（面向社会监督）
- 年检：向登记管理机关提交（面向监管审查）
- 两者是独立程序，须分别完成

---

## B3. 信息公开操作

**B3.1 必须公开的信息类别**
1. 本组织基本信息（名称、住所、法定代表人等）
2. 年度工作报告
3. 财务会计报告
4. 公开募捐信息（如有公募资格）
5. 重大资产变动情况
6. 关联交易情况

**B3.2 公开平台**
主要平台：慈善中国（jz.mca.gov.cn）；各省市民政局可能有补充要求。

**B3.3 公开时限**
- 年度报告：每年3月31日前
- 公募活动结束后：活动结束3个月内公示结果（建议核查最新规定）
- 重大事项：发生后及时公开

**B3.4 腾讯公益年度项目报告**
腾讯公益平台的资助项目须按平台要求提交结项/年度报告。通常截止日期为每年4月15日（待种子NGO验证，建议以腾讯公益平台最新通知为准）。

---

## B4. 章程变更程序

**B4.1 触发章程变更的常见情形**
- 机构名称变更
- 住所迁移（跨区域）
- 宗旨或业务范围变更
- 法定代表人变更
- 理事会/监事会成员大规模变动
- 内部规则的重大修订

**B4.2 章程修订流程（通用参考）**
1. 起草修订草案
2. 召开理事会/会员大会，按章程规定程序审议通过（通常需2/3以上多数同意）
3. 向登记管理机关提交章程变更申请（提交材料：修订前后对照表、会议记录、决议、新章程草案等）
4. 登记机关审批（一般30个工作日内）
5. 审批通过后方可执行新章程

**B4.3 注意事项**
- 章程变更须经登记机关批准后方生效，不可自行修改后直接执行
- 业务主管单位（如有）可能须先审批
- 各地要求有差异，建议提前向当地民政局确认材料清单

---

## B5. 法定代表人变更程序

**B5.1 材料清单（通用参考）**
1. 变更登记申请书
2. 主管单位（如有）批准文件
3. 理事会决议（同意变更法定代表人的决议）
4. 新法定代表人身份证明和相关资质证明
5. 原法定代表人离任说明

**B5.2 注意事项**
法定代表人变更属重大事项，须在完成登记机关变更手续后，同步更新银行账户预留信息、公章管理等。

---

## D1. 劳动用工常见问题

**D1.1 NGO 适用劳动法吗？**
是的。NGO（社会组织）作为法人实体，与员工签订劳动合同须遵守《劳动法》和《劳动合同法》，包括：合同签订时限（用工之日起1个月内书面签署）、试用期上限（以合同期限为准，通常≤6个月）、社保缴纳义务等。

**D1.2 志愿者与员工的法律区分**
- 员工：与机构存在劳动关系，须签劳动合同，机构须缴纳社保
- 志愿者：无劳动关系，不得收取固定报酬（偶发性交通餐饮补贴可行；超过标准的"劳务报酬"可能被认定为劳动关系或劳务关系）
- 实习生：在校学生实习，通常不形成劳动关系，但需签实习协议

**D1.3 社保缴纳要求**
机构与员工建立劳动关系后，须为员工缴纳"五险一金"（养老、医疗、失业、工伤、生育保险及住房公积金）。部分地区允许双方协议不缴公积金（需符合当地规定）。未依法缴纳社保的，员工可要求补缴，机构可能面临罚款。

**D1.4 劳动合同签订时限**
建立劳动关系之日起1个月内必须签订书面劳动合同（《劳动合同法》第10条）。超过1个月未签，须支付双倍工资（第82条）。

---

## D2. 税务合规常见问题

**D2.1 NGO 需要税务登记吗？**
是的。社会组织取得登记证书后，须向税务机关办理税务登记（现为"多证合一"，可在民政登记时一并办理）。

**D2.2 公益性捐赠税前扣除资格（PD 资格）**
PD 资格（Public Donation）是指捐赠方可凭捐赠收据在计算企业所得税或个人所得税时享受扣除优惠。取得条件：须经财政部门认定，通常要求机构已登记满3年、年度公益支出达到一定比例等（[请以当期税法和财政部门通知为准]）。

**D2.3 志愿者劳务报酬个人所得税**
- 若向志愿者支付的报酬属于"劳务报酬所得"（非雇佣关系下的劳务服务）：机构须代扣代缴个人所得税，税率参照劳务报酬所得税率
- 若属于合理的交通、餐饮等补贴：通常不视为所得，不需缴税
- 判断标准有一定模糊性，建议就具体情形咨询当地税务机关或税务师

**D2.4 员工工资个人所得税**
NGO 员工工资按"综合所得"计算个人所得税，机构作为扣缴义务人须依法预扣预缴（[请以当期个人所得税法及税务机关规定为准]）。

**D2.5 增值税免税**
非营利组织从事非营利性服务，可申请增值税免税（具体条件见财税相关规定，建议咨询税务师或当地税务机关，各地执行可能有差异）。

**D2.6 企业所得税免税**
取得免税资格的非营利组织，其符合条件的收入免征企业所得税（具体条件见《企业所得税法》及实施条例，须经税务机关认定）。草根 NGO 通常需在机构相对稳定后才申请，建议咨询税务师。

---

## D3. 审计要求

**D3.1 强制审计触发条件**
以下情形通常须进行外部审计：
- 基金会年度收入≥1000万元（《基金会管理条例》，[请核实最新规定]）
- 民政部或省级民政厅直接登记的机构（通常要求）
- 资助方合同要求（腾讯公益/壹基金/南都基金会等资助方通常要求结项审计）
- 章程规定须审计的情形

**D3.2 审计机构资质要求**
须由具有相应资质的注册会计师事务所承接，审计报告需加盖会计师事务所公章及签字注册会计师执业印章。

**D3.3 年检中的审计材料**
年检提交的财务报告是否须附审计报告，由各地民政局规定。部分地区要求收入超过一定标准（如50万元）须附审计报告（建议向当地民政局确认）。

---

## D4. 信息安全与数据保护

**D4.1 PIPL 对 NGO 的适用**
《个人信息保护法》（PIPL）对所有处理个人信息的主体适用，包括 NGO。处理受益人、员工、志愿者等个人信息须合法、正当、必要，并应告知当事人和取得同意。

**D4.2 受益人信息采集注意事项**
- 只采集必要信息，不过度采集
- 采集前告知受益人信息用途
- 未成年人信息须特别保护（14周岁以下儿童须取得监护人同意）
- 不得将受益人个人信息向第三方披露（含媒体采访时）

**D4.3 数据安全实践建议**
- 受益人档案妥善保管，非必要不电子化传输
- 内部项目文件设置访问权限
- 旧设备报废前彻底清除数据

---

## B/D 知识库覆盖范围说明

本段（compliance_ops 合规操作 + faq 常见问题）主要覆盖：年检年报操作流程、信息公开平台操作、章程变更程序、劳动用工基础、税务合规原则、审计要求触发条件、数据保护要点。

**不在覆盖范围内：**
- 具体税务申报操作和计算（建议咨询注册税务师或当地税务机关）
- 各地民政局的差异化年检要求（建议向当地民政局确认）
- 特定资助方的非公开协议条款（无法解读第三方合同文件）
- 劳动争议处理（一旦涉及纠纷，建议咨询劳动仲裁机构或律师）

**数字/期限提示：**
税率、起征点、罚款金额等数字变动频繁，本知识库不提供具体数字，使用时请以当期法规和税务机关通知为准。


path: templates/knowledge-charity-law.md
content: ---
version: v20260330
source_url: https://www.mca.gov.cn
verified_date: 2026-03-30
segment: policy_qa
---

# 慈善法核心条文摘要（政策法规知识库 Segment A）

> 知识库版本：v20260330
> 依据法规：《中华人民共和国慈善法》（2023年修订版，2023年9月5日起施行）
> 覆盖范围：信息公开、年检与年报、慈善组织认定与登记、公开募捐资格、法律责任
> 本段知识库覆盖范围：中国慈善组织相关政策法规的原则性内容，不含各地民政局的实施细则。

---

## A1. 慈善组织认定与登记（慈善法第9-14条）

**A1.1 慈善组织定义**
依法成立、符合慈善法规定，以面向社会开展慈善活动为宗旨的非营利组织。包括基金会、社会团体、社会服务机构等（《慈善法》第9条）。

**A1.2 认定要求**
申请认定为慈善组织，须满足：(1) 以开展慈善活动为宗旨，且慈善活动支出占本组织年度总支出的比例符合规定；(2) 不以营利为目的，收益和剩余财产用于慈善目的；(3) 有符合要求的章程；(4) 有必要的场所、人员和财产；(5) 登记机关依法登记（《慈善法》第9-10条）。

**A1.3 社会服务机构（原民办非企业单位）**
2022年3月17日起，"民办非企业单位"正式更名为"社会服务机构"（《社会服务机构登记管理条例》施行）。现有"民非"证书在有效期内继续有效，换证按各地民政局通知执行。

**A1.4 三类主体区别**
- 基金会：以财产为运作基础，开展慈善活动；须向省级以上民政部门登记，注册资金门槛较高（全国性≥800万元，地方性≥200万元）
- 社会团体（社团）：以会员为基础，须有一定数量的发起人和会员（通常≥50人）
- 社会服务机构：以服务为核心，注册门槛相对低，草根 NGO 最常见的主体形式

**A1.5 地方差异提示**
各类主体的具体登记要求和门槛由省级民政部门细化，建议向当地民政局确认最新要求。

---

## A2. 公开募捐资格（慈善法第22-26条）

**A2.1 公开募捐定义**
面向社会公众开展募捐活动，即为公开募捐。向特定对象（如会员、特定机构）募集不属于公开募捐。

**A2.2 公募资格取得条件**
须依法取得公开募捐资格证书，由民政部门颁发。基础条件：已登记满2年、无违法记录、有健全的内部治理结构（《慈善法》第22条）。多数草根 NGO 不具备公募资格。

**A2.3 无公募资格组织的募款方式**
无公募资格组织不得自行发起公开募捐，但可与有公募资格的慈善组织合作开展公开募捐活动，合作须签订协议（《慈善法》第26条）。腾讯公益、壹基金等平台上的项目通常需要联合有公募资格的基金会。

**A2.4 网络募捐平台**
公开募捐须通过民政部指定的互联网募捐信息平台开展（如腾讯公益、支付宝公益、京东公益等），不得在未指定平台上发起（《慈善法》第23条，《慈善组织互联网公开募捐信息平台管理办法》）。

---

## A3. 信息公开义务（慈善法第69-79条）

**A3.1 信息公开总体要求**
慈善组织依法公开慈善信息，接受社会监督（《慈善法》第69条）。信息公开遵循"真实、完整、及时"原则，不得有虚假记载、误导性陈述或重大遗漏。

**A3.2 年度报告公开时限**
慈善组织须于每年3月31日前，在慈善信息平台向社会公开上年度工作报告（《慈善法》第72条）。报告内容包括：财务收支情况、慈善项目开展情况、受益人基本情况等。

**A3.3 募捐活动信息公开（公募专项）**
公开募捐活动结束后须及时公告，公示内容包括捐款数额、使用情况等。大额捐款（单笔≥50万元，具体标准见《慈善组织信息公开管理办法》第X条）须单独公示（建议核查最新规定）。

**A3.4 信息公开平台**
- 慈善中国（jz.mca.gov.cn）：民政部官方平台，所有慈善组织须在此公开信息
- 中国社会组织公众服务平台（chinanpo.mca.gov.cn）：查询社会组织登记信息
- 各省级民政局平台：部分地区有本地化要求

**A3.5 重大事项披露**
重大资产变动、主要负责人变更、接受境外捐赠等重大事项须及时向登记管理机关报告并向社会公开（《慈善法》第74条）。

---

## A4. 年度检查（年检）制度

**A4.1 年检基本要求**
社会组织须每年向登记管理机关提交年度检查报告（年检）。年检时间窗口通常为每年1月1日至4月30日，各地可能有差异（建议向当地民政局确认截止日期）。

**A4.2 年检材料（通用清单，各地可能不同）**
- 年度检查报告书（按民政部门提供的格式填写）
- 上年度工作报告（含财务收支情况）
- 上年度经审计的财务会计报告（部分情况须审计）
- 法定代表人签署的诚信承诺书
- 其他登记机关要求的材料

**A4.3 慈善组织年度报告 vs 社会组织年检**
慈善组织（已取得慈善组织认定资格）须同时完成：(1) 向慈善信息平台提交年度报告（3月31日前）；(2) 向登记管理机关提交年度检查报告（各地截止日期不同）。两者是不同程序。

---

## A5. 慈善组织治理要求（慈善法第12-14条）

**A5.1 章程要求**
章程须明确：名称、住所、宗旨、业务范围、内部治理结构、财务管理制度等（《慈善法》第12条）。章程是最重要的内部管理文件，内部治理首先按章程执行。

**A5.2 理事会基本要求**
- 理事会是慈善组织的决策机构（或执行机构）
- 理事人数下限：基金会≥5人；社会团体理事≥15人（《社会团体登记管理条例》第16条）；社会服务机构无全国统一下限，以章程和登记机关要求为准
- 理事应定期召开会议，基金会每年至少2次（《基金会管理条例》），其他类型以章程为准

**A5.3 禁止关联交易（慈善法第15条）**
慈善组织的发起人、主要捐赠人及慈善组织管理人员，不得利用关联关系损害慈善组织利益。慈善组织不得向上述人员提供与其履职不相当的报酬及利益。关联交易须经理事会决议，且相关关联方须回避表决。

**A5.4 盈余与分配限制**
慈善组织的财产只能用于章程规定的慈善活动，不得向发起人、捐赠人、志愿者等相关人员分配（《慈善法》第52条）。

---

## A6. 法律责任（慈善法第106-115条）

**A6.1 信息公开违规处罚**
未依法公开慈善信息：警告，责令限期改正；逾期不改正的，处1万元以上10万元以下罚款；情节严重的，由登记管理机关撤销登记（《慈善法》第111条，建议核查最新条文）。

**A6.2 非法募款处罚**
未经许可擅自开展公开募捐：没收违法所得，罚款（具体标准见《慈善法》第106条，建议核查最新条文）。

**A6.3 财务违规处罚**
将慈善财产用于非慈善目的，或向关联人员提供不当利益：限期追缴，并处追缴金额1-3倍罚款；情节严重的撤销登记（《慈善法》第108条，建议核查最新条文）。

**A6.4 注意事项**
具体处罚金额和情节认定标准以最新有效法规条文为准。建议就具体违规场景向主管民政局或专业律师确认。

---

## A7. 知识库覆盖范围说明

本段（policy_qa）主要覆盖：中国慈善法（2023修订版）、社会组织登记管理条例、公开募捐相关规定、信息公开管理办法的原则性内容。

**不在覆盖范围内：**
- 境外非政府组织境内活动管理（《境外非政府组织境内活动管理法》）
- 各省市民政局的实施细则（建议向当地民政局确认）
- 具体税务优惠的认定程序（建议咨询税务师）
- 慈善信托相关规定

**法规更新提示：**
慈善法2023修订版于2023年9月5日起施行，部分配套办法可能尚在修订中。引用具体条文时建议在中国政府网或民政部官网核查当前有效版本。


path: templates/knowledge-governance.md
content: ---
version: v20260330
source_url: https://www.mca.gov.cn
verified_date: 2026-03-30
segment: governance
---

# 治理管理知识库（Segment C）

> 知识库版本：v20260330
> 覆盖范围：理事会/监事会法定要求、财务内控基础、人力资源管理基础、档案管理、利益冲突回避
> 本段知识库覆盖范围：中国社会组织内部治理规范，具体要求以机构章程和登记机关要求为准。

---

## C1. 理事会法定要求

**C1.1 理事人数下限**
- 基金会：理事人数≥5人（《基金会管理条例》第14条）
- 社会团体（社团）：理事会理事≥15人（《社会团体登记管理条例》第16条）
- 社会服务机构（原民办非企业）：无全国统一下限，以章程和登记机关要求为准（通常≥3人）

**C1.2 理事构成要求**
- 担任同一社会组织负责人不得超过两届（基金会，《基金会管理条例》第19条）
- 基金会理事中，具有利益关联关系的理事不得超过理事总数的1/3（《基金会管理条例》第15条）
- 法定代表人与监事不得由同一人担任

**C1.3 理事任期**
以章程规定为准，通常3-5年；届满须按章程规定程序续任或改选。

---

## C2. 理事会会议与决议

**C2.1 开会频次要求**
- 基金会：理事会每年至少召开2次会议（《基金会管理条例》第23条）
- 社会团体：以章程为准，无全国统一要求
- 社会服务机构：以章程为准

**C2.2 会议法定人数（有效决议的前提）**
多数章程规定：须有2/3以上（含2/3）的理事出席方可召开理事会，且表决须经出席理事2/3以上同意。**以章程具体规定为准**，若章程未明确则参照上述惯例。

**C2.3 书面决议的合法性**
部分紧急事项可通过书面传阅方式形成决议（需章程明确允许）。书面决议须有所有理事的书面确认，并留存原件。

**C2.4 会议记录要求**
理事会会议须形成书面会议记录，内容包括：出席人员名单、讨论事项、表决结果、签到表。会议记录须妥善保存（建议永久保存，至少保存至机构注销后5年）。

**C2.5 常见问题：理事会人数不够怎么办？**
若出席人数未达章程规定的法定人数，该次会议不具有法律效力，不能做出有效决议。应延期召开并确保足够数量的理事出席。若多次难以凑足法定人数，可考虑修订章程调整法定人数要求（须经合法程序）。

---

## C3. 监事会/监事设置

**C3.1 基本要求**
- 基金会：须设监事会，监事人数≥3人；监事不得兼任理事（《基金会管理条例》第26条）
- 社会团体：通常须设监事会或监事
- 社会服务机构：建议设监事，具体要求以章程和登记机关要求为准

**C3.2 监事职责**
监督理事会及工作人员履行职责情况、财务状况、是否存在违规行为。监事有权列席理事会会议，查阅财务账目。

**C3.3 监事回避原则**
监事不得与其监督的事项存在利益关联。担任财务的工作人员不得兼任监事。

---

## C4. 财务内控基础

**C4.1 印章管理**
强烈建议将以下印章分开由不同人员保管：
- 法人公章（对外代表机构）
- 财务专用章（用于财务凭证和对外财务文件）
- 合同专用章（用于合同签署）
- 法定代表人名章

法人章和财务章由同一人保管，是内控缺失的重大风险（如发生挪用公款，难以追责）。

**C4.2 会计与出纳分离原则**
出纳人员（管钱）和会计人员（记账）须由不同人员担任，相互制衡。出纳不得兼任稽核、会计档案保管及收入、支出、费用、债权债务账目的登记工作（《会计法》第40条）。小型机构若确实无法配置独立人员，可考虑理事或监事对账目进行定期抽查。

**C4.3 授权审批制度**
建议建立书面的费用报销和资金支付授权制度：
- 设置不同额度的审批权限（如1000元以下主任签批，1万元以上须理事长审批）
- 所有支出须有票据和凭证
- 大额支出须提前经理事会或授权人批准

**C4.4 银行账户管理**
建议设立专用银行账户（项目专款专用），便于区分不同资金来源。定期（至少每月）与银行流水对账，发现差异及时查明原因。

**C4.5 项目资金专款专用**
资助方资金须严格按照资助协议约定用途使用，不得挪用于其他项目或机构日常运营。超支或节余须按资助方要求处理（通常须提前报告资助方）。

---

## C5. 人力资源管理基础

**C5.1 劳动合同管理**
- 与全职员工签订书面劳动合同（用工1个月内）
- 合同须明确：工作内容、报酬、合同期限、社保缴纳等
- 合同期满续签须在合同到期前协商确定
- 劳动合同须至少保存至劳动关系终止后2年

**C5.2 员工手册/规章制度**
建议制定员工手册，明确：工作时间、假期制度、费用报销规定、保密义务、违规处理程序等。员工手册须经职工（代表）大会讨论（若有），并公示给员工。规章制度是劳动争议处理的重要依据。

**C5.3 试用期规定**
- 劳动合同期限3个月以上不满1年：试用期≤1个月
- 劳动合同期限1年以上不满3年：试用期≤2个月
- 劳动合同期限3年以上或无固定期限：试用期≤6个月
- 同一用人单位对同一劳动者只能约定一次试用期（《劳动合同法》第19条）

**C5.4 员工离职手续**
员工离职须完成：工作交接记录、公物归还、账号权限注销、财务结算。劳动合同解除或终止的，须出具离职证明（《劳动合同法》第50条）。

---

## C6. 档案管理

**C6.1 项目档案保存期限建议**
- 项目档案（合同、报告、评估资料）：建议保存至少10年（部分资助方有具体要求）
- 财务凭证：保存15年（《会计档案管理办法》）
- 会计账簿：保存15年
- 财务报告：永久保存

**C6.2 会议记录保存**
理事会、监事会会议记录，以及会员大会记录：建议永久保存（机构运营期间全部保留）。会议记录是证明机构合法运营的重要文件，在年检、审计、资助申请时可能被查阅。

**C6.3 档案数字化管理**
建议对重要档案进行扫描电子化备份，同时保留纸质原件。电子档案须做好防丢失措施（定期备份至云端或多处存储）。

---

## C7. 利益冲突回避

**C7.1 理事回避制度**
当议题涉及理事本人或其关联方的利益时，该理事须回避表决。回避须在会议记录中注明。

**C7.2 薪酬决策程序**
向有薪理事或主要管理人员支付报酬的决议，须避免利益相关方参与表决。通常做法：由监事会审查薪酬方案，理事会表决时利益相关理事回避。

**C7.3 关联采购透明化**
机构向理事或主要捐赠人控制的企业采购货物或服务，须经理事会批准，相关方回避，并在信息公开文件中披露。

**C7.4 兼职与竞业限制**
主要管理人员（法定代表人、秘书长等）在担任本职期间，通常不得在与本机构存在竞争或利益冲突的其他机构担任职务，具体以章程和聘用合同约定为准。

---

## C8. 知识库覆盖范围说明

本段（governance 组织治理）主要覆盖：理事会/监事会法定要求和实操、财务内控基础框架、人力资源管理基础规范、档案管理要点、利益冲突回避原则。

**不在覆盖范围内：**
- 各地民政局对治理结构的差异化要求（建议向当地民政局确认）
- 具体会计准则和财务报表编制（建议咨询注册会计师）
- 复杂的公司治理结构（含多层架构的基金会）
- 劳动纠纷的仲裁和诉讼处理（建议咨询律师）

**参考来源：**
- 《基金会管理条例》（2004年，待关注修订动态）
- 《社会团体登记管理条例》（2016年修订）
- 《社会服务机构登记管理条例》（2022年施行）
- 《劳动合同法》（2007年，2012年修订）
- 《会计法》（2024年修订，2025年7月1日施行）
- 《会计档案管理办法》（财政部、国家档案局，2015年）


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)